Write docs for iced_web

This commit is contained in:
Héctor Ramón Jiménez 2019-11-22 22:14:04 +01:00
parent 048909b45d
commit fa227255b0
10 changed files with 225 additions and 17 deletions

View File

@ -2,6 +2,12 @@ use crate::Instance;
use std::rc::Rc; 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)] #[derive(Clone)]
pub struct Bus<Message> { pub struct Bus<Message> {
publish: Rc<Box<dyn Fn(Message, &mut dyn dodrio::RootRender)>>, publish: Rc<Box<dyn Fn(Message, &mut dyn dodrio::RootRender)>>,
@ -11,7 +17,7 @@ impl<Message> Bus<Message>
where where
Message: 'static, Message: 'static,
{ {
pub fn new() -> Self { pub(crate) fn new() -> Self {
Self { Self {
publish: Rc::new(Box::new(|message, root| { publish: Rc::new(Box::new(|message, root| {
let app = root.unwrap_mut::<Instance<Message>>(); let app = root.unwrap_mut::<Instance<Message>>();
@ -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) { pub fn publish(&self, message: Message, root: &mut dyn dodrio::RootRender) {
(self.publish)(message, root); (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<B>(&self, mapper: Rc<Box<dyn Fn(B) -> Message>>) -> Bus<B> pub fn map<B>(&self, mapper: Rc<Box<dyn Fn(B) -> Message>>) -> Bus<B>
where where
B: 'static, B: 'static,

View File

@ -3,29 +3,39 @@ use crate::{Bus, Color, Widget};
use dodrio::bumpalo; use dodrio::bumpalo;
use std::rc::Rc; 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<Element>`
/// 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 struct Element<'a, Message> {
pub(crate) widget: Box<dyn Widget<Message> + 'a>, pub(crate) widget: Box<dyn Widget<Message> + 'a>,
} }
impl<'a, Message> Element<'a, Message> { 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<Message> + 'a) -> Self { pub fn new(widget: impl Widget<Message> + 'a) -> Self {
Self { Self {
widget: Box::new(widget), widget: Box::new(widget),
} }
} }
pub fn node<'b>( /// Applies a transformation to the produced message of the [`Element`].
&self, ///
bump: &'b bumpalo::Bump, /// This method is useful when you want to decouple different parts of your
bus: &Bus<Message>, /// UI and make them __composable__.
) -> dodrio::Node<'b> { ///
self.widget.node(bump, bus) /// [`Element`]: struct.Element.html
}
pub fn explain(self, _color: Color) -> Element<'a, Message> {
self
}
pub fn map<F, B>(self, f: F) -> Element<'a, B> pub fn map<F, B>(self, f: F) -> Element<'a, B>
where where
Message: 'static, Message: 'static,
@ -36,6 +46,20 @@ impl<'a, Message> Element<'a, Message> {
widget: Box::new(Map::new(self.widget, f)), 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<Message>,
) -> dodrio::Node<'b> {
self.widget.node(bump, bus)
}
} }
struct Map<'a, A, B> { struct Map<'a, A, B> {

View File

@ -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
//! <!DOCTYPE html>
//! <html>
//! <head>
//! <meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
//! <title>Tour - Iced</title>
//! </head>
//! <body>
//! <script type="module">
//! import init from "./tour/tour.js";
//!
//! init('./tour/tour_bg.wasm');
//! </script>
//! </body>
//! </html>
//! ```
//!
//! 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 dodrio::bumpalo;
use std::cell::RefCell; use std::cell::RefCell;
@ -6,6 +64,7 @@ mod element;
pub mod widget; pub mod widget;
pub use bus::Bus; pub use bus::Bus;
pub use dodrio;
pub use element::Element; pub use element::Element;
pub use iced_core::{ pub use iced_core::{
Align, Background, Color, Font, HorizontalAlignment, Length, Align, Background, Color, Font, HorizontalAlignment, Length,
@ -13,13 +72,42 @@ pub use iced_core::{
}; };
pub use widget::*; 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 `<title>` 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 { pub trait Application {
/// The type of __messages__ your [`Application`] will produce.
///
/// [`Application`]: trait.Application.html
type Message; 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 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) fn run(self)
where where
Self: 'static + Sized, Self: 'static + Sized,

View File

@ -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 crate::Bus;
use dodrio::bumpalo; use dodrio::bumpalo;
pub mod button; pub mod button;
pub mod slider; pub mod slider;
pub mod text;
mod checkbox; mod checkbox;
mod column; mod column;
mod image; mod image;
mod radio; mod radio;
mod row; mod row;
mod text;
#[doc(no_inline)] #[doc(no_inline)]
pub use button::Button; pub use button::Button;
@ -26,7 +42,16 @@ pub use image::Image;
pub use radio::Radio; pub use radio::Radio;
pub use row::Row; 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> { pub trait Widget<Message> {
/// Produces a VDOM node for the [`Widget`].
///
/// [`Widget`]: trait.Widget.html
fn node<'b>( fn node<'b>(
&self, &self,
bump: &'b bumpalo::Bump, bump: &'b bumpalo::Bump,

View File

@ -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 crate::{Background, Bus, Element, Length, Widget};
use dodrio::bumpalo; 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> { pub struct Button<'a, Message> {
content: Element<'a, Message>, content: Element<'a, Message>,
on_press: Option<Message>, on_press: Option<Message>,

View File

@ -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) /// ![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> { pub struct Checkbox<Message> {
is_checked: bool, is_checked: bool,
on_toggle: Box<dyn Fn(bool) -> Message>, on_toggle: Box<dyn Fn(bool) -> Message>,

View File

@ -8,6 +8,7 @@ use std::u32;
/// A [`Column`] will try to fill the horizontal space of its container. /// A [`Column`] will try to fill the horizontal space of its container.
/// ///
/// [`Column`]: struct.Column.html /// [`Column`]: struct.Column.html
#[allow(missing_debug_implementations)]
pub struct Column<'a, Message> { pub struct Column<'a, Message> {
spacing: u16, spacing: u16,
padding: u16, padding: u16,

View File

@ -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) /// ![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> { pub struct Radio<Message> {
is_selected: bool, is_selected: bool,
on_click: Message, on_click: Message,

View File

@ -8,7 +8,7 @@ use std::u32;
/// A [`Row`] will try to fill the horizontal space of its container. /// A [`Row`] will try to fill the horizontal space of its container.
/// ///
/// [`Row`]: struct.Row.html /// [`Row`]: struct.Row.html
#[allow(missing_docs)] #[allow(missing_debug_implementations)]
pub struct Row<'a, Message> { pub struct Row<'a, Message> {
spacing: u16, spacing: u16,
padding: u16, padding: u16,

View File

@ -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 crate::{Bus, Element, Length, Widget};
use dodrio::bumpalo; use dodrio::bumpalo;
use std::{ops::RangeInclusive, rc::Rc}; 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> { pub struct Slider<'a, Message> {
_state: &'a mut State, _state: &'a mut State,
range: RangeInclusive<f32>, 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; pub struct State;
impl State { impl State {
/// Creates a new [`State`].
///
/// [`State`]: struct.State.html
pub fn new() -> Self { pub fn new() -> Self {
Self Self
} }