Skip to content

Commit

Permalink
Implement AnyView in xilem_masonry (#206)
Browse files Browse the repository at this point in the history
* Implement `AnyView` in `xilem_masonry`

* Address review comments

* Improperly wire up anyview to give an idpath element
  • Loading branch information
DJMcNab authored Apr 25, 2024
1 parent f27a3ea commit 25d4a8c
Show file tree
Hide file tree
Showing 4 changed files with 235 additions and 8 deletions.
25 changes: 23 additions & 2 deletions crates/xilem_masonry/examples/mason.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#![windows_subsystem = "windows"]

use xilem_masonry::view::{button, flex};
use xilem_masonry::{MasonryView, Xilem};
use xilem_masonry::{BoxedMasonryView, MasonryView, Xilem};

fn app_logic(data: &mut AppData) -> impl MasonryView<AppData> {
// here's some logic, deriving state for the view from our state
Expand All @@ -20,18 +20,39 @@ fn app_logic(data: &mut AppData) -> impl MasonryView<AppData> {
.collect::<Vec<_>>();
flex((
button(label, |data: &mut AppData| data.count += 1),
toggleable(data),
button("Decrement", |data: &mut AppData| data.count -= 1),
button("Reset", |data: &mut AppData| data.count = 0),
sequence,
))
}

fn toggleable(data: &mut AppData) -> impl MasonryView<AppData> {
let inner_view: BoxedMasonryView<_, _> = if data.active {
Box::new(flex((
button("Deactivate", |data: &mut AppData| {
data.active = false;
}),
button("Unlimited Power", |data: &mut AppData| {
data.count = -1_000_000;
}),
)))
} else {
Box::new(button("Activate", |data: &mut AppData| data.active = true))
};
inner_view
}

struct AppData {
count: i32,
active: bool,
}

fn main() {
let data = AppData { count: 0 };
let data = AppData {
count: 0,
active: false,
};

let app = Xilem::new(data, app_logic);
app.run_windowed("First Example".into()).unwrap();
Expand Down
198 changes: 198 additions & 0 deletions crates/xilem_masonry/src/any_view.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
use std::{num::NonZeroU64, ops::Deref};

use masonry::{
declare_widget,
widget::{StoreInWidgetMut, WidgetMut, WidgetRef},
BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, Point, PointerEvent,
Size, StatusChange, TextEvent, Widget, WidgetPod,
};
use smallvec::SmallVec;
use vello::Scene;

use crate::{ChangeFlags, MasonryView, MessageResult, ViewCx, ViewId};

/// A view which can have any underlying view type.
///
/// This can be used to return type erased views (such as from a trait),
/// or used to implement conditional display and switching of views.
///
/// Note that `Option` can also be used for conditionally displaying
/// views in a [`ViewSequence`](crate::ViewSequence).
// TODO: Mention `Either` when we have implemented that?
pub type BoxedMasonryView<T, A = ()> = Box<dyn AnyMasonryView<T, A>>;

impl<T: 'static, A: 'static> MasonryView<T, A> for BoxedMasonryView<T, A> {
type Element = DynWidget;

fn build(&self, cx: &mut ViewCx) -> masonry::WidgetPod<Self::Element> {
self.deref().dyn_build(cx)
}

fn message(
&self,
id_path: &[ViewId],
message: Box<dyn std::any::Any>,
app_state: &mut T,
) -> crate::MessageResult<A> {
self.deref().dyn_message(id_path, message, app_state)
}

fn rebuild(
&self,
cx: &mut ViewCx,
prev: &Self,
// _id: &mut Id,
element: masonry::widget::WidgetMut<Self::Element>,
) -> ChangeFlags {
self.deref().dyn_rebuild(cx, prev, element)
}
}

/// A trait enabling type erasure of views.
pub trait AnyMasonryView<T, A = ()>: Send {
fn as_any(&self) -> &dyn std::any::Any;

fn dyn_build(&self, cx: &mut ViewCx) -> WidgetPod<DynWidget>;

fn dyn_rebuild(
&self,
cx: &mut ViewCx,
prev: &dyn AnyMasonryView<T, A>,
element: WidgetMut<DynWidget>,
) -> ChangeFlags;

fn dyn_message(
&self,
id_path: &[ViewId],
message: Box<dyn std::any::Any>,
app_state: &mut T,
) -> MessageResult<A>;
}

impl<T, A, V: MasonryView<T, A> + 'static> AnyMasonryView<T, A> for V {
fn as_any(&self) -> &dyn std::any::Any {
self
}

fn dyn_build(&self, cx: &mut ViewCx) -> WidgetPod<DynWidget> {
let gen_1 = NonZeroU64::new(1).unwrap();
let element = cx.with_id(ViewId::for_type::<V>(gen_1), |cx| self.build(cx));
WidgetPod::new(DynWidget {
inner: element.boxed(),
generation: gen_1.checked_add(1).unwrap(),
})
}

fn dyn_rebuild(
&self,
cx: &mut ViewCx,
prev: &dyn AnyMasonryView<T, A>,
mut element: WidgetMut<DynWidget>,
) -> ChangeFlags {
// TODO: Does this need to have a custom view id to enable events sent
// to an outdated view path to be caught and returned?
// Should we store this generation in `element`? Seems plausible
if let Some(prev) = prev.as_any().downcast_ref() {
let generation = element.generation();
// If we were previously of this type, then do a normal rebuild
element.downcast(|element| {
if let Some(element) = element {
cx.with_id(ViewId::for_type::<V>(generation), move |cx| {
self.rebuild(cx, prev, element)
})
} else {
eprintln!("downcast of element failed in dyn_rebuild");
ChangeFlags::UNCHANGED
}
})
} else {
// Otherwise, replace the element
let next_gen = element.next_generation();
let new_element = cx.with_id(ViewId::for_type::<V>(next_gen), |cx| self.build(cx));
element.replace_inner(new_element.boxed());
ChangeFlags::CHANGED
}
}

fn dyn_message(
&self,
id_path: &[ViewId],
message: Box<dyn std::any::Any>,
app_state: &mut T,
) -> MessageResult<A> {
// TODO: Validate this id
self.message(id_path.split_first().unwrap().1, message, app_state)
}
}

/// A widget whose only child can be dynamically replaced.
///
/// `WidgetPod<Box<dyn Widget>>` doesn't expose this possibility.
pub struct DynWidget {
inner: WidgetPod<Box<dyn Widget>>,
// This might be a layer break?
/// The generation of the inner widget, increases whenever the contained widget is replaced
generation: NonZeroU64,
}

/// Forward all events to the child widget.
impl Widget for DynWidget {
fn on_pointer_event(&mut self, ctx: &mut EventCtx, event: &PointerEvent) {
self.inner.on_pointer_event(ctx, event);
}
fn on_text_event(&mut self, ctx: &mut EventCtx, event: &TextEvent) {
self.inner.on_text_event(ctx, event);
}

fn on_status_change(&mut self, _: &mut LifeCycleCtx, _: &StatusChange) {
// Intentionally do nothing
}

fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle) {
self.inner.lifecycle(ctx, event);
}

fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
let size = self.inner.layout(ctx, bc);
ctx.place_child(&mut self.inner, Point::ORIGIN);
size
}

fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) {
self.inner.paint(ctx, scene);
}

fn children(&self) -> SmallVec<[WidgetRef<'_, dyn Widget>; 16]> {
let mut vec = SmallVec::new();
vec.push(self.inner.as_dyn());
vec
}
}

declare_widget!(DynWidgetMut, DynWidget);

impl DynWidget {
pub fn generation(&self) -> NonZeroU64 {
self.generation
}

pub fn next_generation(&self) -> NonZeroU64 {
self.generation.checked_add(1).unwrap()
}
}

impl DynWidgetMut<'_> {
pub(crate) fn replace_inner(&mut self, widget: WidgetPod<Box<dyn Widget>>) {
self.widget.generation = self.next_generation();
self.widget.inner = widget;
self.ctx.children_changed();
}

pub(crate) fn downcast<W: Widget + StoreInWidgetMut, R>(
&mut self,
f: impl FnOnce(Option<WidgetMut<'_, W>>) -> R,
) -> R {
let mut get_mut = self.ctx.get_mut(&mut self.widget.inner);
f(get_mut.downcast())
}
}
16 changes: 10 additions & 6 deletions crates/xilem_masonry/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ use winit::{
window::{Window, WindowBuilder},
};

mod any_view;
mod id;
mod sequence;
mod vec_splice;
pub use any_view::{AnyMasonryView, BoxedMasonryView};
pub mod view;
pub use id::ViewId;
pub use sequence::{ElementSplice, ViewSequence};
Expand Down Expand Up @@ -192,19 +194,21 @@ where
pub trait MasonryView<State, Action = ()>: Send + 'static {
type Element: Widget + StoreInWidgetMut;
fn build(&self, cx: &mut ViewCx) -> WidgetPod<Self::Element>;
fn message(
&self,
id_path: &[ViewId],
message: Box<dyn Any>,
app_state: &mut State,
) -> MessageResult<Action>;

fn rebuild(
&self,
_cx: &mut ViewCx,
prev: &Self,
// _id: &mut Id,
element: WidgetMut<Self::Element>,
) -> ChangeFlags;

fn message(
&self,
id_path: &[ViewId],
message: Box<dyn Any>,
app_state: &mut State,
) -> MessageResult<Action>;
}

#[must_use]
Expand Down
4 changes: 4 additions & 0 deletions crates/xilem_masonry/src/sequence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ impl<State, Action, Marker, VT: ViewSequence<State, Action, Marker>>
fn build(&self, cx: &mut ViewCx, elements: &mut dyn ElementSplice) {
match self {
Some(this) => {
// TODO: Assign a generation based ViewId here
// This needs view state, I think
this.build(cx, elements);
}
None => (),
Expand All @@ -127,6 +129,7 @@ impl<State, Action, Marker, VT: ViewSequence<State, Action, Marker>>
ChangeFlags::CHANGED
}
(Some(this), None) => {
// TODO: Assign an increased generation ViewId here.
this.build(cx, elements);
ChangeFlags::CHANGED
}
Expand Down Expand Up @@ -175,6 +178,7 @@ impl<T, A, Marker, VT: ViewSequence<T, A, Marker>> ViewSequence<T, A, (WasASeque
) -> ChangeFlags {
let mut changed = ChangeFlags::UNCHANGED;
for (i, (child, child_prev)) in self.iter().zip(prev).enumerate() {
// TODO: Do we want these ids to (also?) have a generational component?
let i: u64 = i.try_into().unwrap();
let id = NonZeroU64::new(i + 1).unwrap();
cx.with_id(ViewId::for_type::<VT>(id), |cx| {
Expand Down

0 comments on commit 25d4a8c

Please sign in to comment.