diff options
Diffstat (limited to '')
| -rw-r--r-- | web/src/widget.rs | 13 | ||||
| -rw-r--r-- | web/src/widget/button.rs | 47 | ||||
| -rw-r--r-- | web/src/widget/checkbox.rs | 9 | ||||
| -rw-r--r-- | web/src/widget/column.rs | 30 | ||||
| -rw-r--r-- | web/src/widget/container.rs | 142 | ||||
| -rw-r--r-- | web/src/widget/image.rs | 3 | ||||
| -rw-r--r-- | web/src/widget/radio.rs | 14 | ||||
| -rw-r--r-- | web/src/widget/row.rs | 30 | ||||
| -rw-r--r-- | web/src/widget/scrollable.rs | 157 | ||||
| -rw-r--r-- | web/src/widget/slider.rs | 46 | ||||
| -rw-r--r-- | web/src/widget/text.rs | 23 | ||||
| -rw-r--r-- | web/src/widget/text_input.rs | 197 | 
12 files changed, 656 insertions, 55 deletions
| diff --git a/web/src/widget.rs b/web/src/widget.rs index 30ac8eeb..b0e16692 100644 --- a/web/src/widget.rs +++ b/web/src/widget.rs @@ -14,14 +14,17 @@  //! ```  //!  //! [`Widget`]: trait.Widget.html -use crate::Bus; +use crate::{style, Bus};  use dodrio::bumpalo;  pub mod button; +pub mod scrollable;  pub mod slider; +pub mod text_input;  mod checkbox;  mod column; +mod container;  mod image;  mod radio;  mod row; @@ -29,15 +32,18 @@ mod text;  #[doc(no_inline)]  pub use button::Button; - +#[doc(no_inline)] +pub use scrollable::Scrollable;  #[doc(no_inline)]  pub use slider::Slider; -  #[doc(no_inline)]  pub use text::Text; +#[doc(no_inline)] +pub use text_input::TextInput;  pub use checkbox::Checkbox;  pub use column::Column; +pub use container::Container;  pub use image::Image;  pub use radio::Radio;  pub use row::Row; @@ -56,5 +62,6 @@ pub trait Widget<Message> {          &self,          bump: &'b bumpalo::Bump,          _bus: &Bus<Message>, +        style_sheet: &mut style::Sheet<'b>,      ) -> dodrio::Node<'b>;  } diff --git a/web/src/widget/button.rs b/web/src/widget/button.rs index 1c13f34d..c9625ff8 100644 --- a/web/src/widget/button.rs +++ b/web/src/widget/button.rs @@ -4,7 +4,7 @@  //!  //! [`Button`]: struct.Button.html  //! [`State`]: struct.State.html -use crate::{Background, Bus, Element, Length, Widget}; +use crate::{style, Background, Bus, Color, Element, Length, Style, Widget};  use dodrio::bumpalo; @@ -120,23 +120,54 @@ impl State {  impl<'a, Message> Widget<Message> for Button<'a, Message>  where -    Message: 'static + Copy, +    Message: 'static + Clone,  {      fn node<'b>(          &self,          bump: &'b bumpalo::Bump,          bus: &Bus<Message>, +        style_sheet: &mut style::Sheet<'b>,      ) -> dodrio::Node<'b> {          use dodrio::builder::*; -        let mut node = -            button(bump).children(vec![self.content.node(bump, bus)]); - -        if let Some(on_press) = self.on_press { +        let padding_class = +            style_sheet.insert(bump, Style::Padding(self.padding)); + +        let background = match self.background { +            None => String::from("none"), +            Some(background) => match background { +                Background::Color(Color { r, g, b, a }) => format!( +                    "rgba({}, {}, {}, {})", +                    255.0 * r, +                    255.0 * g, +                    255.0 * b, +                    a +                ), +            }, +        }; + +        let mut node = button(bump) +            .attr( +                "class", +                bumpalo::format!(in bump, "{}", padding_class).into_bump_str(), +            ) +            .attr( +                "style", +                bumpalo::format!( +                    in bump, +                    "background: {}; border-radius: {}px", +                    background, +                    self.border_radius +                ) +                .into_bump_str(), +            ) +            .children(vec![self.content.node(bump, bus, style_sheet)]); + +        if let Some(on_press) = self.on_press.clone() {              let event_bus = bus.clone();              node = node.on("click", move |root, vdom, _event| { -                event_bus.publish(on_press, root); +                event_bus.publish(on_press.clone(), root);                  vdom.schedule_render();              }); @@ -150,7 +181,7 @@ where  impl<'a, Message> From<Button<'a, Message>> for Element<'a, Message>  where -    Message: 'static + Copy, +    Message: 'static + Clone,  {      fn from(button: Button<'a, Message>) -> Element<'a, Message> {          Element::new(button) diff --git a/web/src/widget/checkbox.rs b/web/src/widget/checkbox.rs index 94b42554..b81a0d52 100644 --- a/web/src/widget/checkbox.rs +++ b/web/src/widget/checkbox.rs @@ -1,4 +1,4 @@ -use crate::{Bus, Color, Element, Widget}; +use crate::{style, Bus, Color, Element, Widget};  use dodrio::bumpalo; @@ -61,12 +61,13 @@ impl<Message> Checkbox<Message> {  impl<Message> Widget<Message> for Checkbox<Message>  where -    Message: 'static + Copy, +    Message: 'static + Clone,  {      fn node<'b>(          &self,          bump: &'b bumpalo::Bump,          bus: &Bus<Message>, +        _style_sheet: &mut style::Sheet<'b>,      ) -> dodrio::Node<'b> {          use dodrio::builder::*; @@ -82,7 +83,7 @@ where                      .attr("type", "checkbox")                      .bool_attr("checked", self.is_checked)                      .on("click", move |root, vdom, _event| { -                        event_bus.publish(msg, root); +                        event_bus.publish(msg.clone(), root);                          vdom.schedule_render();                      }) @@ -95,7 +96,7 @@ where  impl<'a, Message> From<Checkbox<Message>> for Element<'a, Message>  where -    Message: 'static + Copy, +    Message: 'static + Clone,  {      fn from(checkbox: Checkbox<Message>) -> Element<'a, Message> {          Element::new(checkbox) diff --git a/web/src/widget/column.rs b/web/src/widget/column.rs index ee8c14fa..cc850f5f 100644 --- a/web/src/widget/column.rs +++ b/web/src/widget/column.rs @@ -1,4 +1,4 @@ -use crate::{Align, Bus, Element, Length, Widget}; +use crate::{style, Align, Bus, Element, Length, Style, Widget};  use dodrio::bumpalo;  use std::u32; @@ -112,18 +112,42 @@ impl<'a, Message> Widget<Message> for Column<'a, Message> {          &self,          bump: &'b bumpalo::Bump,          publish: &Bus<Message>, +        style_sheet: &mut style::Sheet<'b>,      ) -> dodrio::Node<'b> {          use dodrio::builder::*;          let children: Vec<_> = self              .children              .iter() -            .map(|element| element.widget.node(bump, publish)) +            .map(|element| element.widget.node(bump, publish, style_sheet))              .collect(); +        let column_class = style_sheet.insert(bump, Style::Column); + +        let spacing_class = +            style_sheet.insert(bump, Style::Spacing(self.spacing)); + +        let padding_class = +            style_sheet.insert(bump, Style::Padding(self.padding)); + +        let width = style::length(self.width); +        let height = style::length(self.height); +          // TODO: Complete styling          div(bump) -            .attr("style", "display: flex; flex-direction: column") +            .attr( +                "class", +                bumpalo::format!(in bump, "{} {} {}", column_class, spacing_class, padding_class) +                    .into_bump_str(), +            ) +            .attr("style", bumpalo::format!( +                    in bump, +                    "width: {}; height: {}; max-width: {}px", +                    width, +                    height, +                    self.max_width +                ).into_bump_str() +            )              .children(children)              .finish()      } diff --git a/web/src/widget/container.rs b/web/src/widget/container.rs new file mode 100644 index 00000000..25e4ebf8 --- /dev/null +++ b/web/src/widget/container.rs @@ -0,0 +1,142 @@ +use crate::{bumpalo, style, Align, Bus, Element, Length, Style, Widget}; + +/// An element decorating some content. +/// +/// It is normally used for alignment purposes. +#[allow(missing_debug_implementations)] +pub struct Container<'a, Message> { +    width: Length, +    height: Length, +    max_width: u32, +    max_height: u32, +    horizontal_alignment: Align, +    vertical_alignment: Align, +    content: Element<'a, Message>, +} + +impl<'a, Message> Container<'a, Message> { +    /// Creates an empty [`Container`]. +    /// +    /// [`Container`]: struct.Container.html +    pub fn new<T>(content: T) -> Self +    where +        T: Into<Element<'a, Message>>, +    { +        use std::u32; + +        Container { +            width: Length::Shrink, +            height: Length::Shrink, +            max_width: u32::MAX, +            max_height: u32::MAX, +            horizontal_alignment: Align::Start, +            vertical_alignment: Align::Start, +            content: content.into(), +        } +    } + +    /// Sets the width of the [`Container`]. +    /// +    /// [`Container`]: struct.Container.html +    pub fn width(mut self, width: Length) -> Self { +        self.width = width; +        self +    } + +    /// Sets the height of the [`Container`]. +    /// +    /// [`Container`]: struct.Container.html +    pub fn height(mut self, height: Length) -> Self { +        self.height = height; +        self +    } + +    /// Sets the maximum width of the [`Container`]. +    /// +    /// [`Container`]: struct.Container.html +    pub fn max_width(mut self, max_width: u32) -> Self { +        self.max_width = max_width; +        self +    } + +    /// Sets the maximum height of the [`Container`] in pixels. +    /// +    /// [`Container`]: struct.Container.html +    pub fn max_height(mut self, max_height: u32) -> Self { +        self.max_height = max_height; +        self +    } + +    /// Centers the contents in the horizontal axis of the [`Container`]. +    /// +    /// [`Container`]: struct.Container.html +    pub fn center_x(mut self) -> Self { +        self.horizontal_alignment = Align::Center; + +        self +    } + +    /// Centers the contents in the vertical axis of the [`Container`]. +    /// +    /// [`Container`]: struct.Container.html +    pub fn center_y(mut self) -> Self { +        self.vertical_alignment = Align::Center; + +        self +    } +} + +impl<'a, Message> Widget<Message> for Container<'a, Message> +where +    Message: 'static, +{ +    fn node<'b>( +        &self, +        bump: &'b bumpalo::Bump, +        bus: &Bus<Message>, +        style_sheet: &mut style::Sheet<'b>, +    ) -> dodrio::Node<'b> { +        use dodrio::builder::*; + +        let column_class = style_sheet.insert(bump, Style::Column); + +        let width = style::length(self.width); +        let height = style::length(self.height); + +        let align_items = style::align(self.horizontal_alignment); +        let justify_content = style::align(self.vertical_alignment); + +        let node = div(bump) +            .attr( +                "class", +                bumpalo::format!(in bump, "{}", column_class).into_bump_str(), +            ) +            .attr( +                "style", +                bumpalo::format!( +                    in bump, +                    "width: {}; height: {}; max-width: {}px; align-items: {}; justify-content: {}", +                    width, +                    height, +                    self.max_width, +                    align_items, +                    justify_content +                ) +                .into_bump_str(), +            ) +            .children(vec![self.content.node(bump, bus, style_sheet)]); + +        // TODO: Complete styling + +        node.finish() +    } +} + +impl<'a, Message> From<Container<'a, Message>> for Element<'a, Message> +where +    Message: 'static + Clone, +{ +    fn from(container: Container<'a, Message>) -> Element<'a, Message> { +        Element::new(container) +    } +} diff --git a/web/src/widget/image.rs b/web/src/widget/image.rs index ab510bdb..ed8b7ecf 100644 --- a/web/src/widget/image.rs +++ b/web/src/widget/image.rs @@ -1,4 +1,4 @@ -use crate::{Bus, Element, Length, Widget}; +use crate::{style, Bus, Element, Length, Widget};  use dodrio::bumpalo; @@ -57,6 +57,7 @@ impl<Message> Widget<Message> for Image {          &self,          bump: &'b bumpalo::Bump,          _bus: &Bus<Message>, +        _style_sheet: &mut style::Sheet<'b>,      ) -> dodrio::Node<'b> {          use dodrio::builder::*; diff --git a/web/src/widget/radio.rs b/web/src/widget/radio.rs index 32532ebe..4e7d02b8 100644 --- a/web/src/widget/radio.rs +++ b/web/src/widget/radio.rs @@ -1,4 +1,4 @@ -use crate::{Bus, Color, Element, Widget}; +use crate::{style, Bus, Color, Element, Widget};  use dodrio::bumpalo; @@ -70,29 +70,31 @@ impl<Message> Radio<Message> {  impl<Message> Widget<Message> for Radio<Message>  where -    Message: 'static + Copy, +    Message: 'static + Clone,  {      fn node<'b>(          &self,          bump: &'b bumpalo::Bump,          bus: &Bus<Message>, +        _style_sheet: &mut style::Sheet<'b>,      ) -> dodrio::Node<'b> {          use dodrio::builder::*;          let radio_label = bumpalo::format!(in bump, "{}", self.label);          let event_bus = bus.clone(); -        let on_click = self.on_click; +        let on_click = self.on_click.clone();          // TODO: Complete styling          label(bump) -            .attr("style", "display: block") +            .attr("style", "display: block; font-size: 20px")              .children(vec![                  input(bump)                      .attr("type", "radio") +                    .attr("style", "margin-right: 10px")                      .bool_attr("checked", self.is_selected)                      .on("click", move |root, vdom, _event| { -                        event_bus.publish(on_click, root); +                        event_bus.publish(on_click.clone(), root);                          vdom.schedule_render();                      }) @@ -105,7 +107,7 @@ where  impl<'a, Message> From<Radio<Message>> for Element<'a, Message>  where -    Message: 'static + Copy, +    Message: 'static + Clone,  {      fn from(radio: Radio<Message>) -> Element<'a, Message> {          Element::new(radio) diff --git a/web/src/widget/row.rs b/web/src/widget/row.rs index b980d9b4..e47478be 100644 --- a/web/src/widget/row.rs +++ b/web/src/widget/row.rs @@ -1,4 +1,4 @@ -use crate::{Align, Bus, Element, Length, Widget}; +use crate::{style, Align, Bus, Element, Length, Style, Widget};  use dodrio::bumpalo;  use std::u32; @@ -113,18 +113,42 @@ impl<'a, Message> Widget<Message> for Row<'a, Message> {          &self,          bump: &'b bumpalo::Bump,          publish: &Bus<Message>, +        style_sheet: &mut style::Sheet<'b>,      ) -> dodrio::Node<'b> {          use dodrio::builder::*;          let children: Vec<_> = self              .children              .iter() -            .map(|element| element.widget.node(bump, publish)) +            .map(|element| element.widget.node(bump, publish, style_sheet))              .collect(); +        let row_class = style_sheet.insert(bump, Style::Row); + +        let spacing_class = +            style_sheet.insert(bump, Style::Spacing(self.spacing)); + +        let padding_class = +            style_sheet.insert(bump, Style::Padding(self.padding)); + +        let width = style::length(self.width); +        let height = style::length(self.height); +          // TODO: Complete styling          div(bump) -            .attr("style", "display: flex; flex-direction: row") +            .attr( +                "class", +                bumpalo::format!(in bump, "{} {} {}", row_class, spacing_class, padding_class) +                    .into_bump_str(), +            ) +            .attr("style", bumpalo::format!( +                    in bump, +                    "width: {}; height: {}; max-width: {}px", +                    width, +                    height, +                    self.max_width +                ).into_bump_str() +            )              .children(children)              .finish()      } diff --git a/web/src/widget/scrollable.rs b/web/src/widget/scrollable.rs new file mode 100644 index 00000000..710bb70a --- /dev/null +++ b/web/src/widget/scrollable.rs @@ -0,0 +1,157 @@ +//! Navigate an endless amount of content with a scrollbar. +use crate::{bumpalo, style, Align, Bus, Column, Element, Length, Widget}; + +/// A widget that can vertically display an infinite amount of content with a +/// scrollbar. +#[allow(missing_debug_implementations)] +pub struct Scrollable<'a, Message> { +    width: Length, +    height: Length, +    max_height: u32, +    content: Column<'a, Message>, +} + +impl<'a, Message> Scrollable<'a, Message> { +    /// Creates a new [`Scrollable`] with the given [`State`]. +    /// +    /// [`Scrollable`]: struct.Scrollable.html +    /// [`State`]: struct.State.html +    pub fn new(_state: &'a mut State) -> Self { +        use std::u32; + +        Scrollable { +            width: Length::Fill, +            height: Length::Shrink, +            max_height: u32::MAX, +            content: Column::new(), +        } +    } + +    /// 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.content = self.content.spacing(units); +        self +    } + +    /// Sets the padding of the [`Scrollable`]. +    /// +    /// [`Scrollable`]: struct.Scrollable.html +    pub fn padding(mut self, units: u16) -> Self { +        self.content = self.content.padding(units); +        self +    } + +    /// Sets the width of the [`Scrollable`]. +    /// +    /// [`Scrollable`]: struct.Scrollable.html +    pub fn width(mut self, width: Length) -> Self { +        self.width = width; +        self +    } + +    /// Sets the height of the [`Scrollable`]. +    /// +    /// [`Scrollable`]: struct.Scrollable.html +    pub fn height(mut self, height: Length) -> Self { +        self.height = height; +        self +    } + +    /// Sets the maximum width of the [`Scrollable`]. +    /// +    /// [`Scrollable`]: struct.Scrollable.html +    pub fn max_width(mut self, max_width: u32) -> Self { +        self.content = self.content.max_width(max_width); +        self +    } + +    /// Sets the maximum height of the [`Scrollable`] in pixels. +    /// +    /// [`Scrollable`]: struct.Scrollable.html +    pub fn max_height(mut self, max_height: u32) -> Self { +        self.max_height = max_height; +        self +    } + +    /// Sets the horizontal alignment of the contents of the [`Scrollable`] . +    /// +    /// [`Scrollable`]: struct.Scrollable.html +    pub fn align_items(mut self, align_items: Align) -> Self { +        self.content = self.content.align_items(align_items); +        self +    } + +    /// Adds an element to the [`Scrollable`]. +    /// +    /// [`Scrollable`]: struct.Scrollable.html +    pub fn push<E>(mut self, child: E) -> Self +    where +        E: Into<Element<'a, Message>>, +    { +        self.content = self.content.push(child); +        self +    } +} + +impl<'a, Message> Widget<Message> for Scrollable<'a, Message> +where +    Message: 'static, +{ +    fn node<'b>( +        &self, +        bump: &'b bumpalo::Bump, +        bus: &Bus<Message>, +        style_sheet: &mut style::Sheet<'b>, +    ) -> dodrio::Node<'b> { +        use dodrio::builder::*; + +        let width = style::length(self.width); +        let height = style::length(self.height); + +        let node = div(bump) +            .attr( +                "style", +                bumpalo::format!( +                    in bump, +                    "width: {}; height: {}; max-height: {}px; overflow: auto", +                    width, +                    height, +                    self.max_height +                ) +                .into_bump_str(), +            ) +            .children(vec![self.content.node(bump, bus, style_sheet)]); + +        // TODO: Complete styling + +        node.finish() +    } +} + +impl<'a, Message> From<Scrollable<'a, Message>> for Element<'a, Message> +where +    Message: 'static + Clone, +{ +    fn from(scrollable: Scrollable<'a, Message>) -> Element<'a, Message> { +        Element::new(scrollable) +    } +} + +/// The local state of a [`Scrollable`]. +/// +/// [`Scrollable`]: struct.Scrollable.html +#[derive(Debug, Clone, Copy, Default)] +pub struct State; + +impl State { +    /// Creates a new [`State`] with the scrollbar located at the top. +    /// +    /// [`State`]: struct.State.html +    pub fn new() -> Self { +        State::default() +    } +} diff --git a/web/src/widget/slider.rs b/web/src/widget/slider.rs index 16e20b82..5b203e07 100644 --- a/web/src/widget/slider.rs +++ b/web/src/widget/slider.rs @@ -4,7 +4,7 @@  //!  //! [`Slider`]: struct.Slider.html  //! [`State`]: struct.State.html -use crate::{Bus, Element, Length, Widget}; +use crate::{style, Bus, Element, Length, Widget};  use dodrio::bumpalo;  use std::{ops::RangeInclusive, rc::Rc}; @@ -82,12 +82,13 @@ impl<'a, Message> Slider<'a, Message> {  impl<'a, Message> Widget<Message> for Slider<'a, Message>  where -    Message: 'static + Copy, +    Message: 'static + Clone,  {      fn node<'b>(          &self,          bump: &'b bumpalo::Bump,          bus: &Bus<Message>, +        _style_sheet: &mut style::Sheet<'b>,      ) -> dodrio::Node<'b> {          use dodrio::builder::*;          use wasm_bindgen::JsCast; @@ -103,34 +104,33 @@ where          // TODO: Make `step` configurable          // TODO: Complete styling -        label(bump) -            .children(vec![input(bump) -                .attr("type", "range") -                .attr("step", "0.01") -                .attr("min", min.into_bump_str()) -                .attr("max", max.into_bump_str()) -                .attr("value", value.into_bump_str()) -                .on("input", move |root, vdom, event| { -                    let slider = match event.target().and_then(|t| { -                        t.dyn_into::<web_sys::HtmlInputElement>().ok() -                    }) { -                        None => return, -                        Some(slider) => slider, -                    }; +        input(bump) +            .attr("type", "range") +            .attr("step", "0.01") +            .attr("min", min.into_bump_str()) +            .attr("max", max.into_bump_str()) +            .attr("value", value.into_bump_str()) +            .attr("style", "width: 100%") +            .on("input", move |root, vdom, event| { +                let slider = match event.target().and_then(|t| { +                    t.dyn_into::<web_sys::HtmlInputElement>().ok() +                }) { +                    None => return, +                    Some(slider) => slider, +                }; -                    if let Ok(value) = slider.value().parse::<f32>() { -                        event_bus.publish(on_change(value), root); -                        vdom.schedule_render(); -                    } -                }) -                .finish()]) +                if let Ok(value) = slider.value().parse::<f32>() { +                    event_bus.publish(on_change(value), root); +                    vdom.schedule_render(); +                } +            })              .finish()      }  }  impl<'a, Message> From<Slider<'a, Message>> for Element<'a, Message>  where -    Message: 'static + Copy, +    Message: 'static + Clone,  {      fn from(slider: Slider<'a, Message>) -> Element<'a, Message> {          Element::new(slider) diff --git a/web/src/widget/text.rs b/web/src/widget/text.rs index 1183a3cd..6194a12e 100644 --- a/web/src/widget/text.rs +++ b/web/src/widget/text.rs @@ -1,6 +1,6 @@  use crate::{ -    Bus, Color, Element, Font, HorizontalAlignment, Length, VerticalAlignment, -    Widget, +    style, Bus, Color, Element, Font, HorizontalAlignment, Length, +    VerticalAlignment, Widget,  };  use dodrio::bumpalo; @@ -112,15 +112,30 @@ impl<'a, Message> Widget<Message> for Text {          &self,          bump: &'b bumpalo::Bump,          _publish: &Bus<Message>, +        _style_sheet: &mut style::Sheet<'b>,      ) -> dodrio::Node<'b> {          use dodrio::builder::*;          let content = bumpalo::format!(in bump, "{}", self.content); -        let size = bumpalo::format!(in bump, "font-size: {}px", self.size.unwrap_or(20)); +        let color = style::color(self.color.unwrap_or(Color::BLACK)); + +        let text_align = match self.horizontal_alignment { +            HorizontalAlignment::Left => "left", +            HorizontalAlignment::Center => "center", +            HorizontalAlignment::Right => "right", +        }; + +        let style = bumpalo::format!( +            in bump, +            "font-size: {}px; color: {}; text-align: {}", +            self.size.unwrap_or(20), +            color, +            text_align +        );          // TODO: Complete styling          p(bump) -            .attr("style", size.into_bump_str()) +            .attr("style", style.into_bump_str())              .children(vec![text(content.into_bump_str())])              .finish()      } diff --git a/web/src/widget/text_input.rs b/web/src/widget/text_input.rs new file mode 100644 index 00000000..d6357512 --- /dev/null +++ b/web/src/widget/text_input.rs @@ -0,0 +1,197 @@ +//! Display fields that can be filled with text. +//! +//! A [`TextInput`] has some local [`State`]. +//! +//! [`TextInput`]: struct.TextInput.html +//! [`State`]: struct.State.html +use crate::{bumpalo, style, Bus, Element, Length, Style, Widget}; +use std::rc::Rc; + +/// A field that can be filled with text. +/// +/// # Example +/// ``` +/// # use iced_web::{text_input, TextInput}; +/// # +/// enum Message { +///     TextInputChanged(String), +/// } +/// +/// let mut state = text_input::State::new(); +/// let value = "Some text"; +/// +/// let input = TextInput::new( +///     &mut state, +///     "This is the placeholder...", +///     value, +///     Message::TextInputChanged, +/// ); +/// ``` +#[allow(missing_debug_implementations)] +pub struct TextInput<'a, Message> { +    _state: &'a mut State, +    placeholder: String, +    value: String, +    width: Length, +    max_width: Length, +    padding: u16, +    size: Option<u16>, +    on_change: Rc<Box<dyn Fn(String) -> Message>>, +    on_submit: Option<Message>, +} + +impl<'a, Message> TextInput<'a, Message> { +    /// Creates a new [`TextInput`]. +    /// +    /// It expects: +    /// - some [`State`] +    /// - a placeholder +    /// - the current value +    /// - a function that produces a message when the [`TextInput`] changes +    /// +    /// [`TextInput`]: struct.TextInput.html +    /// [`State`]: struct.State.html +    pub fn new<F>( +        state: &'a mut State, +        placeholder: &str, +        value: &str, +        on_change: F, +    ) -> Self +    where +        F: 'static + Fn(String) -> Message, +    { +        Self { +            _state: state, +            placeholder: String::from(placeholder), +            value: String::from(value), +            width: Length::Fill, +            max_width: Length::Shrink, +            padding: 0, +            size: None, +            on_change: Rc::new(Box::new(on_change)), +            on_submit: None, +        } +    } + +    /// Sets the width of the [`TextInput`]. +    /// +    /// [`TextInput`]: struct.TextInput.html +    pub fn width(mut self, width: Length) -> Self { +        self.width = width; +        self +    } + +    /// Sets the maximum width of the [`TextInput`]. +    /// +    /// [`TextInput`]: struct.TextInput.html +    pub fn max_width(mut self, max_width: Length) -> Self { +        self.max_width = max_width; +        self +    } + +    /// Sets the padding of the [`TextInput`]. +    /// +    /// [`TextInput`]: struct.TextInput.html +    pub fn padding(mut self, units: u16) -> Self { +        self.padding = units; +        self +    } + +    /// Sets the text size of the [`TextInput`]. +    /// +    /// [`TextInput`]: struct.TextInput.html +    pub fn size(mut self, size: u16) -> Self { +        self.size = Some(size); +        self +    } + +    /// Sets the message that should be produced when the [`TextInput`] is +    /// focused and the enter key is pressed. +    /// +    /// [`TextInput`]: struct.TextInput.html +    pub fn on_submit(mut self, message: Message) -> Self { +        self.on_submit = Some(message); +        self +    } +} + +impl<'a, Message> Widget<Message> for TextInput<'a, Message> +where +    Message: 'static + Clone, +{ +    fn node<'b>( +        &self, +        bump: &'b bumpalo::Bump, +        bus: &Bus<Message>, +        style_sheet: &mut style::Sheet<'b>, +    ) -> dodrio::Node<'b> { +        use dodrio::builder::*; +        use wasm_bindgen::JsCast; + +        let padding_class = +            style_sheet.insert(bump, Style::Padding(self.padding)); + +        let on_change = self.on_change.clone(); +        let event_bus = bus.clone(); + +        input(bump) +            .attr( +                "class", +                bumpalo::format!(in bump, "{}", padding_class).into_bump_str(), +            ) +            .attr( +                "style", +                bumpalo::format!( +                    in bump, +                    "font-size: {}px", +                    self.size.unwrap_or(20) +                ) +                .into_bump_str(), +            ) +            .attr( +                "placeholder", +                bumpalo::format!(in bump, "{}", self.placeholder) +                    .into_bump_str(), +            ) +            .attr( +                "value", +                bumpalo::format!(in bump, "{}", self.value).into_bump_str(), +            ) +            .on("input", move |root, vdom, event| { +                let text_input = match event.target().and_then(|t| { +                    t.dyn_into::<web_sys::HtmlInputElement>().ok() +                }) { +                    None => return, +                    Some(text_input) => text_input, +                }; + +                event_bus.publish(on_change(text_input.value()), root); +                vdom.schedule_render(); +            }) +            .finish() +    } +} + +impl<'a, Message> From<TextInput<'a, Message>> for Element<'a, Message> +where +    Message: 'static + Clone, +{ +    fn from(text_input: TextInput<'a, Message>) -> Element<'a, Message> { +        Element::new(text_input) +    } +} + +/// The state of a [`TextInput`]. +/// +/// [`TextInput`]: struct.TextInput.html +#[derive(Debug, Clone, Copy, Default)] +pub struct State; + +impl State { +    /// Creates a new [`State`], representing an unfocused [`TextInput`]. +    /// +    /// [`State`]: struct.State.html +    pub fn new() -> Self { +        Self::default() +    } +} | 
