Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Trait revision part 3: merge Component and Layout traits; put layout storage in widget_core!() #314

Merged
merged 16 commits into from
May 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 3 additions & 21 deletions crates/kas-core/src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,9 @@ use crate::geom::{Coord, Rect, Size};
use crate::layout::{Align, AlignHints, AxisInfo, SetRectMgr, SizeRules};
use crate::text::{format, AccelString, Text, TextApi};
use crate::theme::{DrawMgr, MarkStyle, SizeMgr, TextClass};
use crate::{TkAction, WidgetId};
use crate::{Layout, TkAction, WidgetId};
use kas_macros::{autoimpl, impl_scope};

/// Components are not true widgets, but support layout solving
///
/// TODO: since this is a sub-set of widget functionality, should [`crate::Widget`]
/// extend `Component`? (Significant trait revision would be required.)
pub trait Component {
/// Get size rules for the given axis
fn size_rules(&mut self, mgr: SizeMgr, axis: AxisInfo) -> SizeRules;

/// Apply a given `rect` to self
fn set_rect(&mut self, mgr: &mut SetRectMgr, rect: Rect, align: AlignHints);

/// Translate a coordinate to a [`WidgetId`]
fn find_id(&mut self, coord: Coord) -> Option<WidgetId>;

/// Draw the component and its children
fn draw(&mut self, draw: DrawMgr);
}

impl_scope! {
/// A label component
#[impl_default(where T: trait)]
Expand Down Expand Up @@ -110,7 +92,7 @@ impl_scope! {
}
}

impl Component for Self {
impl Layout for Self {
fn size_rules(&mut self, mgr: SizeMgr, axis: AxisInfo) -> SizeRules {
mgr.text_bound(&mut self.text, self.class, axis)
}
Expand Down Expand Up @@ -149,7 +131,7 @@ impl_scope! {
Mark { style, rect }
}
}
impl Component for Self {
impl Layout for Self {
fn size_rules(&mut self, mgr: SizeMgr, axis: AxisInfo) -> SizeRules {
mgr.mark(self.style, axis)
}
Expand Down
8 changes: 3 additions & 5 deletions crates/kas-core/src/core/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use super::Layout;
use super::{Widget, WidgetId};
use crate::event::EventMgr;
use crate::geom::Rect;
use crate::layout::{SetRectMgr, StorageChain};
use crate::layout::SetRectMgr;
use crate::{dir::Direction, WindowId};

#[cfg(feature = "winit")]
Expand Down Expand Up @@ -38,20 +38,18 @@ impl Icon {

/// Common widget data
///
/// All widgets should embed a `#[widget_core] core: CoreData` field.
/// This type may be used for a [`Widget`]'s `core: widget_core!()` field.
#[derive(Default, Debug)]
pub struct CoreData {
pub layout: StorageChain,
pub rect: Rect,
pub id: WidgetId,
}

/// Note: the clone has default-initialised layout storage and identifier.
/// Note: the clone has default-initialised identifier.
/// Configuration and layout solving is required as for any other widget.
impl Clone for CoreData {
fn clone(&self) -> Self {
CoreData {
layout: StorageChain::default(),
rect: self.rect,
id: WidgetId::default(),
}
Expand Down
185 changes: 97 additions & 88 deletions crates/kas-core/src/core/widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,16 @@ use std::fmt;

use crate::event::{self, Event, EventMgr, Response, Scroll};
use crate::geom::{Coord, Offset, Rect};
use crate::layout::{self, AlignHints, AxisInfo, SetRectMgr, SizeRules};
use crate::layout::{AlignHints, AxisInfo, SetRectMgr, SizeRules};
use crate::theme::{DrawMgr, SizeMgr};
use crate::util::IdentifyWidget;
use crate::WidgetId;
use kas_macros::autoimpl;

#[allow(unused)]
use crate::event::EventState;
#[allow(unused)]
use crate::layout::{self, AutoLayout};

impl dyn WidgetCore {
/// Forwards to the method defined on the type `Any`.
Expand Down Expand Up @@ -126,17 +128,22 @@ pub trait WidgetChildren: WidgetCore {

/// Positioning and drawing routines for widgets
///
/// This trait is part of the [`Widget`] family. It may be derived by
/// the [`crate::macros::widget`] macro, but is not by default.
/// This trait is related to [`Widget`], but may be used independently.
///
/// There are two methods of implementing this trait:
///
/// - Implement [`Self::layout`]. This alone suffices in many cases; other
/// methods may be overridden if necessary.
/// - Ignore [`Self::layout`] and implement [`Self::size_rules`] (to give the
/// widget size) and [`Self::draw`] (to make it show something). Other
/// methods may be required (e.g. [`Self::set_rect`] to position child
/// elements).
/// - Use the `#[widget{ layout = .. }]` property (see [`#[widget]`] documentation)
/// - Implement manually. When part of a widget, the [`Self::set_rect`] and
/// [`Self::find_id`] methods gain default implementations (generated by the
/// [`#[widget]`] macro).
///
/// Layout is resolved as follows:
///
/// 1. [`Widget::configure`] is called (widgets only), and may be used to load assets
/// 2. [`Self::size_rules`] is called at least once for each axis
/// 3. [`Self::set_rect`] is called to position elements. This may use data cached by `size_rules`.
/// 4. [`Self::find_id`] may be used to find the widget under the mouse and [`Self::draw`] to draw
/// elements.
///
/// Two methods of setting layout are possible:
///
Expand All @@ -146,51 +153,41 @@ pub trait WidgetChildren: WidgetCore {
/// 2. Only call [`Self::set_rect`]. For some widgets this is fine but for
/// others the internal layout will be incorrect.
///
/// [`derive(Widget)`]: https://docs.rs/kas/latest/kas/macros/index.html#the-derivewidget-macro
/// [`#[widget]`]: kas_macros::widget
#[autoimpl(for<T: trait + ?Sized> Box<T>)]
pub trait Layout: WidgetChildren {
/// Describe layout
///
/// This is purely a helper method used to implement other methods:
/// [`Self::size_rules`], [`Self::set_rect`], [`Widget::find_id`], [`Self::draw`].
/// If those methods are implemented directly (or their default
/// implementation over the default "empty" layout provided by this method
/// suffices), then this method need not be implemented.
///
/// The default implementation is for an empty layout (zero size required,
/// no child elements, no graphics).
#[inline]
fn layout(&mut self) -> layout::Layout<'_> {
Default::default()
}

pub trait Layout {
/// Get size rules for the given axis
///
/// For a description of the widget size model, see [`SizeRules`].
///
/// Typically, this method is called twice: first for the horizontal axis,
/// second for the vertical axis (with resolved width available through
/// the `axis` parameter allowing content wrapping).
/// For a description of the widget size model, see [`SizeRules`].
///
/// When called, this method should cache any data required to determine
/// internal layout (of child widgets and other components), especially data
/// which requires calling `size_rules` on children.
/// This method is expected to cache any size requirements calculated from
/// children which would be required for space allocations in
/// [`Self::set_rect`]. As an example, the horizontal [`SizeRules`] for a
/// row layout is the sum of the rules for each column (plus margins);
/// these per-column [`SizeRules`] are also needed to calculate column
/// widths in [`Self::size_rules`] once the available size is known.
///
/// This method may be implemented through [`Self::layout`] or directly.
/// A [`crate::layout::RulesSolver`] engine may be useful to calculate
/// requirements of complex layouts.
/// For row/column/grid layouts, a [`crate::layout::RulesSolver`] engine
/// may be useful.
///
/// [`Widget::configure`] will be called before this method and may
/// be used to load assets.
fn size_rules(&mut self, size_mgr: SizeMgr, axis: AxisInfo) -> SizeRules {
self.layout().size_rules(size_mgr, axis)
}
/// Default implementation:
///
/// - No default implementation, except,
/// - For a widget with the `layout` property, call [`AutoLayout::size_rules`]
///
/// [`#[widget]`]: kas_macros::widget
fn size_rules(&mut self, size_mgr: SizeMgr, axis: AxisInfo) -> SizeRules;

/// Set size and position
///
/// This is the final step to layout solving. It is expected that [`Self::size_rules`] is called
/// for each axis before this method; if this does not happen then layout may be incorrect.
/// Note that `size_rules` may not be called again before the next call to `set_rect`.
/// This method is called after [`Self::size_rules`] and may use values
/// cached by `size_rules` (in the case `size_rules` is not called first,
/// the widget may exhibit incorrect layout but should not panic). This
/// method should not write over values cached by `size_rules` since
/// `set_rect` may be called multiple times consecutively.
/// After `set_rect` is called, the widget must be ready for drawing and event handling.
///
/// The size of the assigned `rect` is normally at least the minimum size
Expand All @@ -203,13 +200,58 @@ pub trait Layout: WidgetChildren {
/// It is up to the widget to either stretch to occupy this space or align
/// itself within the excess space, according to the `align` hints provided.
///
/// This method may be implemented through [`Self::layout`] or directly.
/// The default implementation assigns `self.core.rect = rect`
/// and applies the layout described by [`Self::layout`].
/// Default implementation:
///
/// - Independent usage: no default
/// - For a widget without `layout` property, set `rect` field of `widget_core!()`
/// - For a widget with the `layout` property, set `rect` field of `widget_core!()` and
/// call [`AutoLayout::set_rect`]
///
/// Default: set `rect` of `widget_core!()` field. If `layout = ..` property
/// is used, also calls `<Self as AutoLayout>::set_rect`.
///
/// [`Stretch`]: crate::layout::Stretch
/// [`#[widget]`]: kas_macros::widget
fn set_rect(&mut self, mgr: &mut SetRectMgr, rect: Rect, align: AlignHints);

/// Translate a coordinate to a [`WidgetId`]
///
/// This method is used in event handling, translating a mouse click or
/// touch input to a widget.
/// Usually, this is the widget which draws the target coordinate, but
/// stealing focus is permitted: e.g. the `Button` widget will return its
/// own [`WidgetId`] when a user clicks on its inner content.
///
/// 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 unless:
///
/// - The `layout` property of [`#[widget]`] is not used but
/// there are child widgets
/// - Event stealing from child widgets is desired (but note that
/// `layout = button: ..;` does this already)
/// - The child widget is in a translated coordinate space *not equal* to
/// [`Widget::translation`]
///
/// To implement directly:
///
/// - Return `None` if `coord` is not within `self.rect()`
/// - Find the child which should respond to input at `coord`, if any, and
/// call `find_id` recursively on this child
/// - Otherwise return `self.id()`
///
/// Default implementation:
///
/// - Independent usage: no default
/// - For a widget without `layout` property, `self.rect().contains(coord).then(|| self.id())`
/// - For a widget with the `layout` property, return `None` if
/// `!self.rect().contains(coord)`, otherwise call [`AutoLayout::find_id`] with the
/// coord translated by [`Widget::translation`].
///
/// [`#[widget]`]: kas_macros::widget
fn find_id(&mut self, coord: Coord) -> Option<WidgetId>;

/// Draw a widget and its children
///
/// This method is invoked each frame to draw visible widgets. It should
Expand All @@ -218,10 +260,11 @@ pub trait Layout: WidgetChildren {
/// It is expected that [`Self::set_rect`] is called before this method,
/// but failure to do so should not cause a fatal error.
///
/// The default impl draws elements as defined by [`Self::layout`].
fn draw(&mut self, draw: DrawMgr) {
self.layout().draw(draw);
}
/// Default implementation:
///
/// - No default implementation, except,
/// - For a widget with the `layout` property, call [`AutoLayout::draw`]
fn draw(&mut self, draw: DrawMgr);
}

/// Widget trait
Expand All @@ -247,7 +290,7 @@ pub trait Layout: WidgetChildren {
///
/// [`derive(Widget)`]: https://docs.rs/kas/latest/kas/macros/index.html#the-derivewidget-macro
#[autoimpl(for<T: trait + ?Sized> Box<T>)]
pub trait Widget: Layout {
pub trait Widget: WidgetChildren + Layout {
/// Make an identifier for a child
///
/// Default impl: `self.id_ref().make_child(index)`
Expand Down Expand Up @@ -306,7 +349,7 @@ pub trait Widget: Layout {

/// Which cursor icon should be used on hover?
///
/// The "hovered" widget is determined by [`Widget::find_id`], thus is the
/// The "hovered" widget is determined by [`Layout::find_id`], thus is the
/// same widget which would receive click events. Other widgets do not
/// affect the cursor icon used.
///
Expand All @@ -322,7 +365,7 @@ pub trait Widget: Layout {
/// need implement this. Such widgets must also implement
/// [`Widget::handle_scroll`].
///
/// Affects event handling via [`Self::find_id`] and affects the positioning
/// Affects event handling via [`Layout::find_id`] and affects the positioning
/// of pop-up menus. [`Layout::draw`] must be implemented directly using
/// [`DrawMgr::with_clip_region`] to offset contents.
#[inline]
Expand Down Expand Up @@ -357,40 +400,6 @@ pub trait Widget: Layout {
crate::util::spatial_nav(reverse, from, self.num_children())
}

/// Translate a coordinate to a [`WidgetId`]
///
/// This method is used in event handling, translating a mouse click or
/// touch input to a widget and resolving a [`Widget::cursor_icon`].
/// Usually, this is the widget which draws the target coordinate, but
/// stealing focus is permitted: e.g. the `Button` widget handles clicks on
/// inner content, while the `CheckBox` widget forwards click events to its
/// `CheckBoxBare` component.
///
/// 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 unless:
///
/// - [`Layout::layout`] is not implemented and there are child widgets
/// - Event stealing from child widgets is desired (but note that
/// [`crate::layout::Layout::button`] does this already)
/// - The child widget is in a translated coordinate space *not equal* to
/// [`Self::translation`]
///
/// To implement directly:
///
/// - Return `None` if `coord` is not within `self.rect()`
/// - Find the child which should respond to input at `coord`, if any, and
/// call `find_id` recursively on this child
/// - Otherwise return `self.id()`
fn find_id(&mut self, coord: Coord) -> Option<WidgetId> {
if !self.rect().contains(coord) {
return None;
}
let coord = coord + self.translation();
self.layout().find_id(coord).or_else(|| Some(self.id()))
}

/// Handle an event sent to this widget
///
/// An [`Event`] is some form of user input, timer or notification.
Expand Down Expand Up @@ -457,7 +466,7 @@ pub trait Widget: Layout {
}

/// Extension trait over widgets
pub trait WidgetExt: WidgetChildren {
pub trait WidgetExt: Widget {
/// Get the widget's identifier
///
/// Note that the default-constructed [`WidgetId`] is *invalid*: any
Expand Down Expand Up @@ -526,4 +535,4 @@ pub trait WidgetExt: WidgetChildren {
}
}
}
impl<W: WidgetChildren + ?Sized> WidgetExt for W {}
impl<W: Widget + ?Sized> WidgetExt for W {}
4 changes: 2 additions & 2 deletions crates/kas-core/src/event/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ pub enum Event {
/// used, then the pop-up will be closed and the event sent again.
///
/// If `cur_id` is `None`, no widget was found at the coordinate (either
/// outside the window or [`crate::Widget::find_id`] failed).
/// outside the window or [`crate::Layout::find_id`] failed).
PressMove {
source: PressSource,
cur_id: Option<WidgetId>,
Expand All @@ -165,7 +165,7 @@ pub enum Event {
/// for the same mouse button or touched finger will be sent.
///
/// If `cur_id` is `None`, no widget was found at the coordinate (either
/// outside the window or [`crate::Widget::find_id`] failed).
/// outside the window or [`crate::Layout::find_id`] failed).
PressEnd {
source: PressSource,
end_id: Option<WidgetId>,
Expand Down
2 changes: 1 addition & 1 deletion crates/kas-core/src/event/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
//! ## Event delivery
//!
//! Events can be addressed only to a [`WidgetId`], so the first step (for
//! mouse and touch events) is to use [`crate::Widget::find_id`] to translate a
//! mouse and touch events) is to use [`crate::Layout::find_id`] to translate a
//! coordinate to a [`WidgetId`].
//!
//! Events are then sent via [`EventMgr::send`] which traverses the widget tree
Expand Down
Loading