From 7d195f9e77344c6d2550b8ba33ac877e4147c4c5 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 7 Oct 2023 11:22:49 +0100 Subject: [PATCH 01/16] kas-core: remove BitOrAssign impls for EventState and contexts; rename send_action -> action and require id parameter --- crates/kas-core/src/action.rs | 15 +++--- crates/kas-core/src/decorations.rs | 2 +- crates/kas-core/src/event/components.rs | 64 ++++++++++++------------ crates/kas-core/src/event/cx/config.rs | 9 +--- crates/kas-core/src/event/cx/cx_pub.rs | 54 +++++++++----------- crates/kas-core/src/event/cx/cx_shell.rs | 10 ++-- crates/kas-core/src/event/cx/mod.rs | 12 ++--- crates/kas-core/src/event/cx/press.rs | 10 ++-- crates/kas-core/src/root.rs | 6 +-- crates/kas-core/src/shell/window.rs | 6 +-- crates/kas-core/src/theme/draw.rs | 9 +--- 11 files changed, 87 insertions(+), 110 deletions(-) diff --git a/crates/kas-core/src/action.rs b/crates/kas-core/src/action.rs index c810b624c..91f937333 100644 --- a/crates/kas-core/src/action.rs +++ b/crates/kas-core/src/action.rs @@ -8,17 +8,14 @@ bitflags! { /// Action required after processing /// - /// This type is returned by many widgets on modification to self and is tracked - /// internally by [`event::EventCx`] to determine which updates are needed to - /// the UI. + /// Some methods operate directly on a context ([`ConfigCx`] or [`EventCx`]) + /// while others don't reqiure a context but do require that some *action* + /// is performed afterwards. This enum is used to convey that action. /// - /// Two `Action` values may be combined via bit-or (`a | b`). Bit-or - /// assignments are supported by both `Action` and [`event::EventCx`]. + /// An `Action` should be passed to a context: `cx.action(self.id(), action)` + /// (assuming `self` is a widget). /// - /// Users receiving a value of this type from a widget update method should - /// usually handle with `*cx |= action;`. Before the event loop starts - /// (`toolkit.run()`) or if the widget in question is not part of a UI these - /// values can be ignored. + /// Two `Action` values may be combined via bit-or (`a | b`). #[must_use] #[derive(Copy, Clone, Debug, Default)] pub struct Action: u32 { diff --git a/crates/kas-core/src/decorations.rs b/crates/kas-core/src/decorations.rs index ab7e805ec..8966c8cfa 100644 --- a/crates/kas-core/src/decorations.rs +++ b/crates/kas-core/src/decorations.rs @@ -286,7 +286,7 @@ impl_scope! { w.set_maximized(!w.is_maximized()); } } - TitleBarButton::Close => cx.send_action(Action::CLOSE), + TitleBarButton::Close => cx.action(self.id(), Action::CLOSE), } } } diff --git a/crates/kas-core/src/event/components.rs b/crates/kas-core/src/event/components.rs index 673df7d21..c3acd5f1d 100644 --- a/crates/kas-core/src/event/components.rs +++ b/crates/kas-core/src/event/components.rs @@ -195,49 +195,45 @@ impl ScrollComponent { /// /// Sets [`Scroll::Rect`] to ensure correct scrolling of parents. /// - /// Returns `true` when the scroll offset changes. - pub fn focus_rect(&mut self, cx: &mut EventCx, rect: Rect, window_rect: Rect) -> bool { + /// Returns [`Action::REGION_MOVED`] when the scroll offset changes. + pub fn focus_rect(&mut self, cx: &mut EventCx, rect: Rect, window_rect: Rect) -> Action { self.glide.stop(); let v = rect.pos - window_rect.pos; let off = Offset::conv(rect.size) - Offset::conv(window_rect.size); let offset = self.offset.max(v + off).min(v); let action = self.set_offset(offset); cx.set_scroll(Scroll::Rect(rect - self.offset)); - if action.is_empty() { - false - } else { - *cx |= action; - true - } + action } /// Handle a [`Scroll`] action - pub fn scroll(&mut self, cx: &mut EventCx, window_rect: Rect, scroll: Scroll) { + pub fn scroll(&mut self, cx: &mut EventCx, window_rect: Rect, scroll: Scroll) -> Action { match scroll { - Scroll::None | Scroll::Scrolled => (), + Scroll::None | Scroll::Scrolled => Action::empty(), Scroll::Offset(delta) => { let old_offset = self.offset; - *cx |= self.set_offset(old_offset - delta); + let action = self.set_offset(old_offset - delta); cx.set_scroll(match delta - old_offset + self.offset { delta if delta == Offset::ZERO => Scroll::Scrolled, delta => Scroll::Offset(delta), }); + action } - Scroll::Rect(rect) => { - self.focus_rect(cx, rect, window_rect); - } + Scroll::Rect(rect) => self.focus_rect(cx, rect, window_rect), } } - fn scroll_by_delta(&mut self, cx: &mut EventCx, d: Offset) -> bool { + // Returns Action::REGION_MOVED or Action::empty() + fn scroll_by_delta(&mut self, cx: &mut EventCx, d: Offset) -> Action { let mut delta = d; - let mut moved = false; + let action; let offset = (self.offset - d).clamp(Offset::ZERO, self.max_offset); if offset != self.offset { - moved = true; delta = d - (self.offset - offset); self.offset = offset; - *cx |= Action::REGION_MOVED; + action = Action::REGION_MOVED; + } else { + action = Action::empty(); } cx.set_scroll(if delta != Offset::ZERO { @@ -246,7 +242,7 @@ impl ScrollComponent { Scroll::Scrolled }); - moved + action } /// Use an event to scroll, if possible @@ -273,7 +269,7 @@ impl ScrollComponent { id: WidgetId, window_rect: Rect, ) -> (bool, IsUsed) { - let mut moved = false; + let mut action = Action::empty(); match event { Event::Command(cmd, _) => { let offset = match cmd { @@ -296,11 +292,7 @@ impl ScrollComponent { self.offset - delta } }; - let action = self.set_offset(offset); - if !action.is_empty() { - moved = true; - *cx |= action; - } + action = self.set_offset(offset); cx.set_scroll(Scroll::Rect(window_rect)); } Event::Scroll(delta) => { @@ -309,19 +301,22 @@ impl ScrollComponent { PixelDelta(d) => d, }; self.glide.stop(); - moved = self.scroll_by_delta(cx, delta); + action = self.scroll_by_delta(cx, delta); } Event::PressStart { press, .. } if self.max_offset != Offset::ZERO && cx.config_enable_pan(*press) => { - let _ = press.grab(id).with_icon(CursorIcon::Grabbing).with_cx(cx); + let _ = press + .grab(id.clone()) + .with_icon(CursorIcon::Grabbing) + .with_cx(cx); self.glide.press_start(); } Event::PressMove { press, delta, .. } if self.max_offset != Offset::ZERO && cx.config_enable_pan(*press) => { if self.glide.press_move(delta) { - moved = self.scroll_by_delta(cx, delta); + action = self.scroll_by_delta(cx, delta); } } Event::PressEnd { press, .. } @@ -330,7 +325,7 @@ impl ScrollComponent { let timeout = cx.config().scroll_flick_timeout(); let pan_dist_thresh = cx.config().pan_dist_thresh(); if self.glide.press_end(timeout, pan_dist_thresh) { - cx.request_timer_update(id, PAYLOAD_GLIDE, Duration::new(0, 0), true); + cx.request_timer_update(id.clone(), PAYLOAD_GLIDE, Duration::new(0, 0), true); } } Event::TimerUpdate(pl) if pl == PAYLOAD_GLIDE => { @@ -338,18 +333,23 @@ impl ScrollComponent { let timeout = cx.config().scroll_flick_timeout(); let decay = cx.config().scroll_flick_decay(); if let Some(delta) = self.glide.step(timeout, decay) { - moved = self.scroll_by_delta(cx, delta); + action = self.scroll_by_delta(cx, delta); if self.glide.vel != Vec2::ZERO { let dur = Duration::from_millis(GLIDE_POLL_MS); - cx.request_timer_update(id, PAYLOAD_GLIDE, dur, true); + cx.request_timer_update(id.clone(), PAYLOAD_GLIDE, dur, true); cx.set_scroll(Scroll::Scrolled); } } } _ => return (false, Unused), } - (moved, Used) + if !action.is_empty() { + cx.action(id, action); + (true, Used) + } else { + (false, Used) + } } } diff --git a/crates/kas-core/src/event/cx/config.rs b/crates/kas-core/src/event/cx/config.rs index 20c0cff74..826f8fbf4 100644 --- a/crates/kas-core/src/event/cx/config.rs +++ b/crates/kas-core/src/event/cx/config.rs @@ -11,7 +11,7 @@ use crate::geom::{Rect, Size}; use crate::layout::AlignPair; use crate::text::TextApi; use crate::theme::{Feature, SizeCx, TextClass, ThemeSize}; -use crate::{Action, Node, WidgetId}; +use crate::{Node, WidgetId}; use std::ops::{Deref, DerefMut}; #[allow(unused)] use crate::{event::Event, Events, Layout}; @@ -116,13 +116,6 @@ impl<'a> ConfigCx<'a> { } } -impl<'a> std::ops::BitOrAssign for ConfigCx<'a> { - #[inline] - fn bitor_assign(&mut self, action: Action) { - self.ev.send_action(action); - } -} - impl<'a> Deref for ConfigCx<'a> { type Target = EventState; fn deref(&self) -> &EventState { diff --git a/crates/kas-core/src/event/cx/cx_pub.rs b/crates/kas-core/src/event/cx/cx_pub.rs index 9588cf683..88940e401 100644 --- a/crates/kas-core/src/event/cx/cx_pub.rs +++ b/crates/kas-core/src/event/cx/cx_pub.rs @@ -20,20 +20,6 @@ use crate::util::warn_about_error; use crate::{Action, Erased, WidgetId, Window, WindowId}; #[allow(unused)] use crate::{Events, Layout}; // for doc-links -impl<'a> std::ops::BitOrAssign for EventCx<'a> { - #[inline] - fn bitor_assign(&mut self, action: Action) { - self.send_action(action); - } -} - -impl std::ops::BitOrAssign for EventState { - #[inline] - fn bitor_assign(&mut self, action: Action) { - self.send_action(action); - } -} - /// Public API impl EventState { /// Get the platform @@ -197,7 +183,7 @@ impl EventState { } } if state { - self.send_action(Action::REDRAW); + self.action(w_id.clone(), Action::REDRAW); self.disabled.push(w_id); } } @@ -248,29 +234,34 @@ impl EventState { /// Notify that a widget must be redrawn /// - /// Note: currently, only full-window redraws are supported, thus this is - /// equivalent to: `cx.send_action(Action::REDRAW);` + /// This is equivalent to: `cx.action(self.id(), Action::REDRAW);` #[inline] - pub fn redraw(&mut self, _id: WidgetId) { - // Theoretically, notifying by WidgetId allows selective redrawing - // (damage events). This is not yet implemented. - self.send_action(Action::REDRAW); + pub fn redraw(&mut self, id: WidgetId) { + self.action(id, Action::REDRAW); } /// Notify that a [`Action`] action should happen /// /// This causes the given action to happen after event handling. /// - /// Calling `cx.send_action(action)` is equivalent to `*cx |= action`. - /// /// Whenever a widget is added, removed or replaced, a reconfigure action is /// required. Should a widget's size requirements change, these will only /// affect the UI after a reconfigure action. #[inline] - pub fn send_action(&mut self, action: Action) { + pub fn action(&mut self, id: WidgetId, action: Action) { + // TODO: make handling more specific via id + let _ = id; self.action |= action; } + /// Pass an [action](Self::action) given some `id` + #[inline] + pub(crate) fn opt_action(&mut self, id: Option, action: Action) { + if let Some(id) = id { + self.action(id, action); + } + } + /// Attempts to set a fallback to receive [`Event::Command`] /// /// In case a navigation key is pressed (see [`Command`]) but no widget has @@ -364,8 +355,8 @@ impl EventState { return; } - self.key_depress.insert(code, id); - self.send_action(Action::REDRAW); + self.key_depress.insert(code, id.clone()); + self.action(id, Action::REDRAW); } /// Request keyboard input focus @@ -420,24 +411,28 @@ impl EventState { /// Queues a redraw and returns `true` if the depress target changes, /// otherwise returns `false`. pub fn set_grab_depress(&mut self, source: PressSource, target: Option) -> bool { + let mut old = None; let mut redraw = false; match source { PressSource::Mouse(_, _) => { if let Some(grab) = self.mouse_grab.as_mut() { redraw = grab.depress != target; + old = grab.depress.take(); grab.depress = target.clone(); } } PressSource::Touch(id) => { if let Some(grab) = self.get_touch(id) { redraw = grab.depress != target; + old = grab.depress.take(); grab.depress = target.clone(); } } } if redraw { log::trace!(target: "kas_core::event", "set_grab_depress: target={target:?}"); - self.send_action(Action::REDRAW); + self.opt_action(old, Action::REDRAW); + self.opt_action(target, Action::REDRAW); } redraw } @@ -469,7 +464,7 @@ impl EventState { /// Clear navigation focus pub fn clear_nav_focus(&mut self) { if let Some(id) = self.nav_focus.take() { - self.send_action(Action::REDRAW); + self.action(id.clone(), Action::REDRAW); self.pending .push_back(Pending::Send(id, Event::LostNavFocus)); log::debug!(target: "kas_core::event", "nav_focus = None"); @@ -491,14 +486,15 @@ impl EventState { return; } - self.send_action(Action::REDRAW); if let Some(old_id) = self.nav_focus.take() { + self.action(old_id.clone(), Action::REDRAW); self.pending .push_back(Pending::Send(old_id, Event::LostNavFocus)); } if id != self.sel_focus { self.clear_key_focus(); } + self.action(id.clone(), Action::REDRAW); self.nav_focus = Some(id.clone()); log::debug!(target: "kas_core::event", "nav_focus = Some({id})"); self.pending diff --git a/crates/kas-core/src/event/cx/cx_shell.rs b/crates/kas-core/src/event/cx/cx_shell.rs index ddd921eae..aff6ada03 100644 --- a/crates/kas-core/src/event/cx/cx_shell.rs +++ b/crates/kas-core/src/event/cx/cx_shell.rs @@ -304,7 +304,7 @@ impl<'a> EventCx<'a> { use winit::event::{MouseScrollDelta, TouchPhase, WindowEvent::*}; match event { - CloseRequested => self.send_action(Action::CLOSE), + CloseRequested => self.action(WidgetId::ROOT, Action::CLOSE), /* Not yet supported: see #98 DroppedFile(path) => , HoveredFile(path) => , @@ -314,7 +314,7 @@ impl<'a> EventCx<'a> { self.window_has_focus = state; if state { // Required to restart theme animations - self.send_action(Action::REDRAW); + self.action(WidgetId::ROOT, Action::REDRAW); } else { // Window focus lost: close all popups while let Some(id) = self.popups.last().map(|(id, _, _)| *id) { @@ -364,7 +364,7 @@ impl<'a> EventCx<'a> { let state = modifiers.state(); if state.alt_key() != self.modifiers.alt_key() { // This controls drawing of access key indicators - self.send_action(Action::REDRAW); + self.action(WidgetId::ROOT, Action::REDRAW); } self.modifiers = state; } @@ -543,7 +543,7 @@ impl<'a> EventCx<'a> { } if redraw { - self.send_action(Action::REDRAW); + self.action(WidgetId::ROOT, Action::REDRAW); } else if let Some(pan_grab) = pan_grab { if usize::conv(pan_grab.1) < MAX_PAN_GRABS { if let Some(pan) = self.pan_grab.get_mut(usize::conv(pan_grab.0)) { @@ -554,7 +554,7 @@ 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()); + self.action(WidgetId::ROOT, grab.flush_click_move()); 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 02cee28e4..0c610a42f 100644 --- a/crates/kas-core/src/event/cx/mod.rs +++ b/crates/kas-core/src/event/cx/mod.rs @@ -318,7 +318,7 @@ impl EventState { "remove_touch: touch_id={touch_id}, start_id={}", grab.start_id ); - self.send_action(Action::REDRAW); // redraw(..) + self.opt_action(grab.depress.clone(), Action::REDRAW); self.remove_pan_grab(grab.pan_grab); return Some(grab); } @@ -524,7 +524,7 @@ impl<'a> EventCx<'a> { if let Some(grab) = self.mouse_grab.take() { log::trace!("remove_mouse_grab: start_id={}", grab.start_id); self.window.set_cursor_icon(self.hover_icon); - self.send_action(Action::REDRAW); // redraw(..) + self.opt_action(grab.depress.clone(), Action::REDRAW); if let GrabDetails::Pan(g) = grab.details { self.remove_pan_grab(g); // Pan grabs do not receive Event::PressEnd @@ -646,10 +646,6 @@ impl<'a> EventCx<'a> { } } - // We redraw in all cases. Since this is not part of widget event - // processing, we can push directly to self.action. - self.send_action(Action::REDRAW); - let advance = if !reverse { NavAdvance::Forward(target.is_some()) } else { @@ -673,7 +669,8 @@ impl<'a> EventCx<'a> { return; } - if let Some(id) = self.nav_focus.clone() { + if let Some(id) = self.nav_focus.take() { + self.redraw(id.clone()); self.pending .push_back(Pending::Send(id, Event::LostNavFocus)); } @@ -684,6 +681,7 @@ impl<'a> EventCx<'a> { self.nav_focus = opt_id.clone(); if let Some(id) = opt_id { log::debug!(target: "kas_core::event", "nav_focus = Some({id})"); + self.redraw(id.clone()); self.pending .push_back(Pending::Send(id, Event::NavFocus(source))); } else { diff --git a/crates/kas-core/src/event/cx/press.rs b/crates/kas-core/src/event/cx/press.rs index 40ba77793..2a64b04b1 100644 --- a/crates/kas-core/src/event/cx/press.rs +++ b/crates/kas-core/src/event/cx/press.rs @@ -200,14 +200,14 @@ impl GrabBuilder { debug_assert!(repetitions >= grab.repetitions); grab.repetitions = repetitions; - grab.depress = Some(id); + grab.depress = Some(id.clone()); grab.details = details; } else { cx.mouse_grab = Some(MouseGrab { button, repetitions, start_id: id.clone(), - depress: Some(id), + depress: Some(id.clone()), details, }); } @@ -222,7 +222,7 @@ impl GrabBuilder { } grab.depress = Some(id.clone()); - grab.cur_id = Some(id); + grab.cur_id = Some(id.clone()); grab.last_move = coord; grab.coord = coord; grab.mode = grab.mode.max(mode); @@ -235,7 +235,7 @@ impl GrabBuilder { id: touch_id, start_id: id.clone(), depress: Some(id.clone()), - cur_id: Some(id), + cur_id: Some(id.clone()), last_move: coord, coord, mode, @@ -245,7 +245,7 @@ impl GrabBuilder { } } - cx.send_action(Action::REDRAW); + cx.action(id, Action::REDRAW); Used } } diff --git a/crates/kas-core/src/root.rs b/crates/kas-core/src/root.rs index c31cfc1da..74f35fdb9 100644 --- a/crates/kas-core/src/root.rs +++ b/crates/kas-core/src/root.rs @@ -228,7 +228,7 @@ impl_scope! { if let Some(cmd) = cx.try_pop() { match cmd { WindowCommand::SetTitle(title) => { - *cx |= self.title_bar.set_title(title); + cx.action(self.id(), self.title_bar.set_title(title)); #[cfg(winit)] if self.decorations == Decorations::Server { if let Some(w) = cx.winit_window() { @@ -413,7 +413,7 @@ impl Window { let index = self.popups.len(); self.popups.push((id, popup, Offset::ZERO)); self.resize_popup(&mut cx.config_cx(), data, index); - cx.send_action(Action::REDRAW); + cx.action(WidgetId::ROOT, Action::REDRAW); } /// Trigger closure of a pop-up @@ -423,7 +423,7 @@ impl Window { for i in 0..self.popups.len() { if id == self.popups[i].0 { self.popups.remove(i); - cx.send_action(Action::REGION_MOVED); + cx.action(WidgetId::ROOT, Action::REGION_MOVED); return; } } diff --git a/crates/kas-core/src/shell/window.rs b/crates/kas-core/src/shell/window.rs index 37cfdceb4..3a5986b74 100644 --- a/crates/kas-core/src/shell/window.rs +++ b/crates/kas-core/src/shell/window.rs @@ -15,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::{autoimpl, Action, AppData, ErasedStack, Layout, LayoutExt, Widget, WindowId}; +use kas::{autoimpl, Action, AppData, ErasedStack, Layout, LayoutExt, Widget, WidgetId, WindowId}; use std::mem::take; use std::time::{Duration, Instant}; use winit::event::WindowEvent; @@ -357,12 +357,12 @@ impl> Window { } pub(super) fn send_action(&mut self, action: Action) { - self.ev_state.send_action(action); + self.ev_state.action(WidgetId::ROOT, action); } pub(super) fn send_close(&mut self, shared: &mut SharedState, id: WindowId) { if id == self.window_id { - self.ev_state.send_action(Action::CLOSE); + self.ev_state.action(WidgetId::ROOT, Action::CLOSE); } else if let Some(window) = self.window.as_ref() { let widget = &mut self.widget; let mut messages = ErasedStack::new(); diff --git a/crates/kas-core/src/theme/draw.rs b/crates/kas-core/src/theme/draw.rs index b4bf1d208..c52216502 100644 --- a/crates/kas-core/src/theme/draw.rs +++ b/crates/kas-core/src/theme/draw.rs @@ -12,7 +12,7 @@ use crate::draw::{Draw, DrawIface, DrawShared, DrawSharedImpl, ImageId, PassType use crate::event::{ConfigCx, EventState}; use crate::geom::{Offset, Rect}; use crate::text::{TextApi, TextDisplay}; -use crate::{autoimpl, Action, Layout, WidgetId}; +use crate::{autoimpl, Layout, WidgetId}; use std::convert::AsRef; use std::ops::{Bound, Range, RangeBounds}; use std::time::Instant; @@ -331,13 +331,6 @@ impl<'a> DrawCx<'a> { } } -impl<'a> std::ops::BitOrAssign for DrawCx<'a> { - #[inline] - fn bitor_assign(&mut self, action: Action) { - self.h.components().2.send_action(action); - } -} - /// Theme drawing implementation /// /// # Theme extension From 52d93e2b9b1db8a155afd310b8f5c8bf6caf7836 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 7 Oct 2023 12:15:23 +0100 Subject: [PATCH 02/16] Add trait HasId; use in EventState methods action and redraw --- crates/kas-core/src/core/layout.rs | 20 +++++++++++++--- crates/kas-core/src/core/widget_id.rs | 33 ++++++++++++++++++++++++++ crates/kas-core/src/decorations.rs | 2 +- crates/kas-core/src/event/cx/cx_pub.rs | 14 +++++------ crates/kas-core/src/prelude.rs | 2 +- crates/kas-macros/src/widget.rs | 4 ++-- crates/kas-resvg/src/canvas.rs | 2 +- crates/kas-resvg/src/svg.rs | 2 +- crates/kas-view/src/list_view.rs | 4 ++-- crates/kas-view/src/matrix_view.rs | 4 ++-- crates/kas-widgets/src/check_box.rs | 4 ++-- crates/kas-widgets/src/edit.rs | 20 ++++++++-------- crates/kas-widgets/src/radio_box.rs | 4 ++-- crates/kas-widgets/src/scroll_label.rs | 12 +++++----- crates/kas-widgets/src/text.rs | 2 +- examples/proxy.rs | 2 +- 16 files changed, 89 insertions(+), 42 deletions(-) diff --git a/crates/kas-core/src/core/layout.rs b/crates/kas-core/src/core/layout.rs index 7caf06ab7..bd6f60b95 100644 --- a/crates/kas-core/src/core/layout.rs +++ b/crates/kas-core/src/core/layout.rs @@ -10,7 +10,7 @@ use crate::geom::{Coord, Offset, Rect}; use crate::layout::{AxisInfo, SizeRules}; use crate::theme::{DrawCx, SizeCx}; use crate::util::IdentifyWidget; -use crate::WidgetId; +use crate::{HasId, WidgetId}; use kas_macros::autoimpl; #[allow(unused)] use super::{Events, Widget}; @@ -296,6 +296,20 @@ pub trait Layout { fn draw(&mut self, draw: DrawCx); } +impl HasId for &W { + #[inline] + fn has_id(self) -> WidgetId { + self.id_ref().clone() + } +} + +impl HasId for &mut W { + #[inline] + fn has_id(self) -> WidgetId { + self.id_ref().clone() + } +} + /// Extension trait over widgets pub trait LayoutExt: Layout { /// Get the widget's identifier @@ -331,7 +345,7 @@ pub trait LayoutExt: Layout { /// This function assumes that `id` is a valid widget. #[inline] fn is_ancestor_of(&self, id: &WidgetId) -> bool { - self.id().is_ancestor_of(id) + self.id_ref().is_ancestor_of(id) } /// Check whether `id` is not self and is a descendant @@ -339,7 +353,7 @@ pub trait LayoutExt: Layout { /// This function assumes that `id` is a valid widget. #[inline] fn is_strict_ancestor_of(&self, id: &WidgetId) -> bool { - !self.eq_id(id) && self.id().is_ancestor_of(id) + !self.eq_id(id) && self.id_ref().is_ancestor_of(id) } /// Run a closure on all children diff --git a/crates/kas-core/src/core/widget_id.rs b/crates/kas-core/src/core/widget_id.rs index a5194a3ca..5bcd7b149 100644 --- a/crates/kas-core/src/core/widget_id.rs +++ b/crates/kas-core/src/core/widget_id.rs @@ -656,6 +656,39 @@ impl fmt::Display for WidgetId { } } +/// Types supporting conversion to [`WidgetId`] +/// +/// A method taking an `id: impl HasId` parameter supports an [`Id`], +/// a reference to an [`Id`] (which is thus cloned), +/// or a (mutable) reference to a widget. +/// +/// Note: in some cases attempting to pass a widget reference does not pass +/// borrow checks. In this case pass `widget.id()` explicitly. +pub trait HasId { + fn has_id(self) -> WidgetId; +} + +impl HasId for WidgetId { + #[inline] + fn has_id(self) -> WidgetId { + self + } +} + +impl HasId for &WidgetId { + #[inline] + fn has_id(self) -> WidgetId { + self.clone() + } +} + +impl HasId for &mut WidgetId { + #[inline] + fn has_id(self) -> WidgetId { + self.clone() + } +} + #[cfg(test)] mod test { use super::*; diff --git a/crates/kas-core/src/decorations.rs b/crates/kas-core/src/decorations.rs index 8966c8cfa..5d8b87638 100644 --- a/crates/kas-core/src/decorations.rs +++ b/crates/kas-core/src/decorations.rs @@ -286,7 +286,7 @@ impl_scope! { w.set_maximized(!w.is_maximized()); } } - TitleBarButton::Close => cx.action(self.id(), Action::CLOSE), + TitleBarButton::Close => cx.action(self, Action::CLOSE), } } } diff --git a/crates/kas-core/src/event/cx/cx_pub.rs b/crates/kas-core/src/event/cx/cx_pub.rs index 88940e401..37a7c0112 100644 --- a/crates/kas-core/src/event/cx/cx_pub.rs +++ b/crates/kas-core/src/event/cx/cx_pub.rs @@ -17,7 +17,7 @@ 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}; +use crate::{Action, Erased, HasId, WidgetId, Window, WindowId}; #[allow(unused)] use crate::{Events, Layout}; // for doc-links /// Public API @@ -183,7 +183,7 @@ impl EventState { } } if state { - self.action(w_id.clone(), Action::REDRAW); + self.action(&w_id, Action::REDRAW); self.disabled.push(w_id); } } @@ -234,9 +234,9 @@ impl EventState { /// Notify that a widget must be redrawn /// - /// This is equivalent to: `cx.action(self.id(), Action::REDRAW);` + /// This is equivalent to: `cx.action(self, Action::REDRAW);` #[inline] - pub fn redraw(&mut self, id: WidgetId) { + pub fn redraw(&mut self, id: impl HasId) { self.action(id, Action::REDRAW); } @@ -248,7 +248,7 @@ impl EventState { /// required. Should a widget's size requirements change, these will only /// affect the UI after a reconfigure action. #[inline] - pub fn action(&mut self, id: WidgetId, action: Action) { + pub fn action(&mut self, id: impl HasId, action: Action) { // TODO: make handling more specific via id let _ = id; self.action |= action; @@ -487,14 +487,14 @@ impl EventState { } if let Some(old_id) = self.nav_focus.take() { - self.action(old_id.clone(), Action::REDRAW); + self.action(&old_id, Action::REDRAW); self.pending .push_back(Pending::Send(old_id, Event::LostNavFocus)); } if id != self.sel_focus { self.clear_key_focus(); } - self.action(id.clone(), Action::REDRAW); + self.action(&id, Action::REDRAW); self.nav_focus = Some(id.clone()); log::debug!(target: "kas_core::event", "nav_focus = Some({id})"); self.pending diff --git a/crates/kas-core/src/prelude.rs b/crates/kas-core/src/prelude.rs index a86addd3c..a95e6d278 100644 --- a/crates/kas-core/src/prelude.rs +++ b/crates/kas-core/src/prelude.rs @@ -23,10 +23,10 @@ pub use crate::layout::{Align, AlignPair, AxisInfo, SizeRules, Stretch}; pub use crate::text::{EditableTextApi, TextApi, TextApiExt}; #[doc(no_inline)] pub use crate::theme::{DrawCx, SizeCx}; #[doc(no_inline)] pub use crate::Action; -#[doc(no_inline)] pub use crate::WidgetId; #[doc(no_inline)] pub use crate::{autoimpl, impl_anon, impl_default, impl_scope, widget, widget_index}; #[doc(no_inline)] pub use crate::{Events, Layout, LayoutExt, Widget, Window, WindowCommand}; +#[doc(no_inline)] pub use crate::{HasId, WidgetId}; #[doc(no_inline)] pub use crate::{HasScrollBars, Node, ScrollBarMode, Scrollable}; diff --git a/crates/kas-macros/src/widget.rs b/crates/kas-macros/src/widget.rs index 72f9f6af5..719f0ec12 100644 --- a/crates/kas-macros/src/widget.rs +++ b/crates/kas-macros/src/widget.rs @@ -770,7 +770,7 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul (true, None) => quote! { #[inline] fn handle_hover(&mut self, cx: &mut EventCx, _: bool) -> ::kas::event::IsUsed { - cx.redraw(self.id()); + cx.redraw(self); ::kas::event::Used } }, @@ -786,7 +786,7 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul (true, Some(icon_expr)) => quote! { #[inline] fn handle_hover(&mut self, cx: &mut EventCx, state: bool) -> ::kas::event::IsUsed { - cx.redraw(self.id()); + cx.redraw(self); if state { cx.set_hover_cursor(#icon_expr); } diff --git a/crates/kas-resvg/src/canvas.rs b/crates/kas-resvg/src/canvas.rs index 1b0a54cf3..51383a08d 100644 --- a/crates/kas-resvg/src/canvas.rs +++ b/crates/kas-resvg/src/canvas.rs @@ -209,7 +209,7 @@ impl_scope! { ds.image_upload(handle, pixmap.data(), ImageFormat::Rgba8); } - cx.redraw(self.id()); + cx.redraw(self); let rect_size: (u32, u32) = self.rect().size.cast(); if rect_size != size { diff --git a/crates/kas-resvg/src/svg.rs b/crates/kas-resvg/src/svg.rs index 3f8e1c36b..e1166105b 100644 --- a/crates/kas-resvg/src/svg.rs +++ b/crates/kas-resvg/src/svg.rs @@ -277,7 +277,7 @@ impl_scope! { ds.image_upload(handle, pixmap.data(), ImageFormat::Rgba8); } - cx.redraw(self.id()); + cx.redraw(self); let inner = std::mem::replace(&mut self.inner, State::None); self.inner = match inner { State::None => State::None, diff --git a/crates/kas-view/src/list_view.rs b/crates/kas-view/src/list_view.rs index 2e6a93d03..1e7b1d4f0 100644 --- a/crates/kas-view/src/list_view.rs +++ b/crates/kas-view/src/list_view.rs @@ -731,13 +731,13 @@ impl_scope! { match self.sel_mode { SelectionMode::None => (), SelectionMode::Single => { - cx.redraw(self.core.id.clone()); + cx.redraw(self); self.selection.clear(); self.selection.insert(key.clone()); cx.push(SelectionMsg::Select(key.clone())); } SelectionMode::Multiple => { - cx.redraw(self.core.id.clone()); + cx.redraw(self); if self.selection.remove(key) { cx.push(SelectionMsg::Deselect(key.clone())); } else { diff --git a/crates/kas-view/src/matrix_view.rs b/crates/kas-view/src/matrix_view.rs index 2dcfab15d..7739ba1e5 100644 --- a/crates/kas-view/src/matrix_view.rs +++ b/crates/kas-view/src/matrix_view.rs @@ -698,13 +698,13 @@ impl_scope! { match self.sel_mode { SelectionMode::None => (), SelectionMode::Single => { - cx.redraw(self.id()); + cx.redraw(self); self.selection.clear(); self.selection.insert(key.clone()); cx.push(SelectionMsg::Select(key)); } SelectionMode::Multiple => { - cx.redraw(self.id()); + cx.redraw(self); if self.selection.remove(&key) { cx.push(SelectionMsg::Deselect(key)); } else { diff --git a/crates/kas-widgets/src/check_box.rs b/crates/kas-widgets/src/check_box.rs index d2f03e302..64a83ffe3 100644 --- a/crates/kas-widgets/src/check_box.rs +++ b/crates/kas-widgets/src/check_box.rs @@ -38,7 +38,7 @@ impl_scope! { if self.state != new_state { self.state = new_state; self.last_change = Some(Instant::now()); - cx.redraw(self.id()); + cx.redraw(self); } } @@ -145,7 +145,7 @@ impl_scope! { // Do animate (even if state never changes): self.last_change = Some(Instant::now()); - cx.redraw(self.id()); + cx.redraw(self); } } } diff --git a/crates/kas-widgets/src/edit.rs b/crates/kas-widgets/src/edit.rs index 61c24c421..f8faa7f9c 100644 --- a/crates/kas-widgets/src/edit.rs +++ b/crates/kas-widgets/src/edit.rs @@ -673,7 +673,7 @@ impl_scope! { if !self.class.multi_line() { self.selection.clear(); self.selection.set_edit_pos(self.text.str_len()); - cx.redraw(self.id()); + cx.redraw(self); } Used } @@ -681,19 +681,19 @@ impl_scope! { Event::LostNavFocus => { if !self.class.multi_line() { self.selection.set_empty(); - cx.redraw(self.id()); + cx.redraw(self); } Used } Event::LostKeyFocus => { self.has_key_focus = false; - cx.redraw(self.id()); + cx.redraw(self); G::focus_lost(self, cx, data); Used } Event::LostSelFocus => { self.selection.set_empty(); - cx.redraw(self.id()); + cx.redraw(self); Used } Event::Command(cmd, code) => { @@ -806,7 +806,7 @@ impl_scope! { if new_offset != self.view_offset { self.view_offset = new_offset; // No widget moves so do not need to report Action::REGION_MOVED - cx.redraw(self.id()); + cx.redraw(self); } new_offset } @@ -1083,7 +1083,7 @@ impl EditField { ); } - cx.redraw(self.id()); + cx.redraw(self); self.set_view_offset_from_edit_pos(cx); } @@ -1165,7 +1165,7 @@ impl EditField { let action = match cmd { Command::Escape | Command::Deselect if !selection.is_empty() => { self.selection.set_empty(); - cx.redraw(self.id()); + cx.redraw(self); Action::None } Command::Activate => Action::Activate, @@ -1409,7 +1409,7 @@ impl EditField { self.set_primary(cx); } self.edit_x_coord = x_coord; - cx.redraw(self.id()); + cx.redraw(self); EditAction::None } }; @@ -1439,7 +1439,7 @@ impl EditField { self.selection.set_edit_pos(pos); self.set_view_offset_from_edit_pos(cx); self.edit_x_coord = None; - cx.redraw(self.id()); + cx.redraw(self); } } } @@ -1459,7 +1459,7 @@ impl EditField { if new_offset != self.view_offset { delta -= self.view_offset - new_offset; self.view_offset = new_offset; - cx.redraw(self.id()); + cx.redraw(self); } cx.set_scroll(if delta == Offset::ZERO { diff --git a/crates/kas-widgets/src/radio_box.rs b/crates/kas-widgets/src/radio_box.rs index 5a838de2e..4d35a10cf 100644 --- a/crates/kas-widgets/src/radio_box.rs +++ b/crates/kas-widgets/src/radio_box.rs @@ -37,7 +37,7 @@ impl_scope! { if self.state != new_state { self.state = new_state; self.last_change = Some(Instant::now()); - cx.redraw(self.id()); + cx.redraw(self); } } @@ -127,7 +127,7 @@ impl_scope! { } self.last_change = Some(Instant::now()); - cx.redraw(self.id()); + cx.redraw(self); } } } diff --git a/crates/kas-widgets/src/scroll_label.rs b/crates/kas-widgets/src/scroll_label.rs index a85788183..836c8cc76 100644 --- a/crates/kas-widgets/src/scroll_label.rs +++ b/crates/kas-widgets/src/scroll_label.rs @@ -129,7 +129,7 @@ impl_scope! { self.selection.set_edit_pos(pos); self.set_view_offset_from_edit_pos(cx, pos); self.bar.set_value(cx, self.view_offset.1); - cx.redraw(self.id()); + cx.redraw(self); } } } @@ -190,7 +190,7 @@ impl_scope! { /// Set offset, updating the scroll bar fn set_offset(&mut self, cx: &mut EventState, offset: Offset) { self.view_offset = offset; - // unnecessary: cx.redraw(self.id()); + // unnecessary: cx.redraw(self); self.bar.set_value(cx, offset.1); } } @@ -220,14 +220,14 @@ impl_scope! { Event::Command(cmd, _) => match cmd { Command::Escape | Command::Deselect if !self.selection.is_empty() => { self.selection.set_empty(); - cx.redraw(self.id()); + cx.redraw(self); Used } Command::SelectAll => { self.selection.set_sel_pos(0); self.selection.set_edit_pos(self.text.str_len()); self.set_primary(cx); - cx.redraw(self.id()); + cx.redraw(self); Used } Command::Cut | Command::Copy => { @@ -240,7 +240,7 @@ impl_scope! { }, Event::LostSelFocus => { self.selection.set_empty(); - cx.redraw(self.id()); + cx.redraw(self); Used } Event::Scroll(delta) => { @@ -278,7 +278,7 @@ impl_scope! { if let Some(ScrollMsg(y)) = cx.try_pop() { let y = y.clamp(0, self.max_scroll_offset().1); self.view_offset.1 = y; - cx.redraw(self.id()); + cx.redraw(self); } } } diff --git a/crates/kas-widgets/src/text.rs b/crates/kas-widgets/src/text.rs index 71d494482..3561b4700 100644 --- a/crates/kas-widgets/src/text.rs +++ b/crates/kas-widgets/src/text.rs @@ -144,7 +144,7 @@ impl_scope! { // infinite offset and thus infinite measured height. match self.label.try_prepare() { Ok(true) => *cx |= Action::RESIZE, - _ => cx.redraw(self.id()), + _ => cx.redraw(self), } } } diff --git a/examples/proxy.rs b/examples/proxy.rs index 3718c0051..b282d5a79 100644 --- a/examples/proxy.rs +++ b/examples/proxy.rs @@ -99,7 +99,7 @@ impl_scope! { fn update(&mut self, cx: &mut ConfigCx, data: &AppData) { self.color = data.color; - cx.redraw(self.id()); + cx.redraw(self); } } } From c3ada77d6e9d2e7f6dfd92408dbfe925e5a097d3 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 7 Oct 2023 14:37:09 +0100 Subject: [PATCH 03/16] Update rest of repo to use new EventState::action --- crates/kas-core/src/event/cx/cx_pub.rs | 24 ++++++++++++++- crates/kas-core/src/event/cx/cx_shell.rs | 2 +- crates/kas-core/src/shell/window.rs | 3 +- crates/kas-resvg/src/canvas.rs | 2 +- crates/kas-resvg/src/svg.rs | 2 +- crates/kas-view/src/list_view.rs | 37 ++++++++++++++---------- crates/kas-view/src/matrix_view.rs | 23 ++++++++++----- crates/kas-widgets/src/combobox.rs | 25 +++++++++------- crates/kas-widgets/src/dialog.rs | 2 +- crates/kas-widgets/src/edit.rs | 26 ++++++++++------- crates/kas-widgets/src/list.rs | 16 +++++----- crates/kas-widgets/src/scroll.rs | 6 ++-- crates/kas-widgets/src/scroll_bar.rs | 7 +++-- crates/kas-widgets/src/slider.rs | 7 +++-- crates/kas-widgets/src/spinner.rs | 9 ++++-- crates/kas-widgets/src/splitter.rs | 21 +++++++------- crates/kas-widgets/src/stack.rs | 12 ++++---- crates/kas-widgets/src/text.rs | 9 +++--- examples/clock.rs | 2 +- examples/data-list-view.rs | 6 ++-- examples/data-list.rs | 3 +- examples/gallery.rs | 19 +++++++----- 22 files changed, 163 insertions(+), 100 deletions(-) diff --git a/crates/kas-core/src/event/cx/cx_pub.rs b/crates/kas-core/src/event/cx/cx_pub.rs index 37a7c0112..334db5d3f 100644 --- a/crates/kas-core/src/event/cx/cx_pub.rs +++ b/crates/kas-core/src/event/cx/cx_pub.rs @@ -234,12 +234,34 @@ impl EventState { /// Notify that a widget must be redrawn /// - /// This is equivalent to: `cx.action(self, Action::REDRAW);` + /// This is equivalent to calling [`Self::action`] with [`Action::REDRAW`]. #[inline] pub fn redraw(&mut self, id: impl HasId) { self.action(id, Action::REDRAW); } + /// Notify that a widget must be resized + /// + /// This is equivalent to calling [`Self::action`] with [`Action::RESIZE`]. + #[inline] + pub fn resize(&mut self, id: impl HasId) { + self.action(id, Action::RESIZE); + } + + /// Notify that widgets under self may have moved + /// + /// This is equivalent to calling [`Self::action`] with [`Action::REGION_MOVED`]. + #[inline] + pub fn region_moved(&mut self, id: impl HasId) { + self.action(id, Action::REGION_MOVED); + } + + /// Terminate the GUI + #[inline] + pub fn exit(&mut self) { + self.action |= Action::EXIT; + } + /// Notify that a [`Action`] action should happen /// /// This causes the given action to happen after event handling. diff --git a/crates/kas-core/src/event/cx/cx_shell.rs b/crates/kas-core/src/event/cx/cx_shell.rs index aff6ada03..12fac04f3 100644 --- a/crates/kas-core/src/event/cx/cx_shell.rs +++ b/crates/kas-core/src/event/cx/cx_shell.rs @@ -96,7 +96,7 @@ impl EventState { } /// Update the widgets under the cursor and touch events - pub(crate) fn region_moved(&mut self, win: &mut Window, data: &A) { + pub(crate) fn handle_region_moved(&mut self, win: &mut Window, data: &A) { log::trace!(target: "kas_core::event", "region_moved"); // Note: redraw is already implied. diff --git a/crates/kas-core/src/shell/window.rs b/crates/kas-core/src/shell/window.rs index 3a5986b74..60b2c8ea8 100644 --- a/crates/kas-core/src/shell/window.rs +++ b/crates/kas-core/src/shell/window.rs @@ -316,7 +316,8 @@ impl> Window { self.apply_size(shared, false); } if action.contains(Action::REGION_MOVED) { - self.ev_state.region_moved(&mut self.widget, &shared.data); + self.ev_state + .handle_region_moved(&mut self.widget, &shared.data); } if !action.is_empty() { if let Some(ref mut window) = self.window { diff --git a/crates/kas-resvg/src/canvas.rs b/crates/kas-resvg/src/canvas.rs index 51383a08d..a17d17adf 100644 --- a/crates/kas-resvg/src/canvas.rs +++ b/crates/kas-resvg/src/canvas.rs @@ -209,7 +209,7 @@ impl_scope! { ds.image_upload(handle, pixmap.data(), ImageFormat::Rgba8); } - cx.redraw(self); + cx.redraw(&self); let rect_size: (u32, u32) = self.rect().size.cast(); if rect_size != size { diff --git a/crates/kas-resvg/src/svg.rs b/crates/kas-resvg/src/svg.rs index e1166105b..fe55b49c9 100644 --- a/crates/kas-resvg/src/svg.rs +++ b/crates/kas-resvg/src/svg.rs @@ -277,7 +277,7 @@ impl_scope! { ds.image_upload(handle, pixmap.data(), ImageFormat::Rgba8); } - cx.redraw(self); + cx.redraw(&self); let inner = std::mem::replace(&mut self.inner, State::None); self.inner = match inner { State::None => State::None, diff --git a/crates/kas-view/src/list_view.rs b/crates/kas-view/src/list_view.rs index 1e7b1d4f0..ceecd7c29 100644 --- a/crates/kas-view/src/list_view.rs +++ b/crates/kas-view/src/list_view.rs @@ -375,7 +375,8 @@ impl_scope! { self.direction, (self.skip * data_len - self.child_inter_margin).max(0), ); - *cx |= self.scroll.set_sizes(view_size, content_size); + let action = self.scroll.set_sizes(view_size, content_size); + cx.action(self, action); } } @@ -410,7 +411,8 @@ impl_scope! { #[inline] fn set_scroll_offset(&mut self, cx: &mut EventCx, offset: Offset) -> Offset { - *cx |= self.scroll.set_offset(offset); + let act = self.scroll.set_offset(offset); + cx.action(&self, act); cx.request_update(self.id()); self.scroll.offset() } @@ -618,7 +620,7 @@ impl_scope! { // We must call at least SET_RECT to update scrollable region // RESIZE allows recalculation of child widget size which may // have been zero if no data was initially available! - *cx |= Action::RESIZE; + cx.resize(&self); } self.update_widgets(cx, data); @@ -658,7 +660,9 @@ 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) { + let act = self.scroll.focus_rect(cx, solver.rect(i_data), self.core.rect); + if !act.is_empty() { + cx.action(&self, act); self.update_widgets(&mut cx.config_cx(), data); } let index = i_data % usize::conv(self.cur_len); @@ -710,19 +714,19 @@ impl_scope! { } fn handle_messages(&mut self, cx: &mut EventCx, data: &A) { - let key; + let key: A::Key; if let Some(index) = cx.last_child() { let w = &mut self.widgets[index]; key = match w.key.as_ref() { - Some(k) => k, + Some(k) => k.clone(), None => return, }; - self.driver.on_messages(cx, data, key, &mut w.widget); + self.driver.on_messages(cx, data, &key, &mut w.widget); } else { // Message is from self key = match self.press_target.as_ref() { - Some((_, k)) => k, + Some((_, k)) => k.clone(), None => return, }; } @@ -731,18 +735,18 @@ impl_scope! { match self.sel_mode { SelectionMode::None => (), SelectionMode::Single => { - cx.redraw(self); + cx.redraw(&self); self.selection.clear(); self.selection.insert(key.clone()); - cx.push(SelectionMsg::Select(key.clone())); + cx.push(SelectionMsg::Select(key)); } SelectionMode::Multiple => { - cx.redraw(self); - if self.selection.remove(key) { + cx.redraw(&self); + if self.selection.remove(&key) { cx.push(SelectionMsg::Deselect(key.clone())); } else { self.selection.insert(key.clone()); - cx.push(SelectionMsg::Select(key.clone())); + cx.push(SelectionMsg::Select(key)); } } } @@ -750,8 +754,9 @@ impl_scope! { } fn handle_scroll(&mut self, cx: &mut EventCx, data: &A, scroll: Scroll) { - self.scroll.scroll(cx, self.rect(), scroll); + let act = self.scroll.scroll(cx, self.rect(), scroll); self.update_widgets(&mut cx.config_cx(), data); + cx.action(self, act); } } @@ -851,7 +856,9 @@ impl_scope! { last_data }; - if self.scroll.focus_rect(cx, solver.rect(data_index), self.core.rect) { + let act = self.scroll.focus_rect(cx, solver.rect(data_index), self.core.rect); + if !act.is_empty() { + cx.action(&self, act); self.update_widgets(&mut cx.config_cx(), data); } diff --git a/crates/kas-view/src/matrix_view.rs b/crates/kas-view/src/matrix_view.rs index 7739ba1e5..c9509812c 100644 --- a/crates/kas-view/src/matrix_view.rs +++ b/crates/kas-view/src/matrix_view.rs @@ -331,7 +331,8 @@ impl_scope! { let skip = self.child_size + self.child_inter_margin; let content_size = (skip.cwise_mul(self.data_len) - self.child_inter_margin) .max(Size::ZERO); - *cx |= self.scroll.set_sizes(view_size, content_size); + let action = self.scroll.set_sizes(view_size, content_size); + cx.action(self, action); } } @@ -355,7 +356,8 @@ impl_scope! { #[inline] fn set_scroll_offset(&mut self, cx: &mut EventCx, offset: Offset) -> Offset { - *cx |= self.scroll.set_offset(offset); + let action = self.scroll.set_offset(offset); + cx.action(&self, action); cx.request_update(self.id()); self.scroll.offset() } @@ -563,7 +565,7 @@ impl_scope! { // We must call at least SET_RECT to update scrollable region // RESIZE allows recalculation of child widget size which may // have been zero if no data was initially available! - *cx |= Action::RESIZE; + cx.resize(&self); } self.update_widgets(cx, data); @@ -607,7 +609,9 @@ 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) { + let action = self.scroll.focus_rect(cx, solver.rect(ci, ri), self.core.rect); + if !action.is_empty() { + cx.action(&self, action); solver = self.update_widgets(&mut cx.config_cx(), data); } @@ -698,13 +702,13 @@ impl_scope! { match self.sel_mode { SelectionMode::None => (), SelectionMode::Single => { - cx.redraw(self); + cx.redraw(&self); self.selection.clear(); self.selection.insert(key.clone()); cx.push(SelectionMsg::Select(key)); } SelectionMode::Multiple => { - cx.redraw(self); + cx.redraw(&self); if self.selection.remove(&key) { cx.push(SelectionMsg::Deselect(key)); } else { @@ -717,8 +721,9 @@ impl_scope! { } fn handle_scroll(&mut self, cx: &mut EventCx, data: &A, scroll: Scroll) { - self.scroll.scroll(cx, self.rect(), scroll); + let act = self.scroll.scroll(cx, self.rect(), scroll); self.update_widgets(&mut cx.config_cx(), data); + cx.action(self, act); } } @@ -828,7 +833,9 @@ impl_scope! { (d_cols - 1, d_rows - 1) }; - if self.scroll.focus_rect(cx, solver.rect(ci, ri), self.core.rect) { + let action = self.scroll.focus_rect(cx, solver.rect(ci, ri), self.core.rect); + if !action.is_empty() { + cx.action(&self, action); solver = self.update_widgets(&mut cx.config_cx(), data); } diff --git a/crates/kas-widgets/src/combobox.rs b/crates/kas-widgets/src/combobox.rs index 07dc009cd..0b07a3ef6 100644 --- a/crates/kas-widgets/src/combobox.rs +++ b/crates/kas-widgets/src/combobox.rs @@ -64,7 +64,7 @@ impl_scope! { { if index != self.active { self.active = index; - *cx |= Action::REDRAW; + cx.redraw(&self); } } else { log::warn!("ComboBox::update: unknown entry {msg:?}"); @@ -104,28 +104,32 @@ impl_scope! { } } else { let last = self.len().saturating_sub(1); - match cmd { + let action = match cmd { cmd if cmd.is_activate() => { open_popup(self, cx, FocusSource::Key); if let Some(code) = code { cx.depress_with_key(self.id(), code); } + Action::empty() } - Command::Up => *cx |= self.set_active(self.active.saturating_sub(1)), - Command::Down => *cx |= self.set_active((self.active + 1).min(last)), - Command::Home => *cx |= self.set_active(0), - Command::End => *cx |= self.set_active(last), + Command::Up => self.set_active(self.active.saturating_sub(1)), + Command::Down => self.set_active((self.active + 1).min(last)), + Command::Home => self.set_active(0), + Command::End => self.set_active(last), _ => return Unused, - } + }; + cx.action(self, action); } Used } Event::Scroll(ScrollDelta::LineDelta(_, y)) if !self.popup.is_open() => { if y > 0.0 { - *cx |= self.set_active(self.active.saturating_sub(1)); + let action = self.set_active(self.active.saturating_sub(1)); + cx.action(&self, action); } else if y < 0.0 { let last = self.len().saturating_sub(1); - *cx |= self.set_active((self.active + 1).min(last)); + let action = self.set_active((self.active + 1).min(last)); + cx.action(&self, action); } Used } @@ -172,7 +176,8 @@ impl_scope! { fn handle_messages(&mut self, cx: &mut EventCx, _: &Self::Data) { if let Some(IndexMsg(index)) = cx.try_pop() { - *cx |= self.set_active(index); + let action = self.set_active(index); + cx.action(&self, action); self.popup.close(cx); if let Some(ref f) = self.on_select { if let Some(msg) = cx.try_pop() { diff --git a/crates/kas-widgets/src/dialog.rs b/crates/kas-widgets/src/dialog.rs index ff619699b..4a8f9c3a1 100644 --- a/crates/kas-widgets/src/dialog.rs +++ b/crates/kas-widgets/src/dialog.rs @@ -57,7 +57,7 @@ impl_scope! { fn handle_messages(&mut self, cx: &mut EventCx, _: &Self::Data) { if let Some(MessageBoxOk) = cx.try_pop() { - cx.send_action(Action::CLOSE); + cx.action(self, Action::CLOSE); } } diff --git a/crates/kas-widgets/src/edit.rs b/crates/kas-widgets/src/edit.rs index f8faa7f9c..82075a4cc 100644 --- a/crates/kas-widgets/src/edit.rs +++ b/crates/kas-widgets/src/edit.rs @@ -178,13 +178,15 @@ impl_scope! { // Reset data on focus loss (update is inhibited with focus). // No need if we just sent a message (should cause an update). let string = (edit.guard.value_fn)(data); - *cx |= edit.set_string(string); + let action = edit.set_string(string); + cx.action(edit, action); } fn update(edit: &mut EditField, cx: &mut ConfigCx, data: &A) { if !edit.has_edit_focus() { let string = (edit.guard.value_fn)(data); - *cx |= edit.set_string(string); + let action = edit.set_string(string); + cx.action(edit, action); } } @@ -239,19 +241,22 @@ impl_scope! { // Reset data on focus loss (update is inhibited with focus). // No need if we just sent a message (should cause an update). let value = (edit.guard.value_fn)(data); - *cx |= edit.set_string(format!("{}", value)); + let action = edit.set_string(format!("{}", value)); + cx.action(edit, action); } } fn edit(edit: &mut EditField, cx: &mut EventCx, _: &A) { edit.guard.parsed = edit.get_str().parse().ok(); - *cx |= edit.set_error_state(edit.guard.parsed.is_none()); + let action = edit.set_error_state(edit.guard.parsed.is_none()); + cx.action(edit, action); } fn update(edit: &mut EditField, cx: &mut ConfigCx, data: &A) { if !edit.has_edit_focus() { let value = (edit.guard.value_fn)(data); - *cx |= edit.set_string(format!("{}", value)); + let action = edit.set_string(format!("{}", value)); + cx.action(&edit, action); edit.guard.parsed = None; } } @@ -384,7 +389,8 @@ impl_scope! { impl Self { fn update_scroll_bar(&mut self, cx: &mut EventState) { let max_offset = self.inner.max_scroll_offset().1; - *cx |= self.bar.set_limits(max_offset, self.inner.rect().size.1); + let action = self.bar.set_limits(max_offset, self.inner.rect().size.1); + cx.action(&self, action); self.bar.set_value(cx, self.inner.view_offset.1); } } @@ -687,7 +693,7 @@ impl_scope! { } Event::LostKeyFocus => { self.has_key_focus = false; - cx.redraw(self); + cx.redraw(&self); G::focus_lost(self, cx, data); Used } @@ -1083,7 +1089,7 @@ impl EditField { ); } - cx.redraw(self); + cx.redraw(&self); self.set_view_offset_from_edit_pos(cx); } @@ -1165,7 +1171,7 @@ impl EditField { let action = match cmd { Command::Escape | Command::Deselect if !selection.is_empty() => { self.selection.set_empty(); - cx.redraw(self); + cx.redraw(&self); Action::None } Command::Activate => Action::Activate, @@ -1409,7 +1415,7 @@ impl EditField { self.set_primary(cx); } self.edit_x_coord = x_coord; - cx.redraw(self); + cx.redraw(&self); EditAction::None } }; diff --git a/crates/kas-widgets/src/list.rs b/crates/kas-widgets/src/list.rs index 03750f332..6f943c6dc 100644 --- a/crates/kas-widgets/src/list.rs +++ b/crates/kas-widgets/src/list.rs @@ -282,7 +282,7 @@ impl_scope! { cx.configure(widget.as_node(data), id); self.widgets.push(widget); - *cx |= Action::RESIZE; + cx.resize(self); index } @@ -292,7 +292,7 @@ impl_scope! { pub fn pop(&mut self, cx: &mut EventState) -> Option { let result = self.widgets.pop(); if let Some(w) = result.as_ref() { - *cx |= Action::RESIZE; + cx.resize(&self); if w.id_ref().is_valid() { if let Some(key) = w.id_ref().next_key_after(self.id_ref()) { @@ -318,7 +318,7 @@ impl_scope! { let id = self.make_child_id(index); cx.configure(widget.as_node(data), id); self.widgets.insert(index, widget); - *cx |= Action::RESIZE; + cx.resize(self); } /// Removes the child widget at position `index` @@ -334,7 +334,7 @@ impl_scope! { } } - *cx |= Action::RESIZE; + cx.resize(&self); for v in self.id_map.values_mut() { if *v > index { @@ -360,7 +360,7 @@ impl_scope! { } } - *cx |= Action::RESIZE; + cx.resize(self); w } @@ -382,7 +382,7 @@ impl_scope! { self.widgets.push(w); } - *cx |= Action::RESIZE; + cx.resize(self); } /// Resize, using the given closure to construct new widgets @@ -395,7 +395,7 @@ impl_scope! { let old_len = self.widgets.len(); if len < old_len { - *cx |= Action::RESIZE; + cx.resize(&self); loop { let w = self.widgets.pop().unwrap(); if w.id_ref().is_valid() { @@ -417,7 +417,7 @@ impl_scope! { cx.configure(w.as_node(data), id); self.widgets.push(w); } - *cx |= Action::RESIZE; + cx.resize(self); } } diff --git a/crates/kas-widgets/src/scroll.rs b/crates/kas-widgets/src/scroll.rs index e619119ab..fe8b43698 100644 --- a/crates/kas-widgets/src/scroll.rs +++ b/crates/kas-widgets/src/scroll.rs @@ -86,7 +86,8 @@ impl_scope! { #[inline] fn set_scroll_offset(&mut self, cx: &mut EventCx, offset: Offset) -> Offset { - *cx |= self.scroll.set_offset(offset); + let action = self.scroll.set_offset(offset); + cx.action(&self, action); self.scroll.offset() } } @@ -148,7 +149,8 @@ impl_scope! { } fn handle_scroll(&mut self, cx: &mut EventCx, _: &Self::Data, scroll: Scroll) { - self.scroll.scroll(cx, self.rect(), scroll); + let action = self.scroll.scroll(cx, self.rect(), scroll); + cx.action(self, action); } } } diff --git a/crates/kas-widgets/src/scroll_bar.rs b/crates/kas-widgets/src/scroll_bar.rs index 6098fc63f..60c901a77 100644 --- a/crates/kas-widgets/src/scroll_bar.rs +++ b/crates/kas-widgets/src/scroll_bar.rs @@ -193,7 +193,8 @@ impl_scope! { let changed = value != self.value; if changed { self.value = value; - *cx |= self.grip.set_offset(self.offset()).1; + let action = self.grip.set_offset(self.offset()).1; + cx.action(&self, action); } self.force_visible(cx); changed @@ -249,7 +250,7 @@ impl_scope! { // true if not equal to old value fn apply_grip_offset(&mut self, cx: &mut EventCx, offset: Offset) { let (offset, action) = self.grip.set_offset(offset); - *cx |= action; + cx.action(&self, action); let len = self.bar_len() - self.grip_len; let mut offset = match self.direction.is_vertical() { @@ -326,7 +327,7 @@ impl_scope! { match event { Event::TimerUpdate(_) => { self.force_visible = false; - *cx |= Action::REDRAW; + cx.redraw(self); Used } Event::PressStart { press } => { diff --git a/crates/kas-widgets/src/slider.rs b/crates/kas-widgets/src/slider.rs index 05e8a7bda..4b23cc0a6 100644 --- a/crates/kas-widgets/src/slider.rs +++ b/crates/kas-widgets/src/slider.rs @@ -286,7 +286,7 @@ impl_scope! { } let action = self.set_value(a + self.range.0); if !action.is_empty() { - *cx |= action; + cx.action(&self, action); if let Some(ref f) = self.on_move { f(cx, data, self.value); } @@ -338,7 +338,8 @@ impl_scope! { type Data = A; fn update(&mut self, cx: &mut ConfigCx, data: &A) { - *cx |= self.set_value((self.state_fn)(cx, data)); + let action = self.set_value((self.state_fn)(cx, data)); + cx.action(self, action); } fn handle_event(&mut self, cx: &mut EventCx, data: &A, event: Event) -> IsUsed { @@ -380,7 +381,7 @@ impl_scope! { let action = self.set_value(value); if !action.is_empty() { - *cx |= action; + cx.action(&self, action); if let Some(ref f) = self.on_move { f(cx, data, self.value); } diff --git a/crates/kas-widgets/src/spinner.rs b/crates/kas-widgets/src/spinner.rs index 948eb9a83..e993021f8 100644 --- a/crates/kas-widgets/src/spinner.rs +++ b/crates/kas-widgets/src/spinner.rs @@ -127,7 +127,8 @@ impl EditGuard for SpinnerGuard { fn update(edit: &mut EditField, cx: &mut ConfigCx, data: &A) { let value = (edit.guard.state_fn)(cx, data); - *cx |= edit.set_string(value.to_string()); + let action = edit.set_string(value.to_string()); + cx.action(edit, action); } fn focus_lost(edit: &mut EditField, cx: &mut EventCx, data: &A) { @@ -135,7 +136,8 @@ impl EditGuard for SpinnerGuard { cx.push(ValueMsg(value)); } else { let value = (edit.guard.state_fn)(&cx.config_cx(), data); - *cx |= edit.set_string(value.to_string()); + let action = edit.set_string(value.to_string()); + cx.action(edit, action); } } @@ -147,7 +149,8 @@ impl EditGuard for SpinnerGuard { } else { is_err = true; }; - *cx |= edit.set_error_state(is_err); + let action = edit.set_error_state(is_err); + cx.action(edit, action); } } diff --git a/crates/kas-widgets/src/splitter.rs b/crates/kas-widgets/src/splitter.rs index 120f253c6..bf8dceda0 100644 --- a/crates/kas-widgets/src/splitter.rs +++ b/crates/kas-widgets/src/splitter.rs @@ -259,7 +259,8 @@ impl_scope! { if let Some(GripMsg::PressMove(offset)) = cx.try_pop() { let n = index >> 1; assert!(n < self.handles.len()); - *cx |= self.handles[n].set_offset(offset).1; + let action = self.handles[n].set_offset(offset).1; + cx.action(&self, action); self.adjust_size(&mut cx.config_cx(), n); } } @@ -379,7 +380,7 @@ impl Splitter { self.widgets.push(widget); self.size_solved = false; - *cx |= Action::RESIZE; + cx.resize(self); index } @@ -389,7 +390,7 @@ impl Splitter { pub fn pop(&mut self, cx: &mut EventState) -> Option { let result = self.widgets.pop(); if let Some(w) = result.as_ref() { - *cx |= Action::RESIZE; + cx.resize(&self); if w.id_ref().is_valid() { if let Some(key) = w.id_ref().next_key_after(self.id_ref()) { @@ -433,7 +434,7 @@ impl Splitter { self.widgets.insert(index, widget); self.size_solved = false; - *cx |= Action::RESIZE; + cx.resize(self); } /// Removes the child widget at position `index` @@ -457,7 +458,7 @@ impl Splitter { } } - *cx |= Action::RESIZE; + cx.resize(&self); for v in self.id_map.values_mut() { if *v > index { @@ -484,7 +485,7 @@ impl Splitter { } self.size_solved = false; - *cx |= Action::RESIZE; + cx.resize(self); w } @@ -519,7 +520,7 @@ impl Splitter { } self.size_solved = false; - *cx |= Action::RESIZE; + cx.resize(self); } /// Resize, using the given closure to construct new widgets @@ -535,12 +536,10 @@ impl Splitter { let old_len = self.widgets.len(); if len < old_len { - *cx |= Action::RESIZE; + cx.resize(&self); loop { let result = self.widgets.pop(); if let Some(w) = result.as_ref() { - *cx |= Action::RESIZE; - if w.id_ref().is_valid() { if let Some(key) = w.id_ref().next_key_after(self.id_ref()) { self.id_map.remove(&key); @@ -579,7 +578,7 @@ impl Splitter { } self.size_solved = false; - *cx |= Action::RESIZE; + cx.resize(self); } } } diff --git a/crates/kas-widgets/src/stack.rs b/crates/kas-widgets/src/stack.rs index d264a6420..2be8b2f6c 100644 --- a/crates/kas-widgets/src/stack.rs +++ b/crates/kas-widgets/src/stack.rs @@ -292,15 +292,15 @@ impl Stack { } if entry.1 == State::Configured { - *cx |= Action::RESIZE; + cx.resize(self); } else { debug_assert_eq!(entry.1, State::Sized); entry.0.set_rect(cx, self.core.rect); - *cx |= Action::REGION_MOVED; + cx.region_moved(self); } } else { if old_index < self.widgets.len() { - *cx |= Action::REGION_MOVED; + cx.region_moved(self); } } } @@ -379,7 +379,7 @@ impl Stack { let result = self.widgets.pop().map(|(w, _)| w); if let Some(w) = result.as_ref() { if self.active > 0 && self.active == self.widgets.len() { - *cx |= Action::REGION_MOVED; + cx.region_moved(&self); } if w.id_ref().is_valid() { @@ -430,7 +430,7 @@ impl Stack { if self.active == index { self.active = usize::MAX; - *cx |= Action::REGION_MOVED; + cx.region_moved(&self); } for v in self.id_map.values_mut() { @@ -462,7 +462,7 @@ impl Stack { } if index == self.active { - *cx |= Action::RESIZE; + cx.resize(self); } widget diff --git a/crates/kas-widgets/src/text.rs b/crates/kas-widgets/src/text.rs index 3561b4700..7eab34315 100644 --- a/crates/kas-widgets/src/text.rs +++ b/crates/kas-widgets/src/text.rs @@ -142,10 +142,11 @@ impl_scope! { if self.label.env().bounds.1.is_finite() { // NOTE: bounds are initially infinite. Alignment results in // infinite offset and thus infinite measured height. - match self.label.try_prepare() { - Ok(true) => *cx |= Action::RESIZE, - _ => cx.redraw(self), - } + let action = match self.label.try_prepare() { + Ok(true) => Action::RESIZE, + _ => Action::REDRAW, + }; + cx.action(self, action); } } } diff --git a/examples/clock.rs b/examples/clock.rs index d7823f6c5..619d7f35e 100644 --- a/examples/clock.rs +++ b/examples/clock.rs @@ -144,7 +144,7 @@ impl_scope! { let ns = 1_000_000_000 - (self.now.time().nanosecond() % 1_000_000_000); log::info!("Requesting update in {}ns", ns); cx.request_timer_update(self.id(), 0, Duration::new(0, ns), true); - *cx |= Action::REDRAW; + cx.redraw(self); Used } _ => Unused, diff --git a/examples/data-list-view.rs b/examples/data-list-view.rs index 30d8cf651..91c0c403d 100644 --- a/examples/data-list-view.rs +++ b/examples/data-list-view.rs @@ -95,7 +95,8 @@ impl EditGuard for ListEntryGuard { fn update(edit: &mut EditField, cx: &mut ConfigCx, data: &Item) { if !edit.has_edit_focus() { - *cx |= edit.set_string(data.1.clone()); + let act = edit.set_string(data.1.clone()); + cx.action(edit, act); } } @@ -226,7 +227,8 @@ fn main() -> kas::shell::Result<()> { let data = Data::new(5); let list = ListView::new(MyDriver).on_update(|cx, list, data| { - *cx |= list.set_direction(data.dir); + let act = list.set_direction(data.dir); + cx.action(list, act); }); let tree = kas::column![ "Demonstration of dynamic widget creation / deletion", diff --git a/examples/data-list.rs b/examples/data-list.rs index 2effd09ef..906e859cf 100644 --- a/examples/data-list.rs +++ b/examples/data-list.rs @@ -165,11 +165,12 @@ fn main() -> kas::shell::Result<()> { }; let list = List::new([]).on_update(|cx, list, data: &Data| { - *cx |= list.set_direction(data.dir); + let act = list.set_direction(data.dir); let len = data.len; if len != list.len() { list.resize_with(cx, data, len, ListEntry::new); } + cx.action(list, act); }); let tree = kas::column![ "Demonstration of dynamic widget creation / deletion", diff --git a/examples/gallery.rs b/examples/gallery.rs index 69902fbb8..9449623e2 100644 --- a/examples/gallery.rs +++ b/examples/gallery.rs @@ -95,7 +95,8 @@ fn widgets() -> Box> { fn edit(edit: &mut EditField, cx: &mut EventCx, _: &Data) { // 7a is the colour of *magic*! - *cx |= edit.set_error_state(edit.get_str().len() % (7 + 1) == 0); + let act = edit.set_error_state(edit.get_str().len() % (7 + 1) == 0); + cx.action(edit, act); } } @@ -120,11 +121,12 @@ fn widgets() -> Box> { if let Some(MsgEdit) = cx.try_pop() { // TODO: do not always set text: if this is a true pop-up it // should not normally lose data. - *cx |= self.popup.set_text(data.text.clone()); + let act = self.popup.set_text(data.text.clone()); // let ed = TextEdit::new(text, true); // cx.add_window::<()>(ed.into_window("Edit text")); // TODO: cx.add_modal(..) self.popup.open(cx, &(), self.id()); + cx.action(self, act); } else if let Some(result) = cx.try_pop() { match result { TextEditResult::Cancel => (), @@ -272,7 +274,8 @@ fn editor() -> Box> { fn edit(edit: &mut EditField, cx: &mut EventCx, _: &AppData) { let result = Markdown::new(edit.get_str()); - *cx |= edit.set_error_state(result.is_err()); + let act = edit.set_error_state(result.is_err()); + cx.action(edit, act); cx.push(result.unwrap_or_else(|err| Markdown::new(&format!("{err}")).unwrap())); } } @@ -323,9 +326,10 @@ Demonstration of *as-you-type* formatting from **Markdown**. Direction::Up => Direction::Right, _ => Direction::Up, }; - *cx |= Action::RESIZE; + cx.resize(self); } else if let Some(md) = cx.try_pop::() { - *cx |= self.label.set_text(md); + let act = self.label.set_text(md); + cx.action(self, act); } } } @@ -375,7 +379,8 @@ fn filter_list() -> Box> { let list_view = filter::FilterBoxList::new(ListView::down(ListGuard), filter, guard) .map(|data: &Data| &data.list) .on_update(|cx, list, data| { - *cx |= list.set_selection_mode(data.mode); + let act = list.set_selection_mode(data.mode); + cx.action(list, act); }); let ui = kas::column![ @@ -608,7 +613,7 @@ fn main() -> kas::shell::Result<()> { cx.update(self.as_node(&())); } Menu::Quit => { - *cx |= Action::EXIT; + cx.exit(); } } } From d9ecb286e63ca4886faa8ddb6259df5b9efd4d05 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 7 Oct 2023 14:44:23 +0100 Subject: [PATCH 04/16] List/MatrixView: bug-fix and simplify cur_len --- crates/kas-view/src/list_view.rs | 3 +-- crates/kas-view/src/matrix_view.rs | 12 ++++++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/crates/kas-view/src/list_view.rs b/crates/kas-view/src/list_view.rs index ceecd7c29..53c0dd6ac 100644 --- a/crates/kas-view/src/list_view.rs +++ b/crates/kas-view/src/list_view.rs @@ -315,8 +315,7 @@ impl_scope! { let mut first_data = usize::conv(offset / u64::conv(self.skip)); let data_len: usize = self.data_len.cast(); - let mut cur_len: usize = self.widgets.len(); - cur_len = cur_len.min(data_len - first_data); + let cur_len: usize = self.widgets.len().min(data_len); first_data = first_data.min(data_len - cur_len); self.cur_len = cur_len.cast(); debug_assert!(self.num_children() <= self.widgets.len()); diff --git a/crates/kas-view/src/matrix_view.rs b/crates/kas-view/src/matrix_view.rs index c9509812c..e1bf8507c 100644 --- a/crates/kas-view/src/matrix_view.rs +++ b/crates/kas-view/src/matrix_view.rs @@ -252,13 +252,13 @@ impl_scope! { let offset = self.scroll_offset(); let skip = (self.child_size + self.child_inter_margin).max(Size(1, 1)); - let mut first_col = usize::conv(u64::conv(offset.0) / u64::conv(skip.0)); - let mut first_row = usize::conv(u64::conv(offset.1) / u64::conv(skip.1)); let data_len = data.len(); - let col_len = (data_len.0 - first_col).min(self.alloc_len.cols.cast()); - let row_len = (data_len.1 - first_row).min(self.alloc_len.rows.cast()); - first_col = first_col.min(data_len.0 - col_len); - first_row = first_row.min(data_len.1 - row_len); + let col_len = data_len.0.min(self.alloc_len.cols.cast()); + let row_len = data_len.1.min(self.alloc_len.rows.cast()); + let first_col = usize::conv(u64::conv(offset.0) / u64::conv(skip.0)) + .min(data_len.0 - col_len); + let first_row = usize::conv(u64::conv(offset.1) / u64::conv(skip.1)) + .min(data_len.1 - row_len); self.cur_len = (col_len.cast(), row_len.cast()); debug_assert!(self.num_children() <= self.widgets.len()); self.first_data = (first_row.cast(), first_col.cast()); From 1a92c1205ec58efbb951a71730dcea965ffca78d Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 7 Oct 2023 14:52:41 +0100 Subject: [PATCH 05/16] Rename WidgetId -> Id --- CONTRIBUTING.md | 2 +- crates/kas-core/src/action.rs | 6 +- crates/kas-core/src/core/data.rs | 16 +- crates/kas-core/src/core/impls.rs | 14 +- crates/kas-core/src/core/layout.rs | 40 ++--- crates/kas-core/src/core/node.rs | 66 ++++---- crates/kas-core/src/core/widget.rs | 18 +-- crates/kas-core/src/core/widget_id.rs | 180 ++++++++++----------- crates/kas-core/src/event/components.rs | 6 +- crates/kas-core/src/event/cx/config.rs | 4 +- crates/kas-core/src/event/cx/cx_pub.rs | 72 ++++----- crates/kas-core/src/event/cx/cx_shell.rs | 16 +- crates/kas-core/src/event/cx/mod.rs | 70 ++++---- crates/kas-core/src/event/cx/press.rs | 10 +- crates/kas-core/src/event/events.rs | 4 +- crates/kas-core/src/event/mod.rs | 10 +- crates/kas-core/src/hidden.rs | 18 +-- crates/kas-core/src/layout/mod.rs | 6 +- crates/kas-core/src/layout/visitor.rs | 20 +-- crates/kas-core/src/popup.rs | 8 +- crates/kas-core/src/prelude.rs | 2 +- crates/kas-core/src/root.rs | 12 +- crates/kas-core/src/shell/shared.rs | 2 +- crates/kas-core/src/shell/window.rs | 6 +- crates/kas-core/src/theme/anim.rs | 4 +- crates/kas-core/src/theme/colors.rs | 8 +- crates/kas-core/src/theme/draw.rs | 54 +++---- crates/kas-core/src/theme/flat_theme.rs | 35 +--- crates/kas-core/src/theme/simple_theme.rs | 46 ++---- crates/kas-core/src/util.rs | 4 +- crates/kas-macros/src/extends.rs | 24 +-- crates/kas-macros/src/lib.rs | 2 +- crates/kas-macros/src/make_layout.rs | 6 +- crates/kas-macros/src/widget.rs | 34 ++-- crates/kas-view/src/data_traits.rs | 28 ++-- crates/kas-view/src/driver.rs | 2 +- crates/kas-view/src/list_view.rs | 18 +-- crates/kas-view/src/matrix_view.rs | 18 +-- crates/kas-wgpu/src/shaded_theme.rs | 33 +--- crates/kas-widgets/src/adapt/with_label.rs | 2 +- crates/kas-widgets/src/check_box.rs | 2 +- crates/kas-widgets/src/edit.rs | 4 +- crates/kas-widgets/src/grid.rs | 2 +- crates/kas-widgets/src/list.rs | 6 +- crates/kas-widgets/src/menu/menu_entry.rs | 4 +- crates/kas-widgets/src/menu/menubar.rs | 6 +- crates/kas-widgets/src/menu/mod.rs | 10 +- crates/kas-widgets/src/menu/submenu.rs | 6 +- crates/kas-widgets/src/radio_box.rs | 2 +- crates/kas-widgets/src/scroll.rs | 2 +- crates/kas-widgets/src/scroll_bar.rs | 4 +- crates/kas-widgets/src/scroll_label.rs | 2 +- crates/kas-widgets/src/slider.rs | 2 +- crates/kas-widgets/src/spinner.rs | 4 +- crates/kas-widgets/src/splitter.rs | 10 +- crates/kas-widgets/src/stack.rs | 8 +- crates/kas-widgets/src/tab_stack.rs | 2 +- examples/cursors.rs | 2 +- src/lib.rs | 2 +- 59 files changed, 458 insertions(+), 548 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2cd2b3ac4..4ed4aa3ec 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -44,7 +44,7 @@ Usage of `unsafe` is allowed, but not preferred. Current use cases: - To get around lifetime restrictions on the theme API's `Theme::draw` and `Window::size` methods; this will no longer require `unsafe` once the `generic_associated_types` feature is stabilised. -- `WidgetId` uses `unsafe` code to support both inline and heap-allocated +- `Id` uses `unsafe` code to support both inline and heap-allocated variants. - Implementing `bytemuck::Pod` and `Zeroable`, as required to assert that values may be copied to the GPU. diff --git a/crates/kas-core/src/action.rs b/crates/kas-core/src/action.rs index 91f937333..4261f368e 100644 --- a/crates/kas-core/src/action.rs +++ b/crates/kas-core/src/action.rs @@ -46,10 +46,8 @@ bitflags! { const EVENT_CONFIG = 1 << 11; /// Reconfigure all widgets of the window /// - /// *Configuring* widgets assigns [`WidgetId`] identifiers and calls - /// [`crate::Events::configure`]. - /// - /// [`WidgetId`]: crate::WidgetId + /// *Configuring* widgets assigns [`Id`](crate::Id) identifiers and calls + /// [`Events::configure`](crate::Events::configure). const RECONFIGURE = 1 << 16; /// Update all widgets /// diff --git a/crates/kas-core/src/core/data.rs b/crates/kas-core/src/core/data.rs index bd77940eb..26afa4a5d 100644 --- a/crates/kas-core/src/core/data.rs +++ b/crates/kas-core/src/core/data.rs @@ -5,8 +5,8 @@ //! Widget data types +use super::Id; #[allow(unused)] use super::Widget; -use super::WidgetId; use crate::geom::Rect; #[cfg(feature = "winit")] pub use winit::window::Icon; @@ -37,7 +37,7 @@ impl Icon { #[derive(Default, Debug)] pub struct CoreData { pub rect: Rect, - pub id: WidgetId, + pub id: Id, #[cfg_attr(not(feature = "internal_doc"), doc(hidden))] #[cfg_attr(doc_cfg, doc(cfg(internal_doc)))] #[cfg(debug_assertions)] @@ -77,20 +77,20 @@ pub enum WidgetStatus { #[cfg(debug_assertions)] impl WidgetStatus { - fn require(&self, id: &WidgetId, expected: Self) { + fn require(&self, id: &Id, expected: Self) { if *self < expected { panic!("WidgetStatus of {id}: require {expected:?}, found {self:?}"); } } /// Configure - pub fn configure(&mut self, _id: &WidgetId) { + pub fn configure(&mut self, _id: &Id) { // re-configure does not require repeating other actions *self = (*self).max(WidgetStatus::Configured); } /// Update - pub fn update(&self, id: &WidgetId) { + pub fn update(&self, id: &Id) { self.require(id, WidgetStatus::Configured); // Update-after-configure is already guaranteed (see impls module). @@ -100,7 +100,7 @@ impl WidgetStatus { } /// Size rules - pub fn size_rules(&mut self, id: &WidgetId, axis: crate::layout::AxisInfo) { + pub fn size_rules(&mut self, id: &Id, axis: crate::layout::AxisInfo) { // NOTE: Possibly this is too strict and we should not require // re-running size_rules(vert) or set_rect afterwards? if axis.is_horizontal() { @@ -113,12 +113,12 @@ impl WidgetStatus { } /// Set rect - pub fn set_rect(&mut self, id: &WidgetId) { + pub fn set_rect(&mut self, id: &Id) { self.require(id, WidgetStatus::SizeRulesY); *self = WidgetStatus::SetRect; } - pub fn require_rect(&self, id: &WidgetId) { + pub fn require_rect(&self, id: &Id) { self.require(id, WidgetStatus::SetRect); } } diff --git a/crates/kas-core/src/core/impls.rs b/crates/kas-core/src/core/impls.rs index ffd56cb3e..500f9349c 100644 --- a/crates/kas-core/src/core/impls.rs +++ b/crates/kas-core/src/core/impls.rs @@ -7,14 +7,14 @@ use crate::event::{Event, EventCx, FocusSource, IsUsed, Scroll, Unused, Used}; #[cfg(debug_assertions)] use crate::util::IdentifyWidget; -use crate::{Erased, Events, Layout, NavAdvance, Node, Widget, WidgetId}; +use crate::{Erased, Events, Id, Layout, NavAdvance, Node, Widget}; /// Generic implementation of [`Widget::_send`] pub fn _send( widget: &mut W, cx: &mut EventCx, data: &::Data, - id: WidgetId, + id: Id, disabled: bool, event: Event, ) -> IsUsed { @@ -88,7 +88,7 @@ pub fn _replay( widget: &mut W, cx: &mut EventCx, data: &::Data, - id: WidgetId, + id: Id, msg: Erased, ) { if let Some(index) = widget.find_child_index(&id) { @@ -134,9 +134,9 @@ pub fn _nav_next( widget: &mut W, cx: &mut EventCx, data: &::Data, - focus: Option<&WidgetId>, + focus: Option<&Id>, advance: NavAdvance, -) -> Option { +) -> Option { let navigable = widget.navigable(); nav_next(widget.as_node(data), cx, focus, advance, navigable) } @@ -144,10 +144,10 @@ pub fn _nav_next( fn nav_next( mut widget: Node<'_>, cx: &mut EventCx, - focus: Option<&WidgetId>, + focus: Option<&Id>, advance: NavAdvance, navigable: bool, -) -> Option { +) -> Option { let id = widget.id_ref(); if !id.is_valid() { log::warn!("nav_next: encountered unconfigured node!"); diff --git a/crates/kas-core/src/core/layout.rs b/crates/kas-core/src/core/layout.rs index bd6f60b95..e73b1de47 100644 --- a/crates/kas-core/src/core/layout.rs +++ b/crates/kas-core/src/core/layout.rs @@ -10,7 +10,7 @@ use crate::geom::{Coord, Offset, Rect}; use crate::layout::{AxisInfo, SizeRules}; use crate::theme::{DrawCx, SizeCx}; use crate::util::IdentifyWidget; -use crate::{HasId, WidgetId}; +use crate::{HasId, Id}; use kas_macros::autoimpl; #[allow(unused)] use super::{Events, Widget}; @@ -70,13 +70,13 @@ pub trait Layout { /// Get the widget's identifier /// - /// Note that the default-constructed [`WidgetId`] is *invalid*: any + /// Note that the default-constructed [`Id`] is *invalid*: any /// operations on this value will cause a panic. A valid identifier is /// assigned when the widget is configured (immediately before calling /// [`Events::configure`]). /// /// This method is implemented by the `#[widget]` macro. - fn id_ref(&self) -> &WidgetId { + fn id_ref(&self) -> &Id { unimplemented!() // make rustdoc show that this is a provided method } @@ -121,11 +121,11 @@ pub trait Layout { /// If `Some(index)` is returned, this is *probably* but not guaranteed /// to be a valid child index. /// - /// The default implementation simply uses [`WidgetId::next_key_after`]. + /// The default implementation simply uses [`Id::next_key_after`]. /// Widgets may choose to assign children custom keys by overriding this /// method and [`Events::make_child_id`]. #[inline] - fn find_child_index(&self, id: &WidgetId) -> Option { + fn find_child_index(&self, id: &Id) -> Option { id.next_key_after(self.id_ref()) } @@ -228,7 +228,7 @@ pub trait Layout { Offset::ZERO } - /// Translate a coordinate to a [`WidgetId`] + /// Translate a coordinate to an [`Id`] /// /// This method is used to determine which widget reacts to the mouse cursor /// or a touch event. The result affects mouse-hover highlighting, event @@ -258,7 +258,7 @@ pub trait Layout { /// /// - Widgets should test `self.rect().contains(coord)`, returning `None` /// if this test is `false`; otherwise, they should always return *some* - /// [`WidgetId`], either a childs or their own. + /// [`Id`], either a childs or their own. /// - If the Widget uses a translated coordinate space (i.e. /// `self.translation() != Offset::ZERO`) then pass /// `coord + self.translation()` to children. @@ -276,7 +276,7 @@ pub trait Layout { /// } /// Some(self.id()) /// ``` - fn find_id(&mut self, coord: Coord) -> Option { + fn find_id(&mut self, coord: Coord) -> Option { let _ = coord; unimplemented!() // make rustdoc show that this is a provided method } @@ -290,7 +290,7 @@ pub trait Layout { /// but failure to do so should not cause a fatal error. /// /// The `draw` parameter is pre-parameterized with this widget's - /// [`WidgetId`], allowing drawn components to react to input state. This + /// [`Id`], allowing drawn components to react to input state. This /// implies that when calling `draw` on children, the child's `id` must be /// supplied via [`DrawCx::re_id`] or [`DrawCx::recurse`]. fn draw(&mut self, draw: DrawCx); @@ -298,14 +298,14 @@ pub trait Layout { impl HasId for &W { #[inline] - fn has_id(self) -> WidgetId { + fn has_id(self) -> Id { self.id_ref().clone() } } impl HasId for &mut W { #[inline] - fn has_id(self) -> WidgetId { + fn has_id(self) -> Id { self.id_ref().clone() } } @@ -314,27 +314,27 @@ impl HasId for &mut W { pub trait LayoutExt: Layout { /// Get the widget's identifier /// - /// Note that the default-constructed [`WidgetId`] is *invalid*: any + /// Note that the default-constructed [`Id`] is *invalid*: any /// operations on this value will cause a panic. Valid identifiers are /// assigned during configure. #[inline] - fn id(&self) -> WidgetId { + fn id(&self) -> Id { self.id_ref().clone() } /// Test widget identifier for equality /// - /// This method may be used to test against `WidgetId`, `Option` - /// and `Option<&WidgetId>`. + /// This method may be used to test against `Id`, `Option` + /// and `Option<&Id>`. #[inline] fn eq_id(&self, rhs: T) -> bool where - WidgetId: PartialEq, + Id: PartialEq, { *self.id_ref() == rhs } - /// Display as "StructName#WidgetId" + /// Display as "StructName#Id" #[inline] fn identify(&self) -> IdentifyWidget { IdentifyWidget(self.widget_name(), self.id_ref()) @@ -344,7 +344,7 @@ pub trait LayoutExt: Layout { /// /// This function assumes that `id` is a valid widget. #[inline] - fn is_ancestor_of(&self, id: &WidgetId) -> bool { + fn is_ancestor_of(&self, id: &Id) -> bool { self.id_ref().is_ancestor_of(id) } @@ -352,7 +352,7 @@ pub trait LayoutExt: Layout { /// /// This function assumes that `id` is a valid widget. #[inline] - fn is_strict_ancestor_of(&self, id: &WidgetId) -> bool { + fn is_strict_ancestor_of(&self, id: &Id) -> bool { !self.eq_id(id) && self.id_ref().is_ancestor_of(id) } @@ -388,7 +388,7 @@ pub trait LayoutExt: Layout { /// /// Since `id` represents a path, this operation is normally `O(d)` where /// `d` is the depth of the path (depending on widget implementations). - fn find_widget(&self, id: &WidgetId) -> Option<&dyn Layout> { + fn find_widget(&self, id: &Id) -> Option<&dyn Layout> { if let Some(child) = self.find_child_index(id).and_then(|i| self.get_child(i)) { child.find_widget(id) } else if self.eq_id(id) { diff --git a/crates/kas-core/src/core/node.rs b/crates/kas-core/src/core/node.rs index 47af8cb3f..ecd512c83 100644 --- a/crates/kas-core/src/core/node.rs +++ b/crates/kas-core/src/core/node.rs @@ -10,42 +10,42 @@ use crate::event::{ConfigCx, Event, EventCx, IsUsed}; use crate::geom::{Coord, Rect}; use crate::layout::{AxisInfo, SizeRules}; use crate::theme::{DrawCx, SizeCx}; -use crate::{Erased, Layout, NavAdvance, WidgetId}; +use crate::{Erased, Id, Layout, NavAdvance}; #[cfg(not(feature = "unsafe_node"))] trait NodeT { - fn id_ref(&self) -> &WidgetId; + fn id_ref(&self) -> &Id; fn rect(&self) -> Rect; fn clone_node(&mut self) -> Node<'_>; fn as_layout(&self) -> &dyn Layout; fn num_children(&self) -> usize; - fn find_child_index(&self, id: &WidgetId) -> Option; + fn find_child_index(&self, id: &Id) -> Option; fn for_child_node(&mut self, index: usize, f: Box) + '_>); fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules; fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect); fn nav_next(&self, reverse: bool, from: Option) -> Option; - fn find_id(&mut self, coord: Coord) -> Option; + fn find_id(&mut self, coord: Coord) -> Option; fn _draw(&mut self, draw: DrawCx); - fn _configure(&mut self, cx: &mut ConfigCx, id: WidgetId); + fn _configure(&mut self, cx: &mut ConfigCx, id: Id); fn _update(&mut self, cx: &mut ConfigCx); - fn _send(&mut self, cx: &mut EventCx, id: WidgetId, disabled: bool, event: Event) -> IsUsed; - fn _replay(&mut self, cx: &mut EventCx, id: WidgetId, msg: Erased); + fn _send(&mut self, cx: &mut EventCx, id: Id, disabled: bool, event: Event) -> IsUsed; + fn _replay(&mut self, cx: &mut EventCx, id: Id, msg: Erased); fn _nav_next( &mut self, cx: &mut EventCx, - focus: Option<&WidgetId>, + focus: Option<&Id>, advance: NavAdvance, - ) -> Option; + ) -> Option; } #[cfg(not(feature = "unsafe_node"))] impl<'a, T> NodeT for (&'a mut dyn Widget, &'a T) { - fn id_ref(&self) -> &WidgetId { + fn id_ref(&self) -> &Id { self.0.id_ref() } fn rect(&self) -> Rect { @@ -62,7 +62,7 @@ impl<'a, T> NodeT for (&'a mut dyn Widget, &'a T) { fn num_children(&self) -> usize { self.0.num_children() } - fn find_child_index(&self, id: &WidgetId) -> Option { + fn find_child_index(&self, id: &Id) -> Option { self.0.find_child_index(id) } @@ -80,32 +80,32 @@ impl<'a, T> NodeT for (&'a mut dyn Widget, &'a T) { fn nav_next(&self, reverse: bool, from: Option) -> Option { self.0.nav_next(reverse, from) } - fn find_id(&mut self, coord: Coord) -> Option { + fn find_id(&mut self, coord: Coord) -> Option { self.0.find_id(coord) } fn _draw(&mut self, mut draw: DrawCx) { draw.recurse(&mut self.0); } - fn _configure(&mut self, cx: &mut ConfigCx, id: WidgetId) { + fn _configure(&mut self, cx: &mut ConfigCx, id: Id) { self.0._configure(cx, self.1, id); } fn _update(&mut self, cx: &mut ConfigCx) { self.0._update(cx, self.1); } - fn _send(&mut self, cx: &mut EventCx, id: WidgetId, disabled: bool, event: Event) -> IsUsed { + fn _send(&mut self, cx: &mut EventCx, id: Id, disabled: bool, event: Event) -> IsUsed { self.0._send(cx, self.1, id, disabled, event) } - fn _replay(&mut self, cx: &mut EventCx, id: WidgetId, msg: Erased) { + fn _replay(&mut self, cx: &mut EventCx, id: Id, msg: Erased) { self.0._replay(cx, self.1, id, msg); } fn _nav_next( &mut self, cx: &mut EventCx, - focus: Option<&WidgetId>, + focus: Option<&Id>, advance: NavAdvance, - ) -> Option { + ) -> Option { self.0._nav_next(cx, self.1, focus, advance) } } @@ -163,24 +163,24 @@ impl<'a> Node<'a> { /// Get the widget's identifier #[inline] - pub fn id_ref(&self) -> &WidgetId { + pub fn id_ref(&self) -> &Id { self.0.id_ref() } /// Get the widget's identifier #[inline] - pub fn id(&self) -> WidgetId { + pub fn id(&self) -> Id { self.id_ref().clone() } /// Test widget identifier for equality /// - /// This method may be used to test against `WidgetId`, `Option` - /// and `Option<&WidgetId>`. + /// This method may be used to test against `Id`, `Option` + /// and `Option<&Id>`. #[inline] pub fn eq_id(&self, rhs: T) -> bool where - WidgetId: PartialEq, + Id: PartialEq, { *self.id_ref() == rhs } @@ -189,7 +189,7 @@ impl<'a> Node<'a> { /// /// This function assumes that `id` is a valid widget. #[inline] - pub fn is_ancestor_of(&self, id: &WidgetId) -> bool { + pub fn is_ancestor_of(&self, id: &Id) -> bool { self.id().is_ancestor_of(id) } @@ -197,7 +197,7 @@ impl<'a> Node<'a> { /// /// This function assumes that `id` is a valid widget. #[inline] - pub fn is_strict_ancestor_of(&self, id: &WidgetId) -> bool { + pub fn is_strict_ancestor_of(&self, id: &Id) -> bool { !self.eq_id(id) && self.id().is_ancestor_of(id) } @@ -259,14 +259,14 @@ impl<'a> Node<'a> { /// If `Some(index)` is returned, this is *probably* but not guaranteed /// to be a valid child index. #[inline] - pub fn find_child_index(&self, id: &WidgetId) -> Option { + pub fn find_child_index(&self, id: &Id) -> Option { self.0.find_child_index(id) } /// Find the descendant with this `id`, if any, and call `cb` on it /// /// Returns `Some(result)` if and only if node `id` was found. - pub fn find_node) -> T, T>(&mut self, id: &WidgetId, cb: F) -> Option { + pub fn find_node) -> T, T>(&mut self, id: &Id, cb: F) -> Option { if let Some(index) = self.find_child_index(id) { self.for_child(index, |mut node| node.find_node(id, cb)) .unwrap() @@ -296,8 +296,8 @@ impl<'a> Node<'a> { self.0.nav_next(reverse, from) } - /// Translate a coordinate to a [`WidgetId`] - pub(crate) fn find_id(&mut self, coord: Coord) -> Option { + /// Translate a coordinate to an [`Id`] + pub(crate) fn find_id(&mut self, coord: Coord) -> Option { self.0.find_id(coord) } @@ -316,7 +316,7 @@ impl<'a> Node<'a> { } /// Internal method: configure recursively - pub(crate) fn _configure(&mut self, cx: &mut ConfigCx, id: WidgetId) { + pub(crate) fn _configure(&mut self, cx: &mut ConfigCx, id: Id) { cfg_if::cfg_if! { if #[cfg(feature = "unsafe_node")] { self.0._configure(cx, self.1, id); @@ -341,7 +341,7 @@ impl<'a> Node<'a> { pub(crate) fn _send( &mut self, cx: &mut EventCx, - id: WidgetId, + id: Id, disabled: bool, event: Event, ) -> IsUsed { @@ -355,7 +355,7 @@ impl<'a> Node<'a> { } /// Internal method: replay recursively - pub(crate) fn _replay(&mut self, cx: &mut EventCx, id: WidgetId, msg: Erased) { + pub(crate) fn _replay(&mut self, cx: &mut EventCx, id: Id, msg: Erased) { cfg_if::cfg_if! { if #[cfg(feature = "unsafe_node")] { self.0._replay(cx, self.1, id, msg); @@ -370,9 +370,9 @@ impl<'a> Node<'a> { pub fn _nav_next( &mut self, cx: &mut EventCx, - focus: Option<&WidgetId>, + focus: Option<&Id>, advance: NavAdvance, - ) -> Option { + ) -> Option { cfg_if::cfg_if! { if #[cfg(feature = "unsafe_node")] { self.0._nav_next(cx, self.1, focus, advance) diff --git a/crates/kas-core/src/core/widget.rs b/crates/kas-core/src/core/widget.rs index 112221540..eff6a8c07 100644 --- a/crates/kas-core/src/core/widget.rs +++ b/crates/kas-core/src/core/widget.rs @@ -7,7 +7,7 @@ use super::{Layout, Node}; use crate::event::{ConfigCx, Event, EventCx, IsUsed, Scroll, Unused}; -use crate::{Erased, WidgetId}; +use crate::{Erased, Id}; use kas_macros::autoimpl; #[allow(unused)] use kas_macros as macros; @@ -42,7 +42,7 @@ pub trait Events: Widget + Sized { /// Make an identifier for a child /// /// This is used to assign children identifiers. It may return - /// [`WidgetId::default`] in order to avoid configuring the child, but in + /// [`Id::default`] in order to avoid configuring the child, but in /// this case the widget must configure via another means. /// /// If this is implemented explicitly then [`Layout::find_child_index`] must @@ -50,7 +50,7 @@ pub trait Events: Widget + Sized { /// /// Default impl: `self.id_ref().make_child(index)` #[inline] - fn make_child_id(&mut self, index: usize) -> WidgetId { + fn make_child_id(&mut self, index: usize) -> Id { self.id_ref().make_child(index) } @@ -194,7 +194,7 @@ pub trait Events: Widget + Sized { &mut self, cx: &mut EventCx, data: &Self::Data, - id: &WidgetId, + id: &Id, event: &Event, ) -> IsUsed { let _ = (cx, data, id, event); @@ -386,7 +386,7 @@ pub trait Widget: Layout { /// Internal method: configure recursively #[cfg_attr(not(feature = "internal_doc"), doc(hidden))] #[cfg_attr(doc_cfg, doc(cfg(internal_doc)))] - fn _configure(&mut self, cx: &mut ConfigCx, data: &Self::Data, id: WidgetId); + fn _configure(&mut self, cx: &mut ConfigCx, data: &Self::Data, id: Id); /// Internal method: update recursively #[cfg_attr(not(feature = "internal_doc"), doc(hidden))] @@ -404,7 +404,7 @@ pub trait Widget: Layout { &mut self, cx: &mut EventCx, data: &Self::Data, - id: WidgetId, + id: Id, disabled: bool, event: Event, ) -> IsUsed; @@ -415,7 +415,7 @@ pub trait Widget: Layout { /// `msg` to the message stack. Widget `id` or any ancestor may handle. #[cfg_attr(not(feature = "internal_doc"), doc(hidden))] #[cfg_attr(doc_cfg, doc(cfg(internal_doc)))] - fn _replay(&mut self, cx: &mut EventCx, data: &Self::Data, id: WidgetId, msg: Erased); + fn _replay(&mut self, cx: &mut EventCx, data: &Self::Data, id: Id, msg: Erased); /// Internal method: search for the previous/next navigation target /// @@ -426,7 +426,7 @@ pub trait Widget: Layout { &mut self, cx: &mut EventCx, data: &Self::Data, - focus: Option<&WidgetId>, + focus: Option<&Id>, advance: NavAdvance, - ) -> Option; + ) -> Option; } diff --git a/crates/kas-core/src/core/widget_id.rs b/crates/kas-core/src/core/widget_id.rs index 5bcd7b149..8a94ebd18 100644 --- a/crates/kas-core/src/core/widget_id.rs +++ b/crates/kas-core/src/core/widget_id.rs @@ -121,7 +121,7 @@ impl IntOrPtr { } else { // We expect either USE_BITS or USE_PTR here; anything else indicates an error let v = n & 3; - assert!(v == 1 || v == 2, "WidgetId::opt_from_u64: invalid value"); + assert!(v == 1 || v == 2, "Id::opt_from_u64: invalid value"); let x = NonZeroU64::new(n).unwrap(); Some(IntOrPtr(x, PhantomData)) } @@ -189,7 +189,7 @@ impl<'a> Iterator for PathIter<'a> { } } -/// Iterator over [`WidgetId`] path components +/// Iterator over [`Id`] path components pub struct WidgetPathIter<'a>(PathIter<'a>); impl<'a> Iterator for WidgetPathIter<'a> { type Item = usize; @@ -211,20 +211,20 @@ impl<'a> Iterator for WidgetPathIter<'a> { /// This type may be tested for equality and order and may be iterated over as /// a "path" of "key" values. /// -/// Formatting a `WidgetId` via [`Display`] prints the the path, for example +/// Formatting an `Id` via [`Display`] prints the the path, for example /// `#1290a4`. Here, `#` represents the root; each following hexadecimal digit /// represents a path component except that digits `8-f` are combined with the /// following digit(s). Hence, the above path has components `1`, `2`, `90`, /// `a4`. To interpret these values, first subtract 8 from each digit but the /// last digit, then read as base-8: `[1, 2, 8, 20]`. /// -/// This type is small (64-bit) and non-zero: `Option` has the same -/// size as `WidgetId`. It is also very cheap to `Clone`: usually only one `if` +/// This type is small (64-bit) and non-zero: `Option` has the same +/// size as `Id`. It is also very cheap to `Clone`: usually only one `if` /// check, and in the worst case a pointer dereference and ref-count increment. /// Paths up to 14 digits long (as printed) are represented internally; /// beyond this limit a reference-counted stack allocation is used. /// -/// `WidgetId` is neither `Send` nor `Sync`. +/// `Id` is neither `Send` nor `Sync`. /// /// Identifiers are assigned when configured and when re-configured /// (via [`Action::RECONFIGURE`] or [`ConfigCx::configure`]). @@ -237,7 +237,7 @@ impl<'a> Iterator for WidgetPathIter<'a> { /// [`Action::RECONFIGURE`]: crate::Action::RECONFIGURE /// [`ConfigCx::configure`]: crate::event::ConfigCx::configure #[derive(Clone)] -pub struct WidgetId(IntOrPtr); +pub struct Id(IntOrPtr); // Encode lowest 48 bits of key into the low bits of a u64, returning also the encoded bit-length fn encode(key: usize) -> (u64, u8) { @@ -298,11 +298,11 @@ impl Iterator for BitsIter { } } -impl WidgetId { +impl Id { /// Identifier of the window - pub(crate) const ROOT: Self = WidgetId(IntOrPtr::ROOT); + pub(crate) const ROOT: Self = Id(IntOrPtr::ROOT); - const INVALID: Self = WidgetId(IntOrPtr::INVALID); + const INVALID: Self = Id(IntOrPtr::INVALID); /// Is the identifier valid? /// @@ -316,7 +316,7 @@ impl WidgetId { /// Iterate over path components pub fn iter(&self) -> WidgetPathIter { match self.0.get() { - Variant::Invalid => panic!("WidgetId::iter: invalid"), + Variant::Invalid => panic!("Id::iter: invalid"), Variant::Int(x) => WidgetPathIter(PathIter::Bits(BitsIter::new(x))), Variant::Slice(path) => WidgetPathIter(PathIter::Slice(path.iter().cloned())), } @@ -325,7 +325,7 @@ impl WidgetId { /// Get the path len (number of indices, not storage length) pub fn path_len(&self) -> usize { match self.0.get() { - Variant::Invalid => panic!("WidgetId::path_len: invalid"), + Variant::Invalid => panic!("Id::path_len: invalid"), Variant::Int(x) => BitsIter::new(x).count(), Variant::Slice(path) => path.len(), } @@ -335,7 +335,7 @@ impl WidgetId { pub fn is_ancestor_of(&self, id: &Self) -> bool { match (self.0.get(), id.0.get()) { (Variant::Invalid, _) | (_, Variant::Invalid) => { - panic!("WidgetId::is_ancestor_of: invalid") + panic!("Id::is_ancestor_of: invalid") } (Variant::Slice(_), Variant::Int(_)) => { // This combo will never be created where id is a child. @@ -378,7 +378,7 @@ impl WidgetId { pub fn next_key_after(&self, id: &Self) -> Option { match (id.0.get(), self.0.get()) { (Variant::Invalid, _) | (_, Variant::Invalid) => { - panic!("WidgetId::next_key_after: invalid") + panic!("Id::next_key_after: invalid") } (Variant::Slice(_), Variant::Int(_)) => None, (Variant::Int(parent_x), Variant::Int(child_x)) => { @@ -421,7 +421,7 @@ impl WidgetId { /// /// Note: there is no guarantee that [`Self::as_u64`] on the result will /// match that of the original parent identifier. - pub fn parent(&self) -> Option { + pub fn parent(&self) -> Option { match self.0.get() { Variant::Invalid => None, Variant::Int(x) => { @@ -435,7 +435,7 @@ impl WidgetId { let len = ((bit_len / 4) as u64) << SHIFT_LEN; let mask = MASK_BITS << (56 - bit_len); let id = (x & mask) | len | USE_BITS; - return Some(WidgetId(IntOrPtr::new_int(id))); + return Some(Id(IntOrPtr::new_int(id))); } None } @@ -443,9 +443,7 @@ impl WidgetId { let len = path.len(); if len > 1 { // TODO(opt): in some cases we could make Variant::Int - Some(WidgetId(IntOrPtr::new_iter( - path[0..len - 1].iter().cloned(), - ))) + Some(Id(IntOrPtr::new_iter(path[0..len - 1].iter().cloned()))) } else { None } @@ -460,7 +458,7 @@ impl WidgetId { #[must_use] pub fn make_child(&self, key: usize) -> Self { match self.0.get() { - Variant::Invalid => panic!("WidgetId::make_child: invalid"), + Variant::Invalid => panic!("Id::make_child: invalid"), Variant::Int(self_x) => { // TODO(opt): this bit-packing approach is designed for space-optimisation, but it may // be better to use a simpler, less-compressed approach, possibly with u128 type. @@ -475,14 +473,12 @@ impl WidgetId { let len = (block_len as u64 + used_blocks as u64) << SHIFT_LEN; let rest = bits << 4 * avail_blocks - bit_len + 8; let id = (self_x & MASK_BITS) | rest | len | USE_BITS; - WidgetId(IntOrPtr::new_int(id)) + Id(IntOrPtr::new_int(id)) } else { - WidgetId(IntOrPtr::new_iter(BitsIter::new(self_x).chain(once(key)))) + Id(IntOrPtr::new_iter(BitsIter::new(self_x).chain(once(key)))) } } - Variant::Slice(path) => { - WidgetId(IntOrPtr::new_iter(path.iter().cloned().chain(once(key)))) - } + Variant::Slice(path) => Id(IntOrPtr::new_iter(path.iter().cloned().chain(once(key)))), } } @@ -493,31 +489,31 @@ impl WidgetId { /// - it is guaranteed non-zero /// - it may be passed to [`Self::opt_from_u64`] /// - comparing two `u64` values generated this way will mostly work as - /// an equality check of the source [`WidgetId`], but can return false + /// an equality check of the source [`Id`], but can return false /// negatives (only if each id was generated through separate calls to /// [`Self::make_child`]) pub fn as_u64(&self) -> u64 { self.0.as_u64() } - /// Convert `Option` to `u64` + /// Convert `Option` to `u64` /// /// This value should not be interpreted, except as follows: /// /// - it is zero if and only if `id == None` /// - it may be passed to [`Self::opt_from_u64`] /// - comparing two `u64` values generated this way will mostly work as - /// an equality check of the source [`WidgetId`], but can return false + /// an equality check of the source [`Id`], but can return false /// negatives (only if each id was generated through separate calls to /// [`Self::make_child`]) - pub fn opt_to_u64(id: Option<&WidgetId>) -> u64 { + pub fn opt_to_u64(id: Option<&Id>) -> u64 { match id { None => 0, Some(id) => id.0.as_u64(), } } - /// Convert `u64` to `Option` + /// Convert `u64` to `Option` /// /// Returns `None` if and only if `n == 0`. /// @@ -531,52 +527,52 @@ impl WidgetId { /// or it is but the source instance of `Self` and all clones have been /// destroyed, then some operations on the result of this method will /// attempt to dereference an invalid pointer. - pub unsafe fn opt_from_u64(n: u64) -> Option { - IntOrPtr::opt_from_u64(n).map(WidgetId) + pub unsafe fn opt_from_u64(n: u64) -> Option { + IntOrPtr::opt_from_u64(n).map(Id) } } -impl PartialEq for WidgetId { +impl PartialEq for Id { fn eq(&self, rhs: &Self) -> bool { match (self.0.get(), rhs.0.get()) { - (Variant::Invalid, _) | (_, Variant::Invalid) => panic!("WidgetId::eq: invalid id"), + (Variant::Invalid, _) | (_, Variant::Invalid) => panic!("Id::eq: invalid id"), (Variant::Int(x), Variant::Int(y)) => x == y, _ => self.iter().eq(rhs.iter()), } } } -impl Eq for WidgetId {} +impl Eq for Id {} -impl PartialEq for WidgetId { +impl PartialEq for Id { fn eq(&self, rhs: &str) -> bool { // TODO(opt): we don't have to format to test this let formatted = format!("{self}"); formatted == rhs } } -impl PartialEq<&str> for WidgetId { +impl PartialEq<&str> for Id { fn eq(&self, rhs: &&str) -> bool { self == *rhs } } -impl PartialOrd for WidgetId { +impl PartialOrd for Id { fn partial_cmp(&self, rhs: &Self) -> Option { Some(self.cmp(rhs)) } } -impl Ord for WidgetId { +impl Ord for Id { fn cmp(&self, rhs: &Self) -> Ordering { match (self.0.get(), rhs.0.get()) { - (Variant::Invalid, _) | (_, Variant::Invalid) => panic!("WidgetId::cmp: invalid id"), + (Variant::Invalid, _) | (_, Variant::Invalid) => panic!("Id::cmp: invalid id"), (Variant::Int(x), Variant::Int(y)) => x.cmp(&y), _ => self.iter().cmp(rhs.iter()), } } } -impl Hash for WidgetId { +impl Hash for Id { fn hash(&self, state: &mut H) { match self.0.get() { Variant::Invalid => (), @@ -590,48 +586,48 @@ impl Hash for WidgetId { } } -impl PartialEq> for WidgetId { +impl PartialEq> for Id { #[inline] - fn eq(&self, rhs: &Option) -> bool { + fn eq(&self, rhs: &Option) -> bool { rhs.as_ref().map(|id| id == self).unwrap_or(false) } } -impl<'a> PartialEq> for WidgetId { +impl<'a> PartialEq> for Id { #[inline] - fn eq(&self, rhs: &Option<&'a WidgetId>) -> bool { + fn eq(&self, rhs: &Option<&'a Id>) -> bool { rhs.map(|id| id == self).unwrap_or(false) } } -impl<'a> PartialEq<&'a WidgetId> for WidgetId { +impl<'a> PartialEq<&'a Id> for Id { #[inline] - fn eq(&self, rhs: &&WidgetId) -> bool { + fn eq(&self, rhs: &&Id) -> bool { self == *rhs } } -impl<'a> PartialEq<&'a Option> for WidgetId { +impl<'a> PartialEq<&'a Option> for Id { #[inline] - fn eq(&self, rhs: &&Option) -> bool { + fn eq(&self, rhs: &&Option) -> bool { self == *rhs } } -impl Default for WidgetId { +impl Default for Id { fn default() -> Self { - WidgetId::INVALID + Id::INVALID } } -impl fmt::Debug for WidgetId { +impl fmt::Debug for Id { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - write!(f, "WidgetId({self})") + write!(f, "Id({self})") } } -impl fmt::Display for WidgetId { +impl fmt::Display for Id { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { match self.0.get() { Variant::Invalid => write!(f, "#INVALID"), @@ -656,7 +652,7 @@ impl fmt::Display for WidgetId { } } -/// Types supporting conversion to [`WidgetId`] +/// Types supporting conversion to [`Id`] /// /// A method taking an `id: impl HasId` parameter supports an [`Id`], /// a reference to an [`Id`] (which is thus cloned), @@ -665,26 +661,26 @@ impl fmt::Display for WidgetId { /// Note: in some cases attempting to pass a widget reference does not pass /// borrow checks. In this case pass `widget.id()` explicitly. pub trait HasId { - fn has_id(self) -> WidgetId; + fn has_id(self) -> Id; } -impl HasId for WidgetId { +impl HasId for Id { #[inline] - fn has_id(self) -> WidgetId { + fn has_id(self) -> Id { self } } -impl HasId for &WidgetId { +impl HasId for &Id { #[inline] - fn has_id(self) -> WidgetId { + fn has_id(self) -> Id { self.clone() } } -impl HasId for &mut WidgetId { +impl HasId for &mut Id { #[inline] - fn has_id(self) -> WidgetId { + fn has_id(self) -> Id { self.clone() } } @@ -696,63 +692,63 @@ mod test { #[test] fn size_of_option_widget_id() { use std::mem::size_of; - assert_eq!(size_of::(), 8); - assert_eq!(size_of::(), size_of::>()); + assert_eq!(size_of::(), 8); + assert_eq!(size_of::(), size_of::>()); } #[test] fn test_partial_eq() { - assert_eq!(WidgetId::ROOT, WidgetId::ROOT); - let c1 = WidgetId::ROOT.make_child(0).make_child(15); - let c2 = WidgetId::ROOT.make_child(1).make_child(15); - let c3 = WidgetId::ROOT.make_child(0).make_child(14); - let c4 = WidgetId::ROOT.make_child(0).make_child(15); + assert_eq!(Id::ROOT, Id::ROOT); + let c1 = Id::ROOT.make_child(0).make_child(15); + let c2 = Id::ROOT.make_child(1).make_child(15); + let c3 = Id::ROOT.make_child(0).make_child(14); + let c4 = Id::ROOT.make_child(0).make_child(15); assert!(c1 != c2); assert!(c1 != c3); assert!(c2 != c3); assert_eq!(c1, c4); - assert!(c1 != WidgetId::ROOT); + assert!(c1 != Id::ROOT); - let d1 = WidgetId(IntOrPtr::new_iter([0, 15].iter().cloned())); - let d2 = WidgetId(IntOrPtr::new_iter([1, 15].iter().cloned())); + let d1 = Id(IntOrPtr::new_iter([0, 15].iter().cloned())); + let d2 = Id(IntOrPtr::new_iter([1, 15].iter().cloned())); assert_eq!(c1, d1); assert_eq!(c2, d2); assert!(d1 != d2); - assert!(d1 != WidgetId::ROOT); + assert!(d1 != Id::ROOT); } #[test] #[should_panic] fn test_partial_eq_invalid_1() { - let _ = WidgetId::INVALID == WidgetId::INVALID; + let _ = Id::INVALID == Id::INVALID; } #[test] #[should_panic] fn test_partial_eq_invalid_2() { - let _ = WidgetId::ROOT == WidgetId::INVALID; + let _ = Id::ROOT == Id::INVALID; } #[test] #[should_panic] fn test_partial_eq_invalid_3() { - let _ = WidgetId::INVALID == WidgetId::ROOT; + let _ = Id::INVALID == Id::ROOT; } #[test] fn test_partial_eq_str() { - assert_eq!(WidgetId::ROOT, "#"); - assert!(WidgetId::ROOT != "#0"); - assert_eq!(WidgetId::ROOT.make_child(0).make_child(15), "#097"); - assert_eq!(WidgetId::ROOT.make_child(1).make_child(15), "#197"); - let c3 = WidgetId::ROOT.make_child(0).make_child(14); + assert_eq!(Id::ROOT, "#"); + assert!(Id::ROOT != "#0"); + assert_eq!(Id::ROOT.make_child(0).make_child(15), "#097"); + assert_eq!(Id::ROOT.make_child(1).make_child(15), "#197"); + let c3 = Id::ROOT.make_child(0).make_child(14); assert_eq!(c3, "#096"); assert!(c3 != "#097"); } #[test] fn test_ord() { - let root = WidgetId::ROOT; + let root = Id::ROOT; let c_0 = root.make_child(0); let c_0_0 = c_0.make_child(0); assert!(root < c_0); @@ -762,9 +758,9 @@ mod test { assert!(c_0_0 < c_1); assert!(c_1 < root.make_child(8)); - let d_0 = WidgetId(IntOrPtr::new_iter([0].iter().cloned())); - let d_0_0 = WidgetId(IntOrPtr::new_iter([0, 0].iter().cloned())); - let d_1 = WidgetId(IntOrPtr::new_iter([1].iter().cloned())); + let d_0 = Id(IntOrPtr::new_iter([0].iter().cloned())); + let d_0_0 = Id(IntOrPtr::new_iter([0, 0].iter().cloned())); + let d_1 = Id(IntOrPtr::new_iter([1].iter().cloned())); assert_eq!(d_0.cmp(&c_0), Ordering::Equal); assert_eq!(d_0_0.cmp(&c_0_0), Ordering::Equal); assert_eq!(d_1.cmp(&c_1), Ordering::Equal); @@ -775,7 +771,7 @@ mod test { #[test] #[should_panic] fn test_ord_invalid() { - let _ = WidgetId::INVALID < WidgetId::ROOT; + let _ = Id::INVALID < Id::ROOT; } #[test] @@ -805,7 +801,7 @@ mod test { fn test_parent() { fn test(seq: &[usize]) { println!("seq: {seq:?}"); - let mut id = WidgetId::ROOT; + let mut id = Id::ROOT; let len = seq.len(); for key in &seq[..len - 1] { id = id.make_child(*key); @@ -831,7 +827,7 @@ mod test { #[test] fn test_make_child() { fn test(seq: &[usize], x: u64) { - let mut id = WidgetId::ROOT; + let mut id = Id::ROOT; for key in seq { id = id.make_child(*key); } @@ -854,7 +850,7 @@ mod test { #[test] fn test_display() { fn from_seq(seq: &[usize]) -> String { - let mut id = WidgetId::ROOT; + let mut id = Id::ROOT; for x in seq { id = id.make_child(*x); } @@ -875,7 +871,7 @@ mod test { #[test] fn test_is_ancestor() { fn test(seq: &[usize], seq2: &[usize]) { - let mut id = WidgetId::ROOT; + let mut id = Id::ROOT; for x in seq { id = id.make_child(*x); } @@ -905,12 +901,12 @@ mod test { #[test] fn test_not_ancestor() { fn test(seq: &[usize], seq2: &[usize]) { - let mut id = WidgetId::ROOT; + let mut id = Id::ROOT; for x in seq { id = id.make_child(*x); } println!("id={} val={:x} from {:?}", id, id.as_u64(), seq); - let mut id2 = WidgetId::ROOT; + let mut id2 = Id::ROOT; for x in seq2 { id2 = id2.make_child(*x); } diff --git a/crates/kas-core/src/event/components.rs b/crates/kas-core/src/event/components.rs index c3acd5f1d..1621ca027 100644 --- a/crates/kas-core/src/event/components.rs +++ b/crates/kas-core/src/event/components.rs @@ -10,7 +10,7 @@ use super::*; use crate::cast::traits::*; use crate::geom::{Coord, Offset, Rect, Size, Vec2}; #[allow(unused)] use crate::text::SelectionHelper; -use crate::{Action, WidgetId}; +use crate::{Action, Id}; use kas_macros::impl_default; use std::time::{Duration, Instant}; @@ -266,7 +266,7 @@ impl ScrollComponent { &mut self, cx: &mut EventCx, event: Event, - id: WidgetId, + id: Id, window_rect: Rect, ) -> (bool, IsUsed) { let mut action = Action::empty(); @@ -406,7 +406,7 @@ impl TextInput { /// /// Implements scrolling and text selection behaviour, excluding handling of /// [`Event::Scroll`]. - pub fn handle(&mut self, cx: &mut EventCx, w_id: WidgetId, event: Event) -> TextInputAction { + pub fn handle(&mut self, cx: &mut EventCx, w_id: Id, event: Event) -> TextInputAction { use TextInputAction as Action; match event { Event::PressStart { press } if press.is_primary() => { diff --git a/crates/kas-core/src/event/cx/config.rs b/crates/kas-core/src/event/cx/config.rs index 826f8fbf4..9b64aef88 100644 --- a/crates/kas-core/src/event/cx/config.rs +++ b/crates/kas-core/src/event/cx/config.rs @@ -11,7 +11,7 @@ use crate::geom::{Rect, Size}; use crate::layout::AlignPair; use crate::text::TextApi; use crate::theme::{Feature, SizeCx, TextClass, ThemeSize}; -use crate::{Node, WidgetId}; +use crate::{Id, Node}; use std::ops::{Deref, DerefMut}; #[allow(unused)] use crate::{event::Event, Events, Layout}; @@ -74,7 +74,7 @@ impl<'a> ConfigCx<'a> { /// Pass the `id` to assign to the widget. This is usually constructed with /// [`Events::make_child_id`]. #[inline] - pub fn configure(&mut self, mut widget: Node<'_>, id: WidgetId) { + pub fn configure(&mut self, mut widget: Node<'_>, id: Id) { widget._configure(self, id); } diff --git a/crates/kas-core/src/event/cx/cx_pub.rs b/crates/kas-core/src/event/cx/cx_pub.rs index 334db5d3f..9231f22c1 100644 --- a/crates/kas-core/src/event/cx/cx_pub.rs +++ b/crates/kas-core/src/event/cx/cx_pub.rs @@ -17,7 +17,7 @@ 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, HasId, WidgetId, Window, WindowId}; +use crate::{Action, Erased, HasId, Id, Window, WindowId}; #[allow(unused)] use crate::{Events, Layout}; // for doc-links /// Public API @@ -50,26 +50,26 @@ impl EventState { /// /// Note that `key_focus` implies `sel_focus`. #[inline] - pub fn has_key_focus(&self, w_id: &WidgetId) -> (bool, bool) { + pub fn has_key_focus(&self, w_id: &Id) -> (bool, bool) { let sel_focus = *w_id == self.sel_focus; (sel_focus && self.key_focus, sel_focus) } /// Get whether this widget has navigation focus #[inline] - pub fn has_nav_focus(&self, w_id: &WidgetId) -> bool { + pub fn has_nav_focus(&self, w_id: &Id) -> bool { *w_id == self.nav_focus } /// Get whether the widget is under the mouse cursor #[inline] - pub fn is_hovered(&self, w_id: &WidgetId) -> bool { + pub fn is_hovered(&self, w_id: &Id) -> bool { self.mouse_grab.is_none() && *w_id == self.hover } /// Get whether widget `id` or any of its descendants are under the mouse cursor #[inline] - pub fn is_hovered_recursive(&self, id: &WidgetId) -> bool { + pub fn is_hovered_recursive(&self, id: &Id) -> bool { self.mouse_grab.is_none() && self .hover @@ -79,7 +79,7 @@ impl EventState { } /// Check whether the given widget is visually depressed - pub fn is_depressed(&self, w_id: &WidgetId) -> bool { + pub fn is_depressed(&self, w_id: &Id) -> bool { for (_, id) in &self.key_depress { if *id == w_id { return true; @@ -110,7 +110,7 @@ impl EventState { /// /// A widget is disabled if any ancestor is. #[inline] - pub fn is_disabled(&self, w_id: &WidgetId) -> bool { + pub fn is_disabled(&self, w_id: &Id) -> bool { // TODO(opt): we should be able to use binary search here for id in &self.disabled { if id.is_ancestor_of(w_id) { @@ -172,7 +172,7 @@ impl EventState { /// Disabled status applies to all descendants and blocks reception of /// events ([`Unused`] is returned automatically when the /// recipient or any ancestor is disabled). - pub fn set_disabled(&mut self, w_id: WidgetId, state: bool) { + pub fn set_disabled(&mut self, w_id: Id, state: bool) { for (i, id) in self.disabled.iter().enumerate() { if w_id == id { if !state { @@ -202,13 +202,7 @@ impl EventState { /// /// If multiple updates with the same `id` and `payload` are requested, /// these are merged (using the earliest time if `first` is true). - pub fn request_timer_update( - &mut self, - id: WidgetId, - payload: u64, - delay: Duration, - first: bool, - ) { + pub fn request_timer_update(&mut self, id: Id, payload: u64, delay: Duration, first: bool) { let time = Instant::now() + delay; if let Some(row) = self .time_updates @@ -278,7 +272,7 @@ impl EventState { /// Pass an [action](Self::action) given some `id` #[inline] - pub(crate) fn opt_action(&mut self, id: Option, action: Action) { + pub(crate) fn opt_action(&mut self, id: Option, action: Action) { if let Some(id) = id { self.action(id, action); } @@ -293,15 +287,15 @@ impl EventState { /// Only one widget can be a fallback, and the *first* to set itself wins. /// This is primarily used to allow scroll-region widgets to /// respond to navigation keys when no widget has focus. - pub fn register_nav_fallback(&mut self, id: WidgetId) { + pub fn register_nav_fallback(&mut self, id: Id) { if self.nav_fallback.is_none() { log::debug!(target: "kas_core::event","register_nav_fallback: id={id}"); self.nav_fallback = Some(id); } } - fn access_layer_for_id(&mut self, id: &WidgetId) -> Option<&mut AccessLayer> { - let root = &WidgetId::ROOT; + fn access_layer_for_id(&mut self, id: &Id) -> Option<&mut AccessLayer> { + let root = &Id::ROOT; for (k, v) in self.access_layers.range_mut(root..=id).rev() { if k.is_ancestor_of(id) { return Some(v); @@ -322,7 +316,7 @@ impl EventState { /// /// If `alt_bypass` is true, then this layer's access keys will be /// active even without Alt pressed (but only highlighted with Alt pressed). - pub fn new_access_layer(&mut self, id: WidgetId, alt_bypass: bool) { + pub fn new_access_layer(&mut self, id: Id, alt_bypass: bool) { self.access_layers.insert(id, (alt_bypass, HashMap::new())); } @@ -332,7 +326,7 @@ impl EventState { /// disable alt-bypass for the access-key layer containing its access keys. /// This allows access keys to be used as shortcuts without the Alt /// key held. See also [`EventState::new_access_layer`]. - pub fn enable_alt_bypass(&mut self, id: &WidgetId, alt_bypass: bool) { + pub fn enable_alt_bypass(&mut self, id: &Id, alt_bypass: bool) { if let Some(layer) = self.access_layer_for_id(id) { layer.0 = alt_bypass; } @@ -356,7 +350,7 @@ impl EventState { /// /// This should only be called from [`Events::configure`]. #[inline] - pub fn add_access_key(&mut self, id: &WidgetId, key: Key) { + pub fn add_access_key(&mut self, id: &Id, key: Key) { if let Some(layer) = self.access_layer_for_id(id) { layer.1.entry(key).or_insert_with(|| id.clone()); } @@ -372,7 +366,7 @@ impl EventState { /// Note that keyboard shortcuts and mnemonics should usually match against /// the "logical key". [`KeyCode`] is used here since the the logical key /// may be changed by modifier keys. - pub fn depress_with_key(&mut self, id: WidgetId, code: KeyCode) { + pub fn depress_with_key(&mut self, id: Id, code: KeyCode) { if self.key_depress.values().any(|v| *v == id) { return; } @@ -394,7 +388,7 @@ impl EventState { /// /// When key focus is lost, [`Event::LostKeyFocus`] is sent. #[inline] - pub fn request_key_focus(&mut self, id: WidgetId) -> bool { + pub fn request_key_focus(&mut self, id: Id) -> bool { self.set_sel_focus(id, true); true } @@ -413,7 +407,7 @@ impl EventState { /// /// When key focus is lost, [`Event::LostSelFocus`] is sent. #[inline] - pub fn request_sel_focus(&mut self, id: WidgetId) -> bool { + pub fn request_sel_focus(&mut self, id: Id) -> bool { self.set_sel_focus(id, false); true } @@ -432,7 +426,7 @@ impl EventState { /// /// Queues a redraw and returns `true` if the depress target changes, /// otherwise returns `false`. - pub fn set_grab_depress(&mut self, source: PressSource, target: Option) -> bool { + pub fn set_grab_depress(&mut self, source: PressSource, target: Option) -> bool { let mut old = None; let mut redraw = false; match source { @@ -460,7 +454,7 @@ impl EventState { } /// Returns true if `id` or any descendant has a mouse or touch grab - pub fn any_pin_on(&self, id: &WidgetId) -> bool { + pub fn any_pin_on(&self, id: &Id) -> bool { if self .mouse_grab .as_ref() @@ -479,7 +473,7 @@ impl EventState { /// /// This is the widget selected by navigating the UI with the Tab key. #[inline] - pub fn nav_focus(&self) -> Option<&WidgetId> { + pub fn nav_focus(&self) -> Option<&Id> { self.nav_focus.as_ref() } @@ -503,7 +497,7 @@ impl EventState { /// Normally, [`Events::navigable`] will be true for widget `id` but this /// is not checked or required. For example, a `ScrollLabel` can receive /// focus on text selection with the mouse. - pub fn set_nav_focus(&mut self, id: WidgetId, source: FocusSource) { + pub fn set_nav_focus(&mut self, id: Id, source: FocusSource) { if id == self.nav_focus || !self.config.nav_focus { return; } @@ -534,7 +528,7 @@ impl EventState { /// If `reverse`, instead search for the previous or last navigable widget. pub fn next_nav_focus( &mut self, - target: impl Into>, + target: impl Into>, reverse: bool, source: FocusSource, ) { @@ -570,7 +564,7 @@ impl EventState { /// [`Events::handle_messages`]. // // TODO: Can we identify the calling widget `id` via the context (EventCx)? - pub fn push_async(&mut self, id: WidgetId, fut: Fut) + pub fn push_async(&mut self, id: Id, fut: Fut) where Fut: IntoFuture + 'static, M: Debug + 'static, @@ -581,7 +575,7 @@ impl EventState { /// Asynchronously push a type-erased message to the stack via a [`Future`] /// /// This is a low-level variant of [`Self::push_async`]. - pub fn push_async_erased(&mut self, id: WidgetId, fut: Fut) + pub fn push_async_erased(&mut self, id: Id, fut: Fut) where Fut: IntoFuture + 'static, { @@ -601,7 +595,7 @@ impl EventState { /// documentation of configuration. #[cfg(feature = "spawn")] #[cfg_attr(doc_cfg, doc(cfg(feature = "spawn")))] - pub fn push_spawn(&mut self, id: WidgetId, fut: Fut) + pub fn push_spawn(&mut self, id: Id, fut: Fut) where Fut: IntoFuture + 'static, Fut::IntoFuture: Send, @@ -616,15 +610,15 @@ impl EventState { /// widget. E.g. if widget `w` adds a new child, it may call /// `request_reconfigure(self.id())` but not /// `request_reconfigure(child_id)` (which would cause a panic: - /// `'WidgetId::next_key_after: invalid'`). - pub fn request_reconfigure(&mut self, id: WidgetId) { + /// `'Id::next_key_after: invalid'`). + pub fn request_reconfigure(&mut self, id: Id) { self.pending.push_back(Pending::Configure(id)); } /// Request update to widget `id` /// /// Schedules a call to [`Events::update`] on widget `id`. - pub fn request_update(&mut self, id: WidgetId) { + pub fn request_update(&mut self, id: Id) { self.pending.push_back(Pending::Update(id)); } } @@ -637,7 +631,7 @@ impl<'a> EventCx<'a> { /// /// This is a shortcut to [`ConfigCx::configure`]. #[inline] - pub fn configure(&mut self, mut widget: Node<'_>, id: WidgetId) { + pub fn configure(&mut self, mut widget: Node<'_>, id: Id) { widget._configure(&mut self.config_cx(), id); } @@ -678,7 +672,7 @@ impl<'a> EventCx<'a> { /// (TODO: do we need another method to find this target?) /// - Some events such as [`Event::PressMove`] contain embedded widget /// identifiers which may affect handling of the event. - pub fn send(&mut self, id: WidgetId, event: Event) { + pub fn send(&mut self, id: Id, event: Event) { self.pending.push_back(Pending::Send(id, event)); } @@ -939,7 +933,7 @@ 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 set_grab_cursor(&mut self, id: &WidgetId, icon: CursorIcon) { + pub fn set_grab_cursor(&mut self, id: &Id, icon: CursorIcon) { if let Some(ref grab) = self.mouse_grab { if grab.start_id == *id { 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 12fac04f3..9338b5103 100644 --- a/crates/kas-core/src/event/cx/cx_shell.rs +++ b/crates/kas-core/src/event/cx/cx_shell.rs @@ -13,7 +13,7 @@ use super::*; use crate::cast::traits::*; use crate::geom::{Coord, DVec2}; use crate::theme::ThemeSize; -use crate::{Action, NavAdvance, WidgetId, Window}; +use crate::{Action, Id, NavAdvance, Window}; // TODO: this should be configurable or derived from the system const DOUBLE_CLICK_TIMEOUT: Duration = Duration::from_secs(1); @@ -68,7 +68,7 @@ impl EventState { /// is created (before or after resizing). /// /// This method calls [`ConfigCx::configure`] in order to assign - /// [`WidgetId`] identifiers and call widgets' [`Events::configure`] + /// [`Id`] identifiers and call widgets' [`Events::configure`] /// method. Additionally, it updates the [`EventState`] to account for /// renamed and removed widgets. pub(crate) fn full_configure( @@ -78,7 +78,7 @@ impl EventState { win: &mut Window, data: &A, ) { - let id = WidgetId::ROOT.make_child(wid.get().cast()); + let id = Id::ROOT.make_child(wid.get().cast()); log::debug!(target: "kas_core::event", "full_configure of Window{id}"); self.action.remove(Action::RECONFIGURE); @@ -304,7 +304,7 @@ impl<'a> EventCx<'a> { use winit::event::{MouseScrollDelta, TouchPhase, WindowEvent::*}; match event { - CloseRequested => self.action(WidgetId::ROOT, Action::CLOSE), + CloseRequested => self.action(Id::ROOT, Action::CLOSE), /* Not yet supported: see #98 DroppedFile(path) => , HoveredFile(path) => , @@ -314,7 +314,7 @@ impl<'a> EventCx<'a> { self.window_has_focus = state; if state { // Required to restart theme animations - self.action(WidgetId::ROOT, Action::REDRAW); + self.action(Id::ROOT, Action::REDRAW); } else { // Window focus lost: close all popups while let Some(id) = self.popups.last().map(|(id, _, _)| *id) { @@ -364,7 +364,7 @@ impl<'a> EventCx<'a> { let state = modifiers.state(); if state.alt_key() != self.modifiers.alt_key() { // This controls drawing of access key indicators - self.action(WidgetId::ROOT, Action::REDRAW); + self.action(Id::ROOT, Action::REDRAW); } self.modifiers = state; } @@ -543,7 +543,7 @@ impl<'a> EventCx<'a> { } if redraw { - self.action(WidgetId::ROOT, Action::REDRAW); + self.action(Id::ROOT, Action::REDRAW); } else if let Some(pan_grab) = pan_grab { if usize::conv(pan_grab.1) < MAX_PAN_GRABS { if let Some(pan) = self.pan_grab.get_mut(usize::conv(pan_grab.0)) { @@ -554,7 +554,7 @@ impl<'a> EventCx<'a> { } ev @ (TouchPhase::Ended | TouchPhase::Cancelled) => { if let Some(mut grab) = self.remove_touch(touch.id) { - self.action(WidgetId::ROOT, grab.flush_click_move()); + self.action(Id::ROOT, grab.flush_click_move()); 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 0c610a42f..8f20d6ef5 100644 --- a/crates/kas-core/src/event/cx/mod.rs +++ b/crates/kas-core/src/event/cx/mod.rs @@ -24,7 +24,7 @@ use crate::geom::Coord; use crate::shell::{Platform, ShellSharedErased, WindowDataErased}; use crate::util::WidgetHierarchy; use crate::LayoutExt; -use crate::{Action, Erased, ErasedStack, NavAdvance, Node, Widget, WidgetId, WindowId}; +use crate::{Action, Erased, ErasedStack, Id, NavAdvance, Node, Widget, WindowId}; mod config; mod cx_pub; @@ -60,7 +60,7 @@ impl GrabMode { #[derive(Clone, Debug)] enum GrabDetails { - Click { cur_id: Option }, + Click { cur_id: Option }, Grab, Pan((u16, u16)), } @@ -75,8 +75,8 @@ impl GrabDetails { struct MouseGrab { button: MouseButton, repetitions: u32, - start_id: WidgetId, - depress: Option, + start_id: Id, + depress: Option, details: GrabDetails, } @@ -104,9 +104,9 @@ impl<'a> EventCx<'a> { #[derive(Clone, Debug)] struct TouchGrab { id: u64, - start_id: WidgetId, - depress: Option, - cur_id: Option, + start_id: Id, + depress: Option, + cur_id: Option, last_move: Coord, coord: Coord, mode: GrabMode, @@ -135,7 +135,7 @@ const MAX_PAN_GRABS: usize = 2; #[derive(Clone, Debug)] struct PanGrab { - id: WidgetId, + id: Id, mode: GrabMode, source_is_touch: bool, n: u16, @@ -145,17 +145,17 @@ struct PanGrab { #[derive(Clone, Debug)] #[allow(clippy::enum_variant_names)] // they all happen to be about Focus enum Pending { - Configure(WidgetId), - Update(WidgetId), - Send(WidgetId, Event), + Configure(Id), + Update(Id), + Send(Id, Event), NextNavFocus { - target: Option, + target: Option, reverse: bool, source: FocusSource, }, } -type AccessLayer = (bool, HashMap); +type AccessLayer = (bool, HashMap); /// Event context state /// @@ -177,17 +177,17 @@ type AccessLayer = (bool, HashMap); pub struct EventState { config: WindowConfig, platform: Platform, - disabled: Vec, + disabled: Vec, window_has_focus: bool, modifiers: ModifiersState, /// key focus is on same widget as sel_focus; otherwise its value is ignored key_focus: bool, - sel_focus: Option, - nav_focus: Option, - nav_fallback: Option, - hover: Option, + sel_focus: Option, + nav_focus: Option, + nav_fallback: Option, + hover: Option, hover_icon: CursorIcon, - key_depress: LinearMap, + key_depress: LinearMap, last_mouse_coord: Coord, last_click_button: MouseButton, last_click_repetitions: u32, @@ -195,13 +195,13 @@ pub struct EventState { mouse_grab: Option, touch_grab: SmallVec<[TouchGrab; 8]>, pan_grab: SmallVec<[PanGrab; 4]>, - access_layers: BTreeMap, + access_layers: BTreeMap, // For each: (WindowId of popup, popup descriptor, old nav focus) - popups: SmallVec<[(WindowId, crate::PopupDescriptor, Option); 16]>, - popup_removed: SmallVec<[(WidgetId, WindowId); 16]>, - time_updates: Vec<(Instant, WidgetId, u64)>, + popups: SmallVec<[(WindowId, crate::PopupDescriptor, Option); 16]>, + popup_removed: SmallVec<[(Id, WindowId); 16]>, + time_updates: Vec<(Instant, Id, u64)>, // Set of futures of messages together with id of sending widget - fut_messages: Vec<(WidgetId, Pin>>)>, + fut_messages: Vec<(Id, Pin>>)>, // FIFO queue of events pending handling pending: VecDeque, #[cfg_attr(not(feature = "internal_doc"), doc(hidden))] @@ -212,7 +212,7 @@ pub struct EventState { /// internals impl EventState { #[inline] - fn key_focus(&self) -> Option { + fn key_focus(&self) -> Option { if self.key_focus { self.sel_focus.clone() } else { @@ -222,7 +222,7 @@ impl EventState { fn set_pan_on( &mut self, - id: WidgetId, + id: Id, mode: GrabMode, source_is_touch: bool, coord: Coord, @@ -337,7 +337,7 @@ impl EventState { } // Set selection focus to `wid`; if `key_focus` also set that - fn set_sel_focus(&mut self, wid: WidgetId, key_focus: bool) { + fn set_sel_focus(&mut self, wid: Id, key_focus: bool) { log::trace!("set_sel_focus: wid={wid}, key_focus={key_focus}"); // The widget probably already has nav focus, but anyway: self.set_nav_focus(wid.clone(), FocusSource::Synthetic); @@ -365,7 +365,7 @@ impl EventState { // Clear old hover, set new hover, send events. // If there is a popup, only permit descendands of that. - fn set_hover(&mut self, mut w_id: Option) { + fn set_hover(&mut self, mut w_id: Option) { if let Some(ref id) = w_id { if let Some(popup) = self.popups.last() { if !popup.1.id.is_ancestor_of(id) { @@ -430,7 +430,7 @@ impl<'a> EventCx<'a> { if let Some(cmd) = opt_command { let mut targets = vec![]; - let mut send = |_self: &mut Self, id: WidgetId, cmd| -> bool { + let mut send = |_self: &mut Self, id: Id, cmd| -> bool { if !targets.contains(&id) { let event = Event::Command(cmd, Some(code)); let used = _self.send_event(widget.re(), id.clone(), event); @@ -520,7 +520,7 @@ impl<'a> EventCx<'a> { } // Clears mouse grab and pan grab, resets cursor and redraws - fn remove_mouse_grab(&mut self, success: bool) -> Option<(WidgetId, Event)> { + fn remove_mouse_grab(&mut self, success: bool) -> Option<(Id, Event)> { if let Some(grab) = self.mouse_grab.take() { log::trace!("remove_mouse_grab: start_id={}", grab.start_id); self.window.set_cursor_icon(self.hover_icon); @@ -555,7 +555,7 @@ impl<'a> EventCx<'a> { } /// Replay a message as if it was pushed by `id` - fn replay(&mut self, mut widget: Node<'_>, id: WidgetId, msg: Erased) { + fn replay(&mut self, mut widget: Node<'_>, id: Id, msg: Erased) { debug_assert!(self.scroll == Scroll::None); debug_assert!(self.last_child.is_none()); self.messages.set_base(); @@ -568,7 +568,7 @@ impl<'a> EventCx<'a> { // Wrapper around Self::send; returns true when event is used #[inline] - fn send_event(&mut self, widget: Node<'_>, id: WidgetId, event: Event) -> bool { + fn send_event(&mut self, widget: Node<'_>, id: Id, event: Event) -> bool { self.messages.set_base(); let used = self.send_event_impl(widget, id, event); self.last_child = None; @@ -577,7 +577,7 @@ impl<'a> EventCx<'a> { } // Send an event; possibly leave messages on the stack - fn send_event_impl(&mut self, mut widget: Node<'_>, mut id: WidgetId, event: Event) -> bool { + fn send_event_impl(&mut self, mut widget: Node<'_>, mut id: Id, event: Event) -> bool { debug_assert!(self.scroll == Scroll::None); debug_assert!(self.last_child.is_none()); self.messages.set_base(); @@ -600,7 +600,7 @@ impl<'a> EventCx<'a> { widget._send(self, id, disabled, event) == Used } - fn send_popup_first(&mut self, mut widget: Node<'_>, id: Option, event: Event) { + fn send_popup_first(&mut self, mut widget: Node<'_>, id: Option, event: Event) { while let Some(pid) = self.popups.last().map(|(_, p, _)| p.id.clone()) { let mut target = pid; if let Some(id) = id.clone() { @@ -622,7 +622,7 @@ impl<'a> EventCx<'a> { pub fn next_nav_focus_impl( &mut self, mut widget: Node, - target: Option, + target: Option, reverse: bool, source: FocusSource, ) { diff --git a/crates/kas-core/src/event/cx/press.rs b/crates/kas-core/src/event/cx/press.rs index 2a64b04b1..211ad3c20 100644 --- a/crates/kas-core/src/event/cx/press.rs +++ b/crates/kas-core/src/event/cx/press.rs @@ -10,7 +10,7 @@ use super::{EventCx, GrabMode, IsUsed, MouseGrab, TouchGrab}; use crate::event::cx::GrabDetails; use crate::event::{CursorIcon, MouseButton, Unused, Used}; use crate::geom::Coord; -use crate::{Action, WidgetId}; +use crate::{Action, Id}; /// Source of `EventChild::Press` #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] @@ -80,7 +80,7 @@ pub struct Press { /// Source pub source: PressSource, /// Identifier of current widget - pub id: Option, + pub id: Option, /// Current coordinate pub coord: Coord, } @@ -108,7 +108,7 @@ impl Press { /// is returned. It is expected that the requested press/pan events are all /// "used" ([`Used`]). #[inline] - pub fn grab(&self, id: WidgetId) -> GrabBuilder { + pub fn grab(&self, id: Id) -> GrabBuilder { GrabBuilder { id, source: self.source, @@ -124,7 +124,7 @@ impl Press { /// Conclude by calling [`Self::with_cx`]. #[must_use] pub struct GrabBuilder { - id: WidgetId, + id: Id, source: PressSource, coord: Coord, mode: GrabMode, @@ -155,7 +155,7 @@ 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 [`Id`] 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). diff --git a/crates/kas-core/src/event/events.rs b/crates/kas-core/src/event/events.rs index 417ec608a..906bfbc34 100644 --- a/crates/kas-core/src/event/events.rs +++ b/crates/kas-core/src/event/events.rs @@ -12,7 +12,7 @@ use super::{EventCx, IsUsed, Unused, Used}; #[allow(unused)] use super::{EventState, GrabMode}; use super::{Key, KeyCode, KeyEvent, Press}; use crate::geom::{DVec2, Offset}; -use crate::{dir::Direction, WidgetId, WindowId}; +use crate::{dir::Direction, Id, WindowId}; #[allow(unused)] use crate::{Events, Popup}; /// Events addressed to a widget @@ -263,7 +263,7 @@ impl Event { pub fn on_activate IsUsed>( self, cx: &mut EventCx, - id: WidgetId, + id: Id, f: F, ) -> IsUsed { match self { diff --git a/crates/kas-core/src/event/mod.rs b/crates/kas-core/src/event/mod.rs index 20a384dba..8ac4fc539 100644 --- a/crates/kas-core/src/event/mod.rs +++ b/crates/kas-core/src/event/mod.rs @@ -8,13 +8,13 @@ //! ## Event handling model //! //! Note: widgets are represented as an acyclic tree, with the *root* at the -//! "top" of the tree. Each tree node is a [`Widget`] and has a [`WidgetId`]. -//! A [`WidgetId`] represents a *path* and may be used to find the most +//! "top" of the tree. Each tree node is a [`Widget`] and has an [`Id`]. +//! An [`Id`] represents a *path* and may be used to find the most //! direct root from the root to the target. //! //! An [`Event`] is [sent](EventCx::send) to a target widget as follows: //! -//! 1. Determine the target's [`WidgetId`]. For example, this may be +//! 1. Determine the target's [`Id`]. For example, this may be //! the [`nav_focus`](EventState::nav_focus) or may be determined from //! from mouse/touch coordinates by calling [`find_id`](crate::Layout::find_id). //! 2. If the target is [disabled](EventState::is_disabled), then find the @@ -22,7 +22,7 @@ //! inhibit calling of [`Events::handle_event`] on this widget (but still //! unwind, calling [`Events::handle_event`] on ancestors)). //! 3. Traverse *down* the widget tree from its root to the target according to -//! the [`WidgetId`]. On each node (excluding the target), +//! the [`Id`]. On each node (excluding the target), //! //! - Call [`Events::steal_event`]; if this method "steals" the event, //! skip to step 5. @@ -55,7 +55,7 @@ //! widgets under a menu. This should be intuitive: UI which is in focus and //! not greyed-out should be interactive. //! -//! [`WidgetId`]: crate::WidgetId +//! [`Id`]: crate::Id pub mod components; pub mod config; diff --git a/crates/kas-core/src/hidden.rs b/crates/kas-core/src/hidden.rs index 51d526868..9281b18f8 100644 --- a/crates/kas-core/src/hidden.rs +++ b/crates/kas-core/src/hidden.rs @@ -15,7 +15,7 @@ use crate::geom::{Coord, Offset, Rect}; use crate::layout::{Align, AxisInfo, SizeRules}; use crate::text::{Text, TextApi}; use crate::theme::{DrawCx, SizeCx, TextClass}; -use crate::{Erased, Layout, NavAdvance, Node, Widget, WidgetId}; +use crate::{Erased, Id, Layout, NavAdvance, Node, Widget}; use kas_macros::{autoimpl, impl_scope}; impl_scope! { @@ -99,7 +99,7 @@ impl_scope! { } #[inline] - fn id_ref(&self) -> &WidgetId { + fn id_ref(&self) -> &Id { self.inner.id_ref() } @@ -123,7 +123,7 @@ impl_scope! { } #[inline] - fn find_child_index(&self, id: &WidgetId) -> Option { + fn find_child_index(&self, id: &Id) -> Option { self.inner.find_child_index(id) } @@ -148,7 +148,7 @@ impl_scope! { } #[inline] - fn find_id(&mut self, coord: Coord) -> Option { + fn find_id(&mut self, coord: Coord) -> Option { self.inner.find_id(coord) } @@ -175,17 +175,17 @@ impl_scope! { self.inner.for_child_node(&(), index, closure) } - fn _configure(&mut self, cx: &mut ConfigCx, _: &A, id: WidgetId) { + fn _configure(&mut self, cx: &mut ConfigCx, _: &A, id: Id) { self.inner._configure(cx, &(), id); } fn _update(&mut self, _: &mut ConfigCx, _: &A) {} - fn _send(&mut self, cx: &mut EventCx, _: &A, id: WidgetId, disabled: bool, event: Event) -> IsUsed { + fn _send(&mut self, cx: &mut EventCx, _: &A, id: Id, disabled: bool, event: Event) -> IsUsed { self.inner._send(cx, &(), id, disabled, event) } - fn _replay(&mut self, cx: &mut EventCx, _: &A, id: WidgetId, msg: Erased) { + fn _replay(&mut self, cx: &mut EventCx, _: &A, id: Id, msg: Erased) { self.inner._replay(cx, &(), id, msg); } @@ -193,9 +193,9 @@ impl_scope! { &mut self, cx: &mut EventCx, _: &A, - focus: Option<&WidgetId>, + focus: Option<&Id>, advance: NavAdvance, - ) -> Option { + ) -> Option { self.inner._nav_next(cx, &(), focus, advance) } } diff --git a/crates/kas-core/src/layout/mod.rs b/crates/kas-core/src/layout/mod.rs index 367efe896..bed2ed967 100644 --- a/crates/kas-core/src/layout/mod.rs +++ b/crates/kas-core/src/layout/mod.rs @@ -49,7 +49,7 @@ use crate::dir::{Direction, Directional, Directions}; use crate::event::ConfigCx; use crate::geom::{Coord, Rect}; use crate::theme::{DrawCx, SizeCx}; -use crate::WidgetId; +use crate::Id; #[allow(unused)] use crate::Layout; @@ -260,10 +260,10 @@ pub trait AutoLayout { /// This functions identically to [`Layout::set_rect`]. fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect); - /// Translate a coordinate to a [`WidgetId`] + /// Translate a coordinate to an [`Id`] /// /// This functions identically to [`Layout::find_id`]. - fn find_id(&mut self, coord: Coord) -> Option; + fn find_id(&mut self, coord: Coord) -> Option; /// Draw a widget and its children /// diff --git a/crates/kas-core/src/layout/visitor.rs b/crates/kas-core/src/layout/visitor.rs index ed472f24b..daa03bd12 100644 --- a/crates/kas-core/src/layout/visitor.rs +++ b/crates/kas-core/src/layout/visitor.rs @@ -16,7 +16,7 @@ use crate::draw::color::Rgb; use crate::event::ConfigCx; use crate::geom::{Coord, Offset, Rect, Size}; use crate::theme::{Background, DrawCx, FrameStyle, MarginStyle, SizeCx}; -use crate::WidgetId; +use crate::Id; use crate::{dir::Directional, dir::Directions, Layout}; use std::iter::ExactSizeIterator; @@ -35,12 +35,12 @@ pub trait Visitable { /// This method is identical to [`Layout::set_rect`]. fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect); - /// Translate a coordinate to a [`WidgetId`] + /// Translate a coordinate to an [`Id`] /// /// Implementations should recursively call `find_id` on children, returning - /// `None` if no child returns a `WidgetId`. + /// `None` if no child returns an `Id`. /// This method is simplified relative to [`Layout::find_id`]. - fn find_id(&mut self, coord: Coord) -> Option; + fn find_id(&mut self, coord: Coord) -> Option; /// Draw a widget and its children /// @@ -263,10 +263,10 @@ impl<'a> Visitor<'a> { /// Does not return the widget's own identifier. See example usage in /// [`Visitor::find_id`]. #[inline] - pub fn find_id(mut self, coord: Coord) -> Option { + pub fn find_id(mut self, coord: Coord) -> Option { self.find_id_(coord) } - fn find_id_(&mut self, coord: Coord) -> Option { + fn find_id_(&mut self, coord: Coord) -> Option { match &mut self.layout { LayoutType::BoxComponent(layout) => layout.find_id(coord), LayoutType::Single(child) | LayoutType::AlignSingle(child, _) => child.find_id(coord), @@ -336,7 +336,7 @@ where } } - fn find_id(&mut self, coord: Coord) -> Option { + fn find_id(&mut self, coord: Coord) -> Option { // TODO(opt): more efficient search strategy? self.children.find_map(|child| child.find_id(coord)) } @@ -374,7 +374,7 @@ where } } - fn find_id(&mut self, coord: Coord) -> Option { + fn find_id(&mut self, coord: Coord) -> Option { self.children.find_map(|child| child.find_id(coord)) } @@ -415,7 +415,7 @@ impl<'a, W: Layout, D: Directional> Visitable for Slice<'a, W, D> { } } - fn find_id(&mut self, coord: Coord) -> Option { + fn find_id(&mut self, coord: Coord) -> Option { let solver = RowPositionSolver::new(self.direction); solver .find_child_mut(self.children, coord) @@ -454,7 +454,7 @@ where } } - fn find_id(&mut self, coord: Coord) -> Option { + fn find_id(&mut self, coord: Coord) -> Option { // TODO(opt): more efficient search strategy? self.children.find_map(|(_, child)| child.find_id(coord)) } diff --git a/crates/kas-core/src/popup.rs b/crates/kas-core/src/popup.rs index 1ecb8bb60..a090ea5d6 100644 --- a/crates/kas-core/src/popup.rs +++ b/crates/kas-core/src/popup.rs @@ -7,15 +7,15 @@ use crate::dir::Direction; use crate::event::{ConfigCx, Event, EventCx, IsUsed, Scroll, Unused, Used}; -use crate::{Events, LayoutExt, Widget, WidgetId, WindowId}; +use crate::{Events, Id, LayoutExt, Widget, WindowId}; use kas_macros::{autoimpl, impl_scope, widget_index}; #[allow(unused)] use crate::event::EventState; #[derive(Clone, Debug)] pub(crate) struct PopupDescriptor { - pub id: WidgetId, - pub parent: WidgetId, + pub id: Id, + pub parent: Id, pub direction: Direction, } @@ -127,7 +127,7 @@ impl_scope! { /// /// Returns `true` when the popup is newly opened. In this case, the /// caller may wish to call [`EventState::next_nav_focus`] next. - pub fn open(&mut self, cx: &mut EventCx, data: &W::Data, parent: WidgetId) -> bool { + pub fn open(&mut self, cx: &mut EventCx, data: &W::Data, parent: Id) -> bool { if self.win_id.is_some() { return false; } diff --git a/crates/kas-core/src/prelude.rs b/crates/kas-core/src/prelude.rs index a95e6d278..dcb7d6703 100644 --- a/crates/kas-core/src/prelude.rs +++ b/crates/kas-core/src/prelude.rs @@ -27,6 +27,6 @@ pub use crate::text::{EditableTextApi, TextApi, TextApiExt}; pub use crate::{autoimpl, impl_anon, impl_default, impl_scope, widget, widget_index}; #[doc(no_inline)] pub use crate::{Events, Layout, LayoutExt, Widget, Window, WindowCommand}; -#[doc(no_inline)] pub use crate::{HasId, WidgetId}; +#[doc(no_inline)] pub use crate::{HasId, Id}; #[doc(no_inline)] pub use crate::{HasScrollBars, Node, ScrollBarMode, Scrollable}; diff --git a/crates/kas-core/src/root.rs b/crates/kas-core/src/root.rs index 74f35fdb9..491d22f61 100644 --- a/crates/kas-core/src/root.rs +++ b/crates/kas-core/src/root.rs @@ -12,7 +12,7 @@ use crate::event::{ConfigCx, Event, EventCx, IsUsed, ResizeDirection, Scroll, Un use crate::geom::{Coord, Offset, Rect, Size}; use crate::layout::{self, AxisInfo, SizeRules}; use crate::theme::{DrawCx, FrameStyle, SizeCx}; -use crate::{Action, Events, Icon, Layout, LayoutExt, Widget, WidgetId}; +use crate::{Action, Events, Icon, Id, Layout, LayoutExt, Widget}; use kas_macros::impl_scope; use smallvec::SmallVec; use std::num::NonZeroU32; @@ -146,7 +146,7 @@ impl_scope! { self.inner.set_rect(cx, Rect::new(p_in, s_in)); } - fn find_id(&mut self, _: Coord) -> Option { + fn find_id(&mut self, _: Coord) -> Option { unimplemented!() } @@ -156,7 +156,7 @@ impl_scope! { } impl Self { - pub(crate) fn find_id(&mut self, data: &Data, coord: Coord) -> Option { + pub(crate) fn find_id(&mut self, data: &Data, coord: Coord) -> Option { if !self.core.rect.contains(coord) { return None; } @@ -413,7 +413,7 @@ impl Window { let index = self.popups.len(); self.popups.push((id, popup, Offset::ZERO)); self.resize_popup(&mut cx.config_cx(), data, index); - cx.action(WidgetId::ROOT, Action::REDRAW); + cx.action(Id::ROOT, Action::REDRAW); } /// Trigger closure of a pop-up @@ -423,7 +423,7 @@ impl Window { for i in 0..self.popups.len() { if id == self.popups[i].0 { self.popups.remove(i); - cx.action(WidgetId::ROOT, Action::REGION_MOVED); + cx.action(Id::ROOT, Action::REGION_MOVED); return; } } @@ -442,7 +442,7 @@ impl Window { // Search for a widget by `id`. On success, return that widget's [`Rect`] and // the translation of its children. -fn find_rect(widget: &dyn Layout, id: WidgetId, mut translation: Offset) -> Option<(Rect, Offset)> { +fn find_rect(widget: &dyn Layout, id: Id, mut translation: Offset) -> Option<(Rect, Offset)> { let mut widget = widget; loop { if widget.eq_id(&id) { diff --git a/crates/kas-core/src/shell/shared.rs b/crates/kas-core/src/shell/shared.rs index 02f674bdd..059d5f615 100644 --- a/crates/kas-core/src/shell/shared.rs +++ b/crates/kas-core/src/shell/shared.rs @@ -109,7 +109,7 @@ where impl> ShellShared { /// Return the next window identifier /// - /// TODO(opt): this should recycle used identifiers since WidgetId does not + /// TODO(opt): this should recycle used identifiers since Id does not /// efficiently represent large numbers. pub(crate) fn next_window_id(&mut self) -> WindowId { let id = self.window_id + 1; diff --git a/crates/kas-core/src/shell/window.rs b/crates/kas-core/src/shell/window.rs index 60b2c8ea8..fa4133a73 100644 --- a/crates/kas-core/src/shell/window.rs +++ b/crates/kas-core/src/shell/window.rs @@ -15,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::{autoimpl, Action, AppData, ErasedStack, Layout, LayoutExt, Widget, WidgetId, WindowId}; +use kas::{autoimpl, Action, AppData, ErasedStack, Id, Layout, LayoutExt, Widget, WindowId}; use std::mem::take; use std::time::{Duration, Instant}; use winit::event::WindowEvent; @@ -358,12 +358,12 @@ impl> Window { } pub(super) fn send_action(&mut self, action: Action) { - self.ev_state.action(WidgetId::ROOT, action); + self.ev_state.action(Id::ROOT, action); } pub(super) fn send_close(&mut self, shared: &mut SharedState, id: WindowId) { if id == self.window_id { - self.ev_state.action(WidgetId::ROOT, Action::CLOSE); + self.ev_state.action(Id::ROOT, Action::CLOSE); } else if let Some(window) = self.window.as_ref() { let widget = &mut self.widget; let mut messages = ErasedStack::new(); diff --git a/crates/kas-core/src/theme/anim.rs b/crates/kas-core/src/theme/anim.rs index 7be58be46..e4485b2da 100644 --- a/crates/kas-core/src/theme/anim.rs +++ b/crates/kas-core/src/theme/anim.rs @@ -6,7 +6,7 @@ //! Animation helpers use crate::draw::DrawImpl; -use crate::WidgetId; +use crate::Id; use std::marker::PhantomData; use std::time::{Duration, Instant}; @@ -69,7 +69,7 @@ impl AnimState { /// Flashing text cursor: return true to draw /// /// Assumption: only one widget may draw a text cursor at any time. - pub fn text_cursor(&mut self, draw: &mut D, id: &WidgetId, byte: usize) -> bool { + pub fn text_cursor(&mut self, draw: &mut D, id: &Id, byte: usize) -> bool { let entry = &mut self.text_cursor; if entry.widget == id.as_u64() && entry.byte == byte { if entry.time < self.now { diff --git a/crates/kas-core/src/theme/colors.rs b/crates/kas-core/src/theme/colors.rs index ce530d098..82369e1b8 100644 --- a/crates/kas-core/src/theme/colors.rs +++ b/crates/kas-core/src/theme/colors.rs @@ -8,7 +8,7 @@ use crate::draw::color::{Rgba, Rgba8Srgb}; use crate::event::EventState; use crate::theme::Background; -use crate::WidgetId; +use crate::Id; use std::str::FromStr; const MULT_DEPRESS: f32 = 0.75; @@ -46,7 +46,7 @@ bitflags::bitflags! { impl InputState { /// Construct, setting all components - pub fn new_all(ev: &EventState, id: &WidgetId) -> Self { + pub fn new_all(ev: &EventState, id: &Id) -> Self { let mut state = Self::new_except_depress(ev, id); if ev.is_depressed(id) { state |= InputState::DEPRESS; @@ -55,7 +55,7 @@ impl InputState { } /// Construct, setting all but depress status - pub fn new_except_depress(ev: &EventState, id: &WidgetId) -> Self { + pub fn new_except_depress(ev: &EventState, id: &Id) -> Self { let (key_focus, sel_focus) = ev.has_key_focus(id); let mut state = InputState::empty(); if ev.is_disabled(id) { @@ -77,7 +77,7 @@ impl InputState { } /// Construct, setting all components, also setting hover from `id2` - pub fn new2(ev: &EventState, id: &WidgetId, id2: &WidgetId) -> Self { + pub fn new2(ev: &EventState, id: &Id, id2: &Id) -> Self { let mut state = Self::new_all(ev, id); if ev.is_hovered(id2) { state |= InputState::HOVER; diff --git a/crates/kas-core/src/theme/draw.rs b/crates/kas-core/src/theme/draw.rs index c52216502..f0926a396 100644 --- a/crates/kas-core/src/theme/draw.rs +++ b/crates/kas-core/src/theme/draw.rs @@ -12,7 +12,7 @@ use crate::draw::{Draw, DrawIface, DrawShared, DrawSharedImpl, ImageId, PassType use crate::event::{ConfigCx, EventState}; use crate::geom::{Offset, Rect}; use crate::text::{TextApi, TextDisplay}; -use crate::{autoimpl, Layout, WidgetId}; +use crate::{autoimpl, Id, Layout}; use std::convert::AsRef; use std::ops::{Bound, Range, RangeBounds}; use std::time::Instant; @@ -41,7 +41,7 @@ pub enum Background { /// `&W` from `&mut W`, since the latter would cause borrow conflicts pub struct DrawCx<'a> { h: &'a mut dyn ThemeDraw, - id: WidgetId, + id: Id, } impl<'a> DrawCx<'a> { @@ -51,7 +51,7 @@ impl<'a> DrawCx<'a> { /// coercion: essentially, the pointer is copied under a new, shorter, lifetime. /// Until rfcs#1403 lands, reborrows on user types require a method call. #[inline(always)] - pub fn re_id<'b>(&'b mut self, id: WidgetId) -> DrawCx<'b> + pub fn re_id<'b>(&'b mut self, id: Id) -> DrawCx<'b> where 'a: 'b, { @@ -83,7 +83,7 @@ impl<'a> DrawCx<'a> { /// Construct from a [`DrawCx`] and [`EventState`] #[cfg_attr(not(feature = "internal_doc"), doc(hidden))] #[cfg_attr(doc_cfg, doc(cfg(internal_doc)))] - pub fn new(h: &'a mut dyn ThemeDraw, id: WidgetId) -> Self { + pub fn new(h: &'a mut dyn ThemeDraw, id: Id) -> Self { DrawCx { h, id } } @@ -400,7 +400,7 @@ pub trait ThemeDraw { /// Draw a frame inside the given `rect` /// /// The frame dimensions are given by [`ThemeSize::frame`]. - fn frame(&mut self, id: &WidgetId, rect: Rect, style: FrameStyle, bg: Background); + fn frame(&mut self, id: &Id, rect: Rect, style: FrameStyle, bg: Background); /// Draw a separator in the given `rect` fn separator(&mut self, rect: Rect); @@ -412,7 +412,7 @@ pub trait ThemeDraw { /// /// [`ConfigCx::text_set_size`] should be called prior to this method to /// select a font, font size and wrap options (based on the [`TextClass`]). - fn text(&mut self, id: &WidgetId, rect: Rect, text: &TextDisplay, class: TextClass); + fn text(&mut self, id: &Id, rect: Rect, text: &TextDisplay, class: TextClass); /// Draw text with effects /// @@ -422,12 +422,12 @@ pub trait ThemeDraw { /// /// [`ConfigCx::text_set_size`] should be called prior to this method to /// select a font, font size and wrap options (based on the [`TextClass`]). - fn text_effects(&mut self, id: &WidgetId, rect: Rect, text: &dyn TextApi, class: TextClass); + fn text_effects(&mut self, id: &Id, rect: Rect, text: &dyn TextApi, class: TextClass); /// Method used to implement [`DrawCx::text_selected`] fn text_selected_range( &mut self, - id: &WidgetId, + id: &Id, rect: Rect, text: &TextDisplay, range: Range, @@ -440,7 +440,7 @@ pub trait ThemeDraw { /// select a font, font size and wrap options (based on the [`TextClass`]). fn text_cursor( &mut self, - id: &WidgetId, + id: &Id, rect: Rect, text: &TextDisplay, class: TextClass, @@ -455,7 +455,7 @@ pub trait ThemeDraw { /// The theme may animate transitions. To achieve this, `last_change` should be /// the time of the last state change caused by the user, or none when the /// last state change was programmatic. - fn check_box(&mut self, id: &WidgetId, rect: Rect, checked: bool, last_change: Option); + fn check_box(&mut self, id: &Id, rect: Rect, checked: bool, last_change: Option); /// Draw UI element: radio button /// @@ -465,50 +465,36 @@ pub trait ThemeDraw { /// The theme may animate transitions. To achieve this, `last_change` should be /// the time of the last state change caused by the user, or none when the /// last state change was programmatic. - fn radio_box(&mut self, id: &WidgetId, rect: Rect, checked: bool, last_change: Option); + fn radio_box(&mut self, id: &Id, rect: Rect, checked: bool, last_change: Option); /// Draw UI element: mark - fn mark(&mut self, id: &WidgetId, rect: Rect, style: MarkStyle); + fn mark(&mut self, id: &Id, rect: Rect, style: MarkStyle); /// Draw UI element: scroll bar /// - /// - `id`: [`WidgetId`] of the bar - /// - `grip_id`: [`WidgetId`] of the grip + /// - `id`: [`Id`] of the bar + /// - `grip_id`: [`Id`] of the grip /// - `rect`: area of whole widget (slider track) /// - `grip_rect`: area of slider grip /// - `dir`: direction of bar - fn scroll_bar( - &mut self, - id: &WidgetId, - grip_id: &WidgetId, - rect: Rect, - grip_rect: Rect, - dir: Direction, - ); + fn scroll_bar(&mut self, id: &Id, grip_id: &Id, rect: Rect, grip_rect: Rect, dir: Direction); /// Draw UI element: slider /// - /// - `id`: [`WidgetId`] of the bar - /// - `grip_id`: [`WidgetId`] of the grip + /// - `id`: [`Id`] of the bar + /// - `grip_id`: [`Id`] of the grip /// - `rect`: area of whole widget (slider track) /// - `grip_rect`: area of slider grip /// - `dir`: direction of slider (currently only LTR or TTB) - fn slider( - &mut self, - id: &WidgetId, - grip_id: &WidgetId, - rect: Rect, - grip_rect: Rect, - dir: Direction, - ); + fn slider(&mut self, id: &Id, grip_id: &Id, rect: Rect, grip_rect: Rect, dir: Direction); /// Draw UI element: progress bar /// - /// - `id`: [`WidgetId`] of the bar + /// - `id`: [`Id`] of the bar /// - `rect`: area of whole widget /// - `dir`: direction of progress bar /// - `value`: progress value, between 0.0 and 1.0 - fn progress_bar(&mut self, id: &WidgetId, rect: Rect, dir: Direction, value: f32); + fn progress_bar(&mut self, id: &Id, rect: Rect, dir: Direction, value: f32); /// Draw an image fn image(&mut self, id: ImageId, rect: Rect); diff --git a/crates/kas-core/src/theme/flat_theme.rs b/crates/kas-core/src/theme/flat_theme.rs index 4a395d38b..9fa992360 100644 --- a/crates/kas-core/src/theme/flat_theme.rs +++ b/crates/kas-core/src/theme/flat_theme.rs @@ -20,7 +20,7 @@ use kas::theme::dimensions as dim; use kas::theme::{Background, FrameStyle, MarkStyle, TextClass}; use kas::theme::{ColorsLinear, Config, InputState, Theme}; use kas::theme::{ThemeControl, ThemeDraw, ThemeSize}; -use kas::{Action, WidgetId}; +use kas::{Action, Id}; // Used to ensure a rectangular background is inside a circular corner. // Also the maximum inner radius of circular borders to overlap with this rect. @@ -242,7 +242,7 @@ where inner } - pub fn edit_box(&mut self, id: &WidgetId, outer: Quad, bg: Background) { + pub fn edit_box(&mut self, id: &Id, outer: Quad, bg: Background) { let state = InputState::new_except_depress(self.ev, id); let col_bg = self.cols.from_edit_bg(bg, state); if col_bg != self.cols.background { @@ -351,7 +351,7 @@ where f(&mut handle); } - fn frame(&mut self, id: &WidgetId, rect: Rect, style: FrameStyle, bg: Background) { + fn frame(&mut self, id: &Id, rect: Rect, style: FrameStyle, bg: Background) { let outer = Quad::conv(rect); match style { FrameStyle::None => { @@ -430,13 +430,7 @@ where } } - fn check_box( - &mut self, - id: &WidgetId, - rect: Rect, - checked: bool, - last_change: Option, - ) { + fn check_box(&mut self, id: &Id, rect: Rect, checked: bool, last_change: Option) { let state = InputState::new_all(self.ev, id); let outer = Quad::conv(rect); let inner = outer.shrink(self.w.dims.button_frame as f32); @@ -455,13 +449,7 @@ where self.check_mark(inner, state, checked, last_change); } - fn radio_box( - &mut self, - id: &WidgetId, - rect: Rect, - checked: bool, - last_change: Option, - ) { + fn radio_box(&mut self, id: &Id, rect: Rect, checked: bool, last_change: Option) { let anim_fade = 1.0 - self.w.anim.fade_bool(self.draw.draw, checked, last_change); let state = InputState::new_all(self.ev, id); @@ -500,14 +488,7 @@ where } } - fn scroll_bar( - &mut self, - id: &WidgetId, - id2: &WidgetId, - rect: Rect, - h_rect: Rect, - _: Direction, - ) { + fn scroll_bar(&mut self, id: &Id, id2: &Id, rect: Rect, h_rect: Rect, _: Direction) { // track let outer = Quad::conv(rect); let inner = outer.shrink(outer.size().min_comp() / 2.0); @@ -525,7 +506,7 @@ where self.draw.rounded_frame(outer, inner, 0.0, col); } - fn slider(&mut self, id: &WidgetId, id2: &WidgetId, rect: Rect, h_rect: Rect, dir: Direction) { + fn slider(&mut self, id: &Id, id2: &Id, rect: Rect, h_rect: Rect, dir: Direction) { let state = InputState::new2(self.ev, id, id2); // track @@ -583,7 +564,7 @@ where self.draw.circle(outer, 14.0 / 16.0, col); } - fn progress_bar(&mut self, _: &WidgetId, rect: Rect, dir: Direction, value: f32) { + fn progress_bar(&mut self, _: &Id, rect: Rect, dir: Direction, value: f32) { let mut outer = Quad::conv(rect); let inner = outer.shrink(outer.size().min_comp() / 2.0); self.draw.rounded_frame(outer, inner, 0.75, self.cols.frame); diff --git a/crates/kas-core/src/theme/simple_theme.rs b/crates/kas-core/src/theme/simple_theme.rs index 068cbf591..b0e1a715a 100644 --- a/crates/kas-core/src/theme/simple_theme.rs +++ b/crates/kas-core/src/theme/simple_theme.rs @@ -21,7 +21,7 @@ use kas::theme::dimensions as dim; use kas::theme::{Background, FrameStyle, MarkStyle, TextClass}; use kas::theme::{ColorsLinear, Config, InputState, Theme}; use kas::theme::{SelectionStyle, ThemeControl, ThemeDraw, ThemeSize}; -use kas::{Action, WidgetId}; +use kas::{Action, Id}; /// A simple theme /// @@ -216,7 +216,7 @@ where inner } - pub fn edit_box(&mut self, id: &WidgetId, outer: Quad, bg: Background) { + pub fn edit_box(&mut self, id: &Id, outer: Quad, bg: Background) { let state = InputState::new_except_depress(self.ev, id); let col_bg = self.cols.from_edit_bg(bg, state); if col_bg != self.cols.background { @@ -316,7 +316,7 @@ where self.draw.get_clip_rect() } - fn frame(&mut self, id: &WidgetId, rect: Rect, style: FrameStyle, bg: Background) { + fn frame(&mut self, id: &Id, rect: Rect, style: FrameStyle, bg: Background) { let outer = Quad::conv(rect); match style { FrameStyle::None => { @@ -387,7 +387,7 @@ where } } - fn text(&mut self, id: &WidgetId, rect: Rect, text: &TextDisplay, _: TextClass) { + fn text(&mut self, id: &Id, rect: Rect, text: &TextDisplay, _: TextClass) { let col = if self.ev.is_disabled(id) { self.cols.text_disabled } else { @@ -396,7 +396,7 @@ where self.draw.text(rect, text, col); } - fn text_effects(&mut self, id: &WidgetId, rect: Rect, text: &dyn TextApi, class: TextClass) { + fn text_effects(&mut self, id: &Id, rect: Rect, text: &dyn TextApi, class: TextClass) { let col = if self.ev.is_disabled(id) { self.cols.text_disabled } else { @@ -412,7 +412,7 @@ where fn text_selected_range( &mut self, - id: &WidgetId, + id: &Id, rect: Rect, text: &TextDisplay, range: Range, @@ -458,14 +458,7 @@ where self.draw.text_effects_rgba(rect, text, &effects); } - fn text_cursor( - &mut self, - id: &WidgetId, - rect: Rect, - text: &TextDisplay, - _: TextClass, - byte: usize, - ) { + fn text_cursor(&mut self, id: &Id, rect: Rect, text: &TextDisplay, _: TextClass, byte: usize) { if self.ev.window_has_focus() && !self.w.anim.text_cursor(self.draw.draw, id, byte) { return; } @@ -500,7 +493,7 @@ where } } - fn check_box(&mut self, id: &WidgetId, rect: Rect, checked: bool, _: Option) { + fn check_box(&mut self, id: &Id, rect: Rect, checked: bool, _: Option) { let state = InputState::new_all(self.ev, id); let outer = Quad::conv(rect); @@ -515,17 +508,11 @@ where } } - fn radio_box( - &mut self, - id: &WidgetId, - rect: Rect, - checked: bool, - last_change: Option, - ) { + fn radio_box(&mut self, id: &Id, rect: Rect, checked: bool, last_change: Option) { self.check_box(id, rect, checked, last_change); } - fn mark(&mut self, id: &WidgetId, rect: Rect, style: MarkStyle) { + fn mark(&mut self, id: &Id, rect: Rect, style: MarkStyle) { let col = if self.ev.is_disabled(id) { self.cols.text_disabled } else { @@ -543,14 +530,7 @@ where self.draw_mark(rect, style, col); } - fn scroll_bar( - &mut self, - id: &WidgetId, - id2: &WidgetId, - rect: Rect, - h_rect: Rect, - _: Direction, - ) { + fn scroll_bar(&mut self, id: &Id, id2: &Id, rect: Rect, h_rect: Rect, _: Direction) { let track = Quad::conv(rect); self.draw.rect(track, self.cols.frame); @@ -560,7 +540,7 @@ where self.draw.rect(grip, col); } - fn slider(&mut self, id: &WidgetId, id2: &WidgetId, rect: Rect, h_rect: Rect, _: Direction) { + fn slider(&mut self, id: &Id, id2: &Id, rect: Rect, h_rect: Rect, _: Direction) { let track = Quad::conv(rect); self.draw.rect(track, self.cols.frame); @@ -570,7 +550,7 @@ where self.draw.rect(grip, col); } - fn progress_bar(&mut self, _: &WidgetId, rect: Rect, dir: Direction, value: f32) { + fn progress_bar(&mut self, _: &Id, rect: Rect, dir: Direction, value: f32) { let mut outer = Quad::conv(rect); self.draw.rect(outer, self.cols.frame); diff --git a/crates/kas-core/src/util.rs b/crates/kas-core/src/util.rs index 58e571053..b308992ac 100644 --- a/crates/kas-core/src/util.rs +++ b/crates/kas-core/src/util.rs @@ -6,13 +6,13 @@ //! Utilities use crate::geom::Coord; -use crate::{Layout, LayoutExt, WidgetId}; +use crate::{Id, Layout, LayoutExt}; use std::fmt; /// Helper to display widget identification (e.g. `MyWidget#01`) /// /// Constructed by [`crate::LayoutExt::identify`]. -pub struct IdentifyWidget<'a>(pub(crate) &'static str, pub(crate) &'a WidgetId); +pub struct IdentifyWidget<'a>(pub(crate) &'static str, pub(crate) &'a Id); impl<'a> fmt::Display for IdentifyWidget<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { write!(f, "{}{}", self.0, self.1) diff --git a/crates/kas-macros/src/extends.rs b/crates/kas-macros/src/extends.rs index 37e797085..b5f9bd55c 100644 --- a/crates/kas-macros/src/extends.rs +++ b/crates/kas-macros/src/extends.rs @@ -64,7 +64,7 @@ impl Extends { (#base).get_clip_rect() } - fn frame(&mut self, id: &WidgetId, rect: Rect, style: ::kas::theme::FrameStyle, bg: Background) { + fn frame(&mut self, id: &Id, rect: Rect, style: ::kas::theme::FrameStyle, bg: Background) { (#base).frame(id, rect, style, bg); } @@ -76,17 +76,17 @@ impl Extends { (#base).selection(rect, style); } - fn text(&mut self, id: &WidgetId, rect: Rect, text: &TextDisplay, class: TextClass) { + fn text(&mut self, id: &Id, rect: Rect, text: &TextDisplay, class: TextClass) { (#base).text(id, rect, text, class); } - fn text_effects(&mut self, id: &WidgetId, rect: Rect, text: &dyn TextApi, class: TextClass) { + fn text_effects(&mut self, id: &Id, rect: Rect, text: &dyn TextApi, class: TextClass) { (#base).text_effects(id, rect, text, class); } fn text_selected_range( &mut self, - id: &WidgetId, + id: &Id, rect: Rect, text: &TextDisplay, range: Range, @@ -97,7 +97,7 @@ impl Extends { fn text_cursor( &mut self, - id: &WidgetId, + id: &Id, rect: Rect, text: &TextDisplay, class: TextClass, @@ -106,22 +106,22 @@ impl Extends { (#base).text_cursor(id, rect, text, class, byte); } - fn check_box(&mut self, id: &WidgetId, rect: Rect, checked: bool, last_change: Option) { + fn check_box(&mut self, id: &Id, rect: Rect, checked: bool, last_change: Option) { (#base).check_box(id, rect, checked, last_change); } - fn radio_box(&mut self, id: &WidgetId, rect: Rect, checked: bool, last_change: Option) { + fn radio_box(&mut self, id: &Id, rect: Rect, checked: bool, last_change: Option) { (#base).radio_box(id, rect, checked, last_change); } - fn mark(&mut self, id: &WidgetId, rect: Rect, style: MarkStyle) { + fn mark(&mut self, id: &Id, rect: Rect, style: MarkStyle) { (#base).mark(id, rect, style); } fn scroll_bar( &mut self, - id: &WidgetId, - id2: &WidgetId, + id: &Id, + id2: &Id, rect: Rect, h_rect: Rect, dir: Direction, @@ -129,11 +129,11 @@ impl Extends { (#base).scroll_bar(id, id2, rect, h_rect, dir); } - fn slider(&mut self, id: &WidgetId, id2: &WidgetId, rect: Rect, h_rect: Rect, dir: Direction) { + fn slider(&mut self, id: &Id, id2: &Id, rect: Rect, h_rect: Rect, dir: Direction) { (#base).slider(id, id2, rect, h_rect, dir); } - fn progress_bar(&mut self, id: &WidgetId, rect: Rect, dir: Direction, value: f32) { + fn progress_bar(&mut self, id: &Id, rect: Rect, dir: Direction, value: f32) { (#base).progress_bar(id, rect, dir, value); } diff --git a/crates/kas-macros/src/lib.rs b/crates/kas-macros/src/lib.rs index 00ed9c146..332ba82f7 100644 --- a/crates/kas-macros/src/lib.rs +++ b/crates/kas-macros/src/lib.rs @@ -195,7 +195,7 @@ pub fn impl_scope(input: TokenStream) -> TokenStream { /// `core`). The macro `widget_core!()` is a placeholder, expanded by /// `#[widget]` and used to identify the field used (any name may be used). /// This field *might* have type [`CoreData`] or might use a special generated -/// type; either way it has fields `id: WidgetId` (assigned by during configure) +/// type; either way it has fields `id: Id` (assigned by during configure) /// and `rect: Rect` (usually assigned by /// `Layout::set_rect`). It may contain additional fields for layout data. The /// type supports `Default` and `Clone` (although `Clone` actually diff --git a/crates/kas-macros/src/make_layout.rs b/crates/kas-macros/src/make_layout.rs index 9198eb3c4..28d251eba 100644 --- a/crates/kas-macros/src/make_layout.rs +++ b/crates/kas-macros/src/make_layout.rs @@ -98,7 +98,7 @@ impl Tree { (#layout).set_rect(cx, rect); } - fn find_id(&mut self, coord: ::kas::geom::Coord) -> Option<::kas::WidgetId> { + fn find_id(&mut self, coord: ::kas::geom::Coord) -> Option<::kas::Id> { use ::kas::{layout, Layout, LayoutExt}; #[cfg(debug_assertions)] @@ -217,7 +217,7 @@ impl Tree { let toks = quote! {{ struct #name #impl_generics { rect: ::kas::geom::Rect, - id: ::kas::WidgetId, + id: ::kas::Id, #[cfg(debug_assertions)] status: ::kas::WidgetStatus, #stor_ty @@ -243,7 +243,7 @@ impl Tree { &mut self, _: &mut ::kas::event::EventCx, _: &Self::Data, - _: &::kas::WidgetId, + _: &::kas::Id, _: &::kas::event::Event, ) -> ::kas::event::IsUsed { #[cfg(debug_assertions)] diff --git a/crates/kas-macros/src/widget.rs b/crates/kas-macros/src/widget.rs index 719f0ec12..2f1ca92d3 100644 --- a/crates/kas-macros/src/widget.rs +++ b/crates/kas-macros/src/widget.rs @@ -343,7 +343,7 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul scope.generated.push(quote! { struct #core_type { rect: ::kas::geom::Rect, - id: ::kas::WidgetId, + id: ::kas::Id, #[cfg(debug_assertions)] status: ::kas::WidgetStatus, #stor_ty @@ -473,7 +473,7 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul self } #[inline] - fn id_ref(&self) -> &::kas::WidgetId { + fn id_ref(&self) -> &::kas::Id { self.#inner.id_ref() } #[inline] @@ -495,7 +495,7 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul self.#inner.get_child(index) } #[inline] - fn find_child_index(&self, id: &::kas::WidgetId) -> Option { + fn find_child_index(&self, id: &::kas::Id) -> Option { self.#inner.find_child_index(id) } }; @@ -532,7 +532,7 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul }); fn_find_id = quote! { #[inline] - fn find_id(&mut self, coord: ::kas::geom::Coord) -> Option<::kas::WidgetId> { + fn find_id(&mut self, coord: ::kas::geom::Coord) -> Option<::kas::Id> { self.#inner.find_id(coord) } }; @@ -564,7 +564,7 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul &mut self, cx: &mut ::kas::event::ConfigCx, data: &Self::Data, - id: ::kas::WidgetId, + id: ::kas::Id, ) { self.#inner._configure(cx, data, id); } @@ -581,7 +581,7 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul &mut self, cx: &mut ::kas::event::EventCx, data: &Self::Data, - id: ::kas::WidgetId, + id: ::kas::Id, disabled: bool, event: ::kas::event::Event, ) -> ::kas::event::IsUsed { @@ -592,7 +592,7 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul &mut self, cx: &mut ::kas::event::EventCx, data: &Self::Data, - id: ::kas::WidgetId, + id: ::kas::Id, msg: ::kas::Erased, ) { self.#inner._replay(cx, data, id, msg); @@ -602,9 +602,9 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul &mut self, cx: &mut ::kas::event::EventCx, data: &Self::Data, - focus: Option<&::kas::WidgetId>, + focus: Option<&::kas::Id>, advance: ::kas::NavAdvance, - ) -> Option<::kas::WidgetId> { + ) -> Option<::kas::Id> { self.#inner._nav_next(cx, data, focus, advance) } } @@ -751,7 +751,7 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul } }; fn_find_id = quote! { - fn find_id(&mut self, coord: ::kas::geom::Coord) -> Option<::kas::WidgetId> { + fn find_id(&mut self, coord: ::kas::geom::Coord) -> Option<::kas::Id> { #[cfg(debug_assertions)] #core_path.status.require_rect(&#core_path.id); @@ -800,7 +800,7 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul &mut self, _: &mut ::kas::event::EventCx, _: &Self::Data, - _: &::kas::WidgetId, + _: &::kas::Id, _: &::kas::event::Event, ) -> ::kas::event::IsUsed { #require_rect @@ -998,7 +998,7 @@ pub fn impl_core_methods(name: &str, core_path: &Toks) -> Toks { self } #[inline] - fn id_ref(&self) -> &::kas::WidgetId { + fn id_ref(&self) -> &::kas::Id { &#core_path.id } #[inline] @@ -1092,7 +1092,7 @@ fn widget_recursive_methods(core_path: &Toks) -> Toks { &mut self, cx: &mut ::kas::event::ConfigCx, data: &Self::Data, - id: ::kas::WidgetId, + id: ::kas::Id, ) { #core_path.id = id; #[cfg(debug_assertions)] @@ -1119,7 +1119,7 @@ fn widget_recursive_methods(core_path: &Toks) -> Toks { &mut self, cx: &mut ::kas::event::EventCx, data: &Self::Data, - id: ::kas::WidgetId, + id: ::kas::Id, disabled: bool, event: ::kas::event::Event, ) -> ::kas::event::IsUsed { @@ -1130,7 +1130,7 @@ fn widget_recursive_methods(core_path: &Toks) -> Toks { &mut self, cx: &mut ::kas::event::EventCx, data: &Self::Data, - id: ::kas::WidgetId, + id: ::kas::Id, msg: ::kas::Erased, ) { ::kas::impls::_replay(self, cx, data, id, msg); @@ -1140,9 +1140,9 @@ fn widget_recursive_methods(core_path: &Toks) -> Toks { &mut self, cx: &mut ::kas::event::EventCx, data: &Self::Data, - focus: Option<&::kas::WidgetId>, + focus: Option<&::kas::Id>, advance: ::kas::NavAdvance, - ) -> Option<::kas::WidgetId> { + ) -> Option<::kas::Id> { ::kas::impls::_nav_next(self, cx, data, focus, advance) } } diff --git a/crates/kas-view/src/data_traits.rs b/crates/kas-view/src/data_traits.rs index 24917abf8..7d3b9911e 100644 --- a/crates/kas-view/src/data_traits.rs +++ b/crates/kas-view/src/data_traits.rs @@ -5,7 +5,7 @@ //! Traits for shared data objects -use kas::{autoimpl, WidgetId}; +use kas::{autoimpl, Id}; use std::borrow::Borrow; #[allow(unused)] // doc links use std::cell::RefCell; @@ -13,29 +13,29 @@ use std::fmt::Debug; /// Bounds on the key type pub trait DataKey: Clone + Debug + Default + PartialEq + Eq + 'static { - /// Make a [`WidgetId`] for a key + /// Make an [`Id`] for a key /// /// The result must be distinct from `parent`. - /// Use [`WidgetId::make_child`]. - fn make_id(&self, parent: &WidgetId) -> WidgetId; + /// Use [`Id::make_child`]. + fn make_id(&self, parent: &Id) -> Id; - /// Reconstruct a key from a [`WidgetId`] + /// Reconstruct a key from an [`Id`] /// /// Where `child` is the output of [`Self::make_id`] for the same `parent` - /// *or any [`WidgetId`] descended from that*, this should return a copy of + /// *or any [`Id`] descended from that*, this should return a copy of /// the `key` passed to `make_id`. /// - /// See: [`WidgetId::next_key_after`], [`WidgetId::iter_keys_after`] - fn reconstruct_key(parent: &WidgetId, child: &WidgetId) -> Option; + /// See: [`Id::next_key_after`], [`Id::iter_keys_after`] + fn reconstruct_key(parent: &Id, child: &Id) -> Option; } impl DataKey for () { - fn make_id(&self, parent: &WidgetId) -> WidgetId { + fn make_id(&self, parent: &Id) -> Id { // We need a distinct child, so use index 0 parent.make_child(0) } - fn reconstruct_key(parent: &WidgetId, child: &WidgetId) -> Option { + fn reconstruct_key(parent: &Id, child: &Id) -> Option { if child.next_key_after(parent) == Some(0) { Some(()) } else { @@ -47,21 +47,21 @@ impl DataKey for () { // NOTE: we cannot use this blanket impl without specialisation / negative impls // impl + Clone + Debug + PartialEq + Eq + 'static> DataKey for Key impl DataKey for usize { - fn make_id(&self, parent: &WidgetId) -> WidgetId { + fn make_id(&self, parent: &Id) -> Id { parent.make_child(*self) } - fn reconstruct_key(parent: &WidgetId, child: &WidgetId) -> Option { + fn reconstruct_key(parent: &Id, child: &Id) -> Option { child.next_key_after(parent) } } impl DataKey for (usize, usize) { - fn make_id(&self, parent: &WidgetId) -> WidgetId { + fn make_id(&self, parent: &Id) -> Id { parent.make_child(self.0).make_child(self.1) } - fn reconstruct_key(parent: &WidgetId, child: &WidgetId) -> Option { + fn reconstruct_key(parent: &Id, child: &Id) -> Option { let mut iter = child.iter_keys_after(parent); let col = iter.next(); let row = iter.next(); diff --git a/crates/kas-view/src/driver.rs b/crates/kas-view/src/driver.rs index d69f866b8..6eb273b68 100644 --- a/crates/kas-view/src/driver.rs +++ b/crates/kas-view/src/driver.rs @@ -29,7 +29,7 @@ use std::default::Default; /// "view widgets". /// A few simple implementations are provided: [`View`], [`NavView`]. /// -/// Each view widget has a [`WidgetId`] corresponding to its data item, and +/// Each view widget has an [`Id`] corresponding to its data item, and /// handles events like any other widget. In order to associate a returned /// message with a [`SharedData::Key`], either embed that key while constructing /// the widget with [`Driver::make`] or intercept the message in diff --git a/crates/kas-view/src/list_view.rs b/crates/kas-view/src/list_view.rs index 53c0dd6ac..4453d6b41 100644 --- a/crates/kas-view/src/list_view.rs +++ b/crates/kas-view/src/list_view.rs @@ -34,7 +34,7 @@ impl_scope! { /// required when the list is scrolled, keeping the number of widgets in /// use roughly proportional to the number of data items within the view. /// - /// Each view widget has a [`WidgetId`] corresponding to its current data + /// Each view widget has an [`Id`] corresponding to its current data /// item, and may handle events and emit messages like other widegts. /// See [`Driver`] documentation for more on event handling. /// @@ -425,7 +425,7 @@ impl_scope! { fn get_child(&self, index: usize) -> Option<&dyn Layout> { self.widgets.get(index).map(|w| w.widget.as_layout()) } - fn find_child_index(&self, id: &WidgetId) -> Option { + fn find_child_index(&self, id: &Id) -> Option { let key = A::Key::reconstruct_key(self.id_ref(), id); if key.is_some() { self.widgets @@ -551,7 +551,7 @@ impl_scope! { self.scroll_offset() } - fn find_id(&mut self, coord: Coord) -> Option { + fn find_id(&mut self, coord: Coord) -> Option { if !self.rect().contains(coord) { return None; } @@ -584,7 +584,7 @@ impl_scope! { impl Events for Self { #[inline] - fn make_child_id(&mut self, _: usize) -> WidgetId { + fn make_child_id(&mut self, _: usize) -> Id { // We configure children in update_widgets and do not want this method to be called unimplemented!() } @@ -778,7 +778,7 @@ impl_scope! { } } - fn _configure(&mut self, cx: &mut ConfigCx, data: &A, id: WidgetId) { + fn _configure(&mut self, cx: &mut ConfigCx, data: &A, id: Id) { self.core.id = id; #[cfg(debug_assertions)] self.core.status.configure(&self.core.id); @@ -798,14 +798,14 @@ impl_scope! { &mut self, cx: &mut EventCx, data: &A, - id: WidgetId, + id: Id, disabled: bool, event: Event, ) -> IsUsed { kas::impls::_send(self, cx, data, id, disabled, event) } - fn _replay(&mut self, cx: &mut EventCx, data: &A, id: WidgetId, msg: kas::Erased) { + fn _replay(&mut self, cx: &mut EventCx, data: &A, id: Id, msg: kas::Erased) { kas::impls::_replay(self, cx, data, id, msg); } @@ -814,9 +814,9 @@ impl_scope! { &mut self, cx: &mut EventCx, data: &A, - focus: Option<&WidgetId>, + focus: Option<&Id>, advance: NavAdvance, - ) -> Option { + ) -> Option { if cx.is_disabled(self.id_ref()) || self.cur_len == 0 { return None; } diff --git a/crates/kas-view/src/matrix_view.rs b/crates/kas-view/src/matrix_view.rs index e1bf8507c..a567d438d 100644 --- a/crates/kas-view/src/matrix_view.rs +++ b/crates/kas-view/src/matrix_view.rs @@ -39,7 +39,7 @@ impl_scope! { /// required when the matrix is scrolled, keeping the number of widgets in /// use roughly proportional to the number of data items within the view. /// - /// Each view widget has a [`WidgetId`] corresponding to its current data + /// Each view widget has an [`Id`] corresponding to its current data /// item, and may handle events and emit messages like other widegts. /// See [`Driver`] documentation for more on event handling. /// @@ -371,7 +371,7 @@ impl_scope! { fn get_child(&self, index: usize) -> Option<&dyn Layout> { self.widgets.get(index).map(|w| w.widget.as_layout()) } - fn find_child_index(&self, id: &WidgetId) -> Option { + fn find_child_index(&self, id: &Id) -> Option { let num = self.num_children(); let key = A::Key::reconstruct_key(self.id_ref(), id); if key.is_some() { @@ -489,7 +489,7 @@ impl_scope! { self.scroll_offset() } - fn find_id(&mut self, coord: Coord) -> Option { + fn find_id(&mut self, coord: Coord) -> Option { if !self.rect().contains(coord) { return None; } @@ -529,7 +529,7 @@ impl_scope! { impl Events for Self { #[inline] - fn make_child_id(&mut self, _: usize) -> WidgetId { + fn make_child_id(&mut self, _: usize) -> Id { // We configure children in update_widgets and do not want this method to be called unimplemented!() } @@ -746,7 +746,7 @@ impl_scope! { } } - fn _configure(&mut self, cx: &mut ConfigCx, data: &A, id: WidgetId) { + fn _configure(&mut self, cx: &mut ConfigCx, data: &A, id: Id) { self.core.id = id; #[cfg(debug_assertions)] self.core.status.configure(&self.core.id); @@ -766,14 +766,14 @@ impl_scope! { &mut self, cx: &mut EventCx, data: &A, - id: WidgetId, + id: Id, disabled: bool, event: Event, ) -> IsUsed { kas::impls::_send(self, cx, data, id, disabled, event) } - fn _replay(&mut self, cx: &mut EventCx, data: &A, id: WidgetId, msg: kas::Erased) { + fn _replay(&mut self, cx: &mut EventCx, data: &A, id: Id, msg: kas::Erased) { kas::impls::_replay(self, cx, data, id, msg); } @@ -782,9 +782,9 @@ impl_scope! { &mut self, cx: &mut EventCx, data: &A, - focus: Option<&WidgetId>, + focus: Option<&Id>, advance: NavAdvance, - ) -> Option { + ) -> Option { if cx.is_disabled(self.id_ref()) || self.cur_len == (0, 0) { return None; } diff --git a/crates/kas-wgpu/src/shaded_theme.rs b/crates/kas-wgpu/src/shaded_theme.rs index 8b7e39d28..156cd01cb 100644 --- a/crates/kas-wgpu/src/shaded_theme.rs +++ b/crates/kas-wgpu/src/shaded_theme.rs @@ -20,7 +20,7 @@ use kas::theme::dimensions as dim; use kas::theme::{Background, ThemeControl, ThemeDraw, ThemeSize}; use kas::theme::{ColorsLinear, Config, FlatTheme, InputState, SimpleTheme, Theme}; use kas::theme::{FrameStyle, MarkStyle, TextClass}; -use kas::{Action, WidgetId}; +use kas::{Action, Id}; /// A theme using simple shading to give apparent depth to elements #[derive(Clone, Debug)] @@ -267,7 +267,7 @@ where f(&mut handle); } - fn frame(&mut self, id: &WidgetId, rect: Rect, style: FrameStyle, bg: Background) { + fn frame(&mut self, id: &Id, rect: Rect, style: FrameStyle, bg: Background) { match style { FrameStyle::Frame => { let outer = Quad::conv(rect); @@ -326,13 +326,7 @@ where self.draw.shaded_round_frame(outer, inner, NORMS_TRACK, col); } - fn check_box( - &mut self, - id: &WidgetId, - rect: Rect, - checked: bool, - last_change: Option, - ) { + fn check_box(&mut self, id: &Id, rect: Rect, checked: bool, last_change: Option) { let state = InputState::new_all(self.ev, id); let bg_col = self.cols.from_edit_bg(Default::default(), state); let inner = self.draw_edit_box(rect, bg_col, state.nav_focus()); @@ -341,13 +335,7 @@ where .check_mark(inner, state, checked, last_change); } - fn radio_box( - &mut self, - id: &WidgetId, - rect: Rect, - checked: bool, - last_change: Option, - ) { + fn radio_box(&mut self, id: &Id, rect: Rect, checked: bool, last_change: Option) { let state = InputState::new_all(self.ev, id); let anim_fade = 1.0 - self.w.anim.fade_bool(self.draw.draw, checked, last_change); @@ -365,14 +353,7 @@ where } } - fn scroll_bar( - &mut self, - id: &WidgetId, - id2: &WidgetId, - rect: Rect, - h_rect: Rect, - _: Direction, - ) { + fn scroll_bar(&mut self, id: &Id, id2: &Id, rect: Rect, h_rect: Rect, _: Direction) { // track let outer = Quad::conv(rect); let inner = outer.shrink(outer.size().min_comp() / 2.0); @@ -384,7 +365,7 @@ where self.draw_grip(h_rect, state); } - fn slider(&mut self, id: &WidgetId, id2: &WidgetId, rect: Rect, h_rect: Rect, dir: Direction) { + fn slider(&mut self, id: &Id, id2: &Id, rect: Rect, h_rect: Rect, dir: Direction) { // track let mut outer = Quad::conv(rect); outer = match dir.is_horizontal() { @@ -400,7 +381,7 @@ where self.draw_grip(h_rect, state); } - fn progress_bar(&mut self, _: &WidgetId, rect: Rect, dir: Direction, value: f32) { + fn progress_bar(&mut self, _: &Id, rect: Rect, dir: Direction, value: f32) { let mut outer = Quad::conv(rect); let inner = outer.shrink(outer.size().min_comp() / 2.0); let col = self.cols.frame; diff --git a/crates/kas-widgets/src/adapt/with_label.rs b/crates/kas-widgets/src/adapt/with_label.rs index f9bc82356..4ba1c8529 100644 --- a/crates/kas-widgets/src/adapt/with_label.rs +++ b/crates/kas-widgets/src/adapt/with_label.rs @@ -113,7 +113,7 @@ impl_scope! { } impl Layout for Self { - fn find_id(&mut self, coord: Coord) -> Option { + fn find_id(&mut self, coord: Coord) -> Option { self.rect().contains(coord).then(|| self.inner.id()) } } diff --git a/crates/kas-widgets/src/check_box.rs b/crates/kas-widgets/src/check_box.rs index 64a83ffe3..5e5344132 100644 --- a/crates/kas-widgets/src/check_box.rs +++ b/crates/kas-widgets/src/check_box.rs @@ -194,7 +194,7 @@ impl_scope! { shrink_to_text(&mut self.core.rect, dir, &self.label); } - fn find_id(&mut self, coord: Coord) -> Option { + fn find_id(&mut self, coord: Coord) -> Option { self.rect().contains(coord).then(|| self.inner.id()) } } diff --git a/crates/kas-widgets/src/edit.rs b/crates/kas-widgets/src/edit.rs index 82075a4cc..0b9e5404b 100644 --- a/crates/kas-widgets/src/edit.rs +++ b/crates/kas-widgets/src/edit.rs @@ -324,7 +324,7 @@ impl_scope! { self.update_scroll_bar(cx); } - fn find_id(&mut self, coord: Coord) -> Option { + fn find_id(&mut self, coord: Coord) -> Option { if !self.rect().contains(coord) { return None; } @@ -613,7 +613,7 @@ impl_scope! { self.view_offset = self.view_offset.min(self.max_scroll_offset()); } - fn find_id(&mut self, coord: Coord) -> Option { + fn find_id(&mut self, coord: Coord) -> Option { self.outer_rect.contains(coord).then_some(self.id()) } diff --git a/crates/kas-widgets/src/grid.rs b/crates/kas-widgets/src/grid.rs index e31496405..c7ca3df52 100644 --- a/crates/kas-widgets/src/grid.rs +++ b/crates/kas-widgets/src/grid.rs @@ -101,7 +101,7 @@ impl_scope! { } } - fn find_id(&mut self, coord: Coord) -> Option { + fn find_id(&mut self, coord: Coord) -> Option { if !self.rect().contains(coord) { return None; } diff --git a/crates/kas-widgets/src/list.rs b/crates/kas-widgets/src/list.rs index 6f943c6dc..84d7f94b3 100644 --- a/crates/kas-widgets/src/list.rs +++ b/crates/kas-widgets/src/list.rs @@ -73,7 +73,7 @@ impl_scope! { widgets: Vec, direction: D, next: usize, - id_map: HashMap, // map key of WidgetId to index + id_map: HashMap, // map key of Id to index on_messages: Option>, } @@ -86,7 +86,7 @@ impl_scope! { self.widgets.get(index).map(|w| w.as_layout()) } - fn find_child_index(&self, id: &WidgetId) -> Option { + fn find_child_index(&self, id: &Id) -> Option { id.next_key_after(self.id_ref()) .and_then(|k| self.id_map.get(&k).cloned()) } @@ -109,7 +109,7 @@ impl_scope! { impl Events for Self { /// Make a fresh id based on `self.next` then insert into `self.id_map` - fn make_child_id(&mut self, index: usize) -> WidgetId { + fn make_child_id(&mut self, index: usize) -> Id { if let Some(child) = self.widgets.get(index) { // Use the widget's existing identifier, if any if child.id_ref().is_valid() { diff --git a/crates/kas-widgets/src/menu/menu_entry.rs b/crates/kas-widgets/src/menu/menu_entry.rs index 602fa6ba2..e57e73b36 100644 --- a/crates/kas-widgets/src/menu/menu_entry.rs +++ b/crates/kas-widgets/src/menu/menu_entry.rs @@ -31,7 +31,7 @@ impl_scope! { } impl Layout for Self { - fn find_id(&mut self, coord: Coord) -> Option { + fn find_id(&mut self, coord: Coord) -> Option { self.rect().contains(coord).then(|| self.id()) } @@ -124,7 +124,7 @@ impl_scope! { } impl Layout for Self { - fn find_id(&mut self, coord: Coord) -> Option { + fn find_id(&mut self, coord: Coord) -> Option { self.rect().contains(coord).then(|| self.checkbox.id()) } diff --git a/crates/kas-widgets/src/menu/menubar.rs b/crates/kas-widgets/src/menu/menubar.rs index 456948339..6d0d39de5 100644 --- a/crates/kas-widgets/src/menu/menubar.rs +++ b/crates/kas-widgets/src/menu/menubar.rs @@ -22,7 +22,7 @@ impl_scope! { direction: D, widgets: Vec>, layout_store: layout::DynRowStorage, - delayed_open: Option, + delayed_open: Option, } impl Self @@ -97,7 +97,7 @@ impl_scope! { } } - fn find_id(&mut self, coord: Coord) -> Option { + fn find_id(&mut self, coord: Coord) -> Option { if !self.rect().contains(coord) { return None; } @@ -256,7 +256,7 @@ impl_scope! { &mut self, cx: &mut EventCx, data: &Data, - target: Option<&WidgetId>, + target: Option<&Id>, set_focus: bool, ) { log::trace!( diff --git a/crates/kas-widgets/src/menu/mod.rs b/crates/kas-widgets/src/menu/mod.rs index 093ae1d35..b4f29c331 100644 --- a/crates/kas-widgets/src/menu/mod.rs +++ b/crates/kas-widgets/src/menu/mod.rs @@ -102,7 +102,7 @@ pub trait Menu: Widget { &mut self, cx: &mut EventCx, data: &Self::Data, - target: Option<&WidgetId>, + target: Option<&Id>, set_focus: bool, ) { let _ = (cx, data, target, set_focus); @@ -118,13 +118,7 @@ impl> Menu for MapAny { self.inner.menu_is_open() } - fn set_menu_path( - &mut self, - cx: &mut EventCx, - _: &A, - target: Option<&WidgetId>, - set_focus: bool, - ) { + fn set_menu_path(&mut self, cx: &mut EventCx, _: &A, target: Option<&Id>, set_focus: bool) { self.inner.set_menu_path(cx, &(), target, set_focus); } } diff --git a/crates/kas-widgets/src/menu/submenu.rs b/crates/kas-widgets/src/menu/submenu.rs index 0a5f480cc..5bc204696 100644 --- a/crates/kas-widgets/src/menu/submenu.rs +++ b/crates/kas-widgets/src/menu/submenu.rs @@ -101,7 +101,7 @@ impl_scope! { None } - fn find_id(&mut self, coord: Coord) -> Option { + fn find_id(&mut self, coord: Coord) -> Option { self.rect().contains(coord).then(|| self.id()) } @@ -164,7 +164,7 @@ impl_scope! { &mut self, cx: &mut EventCx, data: &Data, - target: Option<&WidgetId>, + target: Option<&Id>, set_focus: bool, ) { if !self.id_ref().is_valid() { @@ -357,7 +357,7 @@ impl_scope! { } } - fn find_id(&mut self, coord: Coord) -> Option { + fn find_id(&mut self, coord: Coord) -> Option { if !self.rect().contains(coord) { return None; } diff --git a/crates/kas-widgets/src/radio_box.rs b/crates/kas-widgets/src/radio_box.rs index 4d35a10cf..f354ab3bf 100644 --- a/crates/kas-widgets/src/radio_box.rs +++ b/crates/kas-widgets/src/radio_box.rs @@ -154,7 +154,7 @@ impl_scope! { crate::check_box::shrink_to_text(&mut self.core.rect, dir, &self.label); } - fn find_id(&mut self, coord: Coord) -> Option { + fn find_id(&mut self, coord: Coord) -> Option { self.rect().contains(coord).then(|| self.inner.id()) } } diff --git a/crates/kas-widgets/src/scroll.rs b/crates/kas-widgets/src/scroll.rs index fe8b43698..bf01a51f6 100644 --- a/crates/kas-widgets/src/scroll.rs +++ b/crates/kas-widgets/src/scroll.rs @@ -123,7 +123,7 @@ impl_scope! { self.scroll_offset() } - fn find_id(&mut self, coord: Coord) -> Option { + fn find_id(&mut self, coord: Coord) -> Option { if !self.rect().contains(coord) { return None; } diff --git a/crates/kas-widgets/src/scroll_bar.rs b/crates/kas-widgets/src/scroll_bar.rs index 60c901a77..473833c11 100644 --- a/crates/kas-widgets/src/scroll_bar.rs +++ b/crates/kas-widgets/src/scroll_bar.rs @@ -295,7 +295,7 @@ impl_scope! { let _ = self.update_widgets(); } - fn find_id(&mut self, coord: Coord) -> Option { + fn find_id(&mut self, coord: Coord) -> Option { if !self.rect().contains(coord) { return None; } @@ -517,7 +517,7 @@ impl_scope! { } } - fn find_id(&mut self, coord: Coord) -> Option { + fn find_id(&mut self, coord: Coord) -> Option { if !self.rect().contains(coord) { return None; } diff --git a/crates/kas-widgets/src/scroll_label.rs b/crates/kas-widgets/src/scroll_label.rs index 836c8cc76..1802f6cd2 100644 --- a/crates/kas-widgets/src/scroll_label.rs +++ b/crates/kas-widgets/src/scroll_label.rs @@ -61,7 +61,7 @@ impl_scope! { self.bar.set_value(cx, self.view_offset.1); } - fn find_id(&mut self, coord: Coord) -> Option { + fn find_id(&mut self, coord: Coord) -> Option { if !self.rect().contains(coord) { return None; } diff --git a/crates/kas-widgets/src/slider.rs b/crates/kas-widgets/src/slider.rs index 4b23cc0a6..cc13d2100 100644 --- a/crates/kas-widgets/src/slider.rs +++ b/crates/kas-widgets/src/slider.rs @@ -316,7 +316,7 @@ impl_scope! { let _ = self.grip.set_size_and_offset(size, self.offset()); } - fn find_id(&mut self, coord: Coord) -> Option { + fn find_id(&mut self, coord: Coord) -> Option { if !self.rect().contains(coord) { return None; } diff --git a/crates/kas-widgets/src/spinner.rs b/crates/kas-widgets/src/spinner.rs index e993021f8..ad48db304 100644 --- a/crates/kas-widgets/src/spinner.rs +++ b/crates/kas-widgets/src/spinner.rs @@ -285,7 +285,7 @@ impl_scope! { self.edit.set_outer_rect(rect, FrameStyle::EditBox); } - fn find_id(&mut self, coord: Coord) -> Option { + fn find_id(&mut self, coord: Coord) -> Option { self.b_up.find_id(coord) .or_else(|| self.b_down.find_id(coord)) .or_else(|| self.edit.find_id(coord)) @@ -301,7 +301,7 @@ impl_scope! { impl Events for Self { type Data = A; - fn steal_event(&mut self, cx: &mut EventCx, data: &A, _: &WidgetId, event: &Event) -> IsUsed { + fn steal_event(&mut self, cx: &mut EventCx, data: &A, _: &Id, event: &Event) -> IsUsed { let btn = match event { Event::Command(cmd, code) => match cmd { Command::Down => { diff --git a/crates/kas-widgets/src/splitter.rs b/crates/kas-widgets/src/splitter.rs index bf8dceda0..0e67c5442 100644 --- a/crates/kas-widgets/src/splitter.rs +++ b/crates/kas-widgets/src/splitter.rs @@ -35,7 +35,7 @@ impl_scope! { direction: D, size_solved: bool, next: usize, - id_map: HashMap, // map key of WidgetId to index + id_map: HashMap, // map key of Id to index } impl Self where D: Default { @@ -76,7 +76,7 @@ impl_scope! { } // Assumption: index is a valid entry of self.widgets - fn make_next_id(&mut self, is_handle: bool, index: usize) -> WidgetId { + fn make_next_id(&mut self, is_handle: bool, index: usize) -> Id { let child_index = (2 * index) + (is_handle as usize); if !is_handle { if let Some(child) = self.widgets.get(index) { @@ -114,7 +114,7 @@ impl_scope! { } } - fn find_child_index(&self, id: &WidgetId) -> Option { + fn find_child_index(&self, id: &Id) -> Option { id.next_key_after(self.id_ref()) .and_then(|k| self.id_map.get(&k).cloned()) } @@ -182,7 +182,7 @@ impl_scope! { } } - fn find_id(&mut self, coord: Coord) -> Option { + fn find_id(&mut self, coord: Coord) -> Option { if !self.rect().contains(coord) || !self.size_solved { return None; } @@ -244,7 +244,7 @@ impl_scope! { } impl Events for Self { - fn make_child_id(&mut self, child_index: usize) -> WidgetId { + fn make_child_id(&mut self, child_index: usize) -> Id { let is_handle = (child_index & 1) != 0; self.make_next_id(is_handle, child_index / 2) } diff --git a/crates/kas-widgets/src/stack.rs b/crates/kas-widgets/src/stack.rs index 2be8b2f6c..92a4017ce 100644 --- a/crates/kas-widgets/src/stack.rs +++ b/crates/kas-widgets/src/stack.rs @@ -51,7 +51,7 @@ impl_scope! { active: usize, size_limit: usize, next: usize, - id_map: HashMap, // map key of WidgetId to index + id_map: HashMap, // map key of Id to index } impl Widget for Self { @@ -78,7 +78,7 @@ impl_scope! { self.widgets.get(index).map(|(w, _)| w.as_layout()) } - fn find_child_index(&self, id: &WidgetId) -> Option { + fn find_child_index(&self, id: &Id) -> Option { id.next_key_after(self.id_ref()) .and_then(|k| self.id_map.get(&k).cloned()) } @@ -125,7 +125,7 @@ impl_scope! { } } - fn find_id(&mut self, coord: Coord) -> Option { + fn find_id(&mut self, coord: Coord) -> Option { if let Some(entry) = self.widgets.get_mut(self.active) { debug_assert_eq!(entry.1, State::Sized); return entry.0.find_id(coord); @@ -142,7 +142,7 @@ impl_scope! { } impl Events for Self { - fn make_child_id(&mut self, index: usize) -> WidgetId { + fn make_child_id(&mut self, index: usize) -> Id { if let Some((child, state)) = self.widgets.get(index) { if state.is_configured() { debug_assert!(child.id_ref().is_valid()); diff --git a/crates/kas-widgets/src/tab_stack.rs b/crates/kas-widgets/src/tab_stack.rs index f63164d27..66f0e6978 100644 --- a/crates/kas-widgets/src/tab_stack.rs +++ b/crates/kas-widgets/src/tab_stack.rs @@ -57,7 +57,7 @@ impl_scope! { Visitor::frame(&mut self.frame, label, FrameStyle::Tab).set_rect(cx, rect) } - fn find_id(&mut self, coord: Coord) -> Option { + fn find_id(&mut self, coord: Coord) -> Option { self.rect().contains(coord).then_some(self.id()) } diff --git a/examples/cursors.rs b/examples/cursors.rs index 6fbc46577..775b20804 100644 --- a/examples/cursors.rs +++ b/examples/cursors.rs @@ -22,7 +22,7 @@ impl_scope! { cursor: CursorIcon, } impl Layout for Self { - fn find_id(&mut self, coord: Coord) -> Option { + fn find_id(&mut self, coord: Coord) -> Option { // Steal mouse focus: hover points to self, not self.label self.rect().contains(coord).then(|| self.id()) } diff --git a/src/lib.rs b/src/lib.rs index 5ea0072bf..101ee3e91 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,7 +39,7 @@ /// using widgets in a GUI. pub mod prelude { // Note: using #[doc(no_inline)] here causes doc issues in this crate: - // - kas::WidgetId appears to have no methods + // - kas::Id appears to have no methods // - doc_cfg annotations appear to be attached to the wrong items #[doc(no_inline)] pub use kas_core::prelude::*; From 7d2cee458ad029d72a81012591efb658931d922a Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 7 Oct 2023 19:49:13 +0100 Subject: [PATCH 06/16] EventState::region_moved does not require a widget Id There is no reason to localise this operation --- crates/kas-core/src/event/cx/cx_pub.rs | 7 +++---- crates/kas-widgets/src/stack.rs | 8 ++++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/crates/kas-core/src/event/cx/cx_pub.rs b/crates/kas-core/src/event/cx/cx_pub.rs index 9231f22c1..047d324eb 100644 --- a/crates/kas-core/src/event/cx/cx_pub.rs +++ b/crates/kas-core/src/event/cx/cx_pub.rs @@ -243,11 +243,10 @@ impl EventState { } /// Notify that widgets under self may have moved - /// - /// This is equivalent to calling [`Self::action`] with [`Action::REGION_MOVED`]. #[inline] - pub fn region_moved(&mut self, id: impl HasId) { - self.action(id, Action::REGION_MOVED); + pub fn region_moved(&mut self) { + // Do not take id: this always applies to the whole window + self.action |= Action::REGION_MOVED; } /// Terminate the GUI diff --git a/crates/kas-widgets/src/stack.rs b/crates/kas-widgets/src/stack.rs index 92a4017ce..0098baea6 100644 --- a/crates/kas-widgets/src/stack.rs +++ b/crates/kas-widgets/src/stack.rs @@ -296,11 +296,11 @@ impl Stack { } else { debug_assert_eq!(entry.1, State::Sized); entry.0.set_rect(cx, self.core.rect); - cx.region_moved(self); + cx.region_moved(); } } else { if old_index < self.widgets.len() { - cx.region_moved(self); + cx.region_moved(); } } } @@ -379,7 +379,7 @@ impl Stack { let result = self.widgets.pop().map(|(w, _)| w); if let Some(w) = result.as_ref() { if self.active > 0 && self.active == self.widgets.len() { - cx.region_moved(&self); + cx.region_moved(); } if w.id_ref().is_valid() { @@ -430,7 +430,7 @@ impl Stack { if self.active == index { self.active = usize::MAX; - cx.region_moved(&self); + cx.region_moved(); } for v in self.id_map.values_mut() { From 670e12af82cf2b74cf0745ff42362a4643a9cd14 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sun, 8 Oct 2023 09:29:25 +0100 Subject: [PATCH 07/16] Add Action::SCROLLED Also remove EditBox: Scrollable (the box uses an internal scroll bar) --- crates/kas-core/src/action.rs | 5 ++++ crates/kas-core/src/shell/window.rs | 5 +++- crates/kas-widgets/src/edit.rs | 40 ++++++++++------------------- 3 files changed, 23 insertions(+), 27 deletions(-) diff --git a/crates/kas-core/src/action.rs b/crates/kas-core/src/action.rs index 4261f368e..7681a9034 100644 --- a/crates/kas-core/src/action.rs +++ b/crates/kas-core/src/action.rs @@ -32,6 +32,11 @@ bitflags! { /// /// Implies window redraw. const REGION_MOVED = 1 << 4; + /// A widget was scrolled + /// + /// This is used for inter-widget communication (see `EditBox`). If not + /// handled locally, it is handled identially to [`Self::SET_RECT`]. + const SCROLLED = 1 << 6; /// 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 fa4133a73..9e829ae93 100644 --- a/crates/kas-core/src/shell/window.rs +++ b/crates/kas-core/src/shell/window.rs @@ -312,7 +312,10 @@ impl> Window { window.solve_cache.invalidate_rule_cache(); } self.apply_size(shared, false); - } else if action.contains(Action::SET_RECT) { + } else if !action + .intersection(Action::SET_RECT | Action::SCROLLED) + .is_empty() + { self.apply_size(shared, false); } if action.contains(Action::REGION_MOVED) { diff --git a/crates/kas-widgets/src/edit.rs b/crates/kas-widgets/src/edit.rs index 0b9e5404b..ff0a595d2 100644 --- a/crates/kas-widgets/src/edit.rs +++ b/crates/kas-widgets/src/edit.rs @@ -271,7 +271,7 @@ impl_scope! { /// /// By default, the editor supports a single-line only; /// [`Self::with_multi_line`] and [`Self::with_class`] can be used to change this. - #[autoimpl(Deref, DerefMut, HasStr, HasString using self.inner)] + #[autoimpl(Deref, DerefMut, HasStr using self.inner)] #[autoimpl(Clone, Default, Debug where G: trait)] #[widget] pub struct EditBox> { @@ -363,29 +363,6 @@ impl_scope! { } } - impl Scrollable for Self { - #[inline] - fn scroll_axes(&self, size: Size) -> (bool, bool) { - self.inner.scroll_axes(size) - } - - #[inline] - fn max_scroll_offset(&self) -> Offset { - self.inner.max_scroll_offset() - } - - #[inline] - fn scroll_offset(&self) -> Offset { - self.inner.scroll_offset() - } - - fn set_scroll_offset(&mut self, cx: &mut EventCx, offset: Offset) -> Offset { - let offset = self.inner.set_scroll_offset(cx, offset); - self.update_scroll_bar(cx); - offset - } - } - impl Self { fn update_scroll_bar(&mut self, cx: &mut EventState) { let max_offset = self.inner.max_scroll_offset().1; @@ -395,6 +372,18 @@ impl_scope! { } } + impl HasString for Self { + fn set_string(&mut self, string: String) -> Action { + let mut action = self.inner.set_string(string); + if action.contains(Action::SCROLLED) { + action.remove(Action::SCROLLED); + let max_offset = self.inner.max_scroll_offset().1; + action |= self.bar.set_limits(max_offset, self.inner.rect().size.1); + } + action + } + } + impl ToString for Self { fn to_string(&self) -> String { self.inner.to_string() @@ -837,8 +826,7 @@ impl_scope! { if self.text.try_prepare().is_ok() { self.text_size = Vec2::from(self.text.bounding_box().unwrap().1).cast_ceil(); self.view_offset = self.view_offset.min(self.max_scroll_offset()); - // We use SET_RECT just to set the outer scroll bar position: - action = Action::SET_RECT; + action = Action::SCROLLED; } action | self.set_error_state(false) } From ae24fdcdcdf6750a6a5993085638568514b2dbf2 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sun, 8 Oct 2023 10:37:14 +0100 Subject: [PATCH 08/16] Replace EventCx::send with send_command; add pending_cmds This is the only Event variant it makes sense to support --- crates/kas-core/src/event/cx/cx_pub.rs | 30 ++++++++++-------------- crates/kas-core/src/event/cx/cx_shell.rs | 6 +++++ crates/kas-core/src/event/cx/mod.rs | 1 + crates/kas-widgets/src/combobox.rs | 2 +- crates/kas-widgets/src/menu/menubar.rs | 2 +- 5 files changed, 22 insertions(+), 19 deletions(-) diff --git a/crates/kas-core/src/event/cx/cx_pub.rs b/crates/kas-core/src/event/cx/cx_pub.rs index 047d324eb..06026dc40 100644 --- a/crates/kas-core/src/event/cx/cx_pub.rs +++ b/crates/kas-core/src/event/cx/cx_pub.rs @@ -656,23 +656,19 @@ impl<'a> EventCx<'a> { self.last_child } - /// Send an event to a widget - /// - /// Sends `event` to widget `id`. The event is queued to send later, thus - /// any actions by the receiving widget will not be immediately visible to - /// the caller of this method. - /// - /// When calling this method, be aware that: - /// - /// - Some widgets use an inner component to handle events, thus calling - /// with the outer widget's `id` may not have the desired effect. - /// [`Layout::find_id`] and [`EventState::next_nav_focus`] are able to find - /// the appropriate event-handling target. - /// (TODO: do we need another method to find this target?) - /// - Some events such as [`Event::PressMove`] contain embedded widget - /// identifiers which may affect handling of the event. - pub fn send(&mut self, id: Id, event: Event) { - self.pending.push_back(Pending::Send(id, event)); + /// Send a command to a widget + /// + /// Sends [`Event::Command`] to widget `id`. The event is queued to send + /// later, thus any actions by the receiving widget will not be immediately + /// visible to the caller of this method. + /// + /// When calling this method, be aware that some widgets use an inner + /// component to handle events, thus calling with the outer widget's `id` + /// may not have the desired effect. [`Layout::find_id`] and + /// [`EventState::next_nav_focus`] are usually able to find the appropriate + /// event-handling target. + pub fn send_command(&mut self, id: Id, cmd: Command) { + self.pending_cmds.push_back((id, cmd)); } /// Push a message to the stack diff --git a/crates/kas-core/src/event/cx/cx_shell.rs b/crates/kas-core/src/event/cx/cx_shell.rs index 9338b5103..09c12ba6d 100644 --- a/crates/kas-core/src/event/cx/cx_shell.rs +++ b/crates/kas-core/src/event/cx/cx_shell.rs @@ -53,6 +53,7 @@ impl EventState { time_updates: vec![], fut_messages: vec![], pending: Default::default(), + pending_cmds: Default::default(), action: Action::empty(), } } @@ -234,6 +235,11 @@ impl EventState { } } + while let Some((id, cmd)) = cx.pending_cmds.pop_front() { + log::trace!(target: "kas_core::event", "sending pending command {cmd:?} to {id}"); + cx.send_event(win.as_node(data), id, Event::Command(cmd, None)); + } + // 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)); diff --git a/crates/kas-core/src/event/cx/mod.rs b/crates/kas-core/src/event/cx/mod.rs index 8f20d6ef5..0905d5181 100644 --- a/crates/kas-core/src/event/cx/mod.rs +++ b/crates/kas-core/src/event/cx/mod.rs @@ -204,6 +204,7 @@ pub struct EventState { fut_messages: Vec<(Id, Pin>>)>, // FIFO queue of events pending handling pending: VecDeque, + pending_cmds: VecDeque<(Id, Command)>, #[cfg_attr(not(feature = "internal_doc"), doc(hidden))] #[cfg_attr(doc_cfg, doc(cfg(internal_doc)))] pub action: Action, diff --git a/crates/kas-widgets/src/combobox.rs b/crates/kas-widgets/src/combobox.rs index 0b07a3ef6..f92fe178b 100644 --- a/crates/kas-widgets/src/combobox.rs +++ b/crates/kas-widgets/src/combobox.rs @@ -163,7 +163,7 @@ impl_scope! { return Used; } } else if self.popup.is_open() && self.popup.is_ancestor_of(&id) { - cx.send(id, Event::Command(Command::Activate, None)); + cx.send_command(id, Command::Activate); return Used; } } diff --git a/crates/kas-widgets/src/menu/menubar.rs b/crates/kas-widgets/src/menu/menubar.rs index 6d0d39de5..a6cef08a3 100644 --- a/crates/kas-widgets/src/menu/menubar.rs +++ b/crates/kas-widgets/src/menu/menubar.rs @@ -214,7 +214,7 @@ impl_scope! { if !self.rect().contains(press.coord) { // not on the menubar self.delayed_open = None; - cx.send(id, Event::Command(Command::Activate, None)); + cx.send_command(id, Command::Activate); } Used } From 6f944be58eafbf2fde288d60d6d0529d74d2b7c3 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 9 Oct 2023 11:20:24 +0100 Subject: [PATCH 09/16] Partially revert 93f9d8feb Possibly this was required for handling of shortcuts with nav focus without key focus? In any case, key focus is automatically requested on nav focus now. --- crates/kas-widgets/src/edit.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/crates/kas-widgets/src/edit.rs b/crates/kas-widgets/src/edit.rs index ff0a595d2..65a713fc5 100644 --- a/crates/kas-widgets/src/edit.rs +++ b/crates/kas-widgets/src/edit.rs @@ -692,9 +692,6 @@ impl_scope! { Used } Event::Command(cmd, code) => { - // Note: we can receive a Command without key focus, but should - // ensure we have focus before acting on it. - request_focus(self, cx, data); if self.has_key_focus { match self.control_key(cx, data, cmd, code) { Ok(r) => r, From b8cc18b90d06335622d80da60af335fd8648bb9f Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 9 Oct 2023 11:33:29 +0100 Subject: [PATCH 10/16] Fix shortcut map for Ctrl+X = Cut --- crates/kas-core/src/event/config/shortcuts.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/kas-core/src/event/config/shortcuts.rs b/crates/kas-core/src/event/config/shortcuts.rs index 7fb6aab57..45c5f8f0f 100644 --- a/crates/kas-core/src/event/config/shortcuts.rs +++ b/crates/kas-core/src/event/config/shortcuts.rs @@ -120,7 +120,7 @@ impl Shortcuts { (Key::Character("v".into()), Command::Paste), (Key::Character("]".into()), Command::Paste), (Key::Character("w".into()), Command::Close), - (Key::Character("w".into()), Command::Cut), + (Key::Character("x".into()), Command::Cut), (Key::Character("z".into()), Command::Undo), (Key::Tab, Command::TabNext), ]; From f618b8a1f6bc7c49166949dd2177cac71e43ff92 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 9 Oct 2023 11:43:52 +0100 Subject: [PATCH 11/16] Revise handling of pending nav/sel/key focus; add Event::SelFocus, KeyFocus --- crates/kas-core/src/event/components.rs | 74 +++++++----- crates/kas-core/src/event/cx/config.rs | 12 +- crates/kas-core/src/event/cx/cx_pub.rs | 93 ++++++++------- crates/kas-core/src/event/cx/cx_shell.rs | 25 ++-- crates/kas-core/src/event/cx/mod.rs | 138 +++++++++++++++-------- crates/kas-core/src/event/events.rs | 49 ++++---- crates/kas-core/src/text.rs | 2 +- crates/kas-core/src/text/selection.rs | 32 ++++++ crates/kas-widgets/src/edit.rs | 55 ++++----- crates/kas-widgets/src/scroll_label.rs | 35 +++--- 10 files changed, 321 insertions(+), 194 deletions(-) diff --git a/crates/kas-core/src/event/components.rs b/crates/kas-core/src/event/components.rs index 1621ca027..bb87ca320 100644 --- a/crates/kas-core/src/event/components.rs +++ b/crates/kas-core/src/event/components.rs @@ -9,7 +9,8 @@ use super::ScrollDelta::{LineDelta, PixelDelta}; use super::*; use crate::cast::traits::*; use crate::geom::{Coord, Offset, Rect, Size, Vec2}; -#[allow(unused)] use crate::text::SelectionHelper; +#[allow(unused)] +use crate::text::{SelectionAction, SelectionHelper}; use crate::{Action, Id}; use kas_macros::impl_default; use std::time::{Duration, Instant}; @@ -377,24 +378,21 @@ pub enum TextInputAction { Unused, /// Pan text using the given `delta` Pan(Offset), - /// Keyboard focus should be requested (if not already active) + /// Focus, optionally updating position and selection /// - /// This is also the case for variant `Cursor(_, true, _, _)` (i.e. if - /// `anchor == true`). - Focus, - /// Update cursor and/or selection: `(coord, anchor, clear, repeats)` + /// To handle: /// - /// The cursor position should be moved to `coord`. - /// - /// If `anchor`, the anchor position (used for word and line selection mode) - /// should be set to the new cursor position. - /// - /// If `clear`, the selection should be cleared (move selection position to - /// edit position). - /// - /// If `repeats > 1`, [`SelectionHelper::expand`] should be called with - /// this parameter to enable word/line selection mode. - Cursor(Coord, bool, bool, u32), + /// 1. If a `coord` is included, translate to a text index then call + /// [`SelectionHelper::set_edit_pos`]. + /// 2. Call [`SelectionHelper::action`]. + /// 3. If supporting the primary buffer (Unix), set its contents now if the + /// widget has selection focus or otherwise when handling + /// [`Event::SelFocus`] for a pointer source. + /// 4. Request keyboard or selection focus if not already gained. + Focus { + coord: Option, + action: SelectionAction, + }, } impl TextInput { @@ -410,20 +408,31 @@ impl TextInput { use TextInputAction as Action; match event { Event::PressStart { press } if press.is_primary() => { - let (action, icon) = match *press { + let mut action = Action::Focus { + coord: None, + action: SelectionAction::default(), + }; + let icon = match *press { PressSource::Touch(touch_id) => { self.touch_phase = TouchPhase::Start(touch_id, press.coord); let delay = cx.config().touch_select_delay(); cx.request_timer_update(w_id.clone(), PAYLOAD_SELECT, delay, false); - (Action::Focus, None) + None } PressSource::Mouse(..) if cx.config_enable_mouse_text_pan() => { - (Action::Focus, Some(CursorIcon::Grabbing)) + Some(CursorIcon::Grabbing) + } + PressSource::Mouse(_, repeats) => { + action = Action::Focus { + coord: Some(press.coord), + action: SelectionAction { + anchor: true, + clear: !cx.modifiers().shift_key(), + repeats, + }, + }; + None } - PressSource::Mouse(_, repeats) => ( - Action::Cursor(press.coord, true, !cx.modifiers().shift_key(), repeats), - None, - ), }; press.grab(w_id).with_opt_icon(icon).with_cx(cx); self.glide.press_start(); @@ -443,14 +452,18 @@ impl TextInput { } } TouchPhase::Pan(id) if id == touch_id => Action::Pan(delta), - _ => Action::Cursor(press.coord, false, false, 1), + _ => Action::Focus { + coord: Some(press.coord), + action: SelectionAction::new(false, false, 1), + }, }, PressSource::Mouse(..) if cx.config_enable_mouse_text_pan() => { Action::Pan(delta) } - PressSource::Mouse(_, repeats) => { - Action::Cursor(press.coord, false, false, repeats) - } + PressSource::Mouse(_, repeats) => Action::Focus { + coord: Some(press.coord), + action: SelectionAction::new(false, false, repeats), + }, } } Event::PressEnd { press, .. } if press.is_primary() => { @@ -469,7 +482,10 @@ impl TextInput { match self.touch_phase { TouchPhase::Start(touch_id, coord) => { self.touch_phase = TouchPhase::Cursor(touch_id); - Action::Cursor(coord, true, !cx.modifiers().shift_key(), 1) + Action::Focus { + coord: Some(coord), + action: SelectionAction::new(true, !cx.modifiers().shift_key(), 1), + } } // Note: if the TimerUpdate were from another requester it // should technically be Unused, but it doesn't matter diff --git a/crates/kas-core/src/event/cx/config.rs b/crates/kas-core/src/event/cx/config.rs index 9b64aef88..733bac3b4 100644 --- a/crates/kas-core/src/event/cx/config.rs +++ b/crates/kas-core/src/event/cx/config.rs @@ -5,8 +5,8 @@ //! Configuration context -use super::Pending; -use crate::event::EventState; +use super::PendingNavFocus; +use crate::event::{EventState, FocusSource}; use crate::geom::{Rect, Size}; use crate::layout::AlignPair; use crate::text::TextApi; @@ -57,10 +57,10 @@ impl<'a> ConfigCx<'a> { pub fn disable_nav_focus(&mut self, disabled: bool) { self.ev.config.nav_focus = !disabled; if disabled { - if let Some(id) = self.ev.nav_focus.take() { - self.pending - .push_back(Pending::Send(id, Event::LostNavFocus)); - } + self.pending_nav_focus = PendingNavFocus::Set { + target: None, + source: FocusSource::Synthetic, + }; } } diff --git a/crates/kas-core/src/event/cx/cx_pub.rs b/crates/kas-core/src/event/cx/cx_pub.rs index 06026dc40..10e0225af 100644 --- a/crates/kas-core/src/event/cx/cx_pub.rs +++ b/crates/kas-core/src/event/cx/cx_pub.rs @@ -376,39 +376,50 @@ impl EventState { /// Request keyboard input focus /// - /// When granted, the widget will receive [`Event::Key`] on key presses - /// and releases. It will not receive [`Event::Command`] for these events - /// (though it may still receive [`Event::Command`] from other sources). + /// When granted, the widget will receive [`Event::KeyFocus`] followed by + /// [`Event::Key`] for each key press / release. Note that this disables + /// translation of key events to [`Event::Command`] while key focus is + /// active. + /// + /// The `source` parameter is used by [`Event::SelFocus`]. /// /// Key focus implies sel focus (see [`Self::request_sel_focus`]) and /// navigation focus. - /// - /// Returns true on success or when the widget already had key focus. - /// - /// When key focus is lost, [`Event::LostKeyFocus`] is sent. #[inline] - pub fn request_key_focus(&mut self, id: Id) -> bool { - self.set_sel_focus(id, true); - true + pub fn request_key_focus(&mut self, target: Id, source: FocusSource) { + self.pending_sel_focus = Some(PendingSelFocus { + target, + key_focus: true, + source, + }); } /// Request selection focus /// - /// Returns true on success or when the widget already had sel focus. - /// /// To prevent multiple simultaneous selections (e.g. of text) in the UI, /// only widgets with "selection focus" are allowed to select things. /// Selection focus is implied by character focus. [`Event::LostSelFocus`] /// is sent when selection focus is lost; in this case any existing /// selection should be cleared. /// + /// The `source` parameter is used by [`Event::SelFocus`]. + /// /// Selection focus implies navigation focus. /// /// When key focus is lost, [`Event::LostSelFocus`] is sent. #[inline] - pub fn request_sel_focus(&mut self, id: Id) -> bool { - self.set_sel_focus(id, false); - true + pub fn request_sel_focus(&mut self, target: Id, source: FocusSource) { + if let Some(ref pending) = self.pending_sel_focus { + if pending.target == target { + return; + } + } + + self.pending_sel_focus = Some(PendingSelFocus { + target, + key_focus: false, + source, + }); } /// Set a grab's depress target @@ -471,6 +482,10 @@ impl EventState { /// Get the current navigation focus, if any /// /// This is the widget selected by navigating the UI with the Tab key. + /// + /// Note: changing navigation focus (e.g. via [`Self::clear_nav_focus`], + /// [`Self::set_nav_focus`] or [`Self::next_nav_focus`]) does not + /// immediately affect the result of this method. #[inline] pub fn nav_focus(&self) -> Option<&Id> { self.nav_focus.as_ref() @@ -478,13 +493,10 @@ impl EventState { /// Clear navigation focus pub fn clear_nav_focus(&mut self) { - if let Some(id) = self.nav_focus.take() { - self.action(id.clone(), Action::REDRAW); - self.pending - .push_back(Pending::Send(id, Event::LostNavFocus)); - log::debug!(target: "kas_core::event", "nav_focus = None"); - } - self.clear_key_focus(); + self.pending_nav_focus = PendingNavFocus::Set { + target: None, + source: FocusSource::Synthetic, + }; } /// Set navigation focus directly @@ -497,23 +509,10 @@ impl EventState { /// is not checked or required. For example, a `ScrollLabel` can receive /// focus on text selection with the mouse. pub fn set_nav_focus(&mut self, id: Id, source: FocusSource) { - if id == self.nav_focus || !self.config.nav_focus { - return; - } - - if let Some(old_id) = self.nav_focus.take() { - self.action(&old_id, Action::REDRAW); - self.pending - .push_back(Pending::Send(old_id, Event::LostNavFocus)); - } - if id != self.sel_focus { - self.clear_key_focus(); - } - self.action(&id, Action::REDRAW); - self.nav_focus = Some(id.clone()); - log::debug!(target: "kas_core::event", "nav_focus = Some({id})"); - self.pending - .push_back(Pending::Send(id, Event::NavFocus(source))); + self.pending_nav_focus = PendingNavFocus::Set { + target: Some(id), + source, + }; } /// Advance the navigation focus @@ -531,11 +530,11 @@ impl EventState { reverse: bool, source: FocusSource, ) { - self.pending.push_back(Pending::NextNavFocus { + self.pending_nav_focus = PendingNavFocus::Next { target: target.into(), reverse, source, - }); + }; } /// Set the cursor icon @@ -853,6 +852,18 @@ impl<'a> EventCx<'a> { self.shell.set_clipboard(content) } + /// True if the primary buffer is enabled + #[inline] + pub fn has_primary(&self) -> bool { + cfg_if::cfg_if! { + if #[cfg(unix)] { + true + } else { + false + } + } + } + /// Get contents of primary buffer /// /// Linux has a "primary buffer" with implicit copy on text selection and diff --git a/crates/kas-core/src/event/cx/cx_shell.rs b/crates/kas-core/src/event/cx/cx_shell.rs index 09c12ba6d..617c90cd1 100644 --- a/crates/kas-core/src/event/cx/cx_shell.rs +++ b/crates/kas-core/src/event/cx/cx_shell.rs @@ -53,6 +53,8 @@ impl EventState { time_updates: vec![], fut_messages: vec![], pending: Default::default(), + pending_sel_focus: None, + pending_nav_focus: PendingNavFocus::None, pending_cmds: Default::default(), action: Action::empty(), } @@ -225,16 +227,25 @@ impl EventState { } 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); - } } } + if let Some(pending) = cx.pending_sel_focus.take() { + cx.set_sel_focus(win.as_node(data), pending); + } + + match std::mem::take(&mut cx.pending_nav_focus) { + PendingNavFocus::None => (), + PendingNavFocus::Set { target, source } => { + cx.set_nav_focus_impl(win.as_node(data), target, source) + } + PendingNavFocus::Next { + target, + reverse, + source, + } => cx.next_nav_focus_impl(win.as_node(data), target, reverse, source), + } + while let Some((id, cmd)) = cx.pending_cmds.pop_front() { log::trace!(target: "kas_core::event", "sending pending command {cmd:?} to {id}"); cx.send_event(win.as_node(data), id, Event::Command(cmd, None)); diff --git a/crates/kas-core/src/event/cx/mod.rs b/crates/kas-core/src/event/cx/mod.rs index 0905d5181..3399b4a00 100644 --- a/crates/kas-core/src/event/cx/mod.rs +++ b/crates/kas-core/src/event/cx/mod.rs @@ -148,7 +148,22 @@ enum Pending { Configure(Id), Update(Id), Send(Id, Event), - NextNavFocus { +} + +struct PendingSelFocus { + target: Id, + key_focus: bool, + source: FocusSource, +} + +#[crate::impl_default(PendingNavFocus::None)] +enum PendingNavFocus { + None, + Set { + target: Option, + source: FocusSource, + }, + Next { target: Option, reverse: bool, source: FocusSource, @@ -204,6 +219,9 @@ pub struct EventState { fut_messages: Vec<(Id, Pin>>)>, // FIFO queue of events pending handling pending: VecDeque, + // Optional new target for selection focus. bool is true if this also gains key focus. + pending_sel_focus: Option, + pending_nav_focus: PendingNavFocus, pending_cmds: VecDeque<(Id, Command)>, #[cfg_attr(not(feature = "internal_doc"), doc(hidden))] #[cfg_attr(doc_cfg, doc(cfg(internal_doc)))] @@ -327,43 +345,6 @@ impl EventState { None } - fn clear_key_focus(&mut self) { - if let Some(id) = self.key_focus() { - log::trace!("clear_key_focus"); - // If widget has key focus, this is lost - self.key_focus = false; - self.pending - .push_back(Pending::Send(id, Event::LostKeyFocus)); - } - } - - // Set selection focus to `wid`; if `key_focus` also set that - fn set_sel_focus(&mut self, wid: Id, key_focus: bool) { - log::trace!("set_sel_focus: wid={wid}, key_focus={key_focus}"); - // The widget probably already has nav focus, but anyway: - self.set_nav_focus(wid.clone(), FocusSource::Synthetic); - - if wid == self.sel_focus { - self.key_focus = self.key_focus || key_focus; - return; - } - - if let Some(id) = self.sel_focus.clone() { - if self.key_focus { - // If widget has key focus, this is lost - self.pending - .push_back(Pending::Send(id.clone(), Event::LostKeyFocus)); - } - - // Selection focus is lost if another widget receives key focus - self.pending - .push_back(Pending::Send(id, Event::LostSelFocus)); - } - - self.key_focus = key_focus; - self.sel_focus = Some(wid); - } - // Clear old hover, set new hover, send events. // If there is a popup, only permit descendands of that. fn set_hover(&mut self, mut w_id: Option) { @@ -510,7 +491,6 @@ impl<'a> EventCx<'a> { let event = Event::Command(Command::Activate, Some(code)); self.send_event(widget, id, event); } else if self.config.nav_focus && vkey == Key::Tab { - self.clear_key_focus(); let shift = self.modifiers.shift_key(); self.next_nav_focus_impl(widget.re(), None, shift, FocusSource::Key); } else if vkey == Key::Escape { @@ -619,8 +599,68 @@ impl<'a> EventCx<'a> { } } - /// Advance the keyboard navigation focus - pub fn next_nav_focus_impl( + // Set selection focus to `wid` immediately; if `key_focus` also set that + fn set_sel_focus(&mut self, mut widget: Node<'_>, pending: PendingSelFocus) { + let PendingSelFocus { + target, + key_focus, + source, + } = pending; + + log::trace!("set_sel_focus: target={target}, key_focus={key_focus}"); + // The widget probably already has nav focus, but anyway: + self.set_nav_focus(target.clone(), FocusSource::Synthetic); + + if target == self.sel_focus { + self.key_focus = self.key_focus || key_focus; + return; + } + + if let Some(id) = self.sel_focus.clone() { + if self.key_focus { + // If widget has key focus, this is lost + self.send_event(widget.re(), id.clone(), Event::LostKeyFocus); + } + + // Selection focus is lost if another widget receives key focus + self.send_event(widget.re(), id, Event::LostSelFocus); + } + + self.key_focus = key_focus; + self.sel_focus = Some(target.clone()); + + self.send_event(widget.re(), target.clone(), Event::SelFocus(source)); + if key_focus { + self.send_event(widget, target, Event::KeyFocus); + } + } + + /// Set navigation focus immediately + fn set_nav_focus_impl(&mut self, mut widget: Node, target: Option, source: FocusSource) { + if target == self.nav_focus || !self.config.nav_focus { + return; + } + + if let Some(old) = self.nav_focus.take() { + self.action(&old, Action::REDRAW); + self.send_event(widget.re(), old, Event::LostNavFocus); + } + + if let Some(id) = self.key_focus() { + self.key_focus = false; + self.send_event(widget.re(), id, Event::LostKeyFocus); + } + + self.nav_focus = target.clone(); + log::debug!(target: "kas_core::event", "nav_focus = {target:?}"); + if let Some(id) = target { + self.action(&id, Action::REDRAW); + self.send_event(widget, id, Event::NavFocus(source)); + } + } + + /// Advance the keyboard navigation focus immediately + fn next_nav_focus_impl( &mut self, mut widget: Node, target: Option, @@ -670,21 +710,21 @@ impl<'a> EventCx<'a> { return; } - if let Some(id) = self.nav_focus.take() { - self.redraw(id.clone()); - self.pending - .push_back(Pending::Send(id, Event::LostNavFocus)); + if let Some(old) = self.nav_focus.take() { + self.redraw(&old); + self.send_event(widget.re(), old, Event::LostNavFocus); } - if self.sel_focus != opt_id { - self.clear_key_focus(); + + if let Some(id) = self.key_focus() { + self.key_focus = false; + self.send_event(widget.re(), id, Event::LostKeyFocus); } self.nav_focus = opt_id.clone(); if let Some(id) = opt_id { log::debug!(target: "kas_core::event", "nav_focus = Some({id})"); self.redraw(id.clone()); - self.pending - .push_back(Pending::Send(id, Event::NavFocus(source))); + self.send_event(widget, id, Event::NavFocus(source)); } else { log::debug!(target: "kas_core::event", "nav_focus = None"); // Most likely an error occurred diff --git a/crates/kas-core/src/event/events.rs b/crates/kas-core/src/event/events.rs index 906bfbc34..de0392b2d 100644 --- a/crates/kas-core/src/event/events.rs +++ b/crates/kas-core/src/event/events.rs @@ -185,10 +185,12 @@ pub enum Event { #[cfg_attr(not(feature = "internal_doc"), doc(hidden))] #[cfg_attr(doc_cfg, doc(cfg(internal_doc)))] PopupClosed(WindowId), - /// Sent when a widget receives navigation focus + /// Notification that a widget has gained navigation focus /// /// Navigation focus implies that the widget is highlighted and will be the - /// primary target of [`Event::Command`]. + /// primary target of [`Event::Command`], and is thus able to receive basic + /// keyboard input (e.g. arrow keys). To receive full keyboard input + /// ([`Event::Key`]), call [`EventState::request_key_focus`]. /// /// With [`FocusSource::Pointer`] the widget should already have received /// [`Event::PressStart`]. @@ -196,28 +198,33 @@ pub enum Event { /// With [`FocusSource::Key`], [`EventCx::set_scroll`] is /// called automatically (to ensure that the widget is visible) and the /// response will be forced to [`Used`]. - /// - /// The widget may wish to call [`EventState::request_key_focus`], but - /// likely only when [`FocusSource::key_or_synthetic`]. NavFocus(FocusSource), - /// Sent when a widget becomes the mouse hover target - /// - /// The payload is `true` when focus is gained, `false` when lost. - MouseHover(bool), - /// Sent when a widget loses navigation focus + /// Notification that a widget has lost navigation focus LostNavFocus, - /// Widget lost keyboard input focus - /// - /// This focus is gained through the widget calling [`EventState::request_key_focus`]. - LostKeyFocus, - /// Widget lost selection focus + /// Notification that a widget has gained selection focus /// - /// This focus is gained through the widget calling [`EventState::request_sel_focus`] - /// or [`EventState::request_key_focus`]. + /// This focus must be requested by calling + /// [`EventState::request_sel_focus`] or [`EventState::request_key_focus`]. + SelFocus(FocusSource), + /// Notification that a widget has lost selection focus /// /// In the case the widget also had character focus, [`Event::LostKeyFocus`] is /// received first. LostSelFocus, + /// Notification that a widget has gained keyboard input focus + /// + /// This focus must be requested by calling + /// [`EventState::request_key_focus`]. + /// + /// This is always preceeded by [`Event::SelFocus`] and is received prior to + /// [`Event::Key`] events. + KeyFocus, + /// Notification that a widget has lost keyboard input focus + LostKeyFocus, + /// Notification that a widget gains or loses mouse hover + /// + /// The payload is `true` when focus is gained, `false` when lost. + MouseHover(bool), } impl std::ops::Add for Event { @@ -296,7 +303,7 @@ impl Event { Key(_, _) | Scroll(_) | Pan { .. } => false, CursorMove { .. } | PressStart { .. } | PressMove { .. } | PressEnd { .. } => false, TimerUpdate(_) | PopupClosed(_) => true, - NavFocus { .. } | MouseHover(_) => false, + NavFocus { .. } | SelFocus(_) | KeyFocus | MouseHover(_) => false, LostNavFocus | LostKeyFocus | LostSelFocus => true, } } @@ -317,8 +324,10 @@ impl Event { CursorMove { .. } | PressStart { .. } => true, PressMove { .. } | PressEnd { .. } => false, TimerUpdate(_) | PopupClosed(_) => false, - NavFocus { .. } | MouseHover(_) | LostNavFocus => false, - LostKeyFocus | LostSelFocus => false, + NavFocus { .. } | LostNavFocus => false, + SelFocus(_) | LostSelFocus => false, + KeyFocus | LostKeyFocus => false, + MouseHover(_) => false, } } } diff --git a/crates/kas-core/src/text.rs b/crates/kas-core/src/text.rs index 9667d75e3..ade422430 100644 --- a/crates/kas-core/src/text.rs +++ b/crates/kas-core/src/text.rs @@ -20,7 +20,7 @@ pub use kas_text::*; mod selection; -pub use selection::SelectionHelper; +pub use selection::{SelectionAction, SelectionHelper}; mod string; pub use string::AccessString; diff --git a/crates/kas-core/src/text/selection.rs b/crates/kas-core/src/text/selection.rs index 4f787eb33..0bd912511 100644 --- a/crates/kas-core/src/text/selection.rs +++ b/crates/kas-core/src/text/selection.rs @@ -9,6 +9,25 @@ use super::{TextApi, TextApiExt}; use std::ops::Range; use unicode_segmentation::UnicodeSegmentation; +/// Action used by [`crate::event::components::TextInput`] +#[derive(Default)] +pub struct SelectionAction { + pub anchor: bool, + pub clear: bool, + pub repeats: u32, +} + +impl SelectionAction { + /// Construct + pub fn new(anchor: bool, clear: bool, repeats: u32) -> Self { + SelectionAction { + anchor, + clear, + repeats, + } + } +} + /// Text-selection logic /// /// This struct holds an "edit pos" and a "selection pos", which together form @@ -152,4 +171,17 @@ impl SelectionHelper { self.sel_pos = start; self.edit_pos = end; } + + /// Handle an action + pub fn action(&mut self, text: &T, action: SelectionAction) { + if action.anchor { + self.set_anchor(); + } + if action.clear { + self.set_empty(); + } + if action.repeats > 1 { + self.expand(text, action.repeats); + } + } } diff --git a/crates/kas-widgets/src/edit.rs b/crates/kas-widgets/src/edit.rs index 65a713fc5..c3ccbb35c 100644 --- a/crates/kas-widgets/src/edit.rs +++ b/crates/kas-widgets/src/edit.rs @@ -7,7 +7,7 @@ use crate::{ScrollBar, ScrollMsg}; use kas::event::components::{TextInput, TextInputAction}; -use kas::event::{Command, CursorIcon, ElementState, KeyCode, Scroll, ScrollDelta}; +use kas::event::{Command, CursorIcon, ElementState, FocusSource, KeyCode, Scroll, ScrollDelta}; use kas::geom::Vec2; use kas::prelude::*; use kas::text::{NotReady, SelectionHelper, Text}; @@ -654,17 +654,11 @@ impl_scope! { } fn handle_event(&mut self, cx: &mut EventCx, data: &G::Data, event: Event) -> IsUsed { - fn request_focus(s: &mut EditField, cx: &mut EventCx, data: &G::Data) { - if !s.has_key_focus && cx.request_key_focus(s.id()) { - s.has_key_focus = true; - cx.set_scroll(Scroll::Rect(s.rect())); - G::focus_gained(s, cx, data); - } - } - match event { Event::NavFocus(source) if source.key_or_synthetic() => { - request_focus(self, cx, data); + if !self.has_key_focus { + cx.request_key_focus(self.id(), source); + } if !self.class.multi_line() { self.selection.clear(); self.selection.set_edit_pos(self.text.str_len()); @@ -680,6 +674,21 @@ impl_scope! { } Used } + Event::SelFocus(source) => { + // NOTE: sel focus implies key focus since we only request + // the latter. We must set before calling self.set_primary. + self.has_key_focus = true; + if source == FocusSource::Pointer { + self.set_primary(cx); + } + Used + } + Event::KeyFocus => { + self.has_key_focus = true; + cx.set_scroll(Scroll::Rect(self.rect())); + G::focus_gained(self, cx, data); + Used + } Event::LostKeyFocus => { self.has_key_focus = false; cx.redraw(&self); @@ -751,24 +760,16 @@ impl_scope! { TextInputAction::None => Used, TextInputAction::Unused => Unused, TextInputAction::Pan(delta) => self.pan_delta(cx, delta), - TextInputAction::Focus => { - request_focus(self, cx, data); - Used - } - TextInputAction::Cursor(coord, anchor, clear, repeats) => { - request_focus(self, cx, data); - if self.has_key_focus { + TextInputAction::Focus { coord, action } => { + if let Some(coord) = coord { self.set_edit_pos_from_coord(cx, coord); - if anchor { - self.selection.set_anchor(); - } - if clear { - self.selection.set_empty(); - } - if repeats > 1 { - self.selection.expand(&self.text, repeats); - } + } + self.selection.action(&self.text, action); + + if self.has_key_focus { self.set_primary(cx); + } else { + cx.request_key_focus(self.id(), FocusSource::Pointer); } Used } @@ -1436,7 +1437,7 @@ impl EditField { } fn set_primary(&self, cx: &mut EventCx) { - if !self.selection.is_empty() { + if self.has_key_focus && !self.selection.is_empty() && cx.has_primary() { let range = self.selection.range(); cx.set_primary(String::from(&self.text.as_str()[range])); } diff --git a/crates/kas-widgets/src/scroll_label.rs b/crates/kas-widgets/src/scroll_label.rs index 1802f6cd2..c3dba5681 100644 --- a/crates/kas-widgets/src/scroll_label.rs +++ b/crates/kas-widgets/src/scroll_label.rs @@ -7,7 +7,7 @@ use super::{ScrollBar, ScrollMsg}; use kas::event::components::{TextInput, TextInputAction}; -use kas::event::{Command, CursorIcon, Scroll, ScrollDelta}; +use kas::event::{Command, CursorIcon, FocusSource, Scroll, ScrollDelta}; use kas::geom::Vec2; use kas::prelude::*; use kas::text::format::{EditableText, FormattableText}; @@ -29,6 +29,7 @@ impl_scope! { text: Text, text_size: Size, selection: SelectionHelper, + has_sel_focus: bool, input_handler: TextInput, #[widget] bar: ScrollBar, @@ -98,6 +99,7 @@ impl_scope! { text: Text::new(text), text_size: Size::ZERO, selection: SelectionHelper::new(0, 0), + has_sel_focus: false, input_handler: Default::default(), bar: ScrollBar::new().with_invisible(true), } @@ -135,7 +137,7 @@ impl_scope! { } fn set_primary(&self, cx: &mut EventCx) { - if !self.selection.is_empty() { + if self.has_sel_focus && !self.selection.is_empty() && cx.has_primary() { let range = self.selection.range(); cx.set_primary(String::from(&self.text.as_str()[range])); } @@ -238,7 +240,15 @@ impl_scope! { // TODO: scroll by command _ => Unused, }, + Event::SelFocus(source) => { + self.has_sel_focus = true; + if source == FocusSource::Pointer { + self.set_primary(cx); + } + Used + } Event::LostSelFocus => { + self.has_sel_focus = false; self.selection.set_empty(); cx.redraw(self); Used @@ -251,22 +261,19 @@ impl_scope! { self.pan_delta(cx, delta2) } event => match self.input_handler.handle(cx, self.id(), event) { - TextInputAction::None | TextInputAction::Focus => Used, + TextInputAction::None => Used, TextInputAction::Unused => Unused, TextInputAction::Pan(delta) => self.pan_delta(cx, delta), - TextInputAction::Cursor(coord, anchor, clear, repeats) => { - if (clear && repeats <= 1) || cx.request_sel_focus(self.id()) { + TextInputAction::Focus { coord, action } => { + if let Some(coord) = coord { self.set_edit_pos_from_coord(cx, coord); - if anchor { - self.selection.set_anchor(); - } - if clear { - self.selection.set_empty(); - } - if repeats > 1 { - self.selection.expand(&self.text, repeats); - } + } + self.selection.action(&self.text, action); + + if self.has_sel_focus { self.set_primary(cx); + } else { + cx.request_sel_focus(self.id(), FocusSource::Pointer); } Used } From 12f6eaba292b89dee509224597979be48296f966 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 9 Oct 2023 11:45:07 +0100 Subject: [PATCH 12/16] Move kas-core/src/text.rs to text/mod.rs --- crates/kas-core/src/{text.rs => text/mod.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename crates/kas-core/src/{text.rs => text/mod.rs} (100%) diff --git a/crates/kas-core/src/text.rs b/crates/kas-core/src/text/mod.rs similarity index 100% rename from crates/kas-core/src/text.rs rename to crates/kas-core/src/text/mod.rs From 15043be50488a8dd1efa08089d5e9291b32db0a0 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 9 Oct 2023 14:52:51 +0100 Subject: [PATCH 13/16] Revise handling of region moved --- crates/kas-core/src/event/cx/cx_shell.rs | 55 +++++++++++------------- crates/kas-core/src/event/cx/mod.rs | 54 +++++++++++------------ crates/kas-core/src/shell/window.rs | 7 +-- 3 files changed, 54 insertions(+), 62 deletions(-) diff --git a/crates/kas-core/src/event/cx/cx_shell.rs b/crates/kas-core/src/event/cx/cx_shell.rs index 617c90cd1..a7f5665f4 100644 --- a/crates/kas-core/src/event/cx/cx_shell.rs +++ b/crates/kas-core/src/event/cx/cx_shell.rs @@ -39,6 +39,7 @@ impl EventState { nav_fallback: None, hover: None, hover_icon: CursorIcon::Default, + old_hover_icon: CursorIcon::Default, key_depress: Default::default(), last_mouse_coord: Coord::ZERO, last_click_button: FAKE_MOUSE_BUTTON, @@ -53,6 +54,7 @@ impl EventState { time_updates: vec![], fut_messages: vec![], pending: Default::default(), + region_moved: false, pending_sel_focus: None, pending_nav_focus: PendingNavFocus::None, pending_cmds: Default::default(), @@ -93,23 +95,7 @@ impl EventState { self.new_access_layer(id.clone(), false); ConfigCx::new(sizer, self).configure(win.as_node(data), id); - - let hover = win.find_id(data, self.last_mouse_coord); - self.set_hover(hover); - } - - /// Update the widgets under the cursor and touch events - pub(crate) fn handle_region_moved(&mut self, win: &mut Window, data: &A) { - log::trace!(target: "kas_core::event", "region_moved"); - // Note: redraw is already implied. - - // Update hovered widget - let hover = win.find_id(data, self.last_mouse_coord); - self.set_hover(hover); - - for grab in self.touch_grab.iter_mut() { - grab.cur_id = win.find_id(data, grab.coord); - } + self.region_moved = true; } /// Get the next resume time @@ -148,7 +134,10 @@ impl EventState { win: &mut Window, data: &A, ) -> Action { - let old_hover_icon = self.hover_icon; + if self.action.contains(Action::REGION_MOVED) { + self.region_moved = true; + self.action.remove(Action::REGION_MOVED); + } self.with(shell, window, messages, |cx| { while let Some((id, wid)) = cx.popup_removed.pop() { @@ -215,18 +204,23 @@ impl EventState { 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); + cx.region_moved = true; } 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); - } + } + } + + if cx.region_moved { + cx.region_moved = false; + + // Update hovered widget + let hover = win.find_id(data, cx.last_mouse_coord); + cx.set_hover(win.as_node(data), hover); + + for grab in cx.touch_grab.iter_mut() { + grab.cur_id = win.find_id(data, grab.coord); } } @@ -256,9 +250,10 @@ impl EventState { cx.poll_futures(win.as_node(data)); }); - if self.hover_icon != old_hover_icon && self.mouse_grab.is_none() { + if self.hover_icon != self.old_hover_icon && self.mouse_grab.is_none() { window.set_cursor_icon(self.hover_icon); } + self.old_hover_icon = self.hover_icon; std::mem::take(&mut self.action) } @@ -314,8 +309,8 @@ impl<'a> EventCx<'a> { #[cfg_attr(doc_cfg, doc(cfg(feature = "winit")))] pub(crate) fn handle_winit( &mut self, - data: &A, win: &mut Window, + data: &A, event: winit::event::WindowEvent, ) { use winit::event::{MouseScrollDelta, TouchPhase, WindowEvent::*}; @@ -391,7 +386,7 @@ impl<'a> EventCx<'a> { // Update hovered win let id = win.find_id(data, coord); - self.set_hover(id.clone()); + self.set_hover(win.as_node(data), id.clone()); if let Some(grab) = self.state.mouse_grab.as_mut() { match grab.details { @@ -437,7 +432,7 @@ impl<'a> EventCx<'a> { // If there's a mouse grab, we will continue to receive // coordinates; if not, set a fake coordinate off the window self.last_mouse_coord = Coord(-1, -1); - self.set_hover(None); + self.set_hover(win.as_node(data), None); } } MouseWheel { delta, .. } => { diff --git a/crates/kas-core/src/event/cx/mod.rs b/crates/kas-core/src/event/cx/mod.rs index 3399b4a00..c6173e074 100644 --- a/crates/kas-core/src/event/cx/mod.rs +++ b/crates/kas-core/src/event/cx/mod.rs @@ -147,7 +147,6 @@ struct PanGrab { enum Pending { Configure(Id), Update(Id), - Send(Id, Event), } struct PendingSelFocus { @@ -202,6 +201,7 @@ pub struct EventState { nav_fallback: Option, hover: Option, hover_icon: CursorIcon, + old_hover_icon: CursorIcon, key_depress: LinearMap, last_mouse_coord: Coord, last_click_button: MouseButton, @@ -219,6 +219,7 @@ pub struct EventState { fut_messages: Vec<(Id, Pin>>)>, // FIFO queue of events pending handling pending: VecDeque, + region_moved: bool, // Optional new target for selection focus. bool is true if this also gains key focus. pending_sel_focus: Option, pending_nav_focus: PendingNavFocus, @@ -344,32 +345,6 @@ impl EventState { } None } - - // Clear old hover, set new hover, send events. - // If there is a popup, only permit descendands of that. - fn set_hover(&mut self, mut w_id: Option) { - if let Some(ref id) = w_id { - if let Some(popup) = self.popups.last() { - if !popup.1.id.is_ancestor_of(id) { - w_id = None; - } - } - } - - if self.hover != w_id { - log::trace!("set_hover: w_id={w_id:?}"); - if let Some(id) = self.hover.take() { - self.pending - .push_back(Pending::Send(id, Event::MouseHover(false))); - } - self.hover = w_id.clone(); - - if let Some(id) = w_id { - self.pending - .push_back(Pending::Send(id, Event::MouseHover(true))); - } - } - } } /// Event handling context @@ -599,6 +574,31 @@ impl<'a> EventCx<'a> { } } + // Clear old hover, set new hover, send events. + // If there is a popup, only permit descendands of that. + fn set_hover(&mut self, mut widget: Node<'_>, mut w_id: Option) { + if let Some(ref id) = w_id { + if let Some(popup) = self.popups.last() { + if !popup.1.id.is_ancestor_of(id) { + w_id = None; + } + } + } + + if self.hover != w_id { + log::trace!("set_hover: w_id={w_id:?}"); + self.hover_icon = Default::default(); + if let Some(id) = self.hover.take() { + self.send_event(widget.re(), id, Event::MouseHover(false)); + } + self.hover = w_id.clone(); + + if let Some(id) = w_id { + self.send_event(widget, id, Event::MouseHover(true)); + } + } + } + // Set selection focus to `wid` immediately; if `key_focus` also set that fn set_sel_focus(&mut self, mut widget: Node<'_>, pending: PendingSelFocus) { let PendingSelFocus { diff --git a/crates/kas-core/src/shell/window.rs b/crates/kas-core/src/shell/window.rs index 9e829ae93..4d3067c97 100644 --- a/crates/kas-core/src/shell/window.rs +++ b/crates/kas-core/src/shell/window.rs @@ -231,7 +231,7 @@ impl> Window { let mut messages = ErasedStack::new(); self.ev_state .with(&mut shared.shell, window, &mut messages, |cx| { - cx.handle_winit(&shared.data, &mut self.widget, event); + cx.handle_winit(&mut self.widget, &shared.data, event); }); shared.handle_messages(&mut messages); @@ -318,10 +318,7 @@ impl> Window { { self.apply_size(shared, false); } - if action.contains(Action::REGION_MOVED) { - self.ev_state - .handle_region_moved(&mut self.widget, &shared.data); - } + debug_assert!(!action.contains(Action::REGION_MOVED)); if !action.is_empty() { if let Some(ref mut window) = self.window { window.queued_frame_time = Some(window.next_avail_frame_time); From 0c36ab173dbb4a192d5474f5a0ab38f737c69687 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 10 Oct 2023 15:47:09 +0100 Subject: [PATCH 14/16] Add Id::common_ancestor --- crates/kas-core/src/core/widget_id.rs | 71 ++++++++++++++++----------- 1 file changed, 41 insertions(+), 30 deletions(-) diff --git a/crates/kas-core/src/core/widget_id.rs b/crates/kas-core/src/core/widget_id.rs index 8a94ebd18..ab0429ca0 100644 --- a/crates/kas-core/src/core/widget_id.rs +++ b/crates/kas-core/src/core/widget_id.rs @@ -360,6 +360,18 @@ impl Id { } } + /// Find the most-derived common ancestor + pub fn common_ancestor(&self, id: &Self) -> Self { + let mut r = Id::ROOT; + for (a, b) in self.iter().zip(id.iter()) { + if a != b { + break; + } + r = r.make_child(a); + } + r + } + pub fn iter_keys_after(&self, id: &Self) -> WidgetPathIter { let mut self_iter = self.iter(); for v in id.iter() { @@ -689,6 +701,14 @@ impl HasId for &mut Id { mod test { use super::*; + fn make_id(seq: &[usize]) -> Id { + let mut id = Id::ROOT; + for x in seq { + id = id.make_child(*x); + } + id + } + #[test] fn size_of_option_widget_id() { use std::mem::size_of; @@ -800,12 +820,8 @@ mod test { #[test] fn test_parent() { fn test(seq: &[usize]) { - println!("seq: {seq:?}"); - let mut id = Id::ROOT; let len = seq.len(); - for key in &seq[..len - 1] { - id = id.make_child(*key); - } + let id = make_id(&seq[..len - 1]); if len == 0 { assert_eq!(id.parent(), None); @@ -827,10 +843,7 @@ mod test { #[test] fn test_make_child() { fn test(seq: &[usize], x: u64) { - let mut id = Id::ROOT; - for key in seq { - id = id.make_child(*key); - } + let id = make_id(seq); let v = id.as_u64(); if v != x { panic!("test({seq:?}, {x:x}): found {v:x}"); @@ -850,11 +863,7 @@ mod test { #[test] fn test_display() { fn from_seq(seq: &[usize]) -> String { - let mut id = Id::ROOT; - for x in seq { - id = id.make_child(*x); - } - format!("{id}") + format!("{}", make_id(seq)) } assert_eq!(from_seq(&[]), "#"); @@ -871,16 +880,11 @@ mod test { #[test] fn test_is_ancestor() { fn test(seq: &[usize], seq2: &[usize]) { - let mut id = Id::ROOT; - for x in seq { - id = id.make_child(*x); - } - println!("id={} val={:x} from {:?}", id, id.as_u64(), seq); + let id = make_id(seq); let mut id2 = id.clone(); for x in seq2 { id2 = id2.make_child(*x); } - println!("id2={} val={:x} from {:?}", id2, id2.as_u64(), seq2); let next = seq2.iter().next().cloned(); assert_eq!(id.is_ancestor_of(&id2), next.is_some() || id == id2); assert_eq!(id2.next_key_after(&id), next); @@ -901,16 +905,8 @@ mod test { #[test] fn test_not_ancestor() { fn test(seq: &[usize], seq2: &[usize]) { - let mut id = Id::ROOT; - for x in seq { - id = id.make_child(*x); - } - println!("id={} val={:x} from {:?}", id, id.as_u64(), seq); - let mut id2 = Id::ROOT; - for x in seq2 { - id2 = id2.make_child(*x); - } - println!("id2={} val={:x} from {:?}", id2, id2.as_u64(), seq2); + let id = make_id(seq); + let id2 = make_id(seq2); assert_eq!(id.is_ancestor_of(&id2), false); assert_eq!(id2.next_key_after(&id), None); } @@ -920,4 +916,19 @@ mod test { test(&[2, 10, 1], &[2, 10]); test(&[0, 5, 2], &[0, 1, 5]); } + + #[test] + fn common_ancestor() { + fn test(seq: &[usize], seq2: &[usize], seq3: &[usize]) { + let id = make_id(seq); + let id2 = make_id(seq2); + assert_eq!(id.common_ancestor(&id2), make_id(seq3)); + } + + test(&[0], &[], &[]); + test(&[0], &[1, 2], &[]); + test(&[0], &[0, 2], &[0]); + test(&[2, 10, 1], &[2, 10], &[2, 10]); + test(&[0, 5, 2], &[0, 5, 1], &[0, 5]); + } } From 6417f8d1ea1fce1f61c8f87563c43da0fbd60074 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 10 Oct 2023 15:53:18 +0100 Subject: [PATCH 15/16] Replace remainder of EventState::pending with pending_update (and configure) --- crates/kas-core/src/event/cx/cx_pub.rs | 12 ++++++++++-- crates/kas-core/src/event/cx/cx_shell.rs | 22 ++++++++-------------- crates/kas-core/src/event/cx/mod.rs | 11 ++--------- 3 files changed, 20 insertions(+), 25 deletions(-) diff --git a/crates/kas-core/src/event/cx/cx_pub.rs b/crates/kas-core/src/event/cx/cx_pub.rs index 10e0225af..8faae05d3 100644 --- a/crates/kas-core/src/event/cx/cx_pub.rs +++ b/crates/kas-core/src/event/cx/cx_pub.rs @@ -610,14 +610,22 @@ impl EventState { /// `request_reconfigure(child_id)` (which would cause a panic: /// `'Id::next_key_after: invalid'`). pub fn request_reconfigure(&mut self, id: Id) { - self.pending.push_back(Pending::Configure(id)); + self.pending_update = if let Some((id2, _)) = self.pending_update.take() { + Some((id.common_ancestor(&id2), true)) + } else { + Some((id, true)) + }; } /// Request update to widget `id` /// /// Schedules a call to [`Events::update`] on widget `id`. pub fn request_update(&mut self, id: Id) { - self.pending.push_back(Pending::Update(id)); + self.pending_update = if let Some((id2, reconf)) = self.pending_update.take() { + Some((id.common_ancestor(&id2), reconf)) + } else { + Some((id, false)) + }; } } diff --git a/crates/kas-core/src/event/cx/cx_shell.rs b/crates/kas-core/src/event/cx/cx_shell.rs index a7f5665f4..60948aee1 100644 --- a/crates/kas-core/src/event/cx/cx_shell.rs +++ b/crates/kas-core/src/event/cx/cx_shell.rs @@ -53,7 +53,7 @@ impl EventState { popup_removed: Default::default(), time_updates: vec![], fut_messages: vec![], - pending: Default::default(), + pending_update: None, region_moved: false, pending_sel_focus: None, pending_nav_focus: PendingNavFocus::None, @@ -195,20 +195,14 @@ impl EventState { } } - // 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())); + if let Some((id, reconf)) = cx.pending_update.take() { + if reconf { + win.as_node(data) + .find_node(&id, |node| cx.configure(node, id.clone())); - cx.region_moved = true; - } - Pending::Update(id) => { - win.as_node(data).find_node(&id, |node| cx.update(node)); - } + cx.region_moved = true; + } else { + win.as_node(data).find_node(&id, |node| cx.update(node)); } } diff --git a/crates/kas-core/src/event/cx/mod.rs b/crates/kas-core/src/event/cx/mod.rs index c6173e074..1bce5360c 100644 --- a/crates/kas-core/src/event/cx/mod.rs +++ b/crates/kas-core/src/event/cx/mod.rs @@ -142,13 +142,6 @@ struct PanGrab { coords: [(Coord, Coord); MAX_PAN_GRABS], } -#[derive(Clone, Debug)] -#[allow(clippy::enum_variant_names)] // they all happen to be about Focus -enum Pending { - Configure(Id), - Update(Id), -} - struct PendingSelFocus { target: Id, key_focus: bool, @@ -217,8 +210,8 @@ pub struct EventState { time_updates: Vec<(Instant, Id, u64)>, // Set of futures of messages together with id of sending widget fut_messages: Vec<(Id, Pin>>)>, - // FIFO queue of events pending handling - pending: VecDeque, + // Widget requiring update (and optionally configure) + pending_update: Option<(Id, bool)>, region_moved: bool, // Optional new target for selection focus. bool is true if this also gains key focus. pending_sel_focus: Option, From 6362c4d9260a6b3716c07d5800ef3cac453ad609 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 10 Oct 2023 16:16:33 +0100 Subject: [PATCH 16/16] Merge EventState::request_configure, request_update; do sub-tree update in EventState::action --- crates/kas-core/src/event/cx/cx_pub.rs | 45 ++++++++++++-------------- crates/kas-core/src/shell/window.rs | 5 +-- crates/kas-view/src/list_view.rs | 4 +-- crates/kas-view/src/matrix_view.rs | 4 +-- 4 files changed, 26 insertions(+), 32 deletions(-) diff --git a/crates/kas-core/src/event/cx/cx_pub.rs b/crates/kas-core/src/event/cx/cx_pub.rs index 8faae05d3..91014e362 100644 --- a/crates/kas-core/src/event/cx/cx_pub.rs +++ b/crates/kas-core/src/event/cx/cx_pub.rs @@ -264,9 +264,20 @@ impl EventState { /// affect the UI after a reconfigure action. #[inline] pub fn action(&mut self, id: impl HasId, action: Action) { - // TODO: make handling more specific via id - let _ = id; - self.action |= action; + fn inner(cx: &mut EventState, id: Id, mut action: Action) { + const RE_UP: Action = Action::RECONFIGURE.union(Action::UPDATE); + if !(action & RE_UP).is_empty() { + cx.request_update(id, action.contains(Action::RECONFIGURE)); + action.remove(RE_UP); + } + + // TODO(opt): handle sub-tree SET_RECT and RESIZE. + // NOTE: our draw system is incompatible with partial redraws, and + // in any case redrawing is extremely fast. + + cx.action |= action; + } + inner(self, id.has_id(), action) } /// Pass an [action](Self::action) given some `id` @@ -602,29 +613,15 @@ impl EventState { self.push_async(id, async_global_executor::spawn(fut.into_future())); } - /// Request re-configure of widget `id` - /// - /// This method requires that `id` is a valid path to an already-configured - /// widget. E.g. if widget `w` adds a new child, it may call - /// `request_reconfigure(self.id())` but not - /// `request_reconfigure(child_id)` (which would cause a panic: - /// `'Id::next_key_after: invalid'`). - pub fn request_reconfigure(&mut self, id: Id) { - self.pending_update = if let Some((id2, _)) = self.pending_update.take() { - Some((id.common_ancestor(&id2), true)) - } else { - Some((id, true)) - }; - } - - /// Request update to widget `id` + /// Request update and optionally configure to widget `id` /// - /// Schedules a call to [`Events::update`] on widget `id`. - pub fn request_update(&mut self, id: Id) { - self.pending_update = if let Some((id2, reconf)) = self.pending_update.take() { - Some((id.common_ancestor(&id2), reconf)) + /// If `reconf`, widget `id` will be [configured](Events::configure) + /// (includes update), otherwise `id` will only be [updated](Events::update). + pub fn request_update(&mut self, id: Id, reconf: bool) { + self.pending_update = if let Some((id2, rc2)) = self.pending_update.take() { + Some((id.common_ancestor(&id2), reconf || rc2)) } else { - Some((id, false)) + Some((id, reconf)) }; } } diff --git a/crates/kas-core/src/shell/window.rs b/crates/kas-core/src/shell/window.rs index 4d3067c97..ac9a54b3b 100644 --- a/crates/kas-core/src/shell/window.rs +++ b/crates/kas-core/src/shell/window.rs @@ -312,10 +312,7 @@ impl> Window { window.solve_cache.invalidate_rule_cache(); } self.apply_size(shared, false); - } else if !action - .intersection(Action::SET_RECT | Action::SCROLLED) - .is_empty() - { + } else if !(action & (Action::SET_RECT | Action::SCROLLED)).is_empty() { self.apply_size(shared, false); } debug_assert!(!action.contains(Action::REGION_MOVED)); diff --git a/crates/kas-view/src/list_view.rs b/crates/kas-view/src/list_view.rs index 4453d6b41..ef7b831fc 100644 --- a/crates/kas-view/src/list_view.rs +++ b/crates/kas-view/src/list_view.rs @@ -412,7 +412,7 @@ impl_scope! { fn set_scroll_offset(&mut self, cx: &mut EventCx, offset: Offset) -> Offset { let act = self.scroll.set_offset(offset); cx.action(&self, act); - cx.request_update(self.id()); + cx.request_update(self.id(), false); self.scroll.offset() } } @@ -497,7 +497,7 @@ impl_scope! { // Widgets need configuring and updating: do so by updating self. self.cur_len = 0; // hack: prevent drawing in the mean-time - cx.request_update(self.id()); + cx.request_update(self.id(), false); let mut child_size = rect.size - self.frame_size; let (size, skip); diff --git a/crates/kas-view/src/matrix_view.rs b/crates/kas-view/src/matrix_view.rs index a567d438d..dac8fa20a 100644 --- a/crates/kas-view/src/matrix_view.rs +++ b/crates/kas-view/src/matrix_view.rs @@ -358,7 +358,7 @@ impl_scope! { fn set_scroll_offset(&mut self, cx: &mut EventCx, offset: Offset) -> Offset { let action = self.scroll.set_offset(offset); cx.action(&self, action); - cx.request_update(self.id()); + cx.request_update(self.id(), false); self.scroll.offset() } } @@ -442,7 +442,7 @@ impl_scope! { // Widgets need configuring and updating: do so by updating self. self.cur_len = (0, 0); // hack: prevent drawing in the mean-time - cx.request_update(self.id()); + cx.request_update(self.id(), false); let avail = rect.size - self.frame_size; let child_size = Size(avail.0 / self.ideal_len.cols, avail.1 / self.ideal_len.rows)