Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the lens component #587

Merged
merged 7 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .clippy.toml
Original file line number Diff line number Diff line change
@@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

1 change: 0 additions & 1 deletion masonry/src/render_root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,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>>)>,
Expand Down
11 changes: 4 additions & 7 deletions xilem/examples/components.rs
Original file line number Diff line number Diff line change
@@ -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,
};
Expand All @@ -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),
Expand All @@ -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,
Expand Down
52 changes: 51 additions & 1 deletion xilem_core/src/docs.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,41 @@
// Copyright 2024 the Xilem Authors
// SPDX-License-Identifier: Apache-2.0

// Hide these docs from "general audiences" online.
// but keep them available for developers of Xilem Core to browse.
#![cfg_attr(docsrs, doc(hidden))]

//! 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::ViewPathTracker;
use crate::{run_once, View, ViewPathTracker};

/// A type used for documentation
pub enum Fake {}
Expand All @@ -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(|| {})
}
4 changes: 2 additions & 2 deletions xilem_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 0 additions & 1 deletion xilem_core/src/views/map_action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ pub struct MapAction<
> {
map_fn: F,
child: V,
#[allow(clippy::type_complexity)]
phantom: PhantomData<fn() -> (State, ParentAction, ChildAction)>,
}

Expand Down
85 changes: 74 additions & 11 deletions xilem_core/src/views/map_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, has this kind caused issues already in practice?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so, but I could foresee it being troublesome for the tests.

It might have been fine.

I will add a comment that the variance consequences of this have not been reasoned about.

}

/// A view that "extracts" state from a [`View<ParentState,_,_>`] to [`View<ChildState,_,_>`].
Expand Down Expand Up @@ -42,28 +43,90 @@ 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,
V: View<ChildState, Action, Context, Message>,
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:
/// - `state`: The current outer view's state
/// - `map`: A function from the higher-level state type to `component`'s state type
/// - `component`: The child component the lens is being created for.
DJMcNab marked this conversation as resolved.
Show resolved Hide resolved
///
/// 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;
Expand Down Expand Up @@ -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))
}
}
2 changes: 1 addition & 1 deletion xilem_core/src/views/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
8 changes: 4 additions & 4 deletions xilem_core/src/views/run_once.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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, ()> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just noticed this (+docs.rs), nice trick.

/// run_once(|| eprintln!("View constructed"))
/// }
/// ```
Expand All @@ -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))
/// }
Expand Down
1 change: 0 additions & 1 deletion xilem_web/src/concurrent/memoized_await.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)>,
}

Expand Down
1 change: 0 additions & 1 deletion xilem_web/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)>,
}

Expand Down
5 changes: 4 additions & 1 deletion xilem_web/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down