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

Spinner widget, event stealing, async "loading screen", Widget trait doc #319

Merged
merged 18 commits into from
May 13, 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
4 changes: 3 additions & 1 deletion crates/kas-core/src/core/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,5 +117,7 @@ pub trait Window: Widget {
/// This allows for actions on destruction.
///
/// Default: do nothing.
fn handle_closure(&mut self, _mgr: &mut EventMgr) {}
fn handle_closure(&mut self, mgr: &mut EventMgr) {
let _ = mgr;
}
}
221 changes: 175 additions & 46 deletions crates/kas-core/src/core/widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,18 @@ use kas_macros::autoimpl;
use crate::event::EventState;
#[allow(unused)]
use crate::layout::{self, AutoLayout};
#[allow(unused)]
use crate::TkAction;
#[allow(unused)]
use kas_macros as macros;

/// Base widget functionality
/// Base functionality for [`Widget`]s
///
/// See the [`Widget`] trait for documentation of the widget family.
/// # Implementing WidgetCore
///
/// This trait **must** be implement by the [`derive(Widget)`] macro.
/// Users **must not** implement this `WidgetCore` trait manually or may face
/// unexpected breaking changes.
///
/// [`derive(Widget)`]: https://docs.rs/kas/latest/kas/macros/index.html#the-derivewidget-macro
/// Implementations of this trait are generated via macro.
/// **Directly implementing this trait is not supported**.
/// See [`Widget`] trait documentation.
#[autoimpl(for<'a, T: trait + ?Sized> &'a mut T, Box<T>)]
pub trait WidgetCore: fmt::Debug {
/// Get the widget's identifier
Expand All @@ -50,17 +52,25 @@ pub trait WidgetCore: fmt::Debug {
fn as_widget_mut(&mut self) -> &mut dyn Widget;
}

/// Listing of a widget's children
/// Listing of a [`Widget`]'s children
///
/// This trait is part of the [`Widget`] family and is derived by
/// [`derive(Widget)`] unless `#[widget(children = noauto)]` is used.
/// This trait enumerates child widgets (that is, components of the widget which
/// are themselves widgets).
///
/// Dynamic widgets must implement this trait manually, since [`derive(Widget)`]
/// cannot currently handle fields like `Vec<SomeWidget>`. Additionally, any
/// parent adding child widgets must ensure they get configured by calling
/// [`SetRectMgr::configure`].
/// Enumerated widgets are automatically configured, via recursion, when their
/// parent is. See [`Widget::configure`].
///
/// [`derive(Widget)`]: https://docs.rs/kas/latest/kas/macros/index.html#the-derivewidget-macro
/// # Implementing WidgetChildren
///
/// Implementations of this trait are usually generated via macro.
/// See [`Widget`] trait documentation.
///
/// In a few cases, namely widgets which may add/remove children dynamically,
/// this trait should be implemented directly.
///
/// Note that parents are responsible for ensuring that newly added children
/// get configured, either by sending [`TkAction::RECONFIGURE`] by calling
/// [`SetRectMgr::configure`].
#[autoimpl(for<'a, T: trait + ?Sized> &'a mut T, Box<T>)]
pub trait WidgetChildren: WidgetCore {
/// Get the number of child widgets
Expand Down Expand Up @@ -99,16 +109,24 @@ pub trait WidgetChildren: WidgetCore {
}
}

/// Positioning and drawing routines for widgets
/// Positioning and drawing routines for [`Widget`]s
///
/// This trait is related to [`Widget`], but may be used independently.
///
/// There are two methods of implementing this trait:
/// # Implementing Layout
///
/// - 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).
/// There are three cases:
///
/// - For a non-widget, all methods must be implemented directly.
/// - For a [`Widget`] without using the `layout` macro property,
/// the [`Self::set_rect`] and [`Self::find_id`] methods gain default
/// implementations (generated via macro).
/// - For a [`Widget`] where the `#[widget{ layout = .. }]` property
/// is set (see [`macros::widget`] documentation), all methods have a
/// default implementation. Custom implementations may use [`AutoLayout`] to
/// access these default implementations.
///
/// # Solving layout
///
/// Layout is resolved as follows:
///
Expand All @@ -118,15 +136,9 @@ pub trait WidgetChildren: WidgetCore {
/// 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:
///
/// 1. Use [`layout::solve_size_rules`] or [`layout::SolveCache`] to solve and
/// set layout. This functions by calling [`Self::size_rules`] for each
/// axis then calling [`Self::set_rect`].
/// 2. Only call [`Self::set_rect`]. For some widgets this is fine but for
/// others the internal layout will be incorrect.
///
/// [`#[widget]`]: kas_macros::widget
/// Usually, [`Layout::size_rules`] methods are called recursively. To instead
/// solve layout for a single widget/layout object, it may be useful to use
/// [`layout::solve_size_rules`] or [`layout::SolveCache`].
#[autoimpl(for<'a, T: trait + ?Sized> &'a mut T, Box<T>)]
pub trait Layout {
/// Get size rules for the given axis
Expand Down Expand Up @@ -240,28 +252,132 @@ pub trait Layout {
fn draw(&mut self, draw: DrawMgr);
}

/// Widget trait
/// The Widget trait
///
/// Widgets must implement a family of traits, of which this trait is the final
/// Widgets implement a family of traits, of which this trait is the final
/// member:
///
/// - [`WidgetCore`] — base functionality (this trait is *always* derived)
/// - [`WidgetChildren`] — enumerates children and provides methods derived
/// from this
/// - [`Layout`] — handles sizing and positioning of self and children
/// - [`Widget`] — the final trait
/// - [`WidgetCore`] — base functionality
/// - [`WidgetChildren`] — enumerates children
/// - [`Layout`] — handles sizing and positioning for self and children
/// - [`Widget`] — configuration, some aspects of layout, event handling
///
/// # Implementing Widget
///
/// To implement a widget, use the [`macros::widget`] macro. **This is the
/// only supported method of implementing `Widget`.**
///
/// The [`macros::widget`] macro only works within [`macros::impl_scope`].
/// Other trait implementations can be detected within this scope:
///
/// Widgets **must** use the [`derive(Widget)`] macro to implement at least
/// [`WidgetCore`] and [`Widget`]; these two traits **must not** be implemented
/// manually or users may face unexpected breaking changes.
/// This macro can optionally implement *all* above traits, and by default will
/// implement *all except for `Layout`*. This opt-out derive behaviour means
/// that adding additional traits into the family is not a breaking change.
/// - [`WidgetCore`] is always generated
/// - [`WidgetChildren`] is generated if no direct implementation is present
/// - [`Layout`] is generated if the `layout` attribute property is set, and
/// no direct implementation is found. In other cases where a direct
/// implementation of the trait is found, (default) method implementations
/// may be injected where not already present.
/// - [`Widget`] is generated if no direct implementation is present,
/// otherwise some (default) method implementations are injected where
/// these methods are not directly implemented.
///
/// To refer to a widget via dyn trait, use `&dyn Widget`.
/// To refer to a widget in generic functions, use `<W: Widget>`.
/// Some simple examples follow. See also
/// [examples apps](https://github.com/kas-gui/kas/tree/master/examples)
/// and [`kas_widgets`](https://docs.rs/kas-widgets/latest/kas_widgets/) code.
/// ```
/// # extern crate kas_core as kas;
/// use kas::prelude::*;
/// use kas::event;
/// use kas::theme::TextClass;
/// use std::fmt::Debug;
///
/// [`derive(Widget)`]: https://docs.rs/kas/latest/kas/macros/index.html#the-derivewidget-macro
/// impl_scope! {
/// /// A text label
/// #[derive(Clone, Debug)]
/// #[widget]
/// pub struct AccelLabel {
/// core: widget_core!(),
/// class: TextClass,
/// label: Text<AccelString>,
/// }
///
/// impl Self {
/// /// Construct from `label`
/// pub fn new(label: impl Into<AccelString>) -> Self {
/// AccelLabel {
/// core: Default::default(),
/// class: TextClass::AccelLabel(true),
/// label: Text::new_multi(label.into()),
/// }
/// }
///
/// /// Set text class (inline)
/// pub fn with_class(mut self, class: TextClass) -> Self {
/// self.class = class;
/// self
/// }
///
/// /// Get the accelerator keys
/// pub fn keys(&self) -> &[event::VirtualKeyCode] {
/// self.label.text().keys()
/// }
/// }
///
/// impl Layout for Self {
/// fn size_rules(&mut self, size_mgr: SizeMgr, axis: AxisInfo) -> SizeRules {
/// size_mgr.text_bound(&mut self.label, self.class, axis)
/// }
///
/// fn set_rect(&mut self, mgr: &mut SetRectMgr, rect: Rect, align: AlignHints) {
/// self.core.rect = rect;
/// let align = align.unwrap_or(Align::Default, Align::Center);
/// mgr.text_set_size(&mut self.label, self.class, rect.size, align);
/// }
///
/// fn draw(&mut self, mut draw: DrawMgr) {
/// draw.text_effects(self.rect().pos, &self.label, self.class);
/// }
/// }
/// }
///
/// impl_scope! {
/// /// A push-button with a text label
/// #[derive(Debug)]
/// #[widget {
/// layout = button: self.label;
/// key_nav = true;
/// hover_highlight = true;
/// }]
/// pub struct TextButton<M: Clone + Debug + 'static> {
/// core: widget_core!(),
/// #[widget]
/// label: AccelLabel,
/// message: M,
/// }
///
/// impl Self {
/// /// Construct a button with given `label`
/// pub fn new(label: impl Into<AccelString>, message: M) -> Self {
/// TextButton {
/// core: Default::default(),
/// label: AccelLabel::new(label).with_class(TextClass::Button),
/// message,
/// }
/// }
/// }
/// impl Widget for Self {
/// fn configure(&mut self, mgr: &mut SetRectMgr) {
/// mgr.add_accel_keys(self.id_ref(), self.label.keys());
/// }
///
/// fn handle_event(&mut self, mgr: &mut EventMgr, event: Event) -> Response {
/// event.on_activate(mgr, self.id(), |mgr| {
/// mgr.push_msg(self.message.clone());
/// Response::Used
/// })
/// }
/// }
/// }
/// ```
#[autoimpl(for<'a, T: trait + ?Sized> &'a mut T, Box<T>)]
pub trait Widget: WidgetChildren + Layout {
/// Make an identifier for a child
Expand Down Expand Up @@ -395,6 +511,19 @@ pub trait Widget: WidgetChildren + Layout {
Response::Unused
}

/// Potentially steal an event before it reaches a child
///
/// This is called on each widget while sending an event, including when the
/// target is self.
/// If this returns [`Response::Used`], the event is not sent further.
///
/// Default implementation: return [`Response::Unused`].
#[inline]
fn steal_event(&mut self, mgr: &mut EventMgr, id: &WidgetId, event: &Event) -> Response {
let _ = (mgr, id, event);
Response::Unused
}

/// Handle an event sent to child `index` but left unhandled
///
/// Default implementation: call [`Self::handle_event`] with `event`.
Expand Down
6 changes: 3 additions & 3 deletions crates/kas-core/src/event/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -603,7 +603,9 @@ impl<'a> EventMgr<'a> {
event: Event,
) -> Response {
let mut response = Response::Unused;
if let Some(index) = widget.find_child_index(&id) {
if widget.steal_event(self, &id, &event) == Response::Used {
response = Response::Used;
} else if let Some(index) = widget.find_child_index(&id) {
let translation = widget.translation();
if disabled {
// event is unused
Expand All @@ -617,7 +619,6 @@ impl<'a> EventMgr<'a> {
"Widget {} found index {index} for {id}, but child not found",
widget.identify()
);
return Response::Unused;
}

if matches!(response, Response::Unused) {
Expand All @@ -634,7 +635,6 @@ impl<'a> EventMgr<'a> {
response |= widget.handle_event(self, event)
} else {
warn!("Widget {} cannot find path to {id}", widget.identify());
return Response::Unused;
}

response
Expand Down
23 changes: 15 additions & 8 deletions crates/kas-core/src/theme/draw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,8 +191,8 @@ impl<'a> DrawMgr<'a> {
///
/// [`SizeMgr::text_bound`] should be called prior to this method to
/// select a font, font size and wrap options (based on the [`TextClass`]).
pub fn text(&mut self, pos: Coord, text: &TextDisplay, class: TextClass) {
self.h.text(&self.id, pos, text, class);
pub fn text(&mut self, pos: Coord, text: impl AsRef<TextDisplay>, class: TextClass) {
self.h.text(&self.id, pos, text.as_ref(), class);
}

/// Draw text with effects
Expand All @@ -212,10 +212,10 @@ impl<'a> DrawMgr<'a> {
/// Other than visually highlighting the selection, this method behaves
/// identically to [`Self::text`]. It is likely to be replaced in the
/// future by a higher-level API.
pub fn text_selected<T: AsRef<TextDisplay>, R: RangeBounds<usize>>(
pub fn text_selected<R: RangeBounds<usize>>(
&mut self,
pos: Coord,
text: T,
text: impl AsRef<TextDisplay>,
range: R,
class: TextClass,
) {
Expand All @@ -238,8 +238,15 @@ impl<'a> DrawMgr<'a> {
///
/// [`SizeMgr::text_bound`] should be called prior to this method to
/// select a font, font size and wrap options (based on the [`TextClass`]).
pub fn text_cursor(&mut self, pos: Coord, text: &TextDisplay, class: TextClass, byte: usize) {
self.h.text_cursor(&self.id, pos, text, class, byte);
pub fn text_cursor(
&mut self,
pos: Coord,
text: impl AsRef<TextDisplay>,
class: TextClass,
byte: usize,
) {
self.h
.text_cursor(&self.id, pos, text.as_ref(), class, byte);
}

/// Draw UI element: checkbox
Expand All @@ -264,13 +271,13 @@ impl<'a> DrawMgr<'a> {
}

/// Draw UI element: scrollbar
pub fn scrollbar(&mut self, track_rect: Rect, handle: &dyn Widget, dir: Direction) {
pub fn scrollbar<W: Widget>(&mut self, track_rect: Rect, handle: &W, dir: Direction) {
self.h
.scrollbar(&self.id, handle.id_ref(), track_rect, handle.rect(), dir);
}

/// Draw UI element: slider
pub fn slider(&mut self, track_rect: Rect, handle: &dyn Widget, dir: Direction) {
pub fn slider<W: Widget>(&mut self, track_rect: Rect, handle: &W, dir: Direction) {
self.h
.slider(&self.id, handle.id_ref(), track_rect, handle.rect(), dir);
}
Expand Down
2 changes: 1 addition & 1 deletion crates/kas-core/src/toolkit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ bitflags! {
const RESIZE = 1 << 9;
/// Update theme memory
const THEME_UPDATE = 1 << 10;
/// Window requires reconfiguring
/// Reconfigure all widgets of the window
///
/// *Configuring* widgets assigns [`WidgetId`] identifiers and calls
/// [`crate::Widget::configure`].
Expand Down
4 changes: 2 additions & 2 deletions crates/kas-theme/src/colors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ impl ColorsSrgb {
is_dark: false,
background: Rgba8Srgb::from_str("#FAFAFA").unwrap(),
frame: Rgba8Srgb::from_str("#BCBCBC").unwrap(),
accent: Rgba8Srgb::from_str("#7E3FF2").unwrap(),
accent: Rgba8Srgb::from_str("#8347f2").unwrap(),
accent_soft: Rgba8Srgb::from_str("#B38DF9").unwrap(),
nav_focus: Rgba8Srgb::from_str("#7E3FF2").unwrap(),
edit_bg: Rgba8Srgb::from_str("#FAFAFA").unwrap(),
Expand Down Expand Up @@ -268,7 +268,7 @@ impl ColorsSrgb {
is_dark: false,
background: Rgba8Srgb::from_str("#FFFFFF").unwrap(),
frame: Rgba8Srgb::from_str("#DADADA").unwrap(),
accent: Rgba8Srgb::from_str("#7CDAFF").unwrap(),
accent: Rgba8Srgb::from_str("#3fafd7").unwrap(),
accent_soft: Rgba8Srgb::from_str("#7CDAFF").unwrap(),
nav_focus: Rgba8Srgb::from_str("#3B697A").unwrap(),
edit_bg: Rgba8Srgb::from_str("#FFFFFF").unwrap(),
Expand Down
Loading