summaryrefslogtreecommitdiffstats
path: root/web/src
diff options
context:
space:
mode:
Diffstat (limited to 'web/src')
-rw-r--r--web/src/bus.rs32
-rw-r--r--web/src/css.rs (renamed from web/src/style.rs)85
-rw-r--r--web/src/element.rs14
-rw-r--r--web/src/hasher.rs21
-rw-r--r--web/src/lib.rs139
-rw-r--r--web/src/subscription.rs19
-rw-r--r--web/src/widget.rs16
-rw-r--r--web/src/widget/button.rs70
-rw-r--r--web/src/widget/checkbox.rs63
-rw-r--r--web/src/widget/column.rs23
-rw-r--r--web/src/widget/container.rs44
-rw-r--r--web/src/widget/image.rs93
-rw-r--r--web/src/widget/progress_bar.rs124
-rw-r--r--web/src/widget/radio.rs23
-rw-r--r--web/src/widget/row.rs23
-rw-r--r--web/src/widget/scrollable.rs26
-rw-r--r--web/src/widget/slider.rs27
-rw-r--r--web/src/widget/space.rs69
-rw-r--r--web/src/widget/text.rs24
-rw-r--r--web/src/widget/text_input.rs80
20 files changed, 767 insertions, 248 deletions
diff --git a/web/src/bus.rs b/web/src/bus.rs
index 1b650b28..c66e9659 100644
--- a/web/src/bus.rs
+++ b/web/src/bus.rs
@@ -1,5 +1,4 @@
-use crate::Instance;
-
+use iced_futures::futures::channel::mpsc;
use std::rc::Rc;
/// A publisher of messages.
@@ -8,21 +7,26 @@ use std::rc::Rc;
///
/// [`Application`]: trait.Application.html
#[allow(missing_debug_implementations)]
-#[derive(Clone)]
pub struct Bus<Message> {
- publish: Rc<Box<dyn Fn(Message, &mut dyn dodrio::RootRender)>>,
+ publish: Rc<Box<dyn Fn(Message) -> ()>>,
+}
+
+impl<Message> Clone for Bus<Message> {
+ fn clone(&self) -> Self {
+ Bus {
+ publish: self.publish.clone(),
+ }
+ }
}
impl<Message> Bus<Message>
where
- Message: 'static + Clone,
+ Message: 'static,
{
- pub(crate) fn new() -> Self {
+ pub(crate) fn new(publish: mpsc::UnboundedSender<Message>) -> Self {
Self {
- publish: Rc::new(Box::new(|message, root| {
- let app = root.unwrap_mut::<Instance<Message>>();
-
- app.update(message)
+ publish: Rc::new(Box::new(move |message| {
+ publish.unbounded_send(message).expect("Send message");
})),
}
}
@@ -30,8 +34,8 @@ where
/// Publishes a new message for the [`Application`].
///
/// [`Application`]: trait.Application.html
- pub fn publish(&self, message: Message, root: &mut dyn dodrio::RootRender) {
- (self.publish)(message, root);
+ pub fn publish(&self, message: Message) {
+ (self.publish)(message)
}
/// Creates a new [`Bus`] that applies the given function to the messages
@@ -45,9 +49,7 @@ where
let publish = self.publish.clone();
Bus {
- publish: Rc::new(Box::new(move |message, root| {
- publish(mapper(message), root)
- })),
+ publish: Rc::new(Box::new(move |message| publish(mapper(message)))),
}
}
}
diff --git a/web/src/style.rs b/web/src/css.rs
index 2fb8602a..6a307770 100644
--- a/web/src/style.rs
+++ b/web/src/css.rs
@@ -1,11 +1,11 @@
//! Style your widgets.
-use crate::{bumpalo, Align, Color, Length};
+use crate::{bumpalo, Align, Background, Color, Length};
use std::collections::BTreeMap;
-/// The style of a VDOM node.
+/// A CSS rule of a VDOM node.
#[derive(Debug)]
-pub enum Style {
+pub enum Rule {
/// Container with vertical distribution
Column,
@@ -19,16 +19,16 @@ pub enum Style {
Spacing(u16),
}
-impl Style {
+impl Rule {
/// Returns the class name of the [`Style`].
///
/// [`Style`]: enum.Style.html
pub fn class<'a>(&self) -> String {
match self {
- Style::Column => String::from("c"),
- Style::Row => String::from("r"),
- Style::Padding(padding) => format!("p-{}", padding),
- Style::Spacing(spacing) => format!("s-{}", spacing),
+ Rule::Column => String::from("c"),
+ Rule::Row => String::from("r"),
+ Rule::Padding(padding) => format!("p-{}", padding),
+ Rule::Spacing(spacing) => format!("s-{}", spacing),
}
}
@@ -39,24 +39,24 @@ impl Style {
let class = self.class();
match self {
- Style::Column => {
+ Rule::Column => {
let body = "{ display: flex; flex-direction: column; }";
bumpalo::format!(in bump, ".{} {}", class, body).into_bump_str()
}
- Style::Row => {
+ Rule::Row => {
let body = "{ display: flex; flex-direction: row; }";
bumpalo::format!(in bump, ".{} {}", class, body).into_bump_str()
}
- Style::Padding(padding) => bumpalo::format!(
+ Rule::Padding(padding) => bumpalo::format!(
in bump,
".{} {{ box-sizing: border-box; padding: {}px }}",
class,
padding
)
.into_bump_str(),
- Style::Spacing(spacing) => bumpalo::format!(
+ Rule::Spacing(spacing) => bumpalo::format!(
in bump,
".c.{} > * {{ margin-bottom: {}px }} \
.r.{} > * {{ margin-right: {}px }} \
@@ -74,34 +74,34 @@ impl Style {
}
}
-/// A sheet of styles.
+/// A cascading style sheet.
#[derive(Debug)]
-pub struct Sheet<'a> {
- styles: BTreeMap<String, &'a str>,
+pub struct Css<'a> {
+ rules: BTreeMap<String, &'a str>,
}
-impl<'a> Sheet<'a> {
+impl<'a> Css<'a> {
/// Creates an empty style [`Sheet`].
///
/// [`Sheet`]: struct.Sheet.html
pub fn new() -> Self {
- Self {
- styles: BTreeMap::new(),
+ Css {
+ rules: BTreeMap::new(),
}
}
- /// Inserts the [`Style`] in the [`Sheet`], if it was not previously
+ /// Inserts the [`rule`] in the [`Sheet`], if it was not previously
/// inserted.
///
- /// It returns the class name of the provided [`Style`].
+ /// It returns the class name of the provided [`Rule`].
///
/// [`Sheet`]: struct.Sheet.html
- /// [`Style`]: enum.Style.html
- pub fn insert(&mut self, bump: &'a bumpalo::Bump, style: Style) -> String {
- let class = style.class();
+ /// [`Rule`]: enum.Rule.html
+ pub fn insert(&mut self, bump: &'a bumpalo::Bump, rule: Rule) -> String {
+ let class = rule.class();
- if !self.styles.contains_key(&class) {
- let _ = self.styles.insert(class.clone(), style.declaration(bump));
+ if !self.rules.contains_key(&class) {
+ let _ = self.rules.insert(class.clone(), rule.declaration(bump));
}
class
@@ -119,12 +119,12 @@ impl<'a> Sheet<'a> {
declarations.push(text(
"body { height: 100%; margin: 0; padding: 0; font-family: sans-serif }",
));
- declarations.push(text("p { margin: 0 }"));
+ declarations.push(text("* { margin: 0; padding: 0 }"));
declarations.push(text(
"button { border: none; cursor: pointer; outline: none }",
));
- for declaration in self.styles.values() {
+ for declaration in self.rules.values() {
declarations.push(text(*declaration));
}
@@ -139,7 +139,27 @@ pub fn length(length: Length) -> String {
match length {
Length::Shrink => String::from("auto"),
Length::Units(px) => format!("{}px", px),
- Length::Fill => String::from("100%"),
+ Length::Fill | Length::FillPortion(_) => String::from("100%"),
+ }
+}
+
+/// Returns the style value for the given maximum length in units.
+pub fn max_length(units: u32) -> String {
+ use std::u32;
+
+ if units == u32::MAX {
+ String::from("initial")
+ } else {
+ format!("{}px", units)
+ }
+}
+
+/// Returns the style value for the given minimum length in units.
+pub fn min_length(units: u32) -> String {
+ if units == 0 {
+ String::from("initial")
+ } else {
+ format!("{}px", units)
}
}
@@ -150,6 +170,15 @@ pub fn color(Color { r, g, b, a }: Color) -> String {
format!("rgba({}, {}, {}, {})", 255.0 * r, 255.0 * g, 255.0 * b, a)
}
+/// Returns the style value for the given [`Background`].
+///
+/// [`Background`]: ../struct.Background.html
+pub fn background(background: Background) -> String {
+ match background {
+ Background::Color(c) => color(c),
+ }
+}
+
/// Returns the style value for the given [`Align`].
///
/// [`Align`]: ../enum.Align.html
diff --git a/web/src/element.rs b/web/src/element.rs
index 85fa7c34..93e73713 100644
--- a/web/src/element.rs
+++ b/web/src/element.rs
@@ -1,4 +1,4 @@
-use crate::{style, Bus, Color, Widget};
+use crate::{Bus, Color, Css, Widget};
use dodrio::bumpalo;
use std::rc::Rc;
@@ -38,8 +38,8 @@ impl<'a, Message> Element<'a, Message> {
/// [`Element`]: struct.Element.html
pub fn map<F, B>(self, f: F) -> Element<'a, B>
where
- Message: 'static + Clone,
- B: 'static + Clone,
+ Message: 'static,
+ B: 'static,
F: 'static + Fn(Message) -> B,
{
Element {
@@ -57,7 +57,7 @@ impl<'a, Message> Element<'a, Message> {
&self,
bump: &'b bumpalo::Bump,
bus: &Bus<Message>,
- style_sheet: &mut style::Sheet<'b>,
+ style_sheet: &mut Css<'b>,
) -> dodrio::Node<'b> {
self.widget.node(bump, bus, style_sheet)
}
@@ -82,14 +82,14 @@ impl<'a, A, B> Map<'a, A, B> {
impl<'a, A, B> Widget<B> for Map<'a, A, B>
where
- A: 'static + Clone,
- B: 'static + Clone,
+ A: 'static,
+ B: 'static,
{
fn node<'b>(
&self,
bump: &'b bumpalo::Bump,
bus: &Bus<B>,
- style_sheet: &mut style::Sheet<'b>,
+ style_sheet: &mut Css<'b>,
) -> dodrio::Node<'b> {
self.widget
.node(bump, &bus.map(self.mapper.clone()), style_sheet)
diff --git a/web/src/hasher.rs b/web/src/hasher.rs
new file mode 100644
index 00000000..1a28a2f9
--- /dev/null
+++ b/web/src/hasher.rs
@@ -0,0 +1,21 @@
+use std::collections::hash_map::DefaultHasher;
+
+/// The hasher used to compare subscriptions.
+#[derive(Debug)]
+pub struct Hasher(DefaultHasher);
+
+impl Default for Hasher {
+ fn default() -> Self {
+ Hasher(DefaultHasher::default())
+ }
+}
+
+impl core::hash::Hasher for Hasher {
+ fn write(&mut self, bytes: &[u8]) {
+ self.0.write(bytes)
+ }
+
+ fn finish(&self) -> u64 {
+ self.0.finish()
+ }
+}
diff --git a/web/src/lib.rs b/web/src/lib.rs
index 8239ffc9..258ad9e7 100644
--- a/web/src/lib.rs
+++ b/web/src/lib.rs
@@ -54,27 +54,37 @@
#![deny(missing_docs)]
#![deny(missing_debug_implementations)]
#![deny(unused_results)]
-#![deny(unsafe_code)]
-#![deny(rust_2018_idioms)]
+#![forbid(unsafe_code)]
+#![forbid(rust_2018_idioms)]
use dodrio::bumpalo;
use std::{cell::RefCell, rc::Rc};
mod bus;
mod element;
+mod hasher;
-pub mod style;
+pub mod css;
+pub mod subscription;
pub mod widget;
pub use bus::Bus;
+pub use css::Css;
pub use dodrio;
pub use element::Element;
+pub use hasher::Hasher;
pub use iced_core::{
- Align, Background, Color, Command, Font, HorizontalAlignment, Length,
- VerticalAlignment,
+ Align, Background, Color, Font, HorizontalAlignment, Length, Point, Size,
+ Vector, VerticalAlignment,
};
-pub use style::Style;
+pub use iced_futures::{executor, futures, Command};
+pub use subscription::Subscription;
+
+#[doc(no_inline)]
pub use widget::*;
+#[doc(no_inline)]
+pub use executor::Executor;
+
/// An interactive web application.
///
/// This trait is the main entrypoint of Iced. Once implemented, you can run
@@ -87,7 +97,15 @@ pub trait Application {
/// The type of __messages__ your [`Application`] will produce.
///
/// [`Application`]: trait.Application.html
- type Message: Clone;
+ type Message: Send;
+
+ /// The [`Executor`] that will run commands and subscriptions.
+ ///
+ /// The [`executor::WasmBindgen`] can be a good choice for the Web.
+ ///
+ /// [`Executor`]: trait.Executor.html
+ /// [`executor::Default`]: executor/struct.Default.html
+ type Executor: Executor;
/// Initializes the [`Application`].
///
@@ -130,6 +148,20 @@ pub trait Application {
/// [`Application`]: trait.Application.html
fn view(&mut self) -> Element<'_, Self::Message>;
+ /// Returns the event [`Subscription`] for the current state of the
+ /// application.
+ ///
+ /// A [`Subscription`] will be kept alive as long as you keep returning it,
+ /// and the __messages__ produced will be handled by
+ /// [`update`](#tymethod.update).
+ ///
+ /// By default, this method returns an empty [`Subscription`].
+ ///
+ /// [`Subscription`]: struct.Subscription.html
+ fn subscription(&self) -> Subscription<Self::Message> {
+ Subscription::none()
+ }
+
/// Runs the [`Application`].
///
/// [`Application`]: trait.Application.html
@@ -137,71 +169,66 @@ pub trait Application {
where
Self: 'static + Sized,
{
- let (app, command) = Self::new();
- let mut instance = Instance::new(app);
+ use futures::stream::StreamExt;
- instance.spawn(command);
+ let (app, command) = Self::new();
let window = web_sys::window().unwrap();
-
let document = window.document().unwrap();
- document.set_title(&instance.title);
-
let body = document.body().unwrap();
- let vdom = dodrio::Vdom::new(&body, instance);
- vdom.forget();
- }
-}
+ let mut title = app.title();
+ document.set_title(&title);
-#[derive(Clone)]
-struct Instance<Message> {
- title: String,
- ui: Rc<RefCell<Box<dyn Application<Message = Message>>>>,
-}
+ let (sender, receiver) =
+ iced_futures::futures::channel::mpsc::unbounded();
-impl<Message> Instance<Message>
-where
- Message: 'static + Clone,
-{
- fn new(ui: impl Application<Message = Message> + 'static) -> Self {
- Self {
- title: ui.title(),
- ui: Rc::new(RefCell::new(Box::new(ui))),
- }
- }
+ let mut runtime = iced_futures::Runtime::new(
+ Self::Executor::new().expect("Create executor"),
+ sender.clone(),
+ );
+ runtime.spawn(command);
- fn update(&mut self, message: Message) {
- let command = self.ui.borrow_mut().update(message);
- let title = self.ui.borrow().title();
+ let application = Rc::new(RefCell::new(app));
- self.spawn(command);
+ let instance = Instance {
+ application: application.clone(),
+ bus: Bus::new(sender),
+ };
- let window = web_sys::window().unwrap();
- let document = window.document().unwrap();
+ let vdom = dodrio::Vdom::new(&body, instance);
- if self.title != title {
- document.set_title(&title);
+ let event_loop = receiver.for_each(move |message| {
+ let command = application.borrow_mut().update(message);
+ let subscription = application.borrow().subscription();
+ let new_title = application.borrow().title();
- self.title = title;
- }
- }
+ runtime.spawn(command);
+ runtime.track(subscription);
+
+ if title != new_title {
+ document.set_title(&new_title);
- fn spawn(&mut self, command: Command<Message>) {
- use futures::FutureExt;
+ title = new_title;
+ }
- for future in command.futures() {
- let mut instance = self.clone();
- let future = future.map(move |message| instance.update(message));
+ vdom.weak().schedule_render();
- wasm_bindgen_futures::spawn_local(future);
- }
+ futures::future::ready(())
+ });
+
+ wasm_bindgen_futures::spawn_local(event_loop);
}
}
-impl<Message> dodrio::Render for Instance<Message>
+struct Instance<A: Application> {
+ application: Rc<RefCell<A>>,
+ bus: Bus<A::Message>,
+}
+
+impl<A> dodrio::Render for Instance<A>
where
- Message: 'static + Clone,
+ A: Application,
{
fn render<'a, 'bump>(
&'a self,
@@ -212,15 +239,15 @@ where
{
use dodrio::builder::*;
- let mut ui = self.ui.borrow_mut();
+ let mut ui = self.application.borrow_mut();
let element = ui.view();
- let mut style_sheet = style::Sheet::new();
+ let mut css = Css::new();
- let node = element.widget.node(bump, &Bus::new(), &mut style_sheet);
+ let node = element.widget.node(bump, &self.bus, &mut css);
div(bump)
.attr("style", "width: 100%; height: 100%")
- .children(vec![style_sheet.node(bump), node])
+ .children(vec![css.node(bump), node])
.finish()
}
}
diff --git a/web/src/subscription.rs b/web/src/subscription.rs
new file mode 100644
index 00000000..6b8415c0
--- /dev/null
+++ b/web/src/subscription.rs
@@ -0,0 +1,19 @@
+//! Listen to external events in your application.
+use crate::Hasher;
+
+/// A request to listen to external events.
+///
+/// Besides performing async actions on demand with [`Command`], most
+/// applications also need to listen to external events passively.
+///
+/// A [`Subscription`] is normally provided to some runtime, like a [`Command`],
+/// and it will generate events as long as the user keeps requesting it.
+///
+/// For instance, you can use a [`Subscription`] to listen to a WebSocket
+/// connection, keyboard presses, mouse events, time ticks, etc.
+///
+/// [`Command`]: ../struct.Command.html
+/// [`Subscription`]: struct.Subscription.html
+pub type Subscription<T> = iced_futures::Subscription<Hasher, (), T>;
+
+pub use iced_futures::subscription::Recipe;
diff --git a/web/src/widget.rs b/web/src/widget.rs
index b0e16692..025cf22f 100644
--- a/web/src/widget.rs
+++ b/web/src/widget.rs
@@ -14,20 +14,22 @@
//! ```
//!
//! [`Widget`]: trait.Widget.html
-use crate::{style, Bus};
+use crate::{Bus, Css};
use dodrio::bumpalo;
pub mod button;
+pub mod checkbox;
+pub mod container;
+pub mod image;
+pub mod progress_bar;
+pub mod radio;
pub mod scrollable;
pub mod slider;
pub mod text_input;
-mod checkbox;
mod column;
-mod container;
-mod image;
-mod radio;
mod row;
+mod space;
mod text;
#[doc(no_inline)]
@@ -45,8 +47,10 @@ pub use checkbox::Checkbox;
pub use column::Column;
pub use container::Container;
pub use image::Image;
+pub use progress_bar::ProgressBar;
pub use radio::Radio;
pub use row::Row;
+pub use space::Space;
/// A component that displays information and allows interaction.
///
@@ -62,6 +66,6 @@ pub trait Widget<Message> {
&self,
bump: &'b bumpalo::Bump,
_bus: &Bus<Message>,
- style_sheet: &mut style::Sheet<'b>,
+ style_sheet: &mut Css<'b>,
) -> dodrio::Node<'b>;
}
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);
+/// ```
+///
+/// ![Progress bar](https://user-images.githubusercontent.com/18618951/71662391-a316c200-2d51-11ea-9cef-52758cab85e3.png)
+#[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()
+ }
}