Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 2 additions & 2 deletions crates/mogwai/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "mogwai"
version = "0.7.5"
version = "0.7.6"
edition = "2024"
authors = ["Schell Scivally <efsubenovex@gmail.com>"]
license = "MIT"
Expand All @@ -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 }
Expand Down
9 changes: 9 additions & 0 deletions crates/mogwai/src/future.rs
Original file line number Diff line number Diff line change
@@ -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<Box<dyn Future<Output = T>>>`
//! (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,
Expand Down
17 changes: 10 additions & 7 deletions crates/mogwai/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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::*};
Comment thread
schell marked this conversation as resolved.
}

pub use str::Str;
Expand Down
202 changes: 202 additions & 0 deletions crates/mogwai/src/step.rs
Original file line number Diff line number Diff line change
@@ -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<T>`] | `&self` | A container of `T`-typed children that races a per-child future (supplied by a closure) against its own event. |
//! | [`StepWithMut<T>`] | `&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<V: View> {
/// on_click: V::EventListener,
/// }
///
/// impl<V: View> Step for Button<V> {
/// type Output = V::Event;
/// fn step(&self) -> impl Future<Output = V::Event> {
/// 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<Output = Self::Output>;
}

/// 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: <mogwai::web::Web as View>::EventListener,
/// }
///
/// impl StepMut for Counter {
/// type Output = ();
/// fn step_mut(&mut self) -> impl Future<Output = ()> {
/// 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<Output = Self::Output>;
}

/// 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<V: View, T> {
/// items: Vec<T>,
/// // ...
/// # _phantom: std::marker::PhantomData<V>,
/// }
///
/// impl<V: View, T> StepWith<T> for List<V, T> {
/// type Output = ();
/// fn step_with<Ev>(
/// &self,
/// f: impl for<'a> FnMut(&'a T) -> Pin<Box<dyn Future<Output = Ev> + 'a>>,
/// ) -> impl Future<Output = ()>
/// where
/// Ev: 'static,
/// {
/// async move {
/// // race all children's futures...
/// # std::future::pending::<()>().await
/// }
/// }
/// }
/// ```
pub trait StepWith<T> {
/// 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<Ev>(
&self,
f: impl for<'a> FnMut(&'a T) -> Pin<Box<dyn Future<Output = Ev> + 'a>>,
) -> impl Future<Output = Self::Output>
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<V: View, P> {
/// panes: Vec<P>,
/// // ...
/// # _phantom: std::marker::PhantomData<V>,
/// }
///
/// impl<V: View, P> StepWithMut<P> for TabPanel<V, P> {
/// type Output = ();
/// fn step_with_mut<Ev>(
/// &mut self,
/// f: impl for<'a> FnMut(&'a mut P) -> Pin<Box<dyn Future<Output = Ev> + 'a>>,
/// ) -> impl Future<Output = ()>
/// where
/// Ev: 'static,
/// {
/// async move {
/// // race tab clicks against all pane futures...
/// # std::future::pending::<()>().await
/// }
/// }
/// }
/// ```
pub trait StepWithMut<T> {
/// 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<Ev>(
&mut self,
f: impl for<'a> FnMut(&'a mut T) -> Pin<Box<dyn Future<Output = Ev> + 'a>>,
) -> impl Future<Output = Self::Output>
where
Ev: 'static;
}
Loading