diff options
author | 2022-01-28 16:47:50 +0700 | |
---|---|---|
committer | 2022-01-28 21:37:16 +0700 | |
commit | 825c7749ff364cf1f7ae5cab0c25f27ed85c7d82 (patch) | |
tree | fdd7e499c343a7e3cf690d4b5aa40ba568674a3c /web/src/widget | |
parent | 1e3feee3a36f25d7e2eda231c3e6b895858952c5 (diff) | |
download | iced-825c7749ff364cf1f7ae5cab0c25f27ed85c7d82.tar.gz iced-825c7749ff364cf1f7ae5cab0c25f27ed85c7d82.tar.bz2 iced-825c7749ff364cf1f7ae5cab0c25f27ed85c7d82.zip |
Replace `iced_web` with WebGL support in `wgpu` :tada:
Diffstat (limited to 'web/src/widget')
-rw-r--r-- | web/src/widget/button.rs | 192 | ||||
-rw-r--r-- | web/src/widget/checkbox.rs | 150 | ||||
-rw-r--r-- | web/src/widget/column.rs | 148 | ||||
-rw-r--r-- | web/src/widget/container.rs | 153 | ||||
-rw-r--r-- | web/src/widget/image.rs | 186 | ||||
-rw-r--r-- | web/src/widget/progress_bar.rs | 116 | ||||
-rw-r--r-- | web/src/widget/radio.rs | 155 | ||||
-rw-r--r-- | web/src/widget/row.rs | 148 | ||||
-rw-r--r-- | web/src/widget/scrollable.rs | 152 | ||||
-rw-r--r-- | web/src/widget/slider.rs | 183 | ||||
-rw-r--r-- | web/src/widget/space.rs | 63 | ||||
-rw-r--r-- | web/src/widget/text.rs | 148 | ||||
-rw-r--r-- | web/src/widget/text_input.rs | 234 | ||||
-rw-r--r-- | web/src/widget/toggler.rs | 171 |
14 files changed, 0 insertions, 2199 deletions
diff --git a/web/src/widget/button.rs b/web/src/widget/button.rs deleted file mode 100644 index 88137607..00000000 --- a/web/src/widget/button.rs +++ /dev/null @@ -1,192 +0,0 @@ -//! Allow your users to perform actions by pressing a button. -//! -//! A [`Button`] has some local [`State`]. -use crate::{css, Background, Bus, Css, Element, Length, Padding, Widget}; - -pub use iced_style::button::{Style, StyleSheet}; - -use dodrio::bumpalo; - -/// A generic widget that produces a message when pressed. -/// -/// ``` -/// # use iced_web::{button, Button, Text}; -/// # -/// enum Message { -/// ButtonPressed, -/// } -/// -/// let mut state = button::State::new(); -/// let button = Button::new(&mut state, Text::new("Press me!")) -/// .on_press(Message::ButtonPressed); -/// ``` -/// -/// If a [`Button::on_press`] handler is not set, the resulting [`Button`] will -/// be disabled: -/// -/// ``` -/// # use iced_web::{button, Button, Text}; -/// # -/// #[derive(Clone)] -/// enum Message { -/// ButtonPressed, -/// } -/// -/// fn disabled_button(state: &mut button::State) -> Button<'_, Message> { -/// Button::new(state, Text::new("I'm disabled!")) -/// } -/// -/// fn enabled_button(state: &mut button::State) -> Button<'_, Message> { -/// disabled_button(state).on_press(Message::ButtonPressed) -/// } -/// ``` -#[allow(missing_debug_implementations)] -pub struct Button<'a, Message> { - content: Element<'a, Message>, - on_press: Option<Message>, - width: Length, - #[allow(dead_code)] - height: Length, - min_width: u32, - #[allow(dead_code)] - min_height: u32, - padding: Padding, - style: Box<dyn StyleSheet + 'a>, -} - -impl<'a, Message> Button<'a, Message> { - /// Creates a new [`Button`] with some local [`State`] and the given - /// content. - pub fn new<E>(_state: &'a mut State, content: E) -> Self - where - E: Into<Element<'a, Message>>, - { - Button { - content: content.into(), - on_press: None, - width: Length::Shrink, - height: Length::Shrink, - min_width: 0, - min_height: 0, - padding: Padding::new(5), - style: Default::default(), - } - } - - /// Sets the width of the [`Button`]. - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the height of the [`Button`]. - pub fn height(mut self, height: Length) -> Self { - self.height = height; - self - } - - /// Sets the minimum width of the [`Button`]. - pub fn min_width(mut self, min_width: u32) -> Self { - self.min_width = min_width; - self - } - - /// Sets the minimum height of the [`Button`]. - pub fn min_height(mut self, min_height: u32) -> Self { - self.min_height = min_height; - self - } - - /// Sets the [`Padding`] of the [`Button`]. - pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self { - self.padding = padding.into(); - self - } - - /// Sets the style of the [`Button`]. - pub fn style(mut self, style: impl Into<Box<dyn StyleSheet + 'a>>) -> Self { - self.style = style.into(); - self - } - - /// Sets the message that will be produced when the [`Button`] is pressed. - /// If on_press isn't set, button will be disabled. - pub fn on_press(mut self, msg: Message) -> Self { - self.on_press = Some(msg); - self - } -} - -/// The local state of a [`Button`]. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub struct State; - -impl State { - /// Creates a new [`State`]. - pub fn new() -> State { - State::default() - } -} - -impl<'a, Message> Widget<Message> for Button<'a, Message> -where - Message: 'static + Clone, -{ - fn node<'b>( - &self, - bump: &'b bumpalo::Bump, - bus: &Bus<Message>, - style_sheet: &mut Css<'b>, - ) -> dodrio::Node<'b> { - use dodrio::builder::*; - - // TODO: State-based styling - let style = self.style.active(); - - let background = match style.background { - None => String::from("none"), - Some(background) => match background { - Background::Color(color) => css::color(color), - }, - }; - - let mut node = button(bump) - .attr( - "style", - bumpalo::format!( - in bump, - "background: {}; border-radius: {}px; width:{}; \ - min-width: {}; color: {}; padding: {}", - background, - style.border_radius, - css::length(self.width), - css::min_length(self.min_width), - css::color(style.text_color), - css::padding(self.padding) - ) - .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.clone()); - }); - } else { - node = node.attr("disabled", ""); - } - - node.finish() - } -} - -impl<'a, Message> From<Button<'a, Message>> for Element<'a, Message> -where - 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 deleted file mode 100644 index 844bf862..00000000 --- a/web/src/widget/checkbox.rs +++ /dev/null @@ -1,150 +0,0 @@ -//! 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. -/// -/// # Example -/// -/// ``` -/// # use iced_web::Checkbox; -/// -/// pub enum Message { -/// CheckboxToggled(bool), -/// } -/// -/// let is_checked = true; -/// -/// Checkbox::new(is_checked, "Toggle me!", Message::CheckboxToggled); -/// ``` -/// -///  -#[allow(missing_debug_implementations)] -pub struct Checkbox<'a, Message> { - is_checked: bool, - on_toggle: Rc<dyn Fn(bool) -> Message>, - label: String, - id: Option<String>, - width: Length, - #[allow(dead_code)] - style_sheet: Box<dyn StyleSheet + 'a>, -} - -impl<'a, Message> Checkbox<'a, Message> { - /// Creates a new [`Checkbox`]. - /// - /// It expects: - /// * a boolean describing whether the [`Checkbox`] is checked or not - /// * the label of the [`Checkbox`] - /// * a function that will be called when the [`Checkbox`] is toggled. It - /// will receive the new state of the [`Checkbox`] and must produce a - /// `Message`. - pub fn new<F>(is_checked: bool, label: impl Into<String>, f: F) -> Self - where - F: 'static + Fn(bool) -> Message, - { - Checkbox { - is_checked, - on_toggle: Rc::new(f), - label: label.into(), - id: None, - width: Length::Shrink, - style_sheet: Default::default(), - } - } - - /// Sets the width of the [`Checkbox`]. - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the style of the [`Checkbox`]. - pub fn style( - mut self, - style_sheet: impl Into<Box<dyn StyleSheet + 'a>>, - ) -> Self { - self.style_sheet = style_sheet.into(); - self - } - - /// Sets the id of the [`Checkbox`]. - pub fn id(mut self, id: impl Into<String>) -> Self { - self.id = Some(id.into()); - self - } -} - -impl<'a, Message> Widget<Message> for Checkbox<'a, Message> -where - Message: 'static, -{ - fn node<'b>( - &self, - bump: &'b bumpalo::Bump, - bus: &Bus<Message>, - style_sheet: &mut Css<'b>, - ) -> dodrio::Node<'b> { - use dodrio::builder::*; - use dodrio::bumpalo::collections::String; - - let checkbox_label = - String::from_str_in(&self.label, bump).into_bump_str(); - - let event_bus = bus.clone(); - 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)); - - let (label, input) = if let Some(id) = &self.id { - let id = String::from_str_in(id, bump).into_bump_str(); - - (label(bump).attr("for", id), input(bump).attr("id", id)) - } else { - (label(bump), input(bump)) - }; - - label - .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 - .attr("type", "checkbox") - .bool_attr("checked", self.is_checked) - .on("click", move |_root, vdom, _event| { - let msg = on_toggle(!is_checked); - event_bus.publish(msg); - - vdom.schedule_render(); - }) - .finish(), - text(checkbox_label), - ]) - .finish() - } -} - -impl<'a, Message> From<Checkbox<'a, Message>> for Element<'a, Message> -where - Message: 'static, -{ - fn from(checkbox: Checkbox<'a, Message>) -> Element<'a, Message> { - Element::new(checkbox) - } -} diff --git a/web/src/widget/column.rs b/web/src/widget/column.rs deleted file mode 100644 index 30a57c41..00000000 --- a/web/src/widget/column.rs +++ /dev/null @@ -1,148 +0,0 @@ -use crate::css; -use crate::{Alignment, Bus, Css, Element, Length, Padding, Widget}; - -use dodrio::bumpalo; -use std::u32; - -/// A container that distributes its contents vertically. -/// -/// A [`Column`] will try to fill the horizontal space of its container. -#[allow(missing_debug_implementations)] -pub struct Column<'a, Message> { - spacing: u16, - padding: Padding, - width: Length, - height: Length, - max_width: u32, - max_height: u32, - align_items: Alignment, - children: Vec<Element<'a, Message>>, -} - -impl<'a, Message> Column<'a, Message> { - /// 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>>) -> Self { - Column { - spacing: 0, - padding: Padding::ZERO, - width: Length::Fill, - height: Length::Shrink, - max_width: u32::MAX, - max_height: u32::MAX, - align_items: Alignment::Start, - children, - } - } - - /// 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 - } - - /// Sets the maximum width of the [`Column`]. - pub fn max_width(mut self, max_width: u32) -> Self { - self.max_width = max_width; - self - } - - /// Sets the maximum height of the [`Column`] in pixels. - 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 [`Column`] . - pub fn align_items(mut self, align: Alignment) -> Self { - self.align_items = align; - self - } - - /// Adds an element to the [`Column`]. - pub fn push<E>(mut self, child: E) -> Self - where - E: Into<Element<'a, Message>>, - { - self.children.push(child.into()); - self - } -} - -impl<'a, Message> Widget<Message> for Column<'a, Message> { - fn node<'b>( - &self, - bump: &'b bumpalo::Bump, - publish: &Bus<Message>, - style_sheet: &mut Css<'b>, - ) -> dodrio::Node<'b> { - use dodrio::builder::*; - - let children: Vec<_> = self - .children - .iter() - .map(|element| element.widget.node(bump, publish, style_sheet)) - .collect(); - - let column_class = style_sheet.insert(bump, css::Rule::Column); - - let spacing_class = - style_sheet.insert(bump, css::Rule::Spacing(self.spacing)); - - // TODO: Complete styling - div(bump) - .attr( - "class", - bumpalo::format!(in bump, "{} {}", column_class, spacing_class) - .into_bump_str(), - ) - .attr("style", bumpalo::format!( - in bump, - "width: {}; height: {}; max-width: {}; max-height: {}; padding: {}; align-items: {}", - css::length(self.width), - css::length(self.height), - css::max_length(self.max_width), - css::max_length(self.max_height), - css::padding(self.padding), - css::alignment(self.align_items) - ).into_bump_str() - ) - .children(children) - .finish() - } -} - -impl<'a, Message> From<Column<'a, Message>> for Element<'a, Message> -where - Message: 'static, -{ - fn from(column: Column<'a, Message>) -> Element<'a, Message> { - Element::new(column) - } -} diff --git a/web/src/widget/container.rs b/web/src/widget/container.rs deleted file mode 100644 index 8e345b9a..00000000 --- a/web/src/widget/container.rs +++ /dev/null @@ -1,153 +0,0 @@ -//! Decorate content and apply alignment. -use crate::alignment::{self, Alignment}; -use crate::bumpalo; -use crate::css; -use crate::{Bus, Css, Element, Length, Padding, Widget}; - -pub use iced_style::container::{Style, StyleSheet}; - -/// An element decorating some content. -/// -/// It is normally used for alignment purposes. -#[allow(missing_debug_implementations)] -pub struct Container<'a, Message> { - padding: Padding, - width: Length, - height: Length, - max_width: u32, - #[allow(dead_code)] - max_height: u32, - horizontal_alignment: alignment::Horizontal, - vertical_alignment: alignment::Vertical, - style_sheet: Box<dyn StyleSheet + 'a>, - content: Element<'a, Message>, -} - -impl<'a, Message> Container<'a, Message> { - /// Creates an empty [`Container`]. - pub fn new<T>(content: T) -> Self - where - T: Into<Element<'a, Message>>, - { - use std::u32; - - Container { - padding: Padding::ZERO, - width: Length::Shrink, - height: Length::Shrink, - max_width: u32::MAX, - max_height: u32::MAX, - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Top, - style_sheet: Default::default(), - content: content.into(), - } - } - - /// Sets the [`Padding`] of the [`Container`]. - pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self { - self.padding = padding.into(); - self - } - - /// Sets the width of the [`Container`]. - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the height of the [`Container`]. - pub fn height(mut self, height: Length) -> Self { - self.height = height; - self - } - - /// Sets the maximum width of the [`Container`]. - 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. - 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`]. - pub fn center_x(mut self) -> Self { - self.horizontal_alignment = alignment::Horizontal::Center; - - self - } - - /// Centers the contents in the vertical axis of the [`Container`]. - pub fn center_y(mut self) -> Self { - self.vertical_alignment = alignment::Vertical::Center; - - self - } - - /// Sets the style of the [`Container`]. - pub fn style(mut self, style: impl Into<Box<dyn StyleSheet + 'a>>) -> Self { - self.style_sheet = style.into(); - 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 Css<'b>, - ) -> dodrio::Node<'b> { - use dodrio::builder::*; - - let column_class = style_sheet.insert(bump, css::Rule::Column); - - let style = self.style_sheet.style(); - - 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: {}; padding: {}; 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::padding(self.padding), - css::alignment(Alignment::from(self.horizontal_alignment)), - css::alignment(Alignment::from(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(), - ) - .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, -{ - 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 deleted file mode 100644 index 28435f4f..00000000 --- a/web/src/widget/image.rs +++ /dev/null @@ -1,186 +0,0 @@ -//! 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. -/// -/// # Example -/// -/// ``` -/// # use iced_web::Image; -/// -/// let image = Image::new("resources/ferris.png"); -/// ``` -#[derive(Debug)] -pub struct Image { - /// The image path - pub handle: Handle, - - /// The alt text of the image - pub alt: String, - - /// The width of the image - pub width: Length, - - /// The height of the image - pub height: Length, -} - -impl Image { - /// Creates a new [`Image`] with the given path. - pub fn new<T: Into<Handle>>(handle: T) -> Self { - Image { - handle: handle.into(), - alt: Default::default(), - width: Length::Shrink, - height: Length::Shrink, - } - } - - /// Sets the width of the [`Image`] boundaries. - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the height of the [`Image`] boundaries. - pub fn height(mut self, height: Length) -> Self { - self.height = height; - self - } - - /// Sets the alt text of the [`Image`]. - pub fn alt(mut self, alt: impl Into<String>) -> Self { - self.alt = alt.into(); - self - } -} - -impl<Message> Widget<Message> for Image { - fn node<'b>( - &self, - bump: &'b bumpalo::Bump, - _bus: &Bus<Message>, - _style_sheet: &mut Css<'b>, - ) -> dodrio::Node<'b> { - use dodrio::builder::*; - use dodrio::bumpalo::collections::String; - - let src = match self.handle.data.as_ref() { - Data::Path(path) => { - String::from_str_in(path.to_str().unwrap_or(""), bump) - } - Data::Bytes(bytes) => { - // The web is able to infer the kind of image, so we don't have to add a dependency on image-rs to guess the mime type. - bumpalo::format!(in bump, "data:;base64,{}", base64::encode(bytes)) - }, - } - .into_bump_str(); - - let alt = String::from_str_in(&self.alt, bump).into_bump_str(); - - let mut image = img(bump).attr("src", src).attr("alt", alt); - - match self.width { - Length::Shrink => {} - Length::Fill | Length::FillPortion(_) => { - image = image.attr("width", "100%"); - } - Length::Units(px) => { - image = image.attr( - "width", - bumpalo::format!(in bump, "{}px", px).into_bump_str(), - ); - } - } - - // TODO: Complete styling - - image.finish() - } -} - -impl<'a, Message> From<Image> for Element<'a, Message> { - fn from(image: Image) -> Element<'a, Message> { - Element::new(image) - } -} - -/// An [`Image`] handle. -#[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. - pub fn from_path<T: Into<PathBuf>>(path: T) -> Handle { - Self::from_data(Data::Path(path.into())) - } - - /// Creates an image [`Handle`] containing the image data directly. - /// - /// This is useful if you already have your image loaded in-memory, maybe - /// because you downloaded or generated it procedurally. - pub fn from_memory(bytes: Vec<u8>) -> Handle { - Self::from_data(Data::Bytes(bytes)) - } - - 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`]. - pub fn id(&self) -> u64 { - self.id - } - - /// Returns a reference to the image [`Data`]. - 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`]. -#[derive(Clone, Hash)] -pub enum Data { - /// A remote image - Path(PathBuf), - - /// In-memory data - Bytes(Vec<u8>), -} - -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), - Data::Bytes(_) => write!(f, "Bytes(...)"), - } - } -} diff --git a/web/src/widget/progress_bar.rs b/web/src/widget/progress_bar.rs deleted file mode 100644 index 01f412f8..00000000 --- a/web/src/widget/progress_bar.rs +++ /dev/null @@ -1,116 +0,0 @@ -//! 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<'a> { - range: RangeInclusive<f32>, - value: f32, - width: Length, - height: Option<Length>, - style: Box<dyn StyleSheet + 'a>, -} - -impl<'a> ProgressBar<'a> { - /// Creates a new [`ProgressBar`]. - /// - /// It expects: - /// * an inclusive range of possible values - /// * the current value of the [`ProgressBar`] - 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`]. - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the height of the [`ProgressBar`]. - pub fn height(mut self, height: Length) -> Self { - self.height = Some(height); - self - } - - /// Sets the style of the [`ProgressBar`]. - pub fn style(mut self, style: impl Into<Box<dyn StyleSheet>>) -> Self { - self.style = style.into(); - self - } -} - -impl<'a, Message> Widget<Message> for ProgressBar<'a> { - 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<'a>> for Element<'a, Message> -where - Message: 'static, -{ - fn from(container: ProgressBar<'a>) -> Element<'a, Message> { - Element::new(container) - } -} diff --git a/web/src/widget/radio.rs b/web/src/widget/radio.rs deleted file mode 100644 index 03b2922b..00000000 --- a/web/src/widget/radio.rs +++ /dev/null @@ -1,155 +0,0 @@ -//! Create choices using radio buttons. -use crate::{Bus, Css, Element, Widget}; - -pub use iced_style::radio::{Style, StyleSheet}; - -use dodrio::bumpalo; - -/// A circular button representing a choice. -/// -/// # Example -/// ``` -/// # use iced_web::Radio; -/// -/// #[derive(Debug, Clone, Copy, PartialEq, Eq)] -/// pub enum Choice { -/// A, -/// B, -/// } -/// -/// #[derive(Debug, Clone, Copy)] -/// pub enum Message { -/// RadioSelected(Choice), -/// } -/// -/// let selected_choice = Some(Choice::A); -/// -/// Radio::new(Choice::A, "This is A", selected_choice, Message::RadioSelected); -/// -/// Radio::new(Choice::B, "This is B", selected_choice, Message::RadioSelected); -/// ``` -/// -///  -#[allow(missing_debug_implementations)] -pub struct Radio<'a, Message> { - is_selected: bool, - on_click: Message, - label: String, - id: Option<String>, - name: Option<String>, - #[allow(dead_code)] - style_sheet: Box<dyn StyleSheet + 'a>, -} - -impl<'a, Message> Radio<'a, Message> { - /// Creates a new [`Radio`] button. - /// - /// It expects: - /// * the value related to the [`Radio`] button - /// * the label of the [`Radio`] button - /// * the current selected value - /// * a function that will be called when the [`Radio`] is selected. It - /// receives the value of the radio and must produce a `Message`. - pub fn new<F, V>( - value: V, - label: impl Into<String>, - selected: Option<V>, - f: F, - ) -> Self - where - V: Eq + Copy, - F: 'static + Fn(V) -> Message, - { - Radio { - is_selected: Some(value) == selected, - on_click: f(value), - label: label.into(), - id: None, - name: None, - style_sheet: Default::default(), - } - } - - /// Sets the style of the [`Radio`] button. - pub fn style( - mut self, - style_sheet: impl Into<Box<dyn StyleSheet + 'a>>, - ) -> Self { - self.style_sheet = style_sheet.into(); - self - } - - /// Sets the name attribute of the [`Radio`] button. - pub fn name(mut self, name: impl Into<String>) -> Self { - self.name = Some(name.into()); - self - } - - /// Sets the id of the [`Radio`] button. - pub fn id(mut self, id: impl Into<String>) -> Self { - self.id = Some(id.into()); - self - } -} - -impl<'a, Message> Widget<Message> for Radio<'a, Message> -where - Message: 'static + Clone, -{ - fn node<'b>( - &self, - bump: &'b bumpalo::Bump, - bus: &Bus<Message>, - _style_sheet: &mut Css<'b>, - ) -> dodrio::Node<'b> { - use dodrio::builder::*; - use dodrio::bumpalo::collections::String; - - let radio_label = - String::from_str_in(&self.label, bump).into_bump_str(); - - let event_bus = bus.clone(); - let on_click = self.on_click.clone(); - - let (label, input) = if let Some(id) = &self.id { - let id = String::from_str_in(id, bump).into_bump_str(); - - (label(bump).attr("for", id), input(bump).attr("id", id)) - } else { - (label(bump), input(bump)) - }; - - let input = if let Some(name) = &self.name { - let name = String::from_str_in(name, bump).into_bump_str(); - - dodrio::builder::input(bump).attr("name", name) - } else { - input - }; - - // TODO: Complete styling - label - .attr("style", "display: block; font-size: 20px") - .children(vec![ - input - .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()); - }) - .finish(), - text(radio_label), - ]) - .finish() - } -} - -impl<'a, Message> From<Radio<'a, Message>> for Element<'a, Message> -where - Message: 'static + Clone, -{ - fn from(radio: Radio<'a, Message>) -> Element<'a, Message> { - Element::new(radio) - } -} diff --git a/web/src/widget/row.rs b/web/src/widget/row.rs deleted file mode 100644 index 13eab27d..00000000 --- a/web/src/widget/row.rs +++ /dev/null @@ -1,148 +0,0 @@ -use crate::css; -use crate::{Alignment, Bus, Css, Element, Length, Padding, Widget}; - -use dodrio::bumpalo; -use std::u32; - -/// A container that distributes its contents horizontally. -/// -/// A [`Row`] will try to fill the horizontal space of its container. -#[allow(missing_debug_implementations)] -pub struct Row<'a, Message> { - spacing: u16, - padding: Padding, - width: Length, - height: Length, - max_width: u32, - max_height: u32, - align_items: Alignment, - children: Vec<Element<'a, Message>>, -} - -impl<'a, Message> Row<'a, Message> { - /// 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>>) -> Self { - Row { - spacing: 0, - padding: Padding::ZERO, - width: Length::Fill, - height: Length::Shrink, - max_width: u32::MAX, - max_height: u32::MAX, - align_items: Alignment::Start, - children, - } - } - - /// 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 maximum width of the [`Row`]. - pub fn max_width(mut self, max_width: u32) -> Self { - self.max_width = max_width; - self - } - - /// Sets the maximum height of the [`Row`]. - pub fn max_height(mut self, max_height: u32) -> Self { - self.max_height = max_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<E>(mut self, child: E) -> Self - where - E: Into<Element<'a, Message>>, - { - self.children.push(child.into()); - self - } -} - -impl<'a, Message> Widget<Message> for Row<'a, Message> { - fn node<'b>( - &self, - bump: &'b bumpalo::Bump, - publish: &Bus<Message>, - style_sheet: &mut Css<'b>, - ) -> dodrio::Node<'b> { - use dodrio::builder::*; - - let children: Vec<_> = self - .children - .iter() - .map(|element| element.widget.node(bump, publish, style_sheet)) - .collect(); - - let row_class = style_sheet.insert(bump, css::Rule::Row); - - let spacing_class = - style_sheet.insert(bump, css::Rule::Spacing(self.spacing)); - - // TODO: Complete styling - div(bump) - .attr( - "class", - bumpalo::format!(in bump, "{} {}", row_class, spacing_class) - .into_bump_str(), - ) - .attr("style", bumpalo::format!( - in bump, - "width: {}; height: {}; max-width: {}; max-height: {}; padding: {}; align-items: {}", - css::length(self.width), - css::length(self.height), - css::max_length(self.max_width), - css::max_length(self.max_height), - css::padding(self.padding), - css::alignment(self.align_items) - ).into_bump_str() - ) - .children(children) - .finish() - } -} - -impl<'a, Message> From<Row<'a, Message>> for Element<'a, Message> -where - Message: 'static, -{ - fn from(column: Row<'a, Message>) -> Element<'a, Message> { - Element::new(column) - } -} diff --git a/web/src/widget/scrollable.rs b/web/src/widget/scrollable.rs deleted file mode 100644 index 22cb61be..00000000 --- a/web/src/widget/scrollable.rs +++ /dev/null @@ -1,152 +0,0 @@ -//! Navigate an endless amount of content with a scrollbar. -use crate::bumpalo; -use crate::css; -use crate::{Alignment, Bus, Column, Css, Element, Length, Padding, Widget}; - -pub use iced_style::scrollable::{Scrollbar, Scroller, StyleSheet}; - -/// 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>, - #[allow(dead_code)] - style_sheet: Box<dyn StyleSheet + 'a>, -} - -impl<'a, Message> Scrollable<'a, Message> { - /// Creates a new [`Scrollable`] with the given [`State`]. - 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(), - style_sheet: Default::default(), - } - } - - /// 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`]. - pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self { - self.content = self.content.padding(padding); - self - } - - /// Sets the width of the [`Scrollable`]. - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the height of the [`Scrollable`]. - pub fn height(mut self, height: Length) -> Self { - self.height = height; - self - } - - /// Sets the maximum width of the [`Scrollable`]. - 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. - 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`] . - pub fn align_items(mut self, align_items: Alignment) -> Self { - self.content = self.content.align_items(align_items); - self - } - - /// Sets the style of the [`Scrollable`] . - pub fn style( - mut self, - style_sheet: impl Into<Box<dyn StyleSheet + 'a>>, - ) -> Self { - self.style_sheet = style_sheet.into(); - self - } - - /// Adds an element to the [`Scrollable`]. - 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 Css<'b>, - ) -> dodrio::Node<'b> { - use dodrio::builder::*; - - let width = css::length(self.width); - let height = css::length(self.height); - - // TODO: Scrollbar styling - - 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)]); - - node.finish() - } -} - -impl<'a, Message> From<Scrollable<'a, Message>> for Element<'a, Message> -where - Message: 'static, -{ - fn from(scrollable: Scrollable<'a, Message>) -> Element<'a, Message> { - Element::new(scrollable) - } -} - -/// The local state of a [`Scrollable`]. -#[derive(Debug, Clone, Copy, Default)] -pub struct State; - -impl State { - /// Creates a new [`State`] with the scrollbar located at the top. - pub fn new() -> Self { - State::default() - } -} diff --git a/web/src/widget/slider.rs b/web/src/widget/slider.rs deleted file mode 100644 index 8cbf5bd0..00000000 --- a/web/src/widget/slider.rs +++ /dev/null @@ -1,183 +0,0 @@ -//! Display an interactive selector of a single value from a range of values. -//! -//! A [`Slider`] has some local [`State`]. -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}; - -/// An horizontal bar and a handle that selects a single value from a range of -/// values. -/// -/// A [`Slider`] will try to fill the horizontal space of its container. -/// -/// The [`Slider`] range of numeric values is generic and its step size defaults -/// to 1 unit. -/// -/// # Example -/// ``` -/// # use iced_web::{slider, Slider}; -/// # -/// pub enum Message { -/// SliderChanged(f32), -/// } -/// -/// let state = &mut slider::State::new(); -/// let value = 50.0; -/// -/// Slider::new(state, 0.0..=100.0, value, Message::SliderChanged); -/// ``` -/// -///  -#[allow(missing_debug_implementations)] -pub struct Slider<'a, T, Message> { - _state: &'a mut State, - range: RangeInclusive<T>, - step: T, - value: T, - on_change: Rc<Box<dyn Fn(T) -> Message>>, - #[allow(dead_code)] - width: Length, - #[allow(dead_code)] - style_sheet: Box<dyn StyleSheet + 'a>, -} - -impl<'a, T, Message> Slider<'a, T, Message> -where - T: Copy + From<u8> + std::cmp::PartialOrd, -{ - /// Creates a new [`Slider`]. - /// - /// It expects: - /// * the local [`State`] of the [`Slider`] - /// * an inclusive range of possible values - /// * the current value of the [`Slider`] - /// * a function that will be called when the [`Slider`] is dragged. - /// It receives the new value of the [`Slider`] and must produce a - /// `Message`. - pub fn new<F>( - state: &'a mut State, - range: RangeInclusive<T>, - value: T, - on_change: F, - ) -> Self - where - F: 'static + Fn(T) -> Message, - { - let value = if value >= *range.start() { - value - } else { - *range.start() - }; - - let value = if value <= *range.end() { - value - } else { - *range.end() - }; - - Slider { - _state: state, - value, - range, - step: T::from(1), - on_change: Rc::new(Box::new(on_change)), - width: Length::Fill, - style_sheet: Default::default(), - } - } - - /// Sets the width of the [`Slider`]. - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the style of the [`Slider`]. - pub fn style( - mut self, - style_sheet: impl Into<Box<dyn StyleSheet + 'a>>, - ) -> Self { - self.style_sheet = style_sheet.into(); - self - } - - /// Sets the step size of the [`Slider`]. - pub fn step(mut self, step: T) -> Self { - self.step = step; - self - } -} - -impl<'a, T, Message> Widget<Message> for Slider<'a, T, Message> -where - T: 'static + Copy + Into<f64> + num_traits::FromPrimitive, - Message: 'static, -{ - fn node<'b>( - &self, - bump: &'b bumpalo::Bump, - bus: &Bus<Message>, - _style_sheet: &mut Css<'b>, - ) -> dodrio::Node<'b> { - use dodrio::builder::*; - use wasm_bindgen::JsCast; - - let (start, end) = self.range.clone().into_inner(); - - let min = bumpalo::format!(in bump, "{}", start.into()); - let max = bumpalo::format!(in bump, "{}", end.into()); - let value = bumpalo::format!(in bump, "{}", self.value.into()); - let step = bumpalo::format!(in bump, "{}", self.step.into()); - - let on_change = self.on_change.clone(); - let event_bus = bus.clone(); - - // TODO: Styling - input(bump) - .attr("type", "range") - .attr("step", step.into_bump_str()) - .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::<f64>() { - if let Some(value) = T::from_f64(value) { - event_bus.publish(on_change(value)); - } - } - }) - .finish() - } -} - -impl<'a, T, Message> From<Slider<'a, T, Message>> for Element<'a, Message> -where - T: 'static + Copy + Into<f64> + num_traits::FromPrimitive, - Message: 'static, -{ - fn from(slider: Slider<'a, T, Message>) -> Element<'a, Message> { - Element::new(slider) - } -} - -/// The local state of a [`Slider`]. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub struct State; - -impl State { - /// Creates a new [`State`]. - pub fn new() -> Self { - Self - } -} diff --git a/web/src/widget/space.rs b/web/src/widget/space.rs deleted file mode 100644 index a8571fdb..00000000 --- a/web/src/widget/space.rs +++ /dev/null @@ -1,63 +0,0 @@ -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. - pub fn new(width: Length, height: Length) -> Self { - Space { width, height } - } - - /// Creates an amount of horizontal [`Space`]. - pub fn with_width(width: Length) -> Self { - Space { - width, - height: Length::Shrink, - } - } - - /// Creates an amount of vertical [`Space`]. - 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 deleted file mode 100644 index 53d57bfd..00000000 --- a/web/src/widget/text.rs +++ /dev/null @@ -1,148 +0,0 @@ -use crate::alignment; -use crate::css; -use crate::{Bus, Color, Css, Element, Font, Length, Widget}; -use dodrio::bumpalo; - -/// A paragraph of text. -/// -/// # Example -/// -/// ``` -/// # use iced_web::Text; -/// -/// Text::new("I <3 iced!") -/// .size(40); -/// ``` -#[derive(Debug, Clone)] -pub struct Text { - content: String, - size: Option<u16>, - color: Option<Color>, - font: Font, - width: Length, - height: Length, - horizontal_alignment: alignment::Horizontal, - vertical_alignment: alignment::Vertical, -} - -impl Text { - /// Create a new fragment of [`Text`] with the given contents. - pub fn new<T: Into<String>>(label: T) -> Self { - Text { - content: label.into(), - size: None, - color: None, - font: Font::Default, - width: Length::Shrink, - height: Length::Shrink, - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Top, - } - } - - /// Sets the size of the [`Text`]. - pub fn size(mut self, size: u16) -> Self { - self.size = Some(size); - self - } - - /// Sets the [`Color`] of the [`Text`]. - pub fn color<C: Into<Color>>(mut self, color: C) -> Self { - self.color = Some(color.into()); - self - } - - /// Sets the [`Font`] of the [`Text`]. - pub fn font(mut self, font: Font) -> Self { - self.font = font; - self - } - - /// Sets the width of the [`Text`] boundaries. - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the height of the [`Text`] boundaries. - pub fn height(mut self, height: Length) -> Self { - self.height = height; - self - } - - /// Sets the [`HorizontalAlignment`] of the [`Text`]. - pub fn horizontal_alignment( - mut self, - alignment: alignment::Horizontal, - ) -> Self { - self.horizontal_alignment = alignment; - self - } - - /// Sets the [`VerticalAlignment`] of the [`Text`]. - pub fn vertical_alignment( - mut self, - alignment: alignment::Vertical, - ) -> Self { - self.vertical_alignment = alignment; - self - } -} - -impl<'a, Message> Widget<Message> for Text { - fn node<'b>( - &self, - bump: &'b bumpalo::Bump, - _publish: &Bus<Message>, - _style_sheet: &mut Css<'b>, - ) -> dodrio::Node<'b> { - use dodrio::builder::*; - - let content = { - use dodrio::bumpalo::collections::String; - - String::from_str_in(&self.content, bump) - }; - - 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 { - alignment::Horizontal::Left => "left", - alignment::Horizontal::Center => "center", - alignment::Horizontal::Right => "right", - }; - - let style = bumpalo::format!( - in bump, - "width: {}; height: {}; font-size: {}px; color: {}; \ - text-align: {}; font-family: {}", - width, - height, - self.size.unwrap_or(20), - color, - text_align, - match self.font { - Font::Default => "inherit", - Font::External { name, .. } => name, - } - ); - - // TODO: Complete styling - p(bump) - .attr("style", style.into_bump_str()) - .children(vec![text(content.into_bump_str())]) - .finish() - } -} - -impl<'a, Message> From<Text> for Element<'a, Message> { - fn from(text: Text) -> Element<'a, Message> { - Element::new(text) - } -} diff --git a/web/src/widget/text_input.rs b/web/src/widget/text_input.rs deleted file mode 100644 index c5874485..00000000 --- a/web/src/widget/text_input.rs +++ /dev/null @@ -1,234 +0,0 @@ -//! Display fields that can be filled with text. -//! -//! A [`TextInput`] has some local [`State`]. -use crate::{bumpalo, css, Bus, Css, Element, Length, Padding, Widget}; - -pub use iced_style::text_input::{Style, StyleSheet}; - -use std::{rc::Rc, u32}; - -/// 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, - is_secure: bool, - width: Length, - max_width: u32, - padding: Padding, - size: Option<u16>, - on_change: Rc<Box<dyn Fn(String) -> Message>>, - on_submit: Option<Message>, - style_sheet: Box<dyn StyleSheet + 'a>, -} - -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 - 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), - is_secure: false, - width: Length::Fill, - max_width: u32::MAX, - padding: Padding::ZERO, - 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. - pub fn password(mut self) -> Self { - self.is_secure = true; - self - } - - /// Sets the width of the [`TextInput`]. - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the maximum width of the [`TextInput`]. - pub fn max_width(mut self, max_width: u32) -> Self { - self.max_width = max_width; - self - } - - /// Sets the [`Padding`] of the [`TextInput`]. - pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self { - self.padding = padding.into(); - self - } - - /// Sets the text size of the [`TextInput`]. - 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. - pub fn on_submit(mut self, message: Message) -> Self { - self.on_submit = Some(message); - self - } - - /// Sets the style of the [`TextInput`]. - pub fn style( - mut self, - style_sheet: impl Into<Box<dyn StyleSheet + 'a>>, - ) -> Self { - self.style_sheet = style_sheet.into(); - 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 Css<'b>, - ) -> dodrio::Node<'b> { - use dodrio::builder::*; - use wasm_bindgen::JsCast; - - let placeholder = { - use dodrio::bumpalo::collections::String; - - String::from_str_in(&self.placeholder, bump).into_bump_str() - }; - - let value = { - use dodrio::bumpalo::collections::String; - - String::from_str_in(&self.value, bump).into_bump_str() - }; - - let on_change = self.on_change.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( - "style", - bumpalo::format!( - in bump, - "width: {}; max-width: {}; padding: {}; font-size: {}px; \ - background: {}; border-width: {}px; border-color: {}; \ - border-radius: {}px; color: {}", - css::length(self.width), - css::max_length(self.max_width), - css::padding(self.padding), - 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(), - ) - .attr("placeholder", placeholder) - .attr("value", value) - .attr("type", if self.is_secure { "password" } else { "text" }) - .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, - }; - - 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() - } -} - -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`]. -#[derive(Debug, Clone, Copy, Default)] -pub struct State; - -impl State { - /// Creates a new [`State`], representing an unfocused [`TextInput`]. - pub fn new() -> Self { - Self::default() - } - - /// Creates a new [`State`], representing a focused [`TextInput`]. - pub fn focused() -> Self { - // TODO - Self::default() - } - - /// Selects all the content of the [`TextInput`]. - pub fn select_all(&mut self) { - // TODO - } -} diff --git a/web/src/widget/toggler.rs b/web/src/widget/toggler.rs deleted file mode 100644 index 0a198079..00000000 --- a/web/src/widget/toggler.rs +++ /dev/null @@ -1,171 +0,0 @@ -//! Show toggle controls using togglers. -use crate::{css, Bus, Css, Element, Length, Widget}; - -pub use iced_style::toggler::{Style, StyleSheet}; - -use dodrio::bumpalo; -use std::rc::Rc; - -/// A toggler that can be toggled. -/// -/// # Example -/// -/// ``` -/// # use iced_web::Toggler; -/// -/// pub enum Message { -/// TogglerToggled(bool), -/// } -/// -/// let is_active = true; -/// -/// Toggler::new(is_active, String::from("Toggle me!"), Message::TogglerToggled); -/// ``` -/// -#[allow(missing_debug_implementations)] -pub struct Toggler<Message> { - is_active: bool, - on_toggle: Rc<dyn Fn(bool) -> Message>, - label: Option<String>, - id: Option<String>, - width: Length, - style: Box<dyn StyleSheet>, -} - -impl<Message> Toggler<Message> { - /// Creates a new [`Toggler`]. - /// - /// It expects: - /// * a boolean describing whether the [`Toggler`] is active or not - /// * An optional label for the [`Toggler`] - /// * a function that will be called when the [`Toggler`] is toggled. It - /// will receive the new state of the [`Toggler`] and must produce a - /// `Message`. - /// - /// [`Toggler`]: struct.Toggler.html - pub fn new<F>( - is_active: bool, - label: impl Into<Option<String>>, - f: F, - ) -> Self - where - F: 'static + Fn(bool) -> Message, - { - Toggler { - is_active, - on_toggle: Rc::new(f), - label: label.into(), - id: None, - width: Length::Shrink, - style: Default::default(), - } - } - - /// Sets the width of the [`Toggler`]. - /// - /// [`Toggler`]: struct.Toggler.html - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the style of the [`Toggler`]. - /// - /// [`Toggler`]: struct.Toggler.html - pub fn style(mut self, style: impl Into<Box<dyn StyleSheet>>) -> Self { - self.style = style.into(); - self - } - - /// Sets the id of the [`Toggler`]. - /// - /// [`Toggler`]: struct.Toggler.html - pub fn id(mut self, id: impl Into<String>) -> Self { - self.id = Some(id.into()); - self - } -} - -impl<Message> Widget<Message> for Toggler<Message> -where - Message: 'static, -{ - fn node<'b>( - &self, - bump: &'b bumpalo::Bump, - bus: &Bus<Message>, - style_sheet: &mut Css<'b>, - ) -> dodrio::Node<'b> { - use dodrio::builder::*; - use dodrio::bumpalo::collections::String; - - let toggler_label = &self - .label - .as_ref() - .map(|label| String::from_str_in(&label, bump).into_bump_str()); - - let event_bus = bus.clone(); - let on_toggle = self.on_toggle.clone(); - let is_active = self.is_active; - - let row_class = style_sheet.insert(bump, css::Rule::Row); - let toggler_class = style_sheet.insert(bump, css::Rule::Toggler(16)); - - let (label, input) = if let Some(id) = &self.id { - let id = String::from_str_in(id, bump).into_bump_str(); - - (label(bump).attr("for", id), input(bump).attr("id", id)) - } else { - (label(bump), input(bump)) - }; - - let checkbox = input - .attr("type", "checkbox") - .bool_attr("checked", self.is_active) - .on("click", move |_root, vdom, _event| { - let msg = on_toggle(!is_active); - event_bus.publish(msg); - - vdom.schedule_render(); - }) - .finish(); - - let toggler = span(bump).children(vec![span(bump).finish()]).finish(); - - label - .attr( - "class", - bumpalo::format!(in bump, "{} {}", row_class, toggler_class) - .into_bump_str(), - ) - .attr( - "style", - bumpalo::format!(in bump, "width: {}; align-items: center", css::length(self.width)) - .into_bump_str() - ) - .children( - if let Some(label) = toggler_label { - vec![ - text(label), - checkbox, - toggler, - ] - } else { - vec![ - checkbox, - toggler, - ] - } - ) - .finish() - } -} - -impl<'a, Message> From<Toggler<Message>> for Element<'a, Message> -where - Message: 'static, -{ - fn from(toggler: Toggler<Message>) -> Element<'a, Message> { - Element::new(toggler) - } -} |