diff options
| author | 2019-09-24 15:39:33 +0200 | |
|---|---|---|
| committer | 2019-09-24 15:39:33 +0200 | |
| commit | 68c4752e998dca1d618380ce4e7d8ac52b710056 (patch) | |
| tree | 35e386030b072c189509bb2ed3adeaec5b0fd4d1 /examples | |
| parent | bb5cac49d028eb53c259ae58e3a007ebfb736fd4 (diff) | |
| parent | 05c7c39ecb8910c75b82dc4052a7720fb2d42b4a (diff) | |
| download | iced-68c4752e998dca1d618380ce4e7d8ac52b710056.tar.gz iced-68c4752e998dca1d618380ce4e7d8ac52b710056.tar.bz2 iced-68c4752e998dca1d618380ce4e7d8ac52b710056.zip | |
Merge pull request #17 from hecrj/web
Basic web support (core, native, and web crates)
Diffstat (limited to '')
| -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.ttfBinary files differ index 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.pngBinary files differ index 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.pngBinary files differ index 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>>; | 
