Skip to content

Commit

Permalink
Extract Widget::Props into a type parameter
Browse files Browse the repository at this point in the history
Allows widgets to be polymorphic wrt. `Props`, enabling e.g. widgets
that take unboxed closures by value.
  • Loading branch information
Ralith committed Nov 21, 2023
1 parent f6c424a commit f907eba
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 90 deletions.
9 changes: 5 additions & 4 deletions crates/yakui-core/src/dom/dummy.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use crate::widget::Widget;
use crate::widget::{AnyWidget, Widget};

/// Placeholder widget used internally to emplace a widget without
/// initializing it yet.
#[derive(Debug)]
pub(crate) struct DummyWidget;

impl Widget for DummyWidget {
type Props = ();
impl AnyWidget for DummyWidget {}

impl Widget<()> for DummyWidget {
type Response = ();

#[inline]
Expand All @@ -15,5 +16,5 @@ impl Widget for DummyWidget {
}

#[inline]
fn update(&mut self, _props: Self::Props) -> Self::Response {}
fn update(&mut self, _props: ()) -> Self::Response {}
}
23 changes: 16 additions & 7 deletions crates/yakui-core/src/dom/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mod root;
use std::any::{type_name, TypeId};
use std::cell::{Ref, RefCell, RefMut};
use std::collections::VecDeque;
use std::fmt;
use std::mem::replace;
use std::rc::Rc;

Expand All @@ -16,7 +17,7 @@ use thunderdome::Arena;

use crate::id::WidgetId;
use crate::response::Response;
use crate::widget::{ErasedWidget, Widget};
use crate::widget::{AnyWidget, Widget};

use self::dummy::DummyWidget;
use self::root::RootWidget;
Expand All @@ -38,7 +39,7 @@ struct DomInner {
pub struct DomNode {
/// The widget implementation. Only a subset of the methods from [`Widget`]
/// are available without downcasting the widget first.
pub widget: Box<dyn ErasedWidget>,
pub widget: Box<dyn AnyWidget>,

/// The parent of this node, if it has one.
pub parent: Option<WidgetId>,
Expand Down Expand Up @@ -175,16 +176,24 @@ impl Dom {

/// Convenience method for calling [`Dom::begin_widget`] immediately
/// followed by [`Dom::end_widget`].
pub fn do_widget<T: Widget>(&self, props: T::Props) -> Response<T::Response> {
let response = self.begin_widget::<T>(props);
self.end_widget::<T>(response.id);
pub fn do_widget<T: Widget<Props>, Props>(&self, props: Props) -> Response<T::Response>
where
T: Widget<Props>,
Props: fmt::Debug + 'static,
{
let response = self.begin_widget::<T, _>(props);
self.end_widget::<T, _>(response.id);
response
}

/// Begin building a widget with the given type and props.
///
/// After calling this method, children can be added to this widget.
pub fn begin_widget<T: Widget>(&self, props: T::Props) -> Response<T::Response> {
pub fn begin_widget<T, Props>(&self, props: Props) -> Response<T::Response>
where
T: Widget<Props>,
Props: fmt::Debug + 'static,
{
log::trace!("begin_widget::<{}>({props:#?}", type_name::<T>());

let (id, mut widget) = {
Expand Down Expand Up @@ -224,7 +233,7 @@ impl Dom {

/// Finish building the widget with the given ID. Must be the top of the
/// stack, with no other widgets pending.
pub fn end_widget<T: Widget>(&self, id: WidgetId) {
pub fn end_widget<T: Widget<Props>, Props>(&self, id: WidgetId) {
log::trace!("end_widget::<{}>({id:?})", type_name::<T>());

let old_top = self.inner.stack.borrow_mut().pop().unwrap_or_else(|| {
Expand Down
23 changes: 12 additions & 11 deletions crates/yakui-core/src/dom/root.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,10 @@
use crate::geometry::Constraints;
use crate::widget::{LayoutContext, Widget};
use crate::widget::{AnyWidget, LayoutContext, Widget};

#[derive(Debug)]
pub struct RootWidget;

impl Widget for RootWidget {
type Props = ();
type Response = ();

fn new() -> Self {
Self
}

fn update(&mut self, _props: Self::Props) -> Self::Response {}

impl AnyWidget for RootWidget {
fn layout(&self, mut ctx: LayoutContext<'_>, constraints: Constraints) -> glam::Vec2 {
ctx.layout.new_layer(ctx.dom);

Expand All @@ -26,3 +17,13 @@ impl Widget for RootWidget {
constraints.max
}
}

impl Widget<()> for RootWidget {
type Response = ();

fn new() -> Self {
Self
}

fn update(&mut self, _props: ()) -> Self::Response {}
}
78 changes: 10 additions & 68 deletions crates/yakui-core/src/widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,6 @@ use crate::layout::LayoutDom;
use crate::paint::PaintDom;
use crate::{Flow, WidgetId};

/// Trait that's automatically implemented for all widget props.
///
/// This trait is used by yakui to enforce that props implement `Debug`.
pub trait Props: fmt::Debug {}
impl<T> Props for T where T: fmt::Debug {}

/// Information available to a widget during the layout phase.
#[non_exhaustive]
#[allow(missing_docs)]
Expand Down Expand Up @@ -76,11 +70,10 @@ pub struct NavigateContext<'dom> {

/// A yakui widget. Implement this trait to create a custom widget if composing
/// existing widgets does not solve your use case.
pub trait Widget: 'static + fmt::Debug {
/// The props that this widget needs to be created or updated. Props define
/// all of the values that a widget's user can specify every render.
type Props: Props;

///
/// `Props` defines the properties that this widget needs to be created or updated, i.e. all of the
/// values that a widget's user can specify every render.
pub trait Widget<Props>: AnyWidget {
/// The type that the widget will return to the user when it is created or
/// updated. This type should contain information like whether the widget
/// was clicked, had keyboard input, or other info that might be useful.
Expand All @@ -90,8 +83,11 @@ pub trait Widget: 'static + fmt::Debug {
fn new() -> Self;

/// Update the widget with new props.
fn update(&mut self, props: Self::Props) -> Self::Response;
fn update(&mut self, props: Props) -> Self::Response;
}

/// Operations on a [`Widget`] that don't the caller to know the widget's type
pub trait AnyWidget: Any + fmt::Debug {
/// Returns whether this widget should grow to fill a flexible layout, and
/// if so, what weight should be applied to it if other widgets also want to
/// grow.
Expand Down Expand Up @@ -173,65 +169,11 @@ pub trait Widget: 'static + fmt::Debug {
fn navigate(&self, ctx: NavigateContext<'_>, dir: NavDirection) -> Option<WidgetId> {
None
}
}

/// A type-erased version of [`Widget`].
pub trait ErasedWidget: Any + fmt::Debug {
/// See [`Widget::layout`].
fn layout(&self, ctx: LayoutContext<'_>, constraints: Constraints) -> Vec2;

/// See [`Widget::flex`].
fn flex(&self) -> (u32, FlexFit);

/// See [`Widget::flow`].
fn flow(&self) -> Flow;

/// See [`Widget::paint`].
fn paint(&self, ctx: PaintContext<'_>);

/// See [`Widget::event_interest`].
fn event_interest(&self) -> EventInterest;

/// See [`Widget::event`].
fn event(&mut self, ctx: EventContext<'_>, event: &WidgetEvent) -> EventResponse;

/// Returns the type name of the widget, usable only for debugging.
fn type_name(&self) -> &'static str;
}

impl<T> ErasedWidget for T
where
T: Widget,
{
fn layout(&self, ctx: LayoutContext<'_>, constraints: Constraints) -> Vec2 {
<T as Widget>::layout(self, ctx, constraints)
}

fn flex(&self) -> (u32, FlexFit) {
<T as Widget>::flex(self)
}

fn flow(&self) -> Flow {
<T as Widget>::flow(self)
}

fn paint(&self, ctx: PaintContext<'_>) {
<T as Widget>::paint(self, ctx)
}

fn event_interest(&self) -> EventInterest {
<T as Widget>::event_interest(self)
}

fn event(&mut self, ctx: EventContext<'_>, event: &WidgetEvent) -> EventResponse {
log::debug!("Event on {}: {event:?}", type_name::<T>());

<T as Widget>::event(self, ctx, event)
}

fn type_name(&self) -> &'static str {
type_name::<T>()
type_name::<Self>()
}
}

mopmopafy!(ErasedWidget);
mopmopafy!(AnyWidget);

0 comments on commit f907eba

Please sign in to comment.