Use `ViewMarker` trait instead of the generic `Marker` to allow `impl ViewSequence` as return type (#472)

This allows returning `impl ViewSequence` as this was previously not
possibly as the `Marker` generic parameter couldn't be named (easily) in
`ViewSequence<..., Marker>`.

This also provides specialized `ViewSequence`s for xilem
(`WidgetViewSequence` and `FlexSequence`) as well as for xilem_web
(`DomFragment`). Additional doc-tests/documentation and a small example
(in `counter`) for xilem_web is provided as well.

This has the drawback to not being able to reeimplement `ViewSequence`
for types that already implement `View`, which was previously possible
(as seen by the now removed `NoElementView` `ViewSequence`
implementation).
And additionally by introducing more boilerplate by having to implement
`ViewMarker` for every type that implements `View`.

---------

Co-authored-by: Daniel McNab <36049421+DJMcNab@users.noreply.github.com>
This commit is contained in:
Philipp Mildenberger 2024-08-05 12:53:19 +02:00 committed by GitHub
parent 544a4a1ca9
commit 24427bbb44
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
38 changed files with 317 additions and 233 deletions

View File

@ -5,7 +5,7 @@ use masonry::widget::{CrossAxisAlignment, MainAxisAlignment};
use winit::dpi::LogicalSize; use winit::dpi::LogicalSize;
use winit::error::EventLoopError; use winit::error::EventLoopError;
use winit::window::Window; use winit::window::Window;
use xilem::view::Flex; use xilem::view::{Flex, FlexSequence};
use xilem::EventLoopBuilder; use xilem::EventLoopBuilder;
use xilem::{ use xilem::{
view::{button, flex, label, sized_box, Axis, FlexExt as _, FlexSpacer}, view::{button, flex, label, sized_box, Axis, FlexExt as _, FlexSpacer},
@ -254,7 +254,7 @@ fn app_logic(data: &mut Calculator) -> impl WidgetView<Calculator> {
} }
/// Creates a horizontal centered flex row designed for the display portion of the calculator. /// Creates a horizontal centered flex row designed for the display portion of the calculator.
pub fn centered_flex_row<Seq, Marker>(sequence: Seq) -> Flex<Seq, Marker> { pub fn centered_flex_row<State, Seq: FlexSequence<State>>(sequence: Seq) -> Flex<Seq, State> {
flex(sequence) flex(sequence)
.direction(Axis::Horizontal) .direction(Axis::Horizontal)
.cross_axis_alignment(CrossAxisAlignment::Center) .cross_axis_alignment(CrossAxisAlignment::Center)
@ -263,7 +263,7 @@ pub fn centered_flex_row<Seq, Marker>(sequence: Seq) -> Flex<Seq, Marker> {
} }
/// Creates a horizontal filled flex row designed to be used in a grid. /// Creates a horizontal filled flex row designed to be used in a grid.
pub fn flex_row<Seq, Marker>(sequence: Seq) -> Flex<Seq, Marker> { pub fn flex_row<State, Seq: FlexSequence<State>>(sequence: Seq) -> Flex<Seq, State> {
flex(sequence) flex(sequence)
.direction(Axis::Horizontal) .direction(Axis::Horizontal)
.cross_axis_alignment(CrossAxisAlignment::Fill) .cross_axis_alignment(CrossAxisAlignment::Fill)

View File

@ -26,7 +26,7 @@ enum CountMessage {
// `map_action()` is basically how elm works, i.e. provide a message that the parent view has to handle to update the state. // `map_action()` is basically how elm works, i.e. provide a message that the parent view has to handle to update the state.
// In this case the parent adjusts the count that is given to this view according to the message // In this case the parent adjusts the count that is given to this view according to the message
fn elm_counter<T>(count: i32) -> impl WidgetView<T, CountMessage> { fn elm_counter<T: 'static>(count: i32) -> impl WidgetView<T, CountMessage> {
flex(( flex((
label(format!("elm count: {count}")), label(format!("elm count: {count}")),
button("+", |_| CountMessage::Increment), button("+", |_| CountMessage::Increment),

View File

@ -109,16 +109,18 @@ fn app_logic(data: &mut AppData) -> impl WidgetView<AppData> {
fn toggleable(data: &mut AppData) -> impl WidgetView<AppData> { fn toggleable(data: &mut AppData) -> impl WidgetView<AppData> {
if data.active { if data.active {
flex(( fork(
button("Deactivate", |data: &mut AppData| { flex((
data.active = false; button("Deactivate", |data: &mut AppData| {
}), data.active = false;
button("Unlimited Power", |data: &mut AppData| { }),
data.count = -1_000_000; button("Unlimited Power", |data: &mut AppData| {
}), data.count = -1_000_000;
}),
))
.direction(Axis::Horizontal),
run_once(|| tracing::warn!("The pathway to unlimited power has been revealed")), run_once(|| tracing::warn!("The pathway to unlimited power has been revealed")),
)) )
.direction(Axis::Horizontal)
.boxed() .boxed()
} else { } else {
button("Activate", |data: &mut AppData| data.active = true).boxed() button("Activate", |data: &mut AppData| data.active = true).boxed()

View File

@ -19,6 +19,7 @@ use winit::{
}; };
use xilem_core::{ use xilem_core::{
AsyncCtx, MessageResult, RawProxy, SuperElement, View, ViewElement, ViewId, ViewPathTracker, AsyncCtx, MessageResult, RawProxy, SuperElement, View, ViewElement, ViewId, ViewPathTracker,
ViewSequence,
}; };
pub use masonry::{ pub use masonry::{
@ -209,6 +210,30 @@ where
type Widget = W; type Widget = W;
} }
/// An ordered sequence of widget views, it's used for `0..N` views.
/// See [`ViewSequence`] for more technical details.
///
/// # Examples
///
/// ```
/// use xilem::{view::prose, WidgetViewSequence};
///
/// fn prose_sequence<State: 'static>(
/// texts: impl Iterator<Item = &'static str>,
/// ) -> impl WidgetViewSequence<State> {
/// texts.map(prose).collect::<Vec<_>>()
/// }
/// ```
pub trait WidgetViewSequence<State, Action = ()>:
ViewSequence<State, Action, ViewCtx, Pod<any_view::DynWidget>>
{
}
impl<Seq, State, Action> WidgetViewSequence<State, Action> for Seq where
Seq: ViewSequence<State, Action, ViewCtx, Pod<any_view::DynWidget>>
{
}
type WidgetMap = HashMap<WidgetId, Vec<ViewId>>; type WidgetMap = HashMap<WidgetId, Vec<ViewId>>;
pub struct ViewCtx { pub struct ViewCtx {

View File

@ -4,7 +4,9 @@
use std::{future::Future, marker::PhantomData, sync::Arc}; use std::{future::Future, marker::PhantomData, sync::Arc};
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
use xilem_core::{DynMessage, Message, MessageProxy, NoElement, View, ViewId, ViewPathTracker}; use xilem_core::{
DynMessage, Message, MessageProxy, NoElement, View, ViewId, ViewMarker, ViewPathTracker,
};
use crate::ViewCtx; use crate::ViewCtx;
@ -70,6 +72,7 @@ pub struct AsyncRepeat<F, H, M> {
message: PhantomData<fn() -> M>, message: PhantomData<fn() -> M>,
} }
impl<F, H, M> ViewMarker for AsyncRepeat<F, H, M> {}
impl<State, Action, F, H, M, Fut> View<State, Action, ViewCtx> for AsyncRepeat<F, H, M> impl<State, Action, F, H, M, Fut> View<State, Action, ViewCtx> for AsyncRepeat<F, H, M>
where where
F: Fn(MessageProxy<M>) -> Fut + 'static, F: Fn(MessageProxy<M>) -> Fut + 'static,

View File

@ -3,7 +3,7 @@
use crate::{core::View, Pod}; use crate::{core::View, Pod};
use masonry::{widget, ArcStr}; use masonry::{widget, ArcStr};
use xilem_core::Mut; use xilem_core::{Mut, ViewMarker};
pub use masonry::PointerButton; pub use masonry::PointerButton;
@ -41,6 +41,7 @@ pub struct Button<F> {
callback: F, callback: F,
} }
impl<F> ViewMarker for Button<F> {}
impl<F, State, Action> View<State, Action, ViewCtx> for Button<F> impl<F, State, Action> View<State, Action, ViewCtx> for Button<F>
where where
F: Fn(&mut State, PointerButton) -> MessageResult<Action> + Send + Sync + 'static, F: Fn(&mut State, PointerButton) -> MessageResult<Action> + Send + Sync + 'static,

View File

@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use masonry::{widget, ArcStr}; use masonry::{widget, ArcStr};
use xilem_core::Mut; use xilem_core::{Mut, ViewMarker};
use crate::{MessageResult, Pod, View, ViewCtx, ViewId}; use crate::{MessageResult, Pod, View, ViewCtx, ViewId};
@ -27,6 +27,7 @@ pub struct Checkbox<F> {
callback: F, callback: F,
} }
impl<F> ViewMarker for Checkbox<F> {}
impl<F, State, Action> View<State, Action, ViewCtx> for Checkbox<F> impl<F, State, Action> View<State, Action, ViewCtx> for Checkbox<F>
where where
F: Fn(&mut State, bool) -> Action + Send + Sync + 'static, F: Fn(&mut State, bool) -> Action + Send + Sync + 'static,

View File

@ -9,36 +9,38 @@ use masonry::{
}; };
use xilem_core::{ use xilem_core::{
AppendVec, DynMessage, ElementSplice, MessageResult, Mut, SuperElement, View, ViewElement, AppendVec, DynMessage, ElementSplice, MessageResult, Mut, SuperElement, View, ViewElement,
ViewId, ViewPathTracker, ViewSequence, ViewId, ViewMarker, ViewPathTracker, ViewSequence,
}; };
use crate::{AnyWidgetView, Pod, ViewCtx, WidgetView}; use crate::{AnyWidgetView, Pod, ViewCtx, WidgetView};
pub use masonry::widget::{Axis, CrossAxisAlignment, FlexParams, MainAxisAlignment}; pub use masonry::widget::{Axis, CrossAxisAlignment, FlexParams, MainAxisAlignment};
pub fn flex<Seq, Marker>(sequence: Seq) -> Flex<Seq, Marker> { pub fn flex<State, Action, Seq: FlexSequence<State, Action>>(
sequence: Seq,
) -> Flex<Seq, State, Action> {
Flex { Flex {
phantom: PhantomData,
sequence, sequence,
axis: Axis::Vertical, axis: Axis::Vertical,
cross_axis_alignment: CrossAxisAlignment::Center, cross_axis_alignment: CrossAxisAlignment::Center,
main_axis_alignment: MainAxisAlignment::Start, main_axis_alignment: MainAxisAlignment::Start,
fill_major_axis: false, fill_major_axis: false,
gap: None, gap: None,
phantom: PhantomData,
} }
} }
pub struct Flex<Seq, Marker> { pub struct Flex<Seq, State, Action = ()> {
sequence: Seq, sequence: Seq,
axis: Axis, axis: Axis,
cross_axis_alignment: CrossAxisAlignment, cross_axis_alignment: CrossAxisAlignment,
main_axis_alignment: MainAxisAlignment, main_axis_alignment: MainAxisAlignment,
fill_major_axis: bool, fill_major_axis: bool,
phantom: PhantomData<fn() -> Marker>,
gap: Option<f64>, gap: Option<f64>,
phantom: PhantomData<fn() -> (State, Action)>,
} }
impl<Seq, Marker> Flex<Seq, Marker> { impl<Seq, State, Action> Flex<Seq, State, Action> {
pub fn direction(mut self, axis: Axis) -> Self { pub fn direction(mut self, axis: Axis) -> Self {
self.axis = axis; self.axis = axis;
self self
@ -85,9 +87,12 @@ impl<Seq, Marker> Flex<Seq, Marker> {
} }
} }
impl<State, Action, Seq, Marker: 'static> View<State, Action, ViewCtx> for Flex<Seq, Marker> impl<Seq, State, Action> ViewMarker for Flex<Seq, State, Action> {}
impl<State, Action, Seq> View<State, Action, ViewCtx> for Flex<Seq, State, Action>
where where
Seq: ViewSequence<State, Action, ViewCtx, FlexElement, Marker>, State: 'static,
Action: 'static,
Seq: FlexSequence<State, Action>,
{ {
type Element = Pod<widget::Flex>; type Element = Pod<widget::Flex>;
@ -303,6 +308,31 @@ impl ElementSplice<FlexElement> for FlexSplice<'_> {
} }
} }
/// An ordered sequence of views for a [`Flex`] view.
/// See [`ViewSequence`] for more technical details.
///
/// # Examples
///
/// ```
/// use xilem::view::{label, FlexSequence, FlexExt as _};
///
/// fn label_sequence<State: 'static>(
/// labels: impl Iterator<Item = &'static str>,
/// flex: f64,
/// ) -> impl FlexSequence<State> {
/// labels.map(|l| label(l).flex(flex)).collect::<Vec<_>>()
/// }
/// ```
pub trait FlexSequence<State, Action = ()>:
ViewSequence<State, Action, ViewCtx, FlexElement>
{
}
impl<Seq, State, Action> FlexSequence<State, Action> for Seq where
Seq: ViewSequence<State, Action, ViewCtx, FlexElement>
{
}
/// A trait which extends a [`WidgetView`] with methods to provide parameters for a flex item, or being able to use it interchangeably with a spacer /// A trait which extends a [`WidgetView`] with methods to provide parameters for a flex item, or being able to use it interchangeably with a spacer
pub trait FlexExt<State, Action>: WidgetView<State, Action> { pub trait FlexExt<State, Action>: WidgetView<State, Action> {
/// Applies [`impl Into<FlexParams>`](`FlexParams`) to this view, can be used as child of a [`Flex`] [`View`] /// Applies [`impl Into<FlexParams>`](`FlexParams`) to this view, can be used as child of a [`Flex`] [`View`]
@ -407,6 +437,7 @@ where
} }
} }
impl<V, State, Action> ViewMarker for FlexItem<V, State, Action> {}
impl<State, Action, V> View<State, Action, ViewCtx> for FlexItem<V, State, Action> impl<State, Action, V> View<State, Action, ViewCtx> for FlexItem<V, State, Action>
where where
State: 'static, State: 'static,
@ -485,6 +516,7 @@ impl<State, Action> From<FlexSpacer> for AnyFlexChild<State, Action> {
} }
} }
impl ViewMarker for FlexSpacer {}
// This impl doesn't require a view id, as it neither receives, nor sends any messages // This impl doesn't require a view id, as it neither receives, nor sends any messages
// If this should ever change, it's necessary to adjust the `AnyFlexChild` `View` impl // If this should ever change, it's necessary to adjust the `AnyFlexChild` `View` impl
impl<State, Action> View<State, Action, ViewCtx> for FlexSpacer { impl<State, Action> View<State, Action, ViewCtx> for FlexSpacer {
@ -593,6 +625,7 @@ pub struct AnyFlexChildState<State: 'static, Action: 'static> {
generation: u64, generation: u64,
} }
impl<State, Action> ViewMarker for AnyFlexChild<State, Action> {}
impl<State, Action> View<State, Action, ViewCtx> for AnyFlexChild<State, Action> impl<State, Action> View<State, Action, ViewCtx> for AnyFlexChild<State, Action>
where where
State: 'static, State: 'static,

View File

@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use masonry::{text::TextBrush, widget, ArcStr}; use masonry::{text::TextBrush, widget, ArcStr};
use xilem_core::Mut; use xilem_core::{Mut, ViewMarker};
use crate::{Color, MessageResult, Pod, TextAlignment, View, ViewCtx, ViewId}; use crate::{Color, MessageResult, Pod, TextAlignment, View, ViewCtx, ViewId};
@ -43,6 +43,7 @@ impl Label {
} }
} }
impl ViewMarker for Label {}
impl<State, Action> View<State, Action, ViewCtx> for Label { impl<State, Action> View<State, Action, ViewCtx> for Label {
type Element = Pod<widget::Label>; type Element = Pod<widget::Label>;
type ViewState = (); type ViewState = ();

View File

@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use masonry::{text::TextBrush, widget, ArcStr}; use masonry::{text::TextBrush, widget, ArcStr};
use xilem_core::Mut; use xilem_core::{Mut, ViewMarker};
use crate::{Color, MessageResult, Pod, TextAlignment, View, ViewCtx, ViewId}; use crate::{Color, MessageResult, Pod, TextAlignment, View, ViewCtx, ViewId};
@ -44,6 +44,7 @@ impl Prose {
} }
} }
impl ViewMarker for Prose {}
impl<State, Action> View<State, Action, ViewCtx> for Prose { impl<State, Action> View<State, Action, ViewCtx> for Prose {
type Element = Pod<widget::Prose>; type Element = Pod<widget::Prose>;
type ViewState = (); type ViewState = ();

View File

@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use masonry::widget; use masonry::widget;
use xilem_core::ViewMarker;
use crate::{ use crate::{
core::{Mut, View, ViewId}, core::{Mut, View, ViewId},
@ -74,6 +75,7 @@ impl<V> SizedBox<V> {
} }
} }
impl<V> ViewMarker for SizedBox<V> {}
impl<V, State, Action> View<State, Action, ViewCtx> for SizedBox<V> impl<V, State, Action> View<State, Action, ViewCtx> for SizedBox<V>
where where
V: WidgetView<State, Action>, V: WidgetView<State, Action>,

View File

@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use masonry::{text::TextBrush, widget}; use masonry::{text::TextBrush, widget};
use xilem_core::{Mut, View}; use xilem_core::{Mut, View, ViewMarker};
use crate::{Color, MessageResult, Pod, TextAlignment, ViewCtx, ViewId}; use crate::{Color, MessageResult, Pod, TextAlignment, ViewCtx, ViewId};
@ -63,6 +63,7 @@ impl<State, Action> Textbox<State, Action> {
} }
} }
impl<State, Action> ViewMarker for Textbox<State, Action> {}
impl<State: 'static, Action: 'static> View<State, Action, ViewCtx> for Textbox<State, Action> { impl<State: 'static, Action: 'static> View<State, Action, ViewCtx> for Textbox<State, Action> {
type Element = Pod<widget::Textbox>; type Element = Pod<widget::Textbox>;
type ViewState = (); type ViewState = ();

View File

@ -4,7 +4,7 @@
use std::{io::stdin, path::PathBuf}; use std::{io::stdin, path::PathBuf};
use xilem_core::{ use xilem_core::{
AnyElement, AnyView, Mut, SuperElement, View, ViewElement, ViewId, ViewPathTracker, AnyElement, AnyView, Mut, SuperElement, View, ViewElement, ViewId, ViewMarker, ViewPathTracker,
}; };
#[derive(Debug)] #[derive(Debug)]
@ -145,6 +145,7 @@ impl ViewElement for FsPath {
type Mut<'a> = &'a mut PathBuf; type Mut<'a> = &'a mut PathBuf;
} }
impl ViewMarker for File {}
impl<State, Action> View<State, Action, ViewCtx> for File { impl<State, Action> View<State, Action, ViewCtx> for File {
type Element = FsPath; type Element = FsPath;
type ViewState = (); type ViewState = ();

View File

@ -6,7 +6,8 @@
use core::any::Any; use core::any::Any;
use xilem_core::{ use xilem_core::{
DynMessage, MessageResult, Mut, SuperElement, View, ViewElement, ViewId, ViewPathTracker, DynMessage, MessageResult, Mut, SuperElement, View, ViewElement, ViewId, ViewMarker,
ViewPathTracker,
}; };
pub fn app_logic(_: &mut u32) -> impl WidgetView<u32> { pub fn app_logic(_: &mut u32) -> impl WidgetView<u32> {
@ -44,6 +45,7 @@ impl<W: Widget> ViewElement for WidgetPod<W> {
type Mut<'a> = WidgetMut<'a, W>; type Mut<'a> = WidgetMut<'a, W>;
} }
impl ViewMarker for Button {}
impl<State, Action> View<State, Action, ViewCtx> for Button { impl<State, Action> View<State, Action, ViewCtx> for Button {
type Element = WidgetPod<ButtonWidget>; type Element = WidgetPod<ButtonWidget>;
type ViewState = (); type ViewState = ();

View File

@ -8,7 +8,8 @@ use core::any::Any;
use alloc::boxed::Box; use alloc::boxed::Box;
use crate::{ use crate::{
AnyElement, DynMessage, MessageResult, Mut, View, ViewElement, ViewId, ViewPathTracker, AnyElement, DynMessage, MessageResult, Mut, View, ViewElement, ViewId, ViewMarker,
ViewPathTracker,
}; };
/// A view which can have any view type where the [`View::Element`] is compatible with /// A view which can have any view type where the [`View::Element`] is compatible with
@ -171,6 +172,10 @@ pub struct AnyViewState {
generation: u64, generation: u64,
} }
impl<State, Action, Context, Element, Message> ViewMarker
for dyn AnyView<State, Action, Context, Element, Message>
{
}
impl<State, Action, Context, Element, Message> View<State, Action, Context, Message> impl<State, Action, Context, Element, Message> View<State, Action, Context, Message>
for dyn AnyView<State, Action, Context, Element, Message> for dyn AnyView<State, Action, Context, Element, Message>
where where
@ -220,6 +225,11 @@ where
} }
// TODO: IWBN if we could avoid this // TODO: IWBN if we could avoid this
impl<State, Action, Context, Element, Message> ViewMarker
for dyn AnyView<State, Action, Context, Element, Message> + Send
{
}
impl<State, Action, Context, Element, Message> View<State, Action, Context, Message> impl<State, Action, Context, Element, Message> View<State, Action, Context, Message>
for dyn AnyView<State, Action, Context, Element, Message> + Send for dyn AnyView<State, Action, Context, Element, Message> + Send
where where
@ -268,6 +278,10 @@ where
} }
} }
impl<State, Action, Context, Element, Message> ViewMarker
for dyn AnyView<State, Action, Context, Element, Message> + Send + Sync
{
}
impl<State, Action, Context, Element, Message> View<State, Action, Context, Message> impl<State, Action, Context, Element, Message> View<State, Action, Context, Message>
for dyn AnyView<State, Action, Context, Element, Message> + Send + Sync for dyn AnyView<State, Action, Context, Element, Message> + Send + Sync
where where
@ -316,6 +330,10 @@ where
} }
} }
impl<State, Action, Context, Element, Message> ViewMarker
for dyn AnyView<State, Action, Context, Element, Message> + Sync
{
}
impl<State, Action, Context, Element, Message> View<State, Action, Context, Message> impl<State, Action, Context, Element, Message> View<State, Action, Context, Message>
for dyn AnyView<State, Action, Context, Element, Message> + Sync for dyn AnyView<State, Action, Context, Element, Message> + Sync
where where

View File

@ -94,3 +94,16 @@ pub struct NoElement;
impl ViewElement for NoElement { impl ViewElement for NoElement {
type Mut<'a> = (); type Mut<'a> = ();
} }
impl SuperElement<NoElement> for NoElement {
fn upcast(child: NoElement) -> Self {
child
}
fn with_downcast_val<R>(
this: Mut<'_, Self>,
f: impl FnOnce(Mut<'_, NoElement>) -> R,
) -> (Self::Mut<'_>, R) {
((), f(this))
}
}

View File

@ -29,7 +29,7 @@ mod deferred;
pub use deferred::{AsyncCtx, MessageProxy, PhantomView, ProxyError, RawProxy}; pub use deferred::{AsyncCtx, MessageProxy, PhantomView, ProxyError, RawProxy};
mod view; mod view;
pub use view::{View, ViewId, ViewPathTracker}; pub use view::{View, ViewId, ViewMarker, ViewPathTracker};
mod views; mod views;
pub use views::{ pub use views::{

View File

@ -9,8 +9,10 @@ use core::sync::atomic::Ordering;
use alloc::vec::Drain; use alloc::vec::Drain;
use alloc::vec::Vec; use alloc::vec::Vec;
use crate::element::NoElement; // use crate::element::NoElement;
use crate::{DynMessage, MessageResult, SuperElement, View, ViewElement, ViewId, ViewPathTracker}; use crate::{
DynMessage, MessageResult, SuperElement, View, ViewElement, ViewId, ViewMarker, ViewPathTracker,
};
/// An append only `Vec`. /// An append only `Vec`.
/// ///
@ -74,11 +76,11 @@ impl<T> Default for AppendVec<T> {
/// Note that this will have persistent allocation with size proportional /// Note that this will have persistent allocation with size proportional
/// to the *longest* `Vec` which is ever provided in the View tree, as this /// to the *longest* `Vec` which is ever provided in the View tree, as this
/// uses a generational indexing scheme. /// uses a generational indexing scheme.
/// - An [`array`] of `ViewSequence` values.
/// - Tuples of `ViewSequences` with up to 15 elements. /// - Tuples of `ViewSequences` with up to 15 elements.
/// These can be nested if an ad-hoc sequence of more than 15 sequences is needed. /// These can be nested if an ad-hoc sequence of more than 15 sequences is needed.
/// ///
pub trait ViewSequence<State, Action, Context, Element, Marker, Message = DynMessage>: pub trait ViewSequence<State, Action, Context, Element, Message = DynMessage>: 'static
'static
where where
Context: ViewPathTracker, Context: ViewPathTracker,
Element: ViewElement, Element: ViewElement,
@ -143,15 +145,11 @@ pub trait ElementSplice<Element: ViewElement> {
fn delete<R>(&mut self, f: impl FnOnce(Element::Mut<'_>) -> R) -> R; fn delete<R>(&mut self, f: impl FnOnce(Element::Mut<'_>) -> R) -> R;
} }
/// Marker type to workaround trait ambiguity.
#[doc(hidden)]
pub struct WasAView;
impl<State, Action, Context, V, Element, Message> impl<State, Action, Context, V, Element, Message>
ViewSequence<State, Action, Context, Element, WasAView, Message> for V ViewSequence<State, Action, Context, Element, Message> for V
where where
Context: ViewPathTracker, Context: ViewPathTracker,
V: View<State, Action, Context, Message>, V: View<State, Action, Context, Message> + ViewMarker,
Element: SuperElement<V::Element>, Element: SuperElement<V::Element>,
V::Element: ViewElement, V::Element: ViewElement,
{ {
@ -218,10 +216,10 @@ pub struct OptionSeqState<InnerState> {
/// ///
/// Will mark messages which were sent to a `Some` value if a `None` has since /// Will mark messages which were sent to a `Some` value if a `None` has since
/// occurred as stale. /// occurred as stale.
impl<State, Action, Context, Element, Marker, Seq, Message> impl<State, Action, Context, Element, Seq, Message>
ViewSequence<State, Action, Context, Element, Option<Marker>, Message> for Option<Seq> ViewSequence<State, Action, Context, Element, Message> for Option<Seq>
where where
Seq: ViewSequence<State, Action, Context, Element, Marker, Message>, Seq: ViewSequence<State, Action, Context, Element, Message>,
Context: ViewPathTracker, Context: ViewPathTracker,
Element: ViewElement, Element: ViewElement,
{ {
@ -338,57 +336,6 @@ where
} }
} }
/// A `View` with [no element](crate::NoElement) can be added to any [`ViewSequence`], because it does not use any
/// properties of the `Element` type.
impl<State, Action, Context, Element, NoElementView, Message>
ViewSequence<State, Action, Context, Element, NoElement, Message> for NoElementView
where
NoElementView: View<State, Action, Context, Message, Element = NoElement>,
Element: ViewElement,
Context: ViewPathTracker,
{
#[doc(hidden)]
type SeqState = NoElementView::ViewState;
#[doc(hidden)]
fn seq_build(&self, ctx: &mut Context, _: &mut AppendVec<Element>) -> Self::SeqState {
let (NoElement, state) = self.build(ctx);
state
}
#[doc(hidden)]
fn seq_rebuild(
&self,
prev: &Self,
seq_state: &mut Self::SeqState,
ctx: &mut Context,
_: &mut impl ElementSplice<Element>,
) {
self.rebuild(prev, seq_state, ctx, ());
}
#[doc(hidden)]
fn seq_teardown(
&self,
seq_state: &mut Self::SeqState,
ctx: &mut Context,
_: &mut impl ElementSplice<Element>,
) {
self.teardown(seq_state, ctx, ());
}
#[doc(hidden)]
fn seq_message(
&self,
seq_state: &mut Self::SeqState,
id_path: &[ViewId],
message: Message,
app_state: &mut State,
) -> MessageResult<Action, Message> {
self.message(seq_state, id_path, message, app_state)
}
}
/// The state used to implement `ViewSequence` for `Vec<impl ViewSequence>` /// The state used to implement `ViewSequence` for `Vec<impl ViewSequence>`
/// ///
/// We use a generation arena for vector types, with half of the `ViewId` dedicated /// We use a generation arena for vector types, with half of the `ViewId` dedicated
@ -428,10 +375,10 @@ fn view_id_to_index_generation(view_id: ViewId) -> (usize, u32) {
/// ///
/// Will mark messages which were sent to any index as stale if /// Will mark messages which were sent to any index as stale if
/// that index has been unused in the meantime. /// that index has been unused in the meantime.
impl<State, Action, Context, Element, Marker, Seq, Message> impl<State, Action, Context, Element, Seq, Message>
ViewSequence<State, Action, Context, Element, Vec<Marker>, Message> for Vec<Seq> ViewSequence<State, Action, Context, Element, Message> for Vec<Seq>
where where
Seq: ViewSequence<State, Action, Context, Element, Marker, Message>, Seq: ViewSequence<State, Action, Context, Element, Message>,
Context: ViewPathTracker, Context: ViewPathTracker,
Element: ViewElement, Element: ViewElement,
{ {
@ -582,10 +529,10 @@ where
} }
} }
impl<State, Action, Context, Element, Marker, Seq, Message, const N: usize> impl<State, Action, Context, Element, Seq, Message, const N: usize>
ViewSequence<State, Action, Context, Element, Vec<Marker>, Message> for [Seq; N] ViewSequence<State, Action, Context, Element, Message> for [Seq; N]
where where
Seq: ViewSequence<State, Action, Context, Element, Marker, Message>, Seq: ViewSequence<State, Action, Context, Element, Message>,
Context: ViewPathTracker, Context: ViewPathTracker,
Element: ViewElement, Element: ViewElement,
{ {
@ -661,7 +608,7 @@ where
} }
impl<State, Action, Context, Element, Message> impl<State, Action, Context, Element, Message>
ViewSequence<State, Action, Context, Element, (), Message> for () ViewSequence<State, Action, Context, Element, Message> for ()
where where
Context: ViewPathTracker, Context: ViewPathTracker,
Element: ViewElement, Element: ViewElement,
@ -699,10 +646,10 @@ where
} }
} }
impl<State, Action, Context, Element, Marker, Seq, Message> impl<State, Action, Context, Element, Seq, Message>
ViewSequence<State, Action, Context, Element, (Marker,), Message> for (Seq,) ViewSequence<State, Action, Context, Element, Message> for (Seq,)
where where
Seq: ViewSequence<State, Action, Context, Element, Marker, Message>, Seq: ViewSequence<State, Action, Context, Element, Message>,
Context: ViewPathTracker, Context: ViewPathTracker,
Element: ViewElement, Element: ViewElement,
{ {
@ -753,12 +700,9 @@ macro_rules! impl_view_tuple {
Action, Action,
Context: ViewPathTracker, Context: ViewPathTracker,
Element: ViewElement, Element: ViewElement,
$( $($seq: ViewSequence<State, Action, Context, Element, Message>,)+
$marker,
$seq: ViewSequence<State, Action, Context, Element, $marker, Message>,
)+
Message, Message,
> ViewSequence<State, Action, Context, Element, ($($marker,)+), Message> for ($($seq,)+) > ViewSequence<State, Action, Context, Element, Message> for ($($seq,)+)
{ {
type SeqState = ($($seq::SeqState,)+); type SeqState = ($($seq::SeqState,)+);

View File

@ -9,6 +9,22 @@ use alloc::{boxed::Box, sync::Arc};
use crate::{message::MessageResult, DynMessage, Mut, ViewElement}; use crate::{message::MessageResult, DynMessage, Mut, ViewElement};
/// A type which can be a [`View`]. Imposes no requirements on the underlying type.
/// Should be implemented alongside every `View` implementation:
/// ```ignore
/// impl<...> ViewMarker for Button<...> {}
/// impl<...> View<...> for Button<...> {...}
/// ```
///
/// ## Details
///
/// Because `View` is generic, Rust [allows you](https://doc.rust-lang.org/reference/items/implementations.html#orphan-rules) to implement this trait for certain non-local types.
/// These non-local types can include `Vec<_>` and `Option<_>`.
/// If this trait were not present, those implementations of `View` would conflict with those types' implementations of `ViewSequence`.
/// This is because every `View` type also implementations `ViewSequence`.
/// Since `ViewMarker` is not generic, these non-local implementations are not permitted for this trait, which means that the conflicting implementation cannot happen.
pub trait ViewMarker {}
/// A lightweight, short-lived representation of the state of a retained /// A lightweight, short-lived representation of the state of a retained
/// structure, usually a user interface node. /// structure, usually a user interface node.
/// ///
@ -30,11 +46,21 @@ use crate::{message::MessageResult, DynMessage, Mut, ViewElement};
/// During message handling, mutable access to the app state is given to view nodes, /// During message handling, mutable access to the app state is given to view nodes,
/// which will in turn generally expose it to callbacks. /// which will in turn generally expose it to callbacks.
/// ///
/// Due to restrictions of the [orphan rules](https://doc.rust-lang.org/reference/items/implementations.html#orphan-rules),
/// `ViewMarker` needs to be implemented for every type that implements `View`, see [`ViewMarker`] for more details.
/// For example:
/// ```ignore
/// impl<...> ViewMarker for Button<...> {}
/// impl<...> View<...> for Button<...> {...}
/// ```
///
/// ## Alloc /// ## Alloc
/// ///
/// In order to support the default open-ended [`DynMessage`] type as `Message`, this trait requires an /// In order to support the default open-ended [`DynMessage`] type as `Message`, this trait requires an
/// allocator to be available. /// allocator to be available.
pub trait View<State, Action, Context: ViewPathTracker, Message = DynMessage>: 'static { pub trait View<State, Action, Context: ViewPathTracker, Message = DynMessage>:
ViewMarker + 'static
{
/// The element type which this view operates on. /// The element type which this view operates on.
type Element: ViewElement; type Element: ViewElement;
/// State that is used over the lifetime of the retained representation of the view. /// State that is used over the lifetime of the retained representation of the view.
@ -137,6 +163,7 @@ pub trait ViewPathTracker {
} }
} }
impl<V: ?Sized> ViewMarker for Box<V> {}
impl<State, Action, Context, Message, V> View<State, Action, Context, Message> for Box<V> impl<State, Action, Context, Message, V> View<State, Action, Context, Message> for Box<V>
where where
Context: ViewPathTracker, Context: ViewPathTracker,
@ -185,6 +212,7 @@ pub struct ArcState<ViewState> {
dirty: bool, dirty: bool,
} }
impl<V: ?Sized> ViewMarker for Arc<V> {}
/// An implementation of [`View`] which only runs rebuild if the states are different /// An implementation of [`View`] which only runs rebuild if the states are different
impl<State, Action, Context, Message, V> View<State, Action, Context, Message> for Arc<V> impl<State, Action, Context, Message, V> View<State, Action, Context, Message> for Arc<V>
where where

View File

@ -3,7 +3,7 @@
use core::marker::PhantomData; use core::marker::PhantomData;
use crate::{MessageResult, Mut, View, ViewId, ViewPathTracker}; use crate::{MessageResult, Mut, View, ViewId, ViewMarker, ViewPathTracker};
/// A view that wraps a child view and modifies the state that callbacks have access to. /// A view that wraps a child view and modifies the state that callbacks have access to.
pub struct Adapt< pub struct Adapt<
@ -18,9 +18,7 @@ pub struct Adapt<
&mut ParentState, &mut ParentState,
AdaptThunk<ChildState, ChildAction, Context, ChildView, Message>, AdaptThunk<ChildState, ChildAction, Context, ChildView, Message>,
) -> MessageResult<ParentAction>, ) -> MessageResult<ParentAction>,
> where > {
Context: ViewPathTracker,
{
proxy_fn: ProxyFn, proxy_fn: ProxyFn,
child: ChildView, child: ChildView,
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
@ -147,6 +145,10 @@ where
} }
} }
impl<ParentState, ParentAction, ChildState, ChildAction, Context, Message, V, F> ViewMarker
for Adapt<ParentState, ParentAction, ChildState, ChildAction, Context, V, Message, F>
{
}
impl<ParentState, ParentAction, ChildState, ChildAction, Context, Message, V, F> impl<ParentState, ParentAction, ChildState, ChildAction, Context, Message, V, F>
View<ParentState, ParentAction, Context, Message> View<ParentState, ParentAction, Context, Message>
for Adapt<ParentState, ParentAction, ChildState, ChildAction, Context, V, Message, F> for Adapt<ParentState, ParentAction, ChildState, ChildAction, Context, V, Message, F>

View File

@ -1,40 +1,37 @@
// Copyright 2024 the Xilem Authors // Copyright 2024 the Xilem Authors
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use core::marker::PhantomData;
use crate::{ use crate::{
AppendVec, ElementSplice, Mut, NoElement, View, ViewId, ViewPathTracker, ViewSequence, AppendVec, ElementSplice, Mut, NoElement, View, ViewId, ViewMarker, ViewPathTracker,
ViewSequence,
}; };
/// Create a view which acts as `active_view`, whilst also running `alongside_view`, without inserting it into the tree. /// Create a view which acts as `active_view`, whilst also running `alongside_view`, without inserting it into the tree.
/// ///
/// `alongside_view` must be a `ViewSequence` with an element type of [`NoElement`]. /// `alongside_view` must be a `ViewSequence` with an element type of [`NoElement`].
pub fn fork<Active, Alongside, Marker>( pub fn fork<Active, Alongside>(
active_view: Active, active_view: Active,
alongside_view: Alongside, alongside_view: Alongside,
) -> Fork<Active, Alongside, Marker> { ) -> Fork<Active, Alongside> {
Fork { Fork {
active_view, active_view,
alongside_view, alongside_view,
marker: PhantomData,
} }
} }
/// The view for [`fork`]. /// The view for [`fork`].
pub struct Fork<Active, Alongside, Marker> { pub struct Fork<Active, Alongside> {
active_view: Active, active_view: Active,
alongside_view: Alongside, alongside_view: Alongside,
marker: PhantomData<Marker>,
} }
impl<State, Action, Context, Active, Alongside, Marker, Message> impl<Active, Alongside> ViewMarker for Fork<Active, Alongside> {}
View<State, Action, Context, Message> for Fork<Active, Alongside, Marker> impl<State, Action, Context, Active, Alongside, Message> View<State, Action, Context, Message>
for Fork<Active, Alongside>
where where
Active: View<State, Action, Context, Message>, Active: View<State, Action, Context, Message>,
Alongside: ViewSequence<State, Action, Context, NoElement, Marker, Message>, Alongside: ViewSequence<State, Action, Context, NoElement, Message>,
Context: ViewPathTracker, Context: ViewPathTracker,
Marker: 'static,
{ {
type Element = Active::Element; type Element = Active::Element;
@ -111,12 +108,8 @@ where
/// A stub `ElementSplice` implementation for `NoElement`. /// A stub `ElementSplice` implementation for `NoElement`.
/// ///
/// We know that none of the methods will be called, because the `ViewSequence`
/// implementation for `NoElement` views does not use the provided `elements`.
///
/// It is technically possible for someone to create an implementation of `ViewSequence` /// It is technically possible for someone to create an implementation of `ViewSequence`
/// which uses a `NoElement` `ElementSplice`. But we don't think that sequence could be meaningful, /// which uses a `NoElement` `ElementSplice`. But we don't think that sequence could be meaningful.
/// so we still panic in that case.
struct NoElements; struct NoElements;
impl ElementSplice<NoElement> for NoElements { impl ElementSplice<NoElement> for NoElements {
@ -127,21 +120,15 @@ impl ElementSplice<NoElement> for NoElements {
ret ret
} }
fn insert(&mut self, _: NoElement) { fn insert(&mut self, _: NoElement) {}
unreachable!()
fn mutate<R>(&mut self, f: impl FnOnce(<NoElement as crate::ViewElement>::Mut<'_>) -> R) -> R {
f(())
} }
fn mutate<R>(&mut self, _: impl FnOnce(<NoElement as crate::ViewElement>::Mut<'_>) -> R) -> R { fn skip(&mut self, _: usize) {}
unreachable!()
}
fn skip(&mut self, n: usize) { fn delete<R>(&mut self, f: impl FnOnce(<NoElement as crate::ViewElement>::Mut<'_>) -> R) -> R {
if n > 0 { f(())
unreachable!()
}
}
fn delete<R>(&mut self, _: impl FnOnce(<NoElement as crate::ViewElement>::Mut<'_>) -> R) -> R {
unreachable!()
} }
} }

View File

@ -3,7 +3,7 @@
use core::marker::PhantomData; use core::marker::PhantomData;
use crate::{Mut, View, ViewId, ViewPathTracker}; use crate::{Mut, View, ViewId, ViewMarker, ViewPathTracker};
/// A view that maps a child [`View<State,ChildAction,_>`] to [`View<State,ParentAction,_>`] while providing mutable access to `State` in the map function. /// A view that maps a child [`View<State,ChildAction,_>`] to [`View<State,ParentAction,_>`] while providing mutable access to `State` in the map function.
/// ///
@ -68,6 +68,10 @@ where
} }
} }
impl<State, ParentAction, ChildAction, V, F> ViewMarker
for MapAction<State, ParentAction, ChildAction, V, F>
{
}
impl<State, ParentAction, ChildAction, Context: ViewPathTracker, Message, V, F> impl<State, ParentAction, ChildAction, Context: ViewPathTracker, Message, V, F>
View<State, ParentAction, Context, Message> View<State, ParentAction, Context, Message>
for MapAction<State, ParentAction, ChildAction, V, F> for MapAction<State, ParentAction, ChildAction, V, F>

View File

@ -3,7 +3,7 @@
use core::marker::PhantomData; use core::marker::PhantomData;
use crate::{MessageResult, Mut, View, ViewId, ViewPathTracker}; use crate::{MessageResult, Mut, View, ViewId, ViewMarker, ViewPathTracker};
/// A view that "extracts" state from a [`View<ParentState,_,_>`] to [`View<ChildState,_,_>`]. /// A view that "extracts" state from a [`View<ParentState,_,_>`] to [`View<ChildState,_,_>`].
/// This allows modularization of views based on their state. /// This allows modularization of views based on their state.
@ -56,6 +56,7 @@ where
} }
} }
impl<ParentState, ChildState, V, F> ViewMarker for MapState<ParentState, ChildState, V, F> {}
impl<ParentState, ChildState, Action, Context: ViewPathTracker, Message, V, F> impl<ParentState, ChildState, Action, Context: ViewPathTracker, Message, V, F>
View<ParentState, Action, Context, Message> for MapState<ParentState, ChildState, V, F> View<ParentState, Action, Context, Message> for MapState<ParentState, ChildState, V, F>
where where

View File

@ -4,7 +4,7 @@
use core::marker::PhantomData; use core::marker::PhantomData;
use core::mem::size_of; use core::mem::size_of;
use crate::{MessageResult, Mut, View, ViewId, ViewPathTracker}; use crate::{MessageResult, Mut, View, ViewId, ViewMarker, ViewPathTracker};
/// A view which supports Memoization. /// A view which supports Memoization.
/// ///
@ -64,6 +64,7 @@ pub struct MemoizeState<V, VState> {
dirty: bool, dirty: bool,
} }
impl<Data, ViewFn, State, Action> ViewMarker for Memoize<Data, ViewFn, State, Action> {}
impl<State, Action, Context, Data, V, ViewFn, Message> View<State, Action, Context, Message> impl<State, Action, Context, Data, V, ViewFn, Message> View<State, Action, Context, Message>
for Memoize<Data, ViewFn, State, Action> for Memoize<Data, ViewFn, State, Action>
where where
@ -172,6 +173,7 @@ where
} }
} }
impl<InitView, State, Action> ViewMarker for Frozen<InitView, State, Action> {}
impl<State, Action, Context, Message, V, InitView> View<State, Action, Context, Message> impl<State, Action, Context, Message, V, InitView> View<State, Action, Context, Message>
for Frozen<InitView, State, Action> for Frozen<InitView, State, Action>
where where

View File

@ -3,7 +3,7 @@
//! Statically typed alternatives to the type-erased [`AnyView`](`crate::AnyView`). //! Statically typed alternatives to the type-erased [`AnyView`](`crate::AnyView`).
use crate::{MessageResult, Mut, View, ViewElement, ViewId, ViewPathTracker}; use crate::{MessageResult, Mut, View, ViewElement, ViewId, ViewMarker, ViewPathTracker};
use hidden::OneOfState; use hidden::OneOfState;
/// This trait allows, specifying a type as `ViewElement`, which should never be constructed or used, /// This trait allows, specifying a type as `ViewElement`, which should never be constructed or used,
@ -146,6 +146,7 @@ pub trait OneOfCtx<
); );
} }
impl<A, B, C, D, E, F, G, H, I> ViewMarker for OneOf<A, B, C, D, E, F, G, H, I> {}
/// The `OneOf` types and `Either` are [`View`]s if all of their possible types are themselves `View`s. /// The `OneOf` types and `Either` are [`View`]s if all of their possible types are themselves `View`s.
impl<State, Action, Context, Message, A, B, C, D, E, F, G, H, I> impl<State, Action, Context, Message, A, B, C, D, E, F, G, H, I>
View<State, Action, Context, Message> for OneOf<A, B, C, D, E, F, G, H, I> View<State, Action, Context, Message> for OneOf<A, B, C, D, E, F, G, H, I>
@ -517,13 +518,14 @@ where
// to export it. Since this (`one_of`) module is public, we create a new module, allowing it to be pub but not exposed. // to export it. Since this (`one_of`) module is public, we create a new module, allowing it to be pub but not exposed.
#[doc(hidden)] #[doc(hidden)]
mod hidden { mod hidden {
use crate::View; use crate::{View, ViewMarker};
use super::PhantomElementCtx; use super::PhantomElementCtx;
#[allow(unreachable_pub)] #[allow(unreachable_pub)]
pub enum Never {} pub enum Never {}
impl ViewMarker for Never {}
impl<State, Action, Context: PhantomElementCtx, Message> View<State, Action, Context, Message> impl<State, Action, Context: PhantomElementCtx, Message> View<State, Action, Context, Message>
for Never for Never
{ {

View File

@ -1,7 +1,9 @@
// Copyright 2024 the Xilem Authors // Copyright 2024 the Xilem Authors
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use crate::{DynMessage, MessageResult, Mut, View, ViewElement, ViewId, ViewPathTracker}; use crate::{
DynMessage, MessageResult, Mut, View, ViewElement, ViewId, ViewMarker, ViewPathTracker,
};
/// This trait provides a way to add [`View`] implementations for types that would be restricted otherwise by the orphan rules. /// This trait provides a way to add [`View`] implementations for types that would be restricted otherwise by the orphan rules.
/// Every type that can be supported with this trait, needs a concrete `View` implementation in `xilem_core`, possibly feature-gated. /// Every type that can be supported with this trait, needs a concrete `View` implementation in `xilem_core`, possibly feature-gated.
@ -43,6 +45,8 @@ pub trait OrphanView<V, State, Action, Message = DynMessage>: ViewPathTracker +
macro_rules! impl_orphan_view_for { macro_rules! impl_orphan_view_for {
($ty: ty) => { ($ty: ty) => {
impl ViewMarker for $ty {}
impl<State, Action, Context, Message> View<State, Action, Context, Message> for $ty impl<State, Action, Context, Message> View<State, Action, Context, Message> for $ty
where where
Context: OrphanView<$ty, State, Action, Message>, Context: OrphanView<$ty, State, Action, Message>,
@ -111,7 +115,7 @@ impl_orphan_view_for!(usize);
/// These [`OrphanView`] implementations can e.g. be used in a vector graphics context, as for example seen in `xilem_web` within svg nodes /// These [`OrphanView`] implementations can e.g. be used in a vector graphics context, as for example seen in `xilem_web` within svg nodes
mod kurbo { mod kurbo {
use super::OrphanView; use super::OrphanView;
use crate::{MessageResult, Mut, View, ViewId}; use crate::{MessageResult, Mut, View, ViewId, ViewMarker};
impl_orphan_view_for!(kurbo::PathSeg); impl_orphan_view_for!(kurbo::PathSeg);
impl_orphan_view_for!(kurbo::Arc); impl_orphan_view_for!(kurbo::Arc);
impl_orphan_view_for!(kurbo::BezPath); impl_orphan_view_for!(kurbo::BezPath);

View File

@ -3,7 +3,7 @@
use core::fmt::Debug; use core::fmt::Debug;
use crate::{MessageResult, NoElement, View, ViewPathTracker}; use crate::{MessageResult, NoElement, View, ViewMarker, ViewPathTracker};
/// A view which executes `once` exactly once. /// A view which executes `once` exactly once.
/// ///
@ -75,6 +75,7 @@ pub struct RunOnce<F> {
once: F, once: F,
} }
impl<F> ViewMarker for RunOnce<F> {}
impl<F, State, Action, Context, Message> View<State, Action, Context, Message> for RunOnce<F> impl<F, State, Action, Context, Message> View<State, Action, Context, Message> for RunOnce<F>
where where
Context: ViewPathTracker, Context: ViewPathTracker,

View File

@ -4,8 +4,6 @@
#![allow(dead_code)] // This is a utility module, which means that some exposed items aren't #![allow(dead_code)] // This is a utility module, which means that some exposed items aren't
#![deny(unreachable_pub)] #![deny(unreachable_pub)]
use std::marker::PhantomData;
use xilem_core::*; use xilem_core::*;
#[derive(Default)] #[derive(Default)]
@ -68,27 +66,22 @@ pub(super) struct Action {
_priv: (), _priv: (),
} }
pub(super) struct SequenceView<Seq, Marker> { pub(super) struct SequenceView<Seq> {
id: u32, id: u32,
seq: Seq, seq: Seq,
phantom: PhantomData<Marker>,
} }
pub(super) fn sequence<Seq, Marker>(id: u32, seq: Seq) -> SequenceView<Seq, Marker> pub(super) fn sequence<Seq>(id: u32, seq: Seq) -> SequenceView<Seq>
where where
Seq: ViewSequence<(), Action, TestCtx, TestElement, Marker>, Seq: ViewSequence<(), Action, TestCtx, TestElement>,
{ {
SequenceView { SequenceView { id, seq }
id,
seq,
phantom: PhantomData,
}
} }
impl<Seq, Marker> View<(), Action, TestCtx> for SequenceView<Seq, Marker> impl<Seq> ViewMarker for SequenceView<Seq> {}
impl<Seq> View<(), Action, TestCtx> for SequenceView<Seq>
where where
Seq: ViewSequence<(), Action, TestCtx, TestElement, Marker>, Seq: ViewSequence<(), Action, TestCtx, TestElement>,
Marker: 'static,
{ {
type Element = TestElement; type Element = TestElement;
@ -160,6 +153,7 @@ where
} }
} }
impl<const N: u32> ViewMarker for OperationView<N> {}
impl<const N: u32> View<(), Action, TestCtx> for OperationView<N> { impl<const N: u32> View<(), Action, TestCtx> for OperationView<N> {
type Element = TestElement; type Element = TestElement;

View File

@ -3,7 +3,7 @@
use std::marker::PhantomData; use std::marker::PhantomData;
use wasm_bindgen::{JsCast, UnwrapThrowExt}; use wasm_bindgen::{JsCast, UnwrapThrowExt};
use xilem_core::{MessageResult, Mut, View, ViewElement, ViewId}; use xilem_core::{MessageResult, Mut, View, ViewElement, ViewId, ViewMarker};
use crate::{ use crate::{
vecmap::VecMap, AttributeValue, DomNode, DynMessage, ElementProps, Pod, PodMut, ViewCtx, vecmap::VecMap, AttributeValue, DomNode, DynMessage, ElementProps, Pod, PodMut, ViewCtx,
@ -249,7 +249,8 @@ impl<E, T, A> Attr<E, T, A> {
} }
} }
impl<T, A, E> View<T, A, ViewCtx, DynMessage> for Attr<E, T, A> impl<E, T, A> ViewMarker for Attr<E, T, A> {}
impl<E, T, A> View<T, A, ViewCtx, DynMessage> for Attr<E, T, A>
where where
T: 'static, T: 'static,
A: 'static, A: 'static,

View File

@ -4,7 +4,7 @@
use std::marker::PhantomData; use std::marker::PhantomData;
use wasm_bindgen::{JsCast, UnwrapThrowExt}; use wasm_bindgen::{JsCast, UnwrapThrowExt};
use xilem_core::{MessageResult, Mut, View, ViewElement, ViewId}; use xilem_core::{MessageResult, Mut, View, ViewElement, ViewId, ViewMarker};
use crate::{vecmap::VecMap, DomNode, DynMessage, ElementProps, Pod, PodMut, ViewCtx}; use crate::{vecmap::VecMap, DomNode, DynMessage, ElementProps, Pod, PodMut, ViewCtx};
@ -289,6 +289,7 @@ impl<E, C, T, A> Class<E, C, T, A> {
} }
} }
impl<E, C, T, A> ViewMarker for Class<E, C, T, A> {}
impl<E, C, T, A> View<T, A, ViewCtx, DynMessage> for Class<E, C, T, A> impl<E, C, T, A> View<T, A, ViewCtx, DynMessage> for Class<E, C, T, A>
where where
T: 'static, T: 'static,

View File

@ -5,7 +5,7 @@ use std::{future::Future, marker::PhantomData};
use wasm_bindgen::{closure::Closure, JsCast, UnwrapThrowExt}; use wasm_bindgen::{closure::Closure, JsCast, UnwrapThrowExt};
use wasm_bindgen_futures::spawn_local; use wasm_bindgen_futures::spawn_local;
use xilem_core::{MessageResult, Mut, NoElement, View, ViewId, ViewPathTracker}; use xilem_core::{MessageResult, Mut, NoElement, View, ViewId, ViewMarker, ViewPathTracker};
use crate::{DynMessage, OptionalAction, ViewCtx}; use crate::{DynMessage, OptionalAction, ViewCtx};
@ -153,6 +153,10 @@ enum MemoizedAwaitMessage<Output: std::fmt::Debug> {
ScheduleUpdate, ScheduleUpdate,
} }
impl<State, Action, OA, InitFuture, Data, CB, F, FOut> ViewMarker
for MemoizedAwait<State, Action, OA, InitFuture, Data, CB, F, FOut>
{
}
impl<State, Action, InitFuture, F, FOut, Data, CB, OA> View<State, Action, ViewCtx, DynMessage> impl<State, Action, InitFuture, F, FOut, Data, CB, OA> View<State, Action, ViewCtx, DynMessage>
for MemoizedAwait<State, Action, OA, InitFuture, Data, CB, F, FOut> for MemoizedAwait<State, Action, OA, InitFuture, Data, CB, F, FOut>
where where

View File

@ -8,23 +8,17 @@ use std::{any::Any, rc::Rc};
use wasm_bindgen::{JsCast, UnwrapThrowExt}; use wasm_bindgen::{JsCast, UnwrapThrowExt};
use crate::{ use crate::{
core::{AppendVec, ElementSplice, MessageResult, Mut, View, ViewId, ViewSequence}, core::{AppendVec, ElementSplice, MessageResult, Mut, View, ViewId, ViewMarker},
document, document,
element_props::ElementProps, element_props::ElementProps,
vec_splice::VecSplice, vec_splice::VecSplice,
AnyPod, DomNode, DynMessage, Pod, ViewCtx, HTML_NS, AnyPod, DomFragment, DomNode, DynMessage, Pod, ViewCtx, HTML_NS,
}; };
mod sealed {
pub trait Sealed<State, Action, SeqMarker> {}
}
// sealed, because this should only cover `ViewSequences` with the blanket impl below // sealed, because this should only cover `ViewSequences` with the blanket impl below
/// This is basically a specialized dynamically dispatchable [`ViewSequence`], It's currently not able to change the underlying type unlike [`AnyDomView`](crate::AnyDomView), so it should not be used as `dyn DomViewSequence`. /// This is basically a specialized dynamically dispatchable [`ViewSequence`], It's currently not able to change the underlying type unlike [`AnyDomView`](crate::AnyDomView), so it should not be used as `dyn DomViewSequence`.
/// It's mostly a hack to avoid a completely static view tree, which unfortunately brings rustc (type-checking) down to its knees and results in long compile-times /// It's mostly a hack to avoid a completely static view tree, which unfortunately brings rustc (type-checking) down to its knees and results in long compile-times
pub(crate) trait DomViewSequence<State, Action, SeqMarker>: pub(crate) trait DomViewSequence<State, Action>: 'static {
sealed::Sealed<State, Action, SeqMarker> + 'static
{
/// Get an [`Any`] reference to `self`. /// Get an [`Any`] reference to `self`.
fn as_any(&self) -> &dyn Any; fn as_any(&self) -> &dyn Any;
@ -35,7 +29,7 @@ pub(crate) trait DomViewSequence<State, Action, SeqMarker>:
/// Update the associated widgets. /// Update the associated widgets.
fn dyn_seq_rebuild( fn dyn_seq_rebuild(
&self, &self,
prev: &dyn DomViewSequence<State, Action, SeqMarker>, prev: &dyn DomViewSequence<State, Action>,
seq_state: &mut Box<dyn Any>, seq_state: &mut Box<dyn Any>,
ctx: &mut ViewCtx, ctx: &mut ViewCtx,
elements: &mut DomChildrenSplice, elements: &mut DomChildrenSplice,
@ -62,21 +56,11 @@ pub(crate) trait DomViewSequence<State, Action, SeqMarker>:
) -> MessageResult<Action, DynMessage>; ) -> MessageResult<Action, DynMessage>;
} }
impl<State, Action, SeqMarker, S> sealed::Sealed<State, Action, SeqMarker> for S impl<State, Action, S> DomViewSequence<State, Action> for S
where where
State: 'static, State: 'static,
SeqMarker: 'static,
Action: 'static, Action: 'static,
S: ViewSequence<State, Action, ViewCtx, AnyPod, SeqMarker, DynMessage>, S: DomFragment<State, Action>,
{
}
impl<State, Action, SeqMarker, S> DomViewSequence<State, Action, SeqMarker> for S
where
State: 'static,
SeqMarker: 'static,
Action: 'static,
S: ViewSequence<State, Action, ViewCtx, AnyPod, SeqMarker, DynMessage>,
{ {
fn as_any(&self) -> &dyn Any { fn as_any(&self) -> &dyn Any {
self self
@ -88,7 +72,7 @@ where
fn dyn_seq_rebuild( fn dyn_seq_rebuild(
&self, &self,
prev: &dyn DomViewSequence<State, Action, SeqMarker>, prev: &dyn DomViewSequence<State, Action>,
seq_state: &mut Box<dyn Any>, seq_state: &mut Box<dyn Any>,
ctx: &mut ViewCtx, ctx: &mut ViewCtx,
elements: &mut DomChildrenSplice, elements: &mut DomChildrenSplice,
@ -233,8 +217,8 @@ impl ElementState {
// These (boilerplatey) functions are there to reduce the boilerplate created by the macro-expansion below. // These (boilerplatey) functions are there to reduce the boilerplate created by the macro-expansion below.
pub(crate) fn build_element<State, Action, Element, SeqMarker>( pub(crate) fn build_element<State, Action, Element>(
children: &dyn DomViewSequence<State, Action, SeqMarker>, children: &dyn DomViewSequence<State, Action>,
tag_name: &str, tag_name: &str,
ns: &str, ns: &str,
ctx: &mut ViewCtx, ctx: &mut ViewCtx,
@ -243,7 +227,6 @@ where
State: 'static, State: 'static,
Action: 'static, Action: 'static,
Element: 'static, Element: 'static,
SeqMarker: 'static,
Element: From<Pod<web_sys::Element, ElementProps>>, Element: From<Pod<web_sys::Element, ElementProps>>,
{ {
let mut elements = AppendVec::default(); let mut elements = AppendVec::default();
@ -254,9 +237,9 @@ where
) )
} }
pub(crate) fn rebuild_element<'el, State, Action, Element, SeqMarker>( pub(crate) fn rebuild_element<'el, State, Action, Element>(
children: &dyn DomViewSequence<State, Action, SeqMarker>, children: &dyn DomViewSequence<State, Action>,
prev_children: &dyn DomViewSequence<State, Action, SeqMarker>, prev_children: &dyn DomViewSequence<State, Action>,
element: Mut<'el, Pod<Element, ElementProps>>, element: Mut<'el, Pod<Element, ElementProps>>,
state: &mut ElementState, state: &mut ElementState,
ctx: &mut ViewCtx, ctx: &mut ViewCtx,
@ -265,7 +248,6 @@ where
State: 'static, State: 'static,
Action: 'static, Action: 'static,
Element: 'static, Element: 'static,
SeqMarker: 'static,
Element: DomNode<ElementProps>, Element: DomNode<ElementProps>,
{ {
let mut dom_children_splice = DomChildrenSplice::new( let mut dom_children_splice = DomChildrenSplice::new(
@ -285,8 +267,8 @@ where
element element
} }
pub(crate) fn teardown_element<State, Action, Element, SeqMarker>( pub(crate) fn teardown_element<State, Action, Element>(
children: &dyn DomViewSequence<State, Action, SeqMarker>, children: &dyn DomViewSequence<State, Action>,
element: Mut<'_, Pod<Element, ElementProps>>, element: Mut<'_, Pod<Element, ElementProps>>,
state: &mut ElementState, state: &mut ElementState,
ctx: &mut ViewCtx, ctx: &mut ViewCtx,
@ -294,7 +276,6 @@ pub(crate) fn teardown_element<State, Action, Element, SeqMarker>(
State: 'static, State: 'static,
Action: 'static, Action: 'static,
Element: 'static, Element: 'static,
SeqMarker: 'static,
Element: DomNode<ElementProps>, Element: DomNode<ElementProps>,
{ {
let mut dom_children_splice = DomChildrenSplice::new( let mut dom_children_splice = DomChildrenSplice::new(
@ -309,34 +290,31 @@ pub(crate) fn teardown_element<State, Action, Element, SeqMarker>(
} }
/// An element that can change its tag, it's useful for autonomous custom elements (i.e. web components) /// An element that can change its tag, it's useful for autonomous custom elements (i.e. web components)
pub struct CustomElement<State, Action, SeqMarker> { pub struct CustomElement<State, Action> {
name: Cow<'static, str>, name: Cow<'static, str>,
children: Box<dyn DomViewSequence<State, Action, SeqMarker>>, children: Box<dyn DomViewSequence<State, Action>>,
} }
/// An element that can change its tag, it's useful for autonomous custom elements (i.e. web components) /// An element that can change its tag, it's useful for autonomous custom elements (i.e. web components)
pub fn custom_element<State, Action, SeqMarker, Children>( pub fn custom_element<State, Action, Children>(
name: impl Into<Cow<'static, str>>, name: impl Into<Cow<'static, str>>,
children: Children, children: Children,
) -> CustomElement<State, Action, SeqMarker> ) -> CustomElement<State, Action>
where where
State: 'static, State: 'static,
Action: 'static, Action: 'static,
SeqMarker: 'static, Children: DomFragment<State, Action>,
Children: ViewSequence<State, Action, ViewCtx, AnyPod, SeqMarker, DynMessage>,
{ {
CustomElement { CustomElement {
name: name.into(), name: name.into(),
children: Box::new(children), children: Box::new(children),
} }
} }
impl<State, Action> ViewMarker for CustomElement<State, Action> {}
impl<State, Action, SeqMarker> View<State, Action, ViewCtx, DynMessage> impl<State, Action> View<State, Action, ViewCtx, DynMessage> for CustomElement<State, Action>
for CustomElement<State, Action, SeqMarker>
where where
State: 'static, State: 'static,
Action: 'static, Action: 'static,
SeqMarker: 'static,
{ {
type Element = Pod<web_sys::HtmlElement, ElementProps>; type Element = Pod<web_sys::HtmlElement, ElementProps>;
@ -403,32 +381,26 @@ macro_rules! define_element {
define_element!($ns, ($ty_name, $name, $dom_interface, stringify!($name))); define_element!($ns, ($ty_name, $name, $dom_interface, stringify!($name)));
}; };
($ns:expr, ($ty_name:ident, $name:ident, $dom_interface:ident, $tag_name:expr)) => { ($ns:expr, ($ty_name:ident, $name:ident, $dom_interface:ident, $tag_name:expr)) => {
pub struct $ty_name<State, Action, SeqMarker> { pub struct $ty_name<State, Action> {
children: Box<dyn DomViewSequence<State, Action, SeqMarker>>, children: Box<dyn DomViewSequence<State, Action>>,
} }
/// Builder function for a /// Builder function for a
#[doc = concat!("`", $tag_name, "`")] #[doc = concat!("`", $tag_name, "`")]
/// element view. /// element view.
pub fn $name< pub fn $name<State: 'static, Action: 'static, Children: DomFragment<State, Action>>(
State: 'static,
Action: 'static,
SeqMarker: 'static,
Children: ViewSequence<State, Action, ViewCtx, AnyPod, SeqMarker, DynMessage>,
>(
children: Children, children: Children,
) -> $ty_name<State, Action, SeqMarker> { ) -> $ty_name<State, Action> {
$ty_name { $ty_name {
children: Box::new(children), children: Box::new(children),
} }
} }
impl<State, Action, SeqMarker> View<State, Action, ViewCtx, DynMessage> impl<State, Action> ViewMarker for $ty_name<State, Action> {}
for $ty_name<State, Action, SeqMarker> impl<State, Action> View<State, Action, ViewCtx, DynMessage> for $ty_name<State, Action>
where where
State: 'static, State: 'static,
Action: 'static, Action: 'static,
SeqMarker: 'static,
{ {
type Element = Pod<web_sys::$dom_interface, ElementProps>; type Element = Pod<web_sys::$dom_interface, ElementProps>;
@ -485,8 +457,8 @@ macro_rules! define_elements {
($ns:ident, $($element_def:tt,)*) => { ($ns:ident, $($element_def:tt,)*) => {
use super::{build_element, rebuild_element, teardown_element, DomViewSequence, ElementState}; use super::{build_element, rebuild_element, teardown_element, DomViewSequence, ElementState};
use crate::{ use crate::{
core::{MessageResult, Mut, ViewId, ViewSequence}, core::{MessageResult, Mut, ViewId, ViewMarker},
AnyPod, DynMessage, ElementProps, Pod, View, ViewCtx, DomFragment, DynMessage, ElementProps, Pod, View, ViewCtx,
}; };
$(define_element!(crate::$ns, $element_def);)* $(define_element!(crate::$ns, $element_def);)*
}; };

View File

@ -4,7 +4,7 @@
use std::{borrow::Cow, marker::PhantomData}; use std::{borrow::Cow, marker::PhantomData};
use wasm_bindgen::{prelude::Closure, throw_str, JsCast, UnwrapThrowExt}; use wasm_bindgen::{prelude::Closure, throw_str, JsCast, UnwrapThrowExt};
use web_sys::AddEventListenerOptions; use web_sys::AddEventListenerOptions;
use xilem_core::{MessageResult, Mut, View, ViewId, ViewPathTracker}; use xilem_core::{MessageResult, Mut, View, ViewId, ViewMarker, ViewPathTracker};
use crate::{DynMessage, ElementAsRef, OptionalAction, ViewCtx}; use crate::{DynMessage, ElementAsRef, OptionalAction, ViewCtx};
@ -224,6 +224,7 @@ where
} }
} }
impl<V, State, Action, Event, Callback> ViewMarker for OnEvent<V, State, Action, Event, Callback> {}
impl<V, State, Action, Event, Callback, OA> View<State, Action, ViewCtx, DynMessage> impl<V, State, Action, Event, Callback, OA> View<State, Action, ViewCtx, DynMessage>
for OnEvent<V, State, Action, Event, Callback> for OnEvent<V, State, Action, Event, Callback>
where where
@ -327,6 +328,7 @@ macro_rules! event_definitions {
pub(crate) phantom_event_ty: PhantomData<fn() -> (State, Action)>, pub(crate) phantom_event_ty: PhantomData<fn() -> (State, Action)>,
} }
impl<V, State, Action, Callback> ViewMarker for $ty_name<V, State, Action, Callback> {}
impl<V, State, Action, Callback> $ty_name<V, State, Action, Callback> { impl<V, State, Action, Callback> $ty_name<V, State, Action, Callback> {
pub fn new(element: V, handler: Callback) -> Self { pub fn new(element: V, handler: Callback) -> Self {
Self { Self {

View File

@ -59,6 +59,7 @@ pub use optional_action::{Action, OptionalAction};
pub use pointer::{Pointer, PointerDetails, PointerMsg}; pub use pointer::{Pointer, PointerDetails, PointerMsg};
pub use style::{style, ElementWithStyle, IntoStyles, Style, Styles, WithStyle}; pub use style::{style, ElementWithStyle, IntoStyles, Style, Styles, WithStyle};
pub use xilem_core as core; pub use xilem_core as core;
use xilem_core::ViewSequence;
/// A trait used for type erasure of [`DomNode`]s /// A trait used for type erasure of [`DomNode`]s
/// It is e.g. used in [`AnyPod`] /// It is e.g. used in [`AnyPod`]
@ -169,6 +170,26 @@ where
type Props = P; type Props = P;
} }
/// An ordered sequence of views, or sometimes also called fragment, it's used for `0..N` [`DomView`]s.
/// See [`ViewSequence`] for more technical details.
///
/// # Examples
///
/// ```
/// fn huzzah(clicks: i32) -> impl xilem_web::DomFragment<i32> {
/// (clicks >= 5).then_some("Huzzah, clicked at least 5 times")
/// }
/// ```
pub trait DomFragment<State, Action = ()>:
ViewSequence<State, Action, ViewCtx, AnyPod, DynMessage>
{
}
impl<V, State, Action> DomFragment<State, Action> for V where
V: ViewSequence<State, Action, ViewCtx, AnyPod, DynMessage>
{
}
/// A container, which holds the actual DOM node, and associated props, such as attributes or classes. /// A container, which holds the actual DOM node, and associated props, such as attributes or classes.
/// These attributes are not directly set on the DOM node to avoid mutating or reading from the DOM tree unnecessarily, and to have more control over the whole update flow. /// These attributes are not directly set on the DOM node to avoid mutating or reading from the DOM tree unnecessarily, and to have more control over the whole update flow.
pub struct Pod<E, P> { pub struct Pod<E, P> {

View File

@ -8,7 +8,7 @@ use std::marker::PhantomData;
use wasm_bindgen::{prelude::Closure, throw_str, JsCast, UnwrapThrowExt}; use wasm_bindgen::{prelude::Closure, throw_str, JsCast, UnwrapThrowExt};
use web_sys::PointerEvent; use web_sys::PointerEvent;
use xilem_core::{MessageResult, Mut, View, ViewId, ViewPathTracker}; use xilem_core::{MessageResult, Mut, View, ViewId, ViewMarker, ViewPathTracker};
use crate::{interfaces::Element, DynMessage, ElementAsRef, ViewCtx}; use crate::{interfaces::Element, DynMessage, ElementAsRef, ViewCtx};
@ -69,6 +69,7 @@ pub fn pointer<T, A, F: Fn(&mut T, PointerMsg), V: Element<T, A>>(
} }
} }
impl<V, State, Action, Callback> ViewMarker for Pointer<V, State, Action, Callback> {}
impl<State, Action, Callback, V> View<State, Action, ViewCtx, DynMessage> impl<State, Action, Callback, V> View<State, Action, ViewCtx, DynMessage>
for Pointer<V, State, Action, Callback> for Pointer<V, State, Action, Callback>
where where

View File

@ -6,7 +6,7 @@ use std::{
marker::PhantomData, marker::PhantomData,
}; };
use wasm_bindgen::{JsCast, UnwrapThrowExt}; use wasm_bindgen::{JsCast, UnwrapThrowExt};
use xilem_core::{MessageResult, Mut, View, ViewElement, ViewId}; use xilem_core::{MessageResult, Mut, View, ViewElement, ViewId, ViewMarker};
use crate::{vecmap::VecMap, DomNode, DynMessage, ElementProps, Pod, PodMut, ViewCtx}; use crate::{vecmap::VecMap, DomNode, DynMessage, ElementProps, Pod, PodMut, ViewCtx};
@ -299,6 +299,7 @@ impl<E, T, A> Style<E, T, A> {
} }
} }
impl<E, T, A> ViewMarker for Style<E, T, A> {}
impl<T, A, E> View<T, A, ViewCtx, DynMessage> for Style<E, T, A> impl<T, A, E> View<T, A, ViewCtx, DynMessage> for Style<E, T, A>
where where
T: 'static, T: 'static,

View File

@ -5,7 +5,7 @@ use std::borrow::Cow;
use std::marker::PhantomData; use std::marker::PhantomData;
use peniko::Brush; use peniko::Brush;
use xilem_core::{MessageResult, Mut, View, ViewId}; use xilem_core::{MessageResult, Mut, View, ViewId, ViewMarker};
use crate::{ use crate::{
attribute::{ElementWithAttributes, WithAttributes}, attribute::{ElementWithAttributes, WithAttributes},
@ -61,6 +61,7 @@ fn brush_to_string(brush: &Brush) -> String {
} }
} }
impl<V, State, Action> ViewMarker for Fill<V, State, Action> {}
impl<State, Action, V> View<State, Action, ViewCtx, DynMessage> for Fill<V, State, Action> impl<State, Action, V> View<State, Action, ViewCtx, DynMessage> for Fill<V, State, Action>
where where
State: 'static, State: 'static,
@ -116,6 +117,7 @@ where
} }
} }
impl<V, State, Action> ViewMarker for Stroke<V, State, Action> {}
impl<State, Action, V> View<State, Action, ViewCtx, DynMessage> for Stroke<V, State, Action> impl<State, Action, V> View<State, Action, ViewCtx, DynMessage> for Stroke<V, State, Action>
where where
State: 'static, State: 'static,

View File

@ -5,7 +5,7 @@ use xilem_web::{
document_body, document_body,
elements::html as el, elements::html as el,
interfaces::{Element, HtmlButtonElement, HtmlDivElement}, interfaces::{Element, HtmlButtonElement, HtmlDivElement},
App, App, DomFragment,
}; };
#[derive(Default)] #[derive(Default)]
@ -50,9 +50,15 @@ fn btn(
el::button(label).on_click(click_fn) el::button(label).on_click(click_fn)
} }
/// And functions that return a sequence of views.
fn huzzah(state: &mut AppState) -> impl DomFragment<AppState> {
(state.clicks >= 5).then_some("Huzzah, clicked at least 5 times")
}
fn app_logic(state: &mut AppState) -> impl HtmlDivElement<AppState> { fn app_logic(state: &mut AppState) -> impl HtmlDivElement<AppState> {
el::div(( el::div((
el::span(format!("clicked {} times", state.clicks)).class(state.class), el::span(format!("clicked {} times", state.clicks)).class(state.class),
huzzah(state),
el::br(()), el::br(()),
btn("+1 click", |state, _| state.increment()), btn("+1 click", |state, _| state.increment()),
btn("-1 click", |state, _| state.decrement()), btn("-1 click", |state, _| state.decrement()),