diff --git a/crates/kas-core/src/core/layout.rs b/crates/kas-core/src/core/layout.rs index 86edb77a1..2b440b7f9 100644 --- a/crates/kas-core/src/core/layout.rs +++ b/crates/kas-core/src/core/layout.rs @@ -42,7 +42,7 @@ use kas_macros::autoimpl; /// cached by `size_rules`. /// 4. The widget is updated again after any data change (see [`ConfigCx::update`]). /// 5. The widget is ready for event-handling and drawing ([`Events`], -/// [`Self::find_id`], [`Self::draw`]). +/// [`Self::try_probe`], [`Self::draw`]). /// /// Widgets are responsible for ensuring that their children may observe this /// lifecycle. Usually this simply involves inclusion of the child in layout @@ -176,7 +176,7 @@ pub trait Layout { /// Required: [`Self::size_rules`] is called for both axes before this /// method is called, and that this method has been called *after* the last /// call to [`Self::size_rules`] *before* any of the following methods: - /// [`Layout::find_id`], [`Layout::draw`], [`Events::handle_event`]. + /// [`Layout::try_probe`], [`Layout::draw`], [`Events::handle_event`]. /// /// Default implementation when not using the `layout` property: set `rect` /// field of `widget_core!()` to the input `rect`. @@ -216,7 +216,7 @@ pub trait Layout { /// *and* child widgets need to implement this. /// Such widgets must also implement [`Events::handle_scroll`]. /// - /// Affects event handling via [`Layout::find_id`] and affects the positioning + /// Affects event handling via [`Layout::probe`] and affects the positioning /// of pop-up menus. [`Layout::draw`] must be implemented directly using /// [`DrawCx::with_clip_region`] to offset contents. /// @@ -226,14 +226,19 @@ pub trait Layout { Offset::ZERO } - /// Translate a coordinate to an [`Id`] + /// Probe a coordinate for a widget's [`Id`] /// - /// This method is used to determine which widget reacts to the mouse cursor - /// or a touch event. The result affects mouse-hover highlighting, event - /// handling by the target, and potentially also event handling by other - /// widgets (e.g. a `Label` widget will not handle touch events, but if it - /// is contained by a `ScrollRegion`, that widget may capture these via - /// [`Events::handle_event`] to implement touch scrolling). + /// Returns the [`Id`] of the lowest descendant (leaf-most element of the + /// widget tree) occupying `coord` (exceptions possible; see below). + /// + /// The callee may assume that it occupies `coord`. + /// Callers should prefer to call [`Tile::try_probe`] instead. + /// + /// This method is used to determine which widget reacts to the mouse and + /// touch events at the given coordinates. The widget identified by this + /// method may be highlighted (if hovered by the mouse) and may respond to + /// click/touch events. Unhandled click/touch events are passed to the + /// parent widget and so on up the widget tree. /// /// The result is usually the widget which draws at the given `coord`, but /// does not have to be. For example, a `Button` widget will return its own @@ -241,44 +246,45 @@ pub trait Layout { /// widget uses an internal component for event handling and thus reports /// this component's `id` even over its own area. /// + /// ### Call order + /// /// It is expected that [`Layout::set_rect`] is called before this method, /// but failure to do so should not cause a fatal error. /// - /// The default implementation suffices for widgets without children as well - /// as widgets using the `layout` property of [`#[widget]`](crate::widget). - /// Custom implementations may be required if: - /// - /// - A custom [`Layout`] implementation is used - /// - Event stealing or donation is desired (but note that - /// `layout = button: ..;` does this already) + /// ### Default implementation /// - /// When writing a custom implementation: - /// - /// - Widgets should test `self.rect().contains(coord)`, returning `None` - /// if this test is `false`; otherwise, they should always return *some* - /// [`Id`], either a childs or their own. - /// - If the Widget uses a translated coordinate space (i.e. - /// `self.translation() != Offset::ZERO`) then pass - /// `coord + self.translation()` to children. - /// - /// The default implementation is non-trivial: + /// The default macro-generated implementation considers all children of the + /// `layout` property and of [`#[widget]`](crate::widget) fields: /// ```ignore - /// if !self.rect().contains(coord) { - /// return None; - /// } /// let coord = coord + self.translation(); /// for child in ITER_OVER_CHILDREN { - /// if let Some(id) = child.find_id(coord) { + /// if let Some(id) = child.try_probe(coord) { /// return Some(id); /// } /// } - /// Some(self.id()) + /// self.id() /// ``` - fn find_id(&mut self, coord: Coord) -> Option { + fn probe(&mut self, coord: Coord) -> Id { let _ = coord; unimplemented!() // make rustdoc show that this is a provided method } + /// Probe a coordinate for a widget's [`Id`] + /// + /// Returns the [`Id`] of the lowest descendant (leaf-most element of the + /// widget tree) occupying `coord`, if any. + /// + /// This method returns `None` if `!self.rect().contains(coord)`, otherwise + /// returning the result of [`Layout::probe`]. + /// + /// ### Call order + /// + /// It is expected that [`Tile::set_rect`] is called before this method, + /// but failure to do so should not cause a fatal error. + fn try_probe(&mut self, coord: Coord) -> Option { + self.rect().contains(coord).then(|| self.probe(coord)) + } + /// Draw a widget and its children /// /// This method is invoked each frame to draw visible widgets. It should diff --git a/crates/kas-core/src/core/node.rs b/crates/kas-core/src/core/node.rs index 0019fb2fc..b3f4c288c 100644 --- a/crates/kas-core/src/core/node.rs +++ b/crates/kas-core/src/core/node.rs @@ -28,7 +28,7 @@ trait NodeT { fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints); fn nav_next(&self, reverse: bool, from: Option) -> Option; - fn find_id(&mut self, coord: Coord) -> Option; + fn try_probe(&mut self, coord: Coord) -> Option; fn _draw(&mut self, draw: DrawCx); fn _configure(&mut self, cx: &mut ConfigCx, id: Id); @@ -80,8 +80,8 @@ impl<'a, T> NodeT for (&'a mut dyn Widget, &'a T) { fn nav_next(&self, reverse: bool, from: Option) -> Option { self.0.nav_next(reverse, from) } - fn find_id(&mut self, coord: Coord) -> Option { - self.0.find_id(coord) + fn try_probe(&mut self, coord: Coord) -> Option { + self.0.try_probe(coord) } fn _draw(&mut self, mut draw: DrawCx) { draw.recurse(&mut self.0); @@ -303,8 +303,8 @@ impl<'a> Node<'a> { } /// Translate a coordinate to an [`Id`] - pub(crate) fn find_id(&mut self, coord: Coord) -> Option { - self.0.find_id(coord) + pub(crate) fn try_probe(&mut self, coord: Coord) -> Option { + self.0.try_probe(coord) } cfg_if::cfg_if! { diff --git a/crates/kas-core/src/core/widget.rs b/crates/kas-core/src/core/widget.rs index 1a6ee04ba..92982ef4a 100644 --- a/crates/kas-core/src/core/widget.rs +++ b/crates/kas-core/src/core/widget.rs @@ -31,7 +31,7 @@ use kas_macros::autoimpl; /// cached by `size_rules`. /// 4. The widget is updated again after any data change (see [`ConfigCx::update`]). /// 5. The widget is ready for event-handling and drawing -/// ([`Events::handle_event`], [`Layout::find_id`], [`Layout::draw`]). +/// ([`Events::handle_event`], [`Layout::try_probe`], [`Layout::draw`]). /// /// Widgets are responsible for ensuring that their children may observe this /// lifecycle. Usually this simply involves inclusion of the child in layout @@ -256,7 +256,7 @@ pub enum NavAdvance { /// cached by `size_rules`. /// 4. The widget is updated again after any data change (see [`ConfigCx::update`]). /// 5. The widget is ready for event-handling and drawing -/// ([`Events::handle_event`], [`Layout::find_id`], [`Layout::draw`]). +/// ([`Events::handle_event`], [`Layout::try_probe`], [`Layout::draw`]). /// /// Widgets are responsible for ensuring that their children may observe this /// lifecycle. Usually this simply involves inclusion of the child in layout @@ -310,7 +310,7 @@ pub enum NavAdvance { /// - **Layout** is specified either via [layout syntax](macros::widget#layout-1) /// or via implementation of at least [`Layout::size_rules`] and /// [`Layout::draw`] (optionally also `set_rect`, `nav_next`, `translation` -/// and `find_id`). +/// and `probe`). ///- **Event handling** is optional, implemented through [`Events`]. /// /// For examples, check the source code of widgets in the widgets library diff --git a/crates/kas-core/src/event/cx/cx_pub.rs b/crates/kas-core/src/event/cx/cx_pub.rs index 5a355748e..d36e05655 100644 --- a/crates/kas-core/src/event/cx/cx_pub.rs +++ b/crates/kas-core/src/event/cx/cx_pub.rs @@ -717,7 +717,7 @@ impl<'a> EventCx<'a> { /// /// When calling this method, be aware that some widgets use an inner /// component to handle events, thus calling with the outer widget's `id` - /// may not have the desired effect. [`Layout::find_id`] and + /// may not have the desired effect. [`Layout::try_probe`] and /// [`EventState::next_nav_focus`] are usually able to find the appropriate /// event-handling target. pub fn send_command(&mut self, id: Id, cmd: Command) { diff --git a/crates/kas-core/src/event/cx/platform.rs b/crates/kas-core/src/event/cx/platform.rs index fd2bcb12c..8ce22141a 100644 --- a/crates/kas-core/src/event/cx/platform.rs +++ b/crates/kas-core/src/event/cx/platform.rs @@ -266,11 +266,11 @@ impl EventState { cx.action.remove(Action::REGION_MOVED); // Update hovered widget - let hover = win.find_id(data, cx.last_mouse_coord); + let hover = win.try_probe(data, cx.last_mouse_coord); cx.set_hover(win.as_node(data), hover); for grab in cx.touch_grab.iter_mut() { - grab.cur_id = win.find_id(data, grab.coord); + grab.cur_id = win.try_probe(data, grab.coord); } } }); @@ -408,7 +408,7 @@ impl<'a> EventCx<'a> { let coord = position.cast_approx(); // Update hovered win - let id = win.find_id(data, coord); + let id = win.try_probe(data, coord); self.set_hover(win.as_node(data), id.clone()); if let Some(grab) = self.state.mouse_grab.as_mut() { @@ -527,7 +527,7 @@ impl<'a> EventCx<'a> { let coord = touch.location.cast_approx(); match touch.phase { TouchPhase::Started => { - let start_id = win.find_id(data, coord); + let start_id = win.try_probe(data, coord); if let Some(id) = start_id.as_ref() { if self.config.event().touch_nav_focus() { if let Some(id) = @@ -547,7 +547,7 @@ impl<'a> EventCx<'a> { } } TouchPhase::Moved => { - let cur_id = win.find_id(data, coord); + let cur_id = win.try_probe(data, coord); let mut redraw = false; let mut pan_grab = None; diff --git a/crates/kas-core/src/event/events.rs b/crates/kas-core/src/event/events.rs index 611ff0e60..3c8af3c96 100644 --- a/crates/kas-core/src/event/events.rs +++ b/crates/kas-core/src/event/events.rs @@ -152,7 +152,7 @@ pub enum Event { /// Motion events for the grabbed mouse pointer or touched finger are sent. /// /// If `cur_id` is `None`, no widget was found at the coordinate (either - /// outside the window or [`crate::Layout::find_id`] failed). + /// outside the window or [`crate::Layout::try_probe`] failed). PressMove { press: Press, delta: Offset }, /// End of a click/touch press /// @@ -168,7 +168,7 @@ pub enum Event { /// sent. /// /// If `cur_id` is `None`, no widget was found at the coordinate (either - /// outside the window or [`crate::Layout::find_id`] failed). + /// outside the window or [`crate::Layout::try_probe`] failed). PressEnd { press: Press, success: bool }, /// Update from a timer /// diff --git a/crates/kas-core/src/event/mod.rs b/crates/kas-core/src/event/mod.rs index 5ec4f7f74..c869bf5f9 100644 --- a/crates/kas-core/src/event/mod.rs +++ b/crates/kas-core/src/event/mod.rs @@ -16,7 +16,7 @@ //! //! 1. Determine the target's [`Id`]. For example, this may be //! the [`nav_focus`](EventState::nav_focus) or may be determined from -//! from mouse/touch coordinates by calling [`find_id`](crate::Layout::find_id). +//! from mouse/touch coordinates by calling [`try_probe`](crate::Layout::try_probe). //! 2. If the target is [disabled](EventState::is_disabled), then find the //! top-most ancestor which is disabled and make that the target, but //! inhibit calling of [`Events::handle_event`] on this widget (but still diff --git a/crates/kas-core/src/layout/visitor.rs b/crates/kas-core/src/layout/visitor.rs index cb2e7928d..661d67cc8 100644 --- a/crates/kas-core/src/layout/visitor.rs +++ b/crates/kas-core/src/layout/visitor.rs @@ -36,15 +36,12 @@ pub trait Visitable { /// In other respects, this functions identically to [`Layout::set_rect`]. fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints); - /// Translate a coordinate to an [`Id`] - /// - /// The caller is expected to + /// Look for a widget at this `coord` /// - /// 1. Return `None` if `!self.rect().contains(coord)` - /// 2. Translate `coord`: `let coord = coord + self.translation();` - /// 3. Call `find_id` (this method), returning its result if not `None` - /// 4. Otherwise return `Some(self.id())` - fn find_id(&mut self, coord: Coord) -> Option; + /// Returns the [`Id`] of a child when some child occupies `coord`. Returns + /// [`None`] when there is no (probable) child widget at `coord`, in which + /// case the caller may use its own [`Id`]. + fn try_probe(&mut self, coord: Coord) -> Option; /// Draw a widget and its children /// @@ -228,14 +225,14 @@ impl Visitor { /// /// 1. Return `None` if `!self.rect().contains(coord)` /// 2. Translate `coord`: `let coord = coord + self.translation();` - /// 3. Call `find_id` (this method), returning its result if not `None` + /// 3. Call `try_probe` (this method), returning its result if not `None` /// 4. Otherwise return `Some(self.id())` #[inline] - pub fn find_id(mut self, coord: Coord) -> Option { - self.find_id_(coord) + pub fn try_probe(mut self, coord: Coord) -> Option { + self.try_probe_(coord) } - fn find_id_(&mut self, coord: Coord) -> Option { - self.0.find_id(coord) + fn try_probe_(&mut self, coord: Coord) -> Option { + self.0.try_probe(coord) } /// Draw a widget and its children @@ -259,8 +256,8 @@ impl Visitable for Visitor { self.set_rect_(cx, rect, hints); } - fn find_id(&mut self, coord: Coord) -> Option { - self.find_id_(coord) + fn try_probe(&mut self, coord: Coord) -> Option { + self.try_probe_(coord) } fn draw(&mut self, draw: DrawCx) { @@ -281,8 +278,8 @@ impl<'a> Visitable for Single<'a> { self.widget.set_rect(cx, rect, hints); } - fn find_id(&mut self, coord: Coord) -> Option { - self.widget.find_id(coord) + fn try_probe(&mut self, coord: Coord) -> Option { + self.widget.try_probe(coord) } fn draw(&mut self, mut draw: DrawCx) { @@ -305,8 +302,8 @@ impl Visitable for Align { self.child.set_rect(cx, rect, hints); } - fn find_id(&mut self, coord: Coord) -> Option { - self.child.find_id(coord) + fn try_probe(&mut self, coord: Coord) -> Option { + self.child.try_probe(coord) } fn draw(&mut self, draw: DrawCx) { @@ -336,8 +333,8 @@ impl<'a, C: Visitable> Visitable for Pack<'a, C> { self.child.set_rect(cx, rect, hints); } - fn find_id(&mut self, coord: Coord) -> Option { - self.child.find_id(coord) + fn try_probe(&mut self, coord: Coord) -> Option { + self.child.try_probe(coord) } fn draw(&mut self, draw: DrawCx) { @@ -372,8 +369,8 @@ impl Visitable for Margins { self.child.set_rect(cx, rect, hints); } - fn find_id(&mut self, coord: Coord) -> Option { - self.child.find_id(coord) + fn try_probe(&mut self, coord: Coord) -> Option { + self.child.try_probe(coord) } fn draw(&mut self, draw: DrawCx) { @@ -405,8 +402,8 @@ impl<'a, C: Visitable> Visitable for Frame<'a, C> { self.child.set_rect(cx, child_rect, hints); } - fn find_id(&mut self, coord: Coord) -> Option { - self.child.find_id(coord) + fn try_probe(&mut self, coord: Coord) -> Option { + self.child.try_probe(coord) } fn draw(&mut self, mut draw: DrawCx) { @@ -438,7 +435,7 @@ impl<'a, C: Visitable> Visitable for Button<'a, C> { self.child.set_rect(cx, child_rect, AlignHints::CENTER); } - fn find_id(&mut self, _: Coord) -> Option { + fn try_probe(&mut self, _: Coord) -> Option { // Buttons steal clicks, hence Button never returns ID of content None } @@ -486,11 +483,11 @@ where } } - fn find_id(&mut self, coord: Coord) -> Option { + fn try_probe(&mut self, coord: Coord) -> Option { // TODO(opt): more efficient search strategy? for i in 0..self.children.len() { if let Some(child) = self.children.get_item(i) { - if let Some(id) = child.find_id(coord) { + if let Some(id) = child.try_probe(coord) { return Some(id); } } @@ -534,10 +531,10 @@ where } } - fn find_id(&mut self, coord: Coord) -> Option { + fn try_probe(&mut self, coord: Coord) -> Option { for i in 0..self.children.len() { if let Some(child) = self.children.get_item(i) { - if let Some(id) = child.find_id(coord) { + if let Some(id) = child.try_probe(coord) { return Some(id); } } @@ -590,11 +587,11 @@ where } } - fn find_id(&mut self, coord: Coord) -> Option { + fn try_probe(&mut self, coord: Coord) -> Option { // TODO(opt): more efficient search strategy? for i in 0..self.children.len() { if let Some(child) = self.children.get_item(i) { - if let Some(id) = child.find_id(coord) { + if let Some(id) = child.try_probe(coord) { return Some(id); } } diff --git a/crates/kas-core/src/root.rs b/crates/kas-core/src/root.rs index 8be1851ad..d8c73ef6d 100644 --- a/crates/kas-core/src/root.rs +++ b/crates/kas-core/src/root.rs @@ -146,7 +146,7 @@ impl_scope! { self.inner.set_rect(cx, Rect::new(p_in, s_in), hints); } - fn find_id(&mut self, _: Coord) -> Option { + fn probe(&mut self, _: Coord) -> Id { unimplemented!() } @@ -156,29 +156,29 @@ impl_scope! { } impl Self { - pub(crate) fn find_id(&mut self, data: &Data, coord: Coord) -> Option { + pub(crate) fn try_probe(&mut self, data: &Data, coord: Coord) -> Option { if !self.core.rect.contains(coord) { return None; } for (_, popup, translation) in self.popups.iter_mut().rev() { - if let Some(Some(id)) = self.inner.as_node(data).find_node(&popup.id, |mut node| node.find_id(coord + *translation)) { + if let Some(Some(id)) = self.inner.as_node(data).find_node(&popup.id, |mut node| node.try_probe(coord + *translation)) { return Some(id); } } if self.bar_h > 0 { - if let Some(id) = self.title_bar.find_id(coord) { + if let Some(id) = self.title_bar.try_probe(coord) { return Some(id); } } - self.inner.find_id(coord) - .or_else(|| self.b_w.find_id(coord)) - .or_else(|| self.b_e.find_id(coord)) - .or_else(|| self.b_n.find_id(coord)) - .or_else(|| self.b_s.find_id(coord)) - .or_else(|| self.b_nw.find_id(coord)) - .or_else(|| self.b_ne.find_id(coord)) - .or_else(|| self.b_sw.find_id(coord)) - .or_else(|| self.b_se.find_id(coord)) + self.inner.try_probe(coord) + .or_else(|| self.b_w.try_probe(coord)) + .or_else(|| self.b_e.try_probe(coord)) + .or_else(|| self.b_n.try_probe(coord)) + .or_else(|| self.b_s.try_probe(coord)) + .or_else(|| self.b_nw.try_probe(coord)) + .or_else(|| self.b_ne.try_probe(coord)) + .or_else(|| self.b_sw.try_probe(coord)) + .or_else(|| self.b_se.try_probe(coord)) .or_else(|| Some(self.id())) } diff --git a/crates/kas-macros/src/make_layout.rs b/crates/kas-macros/src/make_layout.rs index d62daaf85..072fcec98 100644 --- a/crates/kas-macros/src/make_layout.rs +++ b/crates/kas-macros/src/make_layout.rs @@ -195,18 +195,15 @@ impl Tree { ::kas::layout::LayoutVisitor::layout_visitor(self).set_rect(cx, rect, hints); } - fn find_id(&mut self, coord: ::kas::geom::Coord) -> Option<::kas::Id> { + fn probe(&mut self, coord: ::kas::geom::Coord) -> ::kas::Id { use ::kas::{Layout, LayoutExt, layout::LayoutVisitor}; #[cfg(debug_assertions)] #core_path.status.require_rect(&#core_path.id); - if !self.rect().contains(coord) { - return None; - } let coord = coord + self.translation(); self.layout_visitor() - .find_id(coord) - .or_else(|| Some(self.id())) + .try_probe(coord) + .unwrap_or_else(|| self.id()) } fn draw(&mut self, draw: ::kas::theme::DrawCx) { diff --git a/crates/kas-macros/src/widget.rs b/crates/kas-macros/src/widget.rs index 6f9b6c475..6708151d4 100644 --- a/crates/kas-macros/src/widget.rs +++ b/crates/kas-macros/src/widget.rs @@ -383,9 +383,9 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul let mut fn_nav_next_err = None; let mut fn_size_rules = None; let mut set_rect = quote! { self.#core.rect = rect; }; - let mut find_id = quote! { + let mut probe = quote! { use ::kas::{Layout, LayoutExt}; - self.rect().contains(coord).then(|| self.id()) + self.id() }; let mut fn_draw = None; if let Some(Layout { tree, .. }) = args.layout.take() { @@ -422,16 +422,13 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul #core_path.rect = rect; ::kas::layout::LayoutVisitor::layout_visitor(self).set_rect(cx, rect, hints); }; - find_id = quote! { + probe = quote! { use ::kas::{Layout, LayoutExt, layout::LayoutVisitor}; - if !self.rect().contains(coord) { - return None; - } let coord = coord + self.translation(); self.layout_visitor() - .find_id(coord) - .or_else(|| Some(self.id())) + .try_probe(coord) + .unwrap_or_else(|| self.id()) }; fn_draw = Some(quote! { fn draw(&mut self, draw: ::kas::theme::DrawCx) { @@ -460,12 +457,12 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul #set_rect } }; - let fn_find_id = quote! { - fn find_id(&mut self, coord: ::kas::geom::Coord) -> Option<::kas::Id> { + let fn_probe = quote! { + fn probe(&mut self, coord: ::kas::geom::Coord) -> ::kas::Id { #[cfg(debug_assertions)] #core_path.status.require_rect(&#core_path.id); - #find_id + #probe } }; @@ -601,7 +598,7 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul } } - if let Some((index, _)) = item_idents.iter().find(|(_, ident)| *ident == "find_id") { + if let Some((index, _)) = item_idents.iter().find(|(_, ident)| *ident == "probe") { if let Some(ref core) = core_data { if let ImplItem::Fn(f) = &mut layout_impl.items[*index] { f.block.stmts.insert(0, parse_quote! { @@ -611,7 +608,16 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul } } } else { - layout_impl.items.push(Verbatim(fn_find_id)); + layout_impl.items.push(Verbatim(fn_probe)); + } + + if let Some((index, _)) = item_idents.iter().find(|(_, ident)| *ident == "try_probe") { + if let ImplItem::Fn(f) = &mut layout_impl.items[*index] { + emit_warning!( + f, + "Implementations are expected to impl `fn probe`, not `try_probe`" + ); + } } if let Some((index, _)) = item_idents.iter().find(|(_, ident)| *ident == "draw") { @@ -639,7 +645,7 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul #fn_size_rules #fn_set_rect #fn_nav_next - #fn_find_id + #fn_probe #fn_draw } }); diff --git a/crates/kas-macros/src/widget_derive.rs b/crates/kas-macros/src/widget_derive.rs index 21645b1c0..55b43cb5c 100644 --- a/crates/kas-macros/src/widget_derive.rs +++ b/crates/kas-macros/src/widget_derive.rs @@ -161,10 +161,10 @@ pub fn widget(_attr_span: Span, args: WidgetArgs, scope: &mut Scope) -> Result<( self.#inner.translation() } }; - let fn_find_id = quote! { + let fn_try_probe_forward = quote! { #[inline] - fn find_id(&mut self, coord: ::kas::geom::Coord) -> Option<::kas::Id> { - self.#inner.find_id(coord) + fn try_probe(&mut self, coord: ::kas::geom::Coord) -> Option<::kas::Id> { + self.#inner.try_probe(coord) } }; let fn_draw = quote! { @@ -280,8 +280,20 @@ pub fn widget(_attr_span: Span, args: WidgetArgs, scope: &mut Scope) -> Result<( layout_impl.items.push(Verbatim(fn_translation)); } - if !has_item("find_id") { - layout_impl.items.push(Verbatim(fn_find_id)); + if has_item("probe") { + // Use default Layout::try_probe impl + } else { + // Use default Layout::probe (unimplemented) + layout_impl.items.push(Verbatim(fn_try_probe_forward)); + } + + if let Some((index, _)) = item_idents.iter().find(|(_, ident)| *ident == "try_probe") { + if let syn::ImplItem::Fn(f) = &mut layout_impl.items[*index] { + emit_warning!( + f, + "Implementations are expected to impl `fn probe`, not `try_probe`" + ); + } } if !has_item("draw") { @@ -295,7 +307,7 @@ pub fn widget(_attr_span: Span, args: WidgetArgs, scope: &mut Scope) -> Result<( #fn_set_rect #fn_nav_next #fn_translation - #fn_find_id + #fn_try_probe_forward #fn_draw } }); diff --git a/crates/kas-view/src/list_view.rs b/crates/kas-view/src/list_view.rs index 8a5fbfca3..e3739747e 100644 --- a/crates/kas-view/src/list_view.rs +++ b/crates/kas-view/src/list_view.rs @@ -553,20 +553,16 @@ impl_scope! { self.scroll_offset() } - fn find_id(&mut self, coord: Coord) -> Option { - if !self.rect().contains(coord) { - return None; - } - + fn probe(&mut self, coord: Coord) -> Id { let coord = coord + self.scroll.offset(); for child in &mut self.widgets[..self.cur_len.cast()] { if child.key.is_some() { - if let Some(id) = child.widget.find_id(coord) { - return Some(id); + if let Some(id) = child.widget.try_probe(coord) { + return id; } } } - Some(self.id()) + self.id() } fn draw(&mut self, mut draw: DrawCx) { diff --git a/crates/kas-view/src/matrix_view.rs b/crates/kas-view/src/matrix_view.rs index 4874d27a9..3c0b08e42 100644 --- a/crates/kas-view/src/matrix_view.rs +++ b/crates/kas-view/src/matrix_view.rs @@ -487,21 +487,17 @@ impl_scope! { self.scroll_offset() } - fn find_id(&mut self, coord: Coord) -> Option { - if !self.rect().contains(coord) { - return None; - } - + fn probe(&mut self, coord: Coord) -> Id { let num = self.num_children(); let coord = coord + self.scroll.offset(); for child in &mut self.widgets[..num] { if child.key.is_some() { - if let Some(id) = child.widget.find_id(coord) { - return Some(id); + if let Some(id) = child.widget.try_probe(coord) { + return id; } } } - Some(self.id()) + self.id() } fn draw(&mut self, mut draw: DrawCx) { diff --git a/crates/kas-widgets/src/adapt/with_label.rs b/crates/kas-widgets/src/adapt/with_label.rs index 4ba1c8529..436db984d 100644 --- a/crates/kas-widgets/src/adapt/with_label.rs +++ b/crates/kas-widgets/src/adapt/with_label.rs @@ -113,8 +113,8 @@ impl_scope! { } impl Layout for Self { - fn find_id(&mut self, coord: Coord) -> Option { - self.rect().contains(coord).then(|| self.inner.id()) + fn probe(&mut self, _: Coord) -> Id { + self.inner.id() } } } diff --git a/crates/kas-widgets/src/check_box.rs b/crates/kas-widgets/src/check_box.rs index b7b60b6a2..a9465a524 100644 --- a/crates/kas-widgets/src/check_box.rs +++ b/crates/kas-widgets/src/check_box.rs @@ -192,8 +192,8 @@ impl_scope! { shrink_to_text(&mut self.core.rect, dir, &self.label); } - fn find_id(&mut self, coord: Coord) -> Option { - self.rect().contains(coord).then(|| self.inner.id()) + fn probe(&mut self, _: Coord) -> Id { + self.inner.id() } } diff --git a/crates/kas-widgets/src/edit.rs b/crates/kas-widgets/src/edit.rs index b5639bf5c..810b04491 100644 --- a/crates/kas-widgets/src/edit.rs +++ b/crates/kas-widgets/src/edit.rs @@ -396,20 +396,16 @@ impl_scope! { self.update_scroll_bar(cx); } - fn find_id(&mut self, coord: Coord) -> Option { - if !self.rect().contains(coord) { - return None; - } - + fn probe(&mut self, coord: Coord) -> Id { if self.max_scroll_offset().1 > 0 { - if let Some(id) = self.bar.find_id(coord) { - return Some(id); + if let Some(id) = self.bar.try_probe(coord) { + return id; } } // If coord is over self but not over self.bar, we assign // the event to self.inner without further question. - Some(self.inner.id()) + self.inner.id() } fn draw(&mut self, mut draw: DrawCx) { @@ -692,8 +688,8 @@ impl_scope! { self.view_offset = self.view_offset.min(self.max_scroll_offset()); } - fn find_id(&mut self, coord: Coord) -> Option { - self.outer_rect.contains(coord).then_some(self.id()) + fn probe(&mut self, _: Coord) -> Id { + self.id() } fn draw(&mut self, mut draw: DrawCx) { @@ -1040,10 +1036,8 @@ impl EditField { /// /// Optionally, call this immediately after [`Self::set_rect`] with the /// "outer" rect and frame style. In this case, a frame will be drawn using - /// this `outer_rect` and `style`. The advantages are: - /// - /// - The "error state" background can correctly fill the frame - /// - Clicks on the frame get registered as clicks on self + /// this `outer_rect` and `style`. This allows the "error state" background + /// to correctly fill the frame. /// /// Any other widgets painted over the `outer_rect` should be drawn after /// the `EditField`. diff --git a/crates/kas-widgets/src/grid.rs b/crates/kas-widgets/src/grid.rs index 0fe98d701..ec2452dbf 100644 --- a/crates/kas-widgets/src/grid.rs +++ b/crates/kas-widgets/src/grid.rs @@ -100,18 +100,15 @@ impl_scope! { } } - fn find_id(&mut self, coord: Coord) -> Option { - if !self.rect().contains(coord) { - return None; - } + fn probe(&mut self, coord: Coord) -> Id { for n in 0..self.widgets.len() { if let Some(child) = self.widgets.get_mut_layout(n) { - if let Some(id) = child.find_id(coord) { - return Some(id); + if let Some(id) = child.try_probe(coord) { + return id; } } } - Some(self.id()) + self.id() } fn draw(&mut self, mut draw: DrawCx) { diff --git a/crates/kas-widgets/src/list.rs b/crates/kas-widgets/src/list.rs index 267354b88..50e1213f9 100644 --- a/crates/kas-widgets/src/list.rs +++ b/crates/kas-widgets/src/list.rs @@ -119,11 +119,12 @@ impl_scope! { } } - fn find_id(&mut self, coord: Coord) -> Option { + fn probe(&mut self, coord: Coord) -> Id { let solver = RowPositionSolver::new(self.direction); solver .find_child_mut(&mut self.widgets, coord) - .and_then(|child| child.find_id(coord)) + .and_then(|child| child.try_probe(coord)) + .unwrap_or_else(|| self.id()) } fn draw(&mut self, mut draw: DrawCx) { diff --git a/crates/kas-widgets/src/menu/menu_entry.rs b/crates/kas-widgets/src/menu/menu_entry.rs index f38646f01..14f458cda 100644 --- a/crates/kas-widgets/src/menu/menu_entry.rs +++ b/crates/kas-widgets/src/menu/menu_entry.rs @@ -31,13 +31,13 @@ impl_scope! { } impl Layout for Self { - fn find_id(&mut self, coord: Coord) -> Option { - self.rect().contains(coord).then(|| self.id()) + fn probe(&mut self, _: Coord) -> Id { + self.id() } fn draw(&mut self, mut draw: DrawCx) { draw.frame(self.rect(), FrameStyle::MenuEntry, Default::default()); - self.label.draw(draw); + draw.recurse(&mut self.label); } } @@ -120,8 +120,8 @@ impl_scope! { } impl Layout for Self { - fn find_id(&mut self, coord: Coord) -> Option { - self.rect().contains(coord).then(|| self.checkbox.id()) + fn probe(&mut self, _: Coord) -> Id { + self.checkbox.id() } fn draw(&mut self, mut draw: DrawCx) { diff --git a/crates/kas-widgets/src/menu/menubar.rs b/crates/kas-widgets/src/menu/menubar.rs index 1d994c34a..047b47a23 100644 --- a/crates/kas-widgets/src/menu/menubar.rs +++ b/crates/kas-widgets/src/menu/menubar.rs @@ -99,15 +99,12 @@ impl_scope! { } } - fn find_id(&mut self, coord: Coord) -> Option { - if !self.rect().contains(coord) { - return None; - } + fn probe(&mut self, coord: Coord) -> Id { let solver = RowPositionSolver::new(self.direction); solver .find_child_mut(&mut self.widgets, coord) - .and_then(|child| child.find_id(coord)) - .or_else(|| Some(self.id())) + .and_then(|child| child.try_probe(coord)) + .unwrap_or_else(|| self.id()) } fn draw(&mut self, mut draw: DrawCx) { diff --git a/crates/kas-widgets/src/menu/mod.rs b/crates/kas-widgets/src/menu/mod.rs index b4f29c331..17509c2c9 100644 --- a/crates/kas-widgets/src/menu/mod.rs +++ b/crates/kas-widgets/src/menu/mod.rs @@ -48,7 +48,7 @@ pub struct SubItems<'a> { /// Trait governing menus, sub-menus and menu-entries /// /// Implementations will automatically receive nav focus on mouse-hover, thus -/// should ensure that [`Layout::find_id`] returns the identifier of the widget +/// should ensure that [`Layout::probe`] returns the identifier of the widget /// which should be focussed, and that this widget has /// [`Events::navigable`] return true. #[autoimpl(for Box)] diff --git a/crates/kas-widgets/src/menu/submenu.rs b/crates/kas-widgets/src/menu/submenu.rs index e378e4469..66b078c90 100644 --- a/crates/kas-widgets/src/menu/submenu.rs +++ b/crates/kas-widgets/src/menu/submenu.rs @@ -101,13 +101,13 @@ impl_scope! { None } - fn find_id(&mut self, coord: Coord) -> Option { - self.rect().contains(coord).then(|| self.id()) + fn probe(&mut self, _: Coord) -> Id { + self.id() } fn draw(&mut self, mut draw: DrawCx) { draw.frame(self.rect(), FrameStyle::MenuEntry, Default::default()); - self.label.draw(draw.re_id(self.id())); + draw.recurse(&mut self.label); if self.mark.rect().size != Size::ZERO { draw.recurse(&mut self.mark); } @@ -354,17 +354,13 @@ impl_scope! { } } - fn find_id(&mut self, coord: Coord) -> Option { - if !self.rect().contains(coord) { - return None; - } - + fn probe(&mut self, coord: Coord) -> Id { for child in self.list.iter_mut() { - if let Some(id) = child.find_id(coord) { - return Some(id); + if let Some(id) = child.try_probe(coord) { + return id; } } - Some(self.id()) + self.id() } fn draw(&mut self, mut draw: DrawCx) { diff --git a/crates/kas-widgets/src/radio_box.rs b/crates/kas-widgets/src/radio_box.rs index 700629547..053d08c6c 100644 --- a/crates/kas-widgets/src/radio_box.rs +++ b/crates/kas-widgets/src/radio_box.rs @@ -153,8 +153,8 @@ impl_scope! { crate::check_box::shrink_to_text(&mut self.core.rect, dir, &self.label); } - fn find_id(&mut self, coord: Coord) -> Option { - self.rect().contains(coord).then(|| self.inner.id()) + fn probe(&mut self, _: Coord) -> Id { + self.inner.id() } } diff --git a/crates/kas-widgets/src/scroll.rs b/crates/kas-widgets/src/scroll.rs index 36089f352..5d8395ffb 100644 --- a/crates/kas-widgets/src/scroll.rs +++ b/crates/kas-widgets/src/scroll.rs @@ -123,11 +123,9 @@ impl_scope! { self.scroll_offset() } - fn find_id(&mut self, coord: Coord) -> Option { - if !self.rect().contains(coord) { - return None; - } - self.inner.find_id(coord + self.translation()) + fn probe(&mut self, coord: Coord) -> Id { + self.inner.try_probe(coord + self.translation()) + .unwrap_or_else(|| self.id()) } fn draw(&mut self, mut draw: DrawCx) { diff --git a/crates/kas-widgets/src/scroll_bar.rs b/crates/kas-widgets/src/scroll_bar.rs index 315623d3a..3fac1add1 100644 --- a/crates/kas-widgets/src/scroll_bar.rs +++ b/crates/kas-widgets/src/scroll_bar.rs @@ -290,14 +290,11 @@ impl_scope! { let _ = self.update_widgets(); } - fn find_id(&mut self, coord: Coord) -> Option { - if !self.rect().contains(coord) { - return None; - } + fn probe(&mut self, coord: Coord) -> Id { if self.invisible && self.max_value == 0 { - return None; + return self.id(); } - self.grip.find_id(coord).or_else(|| Some(self.id())) + self.grip.try_probe(coord).unwrap_or_else(|| self.id()) } fn draw(&mut self, mut draw: DrawCx) { @@ -500,15 +497,11 @@ impl_scope! { } } - fn find_id(&mut self, coord: Coord) -> Option { - if !self.rect().contains(coord) { - return None; - } - self.vert_bar - .find_id(coord) - .or_else(|| self.horiz_bar.find_id(coord)) - .or_else(|| self.inner.find_id(coord)) - .or_else(|| Some(self.id())) + fn probe(&mut self, coord: Coord) -> Id { + self.vert_bar.try_probe(coord) + .or_else(|| self.horiz_bar.try_probe(coord)) + .or_else(|| self.inner.try_probe(coord)) + .unwrap_or_else(|| self.id()) } fn draw(&mut self, mut draw: DrawCx) { diff --git a/crates/kas-widgets/src/scroll_label.rs b/crates/kas-widgets/src/scroll_label.rs index 3d732654a..072eb5542 100644 --- a/crates/kas-widgets/src/scroll_label.rs +++ b/crates/kas-widgets/src/scroll_label.rs @@ -61,12 +61,8 @@ impl_scope! { self.bar.set_value(cx, self.view_offset.1); } - fn find_id(&mut self, coord: Coord) -> Option { - if !self.rect().contains(coord) { - return None; - } - - self.bar.find_id(coord).or_else(|| Some(self.id())) + fn probe(&mut self, coord: Coord) -> Id { + self.bar.try_probe(coord).unwrap_or_else(|| self.id()) } fn draw(&mut self, mut draw: DrawCx) { diff --git a/crates/kas-widgets/src/scroll_text.rs b/crates/kas-widgets/src/scroll_text.rs index dcf13acb2..d42d1a91e 100644 --- a/crates/kas-widgets/src/scroll_text.rs +++ b/crates/kas-widgets/src/scroll_text.rs @@ -61,12 +61,8 @@ impl_scope! { self.bar.set_value(cx, self.view_offset.1); } - fn find_id(&mut self, coord: Coord) -> Option { - if !self.rect().contains(coord) { - return None; - } - - self.bar.find_id(coord).or_else(|| Some(self.id())) + fn probe(&mut self, coord: Coord) -> Id { + self.bar.try_probe(coord).unwrap_or_else(|| self.id()) } fn draw(&mut self, mut draw: DrawCx) { diff --git a/crates/kas-widgets/src/slider.rs b/crates/kas-widgets/src/slider.rs index e94a98ff2..ebfa1906a 100644 --- a/crates/kas-widgets/src/slider.rs +++ b/crates/kas-widgets/src/slider.rs @@ -311,16 +311,13 @@ impl_scope! { let _ = self.grip.set_size_and_offset(size, self.offset()); } - fn find_id(&mut self, coord: Coord) -> Option { - if !self.rect().contains(coord) { - return None; - } + fn probe(&mut self, coord: Coord) -> Id { if self.on_move.is_some() { - if let Some(id) = self.grip.find_id(coord) { - return Some(id); + if let Some(id) = self.grip.try_probe(coord) { + return id; } } - Some(self.id()) + self.id() } fn draw(&mut self, mut draw: DrawCx) { diff --git a/crates/kas-widgets/src/spinner.rs b/crates/kas-widgets/src/spinner.rs index 83bcb354e..6d8e4dc23 100644 --- a/crates/kas-widgets/src/spinner.rs +++ b/crates/kas-widgets/src/spinner.rs @@ -286,10 +286,10 @@ impl_scope! { self.edit.set_outer_rect(rect, FrameStyle::EditBox); } - fn find_id(&mut self, coord: Coord) -> Option { - self.b_up.find_id(coord) - .or_else(|| self.b_down.find_id(coord)) - .or_else(|| self.edit.find_id(coord)) + fn probe(&mut self, coord: Coord) -> Id { + self.b_up.try_probe(coord) + .or_else(|| self.b_down.try_probe(coord)) + .unwrap_or_else(|| self.edit.id()) } fn draw(&mut self, mut draw: DrawCx) { diff --git a/crates/kas-widgets/src/splitter.rs b/crates/kas-widgets/src/splitter.rs index e8683e5c4..44241efbf 100644 --- a/crates/kas-widgets/src/splitter.rs +++ b/crates/kas-widgets/src/splitter.rs @@ -213,9 +213,10 @@ impl_scope! { } } - fn find_id(&mut self, coord: Coord) -> Option { - if !self.rect().contains(coord) || !self.size_solved { - return None; + fn probe(&mut self, coord: Coord) -> Id { + if !self.size_solved { + debug_assert!(false); + return self.id(); } // find_child should gracefully handle the case that a coord is between @@ -224,22 +225,23 @@ impl_scope! { let solver = layout::RowPositionSolver::new(self.direction); if let Some(child) = solver.find_child_mut(&mut self.widgets, coord) { - return child.find_id(coord).or_else(|| Some(self.id())); + return child.try_probe(coord).unwrap_or_else(|| self.id()); } let solver = layout::RowPositionSolver::new(self.direction); if let Some(child) = solver.find_child_mut(&mut self.grips, coord) { - return child.find_id(coord).or_else(|| Some(self.id())); + return child.try_probe(coord).unwrap_or_else(|| self.id()); } - Some(self.id()) + self.id() } fn draw(&mut self, mut draw: DrawCx) { if !self.size_solved { + debug_assert!(false); return; } - // as with find_id, there's not much harm in invoking the solver twice + // as with probe, there's not much harm in invoking the solver twice let solver = layout::RowPositionSolver::new(self.direction); solver.for_children_mut(&mut self.widgets, draw.get_clip_rect(), |w| { diff --git a/crates/kas-widgets/src/stack.rs b/crates/kas-widgets/src/stack.rs index 1aa47a0c5..fefbe737b 100644 --- a/crates/kas-widgets/src/stack.rs +++ b/crates/kas-widgets/src/stack.rs @@ -127,12 +127,14 @@ impl_scope! { } } - fn find_id(&mut self, coord: Coord) -> Option { + fn probe(&mut self, coord: Coord) -> Id { if let Some(entry) = self.widgets.get_mut(self.active) { debug_assert_eq!(entry.1, State::Sized); - return entry.0.find_id(coord); + if let Some(id) = entry.0.try_probe(coord) { + return id; + } } - None + self.id() } fn draw(&mut self, mut draw: DrawCx) { diff --git a/crates/kas-widgets/src/tab_stack.rs b/crates/kas-widgets/src/tab_stack.rs index 08c09ab33..e89d78794 100644 --- a/crates/kas-widgets/src/tab_stack.rs +++ b/crates/kas-widgets/src/tab_stack.rs @@ -44,8 +44,8 @@ impl_scope! { } impl Layout for Self { - fn find_id(&mut self, coord: Coord) -> Option { - self.rect().contains(coord).then_some(self.id()) + fn probe(&mut self, _: Coord) -> Id { + self.id() } } diff --git a/examples/cursors.rs b/examples/cursors.rs index 0a00938fe..de34e1db9 100644 --- a/examples/cursors.rs +++ b/examples/cursors.rs @@ -22,9 +22,9 @@ impl_scope! { cursor: CursorIcon, } impl Layout for Self { - fn find_id(&mut self, coord: Coord) -> Option { + fn probe(&mut self, _: Coord) -> Id { // Steal mouse focus: hover points to self, not self.label - self.rect().contains(coord).then(|| self.id()) + self.id() } } }