Add documentation

This commit is contained in:
Richard Dodd 2023-07-02 17:05:05 +01:00
parent a7cc3a1e4e
commit cc9e81dbc9
9 changed files with 120 additions and 8 deletions

View File

@ -57,6 +57,9 @@ macro_rules! impl_view_tuple {
#[macro_export]
macro_rules! generate_viewsequence_trait {
($viewseq:ident, $view:ident, $viewmarker: ident, $bound:ident, $cx:ty, $changeflags:ty, $pod:ty; $( $ss:tt )* ) => {
/// This trait represents a (possibly empty) sequence of views.
///
/// It is up to the parent view how to lay out and display them.
pub trait $viewseq<T, A = ()> $( $ss )* {
/// Associated states for the views.
type State $( $ss )*;
@ -283,8 +286,8 @@ macro_rules! generate_viewsequence_trait {
/// This trait marks a type a
#[doc = concat!(stringify!($view), ".")]
///
/// This trait is a workaround for Rust's orphan rules. It serves as a switch between default"]
/// and custom
/// This trait is a workaround for Rust's orphan rules. It serves as a switch between
/// default and custom
#[doc = concat!("`", stringify!($viewseq), "`")]
/// implementations. You can't implement
#[doc = concat!("`", stringify!($viewseq), "`")]

View File

@ -68,6 +68,59 @@ macro_rules! generate_view_trait {
) -> $crate::MessageResult<A>;
}
/// A view that wraps a child view and modifies the state that callbacks have access to.
///
/// # Examples
///
/// Suppose you have an outer type that looks like
///
/// ```
/// struct State {
/// todos: Vec<Todo>
/// }
/// ```
///
/// and an inner type/view that looks like
///
/// ```ignore
/// struct Todo {
/// label: String
/// }
///
/// struct TodoView {
/// label: String
/// }
///
/// enum TodoAction {
/// Delete
/// }
///
/// impl View<Todo, TodoAction> for TodoView {
/// // ...
/// }
/// ```
///
/// then your top-level action (`()`) and state type (`State`) don't match `TodoView`'s.
/// You can use the `Adapt` view to mediate between them:
///
/// ```ignore
/// state
/// .todos
/// .enumerate()
/// .map(|(idx, todo)| {
/// Adapt::new(
/// move |data: &mut AppState, thunk| {
/// if let MessageResult::Action(action) = thunk.call(&mut data.todos[idx]) {
/// match action {
/// TodoAction::Delete => data.todos.remove(idx),
/// }
/// }
/// MessageResult::Nop
/// },
/// TodoView { label: todo.label }
/// )
/// })
/// ```
pub struct Adapt<OutData, OutMsg, InData, InMsg, F: Fn(&mut OutData, AdaptThunk<InData, InMsg, V>) -> $crate::MessageResult<OutMsg>, V: View<InData, InMsg>> {
f: F,
child: V,

View File

@ -10,6 +10,7 @@ use crate::{
};
use xilem_core::{Id, MessageResult};
/// The type responsible for running your app.
pub struct App<T, V: View<T>, F: FnMut(&mut T) -> V>(Rc<RefCell<AppInner<T, V, F>>>);
struct AppInner<T, V: View<T>, F: FnMut(&mut T) -> V> {
@ -35,6 +36,7 @@ impl<T: 'static, V: View<T> + 'static, F: FnMut(&mut T) -> V + 'static> Clone fo
}
impl<T: 'static, V: View<T> + 'static, F: FnMut(&mut T) -> V + 'static> App<T, V, F> {
/// Create an instance of your app with the given logic and initial state.
pub fn new(data: T, app_logic: F) -> Self {
let inner = AppInner::new(data, app_logic);
let app = App(Rc::new(RefCell::new(inner)));
@ -42,6 +44,10 @@ impl<T: 'static, V: View<T> + 'static, F: FnMut(&mut T) -> V + 'static> App<T, V
app
}
/// Run the app.
///
/// Because we don't want to block the render thread, we return immediately here. The app is
/// forgotten, and will continue to respond to events in the background.
pub fn run(self, root: &web_sys::HtmlElement) {
self.0.borrow_mut().ensure_app(root);
// Latter may not be necessary, we have an rc loop.

View File

@ -1,4 +1,4 @@
//! Macros to generate all the different html elements
//! Types that wrap [`Element`][super::Element] and represent specific element types.
//!
macro_rules! elements {
() => {};
@ -10,8 +10,14 @@ macro_rules! elements {
macro_rules! element {
($ty_name:ident, $builder_name:ident, $name:literal, $web_sys_ty:ty) => {
/// A view representing a
#[doc = concat!("`", $name, "`")]
/// element.
pub struct $ty_name<ViewSeq>(crate::Element<$web_sys_ty, ViewSeq>);
/// Builder function for a
#[doc = concat!("`", $name, "`")]
/// view.
pub fn $builder_name<ViewSeq>(children: ViewSeq) -> $ty_name<ViewSeq> {
$ty_name(crate::element($name, children))
}

View File

@ -15,6 +15,8 @@ use xilem_core::{Id, MessageResult, VecSplice};
pub mod elements;
/// A view representing a HTML element.
///
/// If the element has no chilcdren, use the unit type (e.g. `let view = element("div", ())`).
pub struct Element<El, Children = ()> {
name: Cow<'static, str>,
attributes: BTreeMap<Cow<'static, str>, Cow<'static, str>>,
@ -46,7 +48,9 @@ pub struct ElementState<ViewSeqState> {
child_elements: Vec<Pod>,
}
/// Create a new element
/// Create a new element view
///
/// If the element has no chilcdren, use the unit type (e.g. `let view = element("div", ())`).
pub fn element<E, ViewSeq>(
name: impl Into<Cow<'static, str>>,
children: ViewSeq,
@ -71,10 +75,16 @@ impl<E, ViewSeq> Element<E, ViewSeq> {
name: impl Into<Cow<'static, str>>,
value: impl Into<Cow<'static, str>>,
) -> Self {
self.attributes.insert(name.into(), value.into());
self.set_attr(name, value);
self
}
/// Set an attribute on this element.
///
/// # Panics
///
/// If the name contains characters that are not valid in an attribute name,
/// then the `View::build`/`View::rebuild` functions will panic for this view.
pub fn set_attr(
&mut self,
name: impl Into<Cow<'static, str>>,

View File

@ -10,6 +10,9 @@ macro_rules! events {
macro_rules! event {
($ty_name:ident, $builder_name:ident, $name:literal, $web_sys_ty:ty) => {
/// A view that listens for the
#[doc = concat!("`", $name, "`")]
/// event.
pub struct $ty_name<T, A, V, F, OA>
where
V: crate::view::View<T, A>,
@ -23,6 +26,9 @@ macro_rules! event {
optional_action: std::marker::PhantomData<OA>,
}
/// Builder for the
#[doc = concat!("`", $name, "`")]
/// event listener.
pub fn $builder_name<T, A, V, F, OA>(child: V, callback: F) -> $ty_name<T, A, V, F, OA>
where
V: crate::view::View<T, A>,

View File

@ -15,6 +15,10 @@ use crate::{
view::{DomNode, View, ViewMarker},
};
/// Wraps a [`View`] `V` and attaches an event listener.
///
/// The event type `E` contains both the [`web_sys::Event`] subclass for this event and the
/// [`web_sys::HtmlElement`] subclass that matches `V::Element`.
pub struct OnEvent<E, V, F> {
// TODO changing this after creation is unsupported for now,
// please create a new view instead.
@ -107,6 +111,7 @@ pub fn on_event<E, V, F>(name: &'static str, child: V, callback: F) -> OnEvent<E
OnEvent::new(name, child, callback)
}
/// State for the `OnEvent` view.
pub struct OnEventState<S> {
#[allow(unused)]
listener: EventListener,
@ -116,6 +121,7 @@ struct EventMsg<E> {
event: E,
}
/// Wraps a `web_sys::Event` and provides auto downcasting for both the event and its target.
pub struct Event<Evt, El> {
raw: Evt,
el: PhantomData<El>,
@ -135,6 +141,9 @@ where
Evt: AsRef<web_sys::Event>,
El: JsCast,
{
/// Get the event target element.
///
/// Because this type knows its child view's element type, we can downcast to this type here.
pub fn target(&self) -> El {
let evt: &web_sys::Event = self.raw.as_ref();
evt.target().unwrap_throw().dyn_into().unwrap_throw()
@ -155,7 +164,7 @@ impl<Evt, El> Deref for Event<Evt, El> {
pub trait Action {}
/// Trait that allows callbacks to be polymorphic on return type
/// (`Action`, `Option<Action>` or `()`)
/// (`Action`, `Option<Action>` or `()`). An implementation detail.
pub trait OptionalAction<A>: sealed::Sealed {
fn action(self) -> Option<A>;
}

View File

@ -10,13 +10,20 @@ use xilem_core::{Id, MessageResult};
use crate::{context::Cx, ChangeFlags};
mod sealed {
pub trait Sealed {}
}
// A possible refinement of xilem_core is to allow a single concrete type
// for a view element, rather than an associated type with a bound.
pub trait DomNode {
/// This trait is implemented for types that implement `AsRef<web_sys::Node>`.
/// It is an implementation detail.
pub trait DomNode: sealed::Sealed {
fn into_pod(self) -> Pod;
fn as_node_ref(&self) -> &web_sys::Node;
}
impl<N: AsRef<web_sys::Node> + 'static> sealed::Sealed for N {}
impl<N: AsRef<web_sys::Node> + 'static> DomNode for N {
fn into_pod(self) -> Pod {
Pod(Box::new(self))
@ -27,6 +34,8 @@ impl<N: AsRef<web_sys::Node> + 'static> DomNode for N {
}
}
/// This trait is implemented for types that implement `AsRef<web_sys::Element>`.
/// It is an implementation detail.
pub trait DomElement: DomNode {
fn as_element_ref(&self) -> &web_sys::Element;
}
@ -37,7 +46,9 @@ impl<N: DomNode + AsRef<web_sys::Element>> DomElement for N {
}
}
pub trait AnyNode {
/// A trait for types that can be type-erased and impl `AsRef<Node>`. It is an
/// implementation detail.
pub trait AnyNode: sealed::Sealed {
fn as_any_mut(&mut self) -> &mut dyn Any;
fn as_node_ref(&self) -> &web_sys::Node;
@ -53,6 +64,7 @@ impl<N: AsRef<web_sys::Node> + Any> AnyNode for N {
}
}
impl sealed::Sealed for Box<dyn AnyNode> {}
impl DomNode for Box<dyn AnyNode> {
fn into_pod(self) -> Pod {
Pod(self)

View File

@ -5,7 +5,10 @@ use std::borrow::Cow;
use crate::{class::Class, event::OptionalAction, events as e, view::View, Event};
/// A trait that makes it possible to attach event listeners and more to views
/// in the continuation style.
pub trait ViewExt<T, A>: View<T, A> + Sized {
/// Add an `onclick` event listener.
fn on_click<
OA: OptionalAction<A>,
F: Fn(&mut T, &Event<web_sys::MouseEvent, Self::Element>) -> OA,
@ -13,6 +16,7 @@ pub trait ViewExt<T, A>: View<T, A> + Sized {
self,
f: F,
) -> e::OnClick<T, A, Self, F, OA>;
/// Add an `ondblclick` event listener.
fn on_dblclick<
OA: OptionalAction<A>,
F: Fn(&mut T, &Event<web_sys::MouseEvent, Self::Element>) -> OA,
@ -20,6 +24,7 @@ pub trait ViewExt<T, A>: View<T, A> + Sized {
self,
f: F,
) -> e::OnDblClick<T, A, Self, F, OA>;
/// Add an `oninput` event listener.
fn on_input<
OA: OptionalAction<A>,
F: Fn(&mut T, &Event<web_sys::InputEvent, Self::Element>) -> OA,
@ -27,6 +32,7 @@ pub trait ViewExt<T, A>: View<T, A> + Sized {
self,
f: F,
) -> e::OnInput<T, A, Self, F, OA>;
/// Add an `onkeydown` event listener.
fn on_keydown<
OA: OptionalAction<A>,
F: Fn(&mut T, &Event<web_sys::KeyboardEvent, Self::Element>) -> OA,
@ -34,6 +40,7 @@ pub trait ViewExt<T, A>: View<T, A> + Sized {
self,
f: F,
) -> e::OnKeyDown<T, A, Self, F, OA>;
/// Apply a CSS class to the child view.
fn class(self, class: impl Into<Cow<'static, str>>) -> Class<Self> {
crate::class::class(self, class)
}