Skip to content

Commit 681e845

Browse files
authored
Merge pull request #479 from kas-gui/work3
Replace LayoutVisitor with macro-generated methods
2 parents 21c94e7 + 0043683 commit 681e845

File tree

14 files changed

+727
-1178
lines changed

14 files changed

+727
-1178
lines changed

crates/kas-core/src/core/layout.rs

Lines changed: 24 additions & 308 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,14 @@
66
//! Layout, Tile and TileExt traits
77
88
use crate::event::ConfigCx;
9-
use crate::geom::{Coord, Offset, Rect};
9+
use crate::geom::{Coord, Rect};
1010
use crate::layout::{AlignHints, AxisInfo, SizeRules};
1111
use crate::theme::{DrawCx, SizeCx};
12-
use crate::util::IdentifyWidget;
13-
use crate::{HasId, Id};
12+
use crate::Id;
1413
use kas_macros::autoimpl;
1514

16-
#[allow(unused)] use super::{Events, Widget};
17-
#[allow(unused)]
18-
use crate::layout::{self, AlignPair, LayoutVisitor};
15+
#[allow(unused)] use super::{Events, Tile, Widget};
16+
#[allow(unused)] use crate::layout::{self, AlignPair};
1917
#[allow(unused)] use kas_macros as macros;
2018

2119
/// Positioning and drawing routines for [`Widget`]s
@@ -86,11 +84,8 @@ pub trait Layout {
8684
///
8785
/// ## Default implementation
8886
///
89-
/// The `#[widget]` macro
90-
/// [may generate a default implementation](macros::widget#layout-1) by
91-
/// implementing [`LayoutVisitor`] for `Self`.
92-
/// In this case the default impl of this method is
93-
/// `self.layout_visitor().size_rules(/* ... */)`.
87+
/// The `#[widget]` macro may implement this method as a wrapper over
88+
/// the corresponding [`MacroDefinedLayout`].
9489
fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules;
9590

9691
/// Set size and position
@@ -126,11 +121,8 @@ pub trait Layout {
126121
///
127122
/// ## Default implementation
128123
///
129-
/// The `#[widget]` macro
130-
/// [may generate a default implementation](macros::widget#layout-1) by
131-
/// implementing [`LayoutVisitor`] for `Self`.
132-
/// In this case the default impl of this method is
133-
/// `self.layout_visitor().set_rect(/* ... */)`.
124+
/// The `#[widget]` macro may implement this method as a wrapper over
125+
/// the corresponding [`MacroDefinedLayout`].
134126
///
135127
/// [`Stretch`]: crate::layout::Stretch
136128
fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints);
@@ -185,11 +177,8 @@ pub trait Layout {
185177
///
186178
/// ## Default implementation
187179
///
188-
/// The `#[widget]` macro
189-
/// [may generate a default implementation](macros::widget#layout-1) by
190-
/// implementing [`LayoutVisitor`] for `Self`.
191-
/// In this case the default impl of this method is
192-
/// `self.layout_visitor().draw(/* ... */)`.
180+
/// The `#[widget]` macro may implement this method as a wrapper over
181+
/// the corresponding [`MacroDefinedLayout`].
193182
///
194183
/// ## Method modification
195184
///
@@ -203,297 +192,24 @@ pub trait Layout {
203192
fn draw(&mut self, draw: DrawCx);
204193
}
205194

206-
/// Positioning and drawing routines for [`Widget`]s
207-
///
208-
/// `Tile` is a super-trait of [`Widget`] which:
209-
///
210-
/// - Has no [`Data`](Widget::Data) parameter
211-
/// - Supports read-only tree reflection: [`Self::get_child`]
212-
/// - Provides some basic operations: [`Self::id_ref`], [`Self::rect`]
213-
/// - Covers sizing and drawing operations from [`Layout`]
214-
///
215-
/// `Tile` may not be implemented directly; it will be implemented by the
216-
/// [`#widget`] macro.
195+
/// Macro-defined layout
217196
///
218-
/// # Tree reflection
197+
/// This trait is a copy of [`Layout`], implemented automatically for custom
198+
/// widgets with macro-defined layout. It may be useful for small hacks where
199+
/// the macro-generated layout implementations should still be used, but with
200+
/// some addition or modification of inputs.
219201
///
220-
/// `Tile` offers a reflection API over the widget tree via
221-
/// [`Tile::get_child`]. This is limited to read-only functions, and thus
222-
/// cannot directly violate the widget lifecycle, however note that the
223-
/// [`id_ref`](Self::id_ref) could be invalid or could be valid but refer to a
224-
/// node which has not yet been sized and positioned (and thus which it is not
225-
/// valid to send events to).
226-
///
227-
/// [`#widget`]: macros::widget
228-
#[autoimpl(for<T: trait + ?Sized> &'_ mut T, Box<T>)]
229-
pub trait Tile: Layout {
230-
/// Get as a `dyn Tile`
231-
///
232-
/// This method is implemented by the `#[widget]` macro.
233-
fn as_tile(&self) -> &dyn Tile {
234-
unimplemented!() // make rustdoc show that this is a provided method
235-
}
236-
237-
/// Get a reference to the widget's identifier
238-
///
239-
/// The widget identifier is assigned when the widget is configured (see
240-
/// [`Events::configure`] and [`Events::configure_recurse`]). In case the
241-
/// [`Id`] is accessed before this, it will be [invalid](Id#invalid-state).
242-
/// The identifier *may* change when widgets which are descendants of some
243-
/// dynamic layout are reconfigured.
244-
///
245-
/// This method is implemented by the `#[widget]` macro.
246-
fn id_ref(&self) -> &Id {
247-
unimplemented!() // make rustdoc show that this is a provided method
248-
}
249-
250-
/// Get the widget's identifier
251-
///
252-
/// This method returns a [`Clone`] of [`Self::id_ref`]. Since cloning an
253-
/// `Id` is [very cheap](Id#representation), this can mostly be ignored.
254-
///
255-
/// The widget identifier is assigned when the widget is configured (see
256-
/// [`Events::configure`] and [`Events::configure_recurse`]). In case the
257-
/// [`Id`] is accessed before this, it will be [invalid](Id#invalid-state).
258-
/// The identifier *may* change when widgets which are descendants of some
259-
/// dynamic layout are reconfigured.
260-
#[inline]
261-
fn id(&self) -> Id {
262-
self.id_ref().clone()
263-
}
264-
265-
/// Get the widget's region, relative to its parent.
266-
///
267-
/// This method is usually implemented by the `#[widget]` macro.
268-
/// See also [`kas::widget_set_rect`].
269-
fn rect(&self) -> Rect;
270-
271-
/// Get the name of the widget struct
272-
///
273-
/// This method is implemented by the `#[widget]` macro.
274-
fn widget_name(&self) -> &'static str {
275-
unimplemented!() // make rustdoc show that this is a provided method
276-
}
277-
278-
/// Get the number of child widgets
279-
///
280-
/// Every value in the range `0..self.num_children()` is a valid child
281-
/// index.
282-
///
283-
/// This method is usually implemented automatically by the `#[widget]`
284-
/// macro. It should be implemented directly if and only if
285-
/// [`Tile::get_child`] and [`Widget::for_child_node`] are
286-
/// implemented directly.
287-
fn num_children(&self) -> usize {
288-
unimplemented!() // make rustdoc show that this is a provided method
289-
}
290-
291-
/// Access a child as a `dyn Tile`
292-
///
293-
/// This method returns `None` exactly when `index >= self.num_children()`.
294-
///
295-
/// This method is usually implemented automatically by the `#[widget]`
296-
/// macro.
297-
fn get_child(&self, index: usize) -> Option<&dyn Tile> {
298-
let _ = index;
299-
unimplemented!() // make rustdoc show that this is a provided method
300-
}
301-
302-
/// Find the child which is an ancestor of this `id`, if any
303-
///
304-
/// If `Some(index)` is returned, this is *probably* but not guaranteed
305-
/// to be a valid child index.
306-
///
307-
/// The default implementation simply uses [`Id::next_key_after`].
308-
/// Widgets may choose to assign children custom keys by overriding this
309-
/// method and [`Events::make_child_id`].
310-
#[inline]
311-
fn find_child_index(&self, id: &Id) -> Option<usize> {
312-
id.next_key_after(self.id_ref())
313-
}
314-
315-
/// Navigation in spatial order
316-
///
317-
/// Controls <kbd>Tab</kbd> navigation order of children.
318-
/// This method should:
319-
///
320-
/// - Return `None` if there is no (next) navigable child
321-
/// - In the case there are navigable children and `from == None`, return
322-
/// the index of the first (or last if `reverse`) navigable child
323-
/// - In the case there are navigable children and `from == Some(index)`,
324-
/// it may be expected that `from` is the output of a previous call to
325-
/// this method; the method should return the next (or previous if
326-
/// `reverse`) navigable child (if any)
327-
///
328-
/// The return value mut be `None` or `Some(index)` where
329-
/// `self.get_child(index).is_some()` (see [`Tile::get_child`]).
330-
///
331-
/// It is not required that all children (all indices `i` for
332-
/// `i < self.num_children()`) are returnable from this method.
333-
///
334-
/// Default (macro generated) implementation:
335-
///
336-
/// - Generated from `#[widget]`'s layout property, if used (not always possible!)
337-
/// - Otherwise, iterate through children in order of definition
338-
fn nav_next(&self, reverse: bool, from: Option<usize>) -> Option<usize> {
339-
let _ = (reverse, from);
340-
unimplemented!() // make rustdoc show that this is a provided method
341-
}
202+
/// TODO: add an example
203+
pub trait MacroDefinedLayout {
204+
/// Get size rules for the given axis
205+
fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules;
342206

343-
/// Get translation of children relative to this widget
344-
///
345-
/// Usually this is zero; only widgets with scrollable or offset content
346-
/// *and* child widgets need to implement this.
347-
/// Such widgets must also implement [`Events::handle_scroll`].
348-
///
349-
/// Affects event handling via [`Tile::probe`] and affects the positioning
350-
/// of pop-up menus. [`Layout::draw`] must be implemented directly using
351-
/// [`DrawCx::with_clip_region`] to offset contents.
352-
///
353-
/// Default implementation: return [`Offset::ZERO`]
354-
#[inline]
355-
fn translation(&self) -> Offset {
356-
Offset::ZERO
357-
}
207+
/// Set size and position
208+
fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints);
358209

359210
/// Probe a coordinate for a widget's [`Id`]
360-
///
361-
/// Returns the [`Id`] of the widget expected to handle clicks and touch
362-
/// events at the given `coord`. Typically this is the lowest descendant in
363-
/// the widget tree at the given `coord`, but it is not required to be; e.g.
364-
/// a `Button` may use an inner widget as a label but return its own [`Id`]
365-
/// to indicate that the button (not the inner label) handles clicks.
366-
///
367-
/// # Calling
368-
///
369-
/// **Prefer to call [`Layout::try_probe`] instead**.
370-
///
371-
/// ## Call order
372-
///
373-
/// It is expected that [`Layout::set_rect`] is called before this method,
374-
/// but failure to do so should not cause a fatal error.
375-
///
376-
/// # Implementation
377-
///
378-
/// The callee may usually assume that it occupies `coord` and may thus
379-
/// return its own [`Id`] when no child occupies the input `coord`.
380-
///
381-
/// ## Default implementation
382-
///
383-
/// ## Default implementation
384-
///
385-
/// The `#[widget]` macro
386-
/// [may generate a default implementation](macros::widget#layout-1) by
387-
/// implementing [`LayoutVisitor`] for `Self`.
388-
/// In this case the default impl of this method is
389-
/// `self.layout_visitor().set_rect(/* ... */)`.
390-
/// The underlying implementation considers all children of the `layout`
391-
/// property and of fields, like this:
392-
/// ```ignore
393-
/// let coord = coord + self.translation();
394-
/// for child in ITER_OVER_CHILDREN {
395-
/// if let Some(id) = child.try_probe(coord) {
396-
/// return Some(id);
397-
/// }
398-
/// }
399-
/// self.id()
400-
/// ```
401-
fn probe(&mut self, coord: Coord) -> Id
402-
where
403-
Self: Sized,
404-
{
405-
let _ = coord;
406-
unimplemented!() // make rustdoc show that this is a provided method
407-
}
408-
}
409-
410-
impl<W: Tile + ?Sized> HasId for &W {
411-
#[inline]
412-
fn has_id(self) -> Id {
413-
self.id_ref().clone()
414-
}
415-
}
416-
417-
impl<W: Tile + ?Sized> HasId for &mut W {
418-
#[inline]
419-
fn has_id(self) -> Id {
420-
self.id_ref().clone()
421-
}
422-
}
423-
424-
/// Extension trait over widgets
425-
pub trait TileExt: Tile {
426-
/// Test widget identifier for equality
427-
///
428-
/// This method may be used to test against `Id`, `Option<Id>`
429-
/// and `Option<&Id>`.
430-
#[inline]
431-
fn eq_id<T>(&self, rhs: T) -> bool
432-
where
433-
Id: PartialEq<T>,
434-
{
435-
*self.id_ref() == rhs
436-
}
437-
438-
/// Display as "StructName#Id"
439-
#[inline]
440-
fn identify(&self) -> IdentifyWidget {
441-
IdentifyWidget(self.widget_name(), self.id_ref())
442-
}
443-
444-
/// Check whether `id` is self or a descendant
445-
///
446-
/// This function assumes that `id` is a valid widget.
447-
#[inline]
448-
fn is_ancestor_of(&self, id: &Id) -> bool {
449-
self.id_ref().is_ancestor_of(id)
450-
}
451-
452-
/// Check whether `id` is not self and is a descendant
453-
///
454-
/// This function assumes that `id` is a valid widget.
455-
#[inline]
456-
fn is_strict_ancestor_of(&self, id: &Id) -> bool {
457-
!self.eq_id(id) && self.id_ref().is_ancestor_of(id)
458-
}
459-
460-
/// Run a closure on all children
461-
fn for_children(&self, mut f: impl FnMut(&dyn Tile)) {
462-
for index in 0..self.num_children() {
463-
if let Some(child) = self.get_child(index) {
464-
f(child);
465-
}
466-
}
467-
}
468-
469-
/// Run a fallible closure on all children
470-
///
471-
/// Returns early in case of error.
472-
fn for_children_try<E>(&self, mut f: impl FnMut(&dyn Tile) -> Result<(), E>) -> Result<(), E> {
473-
let mut result = Ok(());
474-
for index in 0..self.num_children() {
475-
if let Some(child) = self.get_child(index) {
476-
result = f(child);
477-
}
478-
if result.is_err() {
479-
break;
480-
}
481-
}
482-
result
483-
}
211+
fn try_probe(&mut self, coord: Coord) -> Option<Id>;
484212

485-
/// Find the descendant with this `id`, if any
486-
///
487-
/// Since `id` represents a path, this operation is normally `O(d)` where
488-
/// `d` is the depth of the path (depending on widget implementations).
489-
fn find_widget(&self, id: &Id) -> Option<&dyn Tile> {
490-
if let Some(child) = self.find_child_index(id).and_then(|i| self.get_child(i)) {
491-
child.find_widget(id)
492-
} else if self.eq_id(id) {
493-
Some(self.as_tile())
494-
} else {
495-
None
496-
}
497-
}
213+
/// Draw a widget and its children
214+
fn draw(&mut self, draw: DrawCx);
498215
}
499-
impl<W: Tile + ?Sized> TileExt for W {}

crates/kas-core/src/core/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ mod data;
1010
mod layout;
1111
mod node;
1212
mod scroll_traits;
13+
mod tile;
1314
mod widget;
1415
mod widget_id;
1516

@@ -22,5 +23,6 @@ pub use data::*;
2223
pub use layout::*;
2324
pub use node::Node;
2425
pub use scroll_traits::*;
26+
pub use tile::*;
2527
pub use widget::*;
2628
pub use widget_id::*;

0 commit comments

Comments
 (0)