diff options
Diffstat (limited to 'examples')
-rw-r--r-- | examples/README.md | 61 | ||||
-rw-r--r-- | examples/tour/Cargo.toml | 33 | ||||
-rw-r--r-- | examples/tour/README.md | 61 | ||||
-rw-r--r-- | examples/tour/index.html | 13 | ||||
-rw-r--r-- | examples/tour/renderer/image.rs | 51 | ||||
-rw-r--r-- | examples/tour/resources/Roboto-LICENSE (renamed from examples/resources/Roboto-LICENSE) | 0 | ||||
-rw-r--r-- | examples/tour/resources/Roboto-Regular.ttf (renamed from examples/resources/Roboto-Regular.ttf) | bin | 171272 -> 171272 bytes | |||
-rw-r--r-- | examples/tour/resources/ferris.png (renamed from examples/resources/ferris.png) | bin | 33061 -> 33061 bytes | |||
-rw-r--r-- | examples/tour/resources/ui.png (renamed from examples/resources/ui.png) | bin | 16691 -> 16691 bytes | |||
-rw-r--r-- | examples/tour/src/iced_ggez.rs | 6 | ||||
-rw-r--r-- | examples/tour/src/iced_ggez/renderer.rs (renamed from examples/tour/renderer.rs) | 20 | ||||
-rw-r--r-- | examples/tour/src/iced_ggez/renderer/button.rs (renamed from examples/tour/renderer/button.rs) | 29 | ||||
-rw-r--r-- | examples/tour/src/iced_ggez/renderer/checkbox.rs (renamed from examples/tour/renderer/checkbox.rs) | 46 | ||||
-rw-r--r-- | examples/tour/src/iced_ggez/renderer/debugger.rs (renamed from examples/tour/renderer/debugger.rs) | 16 | ||||
-rw-r--r-- | examples/tour/src/iced_ggez/renderer/image.rs | 76 | ||||
-rw-r--r-- | examples/tour/src/iced_ggez/renderer/radio.rs (renamed from examples/tour/renderer/radio.rs) | 45 | ||||
-rw-r--r-- | examples/tour/src/iced_ggez/renderer/slider.rs (renamed from examples/tour/renderer/slider.rs) | 35 | ||||
-rw-r--r-- | examples/tour/src/iced_ggez/renderer/text.rs (renamed from examples/tour/renderer/text.rs) | 62 | ||||
-rw-r--r-- | examples/tour/src/iced_ggez/widget.rs | 12 | ||||
-rw-r--r-- | examples/tour/src/lib.rs | 11 | ||||
-rw-r--r-- | examples/tour/src/main.rs (renamed from examples/tour/main.rs) | 78 | ||||
-rw-r--r-- | examples/tour/src/tour.rs (renamed from examples/tour/tour.rs) | 49 | ||||
-rw-r--r-- | examples/tour/src/web.rs | 33 | ||||
-rw-r--r-- | examples/tour/src/widget.rs | 5 | ||||
-rw-r--r-- | examples/tour/widget.rs | 14 |
25 files changed, 505 insertions, 251 deletions
diff --git a/examples/README.md b/examples/README.md index 0a8a126e..4e83faf1 100644 --- a/examples/README.md +++ b/examples/README.md @@ -11,31 +11,62 @@ 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`]. + the native [`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/tour.rs -[`renderer`]: tour/renderer -[`widget`]: tour/widget.rs -[`main`]: tour/main.rs +[`tour`]: tour/src/tour.rs +[`iced_ggez`]: tour/src/iced_ggez +[`renderer`]: src/iced_ggez/renderer +[`widget`]: tour/src/widget.rs +[`main`]: tour/src/main.rs +[`lib`]: tour/src/lib.rs +[`web`]: tour/src/web.rs +[`wasm-pack`]: https://rustwasm.github.io/wasm-pack/installer/ [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 new file mode 100644 index 00000000..2c79cbf7 --- /dev/null +++ b/examples/tour/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "iced_tour" +version = "0.0.0" +authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] +description = "Tour example for Iced" +license = "MIT" +repository = "https://github.com/hecrj/iced" +edition = "2018" +publish = false + +[lib] +crate-type = ["cdylib", "rlib"] + +[[bin]] +name = "main" +path = "src/main.rs" + +[dependencies] +futures-preview = "=0.3.0-alpha.18" + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +iced_native = { version = "0.1.0-alpha", path = "../../native" } +# A personal `ggez` fork that introduces a `FontCache` type to measure text +# efficiently and fixes HiDPI issues. +ggez = { version = "0.5", git = "https://github.com/hecrj/ggez.git" } +env_logger = "0.6" + +[target.'cfg(target_arch = "wasm32")'.dependencies] +iced_web = { path = "../../web" } +wasm-bindgen = "0.2.50" +log = "0.4" +console_error_panic_hook = "0.1.6" +console_log = "0.1.2" diff --git a/examples/tour/README.md b/examples/tour/README.md index 2af048cc..7ef1a212 100644 --- a/examples/tour/README.md +++ b/examples/tour/README.md @@ -3,31 +3,62 @@ 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`]. + the native [`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 +[`renderer`]: src/iced_ggez/renderer +[`widget`]: src/widget.rs +[`main`]: src/main.rs +[`lib`]: src/lib.rs +[`web`]: src/web.rs +[`wasm-pack`]: https://rustwasm.github.io/wasm-pack/installer/ [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/index.html b/examples/tour/index.html new file mode 100644 index 00000000..b17ac4a2 --- /dev/null +++ b/examples/tour/index.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<html> + <head> + <meta http-equiv="Content-type" content="text/html; charset=utf-8"/> + <title>Tour - Iced</title> + </head> + <body> + <script type="module"> + import init from "./pkg/iced_tour.js"; + init("./pkg/iced_tour_bg.wasm"); + </script> + </body> +</html> diff --git a/examples/tour/renderer/image.rs b/examples/tour/renderer/image.rs deleted file mode 100644 index c3ead5c9..00000000 --- a/examples/tour/renderer/image.rs +++ /dev/null @@ -1,51 +0,0 @@ -use super::Renderer; - -use ggez::{graphics, nalgebra}; -use iced::image; - -impl image::Renderer<graphics::Image> for Renderer<'_> { - fn node( - &self, - style: iced::Style, - image: &graphics::Image, - width: Option<u16>, - height: Option<u16>, - _source: Option<iced::Rectangle<u16>>, - ) -> iced::Node { - let aspect_ratio = image.width() as f32 / image.height() as f32; - - let style = match (width, height) { - (Some(width), Some(height)) => style.width(width).height(height), - (Some(width), None) => style - .width(width) - .height((width as f32 / aspect_ratio).round() as u16), - (None, Some(height)) => style - .height(height) - .width((height as f32 * aspect_ratio).round() as u16), - (None, None) => style.width(image.width()).height(image.height()), - }; - - iced::Node::new(style) - } - - fn draw( - &mut self, - image: &graphics::Image, - bounds: iced::Rectangle, - _source: Option<iced::Rectangle<u16>>, - ) { - // We should probably use batches to draw images efficiently and keep - // draw side-effect free, but this is good enough for the example. - graphics::draw( - self.context, - image, - graphics::DrawParam::new() - .dest(nalgebra::Point2::new(bounds.x, bounds.y)) - .scale(nalgebra::Vector2::new( - bounds.width / image.width() as f32, - bounds.height / image.height() as f32, - )), - ) - .expect("Draw image"); - } -} diff --git a/examples/resources/Roboto-LICENSE b/examples/tour/resources/Roboto-LICENSE index 75b52484..75b52484 100644 --- a/examples/resources/Roboto-LICENSE +++ b/examples/tour/resources/Roboto-LICENSE diff --git a/examples/resources/Roboto-Regular.ttf b/examples/tour/resources/Roboto-Regular.ttf Binary files differindex 2b6392ff..2b6392ff 100644 --- a/examples/resources/Roboto-Regular.ttf +++ b/examples/tour/resources/Roboto-Regular.ttf diff --git a/examples/resources/ferris.png b/examples/tour/resources/ferris.png Binary files differindex ebce1a14..ebce1a14 100644 --- a/examples/resources/ferris.png +++ b/examples/tour/resources/ferris.png diff --git a/examples/resources/ui.png b/examples/tour/resources/ui.png Binary files differindex 4fd3beb3..4fd3beb3 100644 --- a/examples/resources/ui.png +++ b/examples/tour/resources/ui.png diff --git a/examples/tour/src/iced_ggez.rs b/examples/tour/src/iced_ggez.rs new file mode 100644 index 00000000..4a9c0ef4 --- /dev/null +++ b/examples/tour/src/iced_ggez.rs @@ -0,0 +1,6 @@ +mod renderer; +mod widget; + +pub use renderer::Cache as ImageCache; +pub use renderer::Renderer; +pub use widget::*; diff --git a/examples/tour/renderer.rs b/examples/tour/src/iced_ggez/renderer.rs index 8746dd96..c0e6d559 100644 --- a/examples/tour/renderer.rs +++ b/examples/tour/src/iced_ggez/renderer.rs @@ -11,8 +11,11 @@ use ggez::graphics::{ }; use ggez::Context; +pub use image::Cache; + pub struct Renderer<'a> { pub context: &'a mut Context, + pub images: &'a mut image::Cache, pub sprites: SpriteBatch, pub spritesheet: Image, pub font: Font, @@ -20,14 +23,16 @@ pub struct Renderer<'a> { debug_mesh: Option<MeshBuilder>, } -impl Renderer<'_> { +impl<'a> Renderer<'a> { pub fn new( - context: &mut Context, + context: &'a mut Context, + images: &'a mut image::Cache, spritesheet: Image, font: Font, - ) -> Renderer { + ) -> Renderer<'a> { Renderer { context, + images, sprites: SpriteBatch::new(spritesheet.clone()), spritesheet, font, @@ -61,3 +66,12 @@ impl Renderer<'_> { } } } + +pub fn into_color(color: iced_native::Color) -> graphics::Color { + graphics::Color { + r: color.r, + g: color.g, + b: color.b, + a: color.a, + } +} diff --git a/examples/tour/renderer/button.rs b/examples/tour/src/iced_ggez/renderer/button.rs index 486e07ed..78a5de07 100644 --- a/examples/tour/renderer/button.rs +++ b/examples/tour/src/iced_ggez/renderer/button.rs @@ -2,7 +2,7 @@ use super::Renderer; use ggez::graphics::{ self, Align, Color, DrawParam, Rect, Scale, Text, TextFragment, WHITE, }; -use iced::{button, MouseCursor}; +use iced_native::{button, Button, Layout, Length, MouseCursor, Node, Style}; const LEFT: Rect = Rect { x: 0.0, @@ -26,20 +26,29 @@ const RIGHT: Rect = Rect { }; impl button::Renderer for Renderer<'_> { - fn draw( + fn node<Message>(&self, button: &Button<'_, Message>) -> Node { + let style = Style::default() + .width(button.width) + .height(Length::Units(LEFT.h as u16)) + .min_width(Length::Units(100)) + .align_self(button.align_self); + + Node::new(style) + } + + fn draw<Message>( &mut self, - cursor_position: iced::Point, - mut bounds: iced::Rectangle, - state: &button::State, - label: &str, - class: button::Class, + button: &Button<'_, Message>, + layout: Layout<'_>, + cursor_position: iced_native::Point, ) -> MouseCursor { + let mut bounds = layout.bounds(); let mouse_over = bounds.contains(cursor_position); let mut state_offset = 0.0; if mouse_over { - if state.is_pressed() { + if button.state.is_pressed() { bounds.y += 4.0; state_offset = RIGHT.x + RIGHT.w; } else { @@ -47,7 +56,7 @@ impl button::Renderer for Renderer<'_> { } } - let class_index = match class { + let class_index = match button.class { button::Class::Primary => 0, button::Class::Secondary => 1, button::Class::Positive => 2, @@ -103,7 +112,7 @@ impl button::Renderer for Renderer<'_> { }); let mut text = Text::new(TextFragment { - text: String::from(label), + text: button.label.clone(), font: Some(self.font), scale: Some(Scale { x: 20.0, y: 20.0 }), ..Default::default() diff --git a/examples/tour/renderer/checkbox.rs b/examples/tour/src/iced_ggez/renderer/checkbox.rs index 20a91be5..807185d9 100644 --- a/examples/tour/renderer/checkbox.rs +++ b/examples/tour/src/iced_ggez/renderer/checkbox.rs @@ -1,7 +1,10 @@ use super::Renderer; use ggez::graphics::{DrawParam, Rect}; -use iced::{checkbox, MouseCursor}; +use iced_native::{ + checkbox, text, Align, Checkbox, Column, Layout, Length, MouseCursor, Node, + Row, Text, Widget, +}; const SPRITE: Rect = Rect { x: 98.0, @@ -10,14 +13,41 @@ const SPRITE: Rect = Rect { h: 28.0, }; -impl checkbox::Renderer for Renderer<'_> { - fn draw( +impl checkbox::Renderer for Renderer<'_> +where + Self: text::Renderer, +{ + fn node<Message>(&mut self, checkbox: &Checkbox<Message>) -> Node { + Row::<(), Self>::new() + .spacing(15) + .align_items(Align::Center) + .push( + Column::new() + .width(Length::Units(SPRITE.w as u16)) + .height(Length::Units(SPRITE.h as u16)), + ) + .push(Text::new(&checkbox.label)) + .node(self) + } + + fn draw<Message>( &mut self, - cursor_position: iced::Point, - bounds: iced::Rectangle, - text_bounds: iced::Rectangle, - is_checked: bool, + checkbox: &Checkbox<Message>, + layout: Layout<'_>, + cursor_position: iced_native::Point, ) -> MouseCursor { + let bounds = layout.bounds(); + let children: Vec<_> = layout.children().collect(); + let text_bounds = children[1].bounds(); + + let mut text = Text::new(&checkbox.label); + + if let Some(label_color) = checkbox.label_color { + text = text.color(label_color); + } + + text::Renderer::draw(self, &text, children[1]); + let mouse_over = bounds.contains(cursor_position) || text_bounds.contains(cursor_position); @@ -39,7 +69,7 @@ impl checkbox::Renderer for Renderer<'_> { ..DrawParam::default() }); - if is_checked { + if checkbox.is_checked { self.sprites.add(DrawParam { src: Rect { x: (SPRITE.x + SPRITE.w * 2.0) / width, diff --git a/examples/tour/renderer/debugger.rs b/examples/tour/src/iced_ggez/renderer/debugger.rs index 98124795..ffb658af 100644 --- a/examples/tour/renderer/debugger.rs +++ b/examples/tour/src/iced_ggez/renderer/debugger.rs @@ -1,10 +1,12 @@ -use super::Renderer; -use ggez::graphics::{Color, DrawMode, MeshBuilder, Rect}; +use super::{into_color, Renderer}; +use ggez::graphics::{DrawMode, MeshBuilder, Rect}; -impl iced::renderer::Debugger for Renderer<'_> { - type Color = Color; - - fn explain(&mut self, layout: &iced::Layout<'_>, color: Color) { +impl iced_native::renderer::Debugger for Renderer<'_> { + fn explain( + &mut self, + layout: &iced_native::Layout<'_>, + color: iced_native::Color, + ) { let bounds = layout.bounds(); let mut debug_mesh = @@ -18,7 +20,7 @@ impl iced::renderer::Debugger for Renderer<'_> { w: bounds.width, h: bounds.height, }, - color, + into_color(color), ); self.debug_mesh = Some(debug_mesh); diff --git a/examples/tour/src/iced_ggez/renderer/image.rs b/examples/tour/src/iced_ggez/renderer/image.rs new file mode 100644 index 00000000..b12b65c3 --- /dev/null +++ b/examples/tour/src/iced_ggez/renderer/image.rs @@ -0,0 +1,76 @@ +use super::Renderer; + +use ggez::{graphics, nalgebra}; +use iced_native::{image, Image, Layout, Length, Style}; + +pub struct Cache { + images: std::collections::HashMap<String, graphics::Image>, +} + +impl Cache { + pub fn new() -> Self { + Self { + images: std::collections::HashMap::new(), + } + } + + fn get<'a>( + &mut self, + name: &'a str, + context: &mut ggez::Context, + ) -> graphics::Image { + if let Some(image) = self.images.get(name) { + return image.clone(); + } + + let mut image = graphics::Image::new(context, &format!("/{}", name)) + .expect("Load ferris image"); + + image.set_filter(graphics::FilterMode::Linear); + + self.images.insert(name.to_string(), image.clone()); + + image + } +} + +impl<'a> image::Renderer<&'a str> for Renderer<'_> { + fn node(&mut self, image: &Image<&'a str>) -> iced_native::Node { + let ggez_image = self.images.get(image.handle, self.context); + + let aspect_ratio = + ggez_image.width() as f32 / ggez_image.height() as f32; + + let mut style = Style::default().align_self(image.align_self); + + style = match (image.width, image.height) { + (Length::Units(width), _) => style.width(image.width).height( + Length::Units((width as f32 / aspect_ratio).round() as u16), + ), + (_, _) => style + .width(Length::Units(ggez_image.width())) + .height(Length::Units(ggez_image.height())), + }; + + iced_native::Node::new(style) + } + + fn draw(&mut self, image: &Image<&'a str>, layout: Layout<'_>) { + let image = self.images.get(image.handle, self.context); + let bounds = layout.bounds(); + + // We should probably use batches to draw images efficiently and keep + // draw side-effect free, but this is good enough for the example. + graphics::draw( + self.context, + &image, + graphics::DrawParam::new() + .dest(nalgebra::Point2::new(bounds.x, bounds.y)) + .scale(nalgebra::Vector2::new( + bounds.width / image.width() as f32, + bounds.height / image.height() as f32, + )), + ) + .expect("Draw image"); + } +} diff --git a/examples/tour/renderer/radio.rs b/examples/tour/src/iced_ggez/renderer/radio.rs index 0f7815d6..dbd29ecd 100644 --- a/examples/tour/renderer/radio.rs +++ b/examples/tour/src/iced_ggez/renderer/radio.rs @@ -1,7 +1,10 @@ use super::Renderer; use ggez::graphics::{DrawParam, Rect}; -use iced::{radio, MouseCursor, Point, Rectangle}; +use iced_native::{ + radio, text, Align, Column, Layout, Length, MouseCursor, Node, Point, + Radio, Row, Text, Widget, +}; const SPRITE: Rect = Rect { x: 98.0, @@ -10,15 +13,41 @@ const SPRITE: Rect = Rect { h: 28.0, }; -impl radio::Renderer for Renderer<'_> { - fn draw( +impl radio::Renderer for Renderer<'_> +where + Self: text::Renderer, +{ + fn node<Message>(&mut self, radio: &Radio<Message>) -> Node { + Row::<(), Self>::new() + .spacing(15) + .align_items(Align::Center) + .push( + Column::new() + .width(Length::Units(SPRITE.w as u16)) + .height(Length::Units(SPRITE.h as u16)), + ) + .push(Text::new(&radio.label)) + .node(self) + } + + fn draw<Message>( &mut self, + radio: &Radio<Message>, + layout: Layout<'_>, cursor_position: Point, - bounds: Rectangle, - bounds_with_label: Rectangle, - is_selected: bool, ) -> MouseCursor { - let mouse_over = bounds_with_label.contains(cursor_position); + let children: Vec<_> = layout.children().collect(); + + let mut text = Text::new(&radio.label); + + if let Some(label_color) = radio.label_color { + text = text.color(label_color); + } + + text::Renderer::draw(self, &text, children[1]); + + let bounds = layout.bounds(); + let mouse_over = bounds.contains(cursor_position); let width = self.spritesheet.width() as f32; let height = self.spritesheet.height() as f32; @@ -38,7 +67,7 @@ impl radio::Renderer for Renderer<'_> { ..DrawParam::default() }); - if is_selected { + if radio.is_selected { self.sprites.add(DrawParam { src: Rect { x: (SPRITE.x + SPRITE.w * 2.0) / width, diff --git a/examples/tour/renderer/slider.rs b/examples/tour/src/iced_ggez/renderer/slider.rs index 146cee18..60c40c55 100644 --- a/examples/tour/renderer/slider.rs +++ b/examples/tour/src/iced_ggez/renderer/slider.rs @@ -1,8 +1,9 @@ use super::Renderer; use ggez::graphics::{DrawParam, Rect}; -use iced::{slider, MouseCursor, Point, Rectangle}; -use std::ops::RangeInclusive; +use iced_native::{ + slider, Layout, Length, MouseCursor, Node, Point, Slider, Style, +}; const RAIL: Rect = Rect { x: 98.0, @@ -19,14 +20,22 @@ const MARKER: Rect = Rect { }; impl slider::Renderer for Renderer<'_> { - fn draw( + fn node<Message>(&self, slider: &Slider<'_, Message>) -> Node { + let style = Style::default() + .width(slider.width) + .height(Length::Units(25)) + .min_width(Length::Units(100)); + + Node::new(style) + } + + fn draw<Message>( &mut self, + slider: &Slider<'_, Message>, + layout: Layout<'_>, cursor_position: Point, - bounds: Rectangle, - state: &slider::State, - range: RangeInclusive<f32>, - value: f32, ) -> MouseCursor { + let bounds = layout.bounds(); let width = self.spritesheet.width() as f32; let height = self.spritesheet.height() as f32; @@ -48,13 +57,14 @@ impl slider::Renderer for Renderer<'_> { ..DrawParam::default() }); - let (range_start, range_end) = range.into_inner(); + let (range_start, range_end) = slider.range.clone().into_inner(); let marker_offset = (bounds.width - MARKER.w as f32) - * ((value - range_start) / (range_end - range_start).max(1.0)); + * ((slider.value - range_start) + / (range_end - range_start).max(1.0)); let mouse_over = bounds.contains(cursor_position); - let is_active = state.is_dragging() || mouse_over; + let is_active = slider.state.is_dragging() || mouse_over; self.sprites.add(DrawParam { src: Rect { @@ -66,12 +76,13 @@ impl slider::Renderer for Renderer<'_> { }, dest: ggez::mint::Point2 { x: bounds.x + marker_offset.round(), - y: bounds.y + (if state.is_dragging() { 2.0 } else { 0.0 }), + y: bounds.y + + (if slider.state.is_dragging() { 2.0 } else { 0.0 }), }, ..DrawParam::default() }); - if state.is_dragging() { + if slider.state.is_dragging() { MouseCursor::Grabbing } else if mouse_over { MouseCursor::Grab diff --git a/examples/tour/renderer/text.rs b/examples/tour/src/iced_ggez/renderer/text.rs index ecf1481e..b51cc220 100644 --- a/examples/tour/renderer/text.rs +++ b/examples/tour/src/iced_ggez/renderer/text.rs @@ -1,20 +1,15 @@ -use super::Renderer; -use ggez::graphics::{self, mint, Align, Color, Scale, Text, TextFragment}; +use super::{into_color, Renderer}; +use ggez::graphics::{self, mint, Align, Scale, Text, TextFragment}; -use iced::text; +use iced_native::{text, Layout, Node, Style}; use std::cell::RefCell; use std::f32; -impl text::Renderer<Color> for Renderer<'_> { - fn node( - &self, - style: iced::Style, - content: &str, - size: Option<u16>, - ) -> iced::Node { +impl text::Renderer for Renderer<'_> { + fn node(&self, text: &iced_native::Text) -> Node { let font = self.font; let font_cache = graphics::font_cache(self.context); - let content = String::from(content); + let content = String::from(&text.content); // TODO: Investigate why stretch tries to measure this MANY times // with every ancestor's bounds. @@ -23,20 +18,22 @@ impl text::Renderer<Color> for Renderer<'_> { // I noticed that the first measure is the one that matters in // practice. Here, we use a RefCell to store the cached measurement. let measure = RefCell::new(None); - let size = size.map(f32::from).unwrap_or(self.font_size); + let size = text.size.map(f32::from).unwrap_or(self.font_size); - iced::Node::with_measure(style, move |bounds| { + let style = Style::default().width(text.width); + + iced_native::Node::with_measure(style, move |bounds| { let mut measure = measure.borrow_mut(); if measure.is_none() { let bounds = ( match bounds.width { - iced::Number::Undefined => f32::INFINITY, - iced::Number::Defined(w) => w, + iced_native::Number::Undefined => f32::INFINITY, + iced_native::Number::Defined(w) => w, }, match bounds.height { - iced::Number::Undefined => f32::INFINITY, - iced::Number::Defined(h) => h, + iced_native::Number::Undefined => f32::INFINITY, + iced_native::Number::Defined(h) => h, }, ); @@ -57,7 +54,7 @@ impl text::Renderer<Color> for Renderer<'_> { let (width, height) = font_cache.dimensions(&text); - let size = iced::Size { + let size = iced_native::Size { width: width as f32, height: height as f32, }; @@ -75,30 +72,23 @@ impl text::Renderer<Color> for Renderer<'_> { }) } - fn draw( - &mut self, - bounds: iced::Rectangle, - content: &str, - size: Option<u16>, - color: Option<Color>, - horizontal_alignment: text::HorizontalAlignment, - _vertical_alignment: text::VerticalAlignment, - ) { - let size = size.map(f32::from).unwrap_or(self.font_size); - - let mut text = Text::new(TextFragment { - text: String::from(content), + fn draw(&mut self, text: &iced_native::Text, layout: Layout<'_>) { + let size = text.size.map(f32::from).unwrap_or(self.font_size); + let bounds = layout.bounds(); + + let mut ggez_text = Text::new(TextFragment { + text: text.content.clone(), font: Some(self.font), scale: Some(Scale { x: size, y: size }), ..Default::default() }); - text.set_bounds( + ggez_text.set_bounds( mint::Point2 { x: bounds.width, y: bounds.height, }, - match horizontal_alignment { + match text.horizontal_alignment { text::HorizontalAlignment::Left => graphics::Align::Left, text::HorizontalAlignment::Center => graphics::Align::Center, text::HorizontalAlignment::Right => graphics::Align::Right, @@ -107,12 +97,14 @@ impl text::Renderer<Color> for Renderer<'_> { graphics::queue_text( self.context, - &text, + &ggez_text, mint::Point2 { x: bounds.x, y: bounds.y, }, - color.or(Some(graphics::BLACK)), + text.color + .or(Some(iced_native::Color::BLACK)) + .map(into_color), ); } } diff --git a/examples/tour/src/iced_ggez/widget.rs b/examples/tour/src/iced_ggez/widget.rs new file mode 100644 index 00000000..948f9fc6 --- /dev/null +++ b/examples/tour/src/iced_ggez/widget.rs @@ -0,0 +1,12 @@ +use super::Renderer; + +pub use iced_native::{ + button, slider, text, Align, Button, Checkbox, Color, Length, Radio, + Slider, Text, +}; + +pub type Image<'a> = iced_native::Image<&'a str>; + +pub type Column<'a, Message> = iced_native::Column<'a, Message, Renderer<'a>>; +pub type Row<'a, Message> = iced_native::Row<'a, Message, Renderer<'a>>; +pub type Element<'a, Message> = iced_native::Element<'a, Message, Renderer<'a>>; diff --git a/examples/tour/src/lib.rs b/examples/tour/src/lib.rs new file mode 100644 index 00000000..eb41fcd9 --- /dev/null +++ b/examples/tour/src/lib.rs @@ -0,0 +1,11 @@ +pub mod tour; + +pub use tour::{Message, Tour}; + +mod widget; + +#[cfg(target_arch = "wasm32")] +mod web; + +#[cfg(not(target_arch = "wasm32"))] +pub mod iced_ggez; diff --git a/examples/tour/main.rs b/examples/tour/src/main.rs index 1b60207b..a34d3298 100644 --- a/examples/tour/main.rs +++ b/examples/tour/src/main.rs @@ -1,10 +1,4 @@ -mod renderer; -mod tour; -mod widget; - -use renderer::Renderer; -use tour::Tour; -use widget::Column; +use iced_tour::{iced_ggez, Tour}; use ggez; use ggez::event; @@ -28,10 +22,7 @@ pub fn main() -> ggez::GameResult { filesystem::mount( context, - std::path::Path::new(&format!( - "{}/examples/resources", - env!("CARGO_MANIFEST_DIR") - )), + std::path::Path::new(env!("CARGO_MANIFEST_DIR")), true, ); @@ -43,10 +34,11 @@ pub fn main() -> ggez::GameResult { struct Game { spritesheet: graphics::Image, font: graphics::Font, + images: iced_ggez::ImageCache, tour: Tour, - events: Vec<iced::Event>, - cache: Option<iced::Cache>, + events: Vec<iced_native::Event>, + cache: Option<iced_native::Cache>, } impl Game { @@ -54,12 +46,15 @@ impl Game { graphics::set_default_filter(context, graphics::FilterMode::Nearest); Ok(Game { - spritesheet: graphics::Image::new(context, "/ui.png").unwrap(), - font: graphics::Font::new(context, "/Roboto-Regular.ttf").unwrap(), - tour: Tour::new(context), + 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::Cache::default()), + cache: Some(iced_native::Cache::default()), }) } } @@ -76,10 +71,10 @@ impl event::EventHandler for Game { _x: f32, _y: f32, ) { - self.events.push(iced::Event::Mouse( - iced::input::mouse::Event::Input { - state: iced::input::ButtonState::Pressed, - button: iced::input::mouse::Button::Left, // TODO: Map `button` + 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` }, )); } @@ -91,10 +86,10 @@ impl event::EventHandler for Game { _x: f32, _y: f32, ) { - self.events.push(iced::Event::Mouse( - iced::input::mouse::Event::Input { - state: iced::input::ButtonState::Released, - button: iced::input::mouse::Button::Left, // TODO: Map `button` + 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` }, )); } @@ -107,8 +102,8 @@ impl event::EventHandler for Game { _dx: f32, _dy: f32, ) { - self.events.push(iced::Event::Mouse( - iced::input::mouse::Event::CursorMoved { x, y }, + self.events.push(iced_native::Event::Mouse( + iced_native::input::mouse::Event::CursorMoved { x, y }, )); } @@ -138,21 +133,22 @@ impl event::EventHandler for Game { let (messages, cursor) = { let view = self.tour.view(); - let content = Column::new() - .width(screen.w as u16) - .height(screen.h as u16) + 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::Align::Center) - .justify_content(iced::Justify::Center) + .align_items(iced_native::Align::Center) + .justify_content(iced_native::Justify::Center) .push(view); - let renderer = &mut Renderer::new( + let renderer = &mut iced_ggez::Renderer::new( context, + &mut self.images, self.spritesheet.clone(), self.font, ); - let mut ui = iced::UserInterface::build( + let mut ui = iced_native::UserInterface::build( content, self.cache.take().unwrap(), renderer, @@ -183,13 +179,13 @@ impl event::EventHandler for Game { } } -fn into_cursor_type(cursor: iced::MouseCursor) -> mouse::MouseCursor { +fn into_cursor_type(cursor: iced_native::MouseCursor) -> mouse::MouseCursor { match cursor { - iced::MouseCursor::OutOfBounds => mouse::MouseCursor::Default, - iced::MouseCursor::Idle => mouse::MouseCursor::Default, - iced::MouseCursor::Pointer => mouse::MouseCursor::Hand, - iced::MouseCursor::Working => mouse::MouseCursor::Progress, - iced::MouseCursor::Grab => mouse::MouseCursor::Grab, - iced::MouseCursor::Grabbing => mouse::MouseCursor::Grabbing, + 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/tour.rs b/examples/tour/src/tour.rs index d0be99b0..04740fce 100644 --- a/examples/tour/tour.rs +++ b/examples/tour/src/tour.rs @@ -1,12 +1,8 @@ -use super::widget::{ - button, slider, Button, Checkbox, Column, Element, Image, Radio, Row, - Slider, Text, +use crate::widget::{ + button, slider, text::HorizontalAlignment, Align, Button, Checkbox, Color, + Column, Element, Image, Length, 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, @@ -65,14 +61,14 @@ impl Tour { } let element: Element<_> = Column::new() - .max_width(500) + .max_width(Length::Units(500)) .spacing(20) .push(steps.view(self.debug).map(Message::StepMessage)) .push(controls) .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<Language>, }, 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, @@ -313,8 +295,8 @@ impl<'a> Step { )) .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.", + 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 \ @@ -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(Length::Units(width)) + .align_self(Align::Center), + ) .push(Slider::new( slider, 100.0..=500.0, diff --git a/examples/tour/src/web.rs b/examples/tour/src/web.rs new file mode 100644 index 00000000..a0a3060f --- /dev/null +++ b/examples/tour/src/web.rs @@ -0,0 +1,33 @@ +use futures::Future; +use iced_web::UserInterface; +use wasm_bindgen::prelude::*; + +use crate::tour::{self, 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<Box<dyn Future<Output = tour::Message>>> { + self.update(message); + + None + } + + fn view(&mut self) -> iced_web::Element<tour::Message> { + self.view() + } +} diff --git a/examples/tour/src/widget.rs b/examples/tour/src/widget.rs new file mode 100644 index 00000000..9c2c4d5b --- /dev/null +++ b/examples/tour/src/widget.rs @@ -0,0 +1,5 @@ +#[cfg(target_arch = "wasm32")] +pub use iced_web::*; + +#[cfg(not(target_arch = "wasm32"))] +pub use crate::iced_ggez::*; diff --git a/examples/tour/widget.rs b/examples/tour/widget.rs deleted file mode 100644 index 9a141c83..00000000 --- a/examples/tour/widget.rs +++ /dev/null @@ -1,14 +0,0 @@ -use super::Renderer; - -use ggez::graphics::{self, Color}; - -pub use iced::{button, slider, Button, Slider}; - -pub type Text = iced::Text<Color>; -pub type Checkbox<Message> = iced::Checkbox<Color, Message>; -pub type Radio<Message> = iced::Radio<Color, Message>; -pub type Image = iced::Image<graphics::Image>; - -pub type Column<'a, Message> = iced::Column<'a, Message, Renderer<'a>>; -pub type Row<'a, Message> = iced::Row<'a, Message, Renderer<'a>>; -pub type Element<'a, Message> = iced::Element<'a, Message, Renderer<'a>>; |