From a97401aed2a173260a4abfdb65a77975ce6c0f01 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 14 Sep 2019 19:16:06 +0200 Subject: Rethink workspace structure --- web/Cargo.toml | 27 ++ web/examples/tour/Cargo.toml | 16 ++ web/examples/tour/index.html | 13 + web/examples/tour/src/lib.rs | 8 + web/examples/tour/src/tour.rs | 578 ++++++++++++++++++++++++++++++++++++++++++ web/src/lib.rs | 1 + 6 files changed, 643 insertions(+) create mode 100644 web/Cargo.toml create mode 100644 web/examples/tour/Cargo.toml create mode 100644 web/examples/tour/index.html create mode 100644 web/examples/tour/src/lib.rs create mode 100644 web/examples/tour/src/tour.rs create mode 100644 web/src/lib.rs (limited to 'web') diff --git a/web/Cargo.toml b/web/Cargo.toml new file mode 100644 index 00000000..acc5f18b --- /dev/null +++ b/web/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "iced_web" +version = "0.1.0-alpha" +authors = ["Héctor Ramón Jiménez "] +edition = "2018" +description = "A web backend for Iced" +license = "MIT" +repository = "https://github.com/hecrj/iced" +documentation = "https://docs.rs/iced_web" +readme = "README.md" +keywords = ["gui", "ui", "web", "interface", "widgets"] +categories = ["web-programming"] + +[badges] +maintenance = { status = "actively-developed" } + +[dependencies] +iced = { version = "0.1.0-alpha", path = ".." } +dodrio = "0.1.0" + +[dependencies.web-sys] +version = "0.3.27" +features = [ + "console", + "Document", + "HtmlElement", +] diff --git a/web/examples/tour/Cargo.toml b/web/examples/tour/Cargo.toml new file mode 100644 index 00000000..15f38fa7 --- /dev/null +++ b/web/examples/tour/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "iced_web_tour" +version = "0.0.0" +authors = ["Héctor Ramón Jiménez "] +edition = "2018" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +iced_web = { path = "../.." } +wasm-bindgen = "0.2.50" +log = "0.4" +console_error_panic_hook = "0.1.6" +console_log = "0.1.2" diff --git a/web/examples/tour/index.html b/web/examples/tour/index.html new file mode 100644 index 00000000..a639a6cb --- /dev/null +++ b/web/examples/tour/index.html @@ -0,0 +1,13 @@ + + + + + Tour - Iced Web + + + + + diff --git a/web/examples/tour/src/lib.rs b/web/examples/tour/src/lib.rs new file mode 100644 index 00000000..e747a193 --- /dev/null +++ b/web/examples/tour/src/lib.rs @@ -0,0 +1,8 @@ +use wasm_bindgen::prelude::*; + +#[wasm_bindgen(start)] +pub fn run() { + console_error_panic_hook::set_once(); + console_log::init_with_level(log::Level::Trace) + .expect("Initialize logging"); +} diff --git a/web/examples/tour/src/tour.rs b/web/examples/tour/src/tour.rs new file mode 100644 index 00000000..d0be99b0 --- /dev/null +++ b/web/examples/tour/src/tour.rs @@ -0,0 +1,578 @@ +use super::widget::{ + button, slider, Button, Checkbox, Column, Element, Image, Radio, Row, + Slider, Text, +}; + +use ggez::graphics::{self, Color, FilterMode, BLACK}; +use ggez::Context; +use iced::{text::HorizontalAlignment, Align}; + +pub struct Tour { + steps: Steps, + back_button: button::State, + next_button: button::State, + debug: bool, +} + +impl Tour { + pub fn new(context: &mut Context) -> Tour { + Tour { + steps: Steps::new(context), + back_button: button::State::new(), + next_button: button::State::new(), + debug: false, + } + } + + pub fn update(&mut self, event: Message) { + match event { + Message::BackPressed => { + self.steps.go_back(); + } + Message::NextPressed => { + self.steps.advance(); + } + Message::StepMessage(step_msg) => { + self.steps.update(step_msg, &mut self.debug); + } + } + } + + pub fn view(&mut self) -> Element { + let Tour { + steps, + back_button, + next_button, + .. + } = self; + + let mut controls = Row::new(); + + if steps.has_previous() { + controls = controls.push( + Button::new(back_button, "Back") + .on_press(Message::BackPressed) + .class(button::Class::Secondary), + ); + } + + controls = controls.push(Column::new()); + + if steps.can_continue() { + controls = controls.push( + Button::new(next_button, "Next").on_press(Message::NextPressed), + ); + } + + let element: Element<_> = Column::new() + .max_width(500) + .spacing(20) + .push(steps.view(self.debug).map(Message::StepMessage)) + .push(controls) + .into(); + + if self.debug { + element.explain(BLACK) + } else { + element + } + } +} + +#[derive(Debug, Clone, Copy)] +pub enum Message { + BackPressed, + NextPressed, + StepMessage(StepMessage), +} + +struct Steps { + steps: Vec, + current: usize, +} + +impl Steps { + fn new(context: &mut Context) -> Steps { + Steps { + steps: vec![ + Step::Welcome, + Step::Slider { + state: slider::State::new(), + value: 50, + }, + Step::RowsAndColumns { + layout: Layout::Row, + spacing_slider: slider::State::new(), + spacing: 20, + }, + Step::Text { + size_slider: slider::State::new(), + size: 30, + color_sliders: [slider::State::new(); 3], + color: BLACK, + }, + Step::Radio { selection: None }, + Step::Image { + ferris: { + let mut image = + graphics::Image::new(context, "/ferris.png") + .expect("Load ferris image"); + + image.set_filter(FilterMode::Linear); + + image + }, + width: 300, + slider: slider::State::new(), + }, + Step::Debugger, + Step::End, + ], + current: 0, + } + } + + fn update(&mut self, msg: StepMessage, debug: &mut bool) { + self.steps[self.current].update(msg, debug); + } + + fn view(&mut self, debug: bool) -> Element { + self.steps[self.current].view(debug) + } + + fn advance(&mut self) { + if self.can_continue() { + self.current += 1; + } + } + + fn go_back(&mut self) { + if self.has_previous() { + self.current -= 1; + } + } + + fn has_previous(&self) -> bool { + self.current > 0 + } + + fn can_continue(&self) -> bool { + self.current + 1 < self.steps.len() + && self.steps[self.current].can_continue() + } +} + +enum Step { + Welcome, + Slider { + state: slider::State, + value: u16, + }, + RowsAndColumns { + layout: Layout, + spacing_slider: slider::State, + spacing: u16, + }, + Text { + size_slider: slider::State, + size: u16, + color_sliders: [slider::State; 3], + color: Color, + }, + Radio { + selection: Option, + }, + Image { + ferris: graphics::Image, + width: u16, + slider: slider::State, + }, + Debugger, + End, +} + +#[derive(Debug, Clone, Copy)] +pub enum StepMessage { + SliderChanged(f32), + LayoutChanged(Layout), + SpacingChanged(f32), + TextSizeChanged(f32), + TextColorChanged(Color), + LanguageSelected(Language), + ImageWidthChanged(f32), + DebugToggled(bool), +} + +impl<'a> Step { + fn update(&mut self, msg: StepMessage, debug: &mut bool) { + match msg { + StepMessage::DebugToggled(value) => { + if let Step::Debugger = self { + *debug = value; + } + } + StepMessage::LanguageSelected(language) => { + if let Step::Radio { selection } = self { + *selection = Some(language); + } + } + StepMessage::SliderChanged(new_value) => { + if let Step::Slider { value, .. } = self { + *value = new_value.round() as u16; + } + } + StepMessage::TextSizeChanged(new_size) => { + if let Step::Text { size, .. } = self { + *size = new_size.round() as u16; + } + } + StepMessage::TextColorChanged(new_color) => { + if let Step::Text { color, .. } = self { + *color = new_color; + } + } + StepMessage::LayoutChanged(new_layout) => { + if let Step::RowsAndColumns { layout, .. } = self { + *layout = new_layout; + } + } + StepMessage::SpacingChanged(new_spacing) => { + if let Step::RowsAndColumns { spacing, .. } = self { + *spacing = new_spacing.round() as u16; + } + } + StepMessage::ImageWidthChanged(new_width) => { + if let Step::Image { width, .. } = self { + *width = new_width.round() as u16; + } + } + }; + } + + fn can_continue(&self) -> bool { + match self { + Step::Welcome => true, + Step::Radio { selection } => *selection == Some(Language::Rust), + Step::Slider { .. } => true, + Step::Text { .. } => true, + Step::Image { .. } => true, + Step::RowsAndColumns { .. } => true, + Step::Debugger => true, + Step::End => false, + } + } + + fn view(&mut self, debug: bool) -> Element { + match self { + Step::Welcome => Self::welcome().into(), + Step::Radio { selection } => Self::radio(*selection).into(), + Step::Slider { state, value } => Self::slider(state, *value).into(), + Step::Text { + size_slider, + size, + color_sliders, + color, + } => Self::text(size_slider, *size, color_sliders, *color).into(), + Step::Image { + ferris, + width, + slider, + } => Self::image(ferris.clone(), *width, slider).into(), + Step::RowsAndColumns { + layout, + spacing_slider, + spacing, + } => { + Self::rows_and_columns(*layout, spacing_slider, *spacing).into() + } + Step::Debugger => Self::debugger(debug).into(), + Step::End => Self::end().into(), + } + } + + fn container(title: &str) -> Column<'a, StepMessage> { + Column::new() + .spacing(20) + .align_items(Align::Stretch) + .push(Text::new(title).size(50)) + } + + fn welcome() -> Column<'a, StepMessage> { + Self::container("Welcome!") + .push(Text::new( + "This a simple tour meant to showcase a bunch of widgets that \ + can be easily implemented on top of Iced.", + )) + .push(Text::new( + "Iced is a renderer-agnostic GUI library for Rust focused on \ + simplicity and type-safety. It is heavily inspired by Elm.", + )) + .push(Text::new( + "It was originally born as part of Coffee, an opinionated \ + 2D game engine for Rust.", + )) + .push(Text::new( + "Iced does not provide a built-in renderer. This example runs \ + on a fairly simple renderer built on top of ggez, another \ + game library.", + )) + .push(Text::new( + "You will need to interact with the UI in order to reach the \ + end!", + )) + } + + fn slider( + state: &'a mut slider::State, + value: u16, + ) -> Column<'a, StepMessage> { + Self::container("Slider") + .push(Text::new( + "A slider allows you to smoothly select a value from a range \ + of values.", + )) + .push(Text::new( + "The following slider lets you choose an integer from \ + 0 to 100:", + )) + .push(Slider::new( + state, + 0.0..=100.0, + value as f32, + StepMessage::SliderChanged, + )) + .push( + Text::new(&value.to_string()) + .horizontal_alignment(HorizontalAlignment::Center), + ) + } + + fn rows_and_columns( + layout: Layout, + spacing_slider: &'a mut slider::State, + spacing: u16, + ) -> Column<'a, StepMessage> { + let row_radio = Radio::new( + Layout::Row, + "Row", + Some(layout), + StepMessage::LayoutChanged, + ); + + let column_radio = Radio::new( + Layout::Column, + "Column", + Some(layout), + StepMessage::LayoutChanged, + ); + + let layout_section: Element<_> = match layout { + Layout::Row => Row::new() + .spacing(spacing) + .push(row_radio) + .push(column_radio) + .into(), + Layout::Column => Column::new() + .spacing(spacing) + .push(row_radio) + .push(column_radio) + .into(), + }; + + let spacing_section = Column::new() + .spacing(10) + .push(Slider::new( + spacing_slider, + 0.0..=80.0, + spacing as f32, + StepMessage::SpacingChanged, + )) + .push( + Text::new(&format!("{} px", spacing)) + .horizontal_alignment(HorizontalAlignment::Center), + ); + + Self::container("Rows and columns") + .spacing(spacing) + .push(Text::new( + "Iced uses a layout model based on flexbox to position UI \ + elements.", + )) + .push(Text::new( + "Rows and columns can be used to distribute content \ + horizontally or vertically, respectively.", + )) + .push(layout_section) + .push(Text::new( + "You can also easily change the spacing between elements:", + )) + .push(spacing_section) + } + + fn text( + size_slider: &'a mut slider::State, + size: u16, + color_sliders: &'a mut [slider::State; 3], + color: Color, + ) -> Column<'a, StepMessage> { + let size_section = Column::new() + .padding(20) + .spacing(20) + .push(Text::new("You can change its size:")) + .push( + Text::new(&format!("This text is {} pixels", size)).size(size), + ) + .push(Slider::new( + size_slider, + 10.0..=70.0, + size as f32, + StepMessage::TextSizeChanged, + )); + + let [red, green, blue] = color_sliders; + let color_section = Column::new() + .padding(20) + .spacing(20) + .push(Text::new("And its color:")) + .push(Text::new(&format!("{:?}", color)).color(color)) + .push( + Row::new() + .spacing(10) + .push(Slider::new(red, 0.0..=1.0, color.r, move |r| { + StepMessage::TextColorChanged(Color { r, ..color }) + })) + .push(Slider::new(green, 0.0..=1.0, color.g, move |g| { + StepMessage::TextColorChanged(Color { g, ..color }) + })) + .push(Slider::new(blue, 0.0..=1.0, color.b, move |b| { + StepMessage::TextColorChanged(Color { b, ..color }) + })), + ); + + Self::container("Text") + .push(Text::new( + "Text is probably the most essential widget for your UI. \ + It will try to adapt to the dimensions of its container.", + )) + .push(size_section) + .push(color_section) + } + + fn radio(selection: Option) -> Column<'a, StepMessage> { + let question = Column::new() + .padding(20) + .spacing(10) + .push(Text::new("Iced is written in...").size(24)) + .push(Language::all().iter().cloned().fold( + Column::new().padding(10).spacing(20), + |choices, language| { + choices.push(Radio::new( + language, + language.into(), + selection, + StepMessage::LanguageSelected, + )) + }, + )); + + Self::container("Radio button") + .push(Text::new( + "A radio button is normally used to represent a choice... \ + Surprise test!", + )) + .push(question) + .push(Text::new( + "Iced works very well with iterators! The list above is \ + basically created by folding a column over the different \ + choices, creating a radio button for each one of them!", + )) + } + + fn image( + ferris: graphics::Image, + width: u16, + slider: &'a mut slider::State, + ) -> Column<'a, StepMessage> { + Self::container("Image") + .push(Text::new("An image that tries to keep its aspect ratio.")) + .push(Image::new(ferris).width(width).align_self(Align::Center)) + .push(Slider::new( + slider, + 100.0..=500.0, + width as f32, + StepMessage::ImageWidthChanged, + )) + .push( + Text::new(&format!("Width: {} px", width.to_string())) + .horizontal_alignment(HorizontalAlignment::Center), + ) + } + + fn debugger(debug: bool) -> Column<'a, StepMessage> { + Self::container("Debugger") + .push(Text::new( + "You can ask Iced to visually explain the layouting of the \ + different elements comprising your UI!", + )) + .push(Text::new( + "Give it a shot! Check the following checkbox to be able to \ + see element boundaries.", + )) + .push(Checkbox::new( + debug, + "Explain layout", + StepMessage::DebugToggled, + )) + .push(Text::new("Feel free to go back and take a look.")) + } + + fn end() -> Column<'a, StepMessage> { + Self::container("You reached the end!") + .push(Text::new( + "This tour will be updated as more features are added.", + )) + .push(Text::new("Make sure to keep an eye on it!")) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Language { + Rust, + Elm, + Ruby, + Haskell, + C, + Other, +} + +impl Language { + fn all() -> [Language; 6] { + [ + Language::C, + Language::Elm, + Language::Ruby, + Language::Haskell, + Language::Rust, + Language::Other, + ] + } +} + +impl From for &str { + fn from(language: Language) -> &'static str { + match language { + Language::Rust => "Rust", + Language::Elm => "Elm", + Language::Ruby => "Ruby", + Language::Haskell => "Haskell", + Language::C => "C", + Language::Other => "Other", + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Layout { + Row, + Column, +} diff --git a/web/src/lib.rs b/web/src/lib.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/web/src/lib.rs @@ -0,0 +1 @@ + -- cgit From 27ac85a9d98474904c422a891e54888376dec00a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 14 Sep 2019 20:54:50 +0200 Subject: Draft web runtime and widgets --- web/Cargo.toml | 1 + web/examples/tour/Cargo.toml | 1 + web/examples/tour/src/lib.rs | 27 ++++++++++++++++++++++++ web/examples/tour/src/tour.rs | 43 +++++++++++++------------------------- web/src/color.rs | 16 +++++++++++++++ web/src/element.rs | 47 ++++++++++++++++++++++++++++++++++++++++++ web/src/lib.rs | 27 ++++++++++++++++++++++++ web/src/widget.rs | 20 ++++++++++++++++++ web/src/widget/button.rs | 16 +++++++++++++++ web/src/widget/checkbox.rs | 14 +++++++++++++ web/src/widget/column.rs | 48 +++++++++++++++++++++++++++++++++++++++++++ web/src/widget/image.rs | 11 ++++++++++ web/src/widget/radio.rs | 14 +++++++++++++ web/src/widget/row.rs | 36 ++++++++++++++++++++++++++++++++ web/src/widget/slider.rs | 16 +++++++++++++++ web/src/widget/text.rs | 13 ++++++++++++ 16 files changed, 321 insertions(+), 29 deletions(-) create mode 100644 web/src/color.rs create mode 100644 web/src/element.rs create mode 100644 web/src/widget.rs create mode 100644 web/src/widget/button.rs create mode 100644 web/src/widget/checkbox.rs create mode 100644 web/src/widget/column.rs create mode 100644 web/src/widget/image.rs create mode 100644 web/src/widget/radio.rs create mode 100644 web/src/widget/row.rs create mode 100644 web/src/widget/slider.rs create mode 100644 web/src/widget/text.rs (limited to 'web') diff --git a/web/Cargo.toml b/web/Cargo.toml index acc5f18b..6d8c37b1 100644 --- a/web/Cargo.toml +++ b/web/Cargo.toml @@ -17,6 +17,7 @@ maintenance = { status = "actively-developed" } [dependencies] iced = { version = "0.1.0-alpha", path = ".." } dodrio = "0.1.0" +futures = "0.1" [dependencies.web-sys] version = "0.3.27" diff --git a/web/examples/tour/Cargo.toml b/web/examples/tour/Cargo.toml index 15f38fa7..c2db46ec 100644 --- a/web/examples/tour/Cargo.toml +++ b/web/examples/tour/Cargo.toml @@ -11,6 +11,7 @@ crate-type = ["cdylib"] [dependencies] iced_web = { path = "../.." } wasm-bindgen = "0.2.50" +futures = "0.1" log = "0.4" console_error_panic_hook = "0.1.6" console_log = "0.1.2" diff --git a/web/examples/tour/src/lib.rs b/web/examples/tour/src/lib.rs index e747a193..30855e8b 100644 --- a/web/examples/tour/src/lib.rs +++ b/web/examples/tour/src/lib.rs @@ -1,8 +1,35 @@ +use futures::{future, Future}; +use iced_web::UserInterface; use wasm_bindgen::prelude::*; +mod tour; + +use tour::Tour; + #[wasm_bindgen(start)] pub fn run() { console_error_panic_hook::set_once(); console_log::init_with_level(log::Level::Trace) .expect("Initialize logging"); + + let tour = Tour::new(); + + tour.run(); +} + +impl iced_web::UserInterface for Tour { + type Message = tour::Message; + + fn update( + &mut self, + message: tour::Message, + ) -> Box> { + self.update(message); + + Box::new(future::err(())) + } + + fn view(&mut self) -> iced_web::Element { + self.view() + } } diff --git a/web/examples/tour/src/tour.rs b/web/examples/tour/src/tour.rs index d0be99b0..6c24622e 100644 --- a/web/examples/tour/src/tour.rs +++ b/web/examples/tour/src/tour.rs @@ -1,12 +1,8 @@ -use super::widget::{ - button, slider, Button, Checkbox, Column, Element, Image, Radio, Row, - Slider, Text, +use iced_web::{ + button, slider, text::HorizontalAlignment, Align, Button, Checkbox, Color, + Column, Element, Image, Radio, Row, Slider, Text, }; -use ggez::graphics::{self, Color, FilterMode, BLACK}; -use ggez::Context; -use iced::{text::HorizontalAlignment, Align}; - pub struct Tour { steps: Steps, back_button: button::State, @@ -15,9 +11,9 @@ pub struct Tour { } impl Tour { - pub fn new(context: &mut Context) -> Tour { + pub fn new() -> Tour { Tour { - steps: Steps::new(context), + steps: Steps::new(), back_button: button::State::new(), next_button: button::State::new(), debug: false, @@ -72,7 +68,7 @@ impl Tour { .into(); if self.debug { - element.explain(BLACK) + element.explain(Color::BLACK) } else { element } @@ -92,7 +88,7 @@ struct Steps { } impl Steps { - fn new(context: &mut Context) -> Steps { + fn new() -> Steps { Steps { steps: vec![ Step::Welcome, @@ -109,19 +105,10 @@ impl Steps { size_slider: slider::State::new(), size: 30, color_sliders: [slider::State::new(); 3], - color: BLACK, + color: Color::BLACK, }, Step::Radio { selection: None }, Step::Image { - ferris: { - let mut image = - graphics::Image::new(context, "/ferris.png") - .expect("Load ferris image"); - - image.set_filter(FilterMode::Linear); - - image - }, width: 300, slider: slider::State::new(), }, @@ -183,7 +170,6 @@ enum Step { selection: Option, }, Image { - ferris: graphics::Image, width: u16, slider: slider::State, }, @@ -273,11 +259,7 @@ impl<'a> Step { color_sliders, color, } => Self::text(size_slider, *size, color_sliders, *color).into(), - Step::Image { - ferris, - width, - slider, - } => Self::image(ferris.clone(), *width, slider).into(), + Step::Image { width, slider } => Self::image(*width, slider).into(), Step::RowsAndColumns { layout, spacing_slider, @@ -489,13 +471,16 @@ impl<'a> Step { } fn image( - ferris: graphics::Image, width: u16, slider: &'a mut slider::State, ) -> Column<'a, StepMessage> { Self::container("Image") .push(Text::new("An image that tries to keep its aspect ratio.")) - .push(Image::new(ferris).width(width).align_self(Align::Center)) + .push( + Image::new("resources/ferris.png") + .width(width) + .align_self(Align::Center), + ) .push(Slider::new( slider, 100.0..=500.0, diff --git a/web/src/color.rs b/web/src/color.rs new file mode 100644 index 00000000..2624c3c9 --- /dev/null +++ b/web/src/color.rs @@ -0,0 +1,16 @@ +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Color { + pub r: f32, + pub g: f32, + pub b: f32, + pub a: f32, +} + +impl Color { + pub const BLACK: Color = Color { + r: 0.0, + g: 0.0, + b: 0.0, + a: 1.0, + }; +} diff --git a/web/src/element.rs b/web/src/element.rs new file mode 100644 index 00000000..c0c9ce5d --- /dev/null +++ b/web/src/element.rs @@ -0,0 +1,47 @@ +use crate::{Color, Widget}; + +pub struct Element<'a, Message> { + pub(crate) widget: Box + 'a>, +} + +impl<'a, Message> Element<'a, Message> { + pub fn new(widget: impl Widget + 'a) -> Self { + Self { + widget: Box::new(widget), + } + } + + pub fn explain(self, color: Color) -> Element<'a, Message> { + self + } + + pub fn map(self, f: F) -> Element<'a, B> + where + Message: 'static, + B: 'static, + F: 'static + Fn(Message) -> B, + { + Element { + widget: Box::new(Map::new(self.widget, f)), + } + } +} + +struct Map<'a, A, B> { + widget: Box + 'a>, + mapper: Box B>, +} + +impl<'a, A, B> Map<'a, A, B> { + pub fn new(widget: Box + 'a>, mapper: F) -> Map<'a, A, B> + where + F: 'static + Fn(A) -> B, + { + Map { + widget, + mapper: Box::new(mapper), + } + } +} + +impl<'a, A, B> Widget for Map<'a, A, B> {} diff --git a/web/src/lib.rs b/web/src/lib.rs index 8b137891..187e8ad9 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -1 +1,28 @@ +use futures::Future; +mod color; +mod element; +mod widget; + +pub use color::Color; +pub use element::Element; +pub use iced::Align; +pub use widget::*; + +pub trait UserInterface { + type Message; + + fn update( + &mut self, + message: Self::Message, + ) -> Box>; + + fn view(&mut self) -> Element; + + fn run(mut self) + where + Self: Sized, + { + let element = self.view(); + } +} diff --git a/web/src/widget.rs b/web/src/widget.rs new file mode 100644 index 00000000..5cb89a60 --- /dev/null +++ b/web/src/widget.rs @@ -0,0 +1,20 @@ +pub mod button; +pub mod slider; +pub mod text; + +mod checkbox; +mod column; +mod image; +mod radio; +mod row; + +pub use button::Button; +pub use checkbox::Checkbox; +pub use column::Column; +pub use image::Image; +pub use radio::Radio; +pub use row::Row; +pub use slider::Slider; +pub use text::Text; + +pub trait Widget {} diff --git a/web/src/widget/button.rs b/web/src/widget/button.rs new file mode 100644 index 00000000..1f117d82 --- /dev/null +++ b/web/src/widget/button.rs @@ -0,0 +1,16 @@ +use crate::{Element, Widget}; + +pub use iced::button::{Class, State}; + +pub type Button<'a, Message> = iced::Button<'a, Message>; + +impl<'a, Message> Widget for Button<'a, Message> {} + +impl<'a, Message> From> for Element<'a, Message> +where + Message: 'static, +{ + 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 new file mode 100644 index 00000000..a231d801 --- /dev/null +++ b/web/src/widget/checkbox.rs @@ -0,0 +1,14 @@ +use crate::{Color, Element, Widget}; + +pub type Checkbox = iced::Checkbox; + +impl Widget for Checkbox {} + +impl<'a, Message> From> for Element<'a, Message> +where + Message: 'static, +{ + fn from(checkbox: Checkbox) -> Element<'a, Message> { + Element::new(checkbox) + } +} diff --git a/web/src/widget/column.rs b/web/src/widget/column.rs new file mode 100644 index 00000000..84068ce6 --- /dev/null +++ b/web/src/widget/column.rs @@ -0,0 +1,48 @@ +use crate::{Align, Element, Widget}; + +pub struct Column<'a, Message> { + children: Vec>, +} + +impl<'a, Message> Column<'a, Message> { + pub fn new() -> Self { + Self { + children: Vec::new(), + } + } + + pub fn spacing(self, _spacing: u16) -> Self { + self + } + + pub fn padding(self, _padding: u16) -> Self { + self + } + + pub fn max_width(self, _max_width: u16) -> Self { + self + } + + pub fn align_items(self, _align: Align) -> Self { + self + } + + pub fn push(mut self, element: E) -> Self + where + E: Into>, + { + self.children.push(element.into()); + self + } +} + +impl<'a, Message> Widget for Column<'a, Message> {} + +impl<'a, Message> From> 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/image.rs b/web/src/widget/image.rs new file mode 100644 index 00000000..ac144fd8 --- /dev/null +++ b/web/src/widget/image.rs @@ -0,0 +1,11 @@ +use crate::{Element, Widget}; + +pub type Image<'a> = iced::Image<&'a str>; + +impl<'a, Message> Widget for Image<'a> {} + +impl<'a, Message> From> for Element<'a, Message> { + fn from(image: Image<'a>) -> Element<'a, Message> { + Element::new(image) + } +} diff --git a/web/src/widget/radio.rs b/web/src/widget/radio.rs new file mode 100644 index 00000000..0c28b46f --- /dev/null +++ b/web/src/widget/radio.rs @@ -0,0 +1,14 @@ +use crate::{Color, Element, Widget}; + +pub type Radio = iced::Radio; + +impl Widget for Radio {} + +impl<'a, Message> From> for Element<'a, Message> +where + Message: 'static, +{ + fn from(radio: Radio) -> Element<'a, Message> { + Element::new(radio) + } +} diff --git a/web/src/widget/row.rs b/web/src/widget/row.rs new file mode 100644 index 00000000..fc474ec3 --- /dev/null +++ b/web/src/widget/row.rs @@ -0,0 +1,36 @@ +use crate::{Element, Widget}; + +pub struct Row<'a, Message> { + children: Vec>, +} + +impl<'a, Message> Row<'a, Message> { + pub fn new() -> Self { + Self { + children: Vec::new(), + } + } + + pub fn spacing(self, _spacing: u16) -> Self { + self + } + + pub fn push(mut self, element: E) -> Self + where + E: Into>, + { + self.children.push(element.into()); + self + } +} + +impl<'a, Message> Widget for Row<'a, Message> {} + +impl<'a, Message> From> 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/slider.rs b/web/src/widget/slider.rs new file mode 100644 index 00000000..9c83befb --- /dev/null +++ b/web/src/widget/slider.rs @@ -0,0 +1,16 @@ +use crate::{Element, Widget}; + +pub use iced::slider::State; + +pub type Slider<'a, Message> = iced::Slider<'a, Message>; + +impl<'a, Message> Widget for Slider<'a, Message> {} + +impl<'a, Message> From> for Element<'a, Message> +where + Message: 'static, +{ + fn from(slider: Slider<'a, Message>) -> Element<'a, Message> { + Element::new(slider) + } +} diff --git a/web/src/widget/text.rs b/web/src/widget/text.rs new file mode 100644 index 00000000..0c1f75b6 --- /dev/null +++ b/web/src/widget/text.rs @@ -0,0 +1,13 @@ +use crate::{Color, Element, Widget}; + +pub use iced::text::HorizontalAlignment; + +pub type Text = iced::Text; + +impl<'a, Message> Widget for Text {} + +impl<'a, Message> From for Element<'a, Message> { + fn from(text: Text) -> Element<'a, Message> { + Element::new(text) + } +} -- cgit From 8834772fa70850559f7bd82cc8432394e3fd9db7 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 15 Sep 2019 17:43:15 +0200 Subject: Draft widget nodes and wire interaction --- web/src/bus.rs | 40 +++++++++++++++++++++++++++++++++++ web/src/element.rs | 25 +++++++++++++++++----- web/src/lib.rs | 54 +++++++++++++++++++++++++++++++++++++++++++++--- web/src/widget.rs | 15 +++++++++++++- web/src/widget/button.rs | 34 +++++++++++++++++++++++++++--- web/src/widget/column.rs | 22 ++++++++++++++++++-- web/src/widget/row.rs | 22 ++++++++++++++++++-- web/src/widget/text.rs | 21 +++++++++++++++++-- 8 files changed, 215 insertions(+), 18 deletions(-) create mode 100644 web/src/bus.rs (limited to 'web') diff --git a/web/src/bus.rs b/web/src/bus.rs new file mode 100644 index 00000000..d76466f5 --- /dev/null +++ b/web/src/bus.rs @@ -0,0 +1,40 @@ +use crate::Application; + +use std::rc::Rc; + +#[derive(Clone)] +pub struct Bus { + publish: Rc>, +} + +impl Bus +where + Message: 'static, +{ + pub fn new() -> Self { + Self { + publish: Rc::new(Box::new(|message, root| { + let app = root.unwrap_mut::>(); + + app.update(message) + })), + } + } + + pub fn publish(&self, message: Message, root: &mut dyn dodrio::RootRender) { + (self.publish)(message, root); + } + + pub fn map(&self, mapper: Rc Message>>) -> Bus + where + B: 'static, + { + let publish = self.publish.clone(); + + Bus { + publish: Rc::new(Box::new(move |message, root| { + publish(mapper(message), root) + })), + } + } +} diff --git a/web/src/element.rs b/web/src/element.rs index c0c9ce5d..8270d8db 100644 --- a/web/src/element.rs +++ b/web/src/element.rs @@ -1,4 +1,7 @@ -use crate::{Color, Widget}; +use crate::{Bus, Color, Widget}; + +use dodrio::bumpalo; +use std::rc::Rc; pub struct Element<'a, Message> { pub(crate) widget: Box + 'a>, @@ -11,7 +14,7 @@ impl<'a, Message> Element<'a, Message> { } } - pub fn explain(self, color: Color) -> Element<'a, Message> { + pub fn explain(self, _color: Color) -> Element<'a, Message> { self } @@ -29,7 +32,7 @@ impl<'a, Message> Element<'a, Message> { struct Map<'a, A, B> { widget: Box + 'a>, - mapper: Box B>, + mapper: Rc B>>, } impl<'a, A, B> Map<'a, A, B> { @@ -39,9 +42,21 @@ impl<'a, A, B> Map<'a, A, B> { { Map { widget, - mapper: Box::new(mapper), + mapper: Rc::new(Box::new(mapper)), } } } -impl<'a, A, B> Widget for Map<'a, A, B> {} +impl<'a, A, B> Widget for Map<'a, A, B> +where + A: 'static, + B: 'static, +{ + fn node<'b>( + &self, + bump: &'b bumpalo::Bump, + bus: &Bus, + ) -> dodrio::Node<'b> { + self.widget.node(bump, &bus.map(self.mapper.clone())) + } +} diff --git a/web/src/lib.rs b/web/src/lib.rs index 187e8ad9..3173e736 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -1,9 +1,13 @@ +use dodrio::bumpalo; use futures::Future; +use std::cell::RefCell; +mod bus; mod color; mod element; mod widget; +pub use bus::Bus; pub use color::Color; pub use element::Element; pub use iced::Align; @@ -19,10 +23,54 @@ pub trait UserInterface { fn view(&mut self) -> Element; - fn run(mut self) + fn run(self) where - Self: Sized, + Self: 'static + Sized, { - let element = self.view(); + let window = web_sys::window().unwrap(); + let document = window.document().unwrap(); + let body = document.body().unwrap(); + + let app = Application::new(self); + + let vdom = dodrio::Vdom::new(&body, app); + vdom.forget(); + } +} + +struct Application { + ui: RefCell>>, +} + +impl Application { + fn new(ui: impl UserInterface + 'static) -> Self { + Self { + ui: RefCell::new(Box::new(ui)), + } + } + + fn update(&mut self, message: Message) { + let mut ui = self.ui.borrow_mut(); + + // TODO: Resolve futures and publish resulting messages + let _ = ui.update(message); + } +} + +impl dodrio::Render for Application +where + Message: 'static, +{ + fn render<'a, 'bump>( + &'a self, + bump: &'bump bumpalo::Bump, + ) -> dodrio::Node<'bump> + where + 'a: 'bump, + { + let mut ui = self.ui.borrow_mut(); + let element = ui.view(); + + element.widget.node(bump, &Bus::new()) } } diff --git a/web/src/widget.rs b/web/src/widget.rs index 5cb89a60..7af592e1 100644 --- a/web/src/widget.rs +++ b/web/src/widget.rs @@ -1,3 +1,6 @@ +use crate::Bus; +use dodrio::bumpalo; + pub mod button; pub mod slider; pub mod text; @@ -17,4 +20,14 @@ pub use row::Row; pub use slider::Slider; pub use text::Text; -pub trait Widget {} +pub trait Widget { + fn node<'b>( + &self, + bump: &'b bumpalo::Bump, + _bus: &Bus, + ) -> dodrio::Node<'b> { + use dodrio::builder::*; + + div(bump).children(vec![text("WIP")]).finish() + } +} diff --git a/web/src/widget/button.rs b/web/src/widget/button.rs index 1f117d82..8ccda107 100644 --- a/web/src/widget/button.rs +++ b/web/src/widget/button.rs @@ -1,14 +1,42 @@ -use crate::{Element, Widget}; +use crate::{Bus, Element, Widget}; +use dodrio::bumpalo; pub use iced::button::{Class, State}; pub type Button<'a, Message> = iced::Button<'a, Message>; -impl<'a, Message> Widget for Button<'a, Message> {} +impl<'a, Message> Widget for Button<'a, Message> +where + Message: 'static + Copy, +{ + fn node<'b>( + &self, + bump: &'b bumpalo::Bump, + bus: &Bus, + ) -> dodrio::Node<'b> { + use dodrio::builder::*; + + let label = bumpalo::format!(in bump, "{}", self.label); + + let mut node = button(bump).children(vec![text(label.into_bump_str())]); + + if let Some(on_press) = self.on_press { + let event_bus = bus.clone(); + + node = node.on("click", move |root, vdom, _event| { + event_bus.publish(on_press, root); + + vdom.schedule_render(); + }); + } + + node.finish() + } +} impl<'a, Message> From> for Element<'a, Message> where - Message: 'static, + Message: 'static + Copy, { fn from(button: Button<'a, Message>) -> Element<'a, Message> { Element::new(button) diff --git a/web/src/widget/column.rs b/web/src/widget/column.rs index 84068ce6..b3131f5e 100644 --- a/web/src/widget/column.rs +++ b/web/src/widget/column.rs @@ -1,4 +1,6 @@ -use crate::{Align, Element, Widget}; +use crate::{Align, Bus, Element, Widget}; + +use dodrio::bumpalo; pub struct Column<'a, Message> { children: Vec>, @@ -36,7 +38,23 @@ impl<'a, Message> Column<'a, Message> { } } -impl<'a, Message> Widget for Column<'a, Message> {} +impl<'a, Message> Widget for Column<'a, Message> { + fn node<'b>( + &self, + bump: &'b bumpalo::Bump, + publish: &Bus, + ) -> dodrio::Node<'b> { + use dodrio::builder::*; + + let children: Vec<_> = self + .children + .iter() + .map(|element| element.widget.node(bump, publish)) + .collect(); + + div(bump).children(children).finish() + } +} impl<'a, Message> From> for Element<'a, Message> where diff --git a/web/src/widget/row.rs b/web/src/widget/row.rs index fc474ec3..40fc68e3 100644 --- a/web/src/widget/row.rs +++ b/web/src/widget/row.rs @@ -1,4 +1,6 @@ -use crate::{Element, Widget}; +use crate::{Bus, Element, Widget}; + +use dodrio::bumpalo; pub struct Row<'a, Message> { children: Vec>, @@ -24,7 +26,23 @@ impl<'a, Message> Row<'a, Message> { } } -impl<'a, Message> Widget for Row<'a, Message> {} +impl<'a, Message> Widget for Row<'a, Message> { + fn node<'b>( + &self, + bump: &'b bumpalo::Bump, + publish: &Bus, + ) -> dodrio::Node<'b> { + use dodrio::builder::*; + + let children: Vec<_> = self + .children + .iter() + .map(|element| element.widget.node(bump, publish)) + .collect(); + + div(bump).children(children).finish() + } +} impl<'a, Message> From> for Element<'a, Message> where diff --git a/web/src/widget/text.rs b/web/src/widget/text.rs index 0c1f75b6..b8fe9565 100644 --- a/web/src/widget/text.rs +++ b/web/src/widget/text.rs @@ -1,10 +1,27 @@ -use crate::{Color, Element, Widget}; +use crate::{Bus, Color, Element, Widget}; +use dodrio::bumpalo; pub use iced::text::HorizontalAlignment; pub type Text = iced::Text; -impl<'a, Message> Widget for Text {} +impl<'a, Message> Widget for Text { + fn node<'b>( + &self, + bump: &'b bumpalo::Bump, + _publish: &Bus, + ) -> dodrio::Node<'b> { + use dodrio::builder::*; + + let content = bumpalo::format!(in bump, "{}", self.content); + let size = bumpalo::format!(in bump, "font-size: {}px", self.size.unwrap_or(20)); + + p(bump) + .attr("style", size.into_bump_str()) + .children(vec![text(content.into_bump_str())]) + .finish() + } +} impl<'a, Message> From for Element<'a, Message> { fn from(text: Text) -> Element<'a, Message> { -- cgit From 655978f480c32bc696f0d5fe2fff834bfbf238ea Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 15 Sep 2019 18:53:13 +0200 Subject: Draft nodes for missing widgets --- web/Cargo.toml | 5 ++++ web/examples/tour/index.html | 2 +- web/examples/tour/resources/ferris.png | 1 + web/examples/tour/src/tour.rs | 4 +-- web/src/widget.rs | 6 +--- web/src/widget/button.rs | 4 +-- web/src/widget/checkbox.rs | 39 ++++++++++++++++++++++-- web/src/widget/column.rs | 5 +++- web/src/widget/image.rs | 25 ++++++++++++++-- web/src/widget/radio.rs | 40 +++++++++++++++++++++++-- web/src/widget/row.rs | 5 +++- web/src/widget/slider.rs | 55 +++++++++++++++++++++++++++++++--- 12 files changed, 167 insertions(+), 24 deletions(-) create mode 120000 web/examples/tour/resources/ferris.png (limited to 'web') diff --git a/web/Cargo.toml b/web/Cargo.toml index 6d8c37b1..0ab89570 100644 --- a/web/Cargo.toml +++ b/web/Cargo.toml @@ -18,6 +18,7 @@ maintenance = { status = "actively-developed" } iced = { version = "0.1.0-alpha", path = ".." } dodrio = "0.1.0" futures = "0.1" +wasm-bindgen = "0.2.50" [dependencies.web-sys] version = "0.3.27" @@ -25,4 +26,8 @@ features = [ "console", "Document", "HtmlElement", + "HtmlInputElement", + "Event", + "EventTarget", + "InputEvent", ] diff --git a/web/examples/tour/index.html b/web/examples/tour/index.html index a639a6cb..527cc54c 100644 --- a/web/examples/tour/index.html +++ b/web/examples/tour/index.html @@ -2,7 +2,7 @@ - Tour - Iced Web + Web Tour - Iced - - diff --git a/web/examples/tour/resources/ferris.png b/web/examples/tour/resources/ferris.png deleted file mode 120000 index 9c4fb51c..00000000 --- a/web/examples/tour/resources/ferris.png +++ /dev/null @@ -1 +0,0 @@ -../../../../examples/resources/ferris.png \ No newline at end of file diff --git a/web/examples/tour/src/lib.rs b/web/examples/tour/src/lib.rs deleted file mode 100644 index dbf04df8..00000000 --- a/web/examples/tour/src/lib.rs +++ /dev/null @@ -1,35 +0,0 @@ -use futures::Future; -use iced_web::UserInterface; -use wasm_bindgen::prelude::*; - -mod tour; - -use tour::Tour; - -#[wasm_bindgen(start)] -pub fn run() { - console_error_panic_hook::set_once(); - console_log::init_with_level(log::Level::Trace) - .expect("Initialize logging"); - - let tour = Tour::new(); - - tour.run(); -} - -impl iced_web::UserInterface for Tour { - type Message = tour::Message; - - fn update( - &mut self, - message: tour::Message, - ) -> Option>> { - self.update(message); - - None - } - - fn view(&mut self) -> iced_web::Element { - self.view() - } -} diff --git a/web/examples/tour/src/tour.rs b/web/examples/tour/src/tour.rs deleted file mode 100644 index 9a60e092..00000000 --- a/web/examples/tour/src/tour.rs +++ /dev/null @@ -1,563 +0,0 @@ -use iced_web::{ - button, slider, text::HorizontalAlignment, Align, Button, Checkbox, Color, - Column, Element, Image, Radio, Row, Slider, Text, -}; - -pub struct Tour { - steps: Steps, - back_button: button::State, - next_button: button::State, - debug: bool, -} - -impl Tour { - pub fn new() -> Tour { - Tour { - steps: Steps::new(), - back_button: button::State::new(), - next_button: button::State::new(), - debug: false, - } - } - - pub fn update(&mut self, event: Message) { - match event { - Message::BackPressed => { - self.steps.go_back(); - } - Message::NextPressed => { - self.steps.advance(); - } - Message::StepMessage(step_msg) => { - self.steps.update(step_msg, &mut self.debug); - } - } - } - - pub fn view(&mut self) -> Element { - let Tour { - steps, - back_button, - next_button, - .. - } = self; - - let mut controls = Row::new(); - - if steps.has_previous() { - controls = controls.push( - Button::new(back_button, "Back") - .on_press(Message::BackPressed) - .class(button::Class::Secondary), - ); - } - - controls = controls.push(Column::new()); - - if steps.can_continue() { - controls = controls.push( - Button::new(next_button, "Next").on_press(Message::NextPressed), - ); - } - - let element: Element<_> = Column::new() - .max_width(500) - .spacing(20) - .push(steps.view(self.debug).map(Message::StepMessage)) - .push(controls) - .into(); - - if self.debug { - element.explain(Color::BLACK) - } else { - element - } - } -} - -#[derive(Debug, Clone, Copy)] -pub enum Message { - BackPressed, - NextPressed, - StepMessage(StepMessage), -} - -struct Steps { - steps: Vec, - current: usize, -} - -impl Steps { - fn new() -> Steps { - Steps { - steps: vec![ - Step::Welcome, - Step::Slider { - state: slider::State::new(), - value: 50, - }, - Step::RowsAndColumns { - layout: Layout::Row, - spacing_slider: slider::State::new(), - spacing: 20, - }, - Step::Text { - size_slider: slider::State::new(), - size: 30, - color_sliders: [slider::State::new(); 3], - color: Color::BLACK, - }, - Step::Radio { selection: None }, - Step::Image { - width: 300, - slider: slider::State::new(), - }, - Step::Debugger, - Step::End, - ], - current: 0, - } - } - - fn update(&mut self, msg: StepMessage, debug: &mut bool) { - self.steps[self.current].update(msg, debug); - } - - fn view(&mut self, debug: bool) -> Element { - self.steps[self.current].view(debug) - } - - fn advance(&mut self) { - if self.can_continue() { - self.current += 1; - } - } - - fn go_back(&mut self) { - if self.has_previous() { - self.current -= 1; - } - } - - fn has_previous(&self) -> bool { - self.current > 0 - } - - fn can_continue(&self) -> bool { - self.current + 1 < self.steps.len() - && self.steps[self.current].can_continue() - } -} - -enum Step { - Welcome, - Slider { - state: slider::State, - value: u16, - }, - RowsAndColumns { - layout: Layout, - spacing_slider: slider::State, - spacing: u16, - }, - Text { - size_slider: slider::State, - size: u16, - color_sliders: [slider::State; 3], - color: Color, - }, - Radio { - selection: Option, - }, - Image { - width: u16, - slider: slider::State, - }, - Debugger, - End, -} - -#[derive(Debug, Clone, Copy)] -pub enum StepMessage { - SliderChanged(f32), - LayoutChanged(Layout), - SpacingChanged(f32), - TextSizeChanged(f32), - TextColorChanged(Color), - LanguageSelected(Language), - ImageWidthChanged(f32), - DebugToggled(bool), -} - -impl<'a> Step { - fn update(&mut self, msg: StepMessage, debug: &mut bool) { - match msg { - StepMessage::DebugToggled(value) => { - if let Step::Debugger = self { - *debug = value; - } - } - StepMessage::LanguageSelected(language) => { - if let Step::Radio { selection } = self { - *selection = Some(language); - } - } - StepMessage::SliderChanged(new_value) => { - if let Step::Slider { value, .. } = self { - *value = new_value.round() as u16; - } - } - StepMessage::TextSizeChanged(new_size) => { - if let Step::Text { size, .. } = self { - *size = new_size.round() as u16; - } - } - StepMessage::TextColorChanged(new_color) => { - if let Step::Text { color, .. } = self { - *color = new_color; - } - } - StepMessage::LayoutChanged(new_layout) => { - if let Step::RowsAndColumns { layout, .. } = self { - *layout = new_layout; - } - } - StepMessage::SpacingChanged(new_spacing) => { - if let Step::RowsAndColumns { spacing, .. } = self { - *spacing = new_spacing.round() as u16; - } - } - StepMessage::ImageWidthChanged(new_width) => { - if let Step::Image { width, .. } = self { - *width = new_width.round() as u16; - } - } - }; - } - - fn can_continue(&self) -> bool { - match self { - Step::Welcome => true, - Step::Radio { selection } => *selection == Some(Language::Rust), - Step::Slider { .. } => true, - Step::Text { .. } => true, - Step::Image { .. } => true, - Step::RowsAndColumns { .. } => true, - Step::Debugger => true, - Step::End => false, - } - } - - fn view(&mut self, debug: bool) -> Element { - match self { - Step::Welcome => Self::welcome().into(), - Step::Radio { selection } => Self::radio(*selection).into(), - Step::Slider { state, value } => Self::slider(state, *value).into(), - Step::Text { - size_slider, - size, - color_sliders, - color, - } => Self::text(size_slider, *size, color_sliders, *color).into(), - Step::Image { width, slider } => Self::image(*width, slider).into(), - Step::RowsAndColumns { - layout, - spacing_slider, - spacing, - } => { - Self::rows_and_columns(*layout, spacing_slider, *spacing).into() - } - Step::Debugger => Self::debugger(debug).into(), - Step::End => Self::end().into(), - } - } - - fn container(title: &str) -> Column<'a, StepMessage> { - Column::new() - .spacing(20) - .align_items(Align::Stretch) - .push(Text::new(title).size(50)) - } - - fn welcome() -> Column<'a, StepMessage> { - Self::container("Welcome!") - .push(Text::new( - "This a simple tour meant to showcase a bunch of widgets that \ - can be easily implemented on top of Iced.", - )) - .push(Text::new( - "Iced is a renderer-agnostic GUI library for Rust focused on \ - simplicity and type-safety. It is heavily inspired by Elm.", - )) - .push(Text::new( - "It was originally born as part of Coffee, an opinionated \ - 2D game engine for Rust.", - )) - .push(Text::new( - "Iced does not provide a built-in renderer. This example runs \ - on WebAssembly using dodrio, an experimental VDOM library \ - for Rust.", - )) - .push(Text::new( - "You will need to interact with the UI in order to reach the \ - end!", - )) - } - - fn slider( - state: &'a mut slider::State, - value: u16, - ) -> Column<'a, StepMessage> { - Self::container("Slider") - .push(Text::new( - "A slider allows you to smoothly select a value from a range \ - of values.", - )) - .push(Text::new( - "The following slider lets you choose an integer from \ - 0 to 100:", - )) - .push(Slider::new( - state, - 0.0..=100.0, - value as f32, - StepMessage::SliderChanged, - )) - .push( - Text::new(&value.to_string()) - .horizontal_alignment(HorizontalAlignment::Center), - ) - } - - fn rows_and_columns( - layout: Layout, - spacing_slider: &'a mut slider::State, - spacing: u16, - ) -> Column<'a, StepMessage> { - let row_radio = Radio::new( - Layout::Row, - "Row", - Some(layout), - StepMessage::LayoutChanged, - ); - - let column_radio = Radio::new( - Layout::Column, - "Column", - Some(layout), - StepMessage::LayoutChanged, - ); - - let layout_section: Element<_> = match layout { - Layout::Row => Row::new() - .spacing(spacing) - .push(row_radio) - .push(column_radio) - .into(), - Layout::Column => Column::new() - .spacing(spacing) - .push(row_radio) - .push(column_radio) - .into(), - }; - - let spacing_section = Column::new() - .spacing(10) - .push(Slider::new( - spacing_slider, - 0.0..=80.0, - spacing as f32, - StepMessage::SpacingChanged, - )) - .push( - Text::new(&format!("{} px", spacing)) - .horizontal_alignment(HorizontalAlignment::Center), - ); - - Self::container("Rows and columns") - .spacing(spacing) - .push(Text::new( - "Iced uses a layout model based on flexbox to position UI \ - elements.", - )) - .push(Text::new( - "Rows and columns can be used to distribute content \ - horizontally or vertically, respectively.", - )) - .push(layout_section) - .push(Text::new( - "You can also easily change the spacing between elements:", - )) - .push(spacing_section) - } - - fn text( - size_slider: &'a mut slider::State, - size: u16, - color_sliders: &'a mut [slider::State; 3], - color: Color, - ) -> Column<'a, StepMessage> { - let size_section = Column::new() - .padding(20) - .spacing(20) - .push(Text::new("You can change its size:")) - .push( - Text::new(&format!("This text is {} pixels", size)).size(size), - ) - .push(Slider::new( - size_slider, - 10.0..=70.0, - size as f32, - StepMessage::TextSizeChanged, - )); - - let [red, green, blue] = color_sliders; - let color_section = Column::new() - .padding(20) - .spacing(20) - .push(Text::new("And its color:")) - .push(Text::new(&format!("{:?}", color)).color(color)) - .push( - Row::new() - .spacing(10) - .push(Slider::new(red, 0.0..=1.0, color.r, move |r| { - StepMessage::TextColorChanged(Color { r, ..color }) - })) - .push(Slider::new(green, 0.0..=1.0, color.g, move |g| { - StepMessage::TextColorChanged(Color { g, ..color }) - })) - .push(Slider::new(blue, 0.0..=1.0, color.b, move |b| { - StepMessage::TextColorChanged(Color { b, ..color }) - })), - ); - - Self::container("Text") - .push(Text::new( - "Text is probably the most essential widget for your UI. \ - It will try to adapt to the dimensions of its container.", - )) - .push(size_section) - .push(color_section) - } - - fn radio(selection: Option) -> Column<'a, StepMessage> { - let question = Column::new() - .padding(20) - .spacing(10) - .push(Text::new("Iced is written in...").size(24)) - .push(Language::all().iter().cloned().fold( - Column::new().padding(10).spacing(20), - |choices, language| { - choices.push(Radio::new( - language, - language.into(), - selection, - StepMessage::LanguageSelected, - )) - }, - )); - - Self::container("Radio button") - .push(Text::new( - "A radio button is normally used to represent a choice... \ - Surprise test!", - )) - .push(question) - .push(Text::new( - "Iced works very well with iterators! The list above is \ - basically created by folding a column over the different \ - choices, creating a radio button for each one of them!", - )) - } - - fn image( - width: u16, - slider: &'a mut slider::State, - ) -> Column<'a, StepMessage> { - Self::container("Image") - .push(Text::new("An image that tries to keep its aspect ratio.")) - .push( - Image::new("resources/ferris.png") - .width(width) - .align_self(Align::Center), - ) - .push(Slider::new( - slider, - 100.0..=500.0, - width as f32, - StepMessage::ImageWidthChanged, - )) - .push( - Text::new(&format!("Width: {} px", width.to_string())) - .horizontal_alignment(HorizontalAlignment::Center), - ) - } - - fn debugger(debug: bool) -> Column<'a, StepMessage> { - Self::container("Debugger") - .push(Text::new( - "You can ask Iced to visually explain the layouting of the \ - different elements comprising your UI!", - )) - .push(Text::new( - "Give it a shot! Check the following checkbox to be able to \ - see element boundaries.", - )) - .push(Checkbox::new( - debug, - "Explain layout", - StepMessage::DebugToggled, - )) - .push(Text::new("Feel free to go back and take a look.")) - } - - fn end() -> Column<'a, StepMessage> { - Self::container("You reached the end!") - .push(Text::new( - "This tour will be updated as more features are added.", - )) - .push(Text::new("Make sure to keep an eye on it!")) - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Language { - Rust, - Elm, - Ruby, - Haskell, - C, - Other, -} - -impl Language { - fn all() -> [Language; 6] { - [ - Language::C, - Language::Elm, - Language::Ruby, - Language::Haskell, - Language::Rust, - Language::Other, - ] - } -} - -impl From for &str { - fn from(language: Language) -> &'static str { - match language { - Language::Rust => "Rust", - Language::Elm => "Elm", - Language::Ruby => "Ruby", - Language::Haskell => "Haskell", - Language::C => "C", - Language::Other => "Other", - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Layout { - Row, - Column, -} diff --git a/web/src/color.rs b/web/src/color.rs deleted file mode 100644 index 2624c3c9..00000000 --- a/web/src/color.rs +++ /dev/null @@ -1,16 +0,0 @@ -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct Color { - pub r: f32, - pub g: f32, - pub b: f32, - pub a: f32, -} - -impl Color { - pub const BLACK: Color = Color { - r: 0.0, - g: 0.0, - b: 0.0, - a: 1.0, - }; -} diff --git a/web/src/lib.rs b/web/src/lib.rs index 5a0dd6f2..a6dc2b79 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -3,14 +3,12 @@ use futures::Future; use std::cell::RefCell; mod bus; -mod color; mod element; mod widget; pub use bus::Bus; -pub use color::Color; pub use element::Element; -pub use iced::Align; +pub use iced::{Align, Color}; pub use widget::*; pub trait UserInterface { -- cgit From b83a4b42dd912b5f59d40e7d4f7f7ccdabc43019 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 19 Sep 2019 18:47:01 +0200 Subject: Remove generic `Color` in widgets --- web/src/widget/checkbox.rs | 4 ++-- web/src/widget/radio.rs | 4 ++-- web/src/widget/text.rs | 6 ++---- 3 files changed, 6 insertions(+), 8 deletions(-) (limited to 'web') diff --git a/web/src/widget/checkbox.rs b/web/src/widget/checkbox.rs index e3c61ca7..5aadd65d 100644 --- a/web/src/widget/checkbox.rs +++ b/web/src/widget/checkbox.rs @@ -1,8 +1,8 @@ -use crate::{Bus, Color, Element, Widget}; +use crate::{Bus, Element, Widget}; use dodrio::bumpalo; -pub type Checkbox = iced::Checkbox; +pub use iced::Checkbox; impl Widget for Checkbox where diff --git a/web/src/widget/radio.rs b/web/src/widget/radio.rs index df08a977..e762ae28 100644 --- a/web/src/widget/radio.rs +++ b/web/src/widget/radio.rs @@ -1,8 +1,8 @@ -use crate::{Bus, Color, Element, Widget}; +use crate::{Bus, Element, Widget}; use dodrio::bumpalo; -pub type Radio = iced::Radio; +pub use iced::Radio; impl Widget for Radio where diff --git a/web/src/widget/text.rs b/web/src/widget/text.rs index b8fe9565..a5709775 100644 --- a/web/src/widget/text.rs +++ b/web/src/widget/text.rs @@ -1,9 +1,7 @@ -use crate::{Bus, Color, Element, Widget}; +use crate::{Bus, Element, Widget}; use dodrio::bumpalo; -pub use iced::text::HorizontalAlignment; - -pub type Text = iced::Text; +pub use iced::text::*; impl<'a, Message> Widget for Text { fn node<'b>( -- cgit From b9e0f7494881ad7cdfbcbc16878ecc6ef717753f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 20 Sep 2019 19:15:31 +0200 Subject: Create `iced_core` and `iced_native` --- web/Cargo.toml | 2 +- web/src/lib.rs | 2 +- web/src/widget/button.rs | 3 +-- web/src/widget/checkbox.rs | 2 +- web/src/widget/column.rs | 38 ++------------------------------------ web/src/widget/image.rs | 20 ++++++++++++++------ web/src/widget/radio.rs | 2 +- web/src/widget/row.rs | 24 +----------------------- web/src/widget/slider.rs | 4 ++-- web/src/widget/text.rs | 2 +- 10 files changed, 25 insertions(+), 74 deletions(-) (limited to 'web') diff --git a/web/Cargo.toml b/web/Cargo.toml index 43377d46..d5a987b0 100644 --- a/web/Cargo.toml +++ b/web/Cargo.toml @@ -15,7 +15,7 @@ categories = ["web-programming"] maintenance = { status = "actively-developed" } [dependencies] -iced = { version = "0.1.0-alpha", path = ".." } +iced_core = { version = "0.1.0-alpha", path = "../core" } dodrio = "0.1.0" futures-preview = "=0.3.0-alpha.18" wasm-bindgen = "0.2.50" diff --git a/web/src/lib.rs b/web/src/lib.rs index a6dc2b79..09ca3460 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -8,7 +8,7 @@ mod widget; pub use bus::Bus; pub use element::Element; -pub use iced::{Align, Color}; +pub use iced_core::{Align, Color, Length}; pub use widget::*; pub trait UserInterface { diff --git a/web/src/widget/button.rs b/web/src/widget/button.rs index 63c0262a..36b35901 100644 --- a/web/src/widget/button.rs +++ b/web/src/widget/button.rs @@ -1,9 +1,8 @@ use crate::{Bus, Element, Widget}; -pub use iced::button::{Class, State}; use dodrio::bumpalo; -pub type Button<'a, Message> = iced::Button<'a, Message>; +pub type Button<'a, Message> = iced_core::Button<'a, Message>; impl<'a, Message> Widget for Button<'a, Message> where diff --git a/web/src/widget/checkbox.rs b/web/src/widget/checkbox.rs index 5aadd65d..34995781 100644 --- a/web/src/widget/checkbox.rs +++ b/web/src/widget/checkbox.rs @@ -2,7 +2,7 @@ use crate::{Bus, Element, Widget}; use dodrio::bumpalo; -pub use iced::Checkbox; +pub use iced_core::Checkbox; impl Widget for Checkbox where diff --git a/web/src/widget/column.rs b/web/src/widget/column.rs index a2b8232e..99491647 100644 --- a/web/src/widget/column.rs +++ b/web/src/widget/column.rs @@ -1,42 +1,8 @@ -use crate::{Align, Bus, Element, Widget}; +use crate::{Bus, Element, Widget}; use dodrio::bumpalo; -pub struct Column<'a, Message> { - children: Vec>, -} - -impl<'a, Message> Column<'a, Message> { - pub fn new() -> Self { - Self { - children: Vec::new(), - } - } - - pub fn spacing(self, _spacing: u16) -> Self { - self - } - - pub fn padding(self, _padding: u16) -> Self { - self - } - - pub fn max_width(self, _max_width: u16) -> Self { - self - } - - pub fn align_items(self, _align: Align) -> Self { - self - } - - pub fn push(mut self, element: E) -> Self - where - E: Into>, - { - self.children.push(element.into()); - self - } -} +pub type Column<'a, Message> = iced_core::Column>; impl<'a, Message> Widget for Column<'a, Message> { fn node<'b>( diff --git a/web/src/widget/image.rs b/web/src/widget/image.rs index a882faff..48ff539f 100644 --- a/web/src/widget/image.rs +++ b/web/src/widget/image.rs @@ -1,8 +1,8 @@ -use crate::{Bus, Element, Widget}; +use crate::{Bus, Element, Length, Widget}; use dodrio::bumpalo; -pub type Image<'a> = iced::Image<&'a str>; +pub type Image<'a> = iced_core::Image<&'a str>; impl<'a, Message> Widget for Image<'a> { fn node<'b>( @@ -12,13 +12,21 @@ impl<'a, Message> Widget for Image<'a> { ) -> dodrio::Node<'b> { use dodrio::builder::*; - let src = bumpalo::format!(in bump, "{}", self.image); + let src = bumpalo::format!(in bump, "{}", self.handle); let mut image = img(bump).attr("src", src.into_bump_str()); - if let Some(width) = self.width { - let width = bumpalo::format!(in bump, "{}", width); - image = image.attr("width", width.into_bump_str()); + match self.width { + Length::Shrink => {} + Length::Fill => { + image = image.attr("width", "100%"); + } + Length::Units(px) => { + image = image.attr( + "width", + bumpalo::format!(in bump, "{}px", px).into_bump_str(), + ); + } } image.finish() diff --git a/web/src/widget/radio.rs b/web/src/widget/radio.rs index e762ae28..9063770a 100644 --- a/web/src/widget/radio.rs +++ b/web/src/widget/radio.rs @@ -2,7 +2,7 @@ use crate::{Bus, Element, Widget}; use dodrio::bumpalo; -pub use iced::Radio; +pub use iced_core::Radio; impl Widget for Radio where diff --git a/web/src/widget/row.rs b/web/src/widget/row.rs index 71532245..d4f4c4a0 100644 --- a/web/src/widget/row.rs +++ b/web/src/widget/row.rs @@ -2,29 +2,7 @@ use crate::{Bus, Element, Widget}; use dodrio::bumpalo; -pub struct Row<'a, Message> { - children: Vec>, -} - -impl<'a, Message> Row<'a, Message> { - pub fn new() -> Self { - Self { - children: Vec::new(), - } - } - - pub fn spacing(self, _spacing: u16) -> Self { - self - } - - pub fn push(mut self, element: E) -> Self - where - E: Into>, - { - self.children.push(element.into()); - self - } -} +pub type Row<'a, Message> = iced_core::Row>; impl<'a, Message> Widget for Row<'a, Message> { fn node<'b>( diff --git a/web/src/widget/slider.rs b/web/src/widget/slider.rs index 31bfcbf3..19668025 100644 --- a/web/src/widget/slider.rs +++ b/web/src/widget/slider.rs @@ -2,9 +2,9 @@ use crate::{Bus, Element, Widget}; use dodrio::bumpalo; -pub type Slider<'a, Message> = iced::Slider<'a, Message>; +pub type Slider<'a, Message> = iced_core::Slider<'a, Message>; -pub use iced::slider::State; +pub use iced_core::slider::State; impl<'a, Message> Widget for Slider<'a, Message> where diff --git a/web/src/widget/text.rs b/web/src/widget/text.rs index a5709775..ef9170ef 100644 --- a/web/src/widget/text.rs +++ b/web/src/widget/text.rs @@ -1,7 +1,7 @@ use crate::{Bus, Element, Widget}; use dodrio::bumpalo; -pub use iced::text::*; +pub use iced_core::text::*; impl<'a, Message> Widget for Text { fn node<'b>( -- cgit From 86dede4c4cc2bca9be7d2e6bd831daa98bd7043d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 21 Sep 2019 13:38:14 +0200 Subject: Make example work on web and update READMEs --- web/src/widget/button.rs | 4 +++- web/src/widget/checkbox.rs | 1 + web/src/widget/column.rs | 1 + web/src/widget/image.rs | 2 ++ web/src/widget/radio.rs | 1 + web/src/widget/row.rs | 1 + web/src/widget/slider.rs | 5 ++--- web/src/widget/text.rs | 1 + 8 files changed, 12 insertions(+), 4 deletions(-) (limited to 'web') diff --git a/web/src/widget/button.rs b/web/src/widget/button.rs index 36b35901..23a4165a 100644 --- a/web/src/widget/button.rs +++ b/web/src/widget/button.rs @@ -2,7 +2,7 @@ use crate::{Bus, Element, Widget}; use dodrio::bumpalo; -pub type Button<'a, Message> = iced_core::Button<'a, Message>; +pub use iced_core::button::*; impl<'a, Message> Widget for Button<'a, Message> where @@ -29,6 +29,8 @@ where }); } + // TODO: Complete styling + node.finish() } } diff --git a/web/src/widget/checkbox.rs b/web/src/widget/checkbox.rs index 34995781..72f0a2aa 100644 --- a/web/src/widget/checkbox.rs +++ b/web/src/widget/checkbox.rs @@ -20,6 +20,7 @@ where let event_bus = bus.clone(); let msg = (self.on_toggle)(!self.is_checked); + // TODO: Complete styling label(bump) .children(vec![ input(bump) diff --git a/web/src/widget/column.rs b/web/src/widget/column.rs index 99491647..becd6bc6 100644 --- a/web/src/widget/column.rs +++ b/web/src/widget/column.rs @@ -18,6 +18,7 @@ impl<'a, Message> Widget for Column<'a, Message> { .map(|element| element.widget.node(bump, publish)) .collect(); + // TODO: Complete styling div(bump) .attr("style", "display: flex; flex-direction: column") .children(children) diff --git a/web/src/widget/image.rs b/web/src/widget/image.rs index 48ff539f..fd4ff0df 100644 --- a/web/src/widget/image.rs +++ b/web/src/widget/image.rs @@ -29,6 +29,8 @@ impl<'a, Message> Widget for Image<'a> { } } + // TODO: Complete styling + image.finish() } } diff --git a/web/src/widget/radio.rs b/web/src/widget/radio.rs index 9063770a..d249ad26 100644 --- a/web/src/widget/radio.rs +++ b/web/src/widget/radio.rs @@ -20,6 +20,7 @@ where let event_bus = bus.clone(); let on_click = self.on_click; + // TODO: Complete styling label(bump) .attr("style", "display: block") .children(vec![ diff --git a/web/src/widget/row.rs b/web/src/widget/row.rs index d4f4c4a0..cf6ae594 100644 --- a/web/src/widget/row.rs +++ b/web/src/widget/row.rs @@ -18,6 +18,7 @@ impl<'a, Message> Widget for Row<'a, Message> { .map(|element| element.widget.node(bump, publish)) .collect(); + // TODO: Complete styling div(bump) .attr("style", "display: flex; flex-direction: row") .children(children) diff --git a/web/src/widget/slider.rs b/web/src/widget/slider.rs index 19668025..54b2fdf6 100644 --- a/web/src/widget/slider.rs +++ b/web/src/widget/slider.rs @@ -2,9 +2,7 @@ use crate::{Bus, Element, Widget}; use dodrio::bumpalo; -pub type Slider<'a, Message> = iced_core::Slider<'a, Message>; - -pub use iced_core::slider::State; +pub use iced_core::slider::*; impl<'a, Message> Widget for Slider<'a, Message> where @@ -28,6 +26,7 @@ where let event_bus = bus.clone(); // TODO: Make `step` configurable + // TODO: Complete styling label(bump) .children(vec![input(bump) .attr("type", "range") diff --git a/web/src/widget/text.rs b/web/src/widget/text.rs index ef9170ef..41ccd6fc 100644 --- a/web/src/widget/text.rs +++ b/web/src/widget/text.rs @@ -14,6 +14,7 @@ impl<'a, Message> Widget for Text { let content = bumpalo::format!(in bump, "{}", self.content); let size = bumpalo::format!(in bump, "font-size: {}px", self.size.unwrap_or(20)); + // TODO: Complete styling p(bump) .attr("style", size.into_bump_str()) .children(vec![text(content.into_bump_str())]) -- cgit From 5e28f80af8349be2638eb80d2a06c81fd14d631c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 24 Sep 2019 15:15:34 +0200 Subject: Improve documentation --- web/src/lib.rs | 4 ++-- web/src/widget.rs | 10 ++++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) (limited to 'web') diff --git a/web/src/lib.rs b/web/src/lib.rs index 09ca3460..caf17df5 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -4,11 +4,11 @@ use std::cell::RefCell; mod bus; mod element; -mod widget; +pub mod widget; pub use bus::Bus; pub use element::Element; -pub use iced_core::{Align, Color, Length}; +pub use iced_core::{Align, Color, Justify, Length}; pub use widget::*; pub trait UserInterface { diff --git a/web/src/widget.rs b/web/src/widget.rs index 67ca8e81..88b2efc9 100644 --- a/web/src/widget.rs +++ b/web/src/widget.rs @@ -11,14 +11,20 @@ mod image; mod radio; mod row; +#[doc(no_inline)] pub use button::Button; + +#[doc(no_inline)] +pub use slider::Slider; + +#[doc(no_inline)] +pub use text::Text; + pub use checkbox::Checkbox; pub use column::Column; pub use image::Image; pub use radio::Radio; pub use row::Row; -pub use slider::Slider; -pub use text::Text; pub trait Widget { fn node<'b>( -- cgit