diff options
Diffstat (limited to 'web/src/widget')
-rw-r--r-- | web/src/widget/button.rs | 70 | ||||
-rw-r--r-- | web/src/widget/checkbox.rs | 63 | ||||
-rw-r--r-- | web/src/widget/column.rs | 23 | ||||
-rw-r--r-- | web/src/widget/container.rs | 44 | ||||
-rw-r--r-- | web/src/widget/image.rs | 93 | ||||
-rw-r--r-- | web/src/widget/progress_bar.rs | 124 | ||||
-rw-r--r-- | web/src/widget/radio.rs | 23 | ||||
-rw-r--r-- | web/src/widget/row.rs | 23 | ||||
-rw-r--r-- | web/src/widget/scrollable.rs | 26 | ||||
-rw-r--r-- | web/src/widget/slider.rs | 27 | ||||
-rw-r--r-- | web/src/widget/space.rs | 69 | ||||
-rw-r--r-- | web/src/widget/text.rs | 24 | ||||
-rw-r--r-- | web/src/widget/text_input.rs | 80 |
13 files changed, 553 insertions, 136 deletions
diff --git a/web/src/widget/button.rs b/web/src/widget/button.rs index 889c0ab1..3a5afe60 100644 --- a/web/src/widget/button.rs +++ b/web/src/widget/button.rs @@ -4,7 +4,9 @@ //! //! [`Button`]: struct.Button.html //! [`State`]: struct.State.html -use crate::{style, Background, Bus, Element, Length, Style, Widget}; +use crate::{css, Background, Bus, Css, Element, Length, Widget}; + +pub use iced_style::button::{Style, StyleSheet}; use dodrio::bumpalo; @@ -26,10 +28,11 @@ pub struct Button<'a, Message> { content: Element<'a, Message>, on_press: Option<Message>, width: Length, + height: Length, min_width: u32, + min_height: u32, padding: u16, - background: Option<Background>, - border_radius: u16, + style: Box<dyn StyleSheet>, } impl<'a, Message> Button<'a, Message> { @@ -46,10 +49,11 @@ impl<'a, Message> Button<'a, Message> { content: content.into(), on_press: None, width: Length::Shrink, + height: Length::Shrink, min_width: 0, - padding: 0, - background: None, - border_radius: 0, + min_height: 0, + padding: 5, + style: Default::default(), } } @@ -61,6 +65,14 @@ impl<'a, Message> Button<'a, Message> { self } + /// Sets the height of the [`Button`]. + /// + /// [`Button`]: struct.Button.html + pub fn height(mut self, height: Length) -> Self { + self.height = height; + self + } + /// Sets the minimum width of the [`Button`]. /// /// [`Button`]: struct.Button.html @@ -69,28 +81,27 @@ impl<'a, Message> Button<'a, Message> { self } - /// Sets the padding of the [`Button`]. + /// Sets the minimum height of the [`Button`]. /// /// [`Button`]: struct.Button.html - pub fn padding(mut self, padding: u16) -> Self { - self.padding = padding; + pub fn min_height(mut self, min_height: u32) -> Self { + self.min_height = min_height; self } - /// Sets the [`Background`] of the [`Button`]. + /// Sets the padding of the [`Button`]. /// /// [`Button`]: struct.Button.html - /// [`Background`]: ../../struct.Background.html - pub fn background(mut self, background: Background) -> Self { - self.background = Some(background); + pub fn padding(mut self, padding: u16) -> Self { + self.padding = padding; self } - /// Sets the border radius of the [`Button`]. + /// Sets the style of the [`Button`]. /// /// [`Button`]: struct.Button.html - pub fn border_radius(mut self, border_radius: u16) -> Self { - self.border_radius = border_radius; + pub fn style(mut self, style: impl Into<Box<dyn StyleSheet>>) -> Self { + self.style = style.into(); self } @@ -126,17 +137,20 @@ where &self, bump: &'b bumpalo::Bump, bus: &Bus<Message>, - style_sheet: &mut style::Sheet<'b>, + style_sheet: &mut Css<'b>, ) -> dodrio::Node<'b> { use dodrio::builder::*; + // TODO: State-based styling + let style = self.style.active(); + let padding_class = - style_sheet.insert(bump, Style::Padding(self.padding)); + style_sheet.insert(bump, css::Rule::Padding(self.padding)); - let background = match self.background { + let background = match style.background { None => String::from("none"), Some(background) => match background { - Background::Color(color) => style::color(color), + Background::Color(color) => css::color(color), }, }; @@ -149,10 +163,12 @@ where "style", bumpalo::format!( in bump, - "background: {}; border-radius: {}px; min-width: {}px", + "background: {}; border-radius: {}px; width:{}; min-width: {}; color: {}", background, - self.border_radius, - self.min_width + style.border_radius, + css::length(self.width), + css::min_length(self.min_width), + css::color(style.text_color) ) .into_bump_str(), ) @@ -161,15 +177,11 @@ where 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.clone(), root); - - vdom.schedule_render(); + node = node.on("click", move |_root, _vdom, _event| { + event_bus.publish(on_press.clone()); }); } - // TODO: Complete styling - node.finish() } } diff --git a/web/src/widget/checkbox.rs b/web/src/widget/checkbox.rs index b81a0d52..0657ccfb 100644 --- a/web/src/widget/checkbox.rs +++ b/web/src/widget/checkbox.rs @@ -1,6 +1,10 @@ -use crate::{style, Bus, Color, Element, Widget}; +//! Show toggle controls using checkboxes. +use crate::{css, Bus, Css, Element, Length, Widget}; + +pub use iced_style::checkbox::{Style, StyleSheet}; use dodrio::bumpalo; +use std::rc::Rc; /// A box that can be checked. /// @@ -22,9 +26,10 @@ use dodrio::bumpalo; #[allow(missing_debug_implementations)] pub struct Checkbox<Message> { is_checked: bool, - on_toggle: Box<dyn Fn(bool) -> Message>, + on_toggle: Rc<dyn Fn(bool) -> Message>, label: String, - label_color: Option<Color>, + width: Length, + style: Box<dyn StyleSheet>, } impl<Message> Checkbox<Message> { @@ -44,51 +49,77 @@ impl<Message> Checkbox<Message> { { Checkbox { is_checked, - on_toggle: Box::new(f), + on_toggle: Rc::new(f), label: String::from(label), - label_color: None, + width: Length::Shrink, + style: Default::default(), } } - /// Sets the color of the label of the [`Checkbox`]. + /// Sets the width of the [`Checkbox`]. + /// + /// [`Checkbox`]: struct.Checkbox.html + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + /// Sets the style of the [`Checkbox`]. /// /// [`Checkbox`]: struct.Checkbox.html - pub fn label_color<C: Into<Color>>(mut self, color: C) -> Self { - self.label_color = Some(color.into()); + pub fn style(mut self, style: impl Into<Box<dyn StyleSheet>>) -> Self { + self.style = style.into(); self } } impl<Message> Widget<Message> for Checkbox<Message> where - Message: 'static + Clone, + Message: 'static, { fn node<'b>( &self, bump: &'b bumpalo::Bump, bus: &Bus<Message>, - _style_sheet: &mut style::Sheet<'b>, + style_sheet: &mut Css<'b>, ) -> dodrio::Node<'b> { use dodrio::builder::*; let checkbox_label = bumpalo::format!(in bump, "{}", self.label); let event_bus = bus.clone(); - let msg = (self.on_toggle)(!self.is_checked); + let on_toggle = self.on_toggle.clone(); + let is_checked = self.is_checked; + + let row_class = style_sheet.insert(bump, css::Rule::Row); + + let spacing_class = style_sheet.insert(bump, css::Rule::Spacing(5)); - // TODO: Complete styling label(bump) + .attr( + "class", + bumpalo::format!(in bump, "{} {}", row_class, spacing_class) + .into_bump_str(), + ) + .attr( + "style", + bumpalo::format!(in bump, "width: {}; align-items: center", css::length(self.width)) + .into_bump_str(), + ) .children(vec![ + // TODO: Checkbox styling input(bump) .attr("type", "checkbox") .bool_attr("checked", self.is_checked) - .on("click", move |root, vdom, _event| { - event_bus.publish(msg.clone(), root); + .on("click", move |_root, vdom, _event| { + let msg = on_toggle(!is_checked); + event_bus.publish(msg); vdom.schedule_render(); }) .finish(), - text(checkbox_label.into_bump_str()), + span(bump).children(vec![ + text(checkbox_label.into_bump_str())]).finish(), ]) .finish() } @@ -96,7 +127,7 @@ where impl<'a, Message> From<Checkbox<Message>> for Element<'a, Message> where - Message: 'static + Clone, + Message: 'static, { 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 cc850f5f..6454ffba 100644 --- a/web/src/widget/column.rs +++ b/web/src/widget/column.rs @@ -1,4 +1,4 @@ -use crate::{style, Align, Bus, Element, Length, Style, Widget}; +use crate::{css, Align, Bus, Css, Element, Length, Widget}; use dodrio::bumpalo; use std::u32; @@ -112,7 +112,7 @@ impl<'a, Message> Widget<Message> for Column<'a, Message> { &self, bump: &'b bumpalo::Bump, publish: &Bus<Message>, - style_sheet: &mut style::Sheet<'b>, + style_sheet: &mut Css<'b>, ) -> dodrio::Node<'b> { use dodrio::builder::*; @@ -122,16 +122,13 @@ impl<'a, Message> Widget<Message> for Column<'a, Message> { .map(|element| element.widget.node(bump, publish, style_sheet)) .collect(); - let column_class = style_sheet.insert(bump, Style::Column); + let column_class = style_sheet.insert(bump, css::Rule::Column); let spacing_class = - style_sheet.insert(bump, Style::Spacing(self.spacing)); + style_sheet.insert(bump, css::Rule::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); + style_sheet.insert(bump, css::Rule::Padding(self.padding)); // TODO: Complete styling div(bump) @@ -142,10 +139,12 @@ impl<'a, Message> Widget<Message> for Column<'a, Message> { ) .attr("style", bumpalo::format!( in bump, - "width: {}; height: {}; max-width: {}px", - width, - height, - self.max_width + "width: {}; height: {}; max-width: {}; max-height: {}; align-items: {}", + css::length(self.width), + css::length(self.height), + css::max_length(self.max_width), + css::max_length(self.max_height), + css::align(self.align_items) ).into_bump_str() ) .children(children) diff --git a/web/src/widget/container.rs b/web/src/widget/container.rs index 25e4ebf8..8e4318f9 100644 --- a/web/src/widget/container.rs +++ b/web/src/widget/container.rs @@ -1,4 +1,7 @@ -use crate::{bumpalo, style, Align, Bus, Element, Length, Style, Widget}; +//! Decorate content and apply alignment. +use crate::{bumpalo, css, Align, Bus, Css, Element, Length, Widget}; + +pub use iced_style::container::{Style, StyleSheet}; /// An element decorating some content. /// @@ -11,6 +14,7 @@ pub struct Container<'a, Message> { max_height: u32, horizontal_alignment: Align, vertical_alignment: Align, + style_sheet: Box<dyn StyleSheet>, content: Element<'a, Message>, } @@ -31,6 +35,7 @@ impl<'a, Message> Container<'a, Message> { max_height: u32::MAX, horizontal_alignment: Align::Start, vertical_alignment: Align::Start, + style_sheet: Default::default(), content: content.into(), } } @@ -84,6 +89,14 @@ impl<'a, Message> Container<'a, Message> { self } + + /// Sets the style of the [`Container`]. + /// + /// [`Container`]: struct.Container.html + pub fn style(mut self, style: impl Into<Box<dyn StyleSheet>>) -> Self { + self.style_sheet = style.into(); + self + } } impl<'a, Message> Widget<Message> for Container<'a, Message> @@ -94,17 +107,13 @@ where &self, bump: &'b bumpalo::Bump, bus: &Bus<Message>, - style_sheet: &mut style::Sheet<'b>, + style_sheet: &mut Css<'b>, ) -> dodrio::Node<'b> { use dodrio::builder::*; - let column_class = style_sheet.insert(bump, Style::Column); + let column_class = style_sheet.insert(bump, css::Rule::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 style = self.style_sheet.style(); let node = div(bump) .attr( @@ -115,12 +124,17 @@ where "style", bumpalo::format!( in bump, - "width: {}; height: {}; max-width: {}px; align-items: {}; justify-content: {}", - width, - height, - self.max_width, - align_items, - justify_content + "width: {}; height: {}; max-width: {}; align-items: {}; justify-content: {}; background: {}; color: {}; border-width: {}px; border-color: {}; border-radius: {}px", + css::length(self.width), + css::length(self.height), + css::max_length(self.max_width), + css::align(self.horizontal_alignment), + css::align(self.vertical_alignment), + style.background.map(css::background).unwrap_or(String::from("initial")), + style.text_color.map(css::color).unwrap_or(String::from("inherit")), + style.border_width, + css::color(style.border_color), + style.border_radius ) .into_bump_str(), ) @@ -134,7 +148,7 @@ where impl<'a, Message> From<Container<'a, Message>> for Element<'a, Message> where - Message: 'static + Clone, + Message: 'static, { 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 ed8b7ecf..029ab352 100644 --- a/web/src/widget/image.rs +++ b/web/src/widget/image.rs @@ -1,6 +1,12 @@ -use crate::{style, Bus, Element, Length, Widget}; +//! Display images in your user interface. +use crate::{Bus, Css, Element, Hasher, Length, Widget}; use dodrio::bumpalo; +use std::{ + hash::{Hash, Hasher as _}, + path::PathBuf, + sync::Arc, +}; /// A frame that displays an image while keeping aspect ratio. /// @@ -14,7 +20,7 @@ use dodrio::bumpalo; #[derive(Debug)] pub struct Image { /// The image path - pub path: String, + pub handle: Handle, /// The width of the image pub width: Length, @@ -27,9 +33,9 @@ impl Image { /// Creates a new [`Image`] with the given path. /// /// [`Image`]: struct.Image.html - pub fn new<T: Into<String>>(path: T) -> Self { + pub fn new<T: Into<Handle>>(handle: T) -> Self { Image { - path: path.into(), + handle: handle.into(), width: Length::Shrink, height: Length::Shrink, } @@ -57,17 +63,19 @@ impl<Message> Widget<Message> for Image { &self, bump: &'b bumpalo::Bump, _bus: &Bus<Message>, - _style_sheet: &mut style::Sheet<'b>, + _style_sheet: &mut Css<'b>, ) -> dodrio::Node<'b> { use dodrio::builder::*; - let src = bumpalo::format!(in bump, "{}", self.path); + let src = bumpalo::format!(in bump, "{}", match self.handle.data.as_ref() { + Data::Path(path) => path.to_str().unwrap_or("") + }); let mut image = img(bump).attr("src", src.into_bump_str()); match self.width { Length::Shrink => {} - Length::Fill => { + Length::Fill | Length::FillPortion(_) => { image = image.attr("width", "100%"); } Length::Units(px) => { @@ -89,3 +97,74 @@ impl<'a, Message> From<Image> for Element<'a, Message> { Element::new(image) } } + +/// An [`Image`] handle. +/// +/// [`Image`]: struct.Image.html +#[derive(Debug, Clone)] +pub struct Handle { + id: u64, + data: Arc<Data>, +} + +impl Handle { + /// Creates an image [`Handle`] pointing to the image of the given path. + /// + /// [`Handle`]: struct.Handle.html + pub fn from_path<T: Into<PathBuf>>(path: T) -> Handle { + Self::from_data(Data::Path(path.into())) + } + + fn from_data(data: Data) -> Handle { + let mut hasher = Hasher::default(); + data.hash(&mut hasher); + + Handle { + id: hasher.finish(), + data: Arc::new(data), + } + } + + /// Returns the unique identifier of the [`Handle`]. + /// + /// [`Handle`]: struct.Handle.html + pub fn id(&self) -> u64 { + self.id + } + + /// Returns a reference to the image [`Data`]. + /// + /// [`Data`]: enum.Data.html + pub fn data(&self) -> &Data { + &self.data + } +} + +impl From<String> for Handle { + fn from(path: String) -> Handle { + Handle::from_path(path) + } +} + +impl From<&str> for Handle { + fn from(path: &str) -> Handle { + Handle::from_path(path) + } +} + +/// The data of an [`Image`]. +/// +/// [`Image`]: struct.Image.html +#[derive(Clone, Hash)] +pub enum Data { + /// A remote image + Path(PathBuf), +} + +impl std::fmt::Debug for Data { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Data::Path(path) => write!(f, "Path({:?})", path), + } + } +} diff --git a/web/src/widget/progress_bar.rs b/web/src/widget/progress_bar.rs new file mode 100644 index 00000000..856203c0 --- /dev/null +++ b/web/src/widget/progress_bar.rs @@ -0,0 +1,124 @@ +//! Provide progress feedback to your users. +use crate::{bumpalo, css, Bus, Css, Element, Length, Widget}; + +pub use iced_style::progress_bar::{Style, StyleSheet}; + +use std::ops::RangeInclusive; + +/// A bar that displays progress. +/// +/// # Example +/// ``` +/// use iced_web::ProgressBar; +/// +/// let value = 50.0; +/// +/// ProgressBar::new(0.0..=100.0, value); +/// ``` +/// +///  +#[allow(missing_debug_implementations)] +pub struct ProgressBar { + range: RangeInclusive<f32>, + value: f32, + width: Length, + height: Option<Length>, + style: Box<dyn StyleSheet>, +} + +impl ProgressBar { + /// Creates a new [`ProgressBar`]. + /// + /// It expects: + /// * an inclusive range of possible values + /// * the current value of the [`ProgressBar`] + /// + /// [`ProgressBar`]: struct.ProgressBar.html + pub fn new(range: RangeInclusive<f32>, value: f32) -> Self { + ProgressBar { + value: value.max(*range.start()).min(*range.end()), + range, + width: Length::Fill, + height: None, + style: Default::default(), + } + } + + /// Sets the width of the [`ProgressBar`]. + /// + /// [`ProgressBar`]: struct.ProgressBar.html + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + /// Sets the height of the [`ProgressBar`]. + /// + /// [`ProgressBar`]: struct.ProgressBar.html + pub fn height(mut self, height: Length) -> Self { + self.height = Some(height); + self + } + + /// Sets the style of the [`ProgressBar`]. + /// + /// [`ProgressBar`]: struct.ProgressBar.html + pub fn style(mut self, style: impl Into<Box<dyn StyleSheet>>) -> Self { + self.style = style.into(); + self + } +} + +impl<Message> Widget<Message> for ProgressBar { + fn node<'b>( + &self, + bump: &'b bumpalo::Bump, + _bus: &Bus<Message>, + _style_sheet: &mut Css<'b>, + ) -> dodrio::Node<'b> { + use dodrio::builder::*; + + let (range_start, range_end) = self.range.clone().into_inner(); + let amount_filled = + (self.value - range_start) / (range_end - range_start).max(1.0); + + let style = self.style.style(); + + let bar = div(bump) + .attr( + "style", + bumpalo::format!( + in bump, + "width: {}%; height: 100%; background: {}", + amount_filled * 100.0, + css::background(style.bar) + ) + .into_bump_str(), + ) + .finish(); + + let node = div(bump).attr( + "style", + bumpalo::format!( + in bump, + "width: {}; height: {}; background: {}; border-radius: {}px; overflow: hidden;", + css::length(self.width), + css::length(self.height.unwrap_or(Length::Units(30))), + css::background(style.background), + style.border_radius + ) + .into_bump_str(), + ).children(vec![bar]); + + node.finish() + } +} + +impl<'a, Message> From<ProgressBar> for Element<'a, Message> +where + Message: 'static, +{ + fn from(container: ProgressBar) -> Element<'a, Message> { + Element::new(container) + } +} diff --git a/web/src/widget/radio.rs b/web/src/widget/radio.rs index 4e7d02b8..e00e26db 100644 --- a/web/src/widget/radio.rs +++ b/web/src/widget/radio.rs @@ -1,4 +1,7 @@ -use crate::{style, Bus, Color, Element, Widget}; +//! Create choices using radio buttons. +use crate::{Bus, Css, Element, Widget}; + +pub use iced_style::radio::{Style, StyleSheet}; use dodrio::bumpalo; @@ -32,7 +35,7 @@ pub struct Radio<Message> { is_selected: bool, on_click: Message, label: String, - label_color: Option<Color>, + style: Box<dyn StyleSheet>, } impl<Message> Radio<Message> { @@ -55,15 +58,15 @@ impl<Message> Radio<Message> { is_selected: Some(value) == selected, on_click: f(value), label: String::from(label), - label_color: None, + style: Default::default(), } } - /// Sets the `Color` of the label of the [`Radio`]. + /// Sets the style of the [`Radio`] button. /// /// [`Radio`]: struct.Radio.html - pub fn label_color<C: Into<Color>>(mut self, color: C) -> Self { - self.label_color = Some(color.into()); + pub fn style(mut self, style: impl Into<Box<dyn StyleSheet>>) -> Self { + self.style = style.into(); self } } @@ -76,7 +79,7 @@ where &self, bump: &'b bumpalo::Bump, bus: &Bus<Message>, - _style_sheet: &mut style::Sheet<'b>, + _style_sheet: &mut Css<'b>, ) -> dodrio::Node<'b> { use dodrio::builder::*; @@ -93,10 +96,8 @@ where .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.clone(), root); - - vdom.schedule_render(); + .on("click", move |_root, _vdom, _event| { + event_bus.publish(on_click.clone()); }) .finish(), text(radio_label.into_bump_str()), diff --git a/web/src/widget/row.rs b/web/src/widget/row.rs index e47478be..02035113 100644 --- a/web/src/widget/row.rs +++ b/web/src/widget/row.rs @@ -1,4 +1,4 @@ -use crate::{style, Align, Bus, Element, Length, Style, Widget}; +use crate::{css, Align, Bus, Css, Element, Length, Widget}; use dodrio::bumpalo; use std::u32; @@ -113,7 +113,7 @@ impl<'a, Message> Widget<Message> for Row<'a, Message> { &self, bump: &'b bumpalo::Bump, publish: &Bus<Message>, - style_sheet: &mut style::Sheet<'b>, + style_sheet: &mut Css<'b>, ) -> dodrio::Node<'b> { use dodrio::builder::*; @@ -123,16 +123,13 @@ impl<'a, Message> Widget<Message> for Row<'a, Message> { .map(|element| element.widget.node(bump, publish, style_sheet)) .collect(); - let row_class = style_sheet.insert(bump, Style::Row); + let row_class = style_sheet.insert(bump, css::Rule::Row); let spacing_class = - style_sheet.insert(bump, Style::Spacing(self.spacing)); + style_sheet.insert(bump, css::Rule::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); + style_sheet.insert(bump, css::Rule::Padding(self.padding)); // TODO: Complete styling div(bump) @@ -143,10 +140,12 @@ impl<'a, Message> Widget<Message> for Row<'a, Message> { ) .attr("style", bumpalo::format!( in bump, - "width: {}; height: {}; max-width: {}px", - width, - height, - self.max_width + "width: {}; height: {}; max-width: {}; max-height: {}; align-items: {}", + css::length(self.width), + css::length(self.height), + css::max_length(self.max_width), + css::max_length(self.max_height), + css::align(self.align_items) ).into_bump_str() ) .children(children) diff --git a/web/src/widget/scrollable.rs b/web/src/widget/scrollable.rs index 710bb70a..07b38aad 100644 --- a/web/src/widget/scrollable.rs +++ b/web/src/widget/scrollable.rs @@ -1,5 +1,7 @@ //! Navigate an endless amount of content with a scrollbar. -use crate::{bumpalo, style, Align, Bus, Column, Element, Length, Widget}; +use crate::{bumpalo, css, Align, Bus, Column, Css, Element, Length, Widget}; + +pub use iced_style::scrollable::{Scrollbar, Scroller, StyleSheet}; /// A widget that can vertically display an infinite amount of content with a /// scrollbar. @@ -9,6 +11,7 @@ pub struct Scrollable<'a, Message> { height: Length, max_height: u32, content: Column<'a, Message>, + style: Box<dyn StyleSheet>, } impl<'a, Message> Scrollable<'a, Message> { @@ -24,6 +27,7 @@ impl<'a, Message> Scrollable<'a, Message> { height: Length::Shrink, max_height: u32::MAX, content: Column::new(), + style: Default::default(), } } @@ -85,6 +89,14 @@ impl<'a, Message> Scrollable<'a, Message> { self } + /// Sets the style of the [`Scrollable`] . + /// + /// [`Scrollable`]: struct.Scrollable.html + pub fn style(mut self, style: impl Into<Box<dyn StyleSheet>>) -> Self { + self.style = style.into(); + self + } + /// Adds an element to the [`Scrollable`]. /// /// [`Scrollable`]: struct.Scrollable.html @@ -105,12 +117,14 @@ where &self, bump: &'b bumpalo::Bump, bus: &Bus<Message>, - style_sheet: &mut style::Sheet<'b>, + style_sheet: &mut Css<'b>, ) -> dodrio::Node<'b> { use dodrio::builder::*; - let width = style::length(self.width); - let height = style::length(self.height); + let width = css::length(self.width); + let height = css::length(self.height); + + // TODO: Scrollbar styling let node = div(bump) .attr( @@ -126,15 +140,13 @@ where ) .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, + Message: 'static, { fn from(scrollable: Scrollable<'a, Message>) -> Element<'a, Message> { Element::new(scrollable) diff --git a/web/src/widget/slider.rs b/web/src/widget/slider.rs index 5b203e07..5aa6439e 100644 --- a/web/src/widget/slider.rs +++ b/web/src/widget/slider.rs @@ -4,7 +4,9 @@ //! //! [`Slider`]: struct.Slider.html //! [`State`]: struct.State.html -use crate::{style, Bus, Element, Length, Widget}; +use crate::{Bus, Css, Element, Length, Widget}; + +pub use iced_style::slider::{Handle, HandleShape, Style, StyleSheet}; use dodrio::bumpalo; use std::{ops::RangeInclusive, rc::Rc}; @@ -38,6 +40,7 @@ pub struct Slider<'a, Message> { value: f32, on_change: Rc<Box<dyn Fn(f32) -> Message>>, width: Length, + style: Box<dyn StyleSheet>, } impl<'a, Message> Slider<'a, Message> { @@ -68,6 +71,7 @@ impl<'a, Message> Slider<'a, Message> { range, on_change: Rc::new(Box::new(on_change)), width: Length::Fill, + style: Default::default(), } } @@ -78,17 +82,25 @@ impl<'a, Message> Slider<'a, Message> { self.width = width; self } + + /// Sets the style of the [`Slider`]. + /// + /// [`Slider`]: struct.Slider.html + pub fn style(mut self, style: impl Into<Box<dyn StyleSheet>>) -> Self { + self.style = style.into(); + self + } } impl<'a, Message> Widget<Message> for Slider<'a, Message> where - Message: 'static + Clone, + Message: 'static, { fn node<'b>( &self, bump: &'b bumpalo::Bump, bus: &Bus<Message>, - _style_sheet: &mut style::Sheet<'b>, + _style_sheet: &mut Css<'b>, ) -> dodrio::Node<'b> { use dodrio::builder::*; use wasm_bindgen::JsCast; @@ -103,7 +115,7 @@ where let event_bus = bus.clone(); // TODO: Make `step` configurable - // TODO: Complete styling + // TODO: Styling input(bump) .attr("type", "range") .attr("step", "0.01") @@ -111,7 +123,7 @@ where .attr("max", max.into_bump_str()) .attr("value", value.into_bump_str()) .attr("style", "width: 100%") - .on("input", move |root, vdom, event| { + .on("input", move |_root, _vdom, event| { let slider = match event.target().and_then(|t| { t.dyn_into::<web_sys::HtmlInputElement>().ok() }) { @@ -120,8 +132,7 @@ where }; if let Ok(value) = slider.value().parse::<f32>() { - event_bus.publish(on_change(value), root); - vdom.schedule_render(); + event_bus.publish(on_change(value)); } }) .finish() @@ -130,7 +141,7 @@ where impl<'a, Message> From<Slider<'a, Message>> for Element<'a, Message> where - Message: 'static + Clone, + Message: 'static, { fn from(slider: Slider<'a, Message>) -> Element<'a, Message> { Element::new(slider) diff --git a/web/src/widget/space.rs b/web/src/widget/space.rs new file mode 100644 index 00000000..4ce52595 --- /dev/null +++ b/web/src/widget/space.rs @@ -0,0 +1,69 @@ +use crate::{css, Bus, Css, Element, Length, Widget}; +use dodrio::bumpalo; + +/// An amount of empty space. +/// +/// It can be useful if you want to fill some space with nothing. +#[derive(Debug)] +pub struct Space { + width: Length, + height: Length, +} + +impl Space { + /// Creates an amount of empty [`Space`] with the given width and height. + /// + /// [`Space`]: struct.Space.html + pub fn new(width: Length, height: Length) -> Self { + Space { width, height } + } + + /// Creates an amount of horizontal [`Space`]. + /// + /// [`Space`]: struct.Space.html + pub fn with_width(width: Length) -> Self { + Space { + width, + height: Length::Shrink, + } + } + + /// Creates an amount of vertical [`Space`]. + /// + /// [`Space`]: struct.Space.html + pub fn with_height(height: Length) -> Self { + Space { + width: Length::Shrink, + height, + } + } +} + +impl<'a, Message> Widget<Message> for Space { + fn node<'b>( + &self, + bump: &'b bumpalo::Bump, + _publish: &Bus<Message>, + _css: &mut Css<'b>, + ) -> dodrio::Node<'b> { + use dodrio::builder::*; + + let width = css::length(self.width); + let height = css::length(self.height); + + let style = bumpalo::format!( + in bump, + "width: {}; height: {};", + width, + height + ); + + div(bump).attr("style", style.into_bump_str()).finish() + } +} + +impl<'a, Message> From<Space> for Element<'a, Message> { + fn from(space: Space) -> Element<'a, Message> { + Element::new(space) + } +} diff --git a/web/src/widget/text.rs b/web/src/widget/text.rs index 6194a12e..3ec565a8 100644 --- a/web/src/widget/text.rs +++ b/web/src/widget/text.rs @@ -1,5 +1,5 @@ use crate::{ - style, Bus, Color, Element, Font, HorizontalAlignment, Length, + css, Bus, Color, Css, Element, Font, HorizontalAlignment, Length, VerticalAlignment, Widget, }; use dodrio::bumpalo; @@ -36,7 +36,7 @@ impl Text { size: None, color: None, font: Font::Default, - width: Length::Fill, + width: Length::Shrink, height: Length::Shrink, horizontal_alignment: HorizontalAlignment::Left, vertical_alignment: VerticalAlignment::Top, @@ -112,12 +112,18 @@ impl<'a, Message> Widget<Message> for Text { &self, bump: &'b bumpalo::Bump, _publish: &Bus<Message>, - _style_sheet: &mut style::Sheet<'b>, + _style_sheet: &mut Css<'b>, ) -> dodrio::Node<'b> { use dodrio::builder::*; let content = bumpalo::format!(in bump, "{}", self.content); - let color = style::color(self.color.unwrap_or(Color::BLACK)); + let color = self + .color + .map(css::color) + .unwrap_or(String::from("inherit")); + + let width = css::length(self.width); + let height = css::length(self.height); let text_align = match self.horizontal_alignment { HorizontalAlignment::Left => "left", @@ -127,10 +133,16 @@ impl<'a, Message> Widget<Message> for Text { let style = bumpalo::format!( in bump, - "font-size: {}px; color: {}; text-align: {}", + "width: {}; height: {}; font-size: {}px; color: {}; text-align: {}; font-family: {}", + width, + height, self.size.unwrap_or(20), color, - text_align + text_align, + match self.font { + Font::Default => "inherit", + Font::External { name, .. } => name, + } ); // TODO: Complete styling diff --git a/web/src/widget/text_input.rs b/web/src/widget/text_input.rs index d6357512..3fa458bd 100644 --- a/web/src/widget/text_input.rs +++ b/web/src/widget/text_input.rs @@ -4,8 +4,11 @@ //! //! [`TextInput`]: struct.TextInput.html //! [`State`]: struct.State.html -use crate::{bumpalo, style, Bus, Element, Length, Style, Widget}; -use std::rc::Rc; +use crate::{bumpalo, css, Bus, Css, Element, Length, Widget}; + +pub use iced_style::text_input::{Style, StyleSheet}; + +use std::{rc::Rc, u32}; /// A field that can be filled with text. /// @@ -32,12 +35,14 @@ pub struct TextInput<'a, Message> { _state: &'a mut State, placeholder: String, value: String, + is_secure: bool, width: Length, - max_width: Length, + max_width: u32, padding: u16, size: Option<u16>, on_change: Rc<Box<dyn Fn(String) -> Message>>, on_submit: Option<Message>, + style_sheet: Box<dyn StyleSheet>, } impl<'a, Message> TextInput<'a, Message> { @@ -64,15 +69,25 @@ impl<'a, Message> TextInput<'a, Message> { _state: state, placeholder: String::from(placeholder), value: String::from(value), + is_secure: false, width: Length::Fill, - max_width: Length::Shrink, + max_width: u32::MAX, padding: 0, size: None, on_change: Rc::new(Box::new(on_change)), on_submit: None, + style_sheet: Default::default(), } } + /// Converts the [`TextInput`] into a secure password input. + /// + /// [`TextInput`]: struct.TextInput.html + pub fn password(mut self) -> Self { + self.is_secure = true; + self + } + /// Sets the width of the [`TextInput`]. /// /// [`TextInput`]: struct.TextInput.html @@ -84,7 +99,7 @@ impl<'a, Message> TextInput<'a, Message> { /// Sets the maximum width of the [`TextInput`]. /// /// [`TextInput`]: struct.TextInput.html - pub fn max_width(mut self, max_width: Length) -> Self { + pub fn max_width(mut self, max_width: u32) -> Self { self.max_width = max_width; self } @@ -113,6 +128,14 @@ impl<'a, Message> TextInput<'a, Message> { self.on_submit = Some(message); self } + + /// Sets the style of the [`TextInput`]. + /// + /// [`TextInput`]: struct.TextInput.html + pub fn style(mut self, style: impl Into<Box<dyn StyleSheet>>) -> Self { + self.style_sheet = style.into(); + self + } } impl<'a, Message> Widget<Message> for TextInput<'a, Message> @@ -123,16 +146,19 @@ where &self, bump: &'b bumpalo::Bump, bus: &Bus<Message>, - style_sheet: &mut style::Sheet<'b>, + style_sheet: &mut Css<'b>, ) -> dodrio::Node<'b> { use dodrio::builder::*; use wasm_bindgen::JsCast; let padding_class = - style_sheet.insert(bump, Style::Padding(self.padding)); + style_sheet.insert(bump, css::Rule::Padding(self.padding)); let on_change = self.on_change.clone(); - let event_bus = bus.clone(); + let on_submit = self.on_submit.clone(); + let input_event_bus = bus.clone(); + let submit_event_bus = bus.clone(); + let style = self.style_sheet.active(); input(bump) .attr( @@ -143,8 +169,15 @@ where "style", bumpalo::format!( in bump, - "font-size: {}px", - self.size.unwrap_or(20) + "width: {}; max-width: {}; font-size: {}px; background: {}; border-width: {}px; border-color: {}; border-radius: {}px; color: {}", + css::length(self.width), + css::max_length(self.max_width), + self.size.unwrap_or(20), + css::background(style.background), + style.border_width, + css::color(style.border_color), + style.border_radius, + css::color(self.style_sheet.value_color()) ) .into_bump_str(), ) @@ -157,7 +190,11 @@ where "value", bumpalo::format!(in bump, "{}", self.value).into_bump_str(), ) - .on("input", move |root, vdom, event| { + .attr( + "type", + bumpalo::format!(in bump, "{}", if self.is_secure { "password" } else { "text" }).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() }) { @@ -165,8 +202,17 @@ where Some(text_input) => text_input, }; - event_bus.publish(on_change(text_input.value()), root); - vdom.schedule_render(); + input_event_bus.publish(on_change(text_input.value())); + }) + .on("keypress", move |_root, _vdom, event| { + if let Some(on_submit) = on_submit.clone() { + let event = event.unchecked_into::<web_sys::KeyboardEvent>(); + + match event.key_code() { + 13 => { submit_event_bus.publish(on_submit); } + _ => {} + } + } }) .finish() } @@ -194,4 +240,12 @@ impl State { pub fn new() -> Self { Self::default() } + + /// Creates a new [`State`], representing a focused [`TextInput`]. + /// + /// [`State`]: struct.State.html + pub fn focused() -> Self { + // TODO + Self::default() + } } |