diff --git a/crates/mogwai/Cargo.toml b/crates/mogwai/Cargo.toml index a8eec66..40c0948 100644 --- a/crates/mogwai/Cargo.toml +++ b/crates/mogwai/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mogwai" -version = "0.7.5" +version = "0.7.6" edition = "2024" authors = ["Schell Scivally "] license = "MIT" @@ -23,7 +23,7 @@ async-channel = { workspace = true, optional = true } futures-core = "0.3.31" futures-lite.workspace = true log.workspace = true -mogwai-macros = { version = "0.2.1", path = "../mogwai-macros" } +mogwai-macros = { version = "0.2.2", path = "../mogwai-macros" } serde.workspace = true wasm-bindgen.workspace = true wasm-bindgen-futures = { workspace = true, optional = true } diff --git a/crates/mogwai/src/future.rs b/crates/mogwai/src/future.rs index 453ee02..d699a96 100644 --- a/crates/mogwai/src/future.rs +++ b/crates/mogwai/src/future.rs @@ -1,10 +1,19 @@ //! Utilitites for working with futures. //! //! These are meant to be small additions to [`futures_lite`]. +//! +//! Re-exports the [`step`](crate::step) traits for convenience. +//! Note: [`race_all`] boxes futures as `Pin>>` +//! (default `'static`), so per-child futures passed to [`StepWith`] / +//! [`StepWithMut`] closures must produce a `'static`-compatible output +//! (`Ev: 'static`) even though the future itself may borrow from its +//! child reference via the higher-ranked lifetime. use std::{future::Future, pin::Pin}; use futures_lite::FutureExt; +pub use crate::step::{Step, StepMut, StepWith, StepWithMut}; + pub trait MogwaiFutureExt where Self: Sized + Future, diff --git a/crates/mogwai/src/lib.rs b/crates/mogwai/src/lib.rs index 049f4f6..a0d1f08 100644 --- a/crates/mogwai/src/lib.rs +++ b/crates/mogwai/src/lib.rs @@ -1,23 +1,26 @@ //! # Mogwai: Cross-Platform UI Library //! -//! Mogwai is a Rust library for building UI components that work across platforms, -//! but primarily in the browser. +//! Mogwai is a Rust library for building UI components that work across +//! platforms, but primarily in the browser. //! //! ## Key Concepts //! -//! - **Low boilerplate view construction**: Use the [`rsx!`](view::rsx) macro to reduce boilerplate. +//! - **Low boilerplate view construction**: Use the [`rsx!`](view::rsx) macro +//! to reduce boilerplate. //! - **Async event handling**: Events are futures, not callbacks. -//! - **Cross-platform support**: [View traits](crate::view) ensure operations are cross-platform, -//! with room for specialization. +//! - **Cross-platform support**: [View traits](crate::view) ensure operations +//! are cross-platform, with room for specialization. //! - **Idiomatic Rust**: Widgets are Rust types //! -//! Mogwai provides tools to implement these concepts efficiently, promoting flexibility and performance. +//! Mogwai provides tools to implement these concepts efficiently, promoting +//! flexibility and performance. pub mod an_introduction; #[cfg(feature = "future")] pub mod future; pub mod proxy; #[cfg(feature = "ssr")] pub mod ssr; +pub mod step; mod str; pub mod sync; pub mod time; @@ -27,7 +30,7 @@ pub mod web; pub mod prelude { //! Common prelude between all platforms. - pub use crate::{proxy::*, str::*, view::*}; + pub use crate::{proxy::*, step::*, str::*, view::*}; } pub use str::Str; diff --git a/crates/mogwai/src/step.rs b/crates/mogwai/src/step.rs new file mode 100644 index 0000000..edebb82 --- /dev/null +++ b/crates/mogwai/src/step.rs @@ -0,0 +1,202 @@ +//! Pull-based stepping traits for widget event loops. +//! +//! Mogwai widgets are driven by a pull-based event loop: a caller awaits the +//! widget's next event, reacts to it, then awaits again. This module +//! formalizes that convention as four traits so the compiler — not prose — +//! enforces the contract and so generic composition (e.g. racing N children) +//! becomes possible. +//! +//! ## When to use which trait +//! +//! | Trait | Receiver | Use when | +//! |------|----------|----------| +//! | [`Step`] | `&self` | `step` only awaits event listeners (interior mutability). Lets a parent race multiple children concurrently. | +//! | [`StepMut`] | `&mut self` | `step` mutates the widget's own fields or drives a mutable resource. Children cannot be raced concurrently. | +//! | [`StepWith`] | `&self` | A container of `T`-typed children that races a per-child future (supplied by a closure) against its own event. | +//! | [`StepWithMut`] | `&mut self` | Same, but with exclusive access to each child. | +//! +//! ## Object safety +//! +//! These traits use `impl Future` returns (RPITIT) and are therefore **not** +//! object-safe. If a `dyn Step` need ever arises, add a boxed companion trait +//! with a blanket bridge as a non-breaking addition. + +use std::{future::Future, pin::Pin}; + +/// Pull-based event source — immutable borrow. +/// +/// Implement this when `step` only awaits event listeners (which use interior +/// mutability). This lets a parent race multiple children's `step()` futures +/// concurrently without borrow conflicts. +/// +/// ## Example +/// +/// ```no_run +/// use mogwai::{prelude::*, step::Step}; +/// +/// struct Button { +/// on_click: V::EventListener, +/// } +/// +/// impl Step for Button { +/// type Output = V::Event; +/// fn step(&self) -> impl Future { +/// self.on_click.next() +/// } +/// } +/// ``` +pub trait Step { + /// The event produced by a single step. Must be `'static` so any `Step` + /// can be plugged into a [`StepWith`] closure. + type Output: 'static; + + /// Advance the widget by one event. May resolve to `Self::Output` or + /// never resolve (e.g. a presentational component with no events). + fn step(&self) -> impl Future; +} + +/// Pull-based event source — exclusive borrow. +/// +/// Implement this when `step` mutates the widget's own fields or drives a +/// mutable resource it owns. A parent cannot race two `StepMut` children +/// concurrently; use [`Step`] instead when concurrent racing is needed. +/// +/// ## Example +/// +/// ```no_run +/// use mogwai::{prelude::*, step::StepMut}; +/// +/// struct Counter { +/// count: u32, +/// on_click: ::EventListener, +/// } +/// +/// impl StepMut for Counter { +/// type Output = (); +/// fn step_mut(&mut self) -> impl Future { +/// async move { +/// let _ev = self.on_click.next().await; +/// self.count += 1; +/// } +/// } +/// } +/// ``` +pub trait StepMut { + /// The event produced by a single step. Must be `'static` so any + /// `StepMut` can be plugged into a [`StepWithMut`] closure. + type Output: 'static; + + /// Advance the widget by one event, mutating internal state. + fn step_mut(&mut self) -> impl Future; +} + +/// A container of `T`-typed children that races a per-child future (supplied by +/// a closure) against its own event future — immutable borrow. +/// +/// This generalizes the `List::step` / `ButtonGroup::step` / `TabList::step` +/// pattern: the container owns `N` children of type `T`, and the caller +/// decides how each child produces a future of type `Ev`. +/// +/// ## Example +/// +/// ```no_run +/// use mogwai::{ +/// prelude::*, +/// step::{Step, StepWith}, +/// }; +/// use std::pin::Pin; +/// +/// struct List { +/// items: Vec, +/// // ... +/// # _phantom: std::marker::PhantomData, +/// } +/// +/// impl StepWith for List { +/// type Output = (); +/// fn step_with( +/// &self, +/// f: impl for<'a> FnMut(&'a T) -> Pin + 'a>>, +/// ) -> impl Future +/// where +/// Ev: 'static, +/// { +/// async move { +/// // race all children's futures... +/// # std::future::pending::<()>().await +/// } +/// } +/// } +/// ``` +pub trait StepWith { + /// The event produced by the container's own step. + type Output: 'static; + + /// Race the container's own event future against a future produced by + /// `f` for each child. The first to resolve wins. + /// + /// The closure uses a higher-ranked trait bound (`for<'a>`) so the returned + /// boxed future is allowed to borrow from the child reference for exactly + /// as long as that borrow lives — e.g. `Box::pin(child.step())` where + /// `step` borrows `&'a self`. + fn step_with( + &self, + f: impl for<'a> FnMut(&'a T) -> Pin + 'a>>, + ) -> impl Future + where + Ev: 'static; +} + +/// A container of `T`-typed children that races a per-child future (supplied by +/// a closure) against its own event future — mutable borrow. +/// +/// This generalizes the `TabPanel::step_with` / `Table::step_with` pattern: the +/// container owns `N` children of type `T` mutably, and the caller decides how +/// each produces a future of type `Ev`. +/// +/// ## Example +/// +/// ```no_run +/// use mogwai::{prelude::*, step::StepWithMut}; +/// use std::pin::Pin; +/// +/// struct TabPanel { +/// panes: Vec

, +/// // ... +/// # _phantom: std::marker::PhantomData, +/// } +/// +/// impl StepWithMut

for TabPanel { +/// type Output = (); +/// fn step_with_mut( +/// &mut self, +/// f: impl for<'a> FnMut(&'a mut P) -> Pin + 'a>>, +/// ) -> impl Future +/// where +/// Ev: 'static, +/// { +/// async move { +/// // race tab clicks against all pane futures... +/// # std::future::pending::<()>().await +/// } +/// } +/// } +/// ``` +pub trait StepWithMut { + /// The event produced by the container's own step. + type Output: 'static; + + /// Race the container's own event future against a future produced by + /// `f` for each child (with mutable access). The first to resolve wins. + /// + /// The closure uses a higher-ranked trait bound (`for<'a>`) so the returned + /// boxed future is allowed to borrow from the child reference for exactly + /// as long as that borrow lives — e.g. `Box::pin(child.step_mut())` where + /// `step_mut` borrows `&'a mut self`. + fn step_with_mut( + &mut self, + f: impl for<'a> FnMut(&'a mut T) -> Pin + 'a>>, + ) -> impl Future + where + Ev: 'static; +}