mirror of https://github.com/linebender/xilem
Add the `lens` component (#587)
See https://xi.zulipchat.com/#narrow/stream/354396-xilem/topic/Lens.20View Usage: ```rust 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>, } ``` Also extends the docs features in Xilem Core, and increases the complexity threshold --------- Co-authored-by: Kaur Kuut <strom@nevermore.ee> Co-authored-by: Philipp Mildenberger <philipp@mildenberger.me>
This commit is contained in:
parent
2ccd9d4712
commit
0d56c592f5
|
@ -1,2 +1,7 @@
|
|||
# Don't warn about these identifiers when using clippy::doc_markdown.
|
||||
doc-valid-idents = ["MathML", ".."]
|
||||
|
||||
# The default clippy value for this is 250, which causes warnings for rather simple types
|
||||
# like Box<dyn Fn(&mut Env, &T)>, which seems overly strict. The new value of 400 is
|
||||
# a simple guess. It might be worth lowering this, or using the default, in the future.
|
||||
type-complexity-threshold = 400
|
||||
|
|
|
@ -75,7 +75,6 @@ pub(crate) struct RenderRootState {
|
|||
pub(crate) scenes: HashMap<WidgetId, Scene>,
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub(crate) struct MutateCallback {
|
||||
pub(crate) id: WidgetId,
|
||||
pub(crate) callback: Box<dyn FnOnce(WidgetMut<'_, Box<dyn Widget>>)>,
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
// Copyright 2024 the Xilem Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Modularizing state can be done with `map_state` which maps a subset of the state from the parent view state
|
||||
//! Modularizing state can be done with `lens` which allows using modular components.
|
||||
|
||||
use masonry::widget::MainAxisAlignment;
|
||||
use winit::error::EventLoopError;
|
||||
use xilem::{
|
||||
core::map_state,
|
||||
core::lens,
|
||||
view::{button, flex, label, Axis},
|
||||
EventLoop, WidgetView, Xilem,
|
||||
};
|
||||
|
@ -17,7 +17,7 @@ struct AppState {
|
|||
global_count: i32,
|
||||
}
|
||||
|
||||
fn modularized_counter(count: &mut i32) -> impl WidgetView<i32> {
|
||||
fn modular_counter(count: &mut i32) -> impl WidgetView<i32> {
|
||||
flex((
|
||||
label(format!("modularized count: {count}")),
|
||||
button("+", |count| *count += 1),
|
||||
|
@ -27,10 +27,7 @@ fn modularized_counter(count: &mut i32) -> impl WidgetView<i32> {
|
|||
|
||||
fn app_logic(state: &mut AppState) -> impl WidgetView<AppState> {
|
||||
flex((
|
||||
map_state(
|
||||
modularized_counter(&mut state.modularized_count),
|
||||
|state: &mut AppState| &mut state.modularized_count,
|
||||
),
|
||||
lens(modular_counter, state, |state| &mut state.modularized_count),
|
||||
button(
|
||||
format!("clicked {} times", state.global_count),
|
||||
|state: &mut AppState| state.global_count += 1,
|
||||
|
|
|
@ -1,9 +1,41 @@
|
|||
// Copyright 2024 the Xilem Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Fake implementations of Xilem traits for use within documentation examples and tests.
|
||||
// Hide these docs from "general audiences" online.
|
||||
// but keep them available for developers of Xilem Core to browse.
|
||||
#![cfg_attr(docsrs, doc(hidden))]
|
||||
|
||||
use crate::ViewPathTracker;
|
||||
//! Fake implementations of Xilem traits for use within documentation examples and tests.
|
||||
//!
|
||||
//! Users of Xilem Core should not use these traits.
|
||||
//!
|
||||
//! The items defined in this trait will often be imported in doc comments in renamed form.
|
||||
//! This is mostly intended for writing documentation internally to Xilem Core.
|
||||
//!
|
||||
//! This module is not required to follow semver. It is public only for documentation purposes.
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! ```
|
||||
//! /// A view to do something fundamental
|
||||
//! ///
|
||||
//! /// # Examples
|
||||
//! /// ```
|
||||
//! /// # use xilem_core::docs::{DocsView as WidgetView, State};
|
||||
//! /// use xilem_core::interesting_primitive;
|
||||
//! /// fn user_component() -> WidgetView<State> {
|
||||
//! /// interesting_primitive()
|
||||
//! /// }
|
||||
//! ///
|
||||
//! /// ```
|
||||
//! fn interesting_primitive() -> InterestingPrimitive {
|
||||
//! // ...
|
||||
//! # InterestingPrimitive
|
||||
//! }
|
||||
//! # struct InterestingPrimitive;
|
||||
//! ```
|
||||
|
||||
use crate::{run_once, View, ViewPathTracker};
|
||||
|
||||
/// A type used for documentation
|
||||
pub enum Fake {}
|
||||
|
@ -20,3 +52,21 @@ impl ViewPathTracker for Fake {
|
|||
match *self {}
|
||||
}
|
||||
}
|
||||
|
||||
/// A version of [`View`] used for documentation.
|
||||
///
|
||||
/// This will often be imported by a different name in a hidden use item.
|
||||
///
|
||||
/// In most cases, that name will be `WidgetView`, as Xilem Core's documentation is
|
||||
/// primarily targeted at users of [Xilem](https://crates.io/crates/xilem/).
|
||||
pub trait DocsView<State, Action = ()>: View<State, Action, Fake> {}
|
||||
impl<V, State, Action> DocsView<State, Action> for V where V: View<State, Action, Fake> {}
|
||||
|
||||
/// A state type usable in a component
|
||||
pub struct State;
|
||||
|
||||
/// A minimal component.
|
||||
pub fn some_component<Action>(_: &mut State) -> impl DocsView<State, Action> {
|
||||
// The view which does nothing already exists in `run_once`.
|
||||
run_once(|| {})
|
||||
}
|
||||
|
|
|
@ -28,8 +28,8 @@ pub use view::{View, ViewId, ViewMarker, ViewPathTracker};
|
|||
|
||||
mod views;
|
||||
pub use views::{
|
||||
adapt, fork, frozen, map_action, map_state, memoize, one_of, run_once, run_once_raw, Adapt,
|
||||
AdaptThunk, Fork, Frozen, MapAction, MapState, Memoize, OrphanView, RunOnce,
|
||||
adapt, fork, frozen, lens, map_action, map_state, memoize, one_of, run_once, run_once_raw,
|
||||
Adapt, AdaptThunk, Fork, Frozen, MapAction, MapState, Memoize, OrphanView, RunOnce,
|
||||
};
|
||||
|
||||
mod message;
|
||||
|
|
|
@ -17,7 +17,6 @@ pub struct MapAction<
|
|||
> {
|
||||
map_fn: F,
|
||||
child: V,
|
||||
#[allow(clippy::type_complexity)]
|
||||
phantom: PhantomData<fn() -> (State, ParentAction, ChildAction)>,
|
||||
}
|
||||
|
||||
|
|
|
@ -5,12 +5,13 @@ use core::marker::PhantomData;
|
|||
|
||||
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.
|
||||
pub struct MapState<ParentState, ChildState, V, F = fn(&mut ParentState) -> &mut ChildState> {
|
||||
f: F,
|
||||
/// 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)>,
|
||||
phantom: PhantomData<fn(ParentState) -> (ChildState, Action, Context, Message)>,
|
||||
}
|
||||
|
||||
/// A view that "extracts" state from a [`View<ParentState,_,_>`] to [`View<ChildState,_,_>`].
|
||||
|
@ -42,7 +43,7 @@ pub struct MapState<ParentState, ChildState, V, F = fn(&mut ParentState) -> &mut
|
|||
pub fn map_state<ParentState, ChildState, Action, Context: ViewPathTracker, Message, V, F>(
|
||||
view: V,
|
||||
f: F,
|
||||
) -> MapState<ParentState, ChildState, V, F>
|
||||
) -> MapState<V, F, ParentState, ChildState, Action, Context, Message>
|
||||
where
|
||||
ParentState: 'static,
|
||||
ChildState: 'static,
|
||||
|
@ -50,20 +51,82 @@ where
|
|||
F: Fn(&mut ParentState) -> &mut ChildState + 'static,
|
||||
{
|
||||
MapState {
|
||||
f,
|
||||
map_state: f,
|
||||
child: view,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
/// 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;
|
||||
|
@ -99,6 +162,6 @@ where
|
|||
app_state: &mut ParentState,
|
||||
) -> MessageResult<Action, Message> {
|
||||
self.child
|
||||
.message(view_state, id_path, message, (self.f)(app_state))
|
||||
.message(view_state, id_path, message, (self.map_state)(app_state))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ mod adapt;
|
|||
pub use adapt::{adapt, Adapt, AdaptThunk};
|
||||
|
||||
mod map_state;
|
||||
pub use map_state::{map_state, MapState};
|
||||
pub use map_state::{lens, map_state, MapState};
|
||||
|
||||
mod map_action;
|
||||
pub use map_action::{map_action, MapAction};
|
||||
|
|
|
@ -16,9 +16,9 @@ use crate::{MessageResult, NoElement, View, ViewMarker, ViewPathTracker};
|
|||
/// This can be useful for logging a value:
|
||||
///
|
||||
/// ```
|
||||
/// # use xilem_core::{run_once, View, docs::{Fake as ViewCtx}, PhantomView};
|
||||
/// # use xilem_core::{run_once, View, docs::{Fake as ViewCtx, DocsView as WidgetView}};
|
||||
/// # struct AppData;
|
||||
/// fn log_lifecycle(data: &mut AppData) -> impl PhantomView<AppData, (), ViewCtx> {
|
||||
/// fn log_lifecycle(data: &mut AppData) -> impl WidgetView<AppData, ()> {
|
||||
/// run_once(|| eprintln!("View constructed"))
|
||||
/// }
|
||||
/// ```
|
||||
|
@ -32,11 +32,11 @@ use crate::{MessageResult, NoElement, View, ViewMarker, ViewPathTracker};
|
|||
/// // <https://doc.rust-lang.org/error_codes/E0080.html>
|
||||
/// // Note that this error code is only checked on nightly
|
||||
/// ```compile_fail,E0080
|
||||
/// # use xilem_core::{run_once, View, docs::{Fake as ViewCtx}, PhantomView};
|
||||
/// # use xilem_core::{run_once, View, docs::{DocsView as WidgetView}};
|
||||
/// # struct AppData {
|
||||
/// # data: u32
|
||||
/// # }
|
||||
/// fn log_data(app: &mut AppData) -> impl PhantomView<AppData, (), ViewCtx> {
|
||||
/// fn log_data(app: &mut AppData) -> impl WidgetView<AppData, ()> {
|
||||
/// let val = app.data;
|
||||
/// run_once(move || println!("{}", val))
|
||||
/// }
|
||||
|
|
|
@ -16,7 +16,6 @@ pub struct MemoizedAwait<State, Action, OA, InitFuture, Data, Callback, F, FOut>
|
|||
callback: Callback,
|
||||
debounce_ms: usize,
|
||||
reset_debounce_on_update: bool,
|
||||
#[allow(clippy::type_complexity)]
|
||||
phantom: PhantomData<fn() -> (State, Action, OA, F, FOut)>,
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,6 @@ pub struct OnEvent<V, State, Action, Event, Callback> {
|
|||
pub(crate) capture: bool,
|
||||
pub(crate) passive: bool,
|
||||
pub(crate) handler: Callback,
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub(crate) phantom_event_ty: PhantomData<fn() -> (State, Action, Event)>,
|
||||
}
|
||||
|
||||
|
|
|
@ -188,7 +188,10 @@ pub trait DomView<State, Action = ()>:
|
|||
}
|
||||
|
||||
/// See [`map_state`](`core::map_state`)
|
||||
fn map_state<ParentState, F>(self, f: F) -> MapState<ParentState, State, Self, F>
|
||||
fn map_state<ParentState, F>(
|
||||
self,
|
||||
f: F,
|
||||
) -> MapState<Self, F, ParentState, State, Action, ViewCtx, DynMessage>
|
||||
where
|
||||
State: 'static,
|
||||
ParentState: 'static,
|
||||
|
|
Loading…
Reference in New Issue