mirror of https://github.com/linebender/xilem
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:
parent
544a4a1ca9
commit
24427bbb44
|
@ -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)
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 = ();
|
||||
|
|
|
@ -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 = ();
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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 = ();
|
||||
|
|
|
@ -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 = ();
|
||||
|
|
|
@ -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 = ();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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::{
|
||||
|
|
|
@ -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,)+);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);)*
|
||||
};
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()),
|
||||
|
|
Loading…
Reference in New Issue