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 --- examples/README.md | 51 +++++++--- examples/tour/Cargo.toml | 7 +- examples/tour/README.md | 57 ++++++++--- examples/tour/src/iced_ggez/main.rs | 189 ------------------------------------ examples/tour/src/main.rs | 189 ++++++++++++++++++++++++++++++++++++ examples/tour/src/tour.rs | 2 +- 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 + 14 files changed, 290 insertions(+), 221 deletions(-) delete mode 100644 examples/tour/src/iced_ggez/main.rs create mode 100644 examples/tour/src/main.rs diff --git a/examples/README.md b/examples/README.md index 2032df49..5ca0eb69 100644 --- a/examples/README.md +++ b/examples/README.md @@ -11,31 +11,60 @@ you want to learn about a specific release, check out [the release list]. A simple UI tour showcasing different widgets that can be built using Iced. It also shows how the library can be integrated into an existing system. -The example is built on top of [`ggez`], a game library for Rust. Currently, it -is using a [personal fork] to [add a `FontCache` type] and -[fix some issues with HiDPI]. +The example can run both on native and web platforms, using the same GUI code! + +The native renderer of the example is built on top of [`ggez`], a game library +for Rust. Currently, it is using a [personal fork] to [add a `FontCache` type] +and [fix some issues with HiDPI]. + +The web version uses `iced_web` directly. This crate is still a work in +progress. In particular, the styling of elements is not finished yet +(text color, alignment, sizing, etc). The implementation consists of different modules: - - __[`tour`]__ contains the actual GUI code: __state__, __messages__, - __update logic__ and __view logic__. - - __[`renderer`]__ implements a simple renderer for each of the used widgets on - top of the graphics module of [`ggez`]. - - __[`widget`]__ re-exposes Iced's built-in widgets with the renderer type parameter - replaced with the implemented [`renderer`], for convenience. + - __[`tour`]__ contains the actual cross-platform GUI code: __state__, + __messages__, __update logic__ and __view logic__. + - __[`iced_ggez`]__ implements a simple renderer for each of the used widgets + on top of the graphics module of [`ggez`]. + - __[`widget`]__ conditionally re-exposes the correct platform widgets based + on the target architecture. - __[`main`]__ integrates Iced with [`ggez`] and connects the [`tour`] with the [`renderer`]. + - __[`lib`]__ exposes the [`tour`] types and conditionally implements the + WebAssembly entrypoint in the [`web`] module. + +The conditional compilation awkwardness from targetting both native and web +platforms should be handled seamlessly by the `iced` crate in the near future! + +If you want to run it as a native app: + +``` +cd examples/tour +cargo run +``` + +If you want to run it on web, you will need [`wasm-pack`]: + +``` +cd examples/tour +wasm-pack build --target web +``` + +Then, simply serve the directory with any HTTP server. For instance: ``` -cargo run --package iced_ggez_tour +python3 -m http.server ``` [![Tour - Iced][gui_gif]][gui_gfycat] [`ggez`]: https://github.com/ggez/ggez [`tour`]: tour/src/tour.rs -[`renderer`]: tour/src/renderer +[`iced_ggez`]: tour/src/iced_ggez [`widget`]: tour/src/widget.rs [`main`]: tour/src/main.rs +[`lib`]: tour/src/lib.rs +[`web`]: tour/src/web.rs [personal fork]: https://github.com/hecrj/ggez [add a `FontCache` type]: https://github.com/ggez/ggez/pull/679 [fix some issues with HiDPI]: https://github.com/hecrj/ggez/commit/dfe2fd2423c51a6daf42c75f66dfaeaacd439fb1 diff --git a/examples/tour/Cargo.toml b/examples/tour/Cargo.toml index a0e3eee8..8cdce295 100644 --- a/examples/tour/Cargo.toml +++ b/examples/tour/Cargo.toml @@ -8,9 +8,12 @@ repository = "https://github.com/hecrj/iced" edition = "2018" publish = false +[lib] +crate-type = ["cdylib", "rlib"] + [[bin]] -name = "ggez" -path = "src/iced_ggez/main.rs" +name = "main" +path = "src/main.rs" [dependencies] futures-preview = "=0.3.0-alpha.18" diff --git a/examples/tour/README.md b/examples/tour/README.md index 2af048cc..a33de7f5 100644 --- a/examples/tour/README.md +++ b/examples/tour/README.md @@ -3,31 +3,60 @@ A simple UI tour showcasing different widgets that can be built using Iced. It also shows how the library can be integrated into an existing system. -The example is built on top of [`ggez`], a game library for Rust. Currently, it -is using a [personal fork] to [add a `FontCache` type] and -[fix some issues with HiDPI]. +The example can run both on native and web platforms, using the same GUI code! + +The native renderer of the example is built on top of [`ggez`], a game library +for Rust. Currently, it is using a [personal fork] to [add a `FontCache` type] +and [fix some issues with HiDPI]. + +The web version uses `iced_web` directly. This crate is still a work in +progress. In particular, the styling of elements is not finished yet +(text color, alignment, sizing, etc). The implementation consists of different modules: - - __[`tour`]__ contains the actual GUI code: __state__, __messages__, - __update logic__ and __view logic__. - - __[`renderer`]__ implements a simple renderer for each of the used widgets on - top of the graphics module of [`ggez`]. - - __[`widget`]__ re-exposes Iced's built-in widgets with the renderer type parameter - replaced with the implemented [`renderer`], for convenience. + - __[`tour`]__ contains the actual cross-platform GUI code: __state__, + __messages__, __update logic__ and __view logic__. + - __[`iced_ggez`]__ implements a simple renderer for each of the used widgets + on top of the graphics module of [`ggez`]. + - __[`widget`]__ conditionally re-exposes the correct platform widgets based + on the target architecture. - __[`main`]__ integrates Iced with [`ggez`] and connects the [`tour`] with the [`renderer`]. + - __[`lib`]__ exposes the [`tour`] types and conditionally implements the + WebAssembly entrypoint in the [`web`] module. + +The conditional compilation awkwardness from targetting both native and web +platforms should be handled seamlessly by the `iced` crate in the near future! + +If you want to run it as a native app: + +``` +cd examples/tour +cargo run +``` + +If you want to run it on web, you will need [`wasm-pack`]: + +``` +cd examples/tour +wasm-pack build --target web +``` + +Then, simply serve the directory with any HTTP server. For instance: ``` -cargo run --example tour +python3 -m http.server ``` [![Tour - Iced][gui_gif]][gui_gfycat] [`ggez`]: https://github.com/ggez/ggez -[`tour`]: tour.rs -[`renderer`]: renderer -[`widget`]: widget.rs -[`main`]: main.rs +[`tour`]: src/tour.rs +[`iced_ggez`]: src/iced_ggez +[`widget`]: src/widget.rs +[`main`]: src/main.rs +[`lib`]: src/lib.rs +[`web`]: src/web.rs [personal fork]: https://github.com/hecrj/ggez [add a `FontCache` type]: https://github.com/ggez/ggez/pull/679 [fix some issues with HiDPI]: https://github.com/hecrj/ggez/commit/dfe2fd2423c51a6daf42c75f66dfaeaacd439fb1 diff --git a/examples/tour/src/iced_ggez/main.rs b/examples/tour/src/iced_ggez/main.rs deleted file mode 100644 index 72774d38..00000000 --- a/examples/tour/src/iced_ggez/main.rs +++ /dev/null @@ -1,189 +0,0 @@ -use iced_tour::{iced_ggez, Tour}; - -use ggez; -use ggez::event; -use ggez::filesystem; -use ggez::graphics; -use ggez::input::mouse; - -pub fn main() -> ggez::GameResult { - let (context, event_loop) = { - &mut ggez::ContextBuilder::new("iced", "ggez") - .window_mode(ggez::conf::WindowMode { - width: 1280.0, - height: 1024.0, - resizable: true, - ..ggez::conf::WindowMode::default() - }) - .build()? - }; - - filesystem::mount( - context, - std::path::Path::new(env!("CARGO_MANIFEST_DIR")), - true, - ); - - let state = &mut Game::new(context)?; - - event::run(context, event_loop, state) -} - -struct Game { - spritesheet: graphics::Image, - font: graphics::Font, - images: iced_ggez::ImageCache, - tour: Tour, - - events: Vec, - cache: Option, -} - -impl Game { - fn new(context: &mut ggez::Context) -> ggez::GameResult { - graphics::set_default_filter(context, graphics::FilterMode::Nearest); - - Ok(Game { - spritesheet: graphics::Image::new(context, "/resources/ui.png") - .unwrap(), - font: graphics::Font::new(context, "/resources/Roboto-Regular.ttf") - .unwrap(), - images: iced_ggez::ImageCache::new(), - tour: Tour::new(), - - events: Vec::new(), - cache: Some(iced_native::Cache::default()), - }) - } -} - -impl event::EventHandler for Game { - fn update(&mut self, _ctx: &mut ggez::Context) -> ggez::GameResult { - Ok(()) - } - - fn mouse_button_down_event( - &mut self, - _context: &mut ggez::Context, - _button: mouse::MouseButton, - _x: f32, - _y: f32, - ) { - self.events.push(iced_native::Event::Mouse( - iced_native::input::mouse::Event::Input { - state: iced_native::input::ButtonState::Pressed, - button: iced_native::input::mouse::Button::Left, // TODO: Map `button` - }, - )); - } - - fn mouse_button_up_event( - &mut self, - _context: &mut ggez::Context, - _button: mouse::MouseButton, - _x: f32, - _y: f32, - ) { - self.events.push(iced_native::Event::Mouse( - iced_native::input::mouse::Event::Input { - state: iced_native::input::ButtonState::Released, - button: iced_native::input::mouse::Button::Left, // TODO: Map `button` - }, - )); - } - - fn mouse_motion_event( - &mut self, - _context: &mut ggez::Context, - x: f32, - y: f32, - _dx: f32, - _dy: f32, - ) { - self.events.push(iced_native::Event::Mouse( - iced_native::input::mouse::Event::CursorMoved { x, y }, - )); - } - - fn resize_event( - &mut self, - context: &mut ggez::Context, - width: f32, - height: f32, - ) { - graphics::set_screen_coordinates( - context, - graphics::Rect { - x: 0.0, - y: 0.0, - w: width, - h: height, - }, - ) - .expect("Set screen coordinates"); - } - - fn draw(&mut self, context: &mut ggez::Context) -> ggez::GameResult { - graphics::clear(context, graphics::WHITE); - - let screen = graphics::screen_coordinates(context); - - let (messages, cursor) = { - let view = self.tour.view(); - - let content = iced_ggez::Column::new() - .width(iced_native::Length::Units(screen.w as u16)) - .height(iced_native::Length::Units(screen.h as u16)) - .padding(20) - .align_items(iced_native::Align::Center) - .justify_content(iced_native::Justify::Center) - .push(view); - - let renderer = &mut iced_ggez::Renderer::new( - context, - &mut self.images, - self.spritesheet.clone(), - self.font, - ); - - let mut ui = iced_native::UserInterface::build( - content, - self.cache.take().unwrap(), - renderer, - ); - - let messages = ui.update(self.events.drain(..)); - let cursor = ui.draw(renderer); - - self.cache = Some(ui.into_cache()); - - renderer.flush(); - - (messages, cursor) - }; - - for message in messages { - self.tour.update(message); - } - - let cursor_type = into_cursor_type(cursor); - - if mouse::cursor_type(context) != cursor_type { - mouse::set_cursor_type(context, cursor_type); - } - - graphics::present(context)?; - Ok(()) - } -} - -fn into_cursor_type(cursor: iced_native::MouseCursor) -> mouse::MouseCursor { - match cursor { - iced_native::MouseCursor::OutOfBounds => mouse::MouseCursor::Default, - iced_native::MouseCursor::Idle => mouse::MouseCursor::Default, - iced_native::MouseCursor::Pointer => mouse::MouseCursor::Hand, - iced_native::MouseCursor::Working => mouse::MouseCursor::Progress, - iced_native::MouseCursor::Grab => mouse::MouseCursor::Grab, - iced_native::MouseCursor::Grabbing => mouse::MouseCursor::Grabbing, - } -} diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs new file mode 100644 index 00000000..72774d38 --- /dev/null +++ b/examples/tour/src/main.rs @@ -0,0 +1,189 @@ +use iced_tour::{iced_ggez, Tour}; + +use ggez; +use ggez::event; +use ggez::filesystem; +use ggez::graphics; +use ggez::input::mouse; + +pub fn main() -> ggez::GameResult { + let (context, event_loop) = { + &mut ggez::ContextBuilder::new("iced", "ggez") + .window_mode(ggez::conf::WindowMode { + width: 1280.0, + height: 1024.0, + resizable: true, + ..ggez::conf::WindowMode::default() + }) + .build()? + }; + + filesystem::mount( + context, + std::path::Path::new(env!("CARGO_MANIFEST_DIR")), + true, + ); + + let state = &mut Game::new(context)?; + + event::run(context, event_loop, state) +} + +struct Game { + spritesheet: graphics::Image, + font: graphics::Font, + images: iced_ggez::ImageCache, + tour: Tour, + + events: Vec, + cache: Option, +} + +impl Game { + fn new(context: &mut ggez::Context) -> ggez::GameResult { + graphics::set_default_filter(context, graphics::FilterMode::Nearest); + + Ok(Game { + spritesheet: graphics::Image::new(context, "/resources/ui.png") + .unwrap(), + font: graphics::Font::new(context, "/resources/Roboto-Regular.ttf") + .unwrap(), + images: iced_ggez::ImageCache::new(), + tour: Tour::new(), + + events: Vec::new(), + cache: Some(iced_native::Cache::default()), + }) + } +} + +impl event::EventHandler for Game { + fn update(&mut self, _ctx: &mut ggez::Context) -> ggez::GameResult { + Ok(()) + } + + fn mouse_button_down_event( + &mut self, + _context: &mut ggez::Context, + _button: mouse::MouseButton, + _x: f32, + _y: f32, + ) { + self.events.push(iced_native::Event::Mouse( + iced_native::input::mouse::Event::Input { + state: iced_native::input::ButtonState::Pressed, + button: iced_native::input::mouse::Button::Left, // TODO: Map `button` + }, + )); + } + + fn mouse_button_up_event( + &mut self, + _context: &mut ggez::Context, + _button: mouse::MouseButton, + _x: f32, + _y: f32, + ) { + self.events.push(iced_native::Event::Mouse( + iced_native::input::mouse::Event::Input { + state: iced_native::input::ButtonState::Released, + button: iced_native::input::mouse::Button::Left, // TODO: Map `button` + }, + )); + } + + fn mouse_motion_event( + &mut self, + _context: &mut ggez::Context, + x: f32, + y: f32, + _dx: f32, + _dy: f32, + ) { + self.events.push(iced_native::Event::Mouse( + iced_native::input::mouse::Event::CursorMoved { x, y }, + )); + } + + fn resize_event( + &mut self, + context: &mut ggez::Context, + width: f32, + height: f32, + ) { + graphics::set_screen_coordinates( + context, + graphics::Rect { + x: 0.0, + y: 0.0, + w: width, + h: height, + }, + ) + .expect("Set screen coordinates"); + } + + fn draw(&mut self, context: &mut ggez::Context) -> ggez::GameResult { + graphics::clear(context, graphics::WHITE); + + let screen = graphics::screen_coordinates(context); + + let (messages, cursor) = { + let view = self.tour.view(); + + let content = iced_ggez::Column::new() + .width(iced_native::Length::Units(screen.w as u16)) + .height(iced_native::Length::Units(screen.h as u16)) + .padding(20) + .align_items(iced_native::Align::Center) + .justify_content(iced_native::Justify::Center) + .push(view); + + let renderer = &mut iced_ggez::Renderer::new( + context, + &mut self.images, + self.spritesheet.clone(), + self.font, + ); + + let mut ui = iced_native::UserInterface::build( + content, + self.cache.take().unwrap(), + renderer, + ); + + let messages = ui.update(self.events.drain(..)); + let cursor = ui.draw(renderer); + + self.cache = Some(ui.into_cache()); + + renderer.flush(); + + (messages, cursor) + }; + + for message in messages { + self.tour.update(message); + } + + let cursor_type = into_cursor_type(cursor); + + if mouse::cursor_type(context) != cursor_type { + mouse::set_cursor_type(context, cursor_type); + } + + graphics::present(context)?; + Ok(()) + } +} + +fn into_cursor_type(cursor: iced_native::MouseCursor) -> mouse::MouseCursor { + match cursor { + iced_native::MouseCursor::OutOfBounds => mouse::MouseCursor::Default, + iced_native::MouseCursor::Idle => mouse::MouseCursor::Default, + iced_native::MouseCursor::Pointer => mouse::MouseCursor::Hand, + iced_native::MouseCursor::Working => mouse::MouseCursor::Progress, + iced_native::MouseCursor::Grab => mouse::MouseCursor::Grab, + iced_native::MouseCursor::Grabbing => mouse::MouseCursor::Grabbing, + } +} diff --git a/examples/tour/src/tour.rs b/examples/tour/src/tour.rs index fd9c2dde..04740fce 100644 --- a/examples/tour/src/tour.rs +++ b/examples/tour/src/tour.rs @@ -16,7 +16,7 @@ impl Tour { steps: Steps::new(), back_button: button::State::new(), next_button: button::State::new(), - debug: true, + debug: false, } } 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