diff --git a/web/src/bus.rs b/web/src/bus.rs index b4fd67c7..09908679 100644 --- a/web/src/bus.rs +++ b/web/src/bus.rs @@ -2,6 +2,12 @@ use crate::Instance; use std::rc::Rc; +/// A publisher of messages. +/// +/// It can be used to route messages back to the [`Application`]. +/// +/// [`Application`]: trait.Application.html +#[allow(missing_debug_implementations)] #[derive(Clone)] pub struct Bus { publish: Rc>, @@ -11,7 +17,7 @@ impl Bus where Message: 'static, { - pub fn new() -> Self { + pub(crate) fn new() -> Self { Self { publish: Rc::new(Box::new(|message, root| { let app = root.unwrap_mut::>(); @@ -21,10 +27,17 @@ where } } + /// Publishes a new message for the [`Application`]. + /// + /// [`Application`]: trait.Application.html pub fn publish(&self, message: Message, root: &mut dyn dodrio::RootRender) { (self.publish)(message, root); } + /// Creates a new [`Bus`] that applies the given function to the messages + /// before publishing. + /// + /// [`Bus`]: struct.Bus.html pub fn map(&self, mapper: Rc Message>>) -> Bus where B: 'static, diff --git a/web/src/element.rs b/web/src/element.rs index a2b78c69..fcf0a4b6 100644 --- a/web/src/element.rs +++ b/web/src/element.rs @@ -3,29 +3,39 @@ use crate::{Bus, Color, Widget}; use dodrio::bumpalo; use std::rc::Rc; +/// A generic [`Widget`]. +/// +/// It is useful to build composable user interfaces that do not leak +/// implementation details in their __view logic__. +/// +/// If you have a [built-in widget], you should be able to use `Into` +/// to turn it into an [`Element`]. +/// +/// [built-in widget]: widget/index.html +/// [`Widget`]: widget/trait.Widget.html +/// [`Element`]: struct.Element.html +#[allow(missing_debug_implementations)] pub struct Element<'a, Message> { pub(crate) widget: Box + 'a>, } impl<'a, Message> Element<'a, Message> { + /// Create a new [`Element`] containing the given [`Widget`]. + /// + /// [`Element`]: struct.Element.html + /// [`Widget`]: widget/trait.Widget.html pub fn new(widget: impl Widget + 'a) -> Self { Self { widget: Box::new(widget), } } - pub fn node<'b>( - &self, - bump: &'b bumpalo::Bump, - bus: &Bus, - ) -> dodrio::Node<'b> { - self.widget.node(bump, bus) - } - - pub fn explain(self, _color: Color) -> Element<'a, Message> { - self - } - + /// Applies a transformation to the produced message of the [`Element`]. + /// + /// This method is useful when you want to decouple different parts of your + /// UI and make them __composable__. + /// + /// [`Element`]: struct.Element.html pub fn map(self, f: F) -> Element<'a, B> where Message: 'static, @@ -36,6 +46,20 @@ impl<'a, Message> Element<'a, Message> { widget: Box::new(Map::new(self.widget, f)), } } + + /// Marks the [`Element`] as _to-be-explained_. + pub fn explain(self, _color: Color) -> Element<'a, Message> { + self + } + + /// Produces a VDOM node for the [`Element`]. + pub fn node<'b>( + &self, + bump: &'b bumpalo::Bump, + bus: &Bus, + ) -> dodrio::Node<'b> { + self.widget.node(bump, bus) + } } struct Map<'a, A, B> { diff --git a/web/src/lib.rs b/web/src/lib.rs index 00a85cf5..77a963ba 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -1,3 +1,61 @@ +//! A web runtime for Iced, targetting the DOM. +//! +//! ![`iced_web` crate graph](https://github.com/hecrj/iced/blob/cae26cb7bc627f4a5b3bcf1cd023a0c552e8c65e/docs/graphs/web.png?raw=true) +//! +//! `iced_web` takes [`iced_core`] and builds a WebAssembly runtime on top. It +//! achieves this by introducing a `Widget` trait that can be used to produce +//! VDOM nodes. +//! +//! The crate is currently a __very experimental__, simple abstraction layer +//! over [`dodrio`]. +//! +//! [`iced_core`]: https://github.com/hecrj/iced/tree/master/core +//! [`dodrio`]: https://github.com/fitzgen/dodrio +//! +//! # Usage +//! The current build process is a bit involved, as [`wasm-pack`] does not +//! currently [support building binary crates](https://github.com/rustwasm/wasm-pack/issues/734). +//! +//! Therefore, we instead build using the `wasm32-unknown-unknown` target and +//! use the [`wasm-bindgen`] CLI to generate appropriate bindings. +//! +//! For instance, let's say we want to build the [`tour` example]: +//! +//! ```bash +//! cd examples +//! cargo build --example tour --target wasm32-unknown-unknown +//! wasm-bindgen ../target/wasm32-unknown-unknown/debug/examples/tour.wasm --out-dir tour --web +//! ``` +//! +//! Then, we need to create an `.html` file to load our application: +//! +//! ```html +//! +//! +//! +//! +//! Tour - Iced +//! +//! +//! +//! +//! +//! ``` +//! +//! Finally, we serve it using an HTTP server and access it with our browser. +//! +//! [`wasm-pack`]: https://github.com/rustwasm/wasm-pack +//! [`wasm-bindgen`]: https://github.com/rustwasm/wasm-bindgen +//! [`tour` example]: https://github.com/hecrj/iced/blob/master/examples/tour.rs +#![deny(missing_docs)] +#![deny(missing_debug_implementations)] +#![deny(unused_results)] +#![deny(unsafe_code)] +#![deny(rust_2018_idioms)] use dodrio::bumpalo; use std::cell::RefCell; @@ -6,6 +64,7 @@ mod element; pub mod widget; pub use bus::Bus; +pub use dodrio; pub use element::Element; pub use iced_core::{ Align, Background, Color, Font, HorizontalAlignment, Length, @@ -13,13 +72,42 @@ pub use iced_core::{ }; pub use widget::*; +/// An interactive web application. +/// +/// This trait is the main entrypoint of Iced. Once implemented, you can run +/// your GUI application by simply calling [`run`](#method.run). It will take +/// control of the `` and the `<body>` of the document. +/// +/// An [`Application`](trait.Application.html) can execute asynchronous actions +/// by returning a [`Command`](struct.Command.html) in some of its methods. pub trait Application { + /// The type of __messages__ your [`Application`] will produce. + /// + /// [`Application`]: trait.Application.html type Message; + /// Handles a __message__ and updates the state of the [`Application`]. + /// + /// This is where you define your __update logic__. All the __messages__, + /// produced by either user interactions or commands, will be handled by + /// this method. + /// + /// Any [`Command`] returned will be executed immediately in the background. + /// + /// [`Application`]: trait.Application.html + /// [`Command`]: struct.Command.html fn update(&mut self, message: Self::Message); - fn view(&mut self) -> Element<Self::Message>; + /// Returns the widgets to display in the [`Application`]. + /// + /// These widgets can produce __messages__ based on user interaction. + /// + /// [`Application`]: trait.Application.html + fn view(&mut self) -> Element<'_, Self::Message>; + /// Runs the [`Application`]. + /// + /// [`Application`]: trait.Application.html fn run(self) where Self: 'static + Sized, diff --git a/web/src/widget.rs b/web/src/widget.rs index 88b2efc9..30ac8eeb 100644 --- a/web/src/widget.rs +++ b/web/src/widget.rs @@ -1,15 +1,31 @@ +//! Use the built-in widgets or create your own. +//! +//! # Custom widgets +//! If you want to implement a custom widget, you simply need to implement the +//! [`Widget`] trait. You can use the API of the built-in widgets as a guide or +//! source of inspiration. +//! +//! # Re-exports +//! For convenience, the contents of this module are available at the root +//! module. Therefore, you can directly type: +//! +//! ``` +//! use iced_web::{button, Button, Widget}; +//! ``` +//! +//! [`Widget`]: trait.Widget.html use crate::Bus; use dodrio::bumpalo; pub mod button; pub mod slider; -pub mod text; mod checkbox; mod column; mod image; mod radio; mod row; +mod text; #[doc(no_inline)] pub use button::Button; @@ -26,7 +42,16 @@ pub use image::Image; pub use radio::Radio; pub use row::Row; +/// A component that displays information and allows interaction. +/// +/// If you want to build your own widgets, you will need to implement this +/// trait. +/// +/// [`Widget`]: trait.Widget.html pub trait Widget<Message> { + /// Produces a VDOM node for the [`Widget`]. + /// + /// [`Widget`]: trait.Widget.html fn node<'b>( &self, bump: &'b bumpalo::Bump, diff --git a/web/src/widget/button.rs b/web/src/widget/button.rs index ddf67743..1c13f34d 100644 --- a/web/src/widget/button.rs +++ b/web/src/widget/button.rs @@ -1,8 +1,27 @@ +//! Allow your users to perform actions by pressing a button. +//! +//! A [`Button`] has some local [`State`]. +//! +//! [`Button`]: struct.Button.html +//! [`State`]: struct.State.html use crate::{Background, Bus, Element, Length, Widget}; use dodrio::bumpalo; -/// A generic widget that produces a message when clicked. +/// A generic widget that produces a message when pressed. +/// +/// ``` +/// # use iced_web::{button, Button, Text}; +/// # +/// enum Message { +/// ButtonPressed, +/// } +/// +/// let mut state = button::State::new(); +/// let button = Button::new(&mut state, Text::new("Press me!")) +/// .on_press(Message::ButtonPressed); +/// ``` +#[allow(missing_debug_implementations)] pub struct Button<'a, Message> { content: Element<'a, Message>, on_press: Option<Message>, diff --git a/web/src/widget/checkbox.rs b/web/src/widget/checkbox.rs index 8bcef816..94b42554 100644 --- a/web/src/widget/checkbox.rs +++ b/web/src/widget/checkbox.rs @@ -19,6 +19,7 @@ use dodrio::bumpalo; /// ``` /// /// ![Checkbox drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/checkbox.png?raw=true) +#[allow(missing_debug_implementations)] pub struct Checkbox<Message> { is_checked: bool, on_toggle: Box<dyn Fn(bool) -> Message>, diff --git a/web/src/widget/column.rs b/web/src/widget/column.rs index cea50f6d..ee8c14fa 100644 --- a/web/src/widget/column.rs +++ b/web/src/widget/column.rs @@ -8,6 +8,7 @@ use std::u32; /// A [`Column`] will try to fill the horizontal space of its container. /// /// [`Column`]: struct.Column.html +#[allow(missing_debug_implementations)] pub struct Column<'a, Message> { spacing: u16, padding: u16, diff --git a/web/src/widget/radio.rs b/web/src/widget/radio.rs index a0b8fc43..32532ebe 100644 --- a/web/src/widget/radio.rs +++ b/web/src/widget/radio.rs @@ -27,6 +27,7 @@ use dodrio::bumpalo; /// ``` /// /// ![Radio buttons drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/radio.png?raw=true) +#[allow(missing_debug_implementations)] pub struct Radio<Message> { is_selected: bool, on_click: Message, diff --git a/web/src/widget/row.rs b/web/src/widget/row.rs index 44cacd50..b980d9b4 100644 --- a/web/src/widget/row.rs +++ b/web/src/widget/row.rs @@ -8,7 +8,7 @@ use std::u32; /// A [`Row`] will try to fill the horizontal space of its container. /// /// [`Row`]: struct.Row.html -#[allow(missing_docs)] +#[allow(missing_debug_implementations)] pub struct Row<'a, Message> { spacing: u16, padding: u16, diff --git a/web/src/widget/slider.rs b/web/src/widget/slider.rs index acdef0a1..16e20b82 100644 --- a/web/src/widget/slider.rs +++ b/web/src/widget/slider.rs @@ -1,8 +1,37 @@ +//! Display an interactive selector of a single value from a range of values. +//! +//! A [`Slider`] has some local [`State`]. +//! +//! [`Slider`]: struct.Slider.html +//! [`State`]: struct.State.html use crate::{Bus, Element, Length, Widget}; use dodrio::bumpalo; use std::{ops::RangeInclusive, rc::Rc}; +/// An horizontal bar and a handle that selects a single value from a range of +/// values. +/// +/// A [`Slider`] will try to fill the horizontal space of its container. +/// +/// [`Slider`]: struct.Slider.html +/// +/// # Example +/// ``` +/// # use iced_web::{slider, Slider}; +/// # +/// pub enum Message { +/// SliderChanged(f32), +/// } +/// +/// let state = &mut slider::State::new(); +/// let value = 50.0; +/// +/// Slider::new(state, 0.0..=100.0, value, Message::SliderChanged); +/// ``` +/// +/// ![Slider drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/slider.png?raw=true) +#[allow(missing_debug_implementations)] pub struct Slider<'a, Message> { _state: &'a mut State, range: RangeInclusive<f32>, @@ -108,9 +137,16 @@ where } } +/// The local state of a [`Slider`]. +/// +/// [`Slider`]: struct.Slider.html +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub struct State; impl State { + /// Creates a new [`State`]. + /// + /// [`State`]: struct.State.html pub fn new() -> Self { Self }