diff --git a/.clippy.toml b/.clippy.toml index 5c5a9538..9187aad4 100644 --- a/.clippy.toml +++ b/.clippy.toml @@ -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, 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 diff --git a/masonry/src/render_root.rs b/masonry/src/render_root.rs index 6d868a7f..1c0ac883 100644 --- a/masonry/src/render_root.rs +++ b/masonry/src/render_root.rs @@ -75,7 +75,6 @@ pub(crate) struct RenderRootState { pub(crate) scenes: HashMap, } -#[allow(clippy::type_complexity)] pub(crate) struct MutateCallback { pub(crate) id: WidgetId, pub(crate) callback: Box>)>, diff --git a/xilem/examples/components.rs b/xilem/examples/components.rs index fea92f4e..06d57e87 100644 --- a/xilem/examples/components.rs +++ b/xilem/examples/components.rs @@ -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 { +fn modular_counter(count: &mut i32) -> impl WidgetView { flex(( label(format!("modularized count: {count}")), button("+", |count| *count += 1), @@ -27,10 +27,7 @@ fn modularized_counter(count: &mut i32) -> impl WidgetView { fn app_logic(state: &mut AppState) -> impl WidgetView { 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, diff --git a/xilem_core/src/docs.rs b/xilem_core/src/docs.rs index f466a465..b1ff5801 100644 --- a/xilem_core/src/docs.rs +++ b/xilem_core/src/docs.rs @@ -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 { +//! /// 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: View {} +impl DocsView for V where V: View {} + +/// A state type usable in a component +pub struct State; + +/// A minimal component. +pub fn some_component(_: &mut State) -> impl DocsView { + // The view which does nothing already exists in `run_once`. + run_once(|| {}) +} diff --git a/xilem_core/src/lib.rs b/xilem_core/src/lib.rs index 29c649a8..f8d77d1e 100644 --- a/xilem_core/src/lib.rs +++ b/xilem_core/src/lib.rs @@ -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; diff --git a/xilem_core/src/views/map_action.rs b/xilem_core/src/views/map_action.rs index af1f5480..b4e1048a 100644 --- a/xilem_core/src/views/map_action.rs +++ b/xilem_core/src/views/map_action.rs @@ -17,7 +17,6 @@ pub struct MapAction< > { map_fn: F, child: V, - #[allow(clippy::type_complexity)] phantom: PhantomData (State, ParentAction, ChildAction)>, } diff --git a/xilem_core/src/views/map_state.rs b/xilem_core/src/views/map_state.rs index e178ddda..f1b74e41 100644 --- a/xilem_core/src/views/map_state.rs +++ b/xilem_core/src/views/map_state.rs @@ -5,12 +5,13 @@ use core::marker::PhantomData; 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. -pub struct MapState &mut ChildState> { - f: F, +/// The View for [`map_state`] and [`lens`]. +/// +/// See their documentation for more context. +pub struct MapState { + map_state: F, child: V, - phantom: PhantomData (ParentState, ChildState)>, + phantom: PhantomData (ChildState, Action, Context, Message)>, } /// A view that "extracts" state from a [`View`] to [`View`]. @@ -42,7 +43,7 @@ pub struct MapState &mut pub fn map_state( view: V, f: F, -) -> MapState +) -> MapState 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 ViewMarker for MapState {} -impl - View for MapState +/// 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`. +/// For example, a date picker might be of the form `fn date_picker(&mut Date) -> impl WidgetView`. +/// 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 { +/// lens(date_picker, state, |state| &mut state.date) +/// } +/// +/// struct FlightPlanner { +/// date: Date, +/// available_flights: Vec, +/// } +/// ``` +pub fn lens( + 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 +where + StateF: Fn(&mut OuterState) -> &mut InnerState + Send + Sync + 'static, + Component: FnOnce(&mut InnerState) -> InnerView, + InnerView: View, + Context: ViewPathTracker, +{ + let mapped = map(state); + let view = component(mapped); + MapState { + child: view, + map_state: map, + phantom: PhantomData, + } +} + +impl ViewMarker + for MapState +{ +} +impl + View + for MapState where ParentState: 'static, ChildState: 'static, V: View, 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 { self.child - .message(view_state, id_path, message, (self.f)(app_state)) + .message(view_state, id_path, message, (self.map_state)(app_state)) } } diff --git a/xilem_core/src/views/mod.rs b/xilem_core/src/views/mod.rs index c362cebb..cae48f24 100644 --- a/xilem_core/src/views/mod.rs +++ b/xilem_core/src/views/mod.rs @@ -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}; diff --git a/xilem_core/src/views/run_once.rs b/xilem_core/src/views/run_once.rs index b2fa7efa..afc2261c 100644 --- a/xilem_core/src/views/run_once.rs +++ b/xilem_core/src/views/run_once.rs @@ -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 { +/// fn log_lifecycle(data: &mut AppData) -> impl WidgetView { /// run_once(|| eprintln!("View constructed")) /// } /// ``` @@ -32,11 +32,11 @@ use crate::{MessageResult, NoElement, View, ViewMarker, ViewPathTracker}; /// // /// // 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 { +/// fn log_data(app: &mut AppData) -> impl WidgetView { /// let val = app.data; /// run_once(move || println!("{}", val)) /// } diff --git a/xilem_web/src/concurrent/memoized_await.rs b/xilem_web/src/concurrent/memoized_await.rs index e9b86e48..6ef605a9 100644 --- a/xilem_web/src/concurrent/memoized_await.rs +++ b/xilem_web/src/concurrent/memoized_await.rs @@ -16,7 +16,6 @@ pub struct MemoizedAwait callback: Callback, debounce_ms: usize, reset_debounce_on_update: bool, - #[allow(clippy::type_complexity)] phantom: PhantomData (State, Action, OA, F, FOut)>, } diff --git a/xilem_web/src/events.rs b/xilem_web/src/events.rs index 7e3af842..8aab6c5c 100644 --- a/xilem_web/src/events.rs +++ b/xilem_web/src/events.rs @@ -22,7 +22,6 @@ pub struct OnEvent { pub(crate) capture: bool, pub(crate) passive: bool, pub(crate) handler: Callback, - #[allow(clippy::type_complexity)] pub(crate) phantom_event_ty: PhantomData (State, Action, Event)>, } diff --git a/xilem_web/src/lib.rs b/xilem_web/src/lib.rs index 700e9ec9..f8396bcf 100644 --- a/xilem_web/src/lib.rs +++ b/xilem_web/src/lib.rs @@ -188,7 +188,10 @@ pub trait DomView: } /// See [`map_state`](`core::map_state`) - fn map_state(self, f: F) -> MapState + fn map_state( + self, + f: F, + ) -> MapState where State: 'static, ParentState: 'static,