Write documentation for iced_pure

This commit is contained in:
Héctor Ramón Jiménez 2022-05-02 20:16:00 +02:00
parent 68e9eb0a9b
commit e7d595c7de
No known key found for this signature in database
GPG key ID: 140CC052C94F138E
26 changed files with 558 additions and 31 deletions

View file

@ -48,7 +48,7 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> {
/// Sets the vertical spacing _between_ elements.
///
/// Custom margins per element do not exist in Iced. You should use this
/// Custom margins per element do not exist in iced. You should use this
/// method instead! While less flexible, it helps you keep spacing between
/// elements consistent.
pub fn spacing(mut self, units: u16) -> Self {

View file

@ -48,7 +48,7 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> {
/// Sets the horizontal spacing _between_ elements.
///
/// Custom margins per element do not exist in Iced. You should use this
/// Custom margins per element do not exist in iced. You should use this
/// method instead! While less flexible, it helps you keep spacing between
/// elements consistent.
pub fn spacing(mut self, units: u16) -> Self {

View file

@ -29,7 +29,7 @@ where
/// The default padding of a [`Tooltip`] drawn by this renderer.
const DEFAULT_PADDING: u16 = 5;
/// Creates an empty [`Tooltip`].
/// Creates a new [`Tooltip`].
///
/// [`Tooltip`]: struct.Tooltip.html
pub fn new(

View file

@ -8,25 +8,171 @@ use iced_native::mouse;
use iced_native::renderer;
use iced_native::{Clipboard, Length, Point, Rectangle, Shell};
/// 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]: crate::widget
pub struct Element<'a, Message, Renderer> {
widget: Box<dyn Widget<Message, Renderer> + 'a>,
}
impl<'a, Message, Renderer> Element<'a, Message, Renderer> {
/// Creates a new [`Element`] containing the given [`Widget`].
pub fn new(widget: impl Widget<Message, Renderer> + 'a) -> Self {
Self {
widget: Box::new(widget),
}
}
/// Returns a reference to the [`Widget`] of the [`Element`],
pub fn as_widget(&self) -> &dyn Widget<Message, Renderer> {
self.widget.as_ref()
}
/// Returns a mutable reference to the [`Widget`] of the [`Element`],
pub fn as_widget_mut(&mut self) -> &mut dyn Widget<Message, Renderer> {
self.widget.as_mut()
}
/// 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__.
///
/// # Example
/// Imagine we want to use [our counter](index.html#usage). But instead of
/// showing a single counter, we want to display many of them. We can reuse
/// the `Counter` type as it is!
///
/// We use composition to model the __state__ of our new application:
///
/// ```
/// # mod counter {
/// # pub struct Counter;
/// # }
/// use counter::Counter;
///
/// struct ManyCounters {
/// counters: Vec<Counter>,
/// }
/// ```
///
/// We can store the state of multiple counters now. However, the
/// __messages__ we implemented before describe the user interactions
/// of a __single__ counter. Right now, we need to also identify which
/// counter is receiving user interactions. Can we use composition again?
/// Yes.
///
/// ```
/// # mod counter {
/// # #[derive(Debug, Clone, Copy)]
/// # pub enum Message {}
/// # }
/// #[derive(Debug, Clone, Copy)]
/// pub enum Message {
/// Counter(usize, counter::Message)
/// }
/// ```
///
/// We compose the previous __messages__ with the index of the counter
/// producing them. Let's implement our __view logic__ now:
///
/// ```
/// # mod counter {
/// # type Text = iced_pure::widget::Text<iced_native::renderer::Null>;
/// #
/// # #[derive(Debug, Clone, Copy)]
/// # pub enum Message {}
/// # pub struct Counter;
/// #
/// # impl Counter {
/// # pub fn view(&mut self) -> Text {
/// # Text::new("")
/// # }
/// # }
/// # }
/// #
/// # mod iced_wgpu {
/// # pub use iced_native::renderer::Null as Renderer;
/// # }
/// #
/// # use counter::Counter;
/// #
/// # struct ManyCounters {
/// # counters: Vec<Counter>,
/// # }
/// #
/// # #[derive(Debug, Clone, Copy)]
/// # pub enum Message {
/// # Counter(usize, counter::Message)
/// # }
/// use iced_pure::Element;
/// use iced_pure::widget::Row;
/// use iced_wgpu::Renderer;
///
/// impl ManyCounters {
/// pub fn view(&mut self) -> Row<Message, Renderer> {
/// // We can quickly populate a `Row` by folding over our counters
/// self.counters.iter_mut().enumerate().fold(
/// Row::new().spacing(20),
/// |row, (index, counter)| {
/// // We display the counter
/// let element: Element<counter::Message, Renderer> =
/// counter.view().into();
///
/// row.push(
/// // Here we turn our `Element<counter::Message>` into
/// // an `Element<Message>` by combining the `index` and the
/// // message of the `element`.
/// element.map(move |message| Message::Counter(index, message))
/// )
/// }
/// )
/// }
/// }
/// ```
///
/// Finally, our __update logic__ is pretty straightforward: simple
/// delegation.
///
/// ```
/// # mod counter {
/// # #[derive(Debug, Clone, Copy)]
/// # pub enum Message {}
/// # pub struct Counter;
/// #
/// # impl Counter {
/// # pub fn update(&mut self, _message: Message) {}
/// # }
/// # }
/// #
/// # use counter::Counter;
/// #
/// # struct ManyCounters {
/// # counters: Vec<Counter>,
/// # }
/// #
/// # #[derive(Debug, Clone, Copy)]
/// # pub enum Message {
/// # Counter(usize, counter::Message)
/// # }
/// impl ManyCounters {
/// pub fn update(&mut self, message: Message) {
/// match message {
/// Message::Counter(index, counter_msg) => {
/// if let Some(counter) = self.counters.get_mut(index) {
/// counter.update(counter_msg);
/// }
/// }
/// }
/// }
/// }
/// ```
pub fn map<B>(
self,
f: impl Fn(Message) -> B + 'a,

View file

@ -1,3 +1,4 @@
//! Helper functions to create pure widgets.
use crate::widget;
use crate::Element;
@ -5,6 +6,9 @@ use iced_native::Length;
use std::borrow::Cow;
use std::ops::RangeInclusive;
/// Creates a new [`Container`] with the provided content.
///
/// [`Container`]: widget::Container
pub fn container<'a, Message, Renderer>(
content: impl Into<Element<'a, Message, Renderer>>,
) -> widget::Container<'a, Message, Renderer>
@ -14,15 +18,24 @@ where
widget::Container::new(content)
}
/// Creates a new [`Column`].
///
/// [`Column`]: widget::Column
pub fn column<'a, Message, Renderer>() -> widget::Column<'a, Message, Renderer>
{
widget::Column::new()
}
/// Creates a new [`Row`].
///
/// [`Row`]: widget::Row
pub fn row<'a, Message, Renderer>() -> widget::Row<'a, Message, Renderer> {
widget::Row::new()
}
/// Creates a new [`Scrollable`] with the provided content.
///
/// [`Scrollable`]: widget::Scrollable
pub fn scrollable<'a, Message, Renderer>(
content: impl Into<Element<'a, Message, Renderer>>,
) -> widget::Scrollable<'a, Message, Renderer>
@ -32,12 +45,19 @@ where
widget::Scrollable::new(content)
}
/// Creates a new [`Button`] with the provided content.
///
/// [`Button`]: widget::Button
pub fn button<'a, Message, Renderer>(
content: impl Into<Element<'a, Message, Renderer>>,
) -> widget::Button<'a, Message, Renderer> {
widget::Button::new(content)
}
/// Creates a new [`Tooltip`] with the provided content, tooltip text, and [`tooltip::Position`].
///
/// [`Tooltip`]: widget::Tooltip
/// [`tooltip::Position`]: widget::tooltip::Position
pub fn tooltip<'a, Message, Renderer>(
content: impl Into<Element<'a, Message, Renderer>>,
tooltip: impl ToString,
@ -49,6 +69,9 @@ where
widget::Tooltip::new(content, tooltip, position)
}
/// Creates a new [`Text`] widget with the provided content.
///
/// [`Text`]: widget::Text
pub fn text<Renderer>(text: impl Into<String>) -> widget::Text<Renderer>
where
Renderer: iced_native::text::Renderer,
@ -56,6 +79,9 @@ where
widget::Text::new(text)
}
/// Creates a new [`Checkbox`].
///
/// [`Checkbox`]: widget::Checkbox
pub fn checkbox<'a, Message, Renderer>(
label: impl Into<String>,
is_checked: bool,
@ -67,6 +93,9 @@ where
widget::Checkbox::new(is_checked, label, f)
}
/// Creates a new [`Radio`].
///
/// [`Radio`]: widget::Radio
pub fn radio<'a, Message, Renderer, V>(
label: impl Into<String>,
value: V,
@ -81,6 +110,9 @@ where
widget::Radio::new(value, label, selected, on_click)
}
/// Creates a new [`Toggler`].
///
/// [`Toggler`]: widget::Toggler
pub fn toggler<'a, Message, Renderer>(
label: impl Into<Option<String>>,
is_checked: bool,
@ -92,6 +124,9 @@ where
widget::Toggler::new(is_checked, label, f)
}
/// Creates a new [`TextInput`].
///
/// [`TextInput`]: widget::TextInput
pub fn text_input<'a, Message, Renderer>(
placeholder: &str,
value: &str,
@ -104,6 +139,9 @@ where
widget::TextInput::new(placeholder, value, on_change)
}
/// Creates a new [`Slider`].
///
/// [`Slider`]: widget::Slider
pub fn slider<'a, Message, T>(
range: std::ops::RangeInclusive<T>,
value: T,
@ -116,6 +154,9 @@ where
widget::Slider::new(range, value, on_change)
}
/// Creates a new [`PickList`].
///
/// [`PickList`]: widget::PickList
pub fn pick_list<'a, Message, Renderer, T>(
options: impl Into<Cow<'a, [T]>>,
selected: Option<T>,
@ -129,14 +170,23 @@ where
widget::PickList::new(options, selected, on_selected)
}
/// Creates a new [`Image`].
///
/// [`Image`]: widget::Image
pub fn image<Handle>(handle: impl Into<Handle>) -> widget::Image<Handle> {
widget::Image::new(handle.into())
}
/// Creates a new horizontal [`Space`] with the given [`Length`].
///
/// [`Space`]: widget::Space
pub fn horizontal_space(width: Length) -> widget::Space {
widget::Space::with_width(width)
}
/// Creates a new vertical [`Space`] with the given [`Length`].
///
/// [`Space`]: widget::Space
pub fn vertical_space(height: Length) -> widget::Space {
widget::Space::with_height(height)
}

View file

@ -1,3 +1,93 @@
//! Stateless, pure widgets for iced.
//!
//! # The Elm Architecture, purity, and continuity
//! As you may know, applications made with `iced` use [The Elm Architecture].
//!
//! In a nutshell, this architecture defines the initial state of the application, a way to `view` it, and a way to `update` it after a user interaction. The `update` logic is called after a meaningful user interaction, which in turn updates the state of the application. Then, the `view` logic is executed to redisplay the application.
//!
//! Since `view` logic is only run after an `update`, all of the mutations to the application state must only happen in the `update` logic. If the application state changes anywhere else, the `view` logic will not be rerun and, therefore, the previously generated `view` may stay outdated.
//!
//! However, the `Application` trait in `iced` defines `view` as:
//!
//! ```ignore
//! pub trait Application {
//! fn view(&mut self) -> Element<Self::Message>;
//! }
//! ```
//!
//! As a consequence, the application state can be mutated in `view` logic. The `view` logic in `iced` is __impure__.
//!
//! This impurity is necessary because `iced` puts the burden of widget __continuity__ on its users. In other words, it's up to you to provide `iced` with the internal state of each widget every time `view` is called.
//!
//! If we take a look at the classic `counter` example:
//!
//! ```ignore
//! struct Counter {
//! value: i32,
//! increment_button: button::State,
//! decrement_button: button::State,
//! }
//!
//! // ...
//!
//! impl Counter {
//! pub fn view(&mut self) -> Column<Message> {
//! Column::new()
//! .push(
//! Button::new(&mut self.increment_button, Text::new("+"))
//! .on_press(Message::IncrementPressed),
//! )
//! .push(Text::new(self.value.to_string()).size(50))
//! .push(
//! Button::new(&mut self.decrement_button, Text::new("-"))
//! .on_press(Message::DecrementPressed),
//! )
//! }
//! }
//! ```
//!
//! We can see how we need to keep track of the `button::State` of each `Button` in our `Counter` state and provide a mutable reference to the widgets in our `view` logic. The widgets produced by `view` are __stateful__.
//!
//! While this approach forces users to keep track of widget state and causes impurity, I originally chose it because it allows `iced` to directly consume the widget tree produced by `view`. Since there is no internal state decoupled from `view` maintained by the runtime, `iced` does not need to compare (e.g. reconciliate) widget trees in order to ensure continuity.
//!
//! # Stateless widgets
//! As the library matures, the need for some kind of persistent widget data (see #553) between `view` calls becomes more apparent (e.g. incremental rendering, animations, accessibility, etc.).
//!
//! If we are going to end up having persistent widget data anyways... There is no reason to have impure, stateful widgets anymore!
//!
//! And so I started exploring and ended up creating a new subcrate called `iced_pure`, which introduces a completely stateless implementation for every widget in `iced`.
//!
//! With the help of this crate, we can now write a pure `counter` example:
//!
//! ```ignore
//! struct Counter {
//! value: i32,
//! }
//!
//! // ...
//!
//! impl Counter {
//! fn view(&self) -> Column<Message> {
//! Column::new()
//! .push(Button::new("Increment").on_press(Message::IncrementPressed))
//! .push(Text::new(self.value.to_string()).size(50))
//! .push(Button::new("Decrement").on_press(Message::DecrementPressed))
//! }
//! }
//! ```
//!
//! Notice how we no longer need to keep track of the `button::State`! The widgets in `iced_pure` do not take any mutable application state in `view`. They are __stateless__ widgets. As a consequence, we do not need mutable access to `self` in `view` anymore. `view` becomes __pure__.
//!
//! [The Elm Architecture]: https://guide.elm-lang.org/architecture/
#![doc(
html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"
)]
#![deny(missing_docs)]
//#![deny(missing_debug_implementations)]
//#![deny(unused_results)]
#![forbid(unsafe_code)]
//#![forbid(rust_2018_idioms)]
pub mod helpers;
pub mod overlay;
pub mod widget;
@ -16,6 +106,32 @@ use iced_native::mouse;
use iced_native::renderer;
use iced_native::{Clipboard, Length, Point, Rectangle, Shell};
/// A bridge between impure and pure widgets.
///
/// If you already have an existing `iced` application, you do not need to switch completely to the new traits in order to benefit from the `pure` module. Instead, you can leverage the new `Pure` widget to include `pure` widgets in your impure `Application`.
///
/// For instance, let's say we want to use our pure `Counter` in an impure application:
///
/// ```ignore
/// use iced_pure::{self, Pure};
///
/// struct Impure {
/// state: pure::State,
/// counter: Counter,
/// }
///
/// impl Sandbox for Impure {
/// // ...
///
/// pub fn view(&mut self) -> Element<Self::Message> {
/// Pure::new(&mut self.state, self.counter.view()).into()
/// }
/// }
/// ```
///
/// [`Pure`] acts as a bridge between pure and impure widgets. It is completely opt-in and can be used to slowly migrate your application to the new architecture.
///
/// The purification of your application may trigger a bunch of important refactors, since it's far easier to keep your data decoupled from the GUI state with stateless widgets. For this reason, I recommend starting small in the most nested views of your application and slowly expand the purity upwards.
pub struct Pure<'a, Message, Renderer> {
state: &'a mut State,
element: Element<'a, Message, Renderer>,
@ -26,6 +142,7 @@ where
Message: 'a,
Renderer: iced_native::Renderer + 'a,
{
/// Creates a new [`Pure`] widget with the given [`State`] and impure [`Element`].
pub fn new(
state: &'a mut State,
content: impl Into<Element<'a, Message, Renderer>>,
@ -37,6 +154,7 @@ where
}
}
/// The internal state of a [`Pure`] widget.
pub struct State {
state_tree: widget::Tree,
}
@ -48,6 +166,7 @@ impl Default for State {
}
impl State {
/// Creates a new [`State`] for a [`Pure`] widget.
pub fn new() -> Self {
Self {
state_tree: widget::Tree::empty(),

View file

@ -1,9 +1,14 @@
//! Display interactive elements on top of other widgets.
use crate::widget::Tree;
use iced_native::Layout;
pub use iced_native::overlay::*;
/// Obtains the first overlay [`Element`] found in the given children.
///
/// This method will generally only be used by advanced users that are
/// implementing the [`Widget`](crate::Widget) trait.
pub fn from_children<'a, Message, Renderer>(
children: &'a [crate::Element<'_, Message, Renderer>],
tree: &'a mut Tree,

View file

@ -1,3 +1,4 @@
//! Use the built-in widgets or create your own.
pub mod button;
pub mod checkbox;
pub mod container;
@ -48,17 +49,28 @@ use iced_native::overlay;
use iced_native::renderer;
use iced_native::{Clipboard, Length, Point, Rectangle, Shell};
/// A component that displays information and allows interaction.
///
/// If you want to build your own widgets, you will need to implement this
/// trait.
pub trait Widget<Message, Renderer> {
/// Returns the width of the [`Widget`].
fn width(&self) -> Length;
/// Returns the height of the [`Widget`].
fn height(&self) -> Length;
/// Returns the [`layout::Node`] of the [`Widget`].
///
/// This [`layout::Node`] is used by the runtime to compute the [`Layout`] of the
/// user interface.
fn layout(
&self,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node;
/// Draws the [`Widget`] using the associated `Renderer`.
fn draw(
&self,
state: &Tree,
@ -69,31 +81,31 @@ pub trait Widget<Message, Renderer> {
viewport: &Rectangle,
);
/// Returns the [`Tag`] of the [`Widget`].
///
/// [`Tag`]: tree::Tag
fn tag(&self) -> tree::Tag {
tree::Tag::stateless()
}
/// Returns the [`State`] of the [`Widget`].
///
/// [`State`]: tree::State
fn state(&self) -> tree::State {
tree::State::None
}
/// Returns the state [`Tree`] of the children of the [`Widget`].
fn children(&self) -> Vec<Tree> {
Vec::new()
}
/// Reconciliates the [`Widget`] with the provided [`Tree`].
fn diff(&self, _tree: &mut Tree) {}
fn mouse_interaction(
&self,
_state: &Tree,
_layout: Layout<'_>,
_cursor_position: Point,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
mouse::Interaction::Idle
}
/// Processes a runtime [`Event`].
///
/// By default, it does nothing.
fn on_event(
&mut self,
_state: &mut Tree,
@ -107,6 +119,21 @@ pub trait Widget<Message, Renderer> {
event::Status::Ignored
}
/// Returns the current [`mouse::Interaction`] of the [`Widget`].
///
/// By default, it returns [`mouse::Interaction::Idle`].
fn mouse_interaction(
&self,
_state: &Tree,
_layout: Layout<'_>,
_cursor_position: Point,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
mouse::Interaction::Idle
}
/// Returns the overlay of the [`Widget`], if there is any.
fn overlay<'a>(
&'a self,
_state: &'a mut Tree,

View file

@ -1,3 +1,4 @@
//! Allow your users to perform actions by pressing a button.
use crate::overlay;
use crate::widget::tree::{self, Tree};
use crate::{Element, Widget};
@ -15,6 +16,40 @@ pub use iced_style::button::{Style, StyleSheet};
use button::State;
/// A generic widget that produces a message when pressed.
///
/// ```
/// # type Button<'a, Message> =
/// # iced_pure::widget::Button<'a, Message, iced_native::renderer::Null>;
/// #
/// #[derive(Clone)]
/// enum Message {
/// ButtonPressed,
/// }
///
/// let button = Button::new("Press me!").on_press(Message::ButtonPressed);
/// ```
///
/// If a [`Button::on_press`] handler is not set, the resulting [`Button`] will
/// be disabled:
///
/// ```
/// # type Button<'a, Message> =
/// # iced_pure::widget::Button<'a, Message, iced_native::renderer::Null>;
/// #
/// #[derive(Clone)]
/// enum Message {
/// ButtonPressed,
/// }
///
/// fn disabled_button<'a>() -> Button<'a, Message> {
/// Button::new("I'm disabled!")
/// }
///
/// fn enabled_button<'a>() -> Button<'a, Message> {
/// disabled_button().on_press(Message::ButtonPressed)
/// }
/// ```
pub struct Button<'a, Message, Renderer> {
content: Element<'a, Message, Renderer>,
on_press: Option<Message>,
@ -25,6 +60,7 @@ pub struct Button<'a, Message, Renderer> {
}
impl<'a, Message, Renderer> Button<'a, Message, Renderer> {
/// Creates a new [`Button`] with the given content.
pub fn new(content: impl Into<Element<'a, Message, Renderer>>) -> Self {
Button {
content: content.into(),

View file

@ -1,3 +1,4 @@
//! Show toggle controls using checkboxes.
use crate::widget::Tree;
use crate::{Element, Widget};

View file

@ -13,6 +13,7 @@ use iced_native::{
use std::u32;
/// A container that distributes its contents vertically.
pub struct Column<'a, Message, Renderer> {
spacing: u16,
padding: Padding,
@ -24,10 +25,12 @@ pub struct Column<'a, Message, Renderer> {
}
impl<'a, Message, Renderer> Column<'a, Message, Renderer> {
/// Creates an empty [`Column`].
pub fn new() -> Self {
Self::with_children(Vec::new())
}
/// Creates a [`Column`] with the given elements.
pub fn with_children(
children: Vec<Element<'a, Message, Renderer>>,
) -> Self {
@ -42,21 +45,29 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> {
}
}
/// Sets the vertical spacing _between_ elements.
///
/// Custom margins per element do not exist in iced. You should use this
/// method instead! While less flexible, it helps you keep spacing between
/// elements consistent.
pub fn spacing(mut self, units: u16) -> Self {
self.spacing = units;
self
}
/// Sets the [`Padding`] of the [`Column`].
pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
self.padding = padding.into();
self
}
/// Sets the width of the [`Column`].
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`Column`].
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
@ -68,11 +79,13 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> {
self
}
/// Sets the horizontal alignment of the contents of the [`Column`] .
pub fn align_items(mut self, align: Alignment) -> Self {
self.align_items = align;
self
}
/// Adds an element to the [`Column`].
pub fn push(
mut self,
child: impl Into<Element<'a, Message, Renderer>>,

View file

@ -1,3 +1,4 @@
//! Display images in your user interface.
use crate::widget::{Tree, Widget};
use crate::Element;

View file

@ -213,7 +213,7 @@ where
fn diff(&self, tree: &mut Tree) {
tree.diff_children_custom(
&self.elements,
|(_, content), state| content.diff(state),
|state, (_, content)| content.diff(state),
|(_, content)| content.state(),
)
}

View file

@ -57,7 +57,7 @@ impl<'a, Message, Renderer> Content<'a, Message, Renderer>
where
Renderer: iced_native::Renderer,
{
pub fn state(&self) -> Tree {
pub(super) fn state(&self) -> Tree {
let children = if let Some(title_bar) = self.title_bar.as_ref() {
vec![Tree::new(&self.body), title_bar.state()]
} else {
@ -70,7 +70,7 @@ where
}
}
pub fn diff(&self, tree: &mut Tree) {
pub(super) fn diff(&self, tree: &mut Tree) {
if tree.children.len() == 2 {
if let Some(title_bar) = self.title_bar.as_ref() {
title_bar.diff(&mut tree.children[1]);

View file

@ -81,7 +81,7 @@ impl<'a, Message, Renderer> TitleBar<'a, Message, Renderer>
where
Renderer: iced_native::Renderer,
{
pub fn state(&self) -> Tree {
pub(super) fn state(&self) -> Tree {
let children = if let Some(controls) = self.controls.as_ref() {
vec![Tree::new(&self.content), Tree::new(controls)]
} else {
@ -94,7 +94,7 @@ where
}
}
pub fn diff(&self, tree: &mut Tree) {
pub(super) fn diff(&self, tree: &mut Tree) {
if tree.children.len() == 2 {
if let Some(controls) = self.controls.as_ref() {
tree.children[1].diff(controls);

View file

@ -1,3 +1,4 @@
//! Provide progress feedback to your users.
use crate::widget::Tree;
use crate::{Element, Widget};

View file

@ -1,3 +1,4 @@
//! Create choices using radio buttons.
use crate::widget::Tree;
use crate::{Element, Widget};

View file

@ -11,6 +11,7 @@ use iced_native::{
Alignment, Clipboard, Length, Padding, Point, Rectangle, Shell,
};
/// A container that distributes its contents horizontally.
pub struct Row<'a, Message, Renderer> {
spacing: u16,
padding: Padding,
@ -21,10 +22,12 @@ pub struct Row<'a, Message, Renderer> {
}
impl<'a, Message, Renderer> Row<'a, Message, Renderer> {
/// Creates an empty [`Row`].
pub fn new() -> Self {
Self::with_children(Vec::new())
}
/// Creates a [`Row`] with the given elements.
pub fn with_children(
children: Vec<Element<'a, Message, Renderer>>,
) -> Self {
@ -38,31 +41,41 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> {
}
}
/// Sets the horizontal spacing _between_ elements.
///
/// Custom margins per element do not exist in iced. You should use this
/// method instead! While less flexible, it helps you keep spacing between
/// elements consistent.
pub fn spacing(mut self, units: u16) -> Self {
self.spacing = units;
self
}
/// Sets the [`Padding`] of the [`Row`].
pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
self.padding = padding.into();
self
}
/// Sets the width of the [`Row`].
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`Row`].
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
}
/// Sets the vertical alignment of the contents of the [`Row`] .
pub fn align_items(mut self, align: Alignment) -> Self {
self.align_items = align;
self
}
/// Adds an [`Element`] to the [`Row`].
pub fn push(
mut self,
child: impl Into<Element<'a, Message, Renderer>>,

View file

@ -1,3 +1,4 @@
//! Display a horizontal or vertical rule for dividing content.
use crate::widget::Tree;
use crate::{Element, Widget};

View file

@ -1,3 +1,4 @@
//! Navigate an endless amount of content with a scrollbar.
use crate::overlay;
use crate::widget::tree::{self, Tree};
use crate::{Element, Widget};

View file

@ -1,3 +1,4 @@
//! Display vector graphics in your application.
use crate::widget::{Tree, Widget};
use crate::Element;

View file

@ -1,3 +1,4 @@
//! Display fields that can be filled with text.
use crate::widget::tree::{self, Tree};
use crate::{Element, Widget};
@ -15,10 +16,7 @@ pub use iced_style::text_input::{Style, StyleSheet};
///
/// # Example
/// ```
/// # use iced_native::renderer::Null;
/// # use iced_pure::widget::text_input;
/// #
/// # pub type TextInput<'a, Message> = iced_pure::widget::TextInput<'a, Message, Null>;
/// # pub type TextInput<'a, Message> = iced_pure::widget::TextInput<'a, Message, iced_native::renderer::Null>;
/// #[derive(Debug, Clone)]
/// enum Message {
/// TextInputChanged(String),

View file

@ -1,3 +1,4 @@
//! Show toggle controls using togglers.
use crate::widget::{Tree, Widget};
use crate::Element;

View file

@ -32,7 +32,7 @@ where
/// The default padding of a [`Tooltip`] drawn by this renderer.
const DEFAULT_PADDING: u16 = 5;
/// Creates an empty [`Tooltip`].
/// Creates a new [`Tooltip`].
///
/// [`Tooltip`]: struct.Tooltip.html
pub fn new(

View file

@ -1,14 +1,24 @@
//! Store internal widget state in a state tree to ensure continuity.
use crate::Element;
use std::any::{self, Any};
/// A persistent state widget tree.
///
/// A [`Tree`] is normally associated with a specific widget in the widget tree.
pub struct Tree {
/// The tag of the [`Tree`].
pub tag: Tag,
/// The [`State`] of the [`Tree`].
pub state: State,
/// The children of the root widget of the [`Tree`].
pub children: Vec<Tree>,
}
impl Tree {
/// Creates an empty, stateless [`Tree`] with no children.
pub fn empty() -> Self {
Self {
tag: Tag::stateless(),
@ -17,6 +27,7 @@ impl Tree {
}
}
/// Creates a new [`Tree`] for the provided [`Element`].
pub fn new<Message, Renderer>(
element: &Element<'_, Message, Renderer>,
) -> Self {
@ -27,6 +38,14 @@ impl Tree {
}
}
/// Reconciliates the current tree with the provided [`Element`].
///
/// If the tag of the [`Element`] matches the tag of the [`Tree`], then the
/// [`Element`] proceeds with the reconciliation (i.e. [`Widget::diff`] is called).
///
/// Otherwise, the whole [`Tree`] is recreated.
///
/// [`Widget::diff`]: crate::Widget::diff
pub fn diff<Message, Renderer>(
&mut self,
new: &Element<'_, Message, Renderer>,
@ -38,21 +57,20 @@ impl Tree {
}
}
/// Reconciliates the children of the tree with the provided list of [`Element`].
pub fn diff_children<Message, Renderer>(
&mut self,
new_children: &[Element<'_, Message, Renderer>],
) {
self.diff_children_custom(
new_children,
|new, child_state| child_state.diff(new),
Self::new,
)
self.diff_children_custom(new_children, Self::diff, Self::new)
}
/// Reconciliates the children of the tree with the provided list of [`Element`] using custom
/// logic both for diffing and creating new widget state.
pub fn diff_children_custom<T>(
&mut self,
new_children: &[T],
diff: impl Fn(&T, &mut Tree),
diff: impl Fn(&mut Tree, &T),
new_state: impl Fn(&T) -> Self,
) {
if self.children.len() > new_children.len() {
@ -62,7 +80,7 @@ impl Tree {
for (child_state, new) in
self.children.iter_mut().zip(new_children.iter())
{
diff(new, child_state);
diff(child_state, new);
}
if self.children.len() < new_children.len() {
@ -73,10 +91,12 @@ impl Tree {
}
}
/// The identifier of some widget state.
#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
pub struct Tag(any::TypeId);
impl Tag {
/// Creates a [`Tag`] for a state of type `T`.
pub fn of<T>() -> Self
where
T: 'static,
@ -84,17 +104,23 @@ impl Tag {
Self(any::TypeId::of::<T>())
}
/// Creates a [`Tag`] for a stateless widget.
pub fn stateless() -> Self {
Self::of::<()>()
}
}
/// The internal [`State`] of a widget.
pub enum State {
/// No meaningful internal state.
None,
/// Some meaningful internal state.
Some(Box<dyn Any>),
}
impl State {
/// Creates a new [`State`].
pub fn new<T>(state: T) -> Self
where
T: 'static,
@ -102,6 +128,10 @@ impl State {
State::Some(Box::new(state))
}
/// Downcasts the [`State`] to `T` and returns a reference to it.
///
/// # Panics
/// This method will panic if the downcast fails or the [`State`] is [`State::None`].
pub fn downcast_ref<T>(&self) -> &T
where
T: 'static,
@ -114,6 +144,10 @@ impl State {
}
}
/// Downcasts the [`State`] to `T` and returns a mutable reference to it.
///
/// # Panics
/// This method will panic if the downcast fails or the [`State`] is [`State::None`].
pub fn downcast_mut<T>(&mut self) -> &mut T
where
T: 'static,

View file

@ -14,6 +14,84 @@
//! offers an alternate [`Application`] trait with a completely pure `view`
//! method.
//!
//! # The Elm Architecture, purity, and continuity
//! As you may know, applications made with `iced` use [The Elm Architecture].
//!
//! In a nutshell, this architecture defines the initial state of the application, a way to `view` it, and a way to `update` it after a user interaction. The `update` logic is called after a meaningful user interaction, which in turn updates the state of the application. Then, the `view` logic is executed to redisplay the application.
//!
//! Since `view` logic is only run after an `update`, all of the mutations to the application state must only happen in the `update` logic. If the application state changes anywhere else, the `view` logic will not be rerun and, therefore, the previously generated `view` may stay outdated.
//!
//! However, the `Application` trait in `iced` defines `view` as:
//!
//! ```ignore
//! pub trait Application {
//! fn view(&mut self) -> Element<Self::Message>;
//! }
//! ```
//!
//! As a consequence, the application state can be mutated in `view` logic. The `view` logic in `iced` is __impure__.
//!
//! This impurity is necessary because `iced` puts the burden of widget __continuity__ on its users. In other words, it's up to you to provide `iced` with the internal state of each widget every time `view` is called.
//!
//! If we take a look at the classic `counter` example:
//!
//! ```ignore
//! struct Counter {
//! value: i32,
//! increment_button: button::State,
//! decrement_button: button::State,
//! }
//!
//! // ...
//!
//! impl Counter {
//! pub fn view(&mut self) -> Column<Message> {
//! Column::new()
//! .push(
//! Button::new(&mut self.increment_button, Text::new("+"))
//! .on_press(Message::IncrementPressed),
//! )
//! .push(Text::new(self.value.to_string()).size(50))
//! .push(
//! Button::new(&mut self.decrement_button, Text::new("-"))
//! .on_press(Message::DecrementPressed),
//! )
//! }
//! }
//! ```
//!
//! We can see how we need to keep track of the `button::State` of each `Button` in our `Counter` state and provide a mutable reference to the widgets in our `view` logic. The widgets produced by `view` are __stateful__.
//!
//! While this approach forces users to keep track of widget state and causes impurity, I originally chose it because it allows `iced` to directly consume the widget tree produced by `view`. Since there is no internal state decoupled from `view` maintained by the runtime, `iced` does not need to compare (e.g. reconciliate) widget trees in order to ensure continuity.
//!
//! # Stateless widgets
//! As the library matures, the need for some kind of persistent widget data (see #553) between `view` calls becomes more apparent (e.g. incremental rendering, animations, accessibility, etc.).
//!
//! If we are going to end up having persistent widget data anyways... There is no reason to have impure, stateful widgets anymore!
//!
//! With the help of this module, we can now write a pure `counter` example:
//!
//! ```ignore
//! struct Counter {
//! value: i32,
//! }
//!
//! // ...
//!
//! impl Counter {
//! fn view(&self) -> Column<Message> {
//! Column::new()
//! .push(Button::new("Increment").on_press(Message::IncrementPressed))
//! .push(Text::new(self.value.to_string()).size(50))
//! .push(Button::new("Decrement").on_press(Message::DecrementPressed))
//! }
//! }
//! ```
//!
//! Notice how we no longer need to keep track of the `button::State`! The widgets in `iced_pure` do not take any mutable application state in `view`. They are __stateless__ widgets. As a consequence, we do not need mutable access to `self` in `view` anymore. `view` becomes __pure__.
//!
//! [The Elm Architecture]: https://guide.elm-lang.org/architecture/
//!
//! [the original widgets]: crate::widget
//! [`button::State`]: crate::widget::button::State
//! [impure `Application`]: crate::Application