diff options
author | 2019-11-23 20:23:38 +0100 | |
---|---|---|
committer | 2019-11-23 20:23:38 +0100 | |
commit | d0f79d2779d00752eef78cd98b6904cd888d59e3 (patch) | |
tree | 436e757d7b24ae9791dc554d341b38d6646285d3 /web/src/widget | |
parent | 3a678561f2da92e089390ee79bd4f9efc2c1a8c7 (diff) | |
download | iced-d0f79d2779d00752eef78cd98b6904cd888d59e3.tar.gz iced-d0f79d2779d00752eef78cd98b6904cd888d59e3.tar.bz2 iced-d0f79d2779d00752eef78cd98b6904cd888d59e3.zip |
Make `tour` work with `iced_web` again :tada:
- Implements `TextInput`, `Scrollable`, and `Container`
- Adds basic style generation
Diffstat (limited to 'web/src/widget')
-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 |
11 files changed, 646 insertions, 52 deletions
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() + } +} |