diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 92bb2b380..2176333a1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -62,7 +62,7 @@ jobs: toolchain: [stable] include: - os: ubuntu-latest - toolchain: "1.56.0" + toolchain: "1.58.0" - os: ubuntu-latest toolchain: beta diff --git a/Cargo.toml b/Cargo.toml index dcffd3083..798c095fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ keywords = ["gui"] categories = ["gui"] repository = "https://github.com/kas-gui/kas" exclude = ["/examples"] -rust-version = "1.56" +rust-version = "1.58" [package.metadata.docs.rs] features = ["nightly"] diff --git a/README.md b/README.md index cf028653f..11e598bea 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ Getting started ### Dependencies -KAS requires a [Rust] compiler, version (MSRV) 1.56 or greater. +KAS requires a [Rust] compiler, version (MSRV) 1.58 or greater. Using the **nightly** channel does have a few advantages: - Procedural macros can only emit warnings using nightly `rustc`. diff --git a/crates/kas-core/src/core/data.rs b/crates/kas-core/src/core/data.rs index 994c61a63..bef410500 100644 --- a/crates/kas-core/src/core/data.rs +++ b/crates/kas-core/src/core/data.rs @@ -8,7 +8,7 @@ #[allow(unused)] use super::Layout; use super::{Widget, WidgetId}; -use crate::event::{self, EventMgr}; +use crate::event::EventMgr; use crate::geom::Rect; use crate::layout::{SetRectMgr, StorageChain}; use crate::{dir::Direction, WindowId}; @@ -74,7 +74,7 @@ impl Clone for CoreData { /// visible). The window is responsible for calling these methods. // // NOTE: it's tempting to include a pointer to the widget here. There are two -// options: (a) an unsafe aliased pointer or (b) Rc>. +// options: (a) an unsafe aliased pointer or (b) Rc>. // Option (a) should work but is an unnecessary performance hack; (b) could in // theory work but requires adjusting WidgetChildren::get, find etc. to take a // closure instead of returning a reference, causing *significant* complication. @@ -86,7 +86,7 @@ pub struct Popup { } /// Functionality required by a window -pub trait Window: Widget { +pub trait Window: Widget { /// Get the window title fn title(&self) -> &str; diff --git a/crates/kas-core/src/core/mod.rs b/crates/kas-core/src/core/mod.rs index 6062e62b3..f3625ab13 100644 --- a/crates/kas-core/src/core/mod.rs +++ b/crates/kas-core/src/core/mod.rs @@ -19,8 +19,8 @@ pub trait Boxed { fn boxed(self) -> Box; } -impl Boxed> for W { - fn boxed(self) -> Box> { +impl Boxed for W { + fn boxed(self) -> Box { Box::new(self) } } diff --git a/crates/kas-core/src/core/widget.rs b/crates/kas-core/src/core/widget.rs index 94b2bef77..4a57a9462 100644 --- a/crates/kas-core/src/core/widget.rs +++ b/crates/kas-core/src/core/widget.rs @@ -70,9 +70,9 @@ pub trait WidgetCore: Any + fmt::Debug { fn widget_name(&self) -> &'static str; /// Erase type - fn as_widget(&self) -> &dyn WidgetConfig; + fn as_widget(&self) -> &dyn Widget; /// Erase type - fn as_widget_mut(&mut self) -> &mut dyn WidgetConfig; + fn as_widget_mut(&mut self) -> &mut dyn Widget; } /// Listing of a widget's children @@ -100,7 +100,7 @@ pub trait WidgetChildren: WidgetCore { /// For convenience, `Index` is implemented via this method. /// /// Required: `index < self.len()`. - fn get_child(&self, index: usize) -> Option<&dyn WidgetConfig>; + fn get_child(&self, index: usize) -> Option<&dyn Widget>; /// Mutable variant of get /// @@ -108,7 +108,7 @@ pub trait WidgetChildren: WidgetCore { /// redraw may break the UI. If a widget is replaced, a reconfigure **must** /// be requested. This can be done via [`EventState::send_action`]. /// This method may be removed in the future. - fn get_child_mut(&mut self, index: usize) -> Option<&mut dyn WidgetConfig>; + fn get_child_mut(&mut self, index: usize) -> Option<&mut dyn Widget>; /// Find the child which is an ancestor of this `id`, if any /// @@ -401,9 +401,7 @@ pub trait Layout: WidgetChildren { /// - [`Layout`] — handles sizing and positioning of self and children /// - [`WidgetConfig`] — the last unparametrised trait allows customisation of /// some aspects of widget behaviour -/// - [`event::Handler`] — parametrised widgets over a `Msg` type and handles -/// events -/// - [`event::SendEvent`] — routes events to children and handles responses +/// - [`event::Handler`] — handles events /// - [`Widget`] — the final trait /// /// Widgets **must** use the [`derive(Widget)`] macro to implement at least @@ -413,14 +411,12 @@ pub trait Layout: WidgetChildren { /// implement *all except for `Layout`*. This opt-out derive behaviour means /// that adding additional traits into the family is not a breaking change. /// -/// To refer to a widget via dyn trait, use `&dyn WidgetConfig` (or, if the -/// message type is known, one may use `&dyn Widget`). -/// To refer to a widget in generic functions, use `` or -/// `>`. +/// To refer to a widget via dyn trait, use `&dyn Widget`. +/// To refer to a widget in generic functions, use ``. /// /// [`derive(Widget)`]: https://docs.rs/kas/latest/kas/macros/index.html#the-derivewidget-macro #[autoimpl(for Box)] -pub trait Widget: event::SendEvent {} +pub trait Widget: event::Handler {} /// Extension trait over widgets pub trait WidgetExt: WidgetChildren { @@ -488,7 +484,7 @@ pub trait WidgetExt: WidgetChildren { } /// Find the descendant with this `id`, if any - fn find_widget(&self, id: &WidgetId) -> Option<&dyn WidgetConfig> { + fn find_widget(&self, id: &WidgetId) -> Option<&dyn Widget> { if let Some(index) = self.find_child_index(id) { self.get_child(index) .and_then(|child| child.find_widget(id)) @@ -500,7 +496,7 @@ pub trait WidgetExt: WidgetChildren { } /// Find the descendant with this `id`, if any - fn find_widget_mut(&mut self, id: &WidgetId) -> Option<&mut dyn WidgetConfig> { + fn find_widget_mut(&mut self, id: &WidgetId) -> Option<&mut dyn Widget> { if let Some(index) = self.find_child_index(id) { self.get_child_mut(index) .and_then(|child| child.find_widget_mut(id)) diff --git a/crates/kas-core/src/event/components.rs b/crates/kas-core/src/event/components.rs index fa790c079..343a9b226 100644 --- a/crates/kas-core/src/event/components.rs +++ b/crates/kas-core/src/event/components.rs @@ -6,7 +6,7 @@ //! Event handling components use super::ScrollDelta::{LineDelta, PixelDelta}; -use super::{Command, CursorIcon, Event, EventMgr, PressSource, Response, VoidMsg}; +use super::{Command, CursorIcon, Event, EventMgr, PressSource, Response, Scroll}; use crate::cast::traits::*; use crate::geom::{Coord, Offset, Rect, Size, Vec2}; #[allow(unused)] @@ -151,7 +151,6 @@ impl ScrollComponent { /// The offset is clamped to the available scroll range. /// Returns [`TkAction::empty()`] if the offset is identical to the old offset, /// or [`TkAction::REGION_MOVED`] if the offset changes. - #[inline] pub fn set_offset(&mut self, offset: Offset) -> TkAction { let offset = offset.min(self.max_offset).max(Offset::ZERO); if offset == self.offset { @@ -162,34 +161,15 @@ impl ScrollComponent { } } - /// Apply offset to an event being sent to the scrolled child - #[inline] - pub fn offset_event(&self, mut event: Event) -> Event { - match &mut event { - Event::PressStart { coord, .. } => { - *coord += self.offset; - } - Event::PressMove { coord, .. } => { - *coord += self.offset; - } - Event::PressEnd { coord, .. } => { - *coord += self.offset; - } - _ => {} - }; - event - } - - /// Handle [`Response::Focus`] + /// Scroll to make the given `rect` visible /// /// Inputs and outputs: /// - /// - `rect`: the focus rect + /// - `rect`: the rect to focus /// - `window_rect`: the rect of the scroll window - /// - returned `Rect`: the focus rect, adjusted for scroll offset; normally this should be - /// returned via another [`Response::Focus`] + /// - returned `Rect`: the focus rect, adjusted for scroll offset; this + /// may be set via [`EventMgr::set_scroll`] /// - returned `TkAction`: action to pass to the event manager - #[inline] pub fn focus_rect(&mut self, rect: Rect, window_rect: Rect) -> (Rect, TkAction) { let v = rect.pos - window_rect.pos; let off = Offset::conv(rect.size) - Offset::conv(window_rect.size); @@ -198,6 +178,26 @@ impl ScrollComponent { (rect - self.offset, action) } + /// Handle a [`Scroll`] action + pub fn scroll(&mut self, mgr: &mut EventMgr, window_rect: Rect, scroll: Scroll) -> Scroll { + match scroll { + s @ Scroll::None | s @ Scroll::Scrolled => s, + Scroll::Offset(delta) => { + let old_offset = self.offset; + *mgr |= self.set_offset(old_offset - delta); + match delta - old_offset + self.offset { + delta if delta == Offset::ZERO => Scroll::Scrolled, + delta => Scroll::Offset(delta), + } + } + Scroll::Rect(rect) => { + let (rect, action) = self.focus_rect(rect, window_rect); + *mgr |= action; + Scroll::Rect(rect) + } + } + } + /// Use an event to scroll, if possible /// /// Consumes the following events: `Command`, `Scroll`, `PressStart`, @@ -212,59 +212,43 @@ impl ScrollComponent { /// depend on modifiers), and if so grabs press events from this `source`. /// `PressMove` is used to scroll by the motion delta and to track speed; /// `PressEnd` initiates momentum-scrolling if the speed is high enough. - /// - /// If the returned [`TkAction`] is `None`, the scroll offset has not changed and - /// the returned [`Response`] is either `Used` or `Unused`. - /// If the returned [`TkAction`] is not `None`, the scroll offset has been - /// updated and the second return value is `Response::Used`. - #[inline] pub fn scroll_by_event( &mut self, mgr: &mut EventMgr, event: Event, id: WidgetId, - window_size: Size, - ) -> (TkAction, Response) { - let mut action = TkAction::empty(); - let mut response = Response::Used; - + window_rect: Rect, + ) -> Response { match event { - Event::Command(Command::Home, _) => { - action = self.set_offset(Offset::ZERO); - } - Event::Command(Command::End, _) => { - action = self.set_offset(self.max_offset); - } Event::Command(cmd, _) => { - let delta = match cmd { - Command::Left => LineDelta(-1.0, 0.0), - Command::Right => LineDelta(1.0, 0.0), - Command::Up => LineDelta(0.0, 1.0), - Command::Down => LineDelta(0.0, -1.0), - Command::PageUp => PixelDelta(Offset(0, window_size.1 / 2)), - Command::PageDown => PixelDelta(Offset(0, -(window_size.1 / 2))), - _ => return (action, Response::Unused), - }; - - let d = match delta { - LineDelta(x, y) => mgr.config().scroll_distance((-x, y), None), - PixelDelta(d) => d, + let offset = match cmd { + Command::Home => Offset::ZERO, + Command::End => self.max_offset, + cmd => { + let delta = match cmd { + Command::Left => LineDelta(-1.0, 0.0), + Command::Right => LineDelta(1.0, 0.0), + Command::Up => LineDelta(0.0, 1.0), + Command::Down => LineDelta(0.0, -1.0), + Command::PageUp => PixelDelta(Offset(0, window_rect.size.1 / 2)), + Command::PageDown => PixelDelta(Offset(0, -(window_rect.size.1 / 2))), + _ => return Response::Unused, + }; + let delta = match delta { + LineDelta(x, y) => mgr.config().scroll_distance((-x, y), None), + PixelDelta(d) => d, + }; + self.offset - delta + } }; - action = self.set_offset(self.offset - d); + *mgr |= self.set_offset(offset); + mgr.set_scroll(Scroll::Rect(window_rect)); } Event::Scroll(delta) => { - let d = match delta { + mgr.set_scroll(Scroll::Offset(match delta { LineDelta(x, y) => mgr.config().scroll_distance((-x, y), None), PixelDelta(d) => d, - }; - let old_offset = self.offset; - action = self.set_offset(old_offset - d); - let delta = d - (old_offset - self.offset); - response = if delta != Offset::ZERO { - Response::Pan(delta) - } else { - Response::Scrolled - }; + })); } Event::PressStart { source, coord, .. } if self.max_offset != Offset::ZERO && mgr.config_enable_pan(source) => @@ -272,16 +256,9 @@ impl ScrollComponent { let icon = Some(CursorIcon::Grabbing); mgr.grab_press_unique(id, source, coord, icon); } - Event::PressMove { mut delta, .. } => { + Event::PressMove { delta, .. } => { self.glide.move_delta(delta); - let old_offset = self.offset; - action = self.set_offset(old_offset - delta); - delta = old_offset - self.offset; - response = if delta != Offset::ZERO { - Response::Pan(delta) - } else { - Response::Scrolled - }; + mgr.set_scroll(Scroll::Offset(delta)); } Event::PressEnd { .. } => { if self.glide.opt_start(mgr.config().scroll_flick_timeout()) { @@ -292,20 +269,21 @@ impl ScrollComponent { // Momentum/glide scrolling: update per arbitrary step time until movment stops. let decay = mgr.config().scroll_flick_decay(); if let Some(delta) = self.glide.step(decay) { - action = self.set_offset(self.offset - delta); + let action = self.set_offset(self.offset - delta); + *mgr |= action; if delta == Offset::ZERO || !action.is_empty() { // Note: when FPS > pixels/sec, delta may be zero while // still scrolling. Glide returns None when we're done, // but we're also done if unable to scroll further. let dur = Duration::from_millis(GLIDE_POLL_MS); mgr.update_on_timer(dur, id, PAYLOAD_GLIDE); - response = Response::Scrolled; + mgr.set_scroll(Scroll::Scrolled); } } } - _ => response = Response::Unused, + _ => return Response::Unused, } - (action, response) + Response::Used } } diff --git a/crates/kas-core/src/event/events.rs b/crates/kas-core/src/event/events.rs index 4cfc19bf3..70c2f3212 100644 --- a/crates/kas-core/src/event/events.rs +++ b/crates/kas-core/src/event/events.rs @@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize}; #[allow(unused)] -use super::{EventMgr, EventState, GrabMode, Response, SendEvent}; // for doc-links +use super::{EventMgr, EventState, GrabMode, Response}; // for doc-links use super::{MouseButton, UpdateHandle, VirtualKeyCode}; use crate::geom::{Coord, DVec2, Offset}; @@ -123,6 +123,9 @@ pub enum Event { /// /// If `start_id` is `None`, then no widget was found at the coordinate and /// the event will only be delivered to pop-up layer owners. + /// + /// When handling, it may be desirable to call [`EventMgr::grab_press`] in + /// order to receive corresponding Move and End events from this `source`. PressStart { source: PressSource, start_id: Option, @@ -190,25 +193,46 @@ pub enum Event { /// Since popups may be removed directly by the EventMgr, the parent should /// clean up any associated state here. PopupRemoved(WindowId), - /// Sent when a widget receives keyboard navigation focus + /// Sent when a widget receives focus /// - /// This event may be used to react (e.g. by requesting char focus) or to + /// When the payload, `key_focus`, is true when the focus was triggered by + /// the keyboard, not the mouse or a touch event. + /// This event may be used e.g. to request char focus or to /// steal focus from a child. /// - /// When the payload, `key_focus`, is true when the focus was triggered by - /// the keyboard, not the mouse or a touch event. When true, the widget - /// should reply with [`Response::Focus`] in order to ensure visibility. - /// This should not be done when `key_focus` is false to avoid moving - /// widgets during interaction via mouse or finger. - /// - /// Widgets using [`EventMgr::handle_generic`] should note that this event - /// is trapped and responded to (in line with the above recommendation). - /// If the widget needs to react to `NavFocus`, the event must be matched - /// *before* calling `handle_generic`, which might require a custom - /// implementation of [`SendEvent`]. + /// When `key_focus` is true, the widget's rect will be made visible (the + /// event sender automatically calls + /// `mgr.set_scroll(Scroll::Rect(widget.rect()));`). NavFocus(bool), } +impl std::ops::Add for Event { + type Output = Self; + + #[inline] + fn add(mut self, offset: Offset) -> Event { + self += offset; + self + } +} + +impl std::ops::AddAssign for Event { + fn add_assign(&mut self, offset: Offset) { + match self { + Event::PressStart { coord, .. } => { + *coord += offset; + } + Event::PressMove { coord, .. } => { + *coord += offset; + } + Event::PressEnd { coord, .. } => { + *coord += offset; + } + _ => (), + } + } +} + /// Command input ([`Event::Command`]) /// /// The exact command sent depends on the type of focus a widget has. diff --git a/crates/kas-core/src/event/handler.rs b/crates/kas-core/src/event/handler.rs index cfdcd3fec..6b60f5fed 100644 --- a/crates/kas-core/src/event/handler.rs +++ b/crates/kas-core/src/event/handler.rs @@ -7,8 +7,8 @@ use super::*; #[allow(unused)] -use crate::Widget; // for doc-links -use crate::{WidgetConfig, WidgetExt, WidgetId}; +use crate::{Layout, Widget}; // for doc-links +use crate::{WidgetConfig, WidgetExt}; use kas_macros::autoimpl; /// Event handling for a [`Widget`] @@ -17,32 +17,16 @@ use kas_macros::autoimpl; /// [`derive(Widget)`] unless `#[handler(handle = noauto)]` /// or `#[handler(noauto)]` is used. /// -/// Interactive widgets should implement their event-handling logic here -/// (although it is also possible to implement this in [`SendEvent::send`], -/// which might be preferable when dealing with child widgets). -/// -/// The default implementation does nothing, and is derived by `derive(Widget)` -/// when a `#[handler]` attribute is present (except with parameter -/// `handler=noauto`). -/// /// [`derive(Widget)`]: ../macros/index.html#the-derivewidget-macro #[autoimpl(for Box)] pub trait Handler: WidgetConfig { - /// Type of message returned by this widget - /// - /// This mechanism allows type-safe handling of user-defined responses to - /// handled actions, for example an enum encoding button presses or a - /// floating-point value from a slider. - /// - /// The [`VoidMsg`] type may be used where messages are never generated. - /// This is distinct from `()`, which might be applicable when a widget only - /// needs to "wake up" a parent. - type Msg: 'static; - /// Generic handler: translate presses to activations /// - /// This is configuration for [`EventMgr::handle_generic`], and can be used - /// to translate *press* (click/touch) events into [`Event::Activate`]. + /// If true, [`Event::PressStart`] (and other press events) will not be sent + /// to [`Handler::handle_event`]; instead [`Event::Activate`] will be sent on + /// "click events". + /// + /// Default impl: return `false`. // NOTE: not an associated constant because these are not object-safe #[inline] fn activation_via_press(&self) -> bool { @@ -51,99 +35,92 @@ pub trait Handler: WidgetConfig { /// Generic handler: focus rect on key navigation /// - /// If this widget receives [`Event::NavFocus`]`(true)` then return - /// [`Response::Focus`] with the widget's rect. By default this is true if - /// and only if [`WidgetConfig::key_nav`] is true. + /// If true, then receiving `Event::NavFocus(true)` will automatically call + /// [`EventMgr::set_scroll`] with `Scroll::Rect(self.rect())` and the event + /// will be considered `Used` even if not matched explicitly. (The facility + /// provided by this method is pure convenience and may be done otherwise.) + /// + /// Default impl: return result of [`WidgetConfig::key_nav`]. #[inline] fn focus_on_key_nav(&self) -> bool { self.key_nav() } - /// Handle an event and return a user-defined message + /// Handle an event sent to this widget + /// + /// An [`Event`] is some form of user input, timer or notification. /// - /// Widgets should handle any events applicable to themselves here, and - /// return all other events via [`Response::Unused`]. + /// This is the primary event handler for a widget. Secondary handlers are: + /// + /// - If this method returns [`Response::Unused`], then + /// [`Handler::handle_unused`] is called on each parent until the event + /// is used (or the root widget is reached) + /// - If a message is left on the stack by [`EventMgr::push_msg`], then + /// [`Handler::handle_message`] is called on each parent until the stack is + /// empty (failing to empty the stack results in a warning in the log). + /// - If any scroll state is set by [`EventMgr::set_scroll`], then + /// [`Handler::handle_scroll`] is called for each parent + /// + /// Default implementation: do nothing; return [`Response::Unused`]. #[inline] - fn handle(&mut self, mgr: &mut EventMgr, event: Event) -> Response { + fn handle_event(&mut self, mgr: &mut EventMgr, event: Event) -> Response { let _ = (mgr, event); Response::Unused } -} -/// Event routing -/// -/// This trait is part of the [`Widget`] family and is derived by -/// [`derive(Widget)`] unless `#[handler(send = noauto)]` -/// or `#[handler(noauto)]` is used. -/// -/// This trait is responsible for routing events to the correct widget. It is -/// separate from [`Handler`] since it can be derived for many parent widgets, -/// even when event *handling* must be implemented manually. -/// -/// This trait is implemented by `derive(Widget)` when a `#[handler]` attribute -/// is present (except with parameter `send=noauto`). -/// -/// [`derive(Widget)`]: ../macros/index.html#the-derivewidget-macro -#[autoimpl(for Box)] -pub trait SendEvent: Handler { - /// Send an event + /// Handle an event sent to child `index` but left unhandled + /// + /// Default implementation: call [`Self::handle_event`] with `event`. + #[inline] + fn handle_unused(&mut self, mgr: &mut EventMgr, index: usize, event: Event) -> Response { + let _ = index; + self.handle_event(mgr, event) + } + + /// Handler for messages from children/descendants /// - /// This method is responsible for routing events toward descendents. - /// [`WidgetId`] values are assigned via depth-first search with parents - /// ordered after all children. + /// This method is called when a child leaves a message on the stack. *Some* + /// parent or ancestor widget should read this message. /// - /// The following logic is recommended for routing events: - /// ```no_test - /// match self.find_child_index(&id) { - /// Some(widget_index![self.child0]) => self.child0.send(mgr, id, event).into(), - /// Some(widget_index![self.child1]) => self.child1.send(mgr, id, event).into(), - /// // ... - /// _ => { - /// debug_assert_eq!(self.id(), id); - /// EventMgr::handle_generic(self, mgr, event), - /// } - /// } - /// ``` + /// The default implementation does nothing. + #[inline] + fn handle_message(&mut self, mgr: &mut EventMgr, index: usize) { + let _ = (mgr, index); + } + + /// Handler for scrolling /// - /// When the child's [`Handler::Msg`] type is not something which converts - /// into the widget's own message type, it must be handled here (in place of `.into()`). + /// This is the last "event handling step" for each widget. If + /// [`Self::handle_event`], [`Self::handle_unused`], [`Self::handle_message`] or any + /// child's event handlers set a non-empty scroll value + /// (via [`EventMgr::set_scroll`]), this gets called and the result set as + /// the new scroll value. /// - /// The example above uses [`EventMgr::handle_generic`], which is an optional - /// tool able to perform some simplifications on events. It is also valid to - /// call [`Handler::handle`] directly or simply to embed handling logic here. + /// If [`Layout::translation`] is non-zero and `scroll` is + /// `Scroll::Rect(_)`, then this method should undo the translation. /// - /// When a child widget returns [`Response::Unused`], the widget may call - /// its own event handler. This is useful e.g. to capture a click+drag on a - /// child which does not handle that event. Note further that in case the - /// child is disabled, events targetting the child may be sent directly to - /// self. - fn send(&mut self, mgr: &mut EventMgr, id: WidgetId, event: Event) -> Response; + /// The default implementation simply returns `scroll`. + #[inline] + fn handle_scroll(&mut self, mgr: &mut EventMgr, scroll: Scroll) -> Scroll { + let _ = mgr; + scroll + } } impl<'a> EventMgr<'a> { /// Generic event simplifier - /// - /// This is a free function often called from [`SendEvent::send`] to - /// simplify certain events and then invoke [`Handler::handle`]. - pub fn handle_generic( - widget: &mut W, - mgr: &mut EventMgr, - mut event: Event, - ) -> Response<::Msg> - where - W: Handler + ?Sized, - { + pub(crate) fn handle_generic(&mut self, widget: &mut dyn Widget, mut event: Event) -> Response { if widget.activation_via_press() { // Translate press events match event { Event::PressStart { source, coord, .. } if source.is_primary() => { - mgr.grab_press(widget.id(), source, coord, GrabMode::Grab, None); + self.grab_press(widget.id(), source, coord, GrabMode::Grab, None); return Response::Used; } Event::PressMove { source, cur_id, .. } => { let cond = widget.eq_id(&cur_id); let target = if cond { cur_id } else { None }; - mgr.set_grab_depress(source, target); + self.set_grab_depress(source, target); return Response::Used; } Event::PressEnd { @@ -156,10 +133,12 @@ impl<'a> EventMgr<'a> { }; } + let mut response = Response::Unused; if widget.focus_on_key_nav() && event == Event::NavFocus(true) { - return Response::Focus(widget.rect()); + self.set_scroll(Scroll::Rect(widget.rect())); + response = Response::Used; } - widget.handle(mgr, event) + response | widget.handle_event(self, event) } } diff --git a/crates/kas-core/src/event/manager.rs b/crates/kas-core/src/event/manager.rs index abf56815a..ee1a81a98 100644 --- a/crates/kas-core/src/event/manager.rs +++ b/crates/kas-core/src/event/manager.rs @@ -9,8 +9,9 @@ #![cfg_attr(not(feature = "winit"), allow(unused))] use linear_map::{set::LinearSet, LinearMap}; -use log::trace; +use log::{trace, warn}; use smallvec::SmallVec; +use std::any::Any; use std::cell::RefCell; use std::collections::{BTreeMap, HashMap}; use std::ops::{Deref, DerefMut}; @@ -353,6 +354,38 @@ impl EventState { } } +// NOTE: we *want* to store Box entries, but Rust doesn't +// support multi-trait objects. An alternative would be to store Box +// where `trait Message: Any + Debug {}`, but Rust does not support +// trait-object upcast, so we cannot downcast the result. +// +// Workaround: pre-format when the message is *pushed*. +struct Message { + any: Box, + #[cfg(debug_assertions)] + fmt: String, +} +impl Message { + fn new(msg: Box) -> Self { + #[cfg(debug_assertions)] + let fmt = format!("{:?}", &msg); + let any = msg; + Message { + #[cfg(debug_assertions)] + fmt, + any, + } + } + + fn is(&self) -> bool { + self.any.is::() + } + + fn downcast(self) -> Result, Box> { + self.any.downcast::() + } +} + /// Manager of event-handling and toolkit actions /// /// An `EventMgr` is in fact a handle around [`EventState`] and [`ShellWindow`] @@ -367,6 +400,8 @@ impl EventState { pub struct EventMgr<'a> { state: &'a mut EventState, shell: &'a mut dyn ShellWindow, + messages: Vec, + scroll: Scroll, action: TkAction, } @@ -382,6 +417,17 @@ impl<'a> DerefMut for EventMgr<'a> { } } +impl<'a> Drop for EventMgr<'a> { + fn drop(&mut self) { + for _msg in self.messages.drain(..) { + #[cfg(debug_assertions)] + log::warn!("EventMgr: unhandled message: {}", _msg.fmt); + #[cfg(not(debug_assertions))] + log::warn!("EventMgr: unhandled message: [use debug build to see value]"); + } + } +} + /// Internal methods impl<'a> EventMgr<'a> { fn set_hover(&mut self, widget: &W, w_id: Option) { @@ -420,7 +466,7 @@ impl<'a> EventMgr<'a> { fn start_key_event(&mut self, widget: &mut W, vkey: VirtualKeyCode, scancode: u32) where - W: Widget + ?Sized, + W: Widget + ?Sized, { trace!( "EventMgr::start_key_event: widget={}, vkey={:?}, scancode={}", @@ -440,7 +486,7 @@ impl<'a> EventMgr<'a> { if let Some(cmd) = opt_command { if self.state.char_focus { if let Some(id) = self.state.sel_focus.clone() { - if self.try_send_event(widget, id, Event::Command(cmd, shift)) { + if self.send_event(widget, id, Event::Command(cmd, shift)) { return; } } @@ -448,28 +494,28 @@ impl<'a> EventMgr<'a> { if !self.state.modifiers.alt() { if let Some(id) = self.state.nav_focus.clone() { - if self.try_send_event(widget, id, Event::Command(cmd, shift)) { + if self.send_event(widget, id, Event::Command(cmd, shift)) { return; } } } if let Some(id) = self.state.popups.last().map(|popup| popup.1.parent.clone()) { - if self.try_send_event(widget, id, Event::Command(cmd, shift)) { + if self.send_event(widget, id, Event::Command(cmd, shift)) { return; } } if self.state.sel_focus != self.state.nav_focus && cmd.suitable_for_sel_focus() { if let Some(id) = self.state.sel_focus.clone() { - if self.try_send_event(widget, id, Event::Command(cmd, shift)) { + if self.send_event(widget, id, Event::Command(cmd, shift)) { return; } } } if let Some(id) = self.state.nav_fallback.clone() { - if self.try_send_event(widget, id, Event::Command(cmd, shift)) { + if self.send_event(widget, id, Event::Command(cmd, shift)) { return; } } @@ -544,38 +590,58 @@ impl<'a> EventMgr<'a> { } } - fn send_impl(&mut self, widget: &mut W, mut id: WidgetId, event: Event) -> Response - where - W: Widget + ?Sized, - { - // TODO(opt): we should be able to use binary search here - for d in &self.disabled { - if d.is_ancestor_of(&id) { - if let Some(p) = d.parent() { - id = p; - } else { - return Response::Unused; - } + // Traverse widget tree by recursive call + // + // Note: cannot use internal stack of mutable references due to borrow checker + fn send_recurse( + &mut self, + widget: &mut dyn Widget, + id: WidgetId, + disabled: bool, + event: Event, + ) -> Response { + let mut response; + if let Some(index) = widget.find_child_index(&id) { + let translation = widget.translation(); + if disabled { + response = Response::Unused; + } else if let Some(w) = widget.get_child_mut(index) { + response = self.send_recurse(w, id, disabled, event.clone() + translation); + } else { + warn!( + "Widget {} found index {index} for {id}, but child not found", + widget.identify() + ); + return Response::Unused; + } + + if matches!(response, Response::Unused) { + response = widget.handle_unused(self, index, event); + } else if self.has_msg() { + widget.handle_message(self, index); } + } else if id == widget.id_ref() { + response = self.handle_generic(widget, event); + } else { + warn!("Widget {} cannot find path to {id}", widget.identify()); + return Response::Unused; } - widget.send(self, id, event) - } - fn send_event(&mut self, widget: &mut W, id: WidgetId, event: Event) { - trace!("Send to {}: {:?}", id, event); - let _ = self.send_impl(widget, id, event); + if self.scroll != Scroll::None { + self.scroll = widget.handle_scroll(self, self.scroll); + } + response } - // Similar to send_event, but return true only if response != Response::Unused - fn try_send_event( + // Wrapper around Self::send; returns true when event is used + #[inline] + fn send_event( &mut self, widget: &mut W, id: WidgetId, event: Event, ) -> bool { - trace!("Send to {}: {:?}", id, event); - let r = self.send_impl(widget, id, event); - !matches!(r, Response::Unused) + self.send(widget, id, event) == Response::Used } fn send_popup_first(&mut self, widget: &mut W, id: Option, event: Event) @@ -589,7 +655,7 @@ impl<'a> EventMgr<'a> { .map(|(wid, p, _)| (*wid, p.parent.clone())) { trace!("Send to popup parent: {}: {:?}", parent, event); - match self.send_impl(widget, parent, event.clone()) { + match self.send(widget, parent, event.clone()) { Response::Unused => (), _ => return, } diff --git a/crates/kas-core/src/event/manager/mgr_pub.rs b/crates/kas-core/src/event/manager/mgr_pub.rs index 22f677a49..27acc8162 100644 --- a/crates/kas-core/src/event/manager/mgr_pub.rs +++ b/crates/kas-core/src/event/manager/mgr_pub.rs @@ -26,7 +26,7 @@ impl<'a> std::ops::BitOrAssign for EventMgr<'a> { } } -/// Public API (around event manager state) +/// Public API impl EventState { /// True when the window has focus #[inline] @@ -148,7 +148,9 @@ impl EventState { /// Set/unset a widget as disabled /// - /// Disabled status applies to all descendants. + /// Disabled status applies to all descendants and blocks reception of + /// events ([`Response::Unused`] is returned automatically when the + /// recipient or any ancestor is disabled). pub fn set_disabled(&mut self, w_id: WidgetId, state: bool) { for (i, id) in self.disabled.iter().enumerate() { if w_id == id { @@ -466,8 +468,65 @@ impl EventState { } } -/// Public API (around toolkit and shell functionality) +/// Public API impl<'a> EventMgr<'a> { + /// Send an event to a widget + /// + /// Messages may be left on the stack after this returns and scroll state + /// may be adjusted. + pub fn send(&mut self, widget: &mut W, mut id: WidgetId, event: Event) -> Response + where + W: Widget + ?Sized, + { + trace!("EventMgr::send to {}: {:?}", id, event); + + // TODO(opt): we should be able to use binary search here + let mut disabled = false; + for d in &self.disabled { + if d.is_ancestor_of(&id) { + id = d.clone(); + disabled = true; + } + } + + self.send_recurse(widget.as_widget_mut(), id, disabled, event) + } + + /// Push a message to the stack + pub fn push_msg(&mut self, msg: M) { + self.push_boxed_msg(Box::new(msg)); + } + + /// Push a pre-boxed message to the stack + pub fn push_boxed_msg(&mut self, msg: Box) { + self.messages.push(Message::new(msg)); + } + + /// True if the message stack is non-empty + pub fn has_msg(&self) -> bool { + !self.messages.is_empty() + } + + /// Try popping the last message from the stack with the given type + pub fn try_pop_msg(&mut self) -> Option { + self.try_pop_boxed_msg().map(|m| *m) + } + + /// Try popping the last message from the stack with the given type + pub fn try_pop_boxed_msg(&mut self) -> Option> { + if self.messages.last().map(|m| m.is::()).unwrap_or(false) { + self.messages.pop().unwrap().downcast::().ok() + } else { + None + } + } + + /// Set a scroll action + #[inline] + pub fn set_scroll(&mut self, scroll: Scroll) { + self.scroll = scroll; + } + /// Add an overlay (pop-up) /// /// A pop-up is a box used for things like tool-tips and menus which is @@ -481,12 +540,13 @@ impl<'a> EventMgr<'a> { /// ([`Event::PressMove`]) which may be used to navigate menus. /// The parent automatically receives the "depressed" visual state. /// + /// It is recommended to call [`EventState::set_nav_focus`] after this method. + /// /// A pop-up may be closed by calling [`EventMgr::close_window`] with /// the [`WindowId`] returned by this method. /// /// Returns `None` if window creation is not currently available (but note /// that `Some` result does not guarantee the operation succeeded). - #[inline] pub fn add_popup(&mut self, popup: crate::Popup) -> Option { trace!("Manager::add_popup({:?})", popup); let new_id = &popup.id; @@ -531,7 +591,6 @@ impl<'a> EventMgr<'a> { /// If `restore_focus` then navigation focus will return to whichever widget /// had focus before the popup was open. (Usually this is true excepting /// where focus has already been changed.) - #[inline] pub fn close_window(&mut self, id: WindowId, restore_focus: bool) { if let Some(index) = self.state.popups.iter().enumerate().find_map( @@ -770,7 +829,7 @@ impl<'a> EventMgr<'a> { /// keyboard input, false if reacting to mouse or touch input. pub fn next_nav_focus( &mut self, - mut widget: &mut dyn WidgetConfig, + mut widget: &mut dyn Widget, reverse: bool, key_focus: bool, ) -> bool { @@ -789,7 +848,7 @@ impl<'a> EventMgr<'a> { fn nav( mgr: &mut SetRectMgr, - widget: &mut dyn WidgetConfig, + widget: &mut dyn Widget, focus: Option<&WidgetId>, rev: bool, ) -> Option { diff --git a/crates/kas-core/src/event/manager/mgr_shell.rs b/crates/kas-core/src/event/manager/mgr_shell.rs index 48c9b75db..da7b6f07f 100644 --- a/crates/kas-core/src/event/manager/mgr_shell.rs +++ b/crates/kas-core/src/event/manager/mgr_shell.rs @@ -75,7 +75,7 @@ impl EventState { /// renamed and removed widgets. pub fn full_configure(&mut self, shell: &mut dyn ShellWindow, widget: &mut W) where - W: Widget + ?Sized, + W: Widget + ?Sized, { debug!("EventMgr::configure"); self.action.remove(TkAction::RECONFIGURE); @@ -129,10 +129,13 @@ impl EventState { let mut mgr = EventMgr { state: self, shell, + messages: vec![], + scroll: Scroll::None, action: TkAction::empty(), }; f(&mut mgr); let action = mgr.action; + drop(mgr); self.send_action(action); } @@ -140,11 +143,13 @@ impl EventState { #[inline] pub fn update(&mut self, shell: &mut dyn ShellWindow, widget: &mut W) -> TkAction where - W: Widget + ?Sized, + W: Widget + ?Sized, { let mut mgr = EventMgr { state: self, shell, + messages: vec![], + scroll: Scroll::None, action: TkAction::empty(), }; @@ -217,7 +222,9 @@ impl EventState { mgr.send_event(widget, id, event); } - let action = mgr.action | self.action; + let action = mgr.action; + drop(mgr); + let action = action | self.action; self.action = TkAction::empty(); action } @@ -269,14 +276,10 @@ impl<'a> EventMgr<'a> { #[cfg_attr(doc_cfg, doc(cfg(feature = "winit")))] pub fn handle_winit(&mut self, widget: &mut W, event: winit::event::WindowEvent) where - W: Widget + ?Sized, + W: Widget + ?Sized, { use winit::event::{ElementState, MouseScrollDelta, TouchPhase, WindowEvent::*}; - // Note: since ::Msg = VoidMsg, only two values of - // Response are possible: Used and Unused. We don't have any use for - // Unused events here, so we can freely ignore all responses. - match event { CloseRequested => self.send_action(TkAction::CLOSE), /* Not yet supported: see #98 diff --git a/crates/kas-core/src/event/mod.rs b/crates/kas-core/src/event/mod.rs index 5c3c5a429..172c2c29a 100644 --- a/crates/kas-core/src/event/mod.rs +++ b/crates/kas-core/src/event/mod.rs @@ -5,14 +5,7 @@ //! Event handling //! -//! Event handling uses *event* messages, passed from the parent into a widget, -//! with responses passed back to the parent. This model is simpler than that -//! commonly used by GUI frameworks: widgets do not need a pointer to their -//! parent and any result is pushed back up the call stack. The model allows -//! type-safety while allowing user-defined result types. -//! -//! We deliver events only on a "need to know" basis: typically, only one widget -//! will receive an event. +//! See documentation of [`Event`] values. //! //! ## Event delivery //! @@ -20,45 +13,45 @@ //! mouse and touch events) is to use [`crate::Layout::find_id`] to translate a //! coordinate to a [`WidgetId`]. //! -//! Events then process from root to leaf. [`SendEvent::send`] is responsible -//! for forwarding an event to the appropriate child. Once the target widget is -//! reached, `send` (usually) calls [`EventMgr::handle_generic`] which may apply -//! some transformations to events, then calls [`Handler::handle`] on target -//! widget. Finally, a [`Response`] is emitted. +//! Events are then sent via [`EventMgr::send`] which traverses the widget tree +//! starting from the root (the window), following the path described by the +//! [`WidgetId`]: +//! +//! - In case any widget encountered is disabled, [`Unused`] is returned +//! - If the target is found, [`Handler::handle_event`] is called. This method may +//! handle the event and may push a message to the stack via +//! [`EventMgr::push_msg`]. +//! - If no target is found, a warning is logged and [`Unused`] returned //! -//! The [`Response`] enum has a few variants; most important is `Msg(msg)` -//! which passes a user-defined payload up to a parent widget. The -//! `Unused` and `Focus(rect)` variants may be trapped by any parent -//! for secondary purposes, e.g. to adjust a `ScrollRegion`. +//! Then, for each parent back to the root, //! -//! ## Mouse and touch events +//! - If [`Unused`] was returned, [`Handler::handle_unused`] is called +//! - Otherwise, if the message stack is non-empty, [`Handler::handle_message`] +//! is called //! -//! Mouse events and touch events are unified: both have a "press" which starts -//! somewhere, moves, and ends somewhere. The main difference concerns move -//! events, which may occur with any number of mouse buttons pressed. +//! This traversal of the widget tree is fast: (`O(len)`) where `len` is the +//! length of the path described by [`WidgetId`]. It is "clean": uninvolved +//! widgets are not sent any event, while actionable messages are sent to an +//! appropriate parent. It allows "recycling" of unused events. //! -//! Motion and release events are only delivered when a "press grab" is active. -//! This is achieved by calling [`EventMgr::grab_press`] and allows receiving -//! both relative and absolute press coordinates. -//! A special "pan" grab allows receiving two-finger scroll/scale/rotate input. +//! ### Keyboard focus //! -//! Each touch event is considered independent. The mouse cursor and multiple -//! fingers may all interact with different parts of a UI simultaneously. The -//! same is partly true of keyboard input, though some actions force keyboard -//! focus. +//! Keyboard focus controls where otherwise undirected keyboard input is sent. +//! This may be set via [`EventState::set_nav_focus`] but is typically controlled +//! via the Tab key, via [`EventMgr::next_nav_focus`]. //! //! ### Pop-ups //! -//! When a pop-up widget is created, this forces keyboard focus to that widget -//! and receives a "weak" grab on press actions, meaning that the widget -//! receives this input first, but if returned via `Response::Unused` the -//! input passes immediately to the next target. This allows pop-up menus to -//! get first chance of handling input and to dismiss themselves when input is -//! for other widgets without blocking other widgets from accepting that input. -//! (This "weak grab" behaviour is intentional to align UI response with a -//! user's intuition that any visible non-grey part of the UI is interactive.) +//! When a pop-up widget is created, the pop-up's parent takes priority for +//! "press" (mouse / touch) input as well as receiving keyboard focus. +//! +//! If this input is unhandled, the pop-up is automatically closed and the event +//! is re-sent to the next candidate, allowing handling of e.g. mouse clicks on +//! widgets under a menu. This should be intuitive: UI which is in focus and +//! not greyed-out should be interactive. //! //! [`WidgetId`]: crate::WidgetId +//! [`Unused`]: Response::Unused pub mod config; #[cfg(not(feature = "winit"))] @@ -84,9 +77,9 @@ pub use config::Config; #[cfg(not(feature = "winit"))] pub use enums::{CursorIcon, ModifiersState, MouseButton, VirtualKeyCode}; pub use events::*; -pub use handler::{Handler, SendEvent}; +pub use handler::Handler; pub use manager::{EventMgr, EventState, GrabMode}; -pub use response::Response; +pub use response::{Response, Scroll}; pub use update::UpdateHandle; /// A type supporting a small number of key bindings @@ -103,77 +96,10 @@ fn size_of_virtual_key_codes() { assert!(std::mem::size_of::() <= 32); } -/// A void message -/// -/// This type is not constructible, therefore `Response` is known at -/// compile-time not to contain a `Response::Msg(..)` variant. -/// -/// It is trivial to implement `From` for any type `T`; unfortunately -/// Rust's type system is too restrictive for us to provide a blanket -/// implementation (due both to orphan rules for trait implementations and to -/// conflicting implementations; it is possible that this may change in the -/// future). -/// -/// `From` is implemented for a number of language types; -/// custom message types are required to implement this via the -/// [`derive(VoidMsg)`](https://docs.rs/kas/latest/kas/macros#the-derivevoidmsg-macro) macro. -#[derive(Clone, Debug)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub enum VoidMsg {} - -/// Alias for `Response` -pub type VoidResponse = Response; - -// TODO(specialization): replace below impls with impl From for T -macro_rules! impl_void_msg { - () => {}; - ($t:ty) => { - impl From for $t { - fn from(_: VoidMsg) -> $t { - unreachable!() - } - } - }; - ($t:ty, $($tt:ty),*) => { - impl_void_msg!($t); - impl_void_msg!($($tt),*); - }; -} -impl_void_msg!(bool, char); -impl_void_msg!(u8, u16, u32, u64, u128, usize); -impl_void_msg!(i8, i16, i32, i64, i128, isize); -impl_void_msg!(f32, f64); -impl_void_msg!(&'static str, String); -impl_void_msg!(std::time::Duration, std::time::Instant); - -/// A keyed message from a child -/// -/// This type is used by some containers to forward messages from children. -#[derive(Clone, Debug)] -pub enum ChildMsg { - Select(K), - Deselect(K), - Child(K, M), -} - -impl From for ChildMsg { - fn from(_: VoidMsg) -> Self { - unreachable!() - } -} - -/// Convert Response> to Response +/// A message indicating press focus /// -/// `ChildMsg::Child(_, msg)` translates to `Response::Msg(msg)`; other -/// variants translate to `Response::Used`. -impl From>> for Response { - fn from(r: Response>) -> Self { - match Response::try_from(r) { - Ok(r) => r, - Err(msg) => match msg { - ChildMsg::Child(_, msg) => Response::Msg(msg), - _ => Response::Used, - }, - } - } -} +/// Widgets which are mouse/touch interactible yet do not support keyboard nav +/// focus may return this on [`Event::PressStart`], allowing a parent to take +/// the navigation focus. +#[derive(Clone, Debug, Default)] +pub struct MsgPressFocus; diff --git a/crates/kas-core/src/event/response.rs b/crates/kas-core/src/event/response.rs index a507208d6..b0f9090d4 100644 --- a/crates/kas-core/src/event/response.rs +++ b/crates/kas-core/src/event/response.rs @@ -5,81 +5,25 @@ //! Event handling: Response type -use super::VoidResponse; use crate::geom::{Offset, Rect}; -/// Response type from [`Handler::handle`]. +/// Response from [`Handler::handle_event`] /// -/// This type wraps [`Handler::Msg`] allowing both custom messages and toolkit -/// messages. -/// -/// [`Handler::handle`]: super::Handler::handle -/// [`Handler::Msg`]: super::Handler::Msg -#[derive(Clone, Debug)] -#[must_use] -pub enum Response { +/// [`Handler::handle_event`]: super::Handler::handle_event +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +pub enum Response { /// Event was unused /// /// Unused events may be used by a parent/ancestor widget or passed to /// another handler until used. Unused, /// Event is used, no other result - /// - /// All variants besides `Unused` indicate that the event was used. This - /// variant is used when no further action happens. Used, - /// Pan scrollable regions by the given delta - /// - /// This may be returned to scroll the closest scrollable ancestor region. - /// This region should attempt to scroll self by this offset, then, if all - /// the offset was used, return `Response::Scrolled`, otherwise return - /// `Response::Pan(d)` with the unused offset `d`. - /// - /// With the usual scroll offset conventions, this delta must be subtracted - /// from the scroll offset. - Pan(Offset), - /// Notify that an inner region scrolled - Scrolled, - /// (Keyboard) focus has changed - /// - /// This region (in the child's coordinate space) should be made visible. - Focus(Rect), - /// Widget wishes to be selected (or have selection status toggled) - Select, - /// Notify of update to widget's data - /// - /// Widgets which hold editable data should return either this or - /// [`Response::Msg`] on handling events which update that data. - /// Note: scrolling/adjusting a view is not considered a data update. - Update, - /// Custom message type - /// - /// This signals a (possible) update to the widget's data, while passing a - /// data payload to the parent widget. - Msg(M), } // Unfortunately we cannot write generic `From` / `TryFrom` impls // due to trait coherence rules, so we impl `from` etc. directly. -impl Response { - /// Construct `None` or `Msg(msg)` - #[inline] - pub fn used_or_msg(opt_msg: Option) -> Self { - match opt_msg { - None => Response::Used, - Some(msg) => Response::Msg(msg), - } - } - - /// Construct `Update` or `Msg(msg)` - #[inline] - pub fn update_or_msg(opt_msg: Option) -> Self { - match opt_msg { - None => Response::Update, - Some(msg) => Response::Msg(msg), - } - } - +impl Response { /// True if variant is `Used` #[inline] pub fn is_used(&self) -> bool { @@ -91,70 +35,39 @@ impl Response { pub fn is_unused(&self) -> bool { matches!(self, Response::Unused) } - - /// True if variant is `Msg` - #[inline] - pub fn is_msg(&self) -> bool { - matches!(self, Response::Msg(_)) - } - - /// Map from one `Response` type to another - /// - /// Once Rust supports specialisation, this will likely be replaced with a - /// `From` implementation. - #[inline] - pub fn from(r: Response) -> Self - where - N: Into, - { - r.try_into().unwrap_or_else(|msg| Response::Msg(msg.into())) - } - - /// Map one `Response` type into another - /// - /// Once Rust supports specialisation, this will likely be redundant. - #[inline] - pub fn into(self) -> Response - where - M: Into, - { - Response::from(self) - } - - /// Try mapping from one `Response` type to another, failing on `Msg` - /// variant and returning the payload. - #[inline] - pub fn try_from(r: Response) -> Result { - use Response::*; - match r { - Unused => Ok(Unused), - Used => Ok(Used), - Pan(delta) => Ok(Pan(delta)), - Scrolled => Ok(Scrolled), - Focus(rect) => Ok(Focus(rect)), - Select => Ok(Select), - Update => Ok(Update), - Msg(m) => Err(m), - } - } - - /// Try mapping one `Response` type into another, failing on `Msg` - /// variant and returning the payload. - #[inline] - pub fn try_into(self) -> Result, M> { - Response::try_from(self) - } } -impl VoidResponse { - /// Convert a `Response` to another `Response` - pub fn void_into(self) -> Response { - self.try_into().unwrap_or(Response::Used) +impl std::ops::BitOr for Response { + type Output = Self; + fn bitor(self, rhs: Self) -> Self { + use Response::{Unused, Used}; + match (self, rhs) { + (Unused, Unused) => Unused, + _ => Used, + } } } -impl From for Response { - fn from(msg: M) -> Self { - Response::Msg(msg) - } +/// Request to / notification of scrolling from a child +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +#[must_use] +pub enum Scroll { + /// No scrolling + None, + /// Child has scrolled; no further scrolling needed + /// + /// External scrollbars use this as a notification to update self. + Scrolled, + /// Pan region by the given offset + /// + /// This may be returned to scroll the closest scrollable ancestor region. + /// This region should attempt to scroll self by this offset, then, if all + /// the offset was used, return `Scroll::Scrolled`, otherwise return + /// `Scroll::Offset(delta)` with the unused offset `delta`. + /// + /// With the usual scroll offset conventions, this delta must be subtracted + /// from the scroll offset. + Offset(Offset), + /// Focus the given rect + Rect(Rect), } diff --git a/crates/kas-core/src/layout/mod.rs b/crates/kas-core/src/layout/mod.rs index 190e4848d..3f22d5c8f 100644 --- a/crates/kas-core/src/layout/mod.rs +++ b/crates/kas-core/src/layout/mod.rs @@ -51,7 +51,9 @@ use crate::event::EventState; use crate::geom::{Size, Vec2}; use crate::text::TextApi; use crate::theme::{SizeHandle, SizeMgr, TextClass}; -use crate::{TkAction, WidgetConfig, WidgetId}; +#[allow(unused)] +use crate::WidgetConfig; +use crate::{TkAction, Widget, WidgetId}; use std::ops::{Deref, DerefMut}; pub use align::{Align, AlignHints, CompleteAlignment}; @@ -194,7 +196,7 @@ impl<'a> SetRectMgr<'a> { /// Pass the `id` to assign to the widget: this should be constructed from /// the parent's id via [`WidgetId::make_child`]. #[inline] - pub fn configure(&mut self, id: WidgetId, widget: &mut dyn WidgetConfig) { + pub fn configure(&mut self, id: WidgetId, widget: &mut dyn Widget) { // Yes, this method is just a shim! We reserve the option to add other code here in the // future, hence do not advise calling `configure_recurse` directly. widget.configure_recurse(self, id); diff --git a/crates/kas-core/src/layout/row_solver.rs b/crates/kas-core/src/layout/row_solver.rs index 0d271aa24..65adee209 100644 --- a/crates/kas-core/src/layout/row_solver.rs +++ b/crates/kas-core/src/layout/row_solver.rs @@ -12,7 +12,7 @@ use super::{Align, AlignHints, AxisInfo, SizeRules}; use super::{RowStorage, RowTemp, RulesSetter, RulesSolver}; use crate::dir::{Direction, Directional}; use crate::geom::{Coord, Rect}; -use crate::{WidgetConfig, WidgetExt}; +use crate::{Widget, WidgetExt}; /// A [`RulesSolver`] for rows (and, without loss of generality, for columns). /// @@ -136,7 +136,7 @@ impl RowSetter { let max_size = total.max_size(); let align = if is_horiz { align.horiz } else { align.vert }; let align = align.unwrap_or(Align::Default); - if rect.size.0 > max_size { + if width > max_size { let extra = width - max_size; width = max_size; let offset = match align { @@ -280,7 +280,7 @@ impl RowPositionSolver { RowPositionSolver { direction } } - fn binary_search(self, widgets: &[W], coord: Coord) -> Result { + fn binary_search(self, widgets: &[W], coord: Coord) -> Result { match self.direction.as_direction() { Direction::Right => widgets.binary_search_by_key(&coord.0, |w| w.rect().pos.0), Direction::Down => widgets.binary_search_by_key(&coord.1, |w| w.rect().pos.1), @@ -293,7 +293,7 @@ impl RowPositionSolver { /// /// Returns `None` when the coordinates lie within the margin area or /// outside of the parent widget. - pub fn find_child_index(self, widgets: &[W], coord: Coord) -> Option { + pub fn find_child_index(self, widgets: &[W], coord: Coord) -> Option { match self.binary_search(widgets, coord) { Ok(i) => Some(i), Err(i) if self.direction.is_reversed() => { @@ -318,7 +318,7 @@ impl RowPositionSolver { /// Returns `None` when the coordinates lie within the margin area or /// outside of the parent widget. #[inline] - pub fn find_child(self, widgets: &[W], coord: Coord) -> Option<&W> { + pub fn find_child(self, widgets: &[W], coord: Coord) -> Option<&W> { self.find_child_index(widgets, coord).map(|i| &widgets[i]) } @@ -327,17 +327,13 @@ impl RowPositionSolver { /// Returns `None` when the coordinates lie within the margin area or /// outside of the parent widget. #[inline] - pub fn find_child_mut( - self, - widgets: &mut [W], - coord: Coord, - ) -> Option<&mut W> { + pub fn find_child_mut(self, widgets: &mut [W], coord: Coord) -> Option<&mut W> { self.find_child_index(widgets, coord) .map(|i| &mut widgets[i]) } /// Call `f` on each child intersecting the given `rect` - pub fn for_children( + pub fn for_children( self, widgets: &mut [W], rect: Rect, diff --git a/crates/kas-core/src/layout/sizer.rs b/crates/kas-core/src/layout/sizer.rs index 3ce9e4d7b..6db8f6941 100644 --- a/crates/kas-core/src/layout/sizer.rs +++ b/crates/kas-core/src/layout/sizer.rs @@ -12,7 +12,7 @@ use super::{AlignHints, AxisInfo, Margins, SetRectMgr, SizeRules}; use crate::cast::Conv; use crate::geom::{Rect, Size}; use crate::theme::SizeMgr; -use crate::{Widget, WidgetConfig, WidgetExt}; +use crate::{Widget, WidgetExt}; /// A [`SizeRules`] solver for layouts /// @@ -131,7 +131,7 @@ impl SolveCache { } /// Calculate required size of widget - pub fn find_constraints(widget: &mut dyn WidgetConfig, size_mgr: SizeMgr) -> Self { + pub fn find_constraints(widget: &mut dyn Widget, size_mgr: SizeMgr) -> Self { let start = std::time::Instant::now(); let w = widget.size_rules(size_mgr.re(), AxisInfo::new(false, None)); @@ -180,7 +180,7 @@ impl SolveCache { /// last used). pub fn apply_rect( &mut self, - widget: &mut dyn WidgetConfig, + widget: &mut dyn Widget, mgr: &mut SetRectMgr, mut rect: Rect, inner_margin: bool, @@ -230,17 +230,16 @@ impl SolveCache { } } -struct WidgetHeirarchy<'a>(&'a dyn WidgetConfig, usize); +struct WidgetHeirarchy<'a>(&'a dyn Widget, usize); impl<'a> fmt::Display for WidgetHeirarchy<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - write!( - f, - "\n{}{}\tpos={:?}\tsize={:?}", - "| ".repeat(self.1), - self.0.identify(), - self.0.rect().pos, - self.0.rect().size, - )?; + let len = 40 - 2 * self.1; + let trail = "| ".repeat(self.1); + // Note: pre-format some items to ensure correct alignment + let identify = format!("{}", self.0.identify()); + let pos = format!("{:?}", self.0.rect().pos); + let size = self.0.rect().size; + write!(f, "\n{trail}{identify: { /// A boxed component BoxComponent(Box), /// A single child widget - Single(&'a mut dyn WidgetConfig), + Single(&'a mut dyn Widget), /// A single child widget with alignment - AlignSingle(&'a mut dyn WidgetConfig, AlignHints), + AlignSingle(&'a mut dyn Widget, AlignHints), /// Apply alignment hints to some sub-layout AlignLayout(Box>, AlignHints), /// Frame around content @@ -96,13 +96,13 @@ impl<'a> Layout<'a> { } /// Construct a single-item layout - pub fn single(widget: &'a mut dyn WidgetConfig) -> Self { + pub fn single(widget: &'a mut dyn Widget) -> Self { let layout = LayoutType::Single(widget); Layout { layout } } /// Construct a single-item layout with alignment hints - pub fn align_single(widget: &'a mut dyn WidgetConfig, hints: AlignHints) -> Self { + pub fn align_single(widget: &'a mut dyn Widget, hints: AlignHints) -> Self { let layout = LayoutType::AlignSingle(widget, hints); Layout { layout } } @@ -159,7 +159,7 @@ impl<'a> Layout<'a> { /// the optimisations are not (currently) so useful. pub fn slice(slice: &'a mut [W], direction: D, data: &'a mut DynRowStorage) -> Self where - W: WidgetConfig, + W: Widget, D: Directional, { let layout = LayoutType::BoxComponent(Box::new(Slice { @@ -338,13 +338,13 @@ where } /// A row/column over a slice -struct Slice<'a, W: WidgetConfig, D: Directional> { +struct Slice<'a, W: Widget, D: Directional> { data: &'a mut DynRowStorage, direction: D, children: &'a mut [W], } -impl<'a, W: WidgetConfig, D: Directional> Component for Slice<'a, W, D> { +impl<'a, W: Widget, D: Directional> Component for Slice<'a, W, D> { fn size_rules(&mut self, mgr: SizeMgr, axis: AxisInfo) -> SizeRules { let dim = (self.direction, self.children.len()); let mut solver = RowSolver::new(axis, dim, self.data); diff --git a/crates/kas-core/src/prelude.rs b/crates/kas-core/src/prelude.rs index 8eeee73f1..42dc85429 100644 --- a/crates/kas-core/src/prelude.rs +++ b/crates/kas-core/src/prelude.rs @@ -17,9 +17,7 @@ pub use crate::dir::{Direction, Directional}; #[doc(no_inline)] pub use crate::draw::{DrawShared, ImageHandle}; #[doc(no_inline)] -pub use crate::event::{ - Event, EventMgr, EventState, Handler, Response, SendEvent, UpdateHandle, VoidMsg, -}; +pub use crate::event::{Event, EventMgr, EventState, Handler, Response, UpdateHandle}; #[doc(no_inline)] pub use crate::geom::{Coord, Offset, Rect, Size}; #[doc(no_inline)] diff --git a/crates/kas-core/src/updatable.rs b/crates/kas-core/src/updatable.rs index ebf86e34e..71fa948fd 100644 --- a/crates/kas-core/src/updatable.rs +++ b/crates/kas-core/src/updatable.rs @@ -7,74 +7,13 @@ //! //! These traits are used for "view widgets", enabling views (and editing) over //! shared data. -//! -//! Shared data must implement [`Updatable`] to allows data updates -//! from widget messages (or potentially from other message sources). mod data_impls; mod data_traits; pub mod filter; mod shared_rc; -use crate::event::UpdateHandle; -#[allow(unused)] // doc links -use std::cell::RefCell; -use std::fmt::Debug; -use std::ops::Deref; - pub use data_traits::{ ListData, ListDataMut, MatrixData, MatrixDataMut, SingleData, SingleDataMut, }; pub use shared_rc::SharedRc; - -/// Trait for data objects which can handle messages -pub trait Updatable { - /// Update data, if supported - /// - /// This is optional and required only to support data updates through view - /// widgets. - /// - /// This method should return some [`UpdateHandle`] if the data was changed - /// by this method, or `None` if nothing happened. - /// - /// This method takes only `&self`, thus probably [`RefCell`] will be used - /// internally, alongside an [`UpdateHandle`]. - fn handle(&self, key: &K, msg: &M) -> Option; -} - -// TODO(spec): can we add this? -// impl Updatable for T { -// fn handle(&self, _: &K, msg: &VoidMsg) -> Option { -// match *msg {} -// } -// } - -impl Updatable for [T] { - fn handle(&self, _: &usize, _: &M) -> Option { - None - } -} - -impl Updatable - for std::collections::BTreeMap -{ - fn handle(&self, _: &K, _: &M) -> Option { - None - } -} - -macro_rules! impl_via_deref { - ($t: ident: $derived:ty) => { - impl + ?Sized> Updatable for $derived { - fn handle(&self, key: &K, msg: &M) -> Option { - self.deref().handle(key, msg) - } - } - }; - ($t: ident: $derived:ty, $($dd:ty),+) => { - impl_via_deref!($t: $derived); - impl_via_deref!($t: $($dd),+); - }; -} -impl_via_deref!(T: &T, &mut T); -impl_via_deref!(T: std::rc::Rc, std::sync::Arc, Box); diff --git a/crates/kas-core/src/updatable/data_impls.rs b/crates/kas-core/src/updatable/data_impls.rs index 2f09f3e15..0a274670d 100644 --- a/crates/kas-core/src/updatable/data_impls.rs +++ b/crates/kas-core/src/updatable/data_impls.rs @@ -6,17 +6,15 @@ //! Impls for data traits use super::*; -use crate::event::UpdateHandle; +use crate::event::{EventMgr, EventState}; use crate::WidgetId; use std::fmt::Debug; -impl ListData for [T] { +impl ListData for [T] { type Key = usize; type Item = T; - fn update_handles(&self) -> Vec { - vec![] - } + fn update_on_handles(&self, _: &mut EventState, _: &WidgetId) {} fn version(&self) -> u64 { 1 } @@ -40,9 +38,8 @@ impl ListData for [T] { self.get(*key).cloned() } - fn update(&self, _: &Self::Key, _: Self::Item) -> Option { + fn update(&self, _: &mut EventMgr, _: &Self::Key, _: Self::Item) { // Note: plain [T] does not support update, but SharedRc<[T]> does. - None } fn iter_vec(&self, limit: usize) -> Vec { @@ -54,7 +51,7 @@ impl ListData for [T] { (start.min(len)..(start + limit).min(len)).collect() } } -impl ListDataMut for [T] { +impl ListDataMut for [T] { fn set(&mut self, key: &Self::Key, item: Self::Item) { self[*key] = item; } diff --git a/crates/kas-core/src/updatable/data_traits.rs b/crates/kas-core/src/updatable/data_traits.rs index 3e3532982..5a302ea26 100644 --- a/crates/kas-core/src/updatable/data_traits.rs +++ b/crates/kas-core/src/updatable/data_traits.rs @@ -5,9 +5,10 @@ //! Traits for shared data objects -use crate::event::UpdateHandle; -use crate::macros::autoimpl; #[allow(unused)] // doc links +use crate::event::{Event, UpdateHandle}; +use crate::event::{EventMgr, EventState}; +use crate::macros::autoimpl; use crate::WidgetId; #[allow(unused)] // doc links use std::cell::RefCell; @@ -19,16 +20,18 @@ use std::fmt::Debug; #[autoimpl(for &T, &mut T, std::rc::Rc, std::sync::Arc, Box)] pub trait SingleData: Debug { /// Output type - type Item: Clone; + type Item: Clone + Debug + 'static; - /// Get any update handles used to notify of updates + /// Register `id` for updates on all used [`UpdateHandle`] values /// /// If the data supports updates through shared references (e.g. via an /// internal [`RefCell`]), then it should have an [`UpdateHandle`] for /// notifying other users of the data of the update. All [`UpdateHandle`]s - /// used should be returned here. View widgets should check the data version - /// and update their view when any of these [`UpdateHandle`]s is triggreed. - fn update_handles(&self) -> Vec; + /// used will notify `id` of updates. + /// + /// View widgets should check the data version and update their view when + /// [`Event::HandleUpdate`] is received. + fn update_on_handles(&self, mgr: &mut EventState, id: &WidgetId); /// Get the data version /// @@ -51,16 +54,36 @@ pub trait SingleData: Debug { /// Update data, if supported /// /// This is optional and required only to support data updates through view - /// widgets. If implemented, then [`Self::update_handles`] should - /// return a copy of the same update handle. + /// widgets. + /// + /// If an update occurs, then the number returned by [`Self::version`] + /// should be increased and [`EventMgr::trigger_update`] should be called + /// with the relevant [`UpdateHandle`] (or handles). + /// + /// This method takes only `&self`, thus some mechanism such as [`RefCell`] + /// is required to obtain `&mut` and lower to [`SingleDataMut::set`]. The + /// provider of this lowering should also provide an [`UpdateHandle`]. + fn update(&self, mgr: &mut EventMgr, value: Self::Item); + + /// Handle a message from a widget + /// + /// This method is called when a view widget returns with a message. + /// It may use [`EventMgr::try_pop_msg`] and update self. /// - /// Updates the [`Self::version`] number and returns an [`UpdateHandle`] if - /// an update occurred. Returns `None` if updates are unsupported. + /// If `self` is updated, then as with [`Self::update`], the version number + /// must be increased and [`EventMgr::trigger_update`] called. /// /// This method takes only `&self`, thus some mechanism such as [`RefCell`] /// is required to obtain `&mut` and lower to [`SingleDataMut::set`]. The /// provider of this lowering should also provide an [`UpdateHandle`]. - fn update(&self, value: Self::Item) -> Option; + /// + /// The default implementation attempts to extract a value of type + /// [`Self::Item`], passing this to [`Self::update`] on success. + fn handle_message(&self, mgr: &mut EventMgr) { + if let Some(value) = mgr.try_pop_msg() { + self.update(mgr, value); + } + } } /// Trait for writable single data items @@ -81,16 +104,18 @@ pub trait ListData: Debug { type Key: Clone + Debug + PartialEq + Eq; /// Item type - type Item: Clone; + type Item: Clone + Debug + 'static; - /// Get any update handles used to notify of updates + /// Register `id` for updates on all used [`UpdateHandle`] values /// /// If the data supports updates through shared references (e.g. via an /// internal [`RefCell`]), then it should have an [`UpdateHandle`] for /// notifying other users of the data of the update. All [`UpdateHandle`]s - /// used should be returned here. View widgets should check the data version - /// and update their view when any of these [`UpdateHandle`]s is triggreed. - fn update_handles(&self) -> Vec; + /// used will notify `id` of updates. + /// + /// View widgets should check the data version and update their view when + /// [`Event::HandleUpdate`] is received. + fn update_on_handles(&self, mgr: &mut EventState, id: &WidgetId); /// Get the data version /// @@ -140,16 +165,36 @@ pub trait ListData: Debug { /// Update data, if supported /// /// This is optional and required only to support data updates through view - /// widgets. If implemented, then [`Self::update_handles`] should - /// return a copy of the same update handle. + /// widgets. + /// + /// If an update occurs, then the number returned by [`Self::version`] + /// should be increased and [`EventMgr::trigger_update`] should be called + /// with the relevant [`UpdateHandle`] (or handles). + /// + /// This method takes only `&self`, thus some mechanism such as [`RefCell`] + /// is required to obtain `&mut` and lower to [`ListDataMut::set`]. The + /// provider of this lowering should also provide an [`UpdateHandle`]. + fn update(&self, mgr: &mut EventMgr, key: &Self::Key, value: Self::Item); + + /// Handle a message from a widget + /// + /// This method is called when a view widget returns with a message. + /// It may use [`EventMgr::try_pop_msg`] and update self. /// - /// Updates the [`Self::version`] number and returns an [`UpdateHandle`] if - /// an update occurred. Returns `None` if updates are unsupported. + /// If `self` is updated, then as with [`Self::update`], the version number + /// must be increased and [`EventMgr::trigger_update`] called. /// /// This method takes only `&self`, thus some mechanism such as [`RefCell`] /// is required to obtain `&mut` and lower to [`ListDataMut::set`]. The /// provider of this lowering should also provide an [`UpdateHandle`]. - fn update(&self, key: &Self::Key, value: Self::Item) -> Option; + /// + /// The default implementation attempts to extract a value of type + /// [`Self::Item`], passing this to [`Self::update`] on success. + fn handle_message(&self, mgr: &mut EventMgr, key: &Self::Key) { + if let Some(value) = mgr.try_pop_msg() { + self.update(mgr, key, value); + } + } // TODO(gat): replace with an iterator /// Iterate over keys as a vec @@ -189,16 +234,18 @@ pub trait MatrixData: Debug { /// Full key type type Key: Clone + Debug + PartialEq + Eq; /// Item type - type Item: Clone; + type Item: Clone + Debug + 'static; - /// Get any update handles used to notify of updates + /// Register `id` for updates on all used [`UpdateHandle`] values /// /// If the data supports updates through shared references (e.g. via an /// internal [`RefCell`]), then it should have an [`UpdateHandle`] for /// notifying other users of the data of the update. All [`UpdateHandle`]s - /// used should be returned here. View widgets should check the data version - /// and update their view when any of these [`UpdateHandle`]s is triggreed. - fn update_handles(&self) -> Vec; + /// used will notify `id` of updates. + /// + /// View widgets should check the data version and update their view when + /// [`Event::HandleUpdate`] is received. + fn update_on_handles(&self, mgr: &mut EventState, id: &WidgetId); /// Get the data version /// @@ -246,16 +293,36 @@ pub trait MatrixData: Debug { /// Update data, if supported /// /// This is optional and required only to support data updates through view - /// widgets. If implemented, then [`Self::update_handles`] should - /// return a copy of the same update handle. + /// widgets. /// - /// Updates the [`Self::version`] number and returns an [`UpdateHandle`] if - /// an update occurred. Returns `None` if updates are unsupported. + /// If an update occurs, then the number returned by [`Self::version`] + /// should be increased and [`EventMgr::trigger_update`] should be called + /// with the relevant [`UpdateHandle`] (or handles). /// /// This method takes only `&self`, thus some mechanism such as [`RefCell`] - /// is required to obtain `&mut` and lower to [`ListDataMut::set`]. The + /// is required to obtain `&mut` and lower to [`MatrixDataMut::set`]. The /// provider of this lowering should also provide an [`UpdateHandle`]. - fn update(&self, key: &Self::Key, value: Self::Item) -> Option; + fn update(&self, mgr: &mut EventMgr, key: &Self::Key, value: Self::Item); + + /// Handle a message from a widget + /// + /// This method is called when a view widget returns with a message. + /// It may use [`EventMgr::try_pop_msg`] and update self. + /// + /// If `self` is updated, then as with [`Self::update`], the version number + /// must be increased and [`EventMgr::trigger_update`] called. + /// + /// This method takes only `&self`, thus some mechanism such as [`RefCell`] + /// is required to obtain `&mut` and lower to [`MatrixDataMut::set`]. The + /// provider of this lowering should also provide an [`UpdateHandle`]. + /// + /// The default implementation attempts to extract a value of type + /// [`Self::Item`], passing this to [`Self::update`] on success. + fn handle_message(&self, mgr: &mut EventMgr, key: &Self::Key) { + if let Some(value) = mgr.try_pop_msg() { + self.update(mgr, key, value); + } + } // TODO(gat): replace with an iterator /// Iterate over column keys as a vec diff --git a/crates/kas-core/src/updatable/filter.rs b/crates/kas-core/src/updatable/filter.rs index c8ca957d3..cc0cd9381 100644 --- a/crates/kas-core/src/updatable/filter.rs +++ b/crates/kas-core/src/updatable/filter.rs @@ -5,8 +5,9 @@ //! Filters over data -use crate::event::{UpdateHandle, VoidMsg}; +use crate::event::{EventMgr, EventState, UpdateHandle}; use crate::updatable::*; +use crate::WidgetId; use std::cell::RefCell; use std::fmt::Debug; use std::rc::Rc; @@ -30,21 +31,11 @@ impl ContainsString { ContainsString(Rc::new((handle, data))) } } -impl Updatable<(), String> for ContainsString { - fn handle(&self, _: &(), msg: &String) -> Option { - self.update(msg.clone()) - } -} -impl Updatable<(), VoidMsg> for ContainsString { - fn handle(&self, _: &(), _: &VoidMsg) -> Option { - None - } -} impl SingleData for ContainsString { type Item = String; - fn update_handles(&self) -> Vec { - vec![(self.0).0] + fn update_on_handles(&self, mgr: &mut EventState, id: &WidgetId) { + mgr.update_on_handle((self.0).0, id.clone()); } fn version(&self) -> u64 { (self.0).1.borrow().1 @@ -53,11 +44,11 @@ impl SingleData for ContainsString { fn get_cloned(&self) -> Self::Item { (self.0).1.borrow().0.to_owned() } - fn update(&self, value: Self::Item) -> Option { + fn update(&self, mgr: &mut EventMgr, value: Self::Item) { let mut cell = (self.0).1.borrow_mut(); cell.0 = value; cell.1 += 1; - Some((self.0).0) + mgr.trigger_update((self.0).0, 0); } } impl SingleDataMut for ContainsString { @@ -99,21 +90,11 @@ impl ContainsCaseInsensitive { ContainsCaseInsensitive(Rc::new((handle, data))) } } -impl Updatable<(), String> for ContainsCaseInsensitive { - fn handle(&self, _: &(), msg: &String) -> Option { - self.update(msg.clone()) - } -} -impl Updatable<(), VoidMsg> for ContainsCaseInsensitive { - fn handle(&self, _: &(), _: &VoidMsg) -> Option { - None - } -} impl SingleData for ContainsCaseInsensitive { type Item = String; - fn update_handles(&self) -> Vec { - vec![(self.0).0] + fn update_on_handles(&self, mgr: &mut EventState, id: &WidgetId) { + mgr.update_on_handle((self.0).0, id.clone()); } fn version(&self) -> u64 { (self.0).1.borrow().2 @@ -122,12 +103,12 @@ impl SingleData for ContainsCaseInsensitive { fn get_cloned(&self) -> Self::Item { (self.0).1.borrow().0.clone() } - fn update(&self, value: Self::Item) -> Option { + fn update(&self, mgr: &mut EventMgr, value: Self::Item) { let mut cell = (self.0).1.borrow_mut(); cell.0 = value; cell.1 = cell.0.to_uppercase(); cell.2 += 1; - Some((self.0).0) + mgr.trigger_update((self.0).0, 0); } } impl SingleDataMut for ContainsCaseInsensitive { diff --git a/crates/kas-core/src/updatable/shared_rc.rs b/crates/kas-core/src/updatable/shared_rc.rs index 6a13e1142..b1c25f067 100644 --- a/crates/kas-core/src/updatable/shared_rc.rs +++ b/crates/kas-core/src/updatable/shared_rc.rs @@ -10,9 +10,8 @@ //! If not, we can probably remove `ListDataMut` and other `*Mut` traits too. //! Probably this question requires seeing more examples/applications to answer. -#[allow(unused)] -use crate::event::EventMgr; use crate::event::UpdateHandle; +use crate::event::{EventMgr, EventState}; use crate::updatable::*; use crate::WidgetId; use std::cell::RefCell; @@ -21,9 +20,7 @@ use std::rc::Rc; /// Wrapper for single-thread shared data /// -/// This wrapper adds an [`UpdateHandle`] and implements the -/// [`Updatable`] traits (the latter with a dummy implementation — -/// if you need custom handlers you will need your own shared data type). +/// This wrapper adds an [`UpdateHandle`]. #[derive(Clone, Debug, Default)] pub struct SharedRc(Rc<(UpdateHandle, RefCell<(T, u64)>)>); @@ -36,17 +33,11 @@ impl SharedRc { } } -impl Updatable for SharedRc { - fn handle(&self, _: &K, _: &M) -> Option { - None - } -} - -impl SingleData for SharedRc { +impl SingleData for SharedRc { type Item = T; - fn update_handles(&self) -> Vec { - vec![(self.0).0] + fn update_on_handles(&self, mgr: &mut EventState, id: &WidgetId) { + mgr.update_on_handle((self.0).0, id.clone()); } fn version(&self) -> u64 { (self.0).1.borrow().1 @@ -56,14 +47,14 @@ impl SingleData for SharedRc { (self.0).1.borrow().0.to_owned() } - fn update(&self, value: Self::Item) -> Option { + fn update(&self, mgr: &mut EventMgr, value: Self::Item) { let mut cell = (self.0).1.borrow_mut(); cell.0 = value; cell.1 += 1; - Some((self.0).0) + mgr.trigger_update((self.0).0, 0); } } -impl SingleDataMut for SharedRc { +impl SingleDataMut for SharedRc { fn set(&mut self, value: Self::Item) { (self.0).1.borrow_mut().0 = value; } @@ -73,8 +64,8 @@ impl ListData for SharedRc { type Key = T::Key; type Item = T::Item; - fn update_handles(&self) -> Vec { - vec![(self.0).0] + fn update_on_handles(&self, mgr: &mut EventState, id: &WidgetId) { + mgr.update_on_handle((self.0).0, id.clone()); } fn version(&self) -> u64 { let cell = (self.0).1.borrow(); @@ -100,11 +91,11 @@ impl ListData for SharedRc { (self.0).1.borrow().0.get_cloned(key) } - fn update(&self, key: &Self::Key, value: Self::Item) -> Option { + fn update(&self, mgr: &mut EventMgr, key: &Self::Key, value: Self::Item) { let mut cell = (self.0).1.borrow_mut(); cell.0.set(key, value); cell.1 += 1; - Some((self.0).0) + mgr.trigger_update((self.0).0, 0); } fn iter_vec(&self, limit: usize) -> Vec { @@ -127,8 +118,8 @@ impl MatrixData for SharedRc { type Key = T::Key; type Item = T::Item; - fn update_handles(&self) -> Vec { - vec![(self.0).0] + fn update_on_handles(&self, mgr: &mut EventState, id: &WidgetId) { + mgr.update_on_handle((self.0).0, id.clone()); } fn version(&self) -> u64 { let cell = (self.0).1.borrow(); @@ -156,11 +147,11 @@ impl MatrixData for SharedRc { (self.0).1.borrow().0.get_cloned(key) } - fn update(&self, key: &Self::Key, value: Self::Item) -> Option { + fn update(&self, mgr: &mut EventMgr, key: &Self::Key, value: Self::Item) { let mut cell = (self.0).1.borrow_mut(); cell.0.set(key, value); cell.1 += 1; - Some((self.0).0) + mgr.trigger_update((self.0).0, 0); } fn col_iter_vec(&self, limit: usize) -> Vec { diff --git a/crates/kas-macros/src/args.rs b/crates/kas-macros/src/args.rs index c684e295f..1ae05e9f6 100644 --- a/crates/kas-macros/src/args.rs +++ b/crates/kas-macros/src/args.rs @@ -20,7 +20,6 @@ use syn::{ #[derive(Debug)] pub struct Child { pub ident: Member, - pub args: WidgetAttrArgs, } fn parse_impl(in_ident: Option<&Ident>, input: ParseStream) -> Result { @@ -151,12 +150,6 @@ mod kw { custom_keyword!(rspan); custom_keyword!(widget); custom_keyword!(handler); - custom_keyword!(flatmap_msg); - custom_keyword!(map_msg); - custom_keyword!(use_msg); - custom_keyword!(discard_msg); - custom_keyword!(update); - custom_keyword!(msg); custom_keyword!(generics); custom_keyword!(single); custom_keyword!(right); @@ -198,86 +191,6 @@ impl Parse for WidgetDerive { } } -#[derive(Debug, Clone, Eq, PartialEq)] -pub enum Handler { - None, - Use(Ident), - Map(Ident), - FlatMap(Ident), - Discard, -} -impl Handler { - fn is_none(&self) -> bool { - *self == Handler::None - } - pub fn any_ref(&self) -> Option<&Ident> { - match self { - Handler::None | Handler::Discard => None, - Handler::Use(n) | Handler::Map(n) | Handler::FlatMap(n) => Some(n), - } - } -} - -#[derive(Debug)] -pub struct WidgetAttrArgs { - pub update: Option, - pub handler: Handler, -} - -impl Parse for WidgetAttrArgs { - fn parse(input: ParseStream) -> Result { - let mut args = WidgetAttrArgs { - update: None, - handler: Handler::None, - }; - if input.is_empty() { - return Ok(args); - } - - let content; - let _ = parenthesized!(content in input); - - loop { - let lookahead = content.lookahead1(); - if args.update.is_none() && lookahead.peek(kw::update) { - let _: kw::update = content.parse()?; - let _: Eq = content.parse()?; - args.update = Some(content.parse()?); - } else if args.handler.is_none() && lookahead.peek(kw::flatmap_msg) { - let _: kw::flatmap_msg = content.parse()?; - let _: Eq = content.parse()?; - args.handler = Handler::FlatMap(content.parse()?); - } else if args.handler.is_none() && lookahead.peek(kw::map_msg) { - let _: kw::map_msg = content.parse()?; - let _: Eq = content.parse()?; - args.handler = Handler::Map(content.parse()?); - } else if args.handler.is_none() && lookahead.peek(kw::use_msg) { - let _: kw::use_msg = content.parse()?; - let _: Eq = content.parse()?; - args.handler = Handler::Use(content.parse()?); - } else if args.handler.is_none() && lookahead.peek(kw::discard_msg) { - let _: kw::discard_msg = content.parse()?; - args.handler = Handler::Discard; - } else if lookahead.peek(kw::handler) { - let tok: Ident = content.parse()?; - return Err(Error::new( - tok.span(), - "handler is obsolete; replace with flatmap_msg, map_msg, use_msg or discard_msg", - )); - } else { - return Err(lookahead.error()); - } - - if content.is_empty() { - break; - } - let _: Comma = content.parse()?; - } - - Ok(args) - } -} - macro_rules! property { ($name:ident : $ty:ty = $def:expr ; $kw:path : $input:ident => $parse:expr ;) => { #[derive(Debug)] @@ -330,7 +243,6 @@ pub struct WidgetArgs { pub derive: Option, pub layout: Option, pub find_id: FindId, - pub msg: Option, } impl Parse for WidgetArgs { @@ -341,7 +253,6 @@ impl Parse for WidgetArgs { let mut derive = None; let mut layout = None; let mut find_id = FindId::default(); - let mut msg = None; while !content.is_empty() { let lookahead = content.lookahead1(); @@ -363,10 +274,6 @@ impl Parse for WidgetArgs { layout = Some(content.parse()?); } else if content.peek(kw::find_id) { find_id = content.parse()?; - } else if msg.is_none() && lookahead.peek(kw::msg) { - let _: kw::msg = content.parse()?; - let _: Eq = content.parse()?; - msg = Some(content.parse()?); } else { return Err(lookahead.error()); } @@ -381,7 +288,6 @@ impl Parse for WidgetArgs { derive, layout, find_id, - msg, }) } } @@ -391,9 +297,8 @@ pub enum ChildType { Fixed(Type), // fixed type // A given type using generics internally InternGeneric(Punctuated, Type), - // Generic, optionally with specified handler msg type, - // optionally with an additional trait bound. - Generic(Option, Option), + // Generic, optionally with an additional trait bound. + Generic(Option), } #[derive(Debug)] @@ -489,7 +394,7 @@ impl Parse for WidgetField { let mut colon_token = None; // Note: Colon matches `::` but that results in confusing error messages - let mut ty = if input.peek(Colon) && !input.peek2(Colon) { + let ty = if input.peek(Colon) && !input.peek2(Colon) { colon_token = Some(input.parse()?); if input.peek(Token![for]) { // internal generic @@ -542,42 +447,17 @@ impl Parse for WidgetField { let ty = input.parse()?; ChildType::InternGeneric(params, ty) } else if input.peek(Token![impl]) { - // generic with trait bound, optionally with msg type + // generic with trait bound let _: Token![impl] = input.parse()?; let bound: TypeTraitObject = input.parse()?; - ChildType::Generic(None, Some(bound)) + ChildType::Generic(Some(bound)) } else { ChildType::Fixed(input.parse()?) } } else { - ChildType::Generic(None, None) + ChildType::Generic(None) }; - if input.peek(Token![->]) { - let arrow: Token![->] = input.parse()?; - if !attrs - .iter() - .any(|attr| attr.path == parse_quote! { widget }) - { - return Err(Error::new( - arrow.span(), - "can only use `-> Msg` type restriction on widgets", - )); - } - let msg: Type = input.parse()?; - match &mut ty { - ChildType::Fixed(_) | ChildType::InternGeneric(_, _) => { - return Err(Error::new( - arrow.span(), - "cannot use `-> Msg` type restriction with fixed `type` or with `for<...> type`", - )); - } - ChildType::Generic(ref mut gen_r, _) => { - *gen_r = Some(msg); - } - } - } - let _: Eq = input.parse()?; let value: Expr = input.parse()?; diff --git a/crates/kas-macros/src/lib.rs b/crates/kas-macros/src/lib.rs index 15edd1ffd..2c59e408f 100644 --- a/crates/kas-macros/src/lib.rs +++ b/crates/kas-macros/src/lib.rs @@ -18,7 +18,6 @@ use impl_tools_lib::autoimpl; use impl_tools_lib::{AttrImplDefault, ImplDefault, Scope, ScopeAttr}; use proc_macro::TokenStream; use proc_macro_error::{emit_call_site_error, proc_macro_error}; -use quote::quote; use syn::parse_macro_input; mod args; @@ -131,8 +130,8 @@ pub fn impl_scope(input: TokenStream) -> TokenStream { /// This may *only* be used within the [`impl_scope!`] macro. /// /// Implements the [`WidgetCore`] and [`Widget`] traits for the deriving type. -/// Implements the [`WidgetChildren`], [`WidgetConfig`], [`Layout`], [`Handler`] -/// and [`SendEvent`] traits only if not implemented explicitly within the +/// Implements the [`WidgetChildren`], [`WidgetConfig`], [`Layout`] +/// and [`Handler`] traits only if not implemented explicitly within the /// defining [`impl_scope!`]. /// /// Using the `derive` argument activates a special "thin wrapper" mode. @@ -164,7 +163,6 @@ pub fn impl_scope(input: TokenStream) -> TokenStream { /// documentation (method of [`Layout`]; defaults to an empty layout) /// - `find_id` `=` _Expr_ — override default implementation of `kas::Layout::find_id` to /// return this expression when `self.rect().contains(coord)` -/// - `msg` `=` _Type_ — set [`Handler::Msg`] associated type (default is [`VoidMsg`]) /// /// Assuming the deriving type is a `struct` or `tuple struct`, fields support /// the following attributes: @@ -174,19 +172,6 @@ pub fn impl_scope(input: TokenStream) -> TokenStream { /// - `#[widget]`: marks the field as a [`Widget`] to be configured, enumerated by /// [`WidgetChildren`] and included by glob layouts /// -/// The `#[widget]` attribute on fields may have arguments, affecting how the -/// implementation of [`SendEvent`] handles [`Response`] values from the child: -/// -/// - `#[widget(update = f)]` — when `Response::Update` is received, `self.f()` is called -/// - `#[widget(use_msg = f)]` — when `Response::Msg(msg)` is received, -/// `self.f(msg)` is called and `Response::Used` is returned -/// - `#[widget(map_msg = f)]` — when `Response::Msg(msg)` is received, -/// `Response::Msg(self.f(msg))` is returned -/// - `#[widget(flatmap_msg = f)]` — when `Response::Msg(msg)` is received, -/// `self.f(msg)` is returned -/// - `#[widget(discard_msg = f)]` — when `Response::Msg(msg)` is received, -/// `Response::Used` is returned -/// /// ## Layout /// /// Widget layout must be described somehow @@ -197,10 +182,7 @@ pub fn impl_scope(input: TokenStream) -> TokenStream { /// [`WidgetConfig`]: https://docs.rs/kas/0.11/kas/trait.WidgetConfig.html /// [`Layout`]: https://docs.rs/kas/0.11/kas/trait.Layout.html /// [`Handler`]: https://docs.rs/kas/0.11/kas/event/trait.Handler.html -/// [`Handler::Msg`]: https://docs.rs/kas/0.11/kas/event/trait.Handler.html#associatedtype.Msg -/// [`SendEvent`]: https://docs.rs/kas/0.11/kas/event/trait.SendEvent.html /// [`CursorIcon`]: https://docs.rs/kas/0.11/kas/event/enum.CursorIcon.html -/// [`VoidMsg`]: https://docs.rs/kas/0.11/kas/event/enum.VoidMsg.html /// [`Response`]: https://docs.rs/kas/0.11/kas/event/enum.Response.html /// [`CoreData`]: https://docs.rs/kas/0.11/kas/struct.CoreData.html #[proc_macro_attribute] @@ -228,10 +210,8 @@ pub fn widget(_: TokenStream, item: TokenStream) -> TokenStream { /// - the type of fields is optional: `ident = value,` works (but see note above) /// - the name of fields is optional: `_: ty = value,` and `_ = value,` are valid /// - instead of a type, a type bound may be used: `ident: impl Trait = value,` -/// - instead of a widget type, only the widget's message type may be specified: -/// `ident -> MessageType = value,` /// - type generics may be used: -/// `#[widget] display: for> Frame = value,` +/// `#[widget] display: for Frame = value,` /// /// Refer to [examples](https://github.com/search?q=make_widget+repo%3Akas-gui%2Fkas+path%3Aexamples&type=Code) for usage. #[proc_macro_error] @@ -341,31 +321,6 @@ pub fn make_layout(input: TokenStream) -> TokenStream { .into() } -/// Implement `From` -/// -/// Since `VoidMsg` is a void type (cannot exist at run-time), `From` -/// can safely be implemented for *any* type. (But due to the theoretical -/// possibility of avoid conflicting implementations, it is not implemented -/// automatically until Rust has some form of specialization.) -#[proc_macro_error] -#[proc_macro_derive(VoidMsg)] -pub fn derive_empty_msg(input: TokenStream) -> TokenStream { - let ast = parse_macro_input!(input as syn::DeriveInput); - let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); - let name = &ast.ident; - - let toks = quote! { - impl #impl_generics From<::kas::event::VoidMsg> - for #name #ty_generics #where_clause - { - fn from(_: ::kas::event::VoidMsg) -> Self { - unreachable!() - } - } - }; - toks.into() -} - /// Index of a child widget /// /// This macro is usable only within an [`impl_scope!`] macro using the diff --git a/crates/kas-macros/src/make_widget.rs b/crates/kas-macros/src/make_widget.rs index 2ef8f334f..6034a2429 100644 --- a/crates/kas-macros/src/make_widget.rs +++ b/crates/kas-macros/src/make_widget.rs @@ -3,64 +3,20 @@ // You may obtain a copy of the License in the LICENSE-APACHE file or at: // https://www.apache.org/licenses/LICENSE-2.0 -use crate::args::{ChildType, Handler, MakeWidget, WidgetAttrArgs}; +use crate::args::{ChildType, MakeWidget}; use impl_tools_lib::{ fields::{Field, Fields, FieldsNamed}, Scope, ScopeItem, }; use proc_macro2::{Span, TokenStream}; -use proc_macro_error::abort; use quote::{quote, TokenStreamExt}; use std::fmt::Write; use syn::parse_quote; use syn::punctuated::Punctuated; -use syn::spanned::Spanned; use syn::token::Comma; -use syn::{AttrStyle, Attribute, Ident, ItemImpl, Result, Type, TypePath, Visibility, WhereClause}; +use syn::{AttrStyle, Attribute, Ident, Result, Type, TypePath, Visibility, WhereClause}; pub(crate) fn make_widget(mut args: MakeWidget) -> Result { - let mut find_handler_ty_buf: Vec<(Ident, Type)> = vec![]; - // find type of handler's message; return None on error - let mut find_handler_ty = |handler: &Ident, impls: &Vec| -> Type { - // check the buffer in case we did this already - for (ident, ty) in &find_handler_ty_buf { - if ident == handler { - return ty.clone(); - } - } - - for impl_ in impls { - if impl_.trait_.is_some() { - continue; - } - for f in &impl_.items { - match f { - syn::ImplItem::Method(syn::ImplItemMethod { sig, .. }) - if sig.ident == *handler => - { - if sig.inputs.len() != 3 { - abort!( - sig.span(), - "handler functions must have signature: fn handler(&mut self, mgr: &mut EventMgr, msg: T)" - ); - } - let arg = sig.inputs.last().unwrap(); - let ty = match arg { - syn::FnArg::Typed(arg) => (*arg.ty).clone(), - _ => panic!("expected typed argument"), // nothing else is possible here? - }; - - find_handler_ty_buf.push((handler.clone(), ty.clone())); - return ty; - } - _ => (), - } - } - } - - abort!(handler.span(), "no methods with this name found"); - }; - // Used to make fresh identifiers for generic types let mut name_buf = String::with_capacity(32); let mut make_ident = move |args: std::fmt::Arguments| -> Ident { @@ -94,44 +50,6 @@ pub(crate) fn make_widget(mut args: MakeWidget) -> Result { core: Default::default(), }; - let mut impl_handler = false; - let mut opt_msg = None; - let msg = if let Some(msg) = args.attr_widget.msg.as_ref() { - impl_handler = true; - msg - } else { - // A little magic: try to deduce parameters, applying defaults otherwise - let msg_ident: Ident = parse_quote! { Msg }; - for impl_ in &args.impls { - if let Some((_, ref name, _)) = impl_.trait_ { - if *name == parse_quote! { Handler } || *name == parse_quote! { ::kas::Handler } { - for item in &impl_.items { - match item { - syn::ImplItem::Type(syn::ImplItemType { - ref ident, ref ty, .. - }) if *ident == msg_ident => { - opt_msg = Some(ty.clone()); - break; - } - _ => (), - } - } - } - } - } - - if let Some(msg) = opt_msg.as_ref() { - msg - } else { - // We could default to msg=VoidMsg here. If error messages weren't - // so terrible this might even be a good idea! - abort!( - args.token.span, - "make_widget: cannot discover msg type from #[handler] attr or Handler impl" - ); - } - }; - if args.generics.where_clause.is_none() { args.generics.where_clause = Some(WhereClause { where_token: Default::default(), @@ -148,15 +66,10 @@ pub(crate) fn make_widget(mut args: MakeWidget) -> Result { None => make_ident(format_args!("mw_anon_{}", index)), }; - let widget_attr = if let Some(attr) = field + let is_widget = field .attrs .iter() - .find(|attr| (attr.path == parse_quote! { widget })) - { - Some(syn::parse2::(attr.tokens.clone())?) - } else { - None - }; + .any(|attr| (attr.path == parse_quote! { widget })); let ty: Type = match field.ty { ChildType::Fixed(ty) => ty.clone(), @@ -164,24 +77,10 @@ pub(crate) fn make_widget(mut args: MakeWidget) -> Result { args.generics.params.extend(gen_args); ty.clone() } - ChildType::Generic(gen_msg, gen_bound) => { + ChildType::Generic(gen_bound) => { let ty = make_ident(format_args!("MWAnon{}", index)); - if let Some(ref wattr) = widget_attr { - if let Some(tyr) = gen_msg { - clauses.push(parse_quote! { #ty: ::kas::Widget }); - } else if let Some(handler) = wattr.handler.any_ref() { - // Message passed to a method; exact type required - let ty_bound = find_handler_ty(handler, &args.impls); - clauses.push(parse_quote! { #ty: ::kas::Widget }); - } else if wattr.handler == Handler::Discard { - // No type bound on discarded message - } else { - // Message converted via Into - clauses - .push(parse_quote! { <#ty as ::kas::event::Handler>::Msg: Into<#msg> }); - } - + if is_widget { if let Some(mut bound) = gen_bound { bound.bounds.push(parse_quote! { ::kas::Widget }); args.generics.params.push(parse_quote! { #ty: #bound }); @@ -220,19 +119,6 @@ pub(crate) fn make_widget(mut args: MakeWidget) -> Result { if clauses.is_empty() { args.generics.where_clause = None; } - let (impl_generics, ty_generics, where_clause) = args.generics.split_for_impl(); - - if impl_handler { - // This cannot go in Scope::generated since ImplWidget checks for it - args.impls.push(parse_quote! { - impl #impl_generics ::kas::event::Handler - for AnonWidget #ty_generics - #where_clause - { - type Msg = #msg; - } - }); - } args.attrs.insert(0, parse_quote! { #[derive(Debug)] }); diff --git a/crates/kas-macros/src/widget.rs b/crates/kas-macros/src/widget.rs index b972d238a..acc1f9553 100644 --- a/crates/kas-macros/src/widget.rs +++ b/crates/kas-macros/src/widget.rs @@ -3,7 +3,7 @@ // You may obtain a copy of the License in the LICENSE-APACHE file or at: // https://www.apache.org/licenses/LICENSE-2.0 -use crate::args::{Child, Handler, WidgetArgs}; +use crate::args::{Child, WidgetArgs}; use impl_tools_lib::fields::{Fields, FieldsNamed, FieldsUnnamed}; use impl_tools_lib::{Scope, ScopeAttr, ScopeItem, SimplePath}; use proc_macro2::{Span, TokenStream}; @@ -44,7 +44,6 @@ pub fn widget(mut attr: WidgetArgs, scope: &mut Scope) -> Result<()> { let mut impl_layout = true; let mut has_find_id_impl = attr.layout.is_some(); let mut handler_impl = None; - let mut send_event_impl = None; let fields = match &mut scope.item { ScopeItem::Struct { token, fields } => match fields { @@ -70,15 +69,20 @@ pub fn widget(mut attr: WidgetArgs, scope: &mut Scope) -> Result<()> { let mut other_attrs = Vec::with_capacity(field.attrs.len()); for attr in field.attrs.drain(..) { if attr.path == parse_quote! { widget_core } { + if !attr.tokens.is_empty() { + return Err(Error::new(attr.tokens.span(), "unexpected token")); + } if core_data.is_none() { core_data = Some(member(i, field.ident.clone())); } else { emit_error!(attr.span(), "multiple fields marked with #[widget_core]"); } } else if attr.path == parse_quote! { widget } { + if !attr.tokens.is_empty() { + return Err(Error::new(attr.tokens.span(), "unexpected token")); + } let ident = member(i, field.ident.clone()); - let args = syn::parse2(attr.tokens)?; - children.push(Child { ident, args }); + children.push(Child { ident }); } else { other_attrs.push(attr); } @@ -147,18 +151,6 @@ pub fn widget(mut attr: WidgetArgs, scope: &mut Scope) -> Result<()> { } // TODO: warn about unused handler stuff if present handler_impl = Some(index); - } else if *path == parse_quote! { ::kas::event::SendEvent } - || *path == parse_quote! { kas::event::SendEvent } - || *path == parse_quote! { event::SendEvent } - || *path == parse_quote! { SendEvent } - { - if opt_derive.is_some() { - emit_error!( - impl_.span(), - "impl conflicts with use of #[widget(derive=FIELD)]" - ); - } - send_event_impl = Some(index); } } } @@ -203,8 +195,8 @@ pub fn widget(mut attr: WidgetArgs, scope: &mut Scope) -> Result<()> { #widget_name } - fn as_widget(&self) -> &dyn ::kas::WidgetConfig { self } - fn as_widget_mut(&mut self) -> &mut dyn ::kas::WidgetConfig { self } + fn as_widget(&self) -> &dyn ::kas::Widget { self } + fn as_widget_mut(&mut self) -> &mut dyn ::kas::Widget { self } } }); @@ -214,15 +206,22 @@ pub fn widget(mut attr: WidgetArgs, scope: &mut Scope) -> Result<()> { impl #impl_generics ::kas::WidgetChildren for #name #ty_generics #where_clause { + #[inline] fn num_children(&self) -> usize { self.#inner.num_children() } - fn get_child(&self, index: usize) -> Option<&dyn ::kas::WidgetConfig> { + #[inline] + fn get_child(&self, index: usize) -> Option<&dyn ::kas::Widget> { self.#inner.get_child(index) } - fn get_child_mut(&mut self, index: usize) -> Option<&mut dyn ::kas::WidgetConfig> { + #[inline] + fn get_child_mut(&mut self, index: usize) -> Option<&mut dyn ::kas::Widget> { self.#inner.get_child_mut(index) } + #[inline] + fn find_child_index(&self, id: &::kas::WidgetId) -> Option { + self.#inner.find_child_index(id) + } } }); } else { @@ -243,13 +242,13 @@ pub fn widget(mut attr: WidgetArgs, scope: &mut Scope) -> Result<()> { fn num_children(&self) -> usize { #count } - fn get_child(&self, _index: usize) -> Option<&dyn ::kas::WidgetConfig> { + fn get_child(&self, _index: usize) -> Option<&dyn ::kas::Widget> { match _index { #get_rules _ => None } } - fn get_child_mut(&mut self, _index: usize) -> Option<&mut dyn ::kas::WidgetConfig> { + fn get_child_mut(&mut self, _index: usize) -> Option<&mut dyn ::kas::Widget> { match _index { #get_mut_rules _ => None @@ -261,14 +260,39 @@ pub fn widget(mut attr: WidgetArgs, scope: &mut Scope) -> Result<()> { } if impl_widget_config { - let key_nav = attr.key_nav.value; - let hover_highlight = attr.hover_highlight.value; - let cursor_icon = attr.cursor_icon.value; + let methods = if let Some(inner) = opt_derive { + quote! { + #[inline] + fn configure_recurse( + &mut self, + mgr: &mut ::kas::layout::SetRectMgr, + id: ::kas::WidgetId, + ) { + self.#inner.configure_recurse(mgr, id); + } + #[inline] + fn configure(&mut self, mgr: &mut ::kas::layout::SetRectMgr) { + self.#inner.configure(mgr); + } + #[inline] + fn key_nav(&self) -> bool { + self.#inner.key_nav() + } + #[inline] + fn hover_highlight(&self) -> bool { + self.#inner.hover_highlight() + } + #[inline] + fn cursor_icon(&self) -> ::kas::event::CursorIcon { + self.#inner.cursor_icon() + } + } + } else { + let key_nav = attr.key_nav.value; + let hover_highlight = attr.hover_highlight.value; + let cursor_icon = attr.cursor_icon.value; - scope.generated.push(quote! { - impl #impl_generics ::kas::WidgetConfig - for #name #ty_generics #where_clause - { + quote! { fn key_nav(&self) -> bool { #key_nav } @@ -279,6 +303,14 @@ pub fn widget(mut attr: WidgetArgs, scope: &mut Scope) -> Result<()> { #cursor_icon } } + }; + + scope.generated.push(quote! { + impl #impl_generics ::kas::WidgetConfig + for #name #ty_generics #where_clause + { + #methods + } }); } else { if let Some(span) = attr.key_nav.span { @@ -298,6 +330,10 @@ pub fn widget(mut attr: WidgetArgs, scope: &mut Scope) -> Result<()> { impl #impl_generics ::kas::Layout for #name #ty_generics #where_clause { + #[inline] + fn layout(&mut self) -> ::kas::layout::Layout<'_> { + self.#inner.layout() + } #[inline] fn size_rules(&mut self, size_mgr: ::kas::theme::SizeMgr, @@ -385,9 +421,6 @@ pub fn widget(mut attr: WidgetArgs, scope: &mut Scope) -> Result<()> { impl_generics = a; where_clause = c; } else { - let msg = attr - .msg - .unwrap_or_else(|| parse_quote! { ::kas::event::VoidMsg }); let handle = if let Some(inner) = opt_derive { quote! { #[inline] @@ -395,8 +428,41 @@ pub fn widget(mut attr: WidgetArgs, scope: &mut Scope) -> Result<()> { self.#inner.activation_via_press() } #[inline] - fn handle(&mut self, mgr: &mut EventMgr, event: Event) -> Response { - self.#inner.handle(mgr, event) + fn focus_on_key_nav(&self) -> bool { + self.#inner.focus_on_key_nav() + } + #[inline] + fn handle_event( + &mut self, + mgr: &mut ::kas::event::EventMgr, + event: ::kas::event::Event, + ) -> ::kas::event::Response { + self.#inner.handle_event(mgr, event) + } + #[inline] + fn handle_unused( + &mut self, + mgr: &mut ::kas::event::EventMgr, + index: usize, + event: ::kas::event::Event, + ) -> ::kas::event::Response { + self.#inner.handle_unused(mgr, index, event) + } + #[inline] + fn handle_message( + &mut self, + mgr: &mut ::kas::event::EventMgr, + index: usize, + ) { + self.#inner.handle_message(mgr, index); + } + #[inline] + fn handle_scroll( + &mut self, + mgr: &mut ::kas::event::EventMgr, + scroll: ::kas::event::Scroll, + ) -> ::kas::event::Scroll { + self.#inner.handle_scroll(mgr, scroll) } } } else { @@ -406,118 +472,11 @@ pub fn widget(mut attr: WidgetArgs, scope: &mut Scope) -> Result<()> { impl #impl_generics ::kas::event::Handler for #name #ty_generics #where_clause { - type Msg = #msg; #handle } }); } - if let Some(index) = send_event_impl { - // Manual SendEvent impl may add additional bounds: - let (a, _, c) = scope.impls[index].generics.split_for_impl(); - impl_generics = a; - where_clause = c; - } else { - let send_impl = if let Some(inner) = opt_derive { - quote! { self.#inner.send(mgr, id, event) } - } else { - let mut ev_to_num = TokenStream::new(); - for (i, child) in children.iter().enumerate() { - #[cfg(feature = "log")] - let id = quote! { id.clone() }; - #[cfg(feature = "log")] - let log_msg = quote! { - ::log::trace!( - "Received by {} from {}: {:?}", - self.id(), - id, - ::kas::util::TryFormat(&msg) - ); - }; - #[cfg(not(feature = "log"))] - let id = quote! { id }; - #[cfg(not(feature = "log"))] - let log_msg = quote! {}; - - let ident = &child.ident; - let update = if let Some(f) = child.args.update.as_ref() { - quote! { - if matches!(r, Response::Update) { - self.#f(mgr); - } - } - } else { - quote! {} - }; - let handler = match &child.args.handler { - Handler::Use(f) => quote! { - r.try_into().unwrap_or_else(|msg| { - #log_msg - let _: () = self.#f(mgr, msg); - Response::Used - }) - }, - Handler::Map(f) => quote! { - r.try_into().unwrap_or_else(|msg| { - #log_msg - Response::Msg(self.#f(mgr, msg)) - }) - }, - Handler::FlatMap(f) => quote! { - r.try_into().unwrap_or_else(|msg| { - #log_msg - self.#f(mgr, msg) - }) - }, - Handler::Discard => quote! { - r.try_into().unwrap_or_else(|msg| { - #log_msg - let _ = msg; - Response::Used - }) - }, - Handler::None => quote! { r.into() }, - }; - - ev_to_num.append_all(quote! { - Some(#i) => { - let r = self.#ident.send(mgr, #id, event); - #update - #handler - } - }); - } - - quote! { - use ::kas::{event::Response, WidgetCore, WidgetChildren, WidgetExt}; - match self.find_child_index(&id) { - #ev_to_num - _ if id == self.core_data().id => ::kas::event::EventMgr::handle_generic(self, mgr, event), - _ => { - debug_assert!(false, "SendEvent::send: bad WidgetId"); - Response::Unused - } - } - } - }; - - scope.generated.push(quote! { - impl #impl_generics ::kas::event::SendEvent - for #name #ty_generics #where_clause - { - fn send( - &mut self, - mgr: &mut ::kas::event::EventMgr, - id: ::kas::WidgetId, - event: ::kas::event::Event - ) -> ::kas::event::Response - { - #send_impl - } - } - }); - } - scope.generated.push(quote! { impl #impl_generics ::kas::Widget for #name #ty_generics #where_clause {} }); diff --git a/crates/kas-widgets/src/adapter/adapt_widget.rs b/crates/kas-widgets/src/adapter/adapt_widget.rs index 97023f75b..c78da22bb 100644 --- a/crates/kas-widgets/src/adapter/adapt_widget.rs +++ b/crates/kas-widgets/src/adapter/adapt_widget.rs @@ -5,63 +5,26 @@ //! Widget extension traits -use super::{MapResponse, Reserve, WithLabel}; +use super::{MapMessage, Reserve, WithLabel}; use kas::dir::Directional; -use kas::event::{EventMgr, Response, VoidMsg}; use kas::layout::{AxisInfo, SizeRules}; use kas::text::AccelString; use kas::theme::SizeMgr; #[allow(unused)] use kas::Layout; use kas::Widget; +use std::fmt::Debug; /// Provides some convenience methods on widgets pub trait AdaptWidget: Widget { - /// Construct a wrapper widget which maps messages to `M` - /// - /// Responses from this widget with a message payload are mapped with `f`. - #[must_use] - fn map_msg(self, f: F) -> MapResponse - where - F: Fn(&mut EventMgr, Self::Msg) -> M + 'static, - Self: Sized, - { - MapResponse::new(self, move |mgr, msg| Response::Msg(f(mgr, msg))) - } - - /// Construct a wrapper widget which maps messages from [`VoidMsg`] to `M` - /// - /// Responses from this widget with a message payload are mapped with `f`. - #[must_use] - fn map_void_msg(self) -> MapResponse - where - Self: Widget + Sized, - { - MapResponse::new(self, |_, msg| match msg {}) - } - - /// Construct a wrapper widget which discards messages from this widget - /// - /// Responses from this widget with a message payload are mapped to - /// [`Response::Used`]. - #[must_use] - fn map_msg_discard(self) -> MapResponse - where - Self: Sized, - { - MapResponse::new(self, |_, _| Response::Used) - } - - /// Construct a wrapper widget which maps message responses from this widget - /// - /// Responses from this widget with a message payload are mapped with `f`. + /// Construct a wrapper widget which maps a message of the given type #[must_use] - fn map_response(self, f: F) -> MapResponse + fn map_msg(self, f: F) -> MapMessage where - F: Fn(&mut EventMgr, Self::Msg) -> Response + 'static, Self: Sized, + F: FnMut(M) -> N, { - MapResponse::new(self, f) + MapMessage::new(self, f) } /// Construct a wrapper widget which reserves extra space diff --git a/crates/kas-widgets/src/adapter/label.rs b/crates/kas-widgets/src/adapter/label.rs index 28e4482e1..9822e5e2e 100644 --- a/crates/kas-widgets/src/adapter/label.rs +++ b/crates/kas-widgets/src/adapter/label.rs @@ -18,7 +18,7 @@ impl_scope! { /// Mouse/touch input on the label sends events to the inner widget. #[autoimpl(Deref, DerefMut using self.inner)] #[derive(Clone, Default, Debug)] - #[widget { msg = W::Msg; }] + #[widget] pub struct WithLabel { #[widget_core] core: CoreData, diff --git a/crates/kas-widgets/src/adapter/map.rs b/crates/kas-widgets/src/adapter/map.rs index 604f09f80..9e4825475 100644 --- a/crates/kas-widgets/src/adapter/map.rs +++ b/crates/kas-widgets/src/adapter/map.rs @@ -5,9 +5,10 @@ //! Message Map widget -use crate::menu; +use crate::menu::{self, Menu}; use kas::prelude::*; -use std::rc::Rc; +use std::fmt::Debug; +use std::marker::PhantomData; impl_scope! { /// Wrapper to map messages from the inner widget @@ -17,58 +18,42 @@ impl_scope! { #[derive(Clone)] #[widget{ layout = single; - msg = M; }] - pub struct MapResponse { + pub struct MapMessage N + 'static> { #[widget_core] core: kas::CoreData, #[widget] inner: W, - map: Rc Response>, + map: F, + _m: PhantomData, + _n: PhantomData, } impl Self { /// Construct /// /// Any response from the child widget with a message payload is mapped - /// through the closure `f`. - pub fn new Response + 'static>(child: W, f: F) -> Self { - Self::new_rc(child, Rc::new(f)) - } - - /// Construct with an Rc-wrapped method - /// - /// Any response from the child widget with a message payload is mapped - /// through the closure `f`. - pub fn new_rc(child: W, f: Rc Response>) -> Self { - MapResponse { + /// through the closure `map`. + pub fn new(inner: W, map: F) -> Self { + MapMessage { core: Default::default(), - inner: child, - map: f, + inner, + map, + _m: PhantomData, + _n: PhantomData, } } } - impl SendEvent for Self { - fn send(&mut self, mgr: &mut EventMgr, id: WidgetId, event: Event) -> Response { - if self.eq_id(&id) { - self.handle(mgr, event) - } else { - let r = self.inner.send(mgr, id.clone(), event); - r.try_into().unwrap_or_else(|msg| { - log::trace!( - "Received by {} from {}: {:?}", - self.id(), - id, - kas::util::TryFormat(&msg) - ); - (self.map)(mgr, msg) - }) + impl Handler for Self { + fn handle_message(&mut self, mgr: &mut EventMgr, _: usize) { + if let Some(msg) = mgr.try_pop_msg() { + mgr.push_msg((self.map)(msg)); } } } - impl menu::Menu for MapResponse { + impl Menu for Self where W: Menu { fn sub_items(&mut self) -> Option { self.inner.sub_items() } diff --git a/crates/kas-widgets/src/adapter/mod.rs b/crates/kas-widgets/src/adapter/mod.rs index 3281bd871..949262663 100644 --- a/crates/kas-widgets/src/adapter/mod.rs +++ b/crates/kas-widgets/src/adapter/mod.rs @@ -12,5 +12,5 @@ mod reserve; pub use adapt_widget::*; pub use label::WithLabel; -pub use map::MapResponse; +pub use map::MapMessage; pub use reserve::{Reserve, ReserveP}; diff --git a/crates/kas-widgets/src/adapter/reserve.rs b/crates/kas-widgets/src/adapter/reserve.rs index bc0e8a721..3b746c4d0 100644 --- a/crates/kas-widgets/src/adapter/reserve.rs +++ b/crates/kas-widgets/src/adapter/reserve.rs @@ -26,7 +26,7 @@ impl_scope! { #[autoimpl(Deref, DerefMut using self.inner)] #[autoimpl(class_traits using self.inner where W: trait)] #[derive(Clone, Default)] - #[widget { msg = ::Msg; }] + #[widget] pub struct Reserve SizeRules + 'static> { #[widget_core] core: CoreData, diff --git a/crates/kas-widgets/src/button.rs b/crates/kas-widgets/src/button.rs index 692057d1f..a18bf3294 100644 --- a/crates/kas-widgets/src/button.rs +++ b/crates/kas-widgets/src/button.rs @@ -11,6 +11,7 @@ use kas::event::{self, VirtualKeyCode, VirtualKeyCodes}; use kas::layout; use kas::prelude::*; use kas::theme::TextClass; +use std::fmt::Debug; use std::rc::Rc; impl_scope! { @@ -22,7 +23,7 @@ impl_scope! { #[autoimpl(class_traits using self.inner where W: trait)] #[derive(Clone)] #[widget] - pub struct Button, M: 'static> { + pub struct Button { #[widget_core] core: kas::CoreData, keys1: VirtualKeyCodes, @@ -30,7 +31,7 @@ impl_scope! { color: Option, #[widget] pub inner: W, - on_push: Option Option>>, + on_push: Option>, } impl WidgetConfig for Self { @@ -53,7 +54,7 @@ impl_scope! { } } - impl> Button { + impl Button { /// Construct a button with given `inner` widget #[inline] pub fn new(inner: W) -> Self { @@ -70,13 +71,12 @@ impl_scope! { /// Set event handler `f` /// /// On activation (through user input events or [`Event::Activate`]) the - /// closure `f` is called. The result of `f` is converted to - /// [`Response::Msg`] or [`Response::Used`] and returned to the parent. + /// closure `f` is called. #[inline] #[must_use] - pub fn on_push(self, f: F) -> Button + pub fn on_push(self, f: F) -> Button where - F: Fn(&mut EventMgr) -> Option + 'static, + F: Fn(&mut EventMgr) + 'static, { Button { core: self.core, @@ -93,12 +93,11 @@ impl_scope! { /// Construct a button with a given `inner` widget and event handler `f` /// /// On activation (through user input events or [`Event::Activate`]) the - /// closure `f` is called. The result of `f` is converted to - /// [`Response::Msg`] or [`Response::Used`] and returned to the parent. + /// closure `f` is called. #[inline] pub fn new_on(inner: W, f: F) -> Self where - F: Fn(&mut EventMgr) -> Option + 'static, + F: Fn(&mut EventMgr) + 'static, { Button::new(inner).on_push(f) } @@ -109,11 +108,8 @@ impl_scope! { /// of `msg` is returned to the parent widget. Click actions must be /// implemented through a handler on the parent widget (or other ancestor). #[inline] - pub fn new_msg(inner: W, msg: M) -> Self - where - M: Clone, - { - Self::new_on(inner, move |_| Some(msg.clone())) + pub fn new_msg(inner: W, msg: M) -> Self { + Self::new_on(inner, move |mgr| mgr.push_msg(msg.clone())) } /// Add accelerator keys (chain style) @@ -138,31 +134,23 @@ impl_scope! { } impl Handler for Self { - type Msg = M; - #[inline] fn activation_via_press(&self) -> bool { true } - fn handle(&mut self, mgr: &mut EventMgr, event: Event) -> Response { + fn handle_event(&mut self, mgr: &mut EventMgr, event: Event) -> Response { match event { - Event::Activate => Response::used_or_msg(self.on_push.as_ref().and_then(|f| f(mgr))), + Event::Activate => { + if let Some(f) = self.on_push.as_ref() { + f(mgr); + } + Response::Used + } _ => Response::Unused, } } } - - impl SendEvent for Self { - fn send(&mut self, mgr: &mut EventMgr, id: WidgetId, event: Event) -> Response { - if self.eq_id(&id) { - EventMgr::handle_generic(self, mgr, event) - } else { - debug_assert!(self.inner.id().is_ancestor_of(&id)); - self.inner.send(mgr, id, event).void_into() - } - } - } } impl_scope! { @@ -177,14 +165,14 @@ impl_scope! { #[autoimpl(Debug ignore self.on_push)] #[derive(Clone)] #[widget] - pub struct TextButton { + pub struct TextButton { #[widget_core] core: kas::CoreData, keys1: VirtualKeyCodes, label: Label, layout_frame: layout::FrameStorage, color: Option, - on_push: Option Option>>, + on_push: Option>, } impl WidgetConfig for Self { @@ -208,7 +196,7 @@ impl_scope! { } } - impl TextButton { + impl Self { /// Construct a button with given `label` #[inline] pub fn new>(label: S) -> Self { @@ -225,13 +213,12 @@ impl_scope! { /// Set event handler `f` /// /// On activation (through user input events or [`Event::Activate`]) the - /// closure `f` is called. The result of `f` is converted to - /// [`Response::Msg`] or [`Response::Used`] and returned to the parent. + /// closure `f` is called. #[inline] #[must_use] - pub fn on_push(self, f: F) -> TextButton + pub fn on_push(self, f: F) -> TextButton where - F: Fn(&mut EventMgr) -> Option + 'static, + F: Fn(&mut EventMgr) + 'static, { TextButton { core: self.core, @@ -242,18 +229,15 @@ impl_scope! { on_push: Some(Rc::new(f)), } } - } - impl Self { /// Construct a button with a given `label` and event handler `f` /// /// On activation (through user input events or [`Event::Activate`]) the - /// closure `f` is called. The result of `f` is converted to - /// [`Response::Msg`] or [`Response::Used`] and returned to the parent. + /// closure `f` is called. #[inline] pub fn new_on, F>(label: S, f: F) -> Self where - F: Fn(&mut EventMgr) -> Option + 'static, + F: Fn(&mut EventMgr) + 'static, { TextButton::new(label).on_push(f) } @@ -264,11 +248,8 @@ impl_scope! { /// of `msg` is returned to the parent widget. Click actions must be /// implemented through a handler on the parent widget (or other ancestor). #[inline] - pub fn new_msg>(label: S, msg: M) -> Self - where - M: Clone, - { - Self::new_on(label, move |_| Some(msg.clone())) + pub fn new_msg, M: Clone + Debug + 'static>(label: S, msg: M) -> Self { + Self::new_on(label, move |mgr| mgr.push_msg(msg.clone())) } /// Add accelerator keys (chain style) @@ -312,16 +293,19 @@ impl_scope! { } impl event::Handler for Self { - type Msg = M; - #[inline] fn activation_via_press(&self) -> bool { true } - fn handle(&mut self, mgr: &mut EventMgr, event: Event) -> Response { + fn handle_event(&mut self, mgr: &mut EventMgr, event: Event) -> Response { match event { - Event::Activate => Response::used_or_msg(self.on_push.as_ref().and_then(|f| f(mgr))), + Event::Activate => { + if let Some(f) = self.on_push.as_ref() { + f(mgr); + } + Response::Used + } _ => Response::Unused, } } diff --git a/crates/kas-widgets/src/checkbox.rs b/crates/kas-widgets/src/checkbox.rs index fa75cd09f..dbcc38335 100644 --- a/crates/kas-widgets/src/checkbox.rs +++ b/crates/kas-widgets/src/checkbox.rs @@ -17,12 +17,12 @@ impl_scope! { key_nav = true; hover_highlight = true; }] - pub struct CheckBoxBare { + pub struct CheckBoxBare { #[widget_core] core: CoreData, state: bool, editable: bool, - on_toggle: Option Option>>, + on_toggle: Option>, } impl Layout for Self { @@ -45,7 +45,7 @@ impl_scope! { } } - impl CheckBoxBare { + impl Self { /// Construct a checkbox #[inline] pub fn new() -> Self { @@ -60,13 +60,12 @@ impl_scope! { /// Set event handler `f` /// /// On toggle (through user input events or [`Event::Activate`]) the - /// closure `f` is called. The result of `f` is converted to - /// [`Response::Msg`] or [`Response::Update`] and returned to the parent. + /// closure `f` is called. #[inline] #[must_use] - pub fn on_toggle(self, f: F) -> CheckBoxBare + pub fn on_toggle(self, f: F) -> CheckBoxBare where - F: Fn(&mut EventMgr, bool) -> Option + 'static, + F: Fn(&mut EventMgr, bool) + 'static, { CheckBoxBare { core: self.core, @@ -75,18 +74,15 @@ impl_scope! { on_toggle: Some(Rc::new(f)), } } - } - impl Self { /// Construct a checkbox with event handler `f` /// /// On activation (through user input events or [`Event::Activate`]) the - /// closure `f` is called. The result of `f` is converted to - /// [`Response::Msg`] or [`Response::Update`] and returned to the parent. + /// closure `f` is called. #[inline] pub fn new_on(f: F) -> Self where - F: Fn(&mut EventMgr, bool) -> Option + 'static, + F: Fn(&mut EventMgr, bool) + 'static, { CheckBoxBare::new().on_toggle(f) } @@ -132,19 +128,20 @@ impl_scope! { } impl event::Handler for Self { - type Msg = M; - #[inline] fn activation_via_press(&self) -> bool { true } - fn handle(&mut self, mgr: &mut EventMgr, event: Event) -> Response { + fn handle_event(&mut self, mgr: &mut EventMgr, event: Event) -> Response { match event { Event::Activate if self.editable => { self.state = !self.state; mgr.redraw(self.id()); - Response::update_or_msg(self.on_toggle.as_ref().and_then(|f| f(mgr, self.state))) + if let Some(f) = self.on_toggle.as_ref() { + f(mgr, self.state); + } + Response::Used } _ => Response::Unused, } @@ -161,11 +158,11 @@ impl_scope! { layout = row: *; find_id = Some(self.inner.id()); }] - pub struct CheckBox { + pub struct CheckBox { #[widget_core] core: CoreData, #[widget] - inner: CheckBoxBare, + inner: CheckBoxBare, #[widget] label: AccelLabel, } @@ -176,11 +173,7 @@ impl_scope! { } } - impl Handler for Self where M: From { - type Msg = M; - } - - impl CheckBox { + impl Self { /// Construct a checkbox with a given `label` /// /// CheckBox labels are optional; if no label is desired, use an empty @@ -197,13 +190,12 @@ impl_scope! { /// Set event handler `f` /// /// On toggle (through user input events or [`Event::Activate`]) the - /// closure `f` is called. The result of `f` is converted to - /// [`Response::Msg`] or [`Response::Update`] and returned to the parent. + /// closure `f` is called. #[inline] #[must_use] - pub fn on_toggle(self, f: F) -> CheckBox + pub fn on_toggle(self, f: F) -> CheckBox where - F: Fn(&mut EventMgr, bool) -> Option + 'static, + F: Fn(&mut EventMgr, bool) + 'static, { CheckBox { core: self.core, @@ -211,21 +203,18 @@ impl_scope! { label: self.label, } } - } - impl Self { /// Construct a checkbox with a given `label` and event handler `f` /// /// CheckBox labels are optional; if no label is desired, use an empty /// string. /// /// On toggle (through user input events or [`Event::Activate`]) the - /// closure `f` is called. The result of `f` is converted to - /// [`Response::Msg`] or [`Response::Update`] and returned to the parent. + /// closure `f` is called. #[inline] pub fn new_on, F>(label: T, f: F) -> Self where - F: Fn(&mut EventMgr, bool) -> Option + 'static, + F: Fn(&mut EventMgr, bool) + 'static, { CheckBox::new(label).on_toggle(f) } diff --git a/crates/kas-widgets/src/combobox.rs b/crates/kas-widgets/src/combobox.rs index 2e1f26f49..e413f1c2d 100644 --- a/crates/kas-widgets/src/combobox.rs +++ b/crates/kas-widgets/src/combobox.rs @@ -5,26 +5,34 @@ //! Combobox -use super::{menu::MenuEntry, IndexedColumn, PopupFrame}; +use super::{menu::MenuEntry, Column, PopupFrame}; use kas::component::{Label, Mark}; -use kas::event::{self, Command}; +use kas::event::Command; use kas::layout; use kas::prelude::*; use kas::theme::{MarkStyle, TextClass}; use kas::WindowId; +use std::fmt::Debug; use std::rc::Rc; +#[derive(Clone, Debug)] +struct IndexMsg(usize); + impl_scope! { /// A pop-up multiple choice menu /// + /// # Messages + /// /// A combobox presents a menu with a fixed set of choices when clicked. + /// Each choice has an associated "message" value of type `M`. + /// + /// If no selection handler exists, then the choice's message is emitted + /// when selected. If a handler is specified via [`Self::on_select`], then + /// this message is passed to the handler and not emitted. #[autoimpl(Debug ignore self.on_select)] #[derive(Clone)] - #[widget{ - key_nav = true; - hover_highlight = true; - }] - pub struct ComboBox { + #[widget] + pub struct ComboBox { #[widget_core] core: CoreData, label: Label, @@ -32,11 +40,31 @@ impl_scope! { layout_list: layout::FixedRowStorage<2>, layout_frame: layout::FrameStorage, #[widget] - popup: ComboPopup, + popup: ComboPopup, active: usize, opening: bool, popup_id: Option, - on_select: Option Option>>, + on_select: Option>, + } + + impl WidgetConfig for Self { + fn configure_recurse(&mut self, mgr: &mut SetRectMgr, id: WidgetId) { + self.core_data_mut().id = id; + mgr.new_accel_layer(self.id(), true); + + let id = self.id_ref().make_child(widget_index![self.popup]); + self.popup.configure_recurse(mgr, id); + + self.configure(mgr); + } + + fn key_nav(&self) -> bool { + true + } + + fn hover_highlight(&self) -> bool { + true + } } impl kas::Layout for Self { @@ -60,17 +88,15 @@ impl_scope! { } } - impl event::Handler for Self { - type Msg = M; - - fn handle(&mut self, mgr: &mut EventMgr, event: Event) -> Response { + impl Handler for Self { + fn handle_event(&mut self, mgr: &mut EventMgr, event: Event) -> Response { let open_popup = |s: &mut Self, mgr: &mut EventMgr, key_focus: bool| { s.popup_id = mgr.add_popup(kas::Popup { id: s.popup.id(), parent: s.id(), direction: Direction::Down, }); - if let Some(id) = s.popup.inner.get_child(s.active).map(|w| w.id()) { + if let Some(id) = s.popup.inner.inner.get_child(s.active).map(|w| w.id()) { mgr.set_nav_focus(id, key_focus); } }; @@ -145,8 +171,7 @@ impl_scope! { return Response::Used; } } else if self.popup_id.is_some() && self.popup.is_ancestor_of(id) { - let r = self.popup.send(mgr, id.clone(), Event::Activate); - return self.map_response(mgr, id.clone(), event, r); + return mgr.send(self, id.clone(), Event::Activate); } } if let Some(id) = self.popup_id { @@ -163,60 +188,51 @@ impl_scope! { _ => Response::Unused, } } - } - impl event::SendEvent for Self { - fn send(&mut self, mgr: &mut EventMgr, id: WidgetId, event: Event) -> Response { - if self.eq_id(&id) { - EventMgr::handle_generic(self, mgr, event) - } else { - debug_assert!(self.popup.id().is_ancestor_of(&id)); - - if let Event::NavFocus(key_focus) = event { - if self.popup_id.is_none() { - // Steal focus since child is invisible - mgr.set_nav_focus(self.id(), key_focus); + fn handle_message(&mut self, mgr: &mut EventMgr, _: usize) { + if let Some(IndexMsg(index)) = mgr.try_pop_msg() { + *mgr |= self.set_active(index); + if let Some(id) = self.popup_id { + mgr.close_window(id, true); + } + if let Some(ref f) = self.on_select { + if let Some(msg) = mgr.try_pop_msg() { + (f)(mgr, msg); } - // Don't bother sending Response::Focus here since NavFocus will - // be sent to this widget, and handle_generic will respond. - return Response::Used; } - - let r = self.popup.send(mgr, id.clone(), event.clone()); - self.map_response(mgr, id, event, r) } } } } -impl ComboBox { +impl ComboBox { /// Construct a combobox /// /// Constructs a combobox with labels derived from an iterator over string - /// types, and the chosen `active` entry. For example: + /// types. For example: /// ``` /// # use kas_widgets::ComboBox; - /// let combobox = ComboBox::new_from_iter(&["zero", "one", "two"], 0); + /// let combobox = ComboBox::new_from_iter([("zero", 0), ("one", 1), ("two", 2)].into_iter()); /// ``` + /// + /// Initially, the first entry is active. #[inline] - pub fn new_from_iter, I: IntoIterator>( - iter: I, - active: usize, - ) -> Self { + pub fn new_from_iter, I: IntoIterator>(iter: I) -> Self { let entries = iter .into_iter() - .map(|label| MenuEntry::new(label, ())) + .map(|(label, msg)| MenuEntry::new(label, msg)) .collect(); - Self::new(entries, active) + Self::new(entries) } /// Construct a combobox with the given menu entries /// - /// A combobox presents a menu with a fixed set of choices when clicked, - /// with the `active` choice selected (0-based index). + /// A combobox presents a menu with a fixed set of choices when clicked. + /// + /// Initially, the first entry is active. #[inline] - pub fn new(entries: Vec>, active: usize) -> Self { - let label = entries.get(active).map(|entry| entry.get_string()); + pub fn new(entries: Vec>) -> Self { + let label = entries.get(0).map(|entry| entry.get_string()); let label = Label::new(label.unwrap_or("".to_string()), TextClass::Button); ComboBox { core: Default::default(), @@ -226,9 +242,11 @@ impl ComboBox { layout_frame: Default::default(), popup: ComboPopup { core: Default::default(), - inner: PopupFrame::new(IndexedColumn::new(entries)), + inner: PopupFrame::new( + Column::new(entries).on_message(|mgr, index| mgr.push_msg(IndexMsg(index))), + ), }, - active, + active: 0, opening: false, popup_id: None, on_select: None, @@ -238,13 +256,12 @@ impl ComboBox { /// Set the selection handler `f` /// /// On selection of a new choice the closure `f` is called with the choice's - /// index. The result of `f` is converted to [`Response::Msg`] or - /// [`Response::Update`] and returned to the parent. + /// message. #[inline] #[must_use] - pub fn on_select(self, f: F) -> ComboBox + pub fn on_select(self, f: F) -> ComboBox where - F: Fn(&mut EventMgr, usize) -> Option + 'static, + F: Fn(&mut EventMgr, M) + 'static, { ComboBox { core: self.core, @@ -261,7 +278,7 @@ impl ComboBox { } } -impl ComboBox { +impl ComboBox { /// Get the index of the active choice /// /// This index is normally less than the number of choices (`self.len()`), @@ -271,8 +288,14 @@ impl ComboBox { self.active } - /// Set the active choice + /// Set the active choice (inline style) #[inline] + pub fn with_active(mut self, index: usize) -> Self { + let _ = self.set_active(index); + self + } + + /// Set the active choice pub fn set_active(&mut self, index: usize) -> TkAction { if self.active != index && index < self.popup.inner.len() { self.active = index; @@ -311,9 +334,9 @@ impl ComboBox { // // TODO(opt): these methods cause full-window resize. They don't need to // resize at all if the menu is closed! - pub fn push>(&mut self, mgr: &mut SetRectMgr, label: T) -> usize { + pub fn push>(&mut self, mgr: &mut SetRectMgr, label: T, msg: M) -> usize { let column = &mut self.popup.inner; - column.push(mgr, MenuEntry::new(label, ())) + column.push(mgr, MenuEntry::new(label, msg)) } /// Pops the last choice from the combobox @@ -324,9 +347,15 @@ impl ComboBox { /// Add a choice at position `index` /// /// Panics if `index > len`. - pub fn insert>(&mut self, mgr: &mut SetRectMgr, index: usize, label: T) { + pub fn insert>( + &mut self, + mgr: &mut SetRectMgr, + index: usize, + label: T, + msg: M, + ) { let column = &mut self.popup.inner; - column.insert(mgr, index, MenuEntry::new(label, ())); + column.insert(mgr, index, MenuEntry::new(label, msg)); } /// Removes the choice at position `index` @@ -339,51 +368,16 @@ impl ComboBox { /// Replace the choice at `index` /// /// Panics if `index` is out of bounds. - pub fn replace>(&mut self, mgr: &mut SetRectMgr, index: usize, label: T) { + pub fn replace>( + &mut self, + mgr: &mut SetRectMgr, + index: usize, + label: T, + msg: M, + ) { self.popup .inner - .replace(mgr, index, MenuEntry::new(label, ())); - } -} - -impl ComboBox { - fn map_response( - &mut self, - mgr: &mut EventMgr, - id: WidgetId, - event: Event, - r: Response<(usize, ())>, - ) -> Response { - match r { - Response::Unused => EventMgr::handle_generic(self, mgr, event), - Response::Update | Response::Select => { - if let Some(id) = self.popup_id { - mgr.close_window(id, true); - } - if let Some(index) = self.popup.inner.find_child_index(&id) { - if index != self.active { - *mgr |= self.set_active(index); - return if let Some(ref f) = self.on_select { - Response::update_or_msg((f)(mgr, index)) - } else { - Response::Update - }; - } - } - Response::Used - } - r => r.try_into().unwrap_or_else(|(index, ())| { - *mgr |= self.set_active(index); - if let Some(id) = self.popup_id { - mgr.close_window(id, true); - } - if let Some(ref f) = self.on_select { - Response::update_or_msg((f)(mgr, index)) - } else { - Response::Update - } - }), - } + .replace(mgr, index, MenuEntry::new(label, msg)); } } @@ -391,12 +385,11 @@ impl_scope! { #[derive(Clone, Debug)] #[widget{ layout = single; - msg = (usize, ()); }] - struct ComboPopup { + struct ComboPopup { #[widget_core] core: CoreData, #[widget] - inner: PopupFrame>>, + inner: PopupFrame>>, } } diff --git a/crates/kas-widgets/src/dialog.rs b/crates/kas-widgets/src/dialog.rs index dc54e26c1..b428f7a77 100644 --- a/crates/kas-widgets/src/dialog.rs +++ b/crates/kas-widgets/src/dialog.rs @@ -14,6 +14,9 @@ use kas::prelude::*; use kas::text::format::FormattableText; use kas::WindowId; +#[derive(Copy, Clone, Debug)] +struct MessageBoxOk; + impl_scope! { /// A simple message box. #[derive(Clone, Debug)] @@ -26,8 +29,8 @@ impl_scope! { title: String, #[widget] label: Label, - #[widget(use_msg = handle_button)] - button: TextButton<()>, + #[widget] + button: TextButton, } impl Self { @@ -36,16 +39,20 @@ impl_scope! { core: Default::default(), title: title.to_string(), label: Label::new(message), - button: TextButton::new_msg("Ok", ()).with_keys(&[ + button: TextButton::new_msg("Ok", MessageBoxOk).with_keys(&[ VirtualKeyCode::Return, VirtualKeyCode::Space, VirtualKeyCode::NumpadEnter, ]), } } + } - fn handle_button(&mut self, mgr: &mut EventMgr, _: ()) { - mgr.send_action(TkAction::CLOSE); + impl kas::event::Handler for Self { + fn handle_message(&mut self, mgr: &mut EventMgr, _: usize) { + if let Some(MessageBoxOk) = mgr.try_pop_msg() { + mgr.send_action(TkAction::CLOSE); + } } } diff --git a/crates/kas-widgets/src/drag.rs b/crates/kas-widgets/src/drag.rs index 1472165fd..824493130 100644 --- a/crates/kas-widgets/src/drag.rs +++ b/crates/kas-widgets/src/drag.rs @@ -7,7 +7,7 @@ use std::fmt::Debug; -use kas::event::{CursorIcon, PressSource}; +use kas::event::{CursorIcon, MsgPressFocus, PressSource}; use kas::prelude::*; impl_scope! { @@ -26,6 +26,16 @@ impl_scope! { /// 3. [`Layout::draw`] does nothing. The parent should handle all drawing. /// 4. Optionally, this widget can handle clicks on the track area via /// [`DragHandle::handle_press_on_track`]. + /// + /// # Messages + /// + /// On [`Event::PressStart`], pushes [`MsgPressFocus`]. + /// + /// On input to change the position, pushes `offset: Offset`. This is a raw + /// offset relative to the track calculated from input (usually this is + /// between `Offset::ZERO` and [`Self::max_offset`], but it is not clamped). + /// The position is not updated by this widget; call [`Self::set_offset`] + /// to clamp the offset and update the position. #[derive(Clone, Debug, Default)] #[widget{ hover_highlight = true; @@ -59,11 +69,10 @@ impl_scope! { } impl Handler for DragHandle { - type Msg = Offset; - - fn handle(&mut self, mgr: &mut EventMgr, event: Event) -> Response { + fn handle_event(&mut self, mgr: &mut EventMgr, event: Event) -> Response { match event { Event::PressStart { source, coord, .. } => { + mgr.push_msg(MsgPressFocus); mgr.grab_press_unique(self.id(), source, coord, Some(CursorIcon::Grabbing)); // Event delivery implies coord is over the handle. @@ -71,14 +80,8 @@ impl_scope! { Response::Used } Event::PressMove { coord, .. } => { - let offset = coord - self.press_coord; - let (offset, action) = self.set_offset(offset); - if action.is_empty() { - Response::Used - } else { - mgr.send_action(action); - Response::Msg(offset) - } + mgr.push_msg(coord - self.press_coord); + Response::Used } Event::PressEnd { .. } => Response::Used, _ => Response::Unused, @@ -147,7 +150,8 @@ impl DragHandle { /// then the parent widget should call this method when receiving /// [`Event::PressStart`]. /// - /// This method moves the handle immediately and returns the new offset. + /// Returns a raw (unclamped) offset calculated from the press, but does + /// not move the handle (maybe call [`Self::set_offset`] with the result). pub fn handle_press_on_track( &mut self, mgr: &mut EventMgr, @@ -157,11 +161,6 @@ impl DragHandle { mgr.grab_press_unique(self.id(), source, coord, Some(CursorIcon::Grabbing)); self.press_coord = self.track.pos + self.core.rect.size / 2; - - // Since the press is not on the handle, we move the bar immediately. - let (offset, action) = self.set_offset(coord - self.press_coord); - debug_assert!(action == TkAction::REDRAW); - mgr.send_action(action); - offset + coord - self.press_coord } } diff --git a/crates/kas-widgets/src/edit_field.rs b/crates/kas-widgets/src/edit_field.rs index bcd1ffdf2..46b30491d 100644 --- a/crates/kas-widgets/src/edit_field.rs +++ b/crates/kas-widgets/src/edit_field.rs @@ -7,7 +7,7 @@ use super::Scrollable; use kas::event::components::{TextInput, TextInputAction}; -use kas::event::{self, Command, ScrollDelta}; +use kas::event::{self, Command, Scroll, ScrollDelta}; use kas::geom::Vec2; use kas::layout::{self, FrameStorage}; use kas::prelude::*; @@ -42,8 +42,7 @@ enum EditAction { /// /// When an [`EditField`] receives input, it updates its contents as expected, /// then invokes a method of `EditGuard`. This method may update the -/// [`EditField`] and may return a message to be returned by the [`EditField`]'s -/// event handler. +/// [`EditField`] and may return a message via [`EventMgr::push_msg`]. /// /// All methods on this trait are passed a reference to the [`EditField`] as /// parameter. The `EditGuard`'s state may be accessed via the @@ -51,21 +50,16 @@ enum EditAction { /// /// All methods have a default implementation which does nothing. /// -/// This trait is implemented for `()` (does nothing; Msg = VoidMsg). +/// This trait is implemented for `()` (does nothing). pub trait EditGuard: Debug + Sized + 'static { - /// The [`event::Handler::Msg`] type - type Msg; - /// Activation guard /// /// This function is called when the widget is "activated", for example by - /// the Enter/Return key for single-line edit boxes. Its return value is - /// converted to [`Response::Used`] or [`Response::Msg`]. + /// the Enter/Return key for single-line edit boxes. /// - /// Note that activation events cannot edit the contents. - fn activate(edit: &mut EditField, mgr: &mut EventMgr) -> Option { + /// The default implementation does nothing. + fn activate(edit: &mut EditField, mgr: &mut EventMgr) { let _ = (edit, mgr); - None } /// Focus-gained guard @@ -77,24 +71,22 @@ pub trait EditGuard: Debug + Sized + 'static { /// Focus-lost guard /// - /// This function is called when the widget loses keyboard input focus. Its - /// return value is converted to [`Response::Used`] or [`Response::Msg`]. - fn focus_lost(edit: &mut EditField, mgr: &mut EventMgr) -> Option { + /// This function is called when the widget loses keyboard input focus. + /// + /// The default implementation does nothing. + fn focus_lost(edit: &mut EditField, mgr: &mut EventMgr) { let _ = (edit, mgr); - None } /// Edit guard /// /// This function is called when contents are updated by the user (but not - /// on programmatic updates — see also [`EditGuard::update`]). Its return - /// value is converted to [`Response::Update`] or [`Response::Msg`]. + /// on programmatic updates — see also [`EditGuard::update`]). /// /// The default implementation calls [`EditGuard::update`]. - fn edit(edit: &mut EditField, mgr: &mut EventMgr) -> Option { + fn edit(edit: &mut EditField, mgr: &mut EventMgr) { Self::update(edit); let _ = mgr; - None } /// Update guard @@ -106,52 +98,47 @@ pub trait EditGuard: Debug + Sized + 'static { } } -impl EditGuard for () { - type Msg = VoidMsg; -} +impl EditGuard for () {} /// An [`EditGuard`] impl which calls a closure when activated #[autoimpl(Debug ignore self.0)] #[derive(Clone)] -pub struct EditActivate Option, M>(pub F); -impl EditGuard for EditActivate +pub struct EditActivate(pub F); +impl EditGuard for EditActivate where - F: FnMut(&str, &mut EventMgr) -> Option + 'static, + F: FnMut(&str, &mut EventMgr) + 'static, { - type Msg = M; - fn activate(edit: &mut EditField, mgr: &mut EventMgr) -> Option { - (edit.guard.0)(edit.text.text(), mgr) + fn activate(edit: &mut EditField, mgr: &mut EventMgr) { + (edit.guard.0)(edit.text.text(), mgr); } } /// An [`EditGuard`] impl which calls a closure when activated or focus is lost #[autoimpl(Debug ignore self.0)] #[derive(Clone)] -pub struct EditAFL Option, M>(pub F); -impl EditGuard for EditAFL +pub struct EditAFL(pub F); +impl EditGuard for EditAFL where - F: FnMut(&str, &mut EventMgr) -> Option + 'static, + F: FnMut(&str, &mut EventMgr) + 'static, { - type Msg = M; - fn activate(edit: &mut EditField, mgr: &mut EventMgr) -> Option { - (edit.guard.0)(edit.text.text(), mgr) + fn activate(edit: &mut EditField, mgr: &mut EventMgr) { + (edit.guard.0)(edit.text.text(), mgr); } - fn focus_lost(edit: &mut EditField, mgr: &mut EventMgr) -> Option { - (edit.guard.0)(edit.text.text(), mgr) + fn focus_lost(edit: &mut EditField, mgr: &mut EventMgr) { + (edit.guard.0)(edit.text.text(), mgr); } } /// An [`EditGuard`] impl which calls a closure when edited #[autoimpl(Debug ignore self.0)] #[derive(Clone)] -pub struct EditEdit Option, M>(pub F); -impl EditGuard for EditEdit +pub struct EditEdit(pub F); +impl EditGuard for EditEdit where - F: FnMut(&str, &mut EventMgr) -> Option + 'static, + F: FnMut(&str, &mut EventMgr) + 'static, { - type Msg = M; - fn edit(edit: &mut EditField, mgr: &mut EventMgr) -> Option { - (edit.guard.0)(edit.text.text(), mgr) + fn edit(edit: &mut EditField, mgr: &mut EventMgr) { + (edit.guard.0)(edit.text.text(), mgr); } } @@ -160,7 +147,6 @@ where #[derive(Clone)] pub struct EditUpdate(pub F); impl EditGuard for EditUpdate { - type Msg = VoidMsg; fn update(edit: &mut EditField) { (edit.guard.0)(edit.text.text()); } @@ -172,7 +158,7 @@ impl_scope! { /// This is just a wrapper around [`EditField`] adding a frame. #[autoimpl(Deref, DerefMut, HasStr, HasString using self.inner)] #[derive(Clone, Default, Debug)] - #[widget { msg = G::Msg; }] + #[widget] pub struct EditBox { #[widget_core] core: CoreData, @@ -231,14 +217,13 @@ impl EditBox<()> { /// /// The closure `f` is called when the `EditBox` is activated (when the /// "enter" key is pressed). - /// Its result, if not `None`, is the event handler's response. /// /// This method is a parametisation of [`EditBox::with_guard`]. Any guard /// previously assigned to the `EditBox` will be replaced. #[must_use] - pub fn on_activate(self, f: F) -> EditBox> + pub fn on_activate(self, f: F) -> EditBox> where - F: FnMut(&str, &mut EventMgr) -> Option + 'static, + F: FnMut(&str, &mut EventMgr) + 'static, { self.with_guard(EditActivate(f)) } @@ -247,14 +232,13 @@ impl EditBox<()> { /// /// The closure `f` is called when the `EditBox` is activated (when the /// "enter" key is pressed) and when keyboard focus is lost. - /// Its result, if not `None`, is the event handler's response. /// /// This method is a parametisation of [`EditBox::with_guard`]. Any guard /// previously assigned to the `EditBox` will be replaced. #[must_use] - pub fn on_afl(self, f: F) -> EditBox> + pub fn on_afl(self, f: F) -> EditBox> where - F: FnMut(&str, &mut EventMgr) -> Option + 'static, + F: FnMut(&str, &mut EventMgr) + 'static, { self.with_guard(EditAFL(f)) } @@ -262,14 +246,13 @@ impl EditBox<()> { /// Set a guard function, called on edit /// /// The closure `f` is called when the `EditBox` is edited by the user. - /// Its result, if not `None`, is the event handler's response. /// /// This method is a parametisation of [`EditBox::with_guard`]. Any guard /// previously assigned to the `EditBox` will be replaced. #[must_use] - pub fn on_edit(self, f: F) -> EditBox> + pub fn on_edit(self, f: F) -> EditBox> where - F: FnMut(&str, &mut EventMgr) -> Option + 'static, + F: FnMut(&str, &mut EventMgr) + 'static, { self.with_guard(EditEdit(f)) } @@ -450,7 +433,7 @@ impl_scope! { self.required = req.into(); } } - let _ = G::update(self); + G::update(self); TkAction::REDRAW } } @@ -459,32 +442,30 @@ impl_scope! { where G: 'static, { - type Msg = G::Msg; - #[inline] fn focus_on_key_nav(&self) -> bool { false } - fn handle(&mut self, mgr: &mut EventMgr, event: Event) -> Response { + fn handle_event(&mut self, mgr: &mut EventMgr, event: Event) -> Response { fn request_focus(s: &mut EditField, mgr: &mut EventMgr) { if !s.has_key_focus && mgr.request_char_focus(s.id()) { s.has_key_focus = true; + mgr.set_scroll(Scroll::Rect(s.rect())); G::focus_gained(s, mgr); } } match event { Event::Activate | Event::NavFocus(true) => { request_focus(self, mgr); - Response::Focus(self.rect()) + Response::Used } Event::NavFocus(false) => Response::Used, Event::LostCharFocus => { self.has_key_focus = false; mgr.redraw(self.id()); - G::focus_lost(self, mgr) - .map(|msg| msg.into()) - .unwrap_or(Response::Used) + G::focus_lost(self, mgr); + Response::Used } Event::LostSelFocus => { self.selection.set_empty(); @@ -499,8 +480,14 @@ impl_scope! { match self.control_key(mgr, cmd, shift) { EditAction::None => Response::Used, EditAction::Unused => Response::Unused, - EditAction::Activate => Response::used_or_msg(G::activate(self, mgr)), - EditAction::Edit => Response::update_or_msg(G::edit(self, mgr)), + EditAction::Activate => { + G::activate(self, mgr); + Response::Used + } + EditAction::Edit => { + G::edit(self, mgr); + Response::Used + } } } else { Response::Unused @@ -508,7 +495,10 @@ impl_scope! { } Event::ReceivedCharacter(c) => match self.received_char(mgr, c) { false => Response::Unused, - true => Response::update_or_msg(G::edit(self, mgr)), + true => { + G::edit(self, mgr); + Response::Used + } }, Event::Scroll(delta) => { let delta2 = match delta { @@ -628,7 +618,7 @@ impl EditField<()> { input_handler: self.input_handler, guard, }; - let _ = G::update(&mut edit); + G::update(&mut edit); edit } @@ -636,15 +626,14 @@ impl EditField<()> { /// /// The closure `f` is called when the `EditField` is activated (when the /// "enter" key is pressed). - /// Its result, if not `None`, is the event handler's response. /// /// This method is a parametisation of [`EditField::with_guard`]. Any guard /// previously assigned to the `EditField` will be replaced. #[must_use] - pub fn on_activate Option + 'static, M: 'static>( + pub fn on_activate( self, f: F, - ) -> EditField> { + ) -> EditField> { self.with_guard(EditActivate(f)) } @@ -652,30 +641,22 @@ impl EditField<()> { /// /// The closure `f` is called when the `EditField` is activated (when the /// "enter" key is pressed) and when keyboard focus is lost. - /// Its result, if not `None`, is the event handler's response. /// /// This method is a parametisation of [`EditField::with_guard`]. Any guard /// previously assigned to the `EditField` will be replaced. #[must_use] - pub fn on_afl Option + 'static, M: 'static>( - self, - f: F, - ) -> EditField> { + pub fn on_afl(self, f: F) -> EditField> { self.with_guard(EditAFL(f)) } /// Set a guard function, called on edit /// /// The closure `f` is called when the `EditField` is edited by the user. - /// Its result, if not `None`, is the event handler's response. /// /// This method is a parametisation of [`EditField::with_guard`]. Any guard /// previously assigned to the `EditField` will be replaced. #[must_use] - pub fn on_edit Option + 'static, M: 'static>( - self, - f: F, - ) -> EditField> { + pub fn on_edit(self, f: F) -> EditField> { self.with_guard(EditEdit(f)) } @@ -1077,7 +1058,7 @@ impl EditField { } // Pan by given delta. Return `Response::Scrolled` or `Response::Pan(remaining)`. - fn pan_delta(&mut self, mgr: &mut EventMgr, mut delta: Offset) -> Response { + fn pan_delta(&mut self, mgr: &mut EventMgr, mut delta: Offset) -> Response { let new_offset = (self.view_offset - delta) .min(self.max_scroll_offset()) .max(Offset::ZERO); @@ -1086,11 +1067,13 @@ impl EditField { self.view_offset = new_offset; mgr.redraw(self.id()); } - if delta == Offset::ZERO { - Response::Scrolled + + mgr.set_scroll(if delta == Offset::ZERO { + Scroll::Scrolled } else { - Response::Pan(delta) - } + Scroll::Offset(delta) + }); + Response::Used } /// Update view_offset after edit_pos changes diff --git a/crates/kas-widgets/src/frame.rs b/crates/kas-widgets/src/frame.rs index 7974d87bb..163457db9 100644 --- a/crates/kas-widgets/src/frame.rs +++ b/crates/kas-widgets/src/frame.rs @@ -17,7 +17,6 @@ impl_scope! { #[derive(Clone, Debug, Default)] #[widget{ layout = frame(kas::theme::FrameStyle::Frame): self.inner; - msg = ::Msg; }] pub struct Frame { #[widget_core] @@ -47,7 +46,6 @@ impl_scope! { #[derive(Clone, Debug, Default)] #[widget{ layout = frame(kas::theme::FrameStyle::Popup): self.inner; - msg = ::Msg; }] pub struct PopupFrame { #[widget_core] diff --git a/crates/kas-widgets/src/grid.rs b/crates/kas-widgets/src/grid.rs index 7bafb0529..a90076124 100644 --- a/crates/kas-widgets/src/grid.rs +++ b/crates/kas-widgets/src/grid.rs @@ -6,7 +6,7 @@ //! A grid widget use kas::layout::{DynGridStorage, GridChildInfo, GridDimensions}; -use kas::{event, layout, prelude::*}; +use kas::{layout, prelude::*}; use std::ops::{Index, IndexMut}; /// A grid of boxed widgets @@ -15,7 +15,7 @@ use std::ops::{Index, IndexMut}; /// This is parameterised over the handler message type. /// /// See documentation of [`Grid`] type. -pub type BoxGrid = Grid>>; +pub type BoxGrid = Grid>; impl_scope! { /// A generic grid widget @@ -44,15 +44,22 @@ impl_scope! { /// ## Performance /// /// Most operations are `O(n)` in the number of children. + /// + /// # Messages + /// + /// If a handler is specified via [`Self::on_message`] then this handler is + /// called when a child pushes a message. #[autoimpl(Default)] - #[derive(Clone, Debug)] - #[widget { msg = ::Msg; }] + #[autoimpl(Debug ignore self.on_message)] + #[derive(Clone)] + #[widget] pub struct Grid { #[widget_core] core: CoreData, widgets: Vec<(GridChildInfo, W)>, data: DynGridStorage, dim: GridDimensions, + on_message: Option, } impl WidgetChildren for Self { @@ -61,11 +68,11 @@ impl_scope! { self.widgets.len() } #[inline] - fn get_child(&self, index: usize) -> Option<&dyn WidgetConfig> { + fn get_child(&self, index: usize) -> Option<&dyn Widget> { self.widgets.get(index).map(|c| c.1.as_widget()) } #[inline] - fn get_child_mut(&mut self, index: usize) -> Option<&mut dyn WidgetConfig> { + fn get_child_mut(&mut self, index: usize) -> Option<&mut dyn Widget> { self.widgets.get_mut(index).map(|c| c.1.as_widget_mut()) } } @@ -80,27 +87,11 @@ impl_scope! { } } - impl event::SendEvent for Self { - fn send(&mut self, mgr: &mut EventMgr, id: WidgetId, event: Event) -> Response { - if let Some(index) = self.find_child_index(&id) { - if let Some((_, child)) = self.widgets.get_mut(index) { - let r = child.send(mgr, id.clone(), event); - return match Response::try_from(r) { - Ok(r) => r, - Err(msg) => { - log::trace!( - "Received by {} from {}: {:?}", - self.id(), - id, - kas::util::TryFormat(&msg) - ); - Response::Msg(msg) - } - }; - } + impl Handler for Self { + fn handle_message(&mut self, mgr: &mut EventMgr, index: usize) { + if let Some(f) = self.on_message { + f(mgr, index); } - - Response::Unused } } } @@ -116,6 +107,25 @@ impl Grid { grid } + /// Assign a child message handler + /// + /// This handler (if any) is called when a child pushes a message: + /// `f(mgr, index)`, where `index` is the child's index. + #[inline] + pub fn set_on_message(&mut self, f: Option) { + self.on_message = f; + } + + /// Assign a child message handler (inline style) + /// + /// This handler is called when a child pushes a message: + /// `f(mgr, index)`, where `index` is the child's index. + #[inline] + pub fn on_message(mut self, f: fn(&mut EventMgr, usize)) -> Self { + self.on_message = Some(f); + self + } + /// Get grid dimensions /// /// The numbers of rows, columns and spans is determined automatically. diff --git a/crates/kas-widgets/src/lib.rs b/crates/kas-widgets/src/lib.rs index d47b15cd7..e09107c42 100644 --- a/crates/kas-widgets/src/lib.rs +++ b/crates/kas-widgets/src/lib.rs @@ -99,7 +99,7 @@ pub use frame::{Frame, PopupFrame}; pub use grid::{BoxGrid, Grid}; pub use label::{AccelLabel, Label, StrLabel, StringLabel}; pub use list::*; -pub use nav_frame::NavFrame; +pub use nav_frame::{NavFrame, SelectMsg}; pub use progress::ProgressBar; pub use radiobox::{RadioBox, RadioBoxBare, RadioBoxGroup}; pub use scroll::ScrollRegion; diff --git a/crates/kas-widgets/src/list.rs b/crates/kas-widgets/src/list.rs index f708bcf7e..08515f5cd 100644 --- a/crates/kas-widgets/src/list.rs +++ b/crates/kas-widgets/src/list.rs @@ -6,39 +6,10 @@ //! A row or column with run-time adjustable contents use kas::dir::{Down, Right}; -use kas::{event, layout, prelude::*}; +use kas::{layout, prelude::*}; use std::collections::hash_map::{Entry, HashMap}; use std::ops::{Index, IndexMut}; -/// Support for optionally-indexed messages -pub trait FromIndexed { - fn from_indexed(i: usize, t: T) -> Self; -} -impl FromIndexed for T { - #[inline] - fn from_indexed(_: usize, t: T) -> Self { - t - } -} -impl FromIndexed for (usize, T) { - #[inline] - fn from_indexed(i: usize, t: T) -> Self { - (i, t) - } -} -impl FromIndexed for (u32, T) { - #[inline] - fn from_indexed(i: usize, t: T) -> Self { - (i.cast(), t) - } -} -impl FromIndexed for (u64, T) { - #[inline] - fn from_indexed(i: usize, t: T) -> Self { - (i.cast(), t) - } -} - /// A generic row widget /// /// See documentation of [`List`] type. See also the [`row`](crate::row) macro. @@ -49,95 +20,22 @@ pub type Row = List; /// See documentation of [`List`] type. See also the [`column`](crate::column) macro. pub type Column = List; -/// A generic row widget -/// -/// See documentation of [`IndexedList`] type. -pub type IndexedRow = IndexedList; - -/// A generic column widget -/// -/// See documentation of [`IndexedList`] type. -pub type IndexedColumn = IndexedList; - /// A row of boxed widgets /// -/// This is parameterised over handler message type. -/// /// See documentation of [`List`] type. -pub type BoxRow = BoxList; +pub type BoxRow = BoxList; /// A column of boxed widgets /// -/// This is parameterised over handler message type. -/// /// See documentation of [`List`] type. -pub type BoxColumn = BoxList; +pub type BoxColumn = BoxList; /// A row/column of boxed widgets /// -/// This is parameterised over directionality and handler message type. +/// This is parameterised over directionality. /// /// See documentation of [`List`] type. -pub type BoxList = List>>; - -/// A generic row/column widget -/// -/// This type is roughly [`Vec`] but for widgets. Generics: -/// -/// - `D:` [`Directional`] — fixed or run-time direction of layout -/// - `W:` [`Widget`] — type of widget -/// -/// The `List` widget forwards messages from children: `M = ::Msg`. -/// -/// ## Alternatives -/// -/// Some more specific type-defs are available: -/// -/// - [`Row`] fixes the direction to [`Right`] -/// - [`Column`] fixes the direction to [`Down`] -/// - [`row`](crate::row) and [`column`](crate::column) macros -/// - [`BoxList`] is parameterised over the message type `M`, using boxed -/// widgets: `Box>` -/// - [`BoxRow`] and [`BoxColumn`] are variants of [`BoxList`] with fixed direction -/// -/// See also [`IndexedList`] and [`GenericList`] which allow other message types. -/// -/// Where the entries are fixed, also consider custom [`Widget`] implementations. -/// -/// ## Performance -/// -/// Configuring and resizing elements is O(n) in the number of children. -/// Drawing and event handling is O(log n) in the number of children (assuming -/// only a small number are visible at any one time). -pub type List = GenericList::Msg>; - -/// A generic row/column widget -/// -/// This type is roughly [`Vec`] but for widgets. Generics: -/// -/// - `D:` [`Directional`] — fixed or run-time direction of layout -/// - `W:` [`Widget`] — type of widget -/// -/// The `IndexedList` widget forwards messages from children together with the -/// child's index in the list: `(usize, M)` where `M = ::Msg`. -/// -/// ## Alternatives -/// -/// Some more specific type-defs are available: -/// -/// - [`IndexedRow`] fixes the direction to [`Right`] -/// - [`IndexedColumn`] fixes the direction to [`Down`] -/// -/// See also [`List`] and [`GenericList`] which allow other message types. -/// -/// Where the entries are fixed, also consider custom [`Widget`] implementations. -/// -/// ## Performance -/// -/// Configuring and resizing elements is O(n) in the number of children. -/// Drawing and event handling is O(log n) in the number of children (assuming -/// only a small number are visible at any one time). -pub type IndexedList = GenericList::Msg)>; +pub type BoxList = List>; impl_scope! { /// A generic row/column widget @@ -146,33 +44,31 @@ impl_scope! { /// /// - `D:` [`Directional`] — fixed or run-time direction of layout /// - `W:` [`Widget`] — type of widget - /// - `M` — the message type; restricted to `M:` [`FromIndexed`]`` where - /// `M2` is the child's message type; this is usually either `M2` or `(usize, M2)` /// /// ## Alternatives /// /// Some more specific type-defs are available: /// - /// - [`List`] fixes the message type to that of the child widget type `M` - /// - [`IndexedList`] fixes the message type to `(usize, M)` - /// - [`Row`], [`Column`], [`IndexedRow`], [`BoxList`], etc. - /// - /// Where the entries are fixed, also consider custom [`Widget`] implementations. + /// - [`Row`] and [`Column`] fix the direction `D` + /// - [`BoxList`] fixes the widget type to `Box` + /// - [`BoxRow`] and [`BoxColumn`] fix both type parameters /// /// ## Performance /// /// Configuring and resizing elements is O(n) in the number of children. /// Drawing and event handling is O(log n) in the number of children (assuming /// only a small number are visible at any one time). + /// + /// # Messages + /// + /// If a handler is specified via [`Self::on_message`] then this handler is + /// called when a child pushes a message. This allows associating the + /// child's index with a message. #[autoimpl(Clone where W: Clone)] - #[autoimpl(Debug)] + #[autoimpl(Debug ignore self.on_message)] #[autoimpl(Default where D: Default)] - #[widget { msg = M; }] - pub struct GenericList< - D: Directional, - W: Widget, - M: FromIndexed<::Msg> + 'static, - > { + #[widget] + pub struct List { #[widget_core] core: CoreData, layout_store: layout::DynRowStorage, @@ -181,7 +77,7 @@ impl_scope! { size_solved: bool, next: usize, id_map: HashMap, // map key of WidgetId to index - _pd: std::marker::PhantomData, + on_message: Option, } impl WidgetChildren for Self { @@ -190,11 +86,11 @@ impl_scope! { self.widgets.len() } #[inline] - fn get_child(&self, index: usize) -> Option<&dyn WidgetConfig> { + fn get_child(&self, index: usize) -> Option<&dyn Widget> { self.widgets.get(index).map(|w| w.as_widget()) } #[inline] - fn get_child_mut(&mut self, index: usize) -> Option<&mut dyn WidgetConfig> { + fn get_child_mut(&mut self, index: usize) -> Option<&mut dyn Widget> { self.widgets.get_mut(index).map(|w| w.as_widget_mut()) } @@ -255,27 +151,11 @@ impl_scope! { } } - impl event::SendEvent for Self { - fn send(&mut self, mgr: &mut EventMgr, id: WidgetId, event: Event) -> Response { - if let Some(index) = self.find_child_index(&id) { - if let Some(child) = self.widgets.get_mut(index) { - let r = child.send(mgr, id.clone(), event); - return match Response::try_from(r) { - Ok(r) => r, - Err(msg) => { - log::trace!( - "Received by {} from {}: {:?}", - self.id(), - id, - kas::util::TryFormat(&msg) - ); - Response::Msg(FromIndexed::from_indexed(index, msg)) - } - }; - } + impl Handler for Self { + fn handle_message(&mut self, mgr: &mut EventMgr, index: usize) { + if let Some(f) = self.on_message { + f(mgr, index); } - - Response::Unused } } @@ -291,7 +171,7 @@ impl_scope! { } } - impl::Msg> + 'static> GenericList { + impl List { /// Set the direction of contents pub fn set_direction(&mut self, direction: Direction) -> TkAction { self.direction = direction; @@ -326,7 +206,7 @@ impl_scope! { /// Construct a new instance with explicit direction #[inline] pub fn new_with_direction(direction: D, widgets: Vec) -> Self { - GenericList { + List { core: Default::default(), layout_store: Default::default(), widgets, @@ -334,10 +214,29 @@ impl_scope! { size_solved: false, next: 0, id_map: Default::default(), - _pd: Default::default(), + on_message: None, } } + /// Assign a child message handler + /// + /// This handler (if any) is called when a child pushes a message: + /// `f(mgr, index)`, where `index` is the child's index. + #[inline] + pub fn set_on_message(&mut self, f: Option) { + self.on_message = f; + } + + /// Assign a child message handler (inline style) + /// + /// This handler is called when a child pushes a message: + /// `f(mgr, index)`, where `index` is the child's index. + #[inline] + pub fn on_message(mut self, f: fn(&mut EventMgr, usize)) -> Self { + self.on_message = Some(f); + self + } + /// Edit the list of children directly /// /// This may be used to edit children before window construction. It may diff --git a/crates/kas-widgets/src/menu.rs b/crates/kas-widgets/src/menu.rs index a05b5a814..3a3a1b85b 100644 --- a/crates/kas-widgets/src/menu.rs +++ b/crates/kas-widgets/src/menu.rs @@ -17,7 +17,6 @@ //! - [`MenuToggle`] //! - [`Separator`] -use crate::adapter::AdaptWidget; use crate::Separator; use kas::component::Component; use kas::dir::Right; @@ -45,7 +44,7 @@ pub struct SubItems<'a> { pub icon: Option<&'a mut dyn Component>, /// Toggle mark // TODO: should be a component? - pub toggle: Option<&'a mut dyn WidgetConfig>, + pub toggle: Option<&'a mut dyn Widget>, } /// Trait governing menus, sub-menus and menu-entries @@ -90,42 +89,42 @@ pub trait Menu: Widget { } /// A boxed menu -pub type BoxedMenu = Box>; +pub type BoxedMenu = Box; /// Builder for a [`SubMenu`] /// /// Access through [`MenuBar::builder`]. -pub struct SubMenuBuilder<'a, M: 'static> { - menu: &'a mut Vec>, +pub struct SubMenuBuilder<'a> { + menu: &'a mut Vec, } -impl<'a, M: 'static> SubMenuBuilder<'a, M> { +impl<'a> SubMenuBuilder<'a> { /// Append an item #[inline] - pub fn push_item(&mut self, item: BoxedMenu) { + pub fn push_item(&mut self, item: BoxedMenu) { self.menu.push(item); } /// Append an item, chain style #[inline] - pub fn item(mut self, item: BoxedMenu) -> Self { + pub fn item(mut self, item: BoxedMenu) -> Self { self.push_item(item); self } /// Append a [`MenuEntry`] - pub fn push_entry>(&mut self, label: S, msg: M) + pub fn push_entry, M>(&mut self, label: S, msg: M) where - M: Clone + Debug, + M: Clone + Debug + 'static, { self.menu.push(Box::new(MenuEntry::new(label, msg))); } /// Append a [`MenuEntry`], chain style #[inline] - pub fn entry>(mut self, label: S, msg: M) -> Self + pub fn entry, M>(mut self, label: S, msg: M) -> Self where - M: Clone + Debug, + M: Clone + Debug + 'static, { self.push_entry(label, msg); self @@ -134,7 +133,7 @@ impl<'a, M: 'static> SubMenuBuilder<'a, M> { /// Append a [`MenuToggle`] pub fn push_toggle, F>(&mut self, label: S, f: F) where - F: Fn(&mut EventMgr, bool) -> Option + 'static, + F: Fn(&mut EventMgr, bool) + 'static, { self.menu .push(Box::new(MenuToggle::new(label).on_toggle(f))); @@ -144,7 +143,7 @@ impl<'a, M: 'static> SubMenuBuilder<'a, M> { #[inline] pub fn toggle, F>(mut self, label: S, f: F) -> Self where - F: Fn(&mut EventMgr, bool) -> Option + 'static, + F: Fn(&mut EventMgr, bool) + 'static, { self.push_toggle(label, f); self @@ -152,7 +151,7 @@ impl<'a, M: 'static> SubMenuBuilder<'a, M> { /// Append a [`Separator`] pub fn push_separator(&mut self) { - self.menu.push(Box::new(Separator::new().map_void_msg())); + self.menu.push(Box::new(Separator::new())); } /// Append a [`Separator`], chain style @@ -168,7 +167,7 @@ impl<'a, M: 'static> SubMenuBuilder<'a, M> { #[inline] pub fn push_submenu(&mut self, label: impl Into, f: F) where - F: FnOnce(SubMenuBuilder), + F: FnOnce(SubMenuBuilder), { self.push_submenu_with_dir(Right, label, f); } @@ -179,7 +178,7 @@ impl<'a, M: 'static> SubMenuBuilder<'a, M> { #[inline] pub fn submenu(mut self, label: impl Into, f: F) -> Self where - F: FnOnce(SubMenuBuilder), + F: FnOnce(SubMenuBuilder), { self.push_submenu_with_dir(Right, label, f); self @@ -191,7 +190,7 @@ impl<'a, M: 'static> SubMenuBuilder<'a, M> { pub fn push_submenu_with_dir(&mut self, dir: D, label: impl Into, f: F) where D: Directional, - F: FnOnce(SubMenuBuilder), + F: FnOnce(SubMenuBuilder), { let mut menu = Vec::new(); f(SubMenuBuilder { menu: &mut menu }); @@ -206,7 +205,7 @@ impl<'a, M: 'static> SubMenuBuilder<'a, M> { pub fn submenu_with_dir(mut self, dir: D, label: impl Into, f: F) -> Self where D: Directional, - F: FnOnce(SubMenuBuilder), + F: FnOnce(SubMenuBuilder), { self.push_submenu_with_dir(dir, label, f); self diff --git a/crates/kas-widgets/src/menu/menu_entry.rs b/crates/kas-widgets/src/menu/menu_entry.rs index 9753e4c5d..409c1d278 100644 --- a/crates/kas-widgets/src/menu/menu_entry.rs +++ b/crates/kas-widgets/src/menu/menu_entry.rs @@ -14,6 +14,11 @@ use std::fmt::Debug; impl_scope! { /// A standard menu entry + /// + /// # Messages + /// + /// A `MenuEntry` has an associated message value of type `M`. A clone of + /// this value is pushed when the entry is activated. #[derive(Clone, Debug, Default)] #[widget] pub struct MenuEntry { @@ -82,11 +87,12 @@ impl_scope! { } impl Handler for Self { - type Msg = M; - - fn handle(&mut self, _: &mut EventMgr, event: Event) -> Response { + fn handle_event(&mut self, mgr: &mut EventMgr, event: Event) -> Response { match event { - Event::Activate => self.msg.clone().into(), + Event::Activate => { + mgr.push_msg(self.msg.clone()); + Response::Used + } _ => Response::Unused, } } @@ -108,11 +114,11 @@ impl_scope! { #[autoimpl(HasBool using self.checkbox)] #[derive(Clone, Default)] #[widget] - pub struct MenuToggle { + pub struct MenuToggle { #[widget_core] core: CoreData, #[widget] - checkbox: CheckBoxBare, + checkbox: CheckBoxBare, label: Label, layout_list: layout::DynRowStorage, } @@ -146,10 +152,6 @@ impl_scope! { } } - impl Handler for Self { - type Msg = M; - } - impl Menu for Self { fn sub_items(&mut self) -> Option { Some(SubItems { @@ -160,7 +162,7 @@ impl_scope! { } } - impl MenuToggle { + impl MenuToggle { /// Construct a toggleable menu entry with a given `label` #[inline] pub fn new>(label: T) -> Self { @@ -175,13 +177,12 @@ impl_scope! { /// Set event handler `f` /// /// On toggle (through user input events or [`Event::Activate`]) the - /// closure `f` is called. The message generated by `f`, if any, - /// is returned for handling through the parent widget (or other ancestor). + /// closure `f` is called. #[inline] #[must_use] - pub fn on_toggle(self, f: F) -> MenuToggle + pub fn on_toggle(self, f: F) -> Self where - F: Fn(&mut EventMgr, bool) -> Option + 'static, + F: Fn(&mut EventMgr, bool) + 'static, { MenuToggle { core: self.core, @@ -196,12 +197,11 @@ impl_scope! { /// Construct a toggleable menu entry with a given `label` and event handler `f` /// /// On toggle (through user input events or [`Event::Activate`]) the - /// closure `f` is called. The message generated by `f`, if any, - /// is returned for handling through the parent widget (or other ancestor). + /// closure `f` is called. #[inline] pub fn new_on, F>(label: T, f: F) -> Self where - F: Fn(&mut EventMgr, bool) -> Option + 'static, + F: Fn(&mut EventMgr, bool) + 'static, { MenuToggle::new(label).on_toggle(f) } diff --git a/crates/kas-widgets/src/menu/menubar.rs b/crates/kas-widgets/src/menu/menubar.rs index 1c2d3fb98..adf5d7b42 100644 --- a/crates/kas-widgets/src/menu/menubar.rs +++ b/crates/kas-widgets/src/menu/menubar.rs @@ -18,11 +18,11 @@ impl_scope! { /// menus. #[autoimpl(Debug where D: trait)] #[widget] - pub struct MenuBar { + pub struct MenuBar { #[widget_core] core: CoreData, direction: D, - widgets: Vec>, + widgets: Vec>, layout_store: layout::DynRowStorage, delayed_open: Option, } @@ -33,14 +33,14 @@ impl_scope! { /// Note: it appears that `MenuBar::new(..)` causes a type inference error, /// however `MenuBar::<_>::new(..)` does not. Alternatively one may specify /// the direction explicitly: `MenuBar::<_, kas::dir::Right>::new(..)`. - pub fn new(menus: Vec>) -> Self { + pub fn new(menus: Vec>) -> Self { MenuBar::new_with_direction(D::default(), menus) } } impl Self { /// Construct a menubar with explicit direction - pub fn new_with_direction(direction: D, mut menus: Vec>) -> Self { + pub fn new_with_direction(direction: D, mut menus: Vec>) -> Self { for menu in menus.iter_mut() { menu.key_nav = false; } @@ -53,7 +53,7 @@ impl_scope! { } } - pub fn builder() -> MenuBuilder { + pub fn builder() -> MenuBuilder { MenuBuilder { menus: vec![] } } } @@ -64,11 +64,11 @@ impl_scope! { self.widgets.len() } #[inline] - fn get_child(&self, index: usize) -> Option<&dyn WidgetConfig> { + fn get_child(&self, index: usize) -> Option<&dyn Widget> { self.widgets.get(index).map(|w| w.as_widget()) } #[inline] - fn get_child_mut(&mut self, index: usize) -> Option<&mut dyn WidgetConfig> { + fn get_child_mut(&mut self, index: usize) -> Option<&mut dyn Widget> { self.widgets.get_mut(index).map(|w| w.as_widget_mut()) } } @@ -102,10 +102,8 @@ impl_scope! { } } - impl event::Handler for MenuBar { - type Msg = M; - - fn handle(&mut self, mgr: &mut EventMgr, event: Event) -> Response { + impl event::Handler for MenuBar { + fn handle_event(&mut self, mgr: &mut EventMgr, event: Event) -> Response { match event { Event::TimerUpdate(id_code) => { if let Some(id) = self.delayed_open.clone() { @@ -189,7 +187,7 @@ impl_scope! { if !self.rect().contains(coord) { // not on the menubar self.delayed_open = None; - return self.send(mgr, id, Event::Activate); + return mgr.send(self, id, Event::Activate); } Response::Used } @@ -227,32 +225,6 @@ impl_scope! { } } - impl event::SendEvent for Self { - fn send(&mut self, mgr: &mut EventMgr, id: WidgetId, event: Event) -> Response { - if self.eq_id(&id) { - return self.handle(mgr, event); - } else if let Some(index) = id.next_key_after(self.id_ref()) { - if let Some(widget) = self.widgets.get_mut(index) { - return match widget.send(mgr, id.clone(), event.clone()) { - Response::Unused => self.handle(mgr, event), - r => r.try_into().unwrap_or_else(|msg| { - log::trace!( - "Received by {} from {}: {:?}", - self.id(), - id, - kas::util::TryFormat(&msg) - ); - Response::Msg(msg) - }), - }; - } - } - - debug_assert!(false, "SendEvent::send: bad WidgetId"); - Response::Unused - } - } - impl Self { fn set_menu_path(&mut self, mgr: &mut EventMgr, target: Option<&WidgetId>, set_focus: bool) { log::trace!("{}::set_menu_path: target={:?}, set_focus={}", self.identify(), target, set_focus); @@ -267,17 +239,17 @@ impl_scope! { /// Builder for [`MenuBar`] /// /// Access through [`MenuBar::builder`]. -pub struct MenuBuilder { - menus: Vec>, +pub struct MenuBuilder { + menus: Vec>, } -impl MenuBuilder { +impl MenuBuilder { /// Add a new menu /// /// The menu's direction is determined via [`Directional::Flipped`]. pub fn menu(mut self, label: impl Into, f: F) -> Self where - F: FnOnce(SubMenuBuilder), + F: FnOnce(SubMenuBuilder), D::Flipped: Default, { let mut menu = Vec::new(); @@ -287,7 +259,7 @@ impl MenuBuilder { } /// Finish, yielding a [`MenuBar`] - pub fn build(self) -> MenuBar + pub fn build(self) -> MenuBar where D: Default, { diff --git a/crates/kas-widgets/src/menu/submenu.rs b/crates/kas-widgets/src/menu/submenu.rs index 832d4eafc..64fd90d43 100644 --- a/crates/kas-widgets/src/menu/submenu.rs +++ b/crates/kas-widgets/src/menu/submenu.rs @@ -8,7 +8,7 @@ use super::{BoxedMenu, Menu, SubItems}; use crate::PopupFrame; use kas::component::{Component, Label, Mark}; -use kas::event::{self, Command}; +use kas::event::Command; use kas::layout::{self, RulesSetter, RulesSolver}; use kas::prelude::*; use kas::theme::{FrameStyle, MarkStyle, TextClass}; @@ -18,7 +18,7 @@ impl_scope! { /// A sub-menu #[autoimpl(Debug where D: trait)] #[widget] - pub struct SubMenu { + pub struct SubMenu { #[widget_core] core: CoreData, direction: D, @@ -26,33 +26,33 @@ impl_scope! { label: Label, mark: Mark, #[widget] - list: PopupFrame>>, + list: PopupFrame>, popup_id: Option, } impl Self where D: Default { /// Construct a sub-menu #[inline] - pub fn new>(label: S, list: Vec>) -> Self { + pub fn new>(label: S, list: Vec) -> Self { SubMenu::new_with_direction(Default::default(), label, list) } } - impl SubMenu { + impl SubMenu { /// Construct a sub-menu, opening to the right // NOTE: this is used since we can't infer direction of a boxed SubMenu. // Consider only accepting an enum of special menu widgets? // Then we can pass type information. #[inline] - pub fn right>(label: S, list: Vec>) -> Self { + pub fn right>(label: S, list: Vec) -> Self { SubMenu::new(label, list) } } - impl SubMenu { + impl SubMenu { /// Construct a sub-menu, opening downwards #[inline] - pub fn down>(label: S, list: Vec>) -> Self { + pub fn down>(label: S, list: Vec) -> Self { SubMenu::new(label, list) } } @@ -63,7 +63,7 @@ impl_scope! { /// The sub-menu is opened in the `direction` given (contents are always vertical). #[inline] pub fn new_with_direction>( - direction: D, label: S, list: Vec> + direction: D, label: S, list: Vec ) -> Self { SubMenu { core: Default::default(), @@ -94,7 +94,7 @@ impl_scope! { } } - fn handle_dir_key(&mut self, mgr: &mut EventMgr, cmd: Command) -> Response { + fn handle_dir_key(&mut self, mgr: &mut EventMgr, cmd: Command) -> Response { if self.menu_is_open() { if let Some(dir) = cmd.as_direction() { if dir.is_vertical() { @@ -159,10 +159,8 @@ impl_scope! { } } - impl event::Handler for SubMenu { - type Msg = M; - - fn handle(&mut self, mgr: &mut EventMgr, event: Event) -> Response { + impl Handler for Self { + fn handle_event(&mut self, mgr: &mut EventMgr, event: Event) -> Response { match event { Event::Activate => { if self.popup_id.is_none() { @@ -179,27 +177,9 @@ impl_scope! { _ => Response::Unused, } } - } - impl event::SendEvent for Self { - fn send(&mut self, mgr: &mut EventMgr, id: WidgetId, event: Event) -> Response { - if !self.eq_id(&id) { - let r = self.list.send(mgr, id.clone(), event.clone()); - - match r { - Response::Unused => (), - Response::Select => { - self.set_menu_path(mgr, Some(&id), true); - return Response::Used; - } - r @ (Response::Update | Response::Msg(_)) => { - self.close_menu(mgr, true); - return r; - } - r => return r, - } - } - EventMgr::handle_generic(self, mgr, event) + fn handle_message(&mut self, mgr: &mut EventMgr, _: usize) { + self.close_menu(mgr, true); } } @@ -256,9 +236,7 @@ const fn menu_view_row_info(row: u32) -> layout::GridChildInfo { impl_scope! { /// A menu view #[autoimpl(Debug)] - #[widget { - msg=::Msg; - }] + #[widget] struct MenuView { #[widget_core] core: CoreData, @@ -273,11 +251,11 @@ impl_scope! { self.list.len() } #[inline] - fn get_child(&self, index: usize) -> Option<&dyn WidgetConfig> { + fn get_child(&self, index: usize) -> Option<&dyn Widget> { self.list.get(index).map(|w| w.as_widget()) } #[inline] - fn get_child_mut(&mut self, index: usize) -> Option<&mut dyn WidgetConfig> { + fn get_child_mut(&mut self, index: usize) -> Option<&mut dyn Widget> { self.list.get_mut(index).map(|w| w.as_widget_mut()) } } @@ -401,30 +379,6 @@ impl_scope! { } } - impl event::SendEvent for Self { - fn send(&mut self, mgr: &mut EventMgr, id: WidgetId, event: Event) -> Response { - if let Some(index) = self.find_child_index(&id) { - if let Some(child) = self.list.get_mut(index) { - let r = child.send(mgr, id.clone(), event); - return match Response::try_from(r) { - Ok(r) => r, - Err(msg) => { - log::trace!( - "Received by {} from {}: {:?}", - self.id(), - id, - kas::util::TryFormat(&msg) - ); - Response::Msg(msg) - } - }; - } - } - - Response::Unused - } - } - impl Self { /// Construct from a list of menu items pub fn new(list: Vec) -> Self { diff --git a/crates/kas-widgets/src/nav_frame.rs b/crates/kas-widgets/src/nav_frame.rs index 8f170f2d5..07d8daf9b 100644 --- a/crates/kas-widgets/src/nav_frame.rs +++ b/crates/kas-widgets/src/nav_frame.rs @@ -7,11 +7,21 @@ use kas::{event, prelude::*}; +/// Message indicating that a child wishes to be selected +/// +/// Emitted by [`NavFrame`]. +#[derive(Clone, Debug)] +pub struct SelectMsg; + impl_scope! { /// Navigation Frame wrapper /// /// This widget is a wrapper that can be used to make a static widget such as a /// `Label` navigable with the keyboard. + /// + /// # Messages + /// + /// When activated, this widget pushes [`SelectMsg`] to the message stack. #[autoimpl(Deref, DerefMut using self.inner)] #[autoimpl(class_traits using self.inner where W: trait)] #[derive(Clone, Debug, Default)] @@ -38,11 +48,12 @@ impl_scope! { } impl event::Handler for Self { - type Msg = ::Msg; - - fn handle(&mut self, _mgr: &mut EventMgr, event: Event) -> Response { + fn handle_event(&mut self, mgr: &mut EventMgr, event: Event) -> Response { match event { - Event::Activate => Response::Select, + Event::Activate => { + mgr.push_msg(SelectMsg); + Response::Used + } _ => Response::Unused, } } diff --git a/crates/kas-widgets/src/radiobox.rs b/crates/kas-widgets/src/radiobox.rs index a434b1a30..9091c6cae 100644 --- a/crates/kas-widgets/src/radiobox.rs +++ b/crates/kas-widgets/src/radiobox.rs @@ -9,6 +9,7 @@ use super::AccelLabel; use kas::prelude::*; use kas::updatable::{SharedRc, SingleData}; use log::trace; +use std::fmt::Debug; use std::rc::Rc; /// Type of radiobox group @@ -19,19 +20,17 @@ impl_scope! { #[autoimpl(Debug ignore self.on_select)] #[derive(Clone)] #[widget] - pub struct RadioBoxBare { + pub struct RadioBoxBare { #[widget_core] core: CoreData, state: bool, group: RadioBoxGroup, - on_select: Option Option>>, + on_select: Option>, } impl WidgetConfig for Self { fn configure(&mut self, mgr: &mut SetRectMgr) { - for handle in self.group.update_handles().into_iter() { - mgr.update_on_handle(handle, self.id()); - } + self.group.update_on_handles(mgr.ev_state(), self.id_ref()); } fn key_nav(&self) -> bool { @@ -43,37 +42,32 @@ impl_scope! { } impl Handler for Self { - type Msg = M; - #[inline] fn activation_via_press(&self) -> bool { true } - fn handle(&mut self, mgr: &mut EventMgr, event: Event) -> Response { + fn handle_event(&mut self, mgr: &mut EventMgr, event: Event) -> Response { match event { Event::Activate => { if !self.state { trace!("RadioBoxBare: set {}", self.id()); self.state = true; mgr.redraw(self.id()); - if let Some(handle) = self.group.update(Some(self.id())) { - mgr.trigger_update(handle, 0); + self.group.update(mgr, Some(self.id())); + if let Some(f) = self.on_select.as_ref() { + f(mgr); } - Response::update_or_msg(self.on_select.as_ref().and_then(|f| f(mgr))) - } else { - Response::Used } + Response::Used } Event::HandleUpdate { .. } => { if self.state && !self.eq_id(self.group.get_cloned()) { trace!("RadioBoxBare: unset {}", self.id()); self.state = false; mgr.redraw(self.id()); - Response::Update - } else { - Response::Used } + Response::Used } _ => Response::Unused, } @@ -100,7 +94,7 @@ impl_scope! { } } - impl RadioBoxBare { + impl Self { /// Construct a radiobox /// /// All instances of [`RadioBoxBare`] and [`RadioBox`] constructed over the @@ -118,15 +112,14 @@ impl_scope! { /// Set event handler `f` /// /// On selection (through user input events or [`Event::Activate`]) the - /// closure `f` is called. The result of `f` is converted to - /// [`Response::Msg`] or [`Response::Update`] and returned to the parent. + /// closure `f` is called. /// - /// No handler is called on deselection, but [`Response::Update`] is returned. + /// No handler is called on deselection. #[inline] #[must_use] - pub fn on_select(self, f: F) -> RadioBoxBare + pub fn on_select(self, f: F) -> RadioBoxBare where - F: Fn(&mut EventMgr) -> Option + 'static, + F: Fn(&mut EventMgr) + 'static, { RadioBoxBare { core: self.core, @@ -135,23 +128,20 @@ impl_scope! { on_select: Some(Rc::new(f)), } } - } - impl Self { /// Construct a radiobox with given `group` and event handler `f` /// /// All instances of [`RadioBoxBare`] and [`RadioBox`] constructed over the /// same `group` will be considered part of a single group. /// /// On selection (through user input events or [`Event::Activate`]) the - /// closure `f` is called. The result of `f` is converted to - /// [`Response::Msg`] or [`Response::Update`] and returned to the parent. + /// closure `f` is called. /// - /// No handler is called on deselection, but [`Response::Update`] is returned. + /// No handler is called on deselection. #[inline] pub fn new_on(group: RadioBoxGroup, f: F) -> Self where - F: Fn(&mut EventMgr) -> Option + 'static, + F: Fn(&mut EventMgr) + 'static, { RadioBoxBare::new(group).on_select(f) } @@ -169,9 +159,7 @@ impl_scope! { /// Note: state will not update until the next draw. #[inline] pub fn unset_all(&self, mgr: &mut EventMgr) { - if let Some(handle) = self.group.update(None) { - mgr.trigger_update(handle, 0); - } + self.group.update(mgr, None); } } @@ -196,26 +184,22 @@ impl_scope! { find_id = Some(self.radiobox.id()); layout = row: *; }] - pub struct RadioBox { + pub struct RadioBox { #[widget_core] core: CoreData, #[widget] - radiobox: RadioBoxBare, + radiobox: RadioBoxBare, #[widget] label: AccelLabel, } - impl Handler for Self where M: From { - type Msg = M; - } - impl WidgetConfig for Self { fn configure(&mut self, mgr: &mut SetRectMgr) { mgr.add_accel_keys(self.radiobox.id_ref(), self.label.keys()); } } - impl RadioBox { + impl Self { /// Construct a radiobox with a given `label` and `group` /// /// RadioBox labels are optional; if no label is desired, use an empty @@ -235,15 +219,14 @@ impl_scope! { /// Set event handler `f` /// /// On selection (through user input events or [`Event::Activate`]) the - /// closure `f` is called. The result of `f` is converted to - /// [`Response::Msg`] or [`Response::Update`] and returned to the parent. + /// closure `f` is called. /// - /// No handler is called on deselection, but [`Response::Update`] is returned. + /// No handler is called on deselection. #[inline] #[must_use] - pub fn on_select(self, f: F) -> RadioBox + pub fn on_select(self, f: F) -> RadioBox where - F: Fn(&mut EventMgr) -> Option + 'static, + F: Fn(&mut EventMgr) + 'static, { RadioBox { core: self.core, @@ -251,9 +234,7 @@ impl_scope! { label: self.label, } } - } - impl Self { /// Construct a radiobox with given `label`, `group` and event handler `f` /// /// RadioBox labels are optional; if no label is desired, use an empty @@ -263,14 +244,13 @@ impl_scope! { /// same `group` will be considered part of a single group. /// /// On selection (through user input events or [`Event::Activate`]) the - /// closure `f` is called. The result of `f` is converted to - /// [`Response::Msg`] or [`Response::Update`] and returned to the parent. + /// closure `f` is called. /// - /// No handler is called on deselection, but [`Response::Update`] is returned. + /// No handler is called on deselection. #[inline] pub fn new_on, F>(label: T, group: RadioBoxGroup, f: F) -> Self where - F: Fn(&mut EventMgr) -> Option + 'static, + F: Fn(&mut EventMgr) + 'static, { RadioBox::new(label, group).on_select(f) } @@ -284,15 +264,16 @@ impl_scope! { /// same `group` will be considered part of a single group. /// /// On selection (through user input events or [`Event::Activate`]) a clone - /// of `msg` is returned to the parent widget via [`Response::Msg`]. + /// of `msg` is returned to the parent widget via [`EventMgr::push_msg`]. /// - /// No handler is called on deselection, but [`Response::Update`] is returned. + /// No handler is called on deselection. #[inline] - pub fn new_msg>(label: S, group: RadioBoxGroup, msg: M) -> Self + pub fn new_msg(label: S, group: RadioBoxGroup, msg: M) -> Self where - M: Clone, + S: Into, + M: Clone + Debug + 'static, { - Self::new_on(label, group, move |_| Some(msg.clone())) + Self::new_on(label, group, move |mgr| mgr.push_msg(msg.clone())) } /// Set the initial state of the radiobox. diff --git a/crates/kas-widgets/src/scroll.rs b/crates/kas-widgets/src/scroll.rs index d05d66608..04d31f279 100644 --- a/crates/kas-widgets/src/scroll.rs +++ b/crates/kas-widgets/src/scroll.rs @@ -6,7 +6,7 @@ //! Scroll region use super::Scrollable; -use kas::event::{self, components::ScrollComponent}; +use kas::event::{components::ScrollComponent, Scroll}; use kas::prelude::*; use kas::theme::TextClass; use std::fmt::Debug; @@ -28,7 +28,7 @@ impl_scope! { #[autoimpl(Deref, DerefMut using self.inner)] #[autoimpl(class_traits using self.inner where W: trait)] #[derive(Clone, Debug, Default)] - #[widget { msg = ::Msg; }] + #[widget] pub struct ScrollRegion { #[widget_core] core: CoreData, @@ -105,7 +105,7 @@ impl_scope! { let line_height = size_mgr.line_height(TextClass::Label(false)); rules.reduce_min_to(line_height); - // We use a to contain the content margin within the scrollable area. + // We use a frame to contain the content margin within the scrollable area. let frame = kas::layout::FrameRules::new(0, 0, 0, (0, 0)); let (rules, offset, size) = frame.surround_with_margin(rules); self.offset.set_component(axis, offset); @@ -142,38 +142,13 @@ impl_scope! { } } - impl event::SendEvent for Self { - fn send(&mut self, mgr: &mut EventMgr, id: WidgetId, event: Event) -> Response { - if self.inner.id().is_ancestor_of(&id) { - let child_event = self.scroll.offset_event(event.clone()); - match self.inner.send(mgr, id, child_event) { - Response::Unused => (), - Response::Pan(delta) => { - return match self.scroll_by_delta(mgr, delta) { - delta if delta == Offset::ZERO => Response::Scrolled, - delta => Response::Pan(delta), - }; - } - Response::Focus(rect) => { - let (rect, action) = self.scroll.focus_rect(rect, self.core.rect); - *mgr |= action; - return Response::Focus(rect); - } - r => return r, - } - } else { - debug_assert!(self.eq_id(id), "SendEvent::send: bad WidgetId"); - }; - - let (action, response) = - self.scroll - .scroll_by_event(mgr, event, self.id(), self.core.rect.size); - if !action.is_empty() { - *mgr |= action; - Response::Focus(self.core.rect) - } else { - response.void_into() - } + impl Handler for Self { + fn handle_event(&mut self, mgr: &mut EventMgr, event: Event) -> Response { + self.scroll.scroll_by_event(mgr, event, self.id(), self.core.rect) + } + + fn handle_scroll(&mut self, mgr: &mut EventMgr, scroll: Scroll) -> Scroll { + self.scroll.scroll(mgr, self.rect(), scroll) } } } diff --git a/crates/kas-widgets/src/scroll_label.rs b/crates/kas-widgets/src/scroll_label.rs index a027d80ca..78f731240 100644 --- a/crates/kas-widgets/src/scroll_label.rs +++ b/crates/kas-widgets/src/scroll_label.rs @@ -7,7 +7,7 @@ use super::Scrollable; use kas::event::components::{TextInput, TextInputAction}; -use kas::event::{self, Command, ScrollDelta}; +use kas::event::{self, Command, Scroll, ScrollDelta}; use kas::geom::Vec2; use kas::prelude::*; use kas::text::format::{EditableText, FormattableText}; @@ -90,18 +90,21 @@ impl_scope! { } // Pan by given delta. Return `Response::Scrolled` or `Response::Pan(remaining)`. - fn pan_delta(&mut self, mgr: &mut EventMgr, mut delta: Offset) -> Response { + fn pan_delta(&mut self, mgr: &mut EventMgr, mut delta: Offset) -> Response { let new_offset = (self.view_offset - delta).min(self.max_scroll_offset()).max(Offset::ZERO); if new_offset != self.view_offset { delta -= self.view_offset - new_offset; self.view_offset = new_offset; mgr.redraw(self.id()); } - if delta == Offset::ZERO { - Response::Scrolled - } else { - Response::Pan(delta) - } + + mgr.set_scroll(if delta == Offset::ZERO { + Scroll::Scrolled + } else { + Scroll::Offset(delta) + } + ); + Response::Used } /// Update view_offset after edit_pos changes @@ -142,9 +145,7 @@ impl_scope! { } impl event::Handler for Self { - type Msg = VoidMsg; - - fn handle(&mut self, mgr: &mut EventMgr, event: Event) -> Response { + fn handle_event(&mut self, mgr: &mut EventMgr, event: Event) -> Response { match event { Event::Command(cmd, _) => match cmd { Command::Escape | Command::Deselect if !self.selection.is_empty() => { diff --git a/crates/kas-widgets/src/scrollbar.rs b/crates/kas-widgets/src/scrollbar.rs index 4bd29a376..1a701ddf2 100644 --- a/crates/kas-widgets/src/scrollbar.rs +++ b/crates/kas-widgets/src/scrollbar.rs @@ -8,17 +8,21 @@ use std::fmt::Debug; use super::{DragHandle, ScrollRegion}; -use kas::{event, prelude::*}; +use kas::event::{MsgPressFocus, Scroll}; +use kas::prelude::*; impl_scope! { /// A scroll bar /// /// Scroll bars allow user-input of a value between 0 and a defined maximum, /// and allow the size of the handle to be specified. + /// + /// # Messages + /// + /// On value change, pushes a value of type `i32`. #[derive(Clone, Debug, Default)] #[widget{ hover_highlight = true; - msg = i32; }] pub struct ScrollBar { #[widget_core] @@ -244,28 +248,31 @@ impl_scope! { } } - impl event::SendEvent for Self { - fn send(&mut self, mgr: &mut EventMgr, id: WidgetId, event: Event) -> Response { - let offset = if self.eq_id(&id) { - match event { - Event::PressStart { source, coord, .. } => { - self.handle.handle_press_on_track(mgr, source, coord) + impl Handler for Self { + fn handle_event(&mut self, mgr: &mut EventMgr, event: Event) -> Response { + match event { + Event::PressStart { source, coord, .. } => { + let offset = self.handle.handle_press_on_track(mgr, source, coord); + let (offset, action) = self.handle.set_offset(offset); + *mgr |= action; + if self.set_offset(offset) { + mgr.push_msg(self.value); } - _ => return Response::Unused, - } - } else { - debug_assert!(self.handle.id().is_ancestor_of(&id)); - match self.handle.send(mgr, id, event).try_into() { - Ok(res) => return res, - Err(offset) => offset, + Response::Used } - }; + _ => Response::Unused + } + } - if self.set_offset(offset) { - mgr.redraw(self.handle.id()); - Response::Msg(self.value) - } else { - Response::Used + fn handle_message(&mut self, mgr: &mut EventMgr, _: usize) { + if let Some(MsgPressFocus) = mgr.try_pop_msg() { + // Useless to us, but we should remove it. + } else if let Some(offset) = mgr.try_pop_msg() { + let (offset, action) = self.handle.set_offset(offset); + *mgr |= action; + if self.set_offset(offset) { + mgr.push_msg(self.value); + } } } } @@ -276,10 +283,7 @@ impl_scope! { /// This trait should be implemented by widgets supporting scrolling, enabling /// a parent (such as the [`ScrollBars`] wrapper) to add controls. /// -/// The implementing widget may use event handlers to scroll itself (e.g. in -/// reaction to a mouse wheel or touch-drag), but when doing so should emit -/// [`Response::Focus`] to notify any wrapper of the new position (usually with -/// `Response::Focus(self.rect())`). +/// If the widget scrolls itself it should set a scroll action via [`EventMgr::set_scroll`]. pub trait Scrollable: Widget { /// Given size `size`, returns whether `(horiz, vert)` scrolling is required fn scroll_axes(&self, size: Size) -> (bool, bool); @@ -307,16 +311,6 @@ pub trait Scrollable: Widget { /// The offset is clamped to the available scroll range and applied. The /// resulting offset is returned. fn set_scroll_offset(&mut self, mgr: &mut EventMgr, offset: Offset) -> Offset; - - /// Scroll by a delta - /// - /// Returns the remaining (unused) delta. - #[inline] - fn scroll_by_delta(&mut self, mgr: &mut EventMgr, delta: Offset) -> Offset { - let old_offset = self.scroll_offset(); - let new_offset = self.set_scroll_offset(mgr, old_offset - delta); - delta - old_offset + new_offset - } } impl_scope! { @@ -330,7 +324,6 @@ impl_scope! { #[derive(Clone, Debug, Default)] #[widget{ derive = self.0; - msg = ::Msg; }] pub struct ScrollBarRegion(ScrollBars>); @@ -426,7 +419,7 @@ impl_scope! { #[autoimpl(Deref, DerefMut using self.inner)] #[autoimpl(class_traits using self.inner where W: trait)] #[derive(Clone, Debug, Default)] - #[widget { msg = ::Msg; }] + #[widget] pub struct ScrollBars { #[widget_core] core: CoreData, @@ -636,40 +629,26 @@ impl_scope! { } } - impl event::SendEvent for Self { - fn send(&mut self, mgr: &mut EventMgr, id: WidgetId, event: Event) -> Response { - match self.find_child_index(&id) { - Some(widget_index![self.horiz_bar]) => self.horiz_bar - .send(mgr, id, event) - .try_into() - .unwrap_or_else(|msg| { - let offset = Offset(msg, self.inner.scroll_offset().1); - self.inner.set_scroll_offset(mgr, offset); - Response::Used - }), - Some(widget_index![self.vert_bar]) => self.vert_bar - .send(mgr, id, event) - .try_into() - .unwrap_or_else(|msg| { - let offset = Offset(self.inner.scroll_offset().0, msg); - self.inner.set_scroll_offset(mgr, offset); - Response::Used - }), - Some(widget_index![self.inner]) => { - let r = self.inner.send(mgr, id, event); - // We assume the inner already updated its positions; this is just to set bars - if matches!(r, Response::Pan(_) | Response::Scrolled | Response::Focus(_)) { - let offset = self.inner.scroll_offset(); - *mgr |= self.horiz_bar.set_value(offset.0) | self.vert_bar.set_value(offset.1); - } - r + impl Handler for Self { + fn handle_message(&mut self, mgr: &mut EventMgr, index: usize) { + if index == widget_index![self.horiz_bar] { + if let Some(msg) = mgr.try_pop_msg() { + let offset = Offset(msg, self.inner.scroll_offset().1); + self.inner.set_scroll_offset(mgr, offset); } - _ if self.eq_id(id) => self.handle(mgr, event), - _ => { - debug_assert!(false, "SendEvent::send: bad WidgetId"); - Response::Unused + } else if index == widget_index![self.vert_bar] { + if let Some(msg) = mgr.try_pop_msg() { + let offset = Offset(self.inner.scroll_offset().0, msg); + self.inner.set_scroll_offset(mgr, offset); } } } + + fn handle_scroll(&mut self, mgr: &mut EventMgr, scroll: Scroll) -> Scroll { + // We assume the inner already updated its positions; this is just to set bars + let offset = self.inner.scroll_offset(); + *mgr |= self.horiz_bar.set_value(offset.0) | self.vert_bar.set_value(offset.1); + scroll + } } } diff --git a/crates/kas-widgets/src/slider.rs b/crates/kas-widgets/src/slider.rs index affb36f4c..792e4916d 100644 --- a/crates/kas-widgets/src/slider.rs +++ b/crates/kas-widgets/src/slider.rs @@ -10,7 +10,7 @@ use std::ops::{Add, Sub}; use std::time::Duration; use super::DragHandle; -use kas::event::{self, Command}; +use kas::event::{Command, MsgPressFocus, Scroll}; use kas::prelude::*; /// Requirements on type used by [`Slider`] @@ -86,11 +86,14 @@ impl_scope! { /// A slider /// /// Sliders allow user input of a value from a fixed range. + /// + /// # Messages + /// + /// On value change, pushes a value of type `T`. #[derive(Clone, Debug, Default)] #[widget{ key_nav = true; hover_highlight = true; - msg = T; }] pub struct Slider { #[widget_core] @@ -161,15 +164,23 @@ impl_scope! { self.value } + #[inline] + #[allow(clippy::neg_cmp_op_on_partial_ord)] + fn clamp_value(&self, value: T) -> T { + if !(value >= self.range.0) { + self.range.0 + } else if !(value <= self.range.1) { + self.range.1 + } else { + value + } + } + /// Set the value /// /// Returns [`TkAction::REDRAW`] if a redraw is required. - pub fn set_value(&mut self, mut value: T) -> TkAction { - if value < self.range.0 { - value = self.range.0; - } else if value > self.range.1 { - value = self.range.1; - } + pub fn set_value(&mut self, value: T) -> TkAction { + let value = self.clamp_value(value); if value == self.value { TkAction::empty() } else { @@ -194,9 +205,7 @@ impl_scope! { } } - // true if not equal to old value - #[allow(clippy::neg_cmp_op_on_partial_ord)] - fn set_offset(&mut self, offset: Offset) -> bool { + fn set_offset_and_push_msg(&mut self, mgr: &mut EventMgr, offset: Offset) { let b = self.range.1 - self.range.0; let max_offset = self.handle.max_offset(); let mut a = match self.direction.is_vertical() { @@ -206,19 +215,12 @@ impl_scope! { if self.direction.is_reversed() { a = b - a; } - let value = a + self.range.0; - let value = if !(value >= self.range.0) { - self.range.0 - } else if !(value <= self.range.1) { - self.range.1 - } else { - value - }; + let value = self.clamp_value(a + self.range.0); if value != self.value { self.value = value; - return true; + *mgr |= self.handle.set_offset(self.offset()).1; + mgr.push_msg(self.value); } - false } } @@ -259,76 +261,59 @@ impl_scope! { } } - impl event::SendEvent for Self { - fn send(&mut self, mgr: &mut EventMgr, id: WidgetId, event: Event) -> Response { - let offset = if self.handle.id().is_ancestor_of(&id) { - match event { - Event::NavFocus(key_focus) => { - mgr.set_nav_focus(self.id(), key_focus); - return Response::Used; // NavFocus event will be sent to self - } - event => match self.handle.send(mgr, id, event).try_into() { - Ok(res) => return res, - Err(offset) => offset, - }, + impl Handler for Self { + fn handle_event(&mut self, mgr: &mut EventMgr, event: Event) -> Response { + match event { + Event::NavFocus(true) => { + mgr.set_scroll(Scroll::Rect(self.rect())); } - } else { - debug_assert_eq!(id, self.id()); - match event { - Event::NavFocus(true) => { - return Response::Focus(self.rect()); - } - Event::NavFocus(false) => { - return Response::Used; - } - Event::Command(cmd, _) => { - let rev = self.direction.is_reversed(); - let v = match cmd { - Command::Left | Command::Up => match rev { - false => self.value - self.step, - true => self.value + self.step, - }, - Command::Right | Command::Down => match rev { - false => self.value + self.step, - true => self.value - self.step, - }, - Command::PageUp | Command::PageDown => { - // Generics makes this easier than constructing a literal and multiplying! - let mut x = self.step + self.step; - x = x + x; - x = x + x; - x = x + x; - match rev == (cmd == Command::PageDown) { - false => self.value + x, - true => self.value - x, - } + Event::Command(cmd, _) => { + let rev = self.direction.is_reversed(); + let v = match cmd { + Command::Left | Command::Up => match rev { + false => self.value - self.step, + true => self.value + self.step, + }, + Command::Right | Command::Down => match rev { + false => self.value + self.step, + true => self.value - self.step, + }, + Command::PageUp | Command::PageDown => { + // Generics makes this easier than constructing a literal and multiplying! + let mut x = self.step + self.step; + x = x + x; + x = x + x; + x = x + x; + match rev == (cmd == Command::PageDown) { + false => self.value + x, + true => self.value - x, } - Command::Home => self.range.0, - Command::End => self.range.1, - _ => return Response::Unused, - }; - let action = self.set_value(v); - return if action.is_empty() { - Response::Used - } else { - mgr.send_action(action); - Response::Msg(self.value) - }; - } - Event::PressStart { source, coord, .. } => { - self.handle.handle_press_on_track(mgr, source, coord) + } + Command::Home => self.range.0, + Command::End => self.range.1, + _ => return Response::Unused, + }; + let action = self.set_value(v); + if !action.is_empty() { + mgr.send_action(action); + mgr.push_msg(self.value); } - _ => return Response::Unused, } - }; + Event::PressStart { source, coord, .. } => { + let offset = self.handle.handle_press_on_track(mgr, source, coord); + self.set_offset_and_push_msg(mgr, offset); + } + _ => return Response::Unused, + } + Response::Used + } - let r = if self.set_offset(offset) { - Response::Msg(self.value) - } else { - Response::Used - }; - *mgr |= self.handle.set_offset(self.offset()).1; - r + fn handle_message(&mut self, mgr: &mut EventMgr, _: usize) { + if let Some(MsgPressFocus) = mgr.try_pop_msg() { + mgr.set_nav_focus(self.id(), false); + } else if let Some(offset) = mgr.try_pop_msg() { + self.set_offset_and_push_msg(mgr, offset); + } } } } diff --git a/crates/kas-widgets/src/splitter.rs b/crates/kas-widgets/src/splitter.rs index d7790fe55..32b9203e8 100644 --- a/crates/kas-widgets/src/splitter.rs +++ b/crates/kas-widgets/src/splitter.rs @@ -11,7 +11,7 @@ use std::ops::{Index, IndexMut}; use super::DragHandle; use kas::dir::{Down, Right}; -use kas::event; +use kas::event::MsgPressFocus; use kas::layout::{self, RulesSetter, RulesSolver}; use kas::prelude::*; @@ -30,42 +30,38 @@ pub type ColumnSplitter = Splitter; /// This is parameterised over handler message type. /// /// See documentation of [`Splitter`] type. -pub type BoxRowSplitter = BoxSplitter; +pub type BoxRowSplitter = BoxSplitter; /// A column of boxed widgets /// /// This is parameterised over handler message type. /// /// See documentation of [`Splitter`] type. -pub type BoxColumnSplitter = BoxSplitter; +pub type BoxColumnSplitter = BoxSplitter; /// A row/column of boxed widgets /// -/// This is parameterised over directionality and handler message type. +/// This is parameterised over directionality. /// /// See documentation of [`Splitter`] type. -pub type BoxSplitter = Splitter>>; +pub type BoxSplitter = Splitter>; /// A row of widget references /// -/// This is parameterised over handler message type. -/// /// See documentation of [`Splitter`] type. -pub type RefRowSplitter<'a, M> = RefSplitter<'a, Right, M>; +pub type RefRowSplitter<'a> = RefSplitter<'a, Right>; /// A column of widget references /// -/// This is parameterised over handler message type. -/// /// See documentation of [`Splitter`] type. -pub type RefColumnSplitter<'a, M> = RefSplitter<'a, Down, M>; +pub type RefColumnSplitter<'a> = RefSplitter<'a, Down>; /// A row/column of widget references /// -/// This is parameterised over directionality and handler message type. +/// This is parameterised over directionality. /// /// See documentation of [`Splitter`] type. -pub type RefSplitter<'a, D, M> = Splitter>; +pub type RefSplitter<'a, D> = Splitter; impl_scope! { /// A resizable row/column widget @@ -73,7 +69,7 @@ impl_scope! { /// Similar to [`crate::List`] but with draggable handles between items. // TODO: better doc #[derive(Clone, Default, Debug)] - #[widget { msg = ::Msg; }] + #[widget] pub struct Splitter { #[widget_core] core: CoreData, @@ -106,7 +102,7 @@ impl_scope! { let key = self.next; self.next += 1; if let Entry::Vacant(entry) = self.id_map.entry(key) { - entry.insert(index); + entry.insert(child_index); return self.id_ref().make_child(key); } } @@ -119,7 +115,7 @@ impl_scope! { self.widgets.len() + self.handles.len() } #[inline] - fn get_child(&self, index: usize) -> Option<&dyn WidgetConfig> { + fn get_child(&self, index: usize) -> Option<&dyn Widget> { if (index & 1) != 0 { self.handles.get(index >> 1).map(|w| w.as_widget()) } else { @@ -127,7 +123,7 @@ impl_scope! { } } #[inline] - fn get_child_mut(&mut self, index: usize) -> Option<&mut dyn WidgetConfig> { + fn get_child_mut(&mut self, index: usize) -> Option<&mut dyn Widget> { if (index & 1) != 0 { self.handles.get_mut(index >> 1).map(|w| w.as_widget_mut()) } else { @@ -267,30 +263,18 @@ impl_scope! { } } - impl event::SendEvent for Self { - fn send(&mut self, mgr: &mut EventMgr, id: WidgetId, event: Event) -> Response { - if !self.widgets.is_empty() { - if let Some(index) = self.find_child_index(&id) { - if (index & 1) == 0 { - if let Some(w) = self.widgets.get_mut(index >> 1) { - return w.send(mgr, id, event); - } - } else { - let n = index >> 1; - if let Some(h) = self.handles.get_mut(n) { - let r = h.send(mgr, id, event); - return r.try_into().unwrap_or_else(|_| { - // Message is the new offset relative to the track; - // the handle has already adjusted its position - mgr.set_rect_mgr(|mgr| self.adjust_size(mgr, n)); - Response::Used - }); - } - } + impl Handler for Self { + fn handle_message(&mut self, mgr: &mut EventMgr, index: usize) { + if (index & 1) == 1 { + if let Some(MsgPressFocus) = mgr.try_pop_msg() { + // Useless to us, but we should remove it. + } else if let Some(offset) = mgr.try_pop_msg::() { + let n = index >> 1; + assert!(n < self.handles.len()); + *mgr |= self.handles[n].set_offset(offset).1; + mgr.set_rect_mgr(|mgr| self.adjust_size(mgr, n)); } } - - Response::Unused } } diff --git a/crates/kas-widgets/src/stack.rs b/crates/kas-widgets/src/stack.rs index 214eae356..54bdadfb4 100644 --- a/crates/kas-widgets/src/stack.rs +++ b/crates/kas-widgets/src/stack.rs @@ -5,7 +5,7 @@ //! A stack -use kas::{event, prelude::*}; +use kas::prelude::*; use std::collections::hash_map::{Entry, HashMap}; use std::fmt::Debug; use std::ops::{Index, IndexMut, Range}; @@ -13,12 +13,12 @@ use std::ops::{Index, IndexMut, Range}; /// A stack of boxed widgets /// /// This is a parametrisation of [`Stack`]. -pub type BoxStack = Stack>>; +pub type BoxStack = Stack>; /// A stack of widget references /// /// This is a parametrisation of [`Stack`]. -pub type RefStack<'a, M> = Stack<&'a mut dyn Widget>; +pub type RefStack<'a> = Stack<&'a mut dyn Widget>; impl_scope! { /// A stack of widgets @@ -34,7 +34,7 @@ impl_scope! { /// or may be limited: see [`Self::set_size_limit`]. Drawing is `O(1)`, and /// so is event handling in the expected case. #[derive(Clone, Default, Debug)] - #[widget { msg = ::Msg; }] + #[widget] pub struct Stack { #[widget_core] core: CoreData, @@ -77,11 +77,11 @@ impl_scope! { self.widgets.len() } #[inline] - fn get_child(&self, index: usize) -> Option<&dyn WidgetConfig> { + fn get_child(&self, index: usize) -> Option<&dyn Widget> { self.widgets.get(index).map(|w| w.as_widget()) } #[inline] - fn get_child_mut(&mut self, index: usize) -> Option<&mut dyn WidgetConfig> { + fn get_child_mut(&mut self, index: usize) -> Option<&mut dyn Widget> { self.widgets.get_mut(index).map(|w| w.as_widget_mut()) } @@ -140,24 +140,6 @@ impl_scope! { } } - impl event::SendEvent for Self { - fn send(&mut self, mgr: &mut EventMgr, id: WidgetId, event: Event) -> Response { - if let Some(index) = self.find_child_index(&id) { - if let Some(child) = self.widgets.get_mut(index) { - return match child.send(mgr, id, event) { - Response::Focus(rect) => { - mgr.set_rect_mgr(|mgr| self.set_active(mgr, index)); - Response::Focus(rect) - } - r => r, - }; - } - } - - Response::Unused - } - } - impl Index for Self { type Output = W; diff --git a/crates/kas-widgets/src/view/driver.rs b/crates/kas-widgets/src/view/driver.rs index db607a14e..a8104a41c 100644 --- a/crates/kas-widgets/src/view/driver.rs +++ b/crates/kas-widgets/src/view/driver.rs @@ -30,10 +30,8 @@ use std::marker::PhantomData; /// - [`Default`](struct@Default) will choose a sensible widget to view the data /// - [`DefaultNav`] will choose a sensible widget to view the data pub trait Driver: Debug + 'static { - /// Type of message sent by the widget - type Msg; /// Type of the widget used to view data - type Widget: kas::Widget; + type Widget: kas::Widget; /// Construct a new widget with no data /// @@ -45,21 +43,6 @@ pub trait Driver: Debug + 'static { /// The widget may expect `configure` to be called at least once before data /// is set and to have `set_rect` called after each time data is set. fn set(&self, widget: &mut Self::Widget, data: T) -> TkAction; - /// Get data from the view - /// - /// This method optionally constructs and returns `data` from the widget. - /// It is only useful on interactive widgets (e.g. a slider or edit box). - /// - /// When the constructed widget emits [`Response::Update`] or - /// [`Response::Msg`], the "view" (e.g. `SingleView`) calls this method; if - /// a data item is returned, then then it is passed to the data model's - /// `update` method to update the model. - /// - /// Note that, additionally, when [`Response::Msg`] is returned, - /// [`kas::updatable::Updatable`] may be used to observe the message. - /// Often it will be sufficient to implement custom handling/update logic - /// in only one of these places. - fn get(&self, widget: &Self::Widget) -> Option; } /// Default view widget constructor @@ -85,7 +68,6 @@ pub struct DefaultNav; macro_rules! impl_via_to_string { ($t:ty) => { impl Driver<$t> for Default { - type Msg = VoidMsg; type Widget = Label; fn make(&self) -> Self::Widget where $t: std::default::Default { Label::new("".to_string()) @@ -93,10 +75,8 @@ macro_rules! impl_via_to_string { fn set(&self, widget: &mut Self::Widget, data: $t) -> TkAction { widget.set_string(data.to_string()) } - fn get(&self, _: &Self::Widget) -> Option<$t> { None } } impl Driver<$t> for DefaultNav { - type Msg = VoidMsg; type Widget = NavFrame>; fn make(&self) -> Self::Widget where $t: std::default::Default { NavFrame::new(Label::new("".to_string())) @@ -104,7 +84,6 @@ macro_rules! impl_via_to_string { fn set(&self, widget: &mut Self::Widget, data: $t) -> TkAction { widget.set_string(data.to_string()) } - fn get(&self, _: &Self::Widget) -> Option<$t> { None } } }; ($t:ty, $($tt:ty),+) => { @@ -118,31 +97,23 @@ impl_via_to_string!(u8, u16, u32, u64, u128, usize); impl_via_to_string!(f32, f64); impl Driver for Default { - type Msg = VoidMsg; - type Widget = CheckBoxBare; + type Widget = CheckBoxBare; fn make(&self) -> Self::Widget { CheckBoxBare::new().with_editable(false) } fn set(&self, widget: &mut Self::Widget, data: bool) -> TkAction { widget.set_bool(data) } - fn get(&self, widget: &Self::Widget) -> Option { - Some(widget.get_bool()) - } } impl Driver for DefaultNav { - type Msg = VoidMsg; - type Widget = CheckBoxBare; + type Widget = CheckBoxBare; fn make(&self) -> Self::Widget { CheckBoxBare::new().with_editable(false) } fn set(&self, widget: &mut Self::Widget, data: bool) -> TkAction { widget.set_bool(data) } - fn get(&self, widget: &Self::Widget) -> Option { - Some(widget.get_bool()) - } } /// Custom view widget constructor @@ -165,7 +136,6 @@ impl Driver for Widget<>::Widget> where Default: Driver, { - type Msg = >::Msg; type Widget = >::Widget; fn make(&self) -> Self::Widget { Default.make() @@ -173,13 +143,9 @@ where fn set(&self, widget: &mut Self::Widget, data: T) -> TkAction { Default.set(widget, data) } - fn get(&self, widget: &Self::Widget) -> Option { - Default.get(widget) - } } impl Driver for Widget> { - type Msg = G::Msg; type Widget = EditField; fn make(&self) -> Self::Widget { let guard = G::default(); @@ -188,12 +154,8 @@ impl Driver for Widget TkAction { widget.set_string(data) } - fn get(&self, widget: &Self::Widget) -> Option { - Some(widget.get_string()) - } } impl Driver for Widget> { - type Msg = G::Msg; type Widget = EditBox; fn make(&self) -> Self::Widget { let guard = G::default(); @@ -202,13 +164,9 @@ impl Driver for Widget> fn set(&self, widget: &mut Self::Widget, data: String) -> TkAction { widget.set_string(data) } - fn get(&self, widget: &Self::Widget) -> Option { - Some(widget.get_string()) - } } impl Driver for Widget> { - type Msg = VoidMsg; type Widget = ProgressBar; fn make(&self) -> Self::Widget { ProgressBar::new() @@ -216,9 +174,6 @@ impl Driver for Widget TkAction { widget.set_value(data) } - fn get(&self, widget: &Self::Widget) -> Option { - Some(widget.value()) - } } /// [`crate::CheckBox`] view widget constructor @@ -234,17 +189,13 @@ impl CheckBox { } } impl Driver for CheckBox { - type Msg = bool; - type Widget = crate::CheckBox; + type Widget = crate::CheckBox; fn make(&self) -> Self::Widget { - crate::CheckBox::new(self.label.clone()).on_toggle(|_, state| Some(state)) + crate::CheckBox::new(self.label.clone()).on_toggle(|mgr, state| mgr.push_msg(state)) } fn set(&self, widget: &mut Self::Widget, data: bool) -> TkAction { widget.set_bool(data) } - fn get(&self, widget: &Self::Widget) -> Option { - Some(widget.get_bool()) - } } /// [`crate::RadioBoxBare`] view widget constructor @@ -259,17 +210,13 @@ impl RadioBoxBare { } } impl Driver for RadioBoxBare { - type Msg = bool; - type Widget = crate::RadioBoxBare; + type Widget = crate::RadioBoxBare; fn make(&self) -> Self::Widget { - crate::RadioBoxBare::new(self.group.clone()).on_select(|_| Some(true)) + crate::RadioBoxBare::new(self.group.clone()).on_select(|mgr| mgr.push_msg(true)) } fn set(&self, widget: &mut Self::Widget, data: bool) -> TkAction { widget.set_bool(data) } - fn get(&self, widget: &Self::Widget) -> Option { - Some(widget.get_bool()) - } } /// [`crate::RadioBox`] view widget constructor @@ -286,17 +233,14 @@ impl RadioBox { } } impl Driver for RadioBox { - type Msg = bool; - type Widget = crate::RadioBox; + type Widget = crate::RadioBox; fn make(&self) -> Self::Widget { - crate::RadioBox::new(self.label.clone(), self.group.clone()).on_select(|_| Some(true)) + crate::RadioBox::new(self.label.clone(), self.group.clone()) + .on_select(|mgr| mgr.push_msg(true)) } fn set(&self, widget: &mut Self::Widget, data: bool) -> TkAction { widget.set_bool(data) } - fn get(&self, widget: &Self::Widget) -> Option { - Some(widget.get_bool()) - } } /// [`crate::Slider`] view widget constructor @@ -330,7 +274,6 @@ impl Slider { } } impl Driver for Slider { - type Msg = T; type Widget = crate::Slider; fn make(&self) -> Self::Widget { crate::Slider::new_with_direction(self.min, self.max, self.step, self.direction) @@ -338,7 +281,4 @@ impl Driver for Slider { fn set(&self, widget: &mut Self::Widget, data: T) -> TkAction { widget.set_value(data) } - fn get(&self, widget: &Self::Widget) -> Option { - Some(widget.value()) - } } diff --git a/crates/kas-widgets/src/view/filter_list.rs b/crates/kas-widgets/src/view/filter_list.rs index b9b32c482..e571c5807 100644 --- a/crates/kas-widgets/src/view/filter_list.rs +++ b/crates/kas-widgets/src/view/filter_list.rs @@ -7,7 +7,7 @@ use kas::prelude::*; use kas::updatable::filter::Filter; -use kas::updatable::{ListData, SingleData, Updatable}; +use kas::updatable::{ListData, SingleData}; use std::cell::RefCell; use std::fmt::Debug; @@ -64,22 +64,13 @@ impl + SingleData> FilteredList } } -impl + 'static, F: Filter + SingleData> Updatable - for FilteredList -{ - fn handle(&self, key: &K, msg: &M) -> Option { - self.data.handle(key, msg) - } -} - impl + SingleData> ListData for FilteredList { type Key = T::Key; type Item = T::Item; - fn update_handles(&self) -> Vec { - let mut v = self.data.update_handles(); - v.append(&mut self.filter.update_handles()); - v + fn update_on_handles(&self, mgr: &mut EventState, id: &WidgetId) { + self.data.update_on_handles(mgr, id); + self.filter.update_on_handles(mgr, id); } fn version(&self) -> u64 { let ver = self.data.version() + self.filter.version(); @@ -111,7 +102,7 @@ impl + SingleData> ListData for Filter .filter(|item| self.filter.matches(item.clone())) } - fn update(&self, key: &Self::Key, value: Self::Item) -> Option { + fn update(&self, mgr: &mut EventMgr, key: &Self::Key, value: Self::Item) { // Filtering does not affect result, but does affect the view if self .data @@ -120,10 +111,10 @@ impl + SingleData> ListData for Filter .unwrap_or(true) { // Not previously visible: no update occurs - return None; + return; } - self.data.update(key, value) + self.data.update(mgr, key, value); } fn iter_vec_from(&self, start: usize, limit: usize) -> Vec { diff --git a/crates/kas-widgets/src/view/list_view.rs b/crates/kas-widgets/src/view/list_view.rs index c2c74a530..d218f6849 100644 --- a/crates/kas-widgets/src/view/list_view.rs +++ b/crates/kas-widgets/src/view/list_view.rs @@ -5,15 +5,15 @@ //! List view widget -use super::{driver, Driver, PressPhase, SelectionError, SelectionMode}; +use super::{driver, Driver, PressPhase, SelectionError, SelectionMode, SelectionMsg}; #[allow(unused)] // doc links use crate::ScrollBars; -use crate::Scrollable; +use crate::{Scrollable, SelectMsg}; use kas::event::components::ScrollComponent; -use kas::event::{ChildMsg, Command, CursorIcon}; +use kas::event::{Command, CursorIcon, Scroll}; use kas::layout::solve_size_rules; use kas::prelude::*; -use kas::updatable::{ListData, Updatable}; +use kas::updatable::ListData; use linear_map::set::LinearSet; use log::{debug, trace}; use std::time::Instant; @@ -29,9 +29,8 @@ impl_scope! { /// /// This widget supports a view over a list of shared data items. /// - /// The shared data type `T` must support [`ListData`] and - /// [`Updatable`], the latter with key type `T::Key` and message type - /// matching the widget's message. One may use [`kas::updatable::SharedRc`] + /// The shared data type `T` must support [`ListData`]. + /// One may use [`kas::updatable::SharedRc`] /// or a custom shared data type. /// /// The driver `V` must implement [`Driver`], with data type @@ -40,11 +39,20 @@ impl_scope! { /// /// This widget is [`Scrollable`], supporting keyboard, wheel and drag /// scrolling. You may wish to wrap this widget with [`ScrollBars`]. + /// + /// # Messages + /// + /// When a child pushes a message, the [`ListData::handle_message`] method is + /// called. After calling [`ListData::handle_message`], this widget attempts to + /// read and handle [`SelectMsg`]. + /// + /// When selection is enabled and an item is selected or deselected, this + /// widget emits a [`SelectionMsg`]. #[derive(Clone, Debug)] #[widget] pub struct ListView< D: Directional, - T: ListData + Updatable + 'static, + T: ListData + 'static, V: Driver = driver::Default, > { #[widget_core] @@ -94,7 +102,7 @@ impl_scope! { Self::new_with_dir_driver(D::default(), view, data) } } - impl, V: Driver> ListView { + impl> ListView { /// Set the direction of contents pub fn set_direction(&mut self, direction: Direction) -> TkAction { self.direction = direction; @@ -151,9 +159,7 @@ impl_scope! { /// [`ListData::update`]). Other widgets sharing this data are notified /// of the update, if data is changed. pub fn set_value(&self, mgr: &mut EventMgr, key: &T::Key, data: T::Item) { - if let Some(handle) = self.data.update(key, data) { - mgr.trigger_update(handle, 0); - } + self.data.update(mgr, key, data); } /// Update shared data @@ -209,8 +215,6 @@ impl_scope! { } /// Clear all selected items - /// - /// Does not send [`ChildMsg`] responses. pub fn clear_selected(&mut self) -> TkAction { if self.selection.is_empty() { TkAction::empty() @@ -225,8 +229,6 @@ impl_scope! { /// Returns `TkAction::REDRAW` if newly selected, `TkAction::empty()` if /// already selected. Fails if selection mode does not permit selection /// or if the key is invalid. - /// - /// Does not send [`ChildMsg`] responses. pub fn select(&mut self, key: T::Key) -> Result { match self.sel_mode { SelectionMode::None => return Err(SelectionError::Disabled), @@ -246,8 +248,6 @@ impl_scope! { /// /// Returns `TkAction::REDRAW` if deselected, `TkAction::empty()` if not /// previously selected or if the key is invalid. - /// - /// Does not send [`ChildMsg`] responses. pub fn deselect(&mut self, key: &T::Key) -> TkAction { match self.selection.remove(key) { true => TkAction::REDRAW, @@ -398,11 +398,11 @@ impl_scope! { self.widgets.len() } #[inline] - fn get_child(&self, index: usize) -> Option<&dyn WidgetConfig> { + fn get_child(&self, index: usize) -> Option<&dyn Widget> { self.widgets.get(index).map(|w| w.widget.as_widget()) } #[inline] - fn get_child_mut(&mut self, index: usize) -> Option<&mut dyn WidgetConfig> { + fn get_child_mut(&mut self, index: usize) -> Option<&mut dyn Widget> { self.widgets .get_mut(index) .map(|w| w.widget.as_widget_mut()) @@ -450,9 +450,7 @@ impl_scope! { } fn configure(&mut self, mgr: &mut SetRectMgr) { - for handle in self.data.update_handles().into_iter() { - mgr.update_on_handle(handle, self.id()); - } + self.data.update_on_handles(mgr.ev_state(), self.id_ref()); mgr.register_nav_fallback(self.id()); } } @@ -612,9 +610,7 @@ impl_scope! { } impl Handler for Self { - type Msg = ChildMsg::Msg>; - - fn handle(&mut self, mgr: &mut EventMgr, event: Event) -> Response { + fn handle_event(&mut self, mgr: &mut EventMgr, event: Event) -> Response { match event { Event::HandleUpdate { .. } => { let data_ver = self.data.version(); @@ -622,7 +618,6 @@ impl_scope! { // TODO(opt): use the update payload to indicate which widgets need updating? self.update_view(mgr); self.data_ver = data_ver; - return Response::Update; } return Response::Used; } @@ -665,21 +660,21 @@ impl_scope! { } } - return match self.sel_mode { - SelectionMode::None => Response::Used, + match self.sel_mode { + SelectionMode::None => (), SelectionMode::Single => { mgr.redraw(self.id()); self.selection.clear(); self.selection.insert(key.clone()); - ChildMsg::Select(key.clone()).into() + mgr.push_msg(SelectionMsg::Select(key.clone())); } SelectionMode::Multiple => { mgr.redraw(self.id()); if self.selection.remove(key) { - ChildMsg::Deselect(key.clone()).into() + mgr.push_msg(SelectionMsg::Deselect(key.clone())); } else { self.selection.insert(key.clone()); - ChildMsg::Select(key.clone()).into() + mgr.push_msg(SelectionMsg::Select(key.clone())); } } } @@ -725,7 +720,8 @@ impl_scope! { } let len = usize::conv(self.cur_len); mgr.set_nav_focus(self.widgets[index % len].widget.id(), true); - Response::Focus(rect) + mgr.set_scroll(Scroll::Rect(rect)); + Response::Used } else { Response::Unused }; @@ -733,113 +729,61 @@ impl_scope! { _ => (), // fall through to scroll handler } - let (action, response) = - self.scroll - .scroll_by_event(mgr, event, self.id(), self.core.rect.size); - if !action.is_empty() { - *mgr |= action; - mgr.set_rect_mgr(|mgr| self.update_widgets(mgr)); - } - response.void_into() + self.scroll.scroll_by_event(mgr, event, self.id(), self.core.rect) } - } - impl SendEvent for Self { - fn send(&mut self, mgr: &mut EventMgr, id: WidgetId, event: Event) -> Response { - if self.eq_id(&id) { - return self.handle(mgr, event); + fn handle_unused(&mut self, mgr: &mut EventMgr, index: usize, event: Event) -> Response { + if let Event::PressStart { source, coord, .. } = event { + if source.is_primary() { + // We request a grab with our ID, hence the + // PressMove/PressEnd events are matched in handle_event(). + mgr.grab_press_unique(self.id(), source, coord, None); + self.press_phase = PressPhase::Start(coord); + self.press_target = self.widgets[index].key.clone(); + Response::Used + } else { + Response::Unused + } + } else { + self.handle_event(mgr, event) } + } - let key = match self.data.reconstruct_key(self.id_ref(), &id) { - Some(key) => key, - None => return Response::Unused, + fn handle_message(&mut self, mgr: &mut EventMgr, index: usize) { + let key = match self.widgets[index].key.clone() { + Some(k) => k, + None => return, }; - let (index, response); - 'outer: loop { - for i in 0..self.widgets.len() { - if self.widgets[i].key.as_ref() == Some(&key) { - index = i; - let child_event = self.scroll.offset_event(event.clone()); - response = self.widgets[i].widget.send(mgr, id, child_event); - break 'outer; - } - } - return Response::Unused; - } + self.data.handle_message(mgr, &key); - if matches!(&response, Response::Update | Response::Msg(_)) { - if let Some(value) = self.view.get(&self.widgets[index].widget) { - if let Some(handle) = self.data.update(&key, value) { - mgr.trigger_update(handle, 0); + if let Some(SelectMsg) = mgr.try_pop_msg() { + match self.sel_mode { + SelectionMode::None => (), + SelectionMode::Single => { + mgr.redraw(self.id()); + self.selection.clear(); + self.selection.insert(key.clone()); + mgr.push_msg(SelectionMsg::Select(key)); } - } - } - - match response { - Response::Unused => { - if let Event::PressStart { source, coord, .. } = event { - if source.is_primary() { - // We request a grab with our ID, hence the - // PressMove/PressEnd events are matched in handle(). - mgr.grab_press_unique(self.id(), source, coord, None); - self.press_phase = PressPhase::Start(coord); - self.press_target = Some(key); - Response::Used + SelectionMode::Multiple => { + mgr.redraw(self.id()); + if self.selection.remove(&key) { + mgr.push_msg(SelectionMsg::Deselect(key)); } else { - Response::Unused - } - } else { - self.handle(mgr, event) - } - } - Response::Used => Response::Used, - Response::Pan(delta) => match self.scroll_by_delta(mgr, delta) { - delta if delta == Offset::ZERO => Response::Scrolled, - delta => Response::Pan(delta), - } - Response::Scrolled => Response::Scrolled, - Response::Focus(rect) => { - let (rect, action) = self.scroll.focus_rect(rect, self.core.rect); - *mgr |= action; - mgr.set_rect_mgr(|mgr| self.update_widgets(mgr)); - Response::Focus(rect) - } - Response::Select => { - match self.sel_mode { - SelectionMode::None => Response::Used, - SelectionMode::Single => { - mgr.redraw(self.id()); - self.selection.clear(); self.selection.insert(key.clone()); - Response::Msg(ChildMsg::Select(key)) - } - SelectionMode::Multiple => { - mgr.redraw(self.id()); - if self.selection.remove(&key) { - Response::Msg(ChildMsg::Deselect(key)) - } else { - self.selection.insert(key.clone()); - Response::Msg(ChildMsg::Select(key)) - } + mgr.push_msg(SelectionMsg::Select(key)); } } } - Response::Update => Response::Used, - Response::Msg(msg) => { - trace!( - "Received by {} from {:?}: {:?}", - self.id(), - &key, - kas::util::TryFormat(&msg) - ); - if let Some(handle) = self.data.handle(&key, &msg) { - mgr.trigger_update(handle, 0); - } - Response::Msg(ChildMsg::Child(key, msg)) - } } } + + fn handle_scroll(&mut self, mgr: &mut EventMgr, scroll: Scroll) -> Scroll { + let s = self.scroll.scroll(mgr, self.rect(), scroll); + mgr.set_rect_mgr(|mgr| self.update_widgets(mgr)); + s + } } } diff --git a/crates/kas-widgets/src/view/matrix_view.rs b/crates/kas-widgets/src/view/matrix_view.rs index 76a5e949e..a8d60a050 100644 --- a/crates/kas-widgets/src/view/matrix_view.rs +++ b/crates/kas-widgets/src/view/matrix_view.rs @@ -5,15 +5,15 @@ //! List view widget -use super::{driver, Driver, PressPhase, SelectionError, SelectionMode}; +use super::{driver, Driver, PressPhase, SelectionError, SelectionMode, SelectionMsg}; #[allow(unused)] // doc links use crate::ScrollBars; -use crate::Scrollable; +use crate::{Scrollable, SelectMsg}; use kas::event::components::ScrollComponent; -use kas::event::{ChildMsg, Command, CursorIcon}; +use kas::event::{Command, CursorIcon, Scroll}; use kas::layout::solve_size_rules; use kas::prelude::*; -use kas::updatable::{MatrixData, Updatable}; +use kas::updatable::MatrixData; use linear_map::set::LinearSet; use log::{debug, trace}; use std::time::Instant; @@ -35,9 +35,8 @@ impl_scope! { /// /// This widget supports a view over a matrix of shared data items. /// - /// The shared data type `T` must support [`MatrixData`] and - /// [`Updatable`], the latter with key type `T::Key` and message type - /// matching the widget's message. One may use [`kas::updatable::SharedRc`] + /// The shared data type `T` must support [`MatrixData`]. + /// One may use [`kas::updatable::SharedRc`] /// or a custom shared data type. /// /// The driver `V` must implement [`Driver`], with data type @@ -46,10 +45,19 @@ impl_scope! { /// /// This widget is [`Scrollable`], supporting keyboard, wheel and drag /// scrolling. You may wish to wrap this widget with [`ScrollBars`]. + /// + /// # Messages + /// + /// When a child pushes a message, the [`MatrixData::handle_message`] method is + /// called. After calling [`MatrixData::handle_message`], this widget attempts + /// to read and handle [`SelectMsg`]. + /// + /// When selection is enabled and an item is selected or deselected, this + /// widget emits a [`SelectionMsg`]. #[derive(Clone, Debug)] #[widget] pub struct MatrixView< - T: MatrixData + Updatable + 'static, + T: MatrixData + 'static, V: Driver = driver::Default, > { #[widget_core] @@ -133,9 +141,7 @@ impl_scope! { /// [`MatrixData::update`]). Other widgets sharing this data are notified /// of the update, if data is changed. pub fn set_value(&self, mgr: &mut EventMgr, key: &T::Key, data: T::Item) { - if let Some(handle) = self.data.update(key, data) { - mgr.trigger_update(handle, 0); - } + self.data.update(mgr, key, data); } /// Update shared data @@ -191,8 +197,6 @@ impl_scope! { } /// Clear all selected items - /// - /// Does not send [`ChildMsg`] responses. pub fn clear_selected(&mut self) -> TkAction { if self.selection.is_empty() { TkAction::empty() @@ -207,8 +211,6 @@ impl_scope! { /// Returns `TkAction::REDRAW` if newly selected, `TkAction::empty()` if /// already selected. Fails if selection mode does not permit selection /// or if the key is invalid. - /// - /// Does not send [`ChildMsg`] responses. pub fn select(&mut self, key: T::Key) -> Result { match self.sel_mode { SelectionMode::None => return Err(SelectionError::Disabled), @@ -228,8 +230,6 @@ impl_scope! { /// /// Returns `TkAction::REDRAW` if deselected, `TkAction::empty()` if not /// previously selected or if the key is invalid. - /// - /// Does not send [`ChildMsg`] responses. pub fn deselect(&mut self, key: &T::Key) -> TkAction { match self.selection.remove(key) { true => TkAction::REDRAW, @@ -371,11 +371,11 @@ impl_scope! { self.widgets.len() } #[inline] - fn get_child(&self, index: usize) -> Option<&dyn WidgetConfig> { + fn get_child(&self, index: usize) -> Option<&dyn Widget> { self.widgets.get(index).map(|w| w.widget.as_widget()) } #[inline] - fn get_child_mut(&mut self, index: usize) -> Option<&mut dyn WidgetConfig> { + fn get_child_mut(&mut self, index: usize) -> Option<&mut dyn Widget> { self.widgets .get_mut(index) .map(|w| w.widget.as_widget_mut()) @@ -427,9 +427,7 @@ impl_scope! { } fn configure(&mut self, mgr: &mut SetRectMgr) { - for handle in self.data.update_handles().into_iter() { - mgr.update_on_handle(handle, self.id()); - } + self.data.update_on_handles(mgr.ev_state(), self.id_ref()); mgr.register_nav_fallback(self.id()); } } @@ -611,16 +609,13 @@ impl_scope! { } impl Handler for Self { - type Msg = ChildMsg::Msg>; - - fn handle(&mut self, mgr: &mut EventMgr, event: Event) -> Response { + fn handle_event(&mut self, mgr: &mut EventMgr, event: Event) -> Response { match event { Event::HandleUpdate { .. } => { let data_ver = self.data.version(); if data_ver > self.data_ver { self.update_view(mgr); self.data_ver = data_ver; - return Response::Update; } return Response::Used; } @@ -663,21 +658,21 @@ impl_scope! { } } - return match self.sel_mode { - SelectionMode::None => Response::Used, + match self.sel_mode { + SelectionMode::None => (), SelectionMode::Single => { mgr.redraw(self.id()); self.selection.clear(); self.selection.insert(key.clone()); - ChildMsg::Select(key.clone()).into() + mgr.push_msg(SelectionMsg::Select(key.clone())); } SelectionMode::Multiple => { mgr.redraw(self.id()); if self.selection.remove(key) { - ChildMsg::Deselect(key.clone()).into() + mgr.push_msg(SelectionMsg::Deselect(key.clone())); } else { self.selection.insert(key.clone()); - ChildMsg::Select(key.clone()).into() + mgr.push_msg(SelectionMsg::Select(key.clone())); } } } @@ -736,7 +731,8 @@ impl_scope! { } mgr.set_nav_focus(id, true); - Response::Focus(rect) + mgr.set_scroll(Scroll::Rect(rect)); + Response::Used } else { Response::Unused }; @@ -744,115 +740,61 @@ impl_scope! { _ => (), // fall through to scroll handler } - let (action, response) = self.scroll - .scroll_by_event(mgr, event, self.id(), self.core.rect.size); + self.scroll.scroll_by_event(mgr, event, self.id(), self.core.rect) + } - if !action.is_empty() { - *mgr |= action; - mgr.set_rect_mgr(|mgr| self.update_widgets(mgr)); - Response::Focus(self.rect()) + fn handle_unused(&mut self, mgr: &mut EventMgr, index: usize, event: Event) -> Response { + if let Event::PressStart { source, coord, .. } = event { + if source.is_primary() { + // We request a grab with our ID, hence the + // PressMove/PressEnd events are matched in handle_event(). + mgr.grab_press_unique(self.id(), source, coord, None); + self.press_phase = PressPhase::Start(coord); + self.press_target = self.widgets[index].key.clone(); + Response::Used + } else { + Response::Unused + } } else { - response.void_into() + self.handle_event(mgr, event) } } - } - - impl SendEvent for Self { - fn send(&mut self, mgr: &mut EventMgr, id: WidgetId, event: Event) -> Response { - if self.eq_id(&id) { - return self.handle(mgr, event); - } - let key = match self.data.reconstruct_key(self.id_ref(), &id) { - Some(key) => key, - None => return Response::Unused, + fn handle_message(&mut self, mgr: &mut EventMgr, index: usize) { + let key = match self.widgets[index].key.clone() { + Some(k) => k, + None => return, }; - let (index, response); - 'outer: loop { - for i in 0..self.widgets.len() { - if self.widgets[i].key.as_ref() == Some(&key) { - index = i; - let child_event = self.scroll.offset_event(event.clone()); - response = self.widgets[i].widget.send(mgr, id, child_event); - break 'outer; - } - } - return Response::Unused; - } + self.data.handle_message(mgr, &key); - if matches!(&response, Response::Update | Response::Msg(_)) { - if let Some(value) = self.view.get(&self.widgets[index].widget) { - if let Some(handle) = self.data.update(&key, value) { - mgr.trigger_update(handle, 0); + if let Some(SelectMsg) = mgr.try_pop_msg() { + match self.sel_mode { + SelectionMode::None => (), + SelectionMode::Single => { + mgr.redraw(self.id()); + self.selection.clear(); + self.selection.insert(key.clone()); + mgr.push_msg(SelectionMsg::Select(key)); } - } - } - - match response { - Response::Unused => { - if let Event::PressStart { source, coord, .. } = event { - if source.is_primary() { - // We request a grab with our ID, hence the - // PressMove/PressEnd events are matched in handle(). - mgr.grab_press_unique(self.id(), source, coord, None); - self.press_phase = PressPhase::Start(coord); - self.press_target = Some(key); - Response::Used + SelectionMode::Multiple => { + mgr.redraw(self.id()); + if self.selection.remove(&key) { + mgr.push_msg(SelectionMsg::Deselect(key)); } else { - Response::Unused - } - } else { - self.handle(mgr, event) - } - } - Response::Used => Response::Used, - Response::Pan(delta) => match self.scroll_by_delta(mgr, delta) { - delta if delta == Offset::ZERO => Response::Scrolled, - delta => Response::Pan(delta), - } - Response::Scrolled => Response::Scrolled, - Response::Focus(rect) => { - let (rect, action) = self.scroll.focus_rect(rect, self.core.rect); - *mgr |= action; - mgr.set_rect_mgr(|mgr| self.update_widgets(mgr)); - Response::Focus(rect) - } - Response::Select => { - match self.sel_mode { - SelectionMode::None => Response::Used, - SelectionMode::Single => { - mgr.redraw(self.id()); - self.selection.clear(); self.selection.insert(key.clone()); - Response::Msg(ChildMsg::Select(key)) - } - SelectionMode::Multiple => { - mgr.redraw(self.id()); - if self.selection.remove(&key) { - Response::Msg(ChildMsg::Deselect(key)) - } else { - self.selection.insert(key.clone()); - Response::Msg(ChildMsg::Select(key)) - } + mgr.push_msg(SelectionMsg::Select(key)); } } } - Response::Update => Response::Used, - Response::Msg(msg) => { - trace!( - "Received by {} from {:?}: {:?}", - self.id(), - &key, - kas::util::TryFormat(&msg) - ); - if let Some(handle) = self.data.handle(&key, &msg) { - mgr.trigger_update(handle, 0); - } - Response::Msg(ChildMsg::Child(key, msg)) - } } } + + fn handle_scroll(&mut self, mgr: &mut EventMgr, scroll: Scroll) -> Scroll { + let s = self.scroll.scroll(mgr, self.rect(), scroll); + mgr.set_rect_mgr(|mgr| self.update_widgets(mgr)); + s + } } } diff --git a/crates/kas-widgets/src/view/mod.rs b/crates/kas-widgets/src/view/mod.rs index 42c45a20a..9f7086ad8 100644 --- a/crates/kas-widgets/src/view/mod.rs +++ b/crates/kas-widgets/src/view/mod.rs @@ -67,7 +67,6 @@ #[allow(unused)] use kas::event::UpdateHandle; -use kas::macros::VoidMsg; #[allow(unused)] use kas::updatable::{ListData, MatrixData, SharedRc, SingleData}; use thiserror::Error; @@ -85,6 +84,17 @@ pub use list_view::ListView; pub use matrix_view::MatrixView; pub use single_view::SingleView; +/// Used to notify selection and deselection of [`ListView`] and [`MatrixView`] children +#[derive(Clone, Debug)] +pub enum SelectionMsg { + /// Selection of item + Select(K), + /// Deselection of item + /// + /// Note: not emitted due to selection of another item in single-item selection mode. + Deselect(K), +} + #[derive(Copy, Clone, Debug, Eq, PartialEq)] enum PressPhase { None, @@ -93,7 +103,7 @@ enum PressPhase { } /// Selection mode used by [`ListView`] -#[derive(Clone, Copy, Debug, VoidMsg)] +#[derive(Clone, Copy, Debug)] pub enum SelectionMode { None, Single, diff --git a/crates/kas-widgets/src/view/single_view.rs b/crates/kas-widgets/src/view/single_view.rs index 624c036aa..6f9165963 100644 --- a/crates/kas-widgets/src/view/single_view.rs +++ b/crates/kas-widgets/src/view/single_view.rs @@ -7,28 +7,32 @@ use super::{driver, Driver}; use kas::prelude::*; -use kas::updatable::{SingleData, Updatable}; +use kas::updatable::SingleData; impl_scope! { /// Single view widget /// /// This widget supports a view over a shared data item. /// - /// The shared data type `T` must support [`SingleData`] and - /// [`Updatable`], the latter with key type `()` and message type - /// matching the widget's message. One may use [`kas::updatable::SharedRc`] + /// The shared data type `T` must support [`SingleData`]. + /// One may use [`kas::updatable::SharedRc`] /// or a custom shared data type. /// /// The driver `V` must implement [`Driver`], with data type /// `::Item`. Several implementations are available in the /// [`driver`] module or a custom implementation may be used. + /// + /// # Messages + /// + /// When a child pushes a message, the [`SingleData::handle_message`] method is + /// called. #[autoimpl(Debug ignore self.view)] #[derive(Clone)] #[widget{ layout = single; }] pub struct SingleView< - T: SingleData + Updatable<(), V::Msg> + 'static, + T: SingleData + 'static, V: Driver = driver::Default, > { #[widget_core] @@ -90,9 +94,7 @@ impl_scope! { /// [`SingleData::update`]). Other widgets sharing this data are notified /// of the update, if data is changed. pub fn set_value(&self, mgr: &mut EventMgr, data: T::Item) { - if let Some(handle) = self.data.update(data) { - mgr.trigger_update(handle, 0); - } + self.data.update(mgr, data); } /// Update shared data @@ -106,9 +108,7 @@ impl_scope! { impl WidgetConfig for Self { fn configure(&mut self, mgr: &mut SetRectMgr) { - for handle in self.data.update_handles().into_iter() { - mgr.update_on_handle(handle, self.id()); - } + self.data.update_on_handles(mgr.ev_state(), self.id_ref()); // We set data now, after child is configured *mgr |= self.view.set(&mut self.child, self.data.get_cloned()); @@ -116,8 +116,7 @@ impl_scope! { } impl Handler for Self { - type Msg = ::Msg; - fn handle(&mut self, mgr: &mut EventMgr, event: Event) -> Response { + fn handle_event(&mut self, mgr: &mut EventMgr, event: Event) -> Response { match event { Event::HandleUpdate { .. } => { let data_ver = self.data.version(); @@ -125,42 +124,15 @@ impl_scope! { let value = self.data.get_cloned(); *mgr |= self.view.set(&mut self.child, value); self.data_ver = data_ver; - Response::Update - } else { - Response::Used } + Response::Used } _ => Response::Unused, } } - } - impl SendEvent for Self { - fn send(&mut self, mgr: &mut EventMgr, id: WidgetId, event: Event) -> Response { - if self.eq_id(&id) { - self.handle(mgr, event) - } else { - let r = self.child.send(mgr, id.clone(), event); - if matches!(&r, Response::Update | Response::Msg(_)) { - if let Some(value) = self.view.get(&self.child) { - if let Some(handle) = self.data.update(value) { - mgr.trigger_update(handle, 0); - } - } - } - if let Response::Msg(ref msg) = &r { - log::trace!( - "Received by {} from {}: {:?}", - self.id(), - id, - kas::util::TryFormat(&msg) - ); - if let Some(handle) = self.data.handle(&(), msg) { - mgr.trigger_update(handle, 0); - } - } - r - } + fn handle_message(&mut self, mgr: &mut EventMgr, _: usize) { + self.data.handle_message(mgr); } } } diff --git a/crates/kas-widgets/src/window.rs b/crates/kas-widgets/src/window.rs index b29b5e86d..f3c02f1a2 100644 --- a/crates/kas-widgets/src/window.rs +++ b/crates/kas-widgets/src/window.rs @@ -60,17 +60,7 @@ impl_scope! { } } - impl SendEvent for Self where W::Msg: Into { - fn send(&mut self, mgr: &mut EventMgr, id: WidgetId, event: Event) -> Response { - if self.eq_id(&id) { - Response::Unused - } else { - self.w.send(mgr, id, event).into() - } - } - } - - impl, W: Widget + 'static> kas::Window for Window { + impl kas::Window for Window { fn title(&self) -> &str { &self.title } @@ -202,7 +192,7 @@ impl Window { } // This is like WidgetChildren::find, but returns a translated Rect. -fn find_rect(widget: &dyn WidgetConfig, id: WidgetId) -> Option { +fn find_rect(widget: &dyn Widget, id: WidgetId) -> Option { match widget.find_child_index(&id) { Some(i) => { if let Some(w) = widget.get_child(i) { diff --git a/examples/async-event.rs b/examples/async-event.rs index add21dfda..3eb65e854 100644 --- a/examples/async-event.rs +++ b/examples/async-event.rs @@ -70,8 +70,7 @@ impl_scope! { } } impl Handler for ColourSquare { - type Msg = VoidMsg; - fn handle(&mut self, mgr: &mut EventMgr, event: Event) -> Response { + fn handle_event(&mut self, mgr: &mut EventMgr, event: Event) -> Response { match event { Event::HandleUpdate { .. } => { // Note: event has `handle` and `payload` params. diff --git a/examples/calculator.rs b/examples/calculator.rs index 4beeec96f..fd8d54cc2 100644 --- a/examples/calculator.rs +++ b/examples/calculator.rs @@ -12,7 +12,7 @@ use kas::event::VirtualKeyCode as VK; use kas::prelude::*; use kas::widgets::{EditBox, TextButton, Window}; -#[derive(Clone, Debug, VoidMsg)] +#[derive(Clone, Debug)] enum Key { Clear, Divide, @@ -37,7 +37,6 @@ fn main() -> kas::shell::Result<()> { 3, 3..5: self.b_eq; 0..2, 4: self.b0; 2, 4: self.b_dot; }; - msg = Key; }] struct { // Buttons get keyboard bindings through the "&" item (e.g. "&1" @@ -74,17 +73,18 @@ fn main() -> kas::shell::Result<()> { let content = make_widget! { #[widget{ layout = column: *; - msg = VoidMsg; }] struct { #[widget] display: impl HasString = EditBox::new("0").with_editable(false).multi_line(true), - #[widget(use_msg = handle_button)] buttons -> Key = buttons, + #[widget] _ = buttons, calc: Calculator = Calculator::new(), } - impl Self { - fn handle_button(&mut self, mgr: &mut EventMgr, msg: Key) { - if self.calc.handle(msg) { - *mgr |= self.display.set_string(self.calc.display()); + impl Handler for Self { + fn handle_message(&mut self, mgr: &mut EventMgr, _: usize) { + if let Some(msg) = mgr.try_pop_msg::() { + if self.calc.handle(msg) { + *mgr |= self.display.set_string(self.calc.display()); + } } } } diff --git a/examples/clock.rs b/examples/clock.rs index 67e60d85b..85d685f38 100644 --- a/examples/clock.rs +++ b/examples/clock.rs @@ -18,10 +18,10 @@ use std::time::Duration; use kas::draw::{color, Draw, DrawIface, DrawRounded, PassType}; use kas::geom::{Offset, Quad, Vec2}; +use kas::prelude::*; use kas::shell::draw::DrawPipe; use kas::text::util::set_text_and_prepare; use kas::widgets::Window; -use kas::{event, prelude::*}; impl_scope! { #[derive(Clone, Debug)] @@ -126,10 +126,8 @@ impl_scope! { } impl Handler for Clock { - type Msg = event::VoidMsg; - #[inline] - fn handle(&mut self, mgr: &mut EventMgr, event: Event) -> Response { + fn handle_event(&mut self, mgr: &mut EventMgr, event: Event) -> Response { match event { Event::TimerUpdate(0) => { self.now = Local::now(); diff --git a/examples/counter.rs b/examples/counter.rs index 4ec3d0e0a..895264648 100644 --- a/examples/counter.rs +++ b/examples/counter.rs @@ -13,21 +13,26 @@ use kas::widgets::{Label, TextButton, Window}; fn main() -> kas::shell::Result<()> { env_logger::init(); + #[derive(Clone, Debug)] + struct Increment(i32); + let counter = make_widget! { - #[widget { msg = VoidMsg; }] + #[widget] struct { #[widget] display: Label = Label::from("0"), - #[widget(use_msg = update)] - b_decr = TextButton::new_msg("−", -1), - #[widget(use_msg = update)] - b_incr = TextButton::new_msg("+", 1), + #[widget] + b_decr = TextButton::new_msg("−", Increment(-1)), + #[widget] + b_incr = TextButton::new_msg("+", Increment(1)), count: i32 = 0, } - impl Self { - fn update(&mut self, mgr: &mut EventMgr, incr: i32) { - self.count += incr; - *mgr |= self.display.set_string(self.count.to_string()); + impl Handler for Self { + fn handle_message(&mut self, mgr: &mut EventMgr, _: usize) { + if let Some(Increment(incr)) = mgr.try_pop_msg() { + self.count += incr; + *mgr |= self.display.set_string(self.count.to_string()); + } } } impl Layout for Self { diff --git a/examples/custom-theme.rs b/examples/custom-theme.rs index f066eb701..993e02d2c 100644 --- a/examples/custom-theme.rs +++ b/examples/custom-theme.rs @@ -99,7 +99,7 @@ impl ThemeControl for CustomTheme { } } -#[derive(Clone, Debug, VoidMsg)] +#[derive(Clone, Debug)] enum Item { White, Red, @@ -110,8 +110,12 @@ enum Item { fn main() -> kas::shell::Result<()> { env_logger::init(); - let widgets = make_widget! { - #[widget{ + let theme = CustomTheme::default(); + + let window = Window::new( + "Theme demo", + make_widget! { + #[widget{ layout = grid: { 1, 1: "Custom theme demo\nChoose your colour!"; 0, 1: self.white; @@ -119,36 +123,23 @@ fn main() -> kas::shell::Result<()> { 2, 1: self.yellow; 1, 0: self.green; }; - msg = Item; - }] - struct { - #[widget] white = TextButton::new_msg("&White", Item::White), - #[widget] red = TextButton::new_msg("&Red", Item::Red), - #[widget] yellow = TextButton::new_msg("&Yellow", Item::Yellow), - #[widget] green = TextButton::new_msg("&Green", Item::Green), - } - }; - - let theme = CustomTheme::default(); - - let window = Window::new( - "Theme demo", - make_widget! { - #[widget{ - layout = single; - msg = VoidMsg; }] struct { - #[widget(use_msg = handler)] _ = widgets, + #[widget] white = TextButton::new_msg("&White", Item::White), + #[widget] red = TextButton::new_msg("&Red", Item::Red), + #[widget] yellow = TextButton::new_msg("&Yellow", Item::Yellow), + #[widget] green = TextButton::new_msg("&Green", Item::Green), } - impl Self { - fn handler(&mut self, _: &mut EventMgr, item: Item) { - match item { - Item::White => BACKGROUND.with(|b| b.set(Rgba::WHITE)), - Item::Red => BACKGROUND.with(|b| b.set(Rgba::rgb(0.9, 0.2, 0.2))), - Item::Green => BACKGROUND.with(|b| b.set(Rgba::rgb(0.2, 0.9, 0.2))), - Item::Yellow => BACKGROUND.with(|b| b.set(Rgba::rgb(0.9, 0.9, 0.2))), - }; + impl Handler for Self { + fn handle_message(&mut self, mgr: &mut EventMgr, _: usize) { + if let Some(item) = mgr.try_pop_msg::() { + match item { + Item::White => BACKGROUND.with(|b| b.set(Rgba::WHITE)), + Item::Red => BACKGROUND.with(|b| b.set(Rgba::rgb(0.9, 0.2, 0.2))), + Item::Green => BACKGROUND.with(|b| b.set(Rgba::rgb(0.2, 0.9, 0.2))), + Item::Yellow => BACKGROUND.with(|b| b.set(Rgba::rgb(0.9, 0.9, 0.2))), + } + } } } }, diff --git a/examples/data-list-view.rs b/examples/data-list-view.rs index 33ae08a5b..63b11be94 100644 --- a/examples/data-list-view.rs +++ b/examples/data-list-view.rs @@ -10,7 +10,6 @@ //! thus only limited by the data types used (specifically the `i32` type used //! to calculate the maximum scroll offset). -use kas::event::ChildMsg; use kas::prelude::*; use kas::updatable::*; use kas::widgets::view::{Driver, ListView}; @@ -18,20 +17,21 @@ use kas::widgets::*; use std::cell::RefCell; use std::collections::HashMap; -#[derive(Clone, Debug, VoidMsg)] +#[derive(Clone, Debug)] enum Control { Set(usize), Dir, + Update(String), } -#[derive(Clone, Debug, VoidMsg)] +#[derive(Clone, Debug)] enum Button { Decr, Incr, Set, } -#[derive(Clone, Debug, VoidMsg)] +#[derive(Clone, Debug)] enum EntryMsg { Select, Update(String), @@ -41,77 +41,63 @@ enum EntryMsg { struct MyData { ver: u64, len: usize, - // (active index, map of strings) - data: RefCell<(usize, HashMap)>, - handle: UpdateHandle, + active: usize, + strings: HashMap, } impl MyData { fn new(len: usize) -> Self { MyData { ver: 1, len, - data: Default::default(), + active: 0, + strings: HashMap::new(), + } + } + fn get(&self, index: usize) -> String { + self.strings + .get(&index) + .cloned() + .unwrap_or_else(|| format!("Entry #{}", index + 1)) + } +} + +#[derive(Debug)] +struct MySharedData { + data: RefCell, + handle: UpdateHandle, +} +impl MySharedData { + fn new(len: usize) -> Self { + MySharedData { + data: RefCell::new(MyData::new(len)), handle: UpdateHandle::new(), } } fn set_len(&mut self, len: usize) -> (Option, UpdateHandle) { - self.ver += 1; - self.len = len; let mut new_text = None; let mut data = self.data.borrow_mut(); - if data.0 >= len && len > 0 { - let active = len - 1; - data.0 = active; - drop(data); - new_text = Some(self.get(active).1); + data.ver += 1; + data.len = len; + if data.active >= len && len > 0 { + data.active = len - 1; + new_text = Some(data.get(data.active)); } (new_text, self.handle) } - fn get_active(&self) -> usize { - self.data.borrow().0 - } - // Note: in general this method should update the data source and return - // self.handle, but for our uses this is sufficient. - fn set_active(&mut self, active: usize) -> String { - self.ver += 1; - self.data.borrow_mut().0 = active; - self.get(active).1 - } - fn get(&self, index: usize) -> (bool, String) { - let data = self.data.borrow(); - let is_active = data.0 == index; - let text = data.1.get(&index).cloned(); - let text = text.unwrap_or_else(|| format!("Entry #{}", index + 1)); - (is_active, text) - } } -impl Updatable for MyData { - fn handle(&self, key: &usize, msg: &EntryMsg) -> Option { - match msg { - EntryMsg::Select => { - self.data.borrow_mut().0 = *key; - Some(self.handle) - } - EntryMsg::Update(text) => { - self.data.borrow_mut().1.insert(*key, text.clone()); - Some(self.handle) - } - } - } -} -impl ListData for MyData { +impl ListData for MySharedData { type Key = usize; type Item = (usize, bool, String); - fn update_handles(&self) -> Vec { - vec![self.handle] + fn update_on_handles(&self, mgr: &mut EventState, id: &WidgetId) { + mgr.update_on_handle(self.handle, id.clone()); } fn version(&self) -> u64 { - self.ver + self.data.borrow().ver } fn len(&self) -> usize { - self.len + self.data.borrow().len } fn make_id(&self, parent: &WidgetId, key: &Self::Key) -> WidgetId { parent.make_child(*key) @@ -121,24 +107,43 @@ impl ListData for MyData { } fn contains_key(&self, key: &Self::Key) -> bool { - *key < self.len + *key < self.len() } fn get_cloned(&self, key: &Self::Key) -> Option { - let (is_active, text) = self.get(*key); - Some((*key, is_active, text)) + let index = *key; + let data = self.data.borrow(); + let is_active = data.active == index; + let text = data.get(index); + Some((index, is_active, text)) } - fn update(&self, _: &Self::Key, _: Self::Item) -> Option { - unimplemented!() + fn update(&self, _: &mut EventMgr, _: &Self::Key, _: Self::Item) {} + + fn handle_message(&self, mgr: &mut EventMgr, key: &Self::Key) { + if let Some(msg) = mgr.try_pop_msg() { + let mut data = self.data.borrow_mut(); + data.ver += 1; + match msg { + EntryMsg::Select => { + data.active = *key; + } + EntryMsg::Update(text) => { + data.strings.insert(*key, text.clone()); + } + } + mgr.push_msg(Control::Update(data.get(data.active))); + mgr.trigger_update(self.handle, 0); + } } fn iter_vec(&self, limit: usize) -> Vec { - (0..limit.min(self.len)).collect() + (0..limit.min(self.len())).collect() } fn iter_vec_from(&self, start: usize, limit: usize) -> Vec { - (start.min(self.len)..(start + limit).min(self.len)).collect() + let len = self.len(); + (start.min(len)..(start + limit).min(len)).collect() } } @@ -147,10 +152,8 @@ impl ListData for MyData { #[derive(Clone, Debug)] struct ListEntryGuard; impl EditGuard for ListEntryGuard { - type Msg = EntryMsg; - - fn edit(entry: &mut EditField, _: &mut EventMgr) -> Option { - Some(EntryMsg::Update(entry.get_string())) + fn edit(entry: &mut EditField, mgr: &mut EventMgr) { + mgr.push_msg(EntryMsg::Update(entry.get_string())); } } @@ -159,7 +162,6 @@ impl_scope! { #[derive(Clone, Debug)] #[widget{ layout = column: *; - msg = EntryMsg; }] struct ListEntry { #[widget_core] @@ -167,7 +169,7 @@ impl_scope! { #[widget] label: StringLabel, #[widget] - radio: RadioBox, + radio: RadioBox, #[widget] entry: EditBox, } @@ -178,7 +180,6 @@ struct MyDriver { radio_group: RadioBoxGroup, } impl Driver<(usize, bool, String)> for MyDriver { - type Msg = EntryMsg; type Widget = ListEntry; fn make(&self) -> Self::Widget { @@ -187,7 +188,7 @@ impl Driver<(usize, bool, String)> for MyDriver { core: Default::default(), label: Label::new(String::default()), radio: RadioBox::new("display this entry", self.radio_group.clone()) - .on_select(move |_| Some(EntryMsg::Select)), + .on_select(|mgr| mgr.push_msg(EntryMsg::Select)), entry: EditBox::new(String::default()).with_guard(ListEntryGuard), } } @@ -197,9 +198,6 @@ impl Driver<(usize, bool, String)> for MyDriver { | widget.radio.set_bool(data.1) | widget.entry.set_string(data.2) } - fn get(&self, _widget: &Self::Widget) -> Option<(usize, bool, String)> { - None // unused - } } fn main() -> kas::shell::Result<()> { @@ -208,45 +206,48 @@ fn main() -> kas::shell::Result<()> { let controls = make_widget! { #[widget{ layout = row: *; - msg = Control; }] struct { #[widget] _ = Label::new("Number of rows:"), - #[widget(flatmap_msg = activate)] edit: impl HasString = EditBox::new("3") - .on_afl(|text, _| text.parse::().ok()), - #[widget(map_msg = button)] _ = TextButton::new_msg("Set", Button::Set), - #[widget(map_msg = button)] _ = TextButton::new_msg("−", Button::Decr), - #[widget(map_msg = button)] _ = TextButton::new_msg("+", Button::Incr), + #[widget] edit: impl HasString = EditBox::new("3") + .on_afl(|text, mgr| match text.parse::() { + Ok(n) => mgr.push_msg(n), + Err(_) => (), + }), + #[widget] _ = TextButton::new_msg("Set", Button::Set), + #[widget] _ = TextButton::new_msg("−", Button::Decr), + #[widget] _ = TextButton::new_msg("+", Button::Incr), #[widget] _ = TextButton::new_msg("↓↑", Control::Dir), n: usize = 3, } - impl Self { - fn activate(&mut self, _: &mut EventMgr, n: usize) -> Response { - if n == self.n { - Response::Used - } else { + impl Handler for Self { + fn handle_message(&mut self, mgr: &mut EventMgr, index: usize) { + if index == widget_index![self.edit] { + if let Some(n) = mgr.try_pop_msg::() { + if n != self.n { + self.n = n; + mgr.push_msg(Control::Set(n)) + } + } + } else if let Some(msg) = mgr.try_pop_msg::