xilem/xilem_core/src/views/map_state.rs

180 lines
5.7 KiB
Rust

// Copyright 2024 the Xilem Authors
// SPDX-License-Identifier: Apache-2.0
use core::{fmt::Debug, marker::PhantomData};
use crate::{MessageResult, Mut, View, ViewId, ViewMarker, ViewPathTracker};
/// The View for [`map_state`] and [`lens`].
///
/// See their documentation for more context.
pub struct MapState<V, F, ParentState, ChildState, Action, Context, Message> {
map_state: F,
child: V,
phantom: PhantomData<fn(ParentState) -> (ChildState, Action, Context, Message)>,
}
impl<V, F, ParentState, ChildState, Action, Context, Message> Debug
for MapState<V, F, ParentState, ChildState, Action, Context, Message>
where
V: Debug,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("MapAction")
.field("child", &self.child)
.finish_non_exhaustive()
}
}
/// A view that "extracts" state from a [`View<ParentState,_,_>`] to [`View<ChildState,_,_>`].
/// This allows modularization of views based on their state.
///
/// # Examples
///
/// (From the Xilem implementation)
///
/// ```ignore
/// #[derive(Default)]
/// struct AppState {
/// count: i32,
/// other: i32,
/// }
///
/// fn count_view(count: i32) -> impl WidgetView<i32> {
/// flex((
/// label(format!("count: {}", count)),
/// button("+", |count| *count += 1),
/// button("-", |count| *count -= 1),
/// ))
/// }
///
/// fn app_logic(state: &mut AppState) -> impl WidgetView<AppState> {
/// map_state(count_view(state.count), |state: &mut AppState| &mut state.count)
/// }
/// ```
pub fn map_state<ParentState, ChildState, Action, Context: ViewPathTracker, Message, V, F>(
view: V,
f: F,
) -> MapState<V, F, ParentState, ChildState, Action, Context, Message>
where
ParentState: 'static,
ChildState: 'static,
V: View<ChildState, Action, Context, Message>,
F: Fn(&mut ParentState) -> &mut ChildState + 'static,
{
MapState {
map_state: f,
child: view,
phantom: PhantomData,
}
}
/// An adapter which allows using a component which only uses one field of the current state.
///
/// In Xilem, many components are functions of the form `fn my_component(&mut SomeState) -> impl WidgetView<SomeState>`.
/// For example, a date picker might be of the form `fn date_picker(&mut Date) -> impl WidgetView<Date>`.
/// The `lens` View allows using these components in a higher-level component, where the higher level state has
/// a field of the inner component's state type.
/// For example, a flight finder app might have a `Date` field for the currently selected date.
///
/// The parameters of this view are:
/// - `component`: The child component the lens is being created for.
/// - `state`: The current outer view's state
/// - `map`: A function from the higher-level state type to `component`'s state type
///
/// This is a wrapper around [`map_state`].
/// That view can be used if the child doesn't follow the expected component signature.
///
/// # Examples
///
/// In code, the date picker example might look like:
///
/// ```
/// # use xilem_core::docs::{DocsView as WidgetView, State as Date, State as Flight, some_component as date_picker};
/// use xilem_core::lens;
///
/// fn app_logic(state: &mut FlightPlanner) -> impl WidgetView<FlightPlanner> {
/// lens(date_picker, state, |state| &mut state.date)
/// }
///
/// struct FlightPlanner {
/// date: Date,
/// available_flights: Vec<Flight>,
/// }
/// ```
pub fn lens<OuterState, Action, Context, Message, InnerState, StateF, InnerView, Component>(
component: Component,
state: &mut OuterState,
// This parameter ordering does run into https://github.com/rust-lang/rustfmt/issues/3605
// Our general advice is to make sure that the lens arguments are short enough...
map: StateF,
) -> MapState<InnerView, StateF, OuterState, InnerState, Action, Context, Message>
where
StateF: Fn(&mut OuterState) -> &mut InnerState + Send + Sync + 'static,
Component: FnOnce(&mut InnerState) -> InnerView,
InnerView: View<InnerState, Action, Context, Message>,
Context: ViewPathTracker,
{
let mapped = map(state);
let view = component(mapped);
MapState {
child: view,
map_state: map,
phantom: PhantomData,
}
}
impl<V, F, ParentState, ChildState, Action, Context, Message> ViewMarker
for MapState<V, F, ParentState, ChildState, Action, Context, Message>
{
}
impl<ParentState, ChildState, Action, Context, Message, V, F>
View<ParentState, Action, Context, Message>
for MapState<V, F, ParentState, ChildState, Action, Context, Message>
where
ParentState: 'static,
ChildState: 'static,
V: View<ChildState, Action, Context, Message>,
F: Fn(&mut ParentState) -> &mut ChildState + 'static,
Action: 'static,
Context: ViewPathTracker + 'static,
Message: 'static,
{
type ViewState = V::ViewState;
type Element = V::Element;
fn build(&self, ctx: &mut Context) -> (Self::Element, Self::ViewState) {
self.child.build(ctx)
}
fn rebuild(
&self,
prev: &Self,
view_state: &mut Self::ViewState,
ctx: &mut Context,
element: Mut<Self::Element>,
) {
self.child.rebuild(&prev.child, view_state, ctx, element);
}
fn teardown(
&self,
view_state: &mut Self::ViewState,
ctx: &mut Context,
element: Mut<Self::Element>,
) {
self.child.teardown(view_state, ctx, element);
}
fn message(
&self,
view_state: &mut Self::ViewState,
id_path: &[ViewId],
message: Message,
app_state: &mut ParentState,
) -> MessageResult<Action, Message> {
self.child
.message(view_state, id_path, message, (self.map_state)(app_state))
}
}