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::error::EventLoopError;
use winit::window::Window;
use xilem::view::Flex;
use xilem::view::{Flex, FlexSequence};
use xilem::EventLoopBuilder;
use xilem::{
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.
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)
.direction(Axis::Horizontal)
.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.
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)
.direction(Axis::Horizontal)
.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.
// 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((
label(format!("elm count: {count}")),
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> {
if data.active {
flex((
button("Deactivate", |data: &mut AppData| {
data.active = false;
}),
button("Unlimited Power", |data: &mut AppData| {
data.count = -1_000_000;
}),
fork(
flex((
button("Deactivate", |data: &mut AppData| {
data.active = false;
}),
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")),
))
.direction(Axis::Horizontal)
)
.boxed()
} else {
button("Activate", |data: &mut AppData| data.active = true).boxed()

View File

@ -19,6 +19,7 @@ use winit::{
};
use xilem_core::{
AsyncCtx, MessageResult, RawProxy, SuperElement, View, ViewElement, ViewId, ViewPathTracker,
ViewSequence,
};
pub use masonry::{
@ -209,6 +210,30 @@ where
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>>;
pub struct ViewCtx {

View File

@ -4,7 +4,9 @@
use std::{future::Future, marker::PhantomData, sync::Arc};
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;
@ -70,6 +72,7 @@ pub struct AsyncRepeat<F, H, 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>
where
F: Fn(MessageProxy<M>) -> Fut + 'static,

View File

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

View File

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

View File

@ -9,36 +9,38 @@ use masonry::{
};
use xilem_core::{
AppendVec, DynMessage, ElementSplice, MessageResult, Mut, SuperElement, View, ViewElement,
ViewId, ViewPathTracker, ViewSequence,
ViewId, ViewMarker, ViewPathTracker, ViewSequence,
};
use crate::{AnyWidgetView, Pod, ViewCtx, WidgetView};
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 {
phantom: PhantomData,
sequence,
axis: Axis::Vertical,
cross_axis_alignment: CrossAxisAlignment::Center,
main_axis_alignment: MainAxisAlignment::Start,
fill_major_axis: false,
gap: None,
phantom: PhantomData,
}
}
pub struct Flex<Seq, Marker> {
pub struct Flex<Seq, State, Action = ()> {
sequence: Seq,
axis: Axis,
cross_axis_alignment: CrossAxisAlignment,
main_axis_alignment: MainAxisAlignment,
fill_major_axis: bool,
phantom: PhantomData<fn() -> Marker>,
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 {
self.axis = axis;
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
Seq: ViewSequence<State, Action, ViewCtx, FlexElement, Marker>,
State: 'static,
Action: 'static,
Seq: FlexSequence<State, Action>,
{
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
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`]
@ -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>
where
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
// If this should ever change, it's necessary to adjust the `AnyFlexChild` `View` impl
impl<State, Action> View<State, Action, ViewCtx> for FlexSpacer {
@ -593,6 +625,7 @@ pub struct AnyFlexChildState<State: 'static, Action: 'static> {
generation: u64,
}
impl<State, Action> ViewMarker for AnyFlexChild<State, Action> {}
impl<State, Action> View<State, Action, ViewCtx> for AnyFlexChild<State, Action>
where
State: 'static,

View File

@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use masonry::{text::TextBrush, widget, ArcStr};
use xilem_core::Mut;
use xilem_core::{Mut, ViewMarker};
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 {
type Element = Pod<widget::Label>;
type ViewState = ();

View File

@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use masonry::{text::TextBrush, widget, ArcStr};
use xilem_core::Mut;
use xilem_core::{Mut, ViewMarker};
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 {
type Element = Pod<widget::Prose>;
type ViewState = ();

View File

@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use masonry::widget;
use xilem_core::ViewMarker;
use crate::{
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>
where
V: WidgetView<State, Action>,

View File

@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use masonry::{text::TextBrush, widget};
use xilem_core::{Mut, View};
use xilem_core::{Mut, View, ViewMarker};
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> {
type Element = Pod<widget::Textbox>;
type ViewState = ();

View File

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

View File

@ -6,7 +6,8 @@
use core::any::Any;
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> {
@ -44,6 +45,7 @@ impl<W: Widget> ViewElement for WidgetPod<W> {
type Mut<'a> = WidgetMut<'a, W>;
}
impl ViewMarker for Button {}
impl<State, Action> View<State, Action, ViewCtx> for Button {
type Element = WidgetPod<ButtonWidget>;
type ViewState = ();

View File

@ -8,7 +8,8 @@ use core::any::Any;
use alloc::boxed::Box;
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
@ -171,6 +172,10 @@ pub struct AnyViewState {
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>
for dyn AnyView<State, Action, Context, Element, Message>
where
@ -220,6 +225,11 @@ where
}
// 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>
for dyn AnyView<State, Action, Context, Element, Message> + Send
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>
for dyn AnyView<State, Action, Context, Element, Message> + Send + Sync
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>
for dyn AnyView<State, Action, Context, Element, Message> + Sync
where

View File

@ -94,3 +94,16 @@ pub struct NoElement;
impl ViewElement for NoElement {
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};
mod view;
pub use view::{View, ViewId, ViewPathTracker};
pub use view::{View, ViewId, ViewMarker, ViewPathTracker};
mod views;
pub use views::{

View File

@ -9,8 +9,10 @@ use core::sync::atomic::Ordering;
use alloc::vec::Drain;
use alloc::vec::Vec;
use crate::element::NoElement;
use crate::{DynMessage, MessageResult, SuperElement, View, ViewElement, ViewId, ViewPathTracker};
// use crate::element::NoElement;
use crate::{
DynMessage, MessageResult, SuperElement, View, ViewElement, ViewId, ViewMarker, ViewPathTracker,
};
/// An append only `Vec`.
///
@ -74,11 +76,11 @@ impl<T> Default for AppendVec<T> {
/// Note that this will have persistent allocation with size proportional
/// to the *longest* `Vec` which is ever provided in the View tree, as this
/// uses a generational indexing scheme.
/// - An [`array`] of `ViewSequence` values.
/// - Tuples of `ViewSequences` with up to 15 elements.
/// 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>:
'static
pub trait ViewSequence<State, Action, Context, Element, Message = DynMessage>: 'static
where
Context: ViewPathTracker,
Element: ViewElement,
@ -143,15 +145,11 @@ pub trait ElementSplice<Element: ViewElement> {
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>
ViewSequence<State, Action, Context, Element, WasAView, Message> for V
ViewSequence<State, Action, Context, Element, Message> for V
where
Context: ViewPathTracker,
V: View<State, Action, Context, Message>,
V: View<State, Action, Context, Message> + ViewMarker,
Element: SuperElement<V::Element>,
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
/// occurred as stale.
impl<State, Action, Context, Element, Marker, Seq, Message>
ViewSequence<State, Action, Context, Element, Option<Marker>, Message> for Option<Seq>
impl<State, Action, Context, Element, Seq, Message>
ViewSequence<State, Action, Context, Element, Message> for Option<Seq>
where
Seq: ViewSequence<State, Action, Context, Element, Marker, Message>,
Seq: ViewSequence<State, Action, Context, Element, Message>,
Context: ViewPathTracker,
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>`
///
/// 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
/// that index has been unused in the meantime.
impl<State, Action, Context, Element, Marker, Seq, Message>
ViewSequence<State, Action, Context, Element, Vec<Marker>, Message> for Vec<Seq>
impl<State, Action, Context, Element, Seq, Message>
ViewSequence<State, Action, Context, Element, Message> for Vec<Seq>
where
Seq: ViewSequence<State, Action, Context, Element, Marker, Message>,
Seq: ViewSequence<State, Action, Context, Element, Message>,
Context: ViewPathTracker,
Element: ViewElement,
{
@ -582,10 +529,10 @@ where
}
}
impl<State, Action, Context, Element, Marker, Seq, Message, const N: usize>
ViewSequence<State, Action, Context, Element, Vec<Marker>, Message> for [Seq; N]
impl<State, Action, Context, Element, Seq, Message, const N: usize>
ViewSequence<State, Action, Context, Element, Message> for [Seq; N]
where
Seq: ViewSequence<State, Action, Context, Element, Marker, Message>,
Seq: ViewSequence<State, Action, Context, Element, Message>,
Context: ViewPathTracker,
Element: ViewElement,
{
@ -661,7 +608,7 @@ where
}
impl<State, Action, Context, Element, Message>
ViewSequence<State, Action, Context, Element, (), Message> for ()
ViewSequence<State, Action, Context, Element, Message> for ()
where
Context: ViewPathTracker,
Element: ViewElement,
@ -699,10 +646,10 @@ where
}
}
impl<State, Action, Context, Element, Marker, Seq, Message>
ViewSequence<State, Action, Context, Element, (Marker,), Message> for (Seq,)
impl<State, Action, Context, Element, Seq, Message>
ViewSequence<State, Action, Context, Element, Message> for (Seq,)
where
Seq: ViewSequence<State, Action, Context, Element, Marker, Message>,
Seq: ViewSequence<State, Action, Context, Element, Message>,
Context: ViewPathTracker,
Element: ViewElement,
{
@ -753,12 +700,9 @@ macro_rules! impl_view_tuple {
Action,
Context: ViewPathTracker,
Element: ViewElement,
$(
$marker,
$seq: ViewSequence<State, Action, Context, Element, $marker, Message>,
)+
$($seq: ViewSequence<State, Action, Context, Element, Message>,)+
Message,
> ViewSequence<State, Action, Context, Element, ($($marker,)+), Message> for ($($seq,)+)
> ViewSequence<State, Action, Context, Element, Message> for ($($seq,)+)
{
type SeqState = ($($seq::SeqState,)+);

View File

@ -9,6 +9,22 @@ use alloc::{boxed::Box, sync::Arc};
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
/// 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,
/// 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
///
/// In order to support the default open-ended [`DynMessage`] type as `Message`, this trait requires an
/// 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.
type Element: ViewElement;
/// 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>
where
Context: ViewPathTracker,
@ -185,6 +212,7 @@ pub struct ArcState<ViewState> {
dirty: bool,
}
impl<V: ?Sized> ViewMarker for Arc<V> {}
/// 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>
where

View File

@ -3,7 +3,7 @@
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.
pub struct Adapt<
@ -18,9 +18,7 @@ pub struct Adapt<
&mut ParentState,
AdaptThunk<ChildState, ChildAction, Context, ChildView, Message>,
) -> MessageResult<ParentAction>,
> where
Context: ViewPathTracker,
{
> {
proxy_fn: ProxyFn,
child: ChildView,
#[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>
View<ParentState, ParentAction, Context, Message>
for Adapt<ParentState, ParentAction, ChildState, ChildAction, Context, V, Message, F>

View File

@ -1,40 +1,37 @@
// Copyright 2024 the Xilem Authors
// SPDX-License-Identifier: Apache-2.0
use core::marker::PhantomData;
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.
///
/// `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,
alongside_view: Alongside,
) -> Fork<Active, Alongside, Marker> {
) -> Fork<Active, Alongside> {
Fork {
active_view,
alongside_view,
marker: PhantomData,
}
}
/// The view for [`fork`].
pub struct Fork<Active, Alongside, Marker> {
pub struct Fork<Active, Alongside> {
active_view: Active,
alongside_view: Alongside,
marker: PhantomData<Marker>,
}
impl<State, Action, Context, Active, Alongside, Marker, Message>
View<State, Action, Context, Message> for Fork<Active, Alongside, Marker>
impl<Active, Alongside> ViewMarker for Fork<Active, Alongside> {}
impl<State, Action, Context, Active, Alongside, Message> View<State, Action, Context, Message>
for Fork<Active, Alongside>
where
Active: View<State, Action, Context, Message>,
Alongside: ViewSequence<State, Action, Context, NoElement, Marker, Message>,
Alongside: ViewSequence<State, Action, Context, NoElement, Message>,
Context: ViewPathTracker,
Marker: 'static,
{
type Element = Active::Element;
@ -111,12 +108,8 @@ where
/// 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`
/// which uses a `NoElement` `ElementSplice`. But we don't think that sequence could be meaningful,
/// so we still panic in that case.
/// which uses a `NoElement` `ElementSplice`. But we don't think that sequence could be meaningful.
struct NoElements;
impl ElementSplice<NoElement> for NoElements {
@ -127,21 +120,15 @@ impl ElementSplice<NoElement> for NoElements {
ret
}
fn insert(&mut self, _: NoElement) {
unreachable!()
fn insert(&mut self, _: NoElement) {}
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 {
unreachable!()
}
fn skip(&mut self, _: usize) {}
fn skip(&mut self, n: usize) {
if n > 0 {
unreachable!()
}
}
fn delete<R>(&mut self, _: impl FnOnce(<NoElement as crate::ViewElement>::Mut<'_>) -> R) -> R {
unreachable!()
fn delete<R>(&mut self, f: impl FnOnce(<NoElement as crate::ViewElement>::Mut<'_>) -> R) -> R {
f(())
}
}

View File

@ -3,7 +3,7 @@
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.
///
@ -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>
View<State, ParentAction, Context, Message>
for MapAction<State, ParentAction, ChildAction, V, F>

View File

@ -3,7 +3,7 @@
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,_,_>`].
/// 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>
View<ParentState, Action, Context, Message> for MapState<ParentState, ChildState, V, F>
where

View File

@ -4,7 +4,7 @@
use core::marker::PhantomData;
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.
///
@ -64,6 +64,7 @@ pub struct MemoizeState<V, VState> {
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>
for Memoize<Data, ViewFn, State, Action>
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>
for Frozen<InitView, State, Action>
where

View File

@ -3,7 +3,7 @@
//! 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;
/// 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.
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>
@ -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.
#[doc(hidden)]
mod hidden {
use crate::View;
use crate::{View, ViewMarker};
use super::PhantomElementCtx;
#[allow(unreachable_pub)]
pub enum Never {}
impl ViewMarker for Never {}
impl<State, Action, Context: PhantomElementCtx, Message> View<State, Action, Context, Message>
for Never
{

View File

@ -1,7 +1,9 @@
// Copyright 2024 the Xilem Authors
// 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.
/// 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 {
($ty: ty) => {
impl ViewMarker for $ty {}
impl<State, Action, Context, Message> View<State, Action, Context, Message> for $ty
where
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
mod kurbo {
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::Arc);
impl_orphan_view_for!(kurbo::BezPath);

View File

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

View File

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

View File

@ -3,7 +3,7 @@
use std::marker::PhantomData;
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, 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
T: 'static,
A: 'static,

View File

@ -4,7 +4,7 @@
use std::marker::PhantomData;
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};
@ -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>
where
T: 'static,

View File

@ -5,7 +5,7 @@ use std::{future::Future, marker::PhantomData};
use wasm_bindgen::{closure::Closure, JsCast, UnwrapThrowExt};
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};
@ -153,6 +153,10 @@ enum MemoizedAwaitMessage<Output: std::fmt::Debug> {
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>
for MemoizedAwait<State, Action, OA, InitFuture, Data, CB, F, FOut>
where

View File

@ -8,23 +8,17 @@ use std::{any::Any, rc::Rc};
use wasm_bindgen::{JsCast, UnwrapThrowExt};
use crate::{
core::{AppendVec, ElementSplice, MessageResult, Mut, View, ViewId, ViewSequence},
core::{AppendVec, ElementSplice, MessageResult, Mut, View, ViewId, ViewMarker},
document,
element_props::ElementProps,
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
/// 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
pub(crate) trait DomViewSequence<State, Action, SeqMarker>:
sealed::Sealed<State, Action, SeqMarker> + 'static
{
pub(crate) trait DomViewSequence<State, Action>: 'static {
/// Get an [`Any`] reference to `self`.
fn as_any(&self) -> &dyn Any;
@ -35,7 +29,7 @@ pub(crate) trait DomViewSequence<State, Action, SeqMarker>:
/// Update the associated widgets.
fn dyn_seq_rebuild(
&self,
prev: &dyn DomViewSequence<State, Action, SeqMarker>,
prev: &dyn DomViewSequence<State, Action>,
seq_state: &mut Box<dyn Any>,
ctx: &mut ViewCtx,
elements: &mut DomChildrenSplice,
@ -62,21 +56,11 @@ pub(crate) trait DomViewSequence<State, Action, SeqMarker>:
) -> 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
State: 'static,
SeqMarker: 'static,
Action: 'static,
S: ViewSequence<State, Action, ViewCtx, AnyPod, SeqMarker, DynMessage>,
{
}
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>,
S: DomFragment<State, Action>,
{
fn as_any(&self) -> &dyn Any {
self
@ -88,7 +72,7 @@ where
fn dyn_seq_rebuild(
&self,
prev: &dyn DomViewSequence<State, Action, SeqMarker>,
prev: &dyn DomViewSequence<State, Action>,
seq_state: &mut Box<dyn Any>,
ctx: &mut ViewCtx,
elements: &mut DomChildrenSplice,
@ -233,8 +217,8 @@ impl ElementState {
// These (boilerplatey) functions are there to reduce the boilerplate created by the macro-expansion below.
pub(crate) fn build_element<State, Action, Element, SeqMarker>(
children: &dyn DomViewSequence<State, Action, SeqMarker>,
pub(crate) fn build_element<State, Action, Element>(
children: &dyn DomViewSequence<State, Action>,
tag_name: &str,
ns: &str,
ctx: &mut ViewCtx,
@ -243,7 +227,6 @@ where
State: 'static,
Action: 'static,
Element: 'static,
SeqMarker: 'static,
Element: From<Pod<web_sys::Element, ElementProps>>,
{
let mut elements = AppendVec::default();
@ -254,9 +237,9 @@ where
)
}
pub(crate) fn rebuild_element<'el, State, Action, Element, SeqMarker>(
children: &dyn DomViewSequence<State, Action, SeqMarker>,
prev_children: &dyn DomViewSequence<State, Action, SeqMarker>,
pub(crate) fn rebuild_element<'el, State, Action, Element>(
children: &dyn DomViewSequence<State, Action>,
prev_children: &dyn DomViewSequence<State, Action>,
element: Mut<'el, Pod<Element, ElementProps>>,
state: &mut ElementState,
ctx: &mut ViewCtx,
@ -265,7 +248,6 @@ where
State: 'static,
Action: 'static,
Element: 'static,
SeqMarker: 'static,
Element: DomNode<ElementProps>,
{
let mut dom_children_splice = DomChildrenSplice::new(
@ -285,8 +267,8 @@ where
element
}
pub(crate) fn teardown_element<State, Action, Element, SeqMarker>(
children: &dyn DomViewSequence<State, Action, SeqMarker>,
pub(crate) fn teardown_element<State, Action, Element>(
children: &dyn DomViewSequence<State, Action>,
element: Mut<'_, Pod<Element, ElementProps>>,
state: &mut ElementState,
ctx: &mut ViewCtx,
@ -294,7 +276,6 @@ pub(crate) fn teardown_element<State, Action, Element, SeqMarker>(
State: 'static,
Action: 'static,
Element: 'static,
SeqMarker: 'static,
Element: DomNode<ElementProps>,
{
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)
pub struct CustomElement<State, Action, SeqMarker> {
pub struct CustomElement<State, Action> {
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)
pub fn custom_element<State, Action, SeqMarker, Children>(
pub fn custom_element<State, Action, Children>(
name: impl Into<Cow<'static, str>>,
children: Children,
) -> CustomElement<State, Action, SeqMarker>
) -> CustomElement<State, Action>
where
State: 'static,
Action: 'static,
SeqMarker: 'static,
Children: ViewSequence<State, Action, ViewCtx, AnyPod, SeqMarker, DynMessage>,
Children: DomFragment<State, Action>,
{
CustomElement {
name: name.into(),
children: Box::new(children),
}
}
impl<State, Action, SeqMarker> View<State, Action, ViewCtx, DynMessage>
for CustomElement<State, Action, SeqMarker>
impl<State, Action> ViewMarker for CustomElement<State, Action> {}
impl<State, Action> View<State, Action, ViewCtx, DynMessage> for CustomElement<State, Action>
where
State: 'static,
Action: 'static,
SeqMarker: 'static,
{
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)));
};
($ns:expr, ($ty_name:ident, $name:ident, $dom_interface:ident, $tag_name:expr)) => {
pub struct $ty_name<State, Action, SeqMarker> {
children: Box<dyn DomViewSequence<State, Action, SeqMarker>>,
pub struct $ty_name<State, Action> {
children: Box<dyn DomViewSequence<State, Action>>,
}
/// Builder function for a
#[doc = concat!("`", $tag_name, "`")]
/// element view.
pub fn $name<
State: 'static,
Action: 'static,
SeqMarker: 'static,
Children: ViewSequence<State, Action, ViewCtx, AnyPod, SeqMarker, DynMessage>,
>(
pub fn $name<State: 'static, Action: 'static, Children: DomFragment<State, Action>>(
children: Children,
) -> $ty_name<State, Action, SeqMarker> {
) -> $ty_name<State, Action> {
$ty_name {
children: Box::new(children),
}
}
impl<State, Action, SeqMarker> View<State, Action, ViewCtx, DynMessage>
for $ty_name<State, Action, SeqMarker>
impl<State, Action> ViewMarker for $ty_name<State, Action> {}
impl<State, Action> View<State, Action, ViewCtx, DynMessage> for $ty_name<State, Action>
where
State: 'static,
Action: 'static,
SeqMarker: 'static,
{
type Element = Pod<web_sys::$dom_interface, ElementProps>;
@ -485,8 +457,8 @@ macro_rules! define_elements {
($ns:ident, $($element_def:tt,)*) => {
use super::{build_element, rebuild_element, teardown_element, DomViewSequence, ElementState};
use crate::{
core::{MessageResult, Mut, ViewId, ViewSequence},
AnyPod, DynMessage, ElementProps, Pod, View, ViewCtx,
core::{MessageResult, Mut, ViewId, ViewMarker},
DomFragment, DynMessage, ElementProps, Pod, View, ViewCtx,
};
$(define_element!(crate::$ns, $element_def);)*
};

View File

@ -4,7 +4,7 @@
use std::{borrow::Cow, marker::PhantomData};
use wasm_bindgen::{prelude::Closure, throw_str, JsCast, UnwrapThrowExt};
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};
@ -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>
for OnEvent<V, State, Action, Event, Callback>
where
@ -327,6 +328,7 @@ macro_rules! event_definitions {
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> {
pub fn new(element: V, handler: Callback) -> Self {
Self {

View File

@ -59,6 +59,7 @@ pub use optional_action::{Action, OptionalAction};
pub use pointer::{Pointer, PointerDetails, PointerMsg};
pub use style::{style, ElementWithStyle, IntoStyles, Style, Styles, WithStyle};
pub use xilem_core as core;
use xilem_core::ViewSequence;
/// A trait used for type erasure of [`DomNode`]s
/// It is e.g. used in [`AnyPod`]
@ -169,6 +170,26 @@ where
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.
/// 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> {

View File

@ -8,7 +8,7 @@ use std::marker::PhantomData;
use wasm_bindgen::{prelude::Closure, throw_str, JsCast, UnwrapThrowExt};
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};
@ -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>
for Pointer<V, State, Action, Callback>
where

View File

@ -6,7 +6,7 @@ use std::{
marker::PhantomData,
};
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};
@ -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>
where
T: 'static,

View File

@ -5,7 +5,7 @@ use std::borrow::Cow;
use std::marker::PhantomData;
use peniko::Brush;
use xilem_core::{MessageResult, Mut, View, ViewId};
use xilem_core::{MessageResult, Mut, View, ViewId, ViewMarker};
use crate::{
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>
where
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>
where
State: 'static,

View File

@ -5,7 +5,7 @@ use xilem_web::{
document_body,
elements::html as el,
interfaces::{Element, HtmlButtonElement, HtmlDivElement},
App,
App, DomFragment,
};
#[derive(Default)]
@ -50,9 +50,15 @@ fn btn(
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> {
el::div((
el::span(format!("clicked {} times", state.clicks)).class(state.class),
huzzah(state),
el::br(()),
btn("+1 click", |state, _| state.increment()),
btn("-1 click", |state, _| state.decrement()),