diff --git a/xilem/examples/calc.rs b/xilem/examples/calc.rs index 4b3d8c7c..fcf68508 100644 --- a/xilem/examples/calc.rs +++ b/xilem/examples/calc.rs @@ -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 { } /// Creates a horizontal centered flex row designed for the display portion of the calculator. -pub fn centered_flex_row(sequence: Seq) -> Flex { +pub fn centered_flex_row>(sequence: Seq) -> Flex { flex(sequence) .direction(Axis::Horizontal) .cross_axis_alignment(CrossAxisAlignment::Center) @@ -263,7 +263,7 @@ pub fn centered_flex_row(sequence: Seq) -> Flex { } /// Creates a horizontal filled flex row designed to be used in a grid. -pub fn flex_row(sequence: Seq) -> Flex { +pub fn flex_row>(sequence: Seq) -> Flex { flex(sequence) .direction(Axis::Horizontal) .cross_axis_alignment(CrossAxisAlignment::Fill) diff --git a/xilem/examples/elm.rs b/xilem/examples/elm.rs index 19d017ec..d3898556 100644 --- a/xilem/examples/elm.rs +++ b/xilem/examples/elm.rs @@ -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(count: i32) -> impl WidgetView { +fn elm_counter(count: i32) -> impl WidgetView { flex(( label(format!("elm count: {count}")), button("+", |_| CountMessage::Increment), diff --git a/xilem/examples/mason.rs b/xilem/examples/mason.rs index 497c7895..efce18e7 100644 --- a/xilem/examples/mason.rs +++ b/xilem/examples/mason.rs @@ -109,16 +109,18 @@ fn app_logic(data: &mut AppData) -> impl WidgetView { fn toggleable(data: &mut AppData) -> impl WidgetView { 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() diff --git a/xilem/src/lib.rs b/xilem/src/lib.rs index 1ac4b231..591b51ed 100644 --- a/xilem/src/lib.rs +++ b/xilem/src/lib.rs @@ -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( +/// texts: impl Iterator, +/// ) -> impl WidgetViewSequence { +/// texts.map(prose).collect::>() +/// } +/// ``` +pub trait WidgetViewSequence: + ViewSequence> +{ +} + +impl WidgetViewSequence for Seq where + Seq: ViewSequence> +{ +} + type WidgetMap = HashMap>; pub struct ViewCtx { diff --git a/xilem/src/view/async_repeat.rs b/xilem/src/view/async_repeat.rs index 0098d4b8..77e82042 100644 --- a/xilem/src/view/async_repeat.rs +++ b/xilem/src/view/async_repeat.rs @@ -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 { message: PhantomData M>, } +impl ViewMarker for AsyncRepeat {} impl View for AsyncRepeat where F: Fn(MessageProxy) -> Fut + 'static, diff --git a/xilem/src/view/button.rs b/xilem/src/view/button.rs index f7371c42..20d43bd4 100644 --- a/xilem/src/view/button.rs +++ b/xilem/src/view/button.rs @@ -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 { callback: F, } +impl ViewMarker for Button {} impl View for Button where F: Fn(&mut State, PointerButton) -> MessageResult + Send + Sync + 'static, diff --git a/xilem/src/view/checkbox.rs b/xilem/src/view/checkbox.rs index 34d6ef55..755bd178 100644 --- a/xilem/src/view/checkbox.rs +++ b/xilem/src/view/checkbox.rs @@ -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 { callback: F, } +impl ViewMarker for Checkbox {} impl View for Checkbox where F: Fn(&mut State, bool) -> Action + Send + Sync + 'static, diff --git a/xilem/src/view/flex.rs b/xilem/src/view/flex.rs index 0502db19..694c7097 100644 --- a/xilem/src/view/flex.rs +++ b/xilem/src/view/flex.rs @@ -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(sequence: Seq) -> Flex { +pub fn flex>( + sequence: Seq, +) -> Flex { 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 { +pub struct Flex { sequence: Seq, axis: Axis, cross_axis_alignment: CrossAxisAlignment, main_axis_alignment: MainAxisAlignment, fill_major_axis: bool, - phantom: PhantomData Marker>, gap: Option, + phantom: PhantomData (State, Action)>, } -impl Flex { +impl Flex { pub fn direction(mut self, axis: Axis) -> Self { self.axis = axis; self @@ -85,9 +87,12 @@ impl Flex { } } -impl View for Flex +impl ViewMarker for Flex {} +impl View for Flex where - Seq: ViewSequence, + State: 'static, + Action: 'static, + Seq: FlexSequence, { type Element = Pod; @@ -303,6 +308,31 @@ impl ElementSplice 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( +/// labels: impl Iterator, +/// flex: f64, +/// ) -> impl FlexSequence { +/// labels.map(|l| label(l).flex(flex)).collect::>() +/// } +/// ``` +pub trait FlexSequence: + ViewSequence +{ +} + +impl FlexSequence for Seq where + Seq: ViewSequence +{ +} + /// 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: WidgetView { /// Applies [`impl Into`](`FlexParams`) to this view, can be used as child of a [`Flex`] [`View`] @@ -407,6 +437,7 @@ where } } +impl ViewMarker for FlexItem {} impl View for FlexItem where State: 'static, @@ -485,6 +516,7 @@ impl From for AnyFlexChild { } } +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 View for FlexSpacer { @@ -593,6 +625,7 @@ pub struct AnyFlexChildState { generation: u64, } +impl ViewMarker for AnyFlexChild {} impl View for AnyFlexChild where State: 'static, diff --git a/xilem/src/view/label.rs b/xilem/src/view/label.rs index 6d929d24..a859c80c 100644 --- a/xilem/src/view/label.rs +++ b/xilem/src/view/label.rs @@ -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 View for Label { type Element = Pod; type ViewState = (); diff --git a/xilem/src/view/prose.rs b/xilem/src/view/prose.rs index 6cf831c7..0bd66615 100644 --- a/xilem/src/view/prose.rs +++ b/xilem/src/view/prose.rs @@ -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 View for Prose { type Element = Pod; type ViewState = (); diff --git a/xilem/src/view/sized_box.rs b/xilem/src/view/sized_box.rs index fafa6f1d..254c7d45 100644 --- a/xilem/src/view/sized_box.rs +++ b/xilem/src/view/sized_box.rs @@ -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 SizedBox { } } +impl ViewMarker for SizedBox {} impl View for SizedBox where V: WidgetView, diff --git a/xilem/src/view/textbox.rs b/xilem/src/view/textbox.rs index 32153108..b029a60c 100644 --- a/xilem/src/view/textbox.rs +++ b/xilem/src/view/textbox.rs @@ -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 Textbox { } } +impl ViewMarker for Textbox {} impl View for Textbox { type Element = Pod; type ViewState = (); diff --git a/xilem_core/examples/filesystem.rs b/xilem_core/examples/filesystem.rs index d48e28a2..67408c42 100644 --- a/xilem_core/examples/filesystem.rs +++ b/xilem_core/examples/filesystem.rs @@ -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 View for File { type Element = FsPath; type ViewState = (); diff --git a/xilem_core/examples/user_interface.rs b/xilem_core/examples/user_interface.rs index b7f151d5..41eb6b36 100644 --- a/xilem_core/examples/user_interface.rs +++ b/xilem_core/examples/user_interface.rs @@ -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 { @@ -44,6 +45,7 @@ impl ViewElement for WidgetPod { type Mut<'a> = WidgetMut<'a, W>; } +impl ViewMarker for Button {} impl View for Button { type Element = WidgetPod; type ViewState = (); diff --git a/xilem_core/src/any_view.rs b/xilem_core/src/any_view.rs index 67303f98..97796240 100644 --- a/xilem_core/src/any_view.rs +++ b/xilem_core/src/any_view.rs @@ -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 ViewMarker + for dyn AnyView +{ +} impl View for dyn AnyView where @@ -220,6 +225,11 @@ where } // TODO: IWBN if we could avoid this + +impl ViewMarker + for dyn AnyView + Send +{ +} impl View for dyn AnyView + Send where @@ -268,6 +278,10 @@ where } } +impl ViewMarker + for dyn AnyView + Send + Sync +{ +} impl View for dyn AnyView + Send + Sync where @@ -316,6 +330,10 @@ where } } +impl ViewMarker + for dyn AnyView + Sync +{ +} impl View for dyn AnyView + Sync where diff --git a/xilem_core/src/element.rs b/xilem_core/src/element.rs index 142ef3dd..22d4da79 100644 --- a/xilem_core/src/element.rs +++ b/xilem_core/src/element.rs @@ -94,3 +94,16 @@ pub struct NoElement; impl ViewElement for NoElement { type Mut<'a> = (); } + +impl SuperElement for NoElement { + fn upcast(child: NoElement) -> Self { + child + } + + fn with_downcast_val( + this: Mut<'_, Self>, + f: impl FnOnce(Mut<'_, NoElement>) -> R, + ) -> (Self::Mut<'_>, R) { + ((), f(this)) + } +} diff --git a/xilem_core/src/lib.rs b/xilem_core/src/lib.rs index ed069454..923d814b 100644 --- a/xilem_core/src/lib.rs +++ b/xilem_core/src/lib.rs @@ -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::{ diff --git a/xilem_core/src/sequence.rs b/xilem_core/src/sequence.rs index ebabb0b3..b1432985 100644 --- a/xilem_core/src/sequence.rs +++ b/xilem_core/src/sequence.rs @@ -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 Default for AppendVec { /// 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: - 'static +pub trait ViewSequence: 'static where Context: ViewPathTracker, Element: ViewElement, @@ -143,15 +145,11 @@ pub trait ElementSplice { fn delete(&mut self, f: impl FnOnce(Element::Mut<'_>) -> R) -> R; } -/// Marker type to workaround trait ambiguity. -#[doc(hidden)] -pub struct WasAView; - impl - ViewSequence for V + ViewSequence for V where Context: ViewPathTracker, - V: View, + V: View + ViewMarker, Element: SuperElement, V::Element: ViewElement, { @@ -218,10 +216,10 @@ pub struct OptionSeqState { /// /// Will mark messages which were sent to a `Some` value if a `None` has since /// occurred as stale. -impl - ViewSequence, Message> for Option +impl + ViewSequence for Option where - Seq: ViewSequence, + Seq: ViewSequence, 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 - ViewSequence for NoElementView -where - NoElementView: View, - Element: ViewElement, - Context: ViewPathTracker, -{ - #[doc(hidden)] - type SeqState = NoElementView::ViewState; - - #[doc(hidden)] - fn seq_build(&self, ctx: &mut Context, _: &mut AppendVec) -> 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, - ) { - self.rebuild(prev, seq_state, ctx, ()); - } - - #[doc(hidden)] - fn seq_teardown( - &self, - seq_state: &mut Self::SeqState, - ctx: &mut Context, - _: &mut impl ElementSplice, - ) { - 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 { - self.message(seq_state, id_path, message, app_state) - } -} - /// The state used to implement `ViewSequence` for `Vec` /// /// 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 - ViewSequence, Message> for Vec +impl + ViewSequence for Vec where - Seq: ViewSequence, + Seq: ViewSequence, Context: ViewPathTracker, Element: ViewElement, { @@ -582,10 +529,10 @@ where } } -impl - ViewSequence, Message> for [Seq; N] +impl + ViewSequence for [Seq; N] where - Seq: ViewSequence, + Seq: ViewSequence, Context: ViewPathTracker, Element: ViewElement, { @@ -661,7 +608,7 @@ where } impl - ViewSequence for () + ViewSequence for () where Context: ViewPathTracker, Element: ViewElement, @@ -699,10 +646,10 @@ where } } -impl - ViewSequence for (Seq,) +impl + ViewSequence for (Seq,) where - Seq: ViewSequence, + Seq: ViewSequence, Context: ViewPathTracker, Element: ViewElement, { @@ -753,12 +700,9 @@ macro_rules! impl_view_tuple { Action, Context: ViewPathTracker, Element: ViewElement, - $( - $marker, - $seq: ViewSequence, - )+ + $($seq: ViewSequence,)+ Message, - > ViewSequence for ($($seq,)+) + > ViewSequence for ($($seq,)+) { type SeqState = ($($seq::SeqState,)+); diff --git a/xilem_core/src/view.rs b/xilem_core/src/view.rs index dd822dfd..8118190d 100644 --- a/xilem_core/src/view.rs +++ b/xilem_core/src/view.rs @@ -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: 'static { +pub trait View: + 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 ViewMarker for Box {} impl View for Box where Context: ViewPathTracker, @@ -185,6 +212,7 @@ pub struct ArcState { dirty: bool, } +impl ViewMarker for Arc {} /// An implementation of [`View`] which only runs rebuild if the states are different impl View for Arc where diff --git a/xilem_core/src/views/adapt.rs b/xilem_core/src/views/adapt.rs index b7a422a0..e88ca51c 100644 --- a/xilem_core/src/views/adapt.rs +++ b/xilem_core/src/views/adapt.rs @@ -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, ) -> MessageResult, -> where - Context: ViewPathTracker, -{ +> { proxy_fn: ProxyFn, child: ChildView, #[allow(clippy::type_complexity)] @@ -147,6 +145,10 @@ where } } +impl ViewMarker + for Adapt +{ +} impl View for Adapt diff --git a/xilem_core/src/views/fork.rs b/xilem_core/src/views/fork.rs index 873ef90b..5b713664 100644 --- a/xilem_core/src/views/fork.rs +++ b/xilem_core/src/views/fork.rs @@ -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( +pub fn fork( active_view: Active, alongside_view: Alongside, -) -> Fork { +) -> Fork { Fork { active_view, alongside_view, - marker: PhantomData, } } /// The view for [`fork`]. -pub struct Fork { +pub struct Fork { active_view: Active, alongside_view: Alongside, - marker: PhantomData, } -impl - View for Fork +impl ViewMarker for Fork {} +impl View + for Fork where Active: View, - Alongside: ViewSequence, + Alongside: ViewSequence, 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 for NoElements { @@ -127,21 +120,15 @@ impl ElementSplice for NoElements { ret } - fn insert(&mut self, _: NoElement) { - unreachable!() + fn insert(&mut self, _: NoElement) {} + + fn mutate(&mut self, f: impl FnOnce(::Mut<'_>) -> R) -> R { + f(()) } - fn mutate(&mut self, _: impl FnOnce(::Mut<'_>) -> R) -> R { - unreachable!() - } + fn skip(&mut self, _: usize) {} - fn skip(&mut self, n: usize) { - if n > 0 { - unreachable!() - } - } - - fn delete(&mut self, _: impl FnOnce(::Mut<'_>) -> R) -> R { - unreachable!() + fn delete(&mut self, f: impl FnOnce(::Mut<'_>) -> R) -> R { + f(()) } } diff --git a/xilem_core/src/views/map_action.rs b/xilem_core/src/views/map_action.rs index 9fa580c0..af1f5480 100644 --- a/xilem_core/src/views/map_action.rs +++ b/xilem_core/src/views/map_action.rs @@ -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`] to [`View`] while providing mutable access to `State` in the map function. /// @@ -68,6 +68,10 @@ where } } +impl ViewMarker + for MapAction +{ +} impl View for MapAction diff --git a/xilem_core/src/views/map_state.rs b/xilem_core/src/views/map_state.rs index 45050074..e178ddda 100644 --- a/xilem_core/src/views/map_state.rs +++ b/xilem_core/src/views/map_state.rs @@ -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`] to [`View`]. /// This allows modularization of views based on their state. @@ -56,6 +56,7 @@ where } } +impl ViewMarker for MapState {} impl View for MapState where diff --git a/xilem_core/src/views/memoize.rs b/xilem_core/src/views/memoize.rs index e628f90a..694f366c 100644 --- a/xilem_core/src/views/memoize.rs +++ b/xilem_core/src/views/memoize.rs @@ -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 { dirty: bool, } +impl ViewMarker for Memoize {} impl View for Memoize where @@ -172,6 +173,7 @@ where } } +impl ViewMarker for Frozen {} impl View for Frozen where diff --git a/xilem_core/src/views/one_of.rs b/xilem_core/src/views/one_of.rs index de7b7161..0b38b53c 100644 --- a/xilem_core/src/views/one_of.rs +++ b/xilem_core/src/views/one_of.rs @@ -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 ViewMarker for OneOf {} /// The `OneOf` types and `Either` are [`View`]s if all of their possible types are themselves `View`s. impl View for OneOf @@ -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 View for Never { diff --git a/xilem_core/src/views/orphan.rs b/xilem_core/src/views/orphan.rs index d9ebab45..005d6aff 100644 --- a/xilem_core/src/views/orphan.rs +++ b/xilem_core/src/views/orphan.rs @@ -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: ViewPathTracker + macro_rules! impl_orphan_view_for { ($ty: ty) => { + impl ViewMarker for $ty {} + impl View 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); diff --git a/xilem_core/src/views/run_once.rs b/xilem_core/src/views/run_once.rs index f8fed951..b2fa7efa 100644 --- a/xilem_core/src/views/run_once.rs +++ b/xilem_core/src/views/run_once.rs @@ -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 { once: F, } +impl ViewMarker for RunOnce {} impl View for RunOnce where Context: ViewPathTracker, diff --git a/xilem_core/tests/common/mod.rs b/xilem_core/tests/common/mod.rs index 33f20f2e..31afc3a7 100644 --- a/xilem_core/tests/common/mod.rs +++ b/xilem_core/tests/common/mod.rs @@ -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 { +pub(super) struct SequenceView { id: u32, seq: Seq, - phantom: PhantomData, } -pub(super) fn sequence(id: u32, seq: Seq) -> SequenceView +pub(super) fn sequence(id: u32, seq: Seq) -> SequenceView where - Seq: ViewSequence<(), Action, TestCtx, TestElement, Marker>, + Seq: ViewSequence<(), Action, TestCtx, TestElement>, { - SequenceView { - id, - seq, - phantom: PhantomData, - } + SequenceView { id, seq } } -impl View<(), Action, TestCtx> for SequenceView +impl ViewMarker for SequenceView {} +impl View<(), Action, TestCtx> for SequenceView where - Seq: ViewSequence<(), Action, TestCtx, TestElement, Marker>, - Marker: 'static, + Seq: ViewSequence<(), Action, TestCtx, TestElement>, { type Element = TestElement; @@ -160,6 +153,7 @@ where } } +impl ViewMarker for OperationView {} impl View<(), Action, TestCtx> for OperationView { type Element = TestElement; diff --git a/xilem_web/src/attribute.rs b/xilem_web/src/attribute.rs index c131c3a0..33001d3f 100644 --- a/xilem_web/src/attribute.rs +++ b/xilem_web/src/attribute.rs @@ -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 Attr { } } -impl View for Attr +impl ViewMarker for Attr {} +impl View for Attr where T: 'static, A: 'static, diff --git a/xilem_web/src/class.rs b/xilem_web/src/class.rs index 47700f8d..1ad40396 100644 --- a/xilem_web/src/class.rs +++ b/xilem_web/src/class.rs @@ -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 Class { } } +impl ViewMarker for Class {} impl View for Class where T: 'static, diff --git a/xilem_web/src/concurrent/memoized_await.rs b/xilem_web/src/concurrent/memoized_await.rs index 55153919..06db90ec 100644 --- a/xilem_web/src/concurrent/memoized_await.rs +++ b/xilem_web/src/concurrent/memoized_await.rs @@ -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 { ScheduleUpdate, } +impl ViewMarker + for MemoizedAwait +{ +} impl View for MemoizedAwait where diff --git a/xilem_web/src/elements.rs b/xilem_web/src/elements.rs index 4c9b1281..64b937e6 100644 --- a/xilem_web/src/elements.rs +++ b/xilem_web/src/elements.rs @@ -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 {} -} - // 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: - sealed::Sealed + 'static -{ +pub(crate) trait DomViewSequence: 'static { /// Get an [`Any`] reference to `self`. fn as_any(&self) -> &dyn Any; @@ -35,7 +29,7 @@ pub(crate) trait DomViewSequence: /// Update the associated widgets. fn dyn_seq_rebuild( &self, - prev: &dyn DomViewSequence, + prev: &dyn DomViewSequence, seq_state: &mut Box, ctx: &mut ViewCtx, elements: &mut DomChildrenSplice, @@ -62,21 +56,11 @@ pub(crate) trait DomViewSequence: ) -> MessageResult; } -impl sealed::Sealed for S +impl DomViewSequence for S where State: 'static, - SeqMarker: 'static, Action: 'static, - S: ViewSequence, -{ -} - -impl DomViewSequence for S -where - State: 'static, - SeqMarker: 'static, - Action: 'static, - S: ViewSequence, + S: DomFragment, { fn as_any(&self) -> &dyn Any { self @@ -88,7 +72,7 @@ where fn dyn_seq_rebuild( &self, - prev: &dyn DomViewSequence, + prev: &dyn DomViewSequence, seq_state: &mut Box, 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( - children: &dyn DomViewSequence, +pub(crate) fn build_element( + children: &dyn DomViewSequence, tag_name: &str, ns: &str, ctx: &mut ViewCtx, @@ -243,7 +227,6 @@ where State: 'static, Action: 'static, Element: 'static, - SeqMarker: 'static, Element: From>, { let mut elements = AppendVec::default(); @@ -254,9 +237,9 @@ where ) } -pub(crate) fn rebuild_element<'el, State, Action, Element, SeqMarker>( - children: &dyn DomViewSequence, - prev_children: &dyn DomViewSequence, +pub(crate) fn rebuild_element<'el, State, Action, Element>( + children: &dyn DomViewSequence, + prev_children: &dyn DomViewSequence, element: Mut<'el, Pod>, state: &mut ElementState, ctx: &mut ViewCtx, @@ -265,7 +248,6 @@ where State: 'static, Action: 'static, Element: 'static, - SeqMarker: 'static, Element: DomNode, { let mut dom_children_splice = DomChildrenSplice::new( @@ -285,8 +267,8 @@ where element } -pub(crate) fn teardown_element( - children: &dyn DomViewSequence, +pub(crate) fn teardown_element( + children: &dyn DomViewSequence, element: Mut<'_, Pod>, state: &mut ElementState, ctx: &mut ViewCtx, @@ -294,7 +276,6 @@ pub(crate) fn teardown_element( State: 'static, Action: 'static, Element: 'static, - SeqMarker: 'static, Element: DomNode, { let mut dom_children_splice = DomChildrenSplice::new( @@ -309,34 +290,31 @@ pub(crate) fn teardown_element( } /// An element that can change its tag, it's useful for autonomous custom elements (i.e. web components) -pub struct CustomElement { +pub struct CustomElement { name: Cow<'static, str>, - children: Box>, + children: Box>, } /// An element that can change its tag, it's useful for autonomous custom elements (i.e. web components) -pub fn custom_element( +pub fn custom_element( name: impl Into>, children: Children, -) -> CustomElement +) -> CustomElement where State: 'static, Action: 'static, - SeqMarker: 'static, - Children: ViewSequence, + Children: DomFragment, { CustomElement { name: name.into(), children: Box::new(children), } } - -impl View - for CustomElement +impl ViewMarker for CustomElement {} +impl View for CustomElement where State: 'static, Action: 'static, - SeqMarker: 'static, { type Element = Pod; @@ -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 { - children: Box>, + pub struct $ty_name { + children: Box>, } /// Builder function for a #[doc = concat!("`", $tag_name, "`")] /// element view. - pub fn $name< - State: 'static, - Action: 'static, - SeqMarker: 'static, - Children: ViewSequence, - >( + pub fn $name>( children: Children, - ) -> $ty_name { + ) -> $ty_name { $ty_name { children: Box::new(children), } } - impl View - for $ty_name + impl ViewMarker for $ty_name {} + impl View for $ty_name where State: 'static, Action: 'static, - SeqMarker: 'static, { type Element = Pod; @@ -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);)* }; diff --git a/xilem_web/src/events.rs b/xilem_web/src/events.rs index 8b05f69e..b78dde9e 100644 --- a/xilem_web/src/events.rs +++ b/xilem_web/src/events.rs @@ -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 ViewMarker for OnEvent {} impl View for OnEvent where @@ -327,6 +328,7 @@ macro_rules! event_definitions { pub(crate) phantom_event_ty: PhantomData (State, Action)>, } + impl ViewMarker for $ty_name {} impl $ty_name { pub fn new(element: V, handler: Callback) -> Self { Self { diff --git a/xilem_web/src/lib.rs b/xilem_web/src/lib.rs index ad39eb1a..3004ca4e 100644 --- a/xilem_web/src/lib.rs +++ b/xilem_web/src/lib.rs @@ -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 { +/// (clicks >= 5).then_some("Huzzah, clicked at least 5 times") +/// } +/// ``` +pub trait DomFragment: + ViewSequence +{ +} + +impl DomFragment for V where + V: ViewSequence +{ +} + /// 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 { diff --git a/xilem_web/src/pointer.rs b/xilem_web/src/pointer.rs index ace47faf..c5d3de9f 100644 --- a/xilem_web/src/pointer.rs +++ b/xilem_web/src/pointer.rs @@ -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>( } } +impl ViewMarker for Pointer {} impl View for Pointer where diff --git a/xilem_web/src/style.rs b/xilem_web/src/style.rs index 7f792e41..63b89e0c 100644 --- a/xilem_web/src/style.rs +++ b/xilem_web/src/style.rs @@ -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 Style { } } +impl ViewMarker for Style {} impl View for Style where T: 'static, diff --git a/xilem_web/src/svg/common_attrs.rs b/xilem_web/src/svg/common_attrs.rs index 67d6e580..472446b8 100644 --- a/xilem_web/src/svg/common_attrs.rs +++ b/xilem_web/src/svg/common_attrs.rs @@ -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 ViewMarker for Fill {} impl View for Fill where State: 'static, @@ -116,6 +117,7 @@ where } } +impl ViewMarker for Stroke {} impl View for Stroke where State: 'static, diff --git a/xilem_web/web_examples/counter/src/main.rs b/xilem_web/web_examples/counter/src/main.rs index a205923e..a4cc0b19 100644 --- a/xilem_web/web_examples/counter/src/main.rs +++ b/xilem_web/web_examples/counter/src/main.rs @@ -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 { + (state.clicks >= 5).then_some("Huzzah, clicked at least 5 times") +} + fn app_logic(state: &mut AppState) -> impl HtmlDivElement { 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()),