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

Add support for impl View for Arc/Rc<impl View> #164

Closed
wants to merge 10 commits into from
14 changes: 7 additions & 7 deletions crates/xilem_core/src/any_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,22 @@

#[macro_export]
macro_rules! generate_anyview_trait {
($anyview:ident, $viewtrait:ident, $viewmarker:ty, $cx:ty, $changeflags:ty, $anywidget:ident, $boxedview:ident; $($ss:tt)*) => {
($anyview:ident, $viewtrait:ident, $viewmarker:ty, $cx:ty, $changeflags:ty, $anywidget:ident, $boxedview:ident; ($($super_bounds:tt)*), ($($state_bounds:tt)*)) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was going to suggest changing the input to avoid the parentheses here too, but unfortunately that runs into rust-lang/rust#18700 (last comment has a workaround, but probably not worth using that here).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe make the last two parameters optional using $()? though? The invocation with (), () looks weird.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I think it's difficult to get rid of () in general (while covering everything that is allowed in trait bounds).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've used optionals as you said

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

fn dyn_build(
&self,
cx: &mut $cx,
) -> ($crate::Id, Box<dyn std::any::Any $( $ss )* >, Box<dyn $anywidget>);
) -> ($crate::Id, Box<dyn std::any::Any $( $state_bounds )* >, Box<dyn $anywidget>);

fn dyn_rebuild(
&self,
cx: &mut $cx,
prev: &dyn $anyview<T, A>,
id: &mut $crate::Id,
state: &mut Box<dyn std::any::Any $( $ss )* >,
state: &mut Box<dyn std::any::Any $( $state_bounds )* >,
element: &mut Box<dyn $anywidget>,
) -> $changeflags;

Expand All @@ -43,7 +43,7 @@ macro_rules! generate_anyview_trait {
fn dyn_build(
&self,
cx: &mut $cx,
) -> ($crate::Id, Box<dyn std::any::Any $( $ss )* >, Box<dyn $anywidget>) {
) -> ($crate::Id, Box<dyn std::any::Any $( $state_bounds )* >, Box<dyn $anywidget>) {
let (id, state, element) = self.build(cx);
(id, Box::new(state), Box::new(element))
}
Expand All @@ -53,7 +53,7 @@ macro_rules! generate_anyview_trait {
cx: &mut $cx,
prev: &dyn $anyview<T, A>,
id: &mut $crate::Id,
state: &mut Box<dyn std::any::Any $( $ss )* >,
state: &mut Box<dyn std::any::Any $( $state_bounds )* >,
element: &mut Box<dyn $anywidget>,
) -> ChangeFlags {
use std::ops::DerefMut;
Expand Down Expand Up @@ -94,12 +94,12 @@ macro_rules! generate_anyview_trait {
}
}

pub type $boxedview<T, A = ()> = Box<dyn $anyview<T, A> $( $ss )* >;
pub type $boxedview<T, A = ()> = Box<dyn $anyview<T, A> $( $super_bounds )* >;

impl<T, A> $viewmarker for $boxedview<T, A> {}

impl<T, A> $viewtrait<T, A> for $boxedview<T, A> {
type State = Box<dyn std::any::Any $( $ss )* >;
type State = Box<dyn std::any::Any $( $state_bounds )* >;

type Element = Box<dyn $anywidget>;

Expand Down
6 changes: 3 additions & 3 deletions crates/xilem_core/src/sequence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,13 @@ macro_rules! impl_view_tuple {

#[macro_export]
macro_rules! generate_viewsequence_trait {
($viewseq:ident, $view:ident, $viewmarker: ident, $bound:ident, $cx:ty, $changeflags:ty, $pod:ty; $( $ss:tt )* ) => {
($viewseq:ident, $view:ident, $viewmarker: ident, $bound:ident, $cx:ty, $changeflags:ty, $pod:ty; ($($super_bounds:tt)*), ($($state_bounds:tt)*)) => {
/// This trait represents a (possibly empty) sequence of views.
///
/// It is up to the parent view how to lay out and display them.
pub trait $viewseq<T, A = ()> $( $ss )* {
pub trait $viewseq<T, A = ()> $( $super_bounds )* {
/// Associated states for the views.
type State $( $ss )*;
type State $( $state_bounds )*;

/// Build the associated widgets and initialize all states.
fn build(&self, cx: &mut $cx, elements: &mut Vec<$pod>) -> Self::State;
Expand Down
11 changes: 6 additions & 5 deletions crates/xilem_core/src/view/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

mod adapt;
mod memoize;
mod rc;

/// Create the `View` trait for a particular xilem context (e.g. html, native, ...).
///
Expand All @@ -14,11 +15,11 @@ mod memoize;
/// methods, and be responsible for managing element creation & deletion.
/// - `$changeflags` - The type that reports down/up the tree. Can be used to avoid
/// doing work when we can prove nothing needs doing.
/// - `$ss` - (optional) parent traits to this trait (e.g. `:Send`). Also applied to
/// the state type requirements
/// - `$super_bounds` - (optional) parent traits to this trait (e.g. `+ Send`).
/// - `$state_bounds` - (optional) trait bounds for the associated type `State` (e.g. `: Send`).
#[macro_export]
macro_rules! generate_view_trait {
($viewtrait:ident, $bound:ident, $cx:ty, $changeflags:ty; $($ss:tt)*) => {
($viewtrait:ident, $bound:ident, $cx:ty, $changeflags:ty; ($($super_bounds:tt)*), ($($state_bounds:tt)*)) => {
/// A view object representing a node in the UI.
///
/// This is a central trait for representing UI. An app will generate a tree of
Expand All @@ -36,9 +37,9 @@ macro_rules! generate_view_trait {
/// and also a type for actions which are passed up the tree in message
/// propagation. During message handling, mutable access to the app state is
/// given to view nodes, which in turn can expose it to callbacks.
pub trait $viewtrait<T, A = ()> $( $ss )* {
pub trait $viewtrait<T, A = ()> $( $super_bounds )* {
/// Associated state for the view.
type State $( $ss )*;
type State $( $state_bounds )*;

/// The associated element for the view.
type Element: $bound;
Expand Down
44 changes: 44 additions & 0 deletions crates/xilem_core/src/view/rc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright 2024 the Xilem Authors.
// SPDX-License-Identifier: Apache-2.0

#[macro_export]
macro_rules! generate_rc_view {
($($rc_ty:ident)::*, $viewtrait:ident, $viewmarker:ty, $cx:ty, $changeflags:ty; $($ss:tt)*) => {
impl<V> $viewmarker for $($rc_ty)::*<V> {}

impl<T, A, V: $viewtrait<T, A> $( $ss )*> $viewtrait<T, A> for $($rc_ty)::*<V> {
type State = V::State;

type Element = V::Element;

fn build(&self, cx: &mut Cx) -> ($crate::Id, Self::State, Self::Element) {
V::build(self, cx)
}

fn rebuild(
&self,
cx: &mut $cx,
prev: &Self,
id: &mut $crate::Id,
state: &mut Self::State,
element: &mut Self::Element,
) -> ChangeFlags {
if !$($rc_ty)::*::ptr_eq(self, prev) {
V::rebuild(self, cx, prev, id, state, element)
} else {
ChangeFlags::empty()
}
}

fn message(
&self,
id_path: &[$crate::Id],
state: &mut Self::State,
message: Box<dyn std::any::Any>,
app_state: &mut T,
) -> $crate::MessageResult<A> {
V::message(self, id_path, state, message, app_state)
}
}
};
}
8 changes: 5 additions & 3 deletions crates/xilem_web/src/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,14 @@ impl Pod {
}
}

xilem_core::generate_view_trait! {View, DomNode, Cx, ChangeFlags;}
xilem_core::generate_viewsequence_trait! {ViewSequence, View, ViewMarker, DomNode, Cx, ChangeFlags, Pod;}
xilem_core::generate_anyview_trait! {AnyView, View, ViewMarker, Cx, ChangeFlags, AnyNode, BoxedView;}
xilem_core::generate_view_trait! {View, DomNode, Cx, ChangeFlags;(),()}
xilem_core::generate_viewsequence_trait! {ViewSequence, View, ViewMarker, DomNode, Cx, ChangeFlags, Pod;(), ()}
xilem_core::generate_anyview_trait! {AnyView, View, ViewMarker, Cx, ChangeFlags, AnyNode, BoxedView;(), ()}
xilem_core::generate_memoize_view! {Memoize, MemoizeState, View, ViewMarker, Cx, ChangeFlags, static_view, memoize;}
xilem_core::generate_adapt_view! {View, Cx, ChangeFlags;}
xilem_core::generate_adapt_state_view! {View, Cx, ChangeFlags;}
xilem_core::generate_rc_view! {std::rc::Rc, View, ViewMarker, Cx, ChangeFlags; }
xilem_core::generate_rc_view! {std::sync::Arc, View, ViewMarker, Cx, ChangeFlags; + Sync}

// strings -> text nodes

Expand Down
65 changes: 65 additions & 0 deletions examples/memoization.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use std::sync::Arc;
use xilem::view::{button, memoize, v_stack, BoxedView};
use xilem::{view::View, App, AppLauncher};

// There are currently two ways to do memoization

fn app_logic(state: &mut AppState) -> impl View<AppState> {
// The following is an example to do memoization with an Arc
let increase_button = if let Some(view) = &state.count_view {
view.clone()
} else {
let view = state.make_increase_button();
state.count_view = Some(view.clone());
view
};

v_stack((
increase_button,
// This is the alternative with Memoize
// Note how this requires a closure that returns the memoized view, while Arc does not
memoize(state.count, |count| {
button(
format!("decrease the count: {count}"),
|data: &mut AppState| {
data.count_view = None;
data.count -= 1;
},
)
}),
button("reset", |data: &mut AppState| {
if data.count != 0 {
data.count_view = None;
}
data.count = 0;
}),
))
.with_spacing(20.0)
}

struct AppState {
count: i32,
// TODO Maybe support `impl View for Arc<dyn AnyView>` to avoid double indirection
count_view: Option<Arc<BoxedView<AppState>>>,
}

impl AppState {
fn make_increase_button(&self) -> Arc<BoxedView<AppState>> {
Arc::new(Box::new(button(
format!("current count is {}", self.count),
|state: &mut AppState| {
state.count += 1;
state.count_view = None;
},
)))
}
}

fn main() {
let data = AppState {
count: 0,
count_view: None,
};

AppLauncher::new(App::new(data, app_logic)).run()
}
9 changes: 6 additions & 3 deletions src/view/button.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,21 @@ use super::{Cx, View};
pub struct Button<T, A> {
label: String,
// consider not boxing
callback: Box<dyn Fn(&mut T) -> A + Send>,
callback: Box<dyn Fn(&mut T) -> A + Send + Sync>,
}

pub fn button<T, A>(
label: impl Into<String>,
clicked: impl Fn(&mut T) -> A + Send + 'static,
clicked: impl Fn(&mut T) -> A + Send + Sync + 'static,
) -> Button<T, A> {
Button::new(label, clicked)
}

impl<T, A> Button<T, A> {
pub fn new(label: impl Into<String>, clicked: impl Fn(&mut T) -> A + Send + 'static) -> Self {
pub fn new(
label: impl Into<String>,
clicked: impl Fn(&mut T) -> A + Send + Sync + 'static,
) -> Self {
Button {
label: label.into(),
callback: Box::new(clicked),
Expand Down
2 changes: 1 addition & 1 deletion src/view/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ pub fn list<T, A, VT: ViewSequence<T, A>, F: Fn(usize) -> VT + Send>(
}
}

impl<T, A, VT: ViewSequence<T, A>, F: Fn(usize) -> VT + Send> ViewSequence<T, A>
impl<T, A, VT: ViewSequence<T, A>, F: Fn(usize) -> VT + Send + Sync> ViewSequence<T, A>
for List<T, A, VT, F>
{
type State = ListState<T, A, VT>;
Expand Down
5 changes: 4 additions & 1 deletion src/view/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ pub use button::button;
pub use linear_layout::{h_stack, v_stack, LinearLayout};
pub use list::{list, List};
pub use switch::switch;
pub use view::{Adapt, AdaptState, Cx, Memoize, View, ViewMarker, ViewSequence};
pub use view::{
memoize, static_view, Adapt, AdaptState, AnyView, BoxedView, Cx, Memoize, MemoizeState, View,
ViewMarker, ViewSequence,
};

#[cfg(feature = "taffy")]
mod taffy_layout;
Expand Down
6 changes: 3 additions & 3 deletions src/view/switch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,18 @@ use super::{Cx, View};
pub struct Switch<T, A> {
is_on: bool,
#[allow(clippy::type_complexity)]
callback: Box<dyn Fn(&mut T, bool) -> A + Send>,
callback: Box<dyn Fn(&mut T, bool) -> A + Send + Sync>,
}

pub fn switch<T, A>(
is_on: bool,
clicked: impl Fn(&mut T, bool) -> A + Send + 'static,
clicked: impl Fn(&mut T, bool) -> A + Send + Sync + 'static,
) -> Switch<T, A> {
Switch::new(is_on, clicked)
}

impl<T, A> Switch<T, A> {
pub fn new(is_on: bool, clicked: impl Fn(&mut T, bool) -> A + Send + 'static) -> Self {
pub fn new(is_on: bool, clicked: impl Fn(&mut T, bool) -> A + Send + Sync + 'static) -> Self {
Switch {
is_on,
callback: Box::new(clicked),
Expand Down
13 changes: 7 additions & 6 deletions src/view/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,13 @@ use xilem_core::{Id, IdPath};

use crate::widget::{AnyWidget, ChangeFlags, Pod, Widget};

xilem_core::generate_view_trait! {View, Widget, Cx, ChangeFlags; : Send}
xilem_core::generate_viewsequence_trait! {ViewSequence, View, ViewMarker, Widget, Cx, ChangeFlags, Pod; : Send}
xilem_core::generate_anyview_trait! {AnyView, View, ViewMarker, Cx, ChangeFlags, AnyWidget, BoxedView; + Send}
xilem_core::generate_memoize_view! {Memoize, MemoizeState, View, ViewMarker, Cx, ChangeFlags, s, memoize; + Send}
xilem_core::generate_adapt_view! {View, Cx, ChangeFlags; + Send}
xilem_core::generate_adapt_state_view! {View, Cx, ChangeFlags; + Send}
xilem_core::generate_view_trait! {View, Widget, Cx, ChangeFlags; (: Send + Sync), (: Send)}
xilem_core::generate_viewsequence_trait! {ViewSequence, View, ViewMarker, Widget, Cx, ChangeFlags, Pod; (: Send + Sync), (: Send)}
xilem_core::generate_anyview_trait! {AnyView, View, ViewMarker, Cx, ChangeFlags, AnyWidget, BoxedView; (+ Send + Sync), (+ Send)}
xilem_core::generate_memoize_view! {Memoize, MemoizeState, View, ViewMarker, Cx, ChangeFlags, static_view, memoize; + Send + Sync}
xilem_core::generate_adapt_view! {View, Cx, ChangeFlags; + Send + Sync}
xilem_core::generate_adapt_state_view! {View, Cx, ChangeFlags; + Send + Sync}
xilem_core::generate_rc_view! {std::sync::Arc, View, ViewMarker, Cx, ChangeFlags; + Sync + Sync}

#[derive(Clone)]
pub struct Cx {
Expand Down