From e1b9d42bf1443ae4958aa9303255ef19c635debb Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 3 Oct 2019 00:01:45 +0200 Subject: Start `iced_winit` and `iced_wgpu` --- Cargo.toml | 9 +- examples/tour/Cargo.toml | 23 +- examples/tour/src/iced_ggez.rs | 6 - examples/tour/src/iced_ggez/renderer.rs | 77 --- examples/tour/src/iced_ggez/renderer/button.rs | 154 ----- examples/tour/src/iced_ggez/renderer/checkbox.rs | 94 --- examples/tour/src/iced_ggez/renderer/debugger.rs | 32 - examples/tour/src/iced_ggez/renderer/image.rs | 76 --- examples/tour/src/iced_ggez/renderer/radio.rs | 92 --- examples/tour/src/iced_ggez/renderer/slider.rs | 93 --- examples/tour/src/iced_ggez/renderer/text.rs | 110 ---- examples/tour/src/iced_ggez/widget.rs | 12 - examples/tour/src/lib.rs | 11 - examples/tour/src/main.rs | 712 +++++++++++++++++------ examples/tour/src/tour.rs | 563 ------------------ examples/tour/src/web.rs | 33 -- examples/tour/src/widget.rs | 5 - src/lib.rs | 23 + wgpu/Cargo.toml | 12 + wgpu/src/lib.rs | 87 +++ winit/Cargo.toml | 12 + winit/src/lib.rs | 1 + 22 files changed, 691 insertions(+), 1546 deletions(-) delete mode 100644 examples/tour/src/iced_ggez.rs delete mode 100644 examples/tour/src/iced_ggez/renderer.rs delete mode 100644 examples/tour/src/iced_ggez/renderer/button.rs delete mode 100644 examples/tour/src/iced_ggez/renderer/checkbox.rs delete mode 100644 examples/tour/src/iced_ggez/renderer/debugger.rs delete mode 100644 examples/tour/src/iced_ggez/renderer/image.rs delete mode 100644 examples/tour/src/iced_ggez/renderer/radio.rs delete mode 100644 examples/tour/src/iced_ggez/renderer/slider.rs delete mode 100644 examples/tour/src/iced_ggez/renderer/text.rs delete mode 100644 examples/tour/src/iced_ggez/widget.rs delete mode 100644 examples/tour/src/lib.rs delete mode 100644 examples/tour/src/tour.rs delete mode 100644 examples/tour/src/web.rs delete mode 100644 examples/tour/src/widget.rs create mode 100644 wgpu/Cargo.toml create mode 100644 wgpu/src/lib.rs create mode 100644 winit/Cargo.toml create mode 100644 winit/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 0975aeeb..4792b3ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced" -version = "0.1.0-alpha" +version = "0.1.0-alpha.1" authors = ["Héctor Ramón Jiménez "] edition = "2018" description = "A cross-platform GUI library inspired by Elm" @@ -21,3 +21,10 @@ members = [ "web", "examples/tour", ] + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +iced_winit = { version = "0.1.0-alpha", path = "winit" } +iced_wgpu = { version = "0.1.0-alpha", path = "wgpu" } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +iced_web = { version = "0.1.0-alpha", path = "web" } diff --git a/examples/tour/Cargo.toml b/examples/tour/Cargo.toml index 2c79cbf7..8b3d7765 100644 --- a/examples/tour/Cargo.toml +++ b/examples/tour/Cargo.toml @@ -8,26 +8,5 @@ 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" +iced = { version = "0.1.0-alpha.1", path = "../.." } diff --git a/examples/tour/src/iced_ggez.rs b/examples/tour/src/iced_ggez.rs deleted file mode 100644 index 4a9c0ef4..00000000 --- a/examples/tour/src/iced_ggez.rs +++ /dev/null @@ -1,6 +0,0 @@ -mod renderer; -mod widget; - -pub use renderer::Cache as ImageCache; -pub use renderer::Renderer; -pub use widget::*; diff --git a/examples/tour/src/iced_ggez/renderer.rs b/examples/tour/src/iced_ggez/renderer.rs deleted file mode 100644 index c0e6d559..00000000 --- a/examples/tour/src/iced_ggez/renderer.rs +++ /dev/null @@ -1,77 +0,0 @@ -mod button; -mod checkbox; -mod debugger; -mod image; -mod radio; -mod slider; -mod text; - -use ggez::graphics::{ - self, spritebatch::SpriteBatch, Font, Image, MeshBuilder, -}; -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, - font_size: f32, - debug_mesh: Option, -} - -impl<'a> Renderer<'a> { - pub fn new( - context: &'a mut Context, - images: &'a mut image::Cache, - spritesheet: Image, - font: Font, - ) -> Renderer<'a> { - Renderer { - context, - images, - sprites: SpriteBatch::new(spritesheet.clone()), - spritesheet, - font, - font_size: 20.0, - debug_mesh: None, - } - } - - pub fn flush(&mut self) { - graphics::draw( - self.context, - &self.sprites, - graphics::DrawParam::default(), - ) - .expect("Draw sprites"); - - graphics::draw_queued_text( - self.context, - graphics::DrawParam::default(), - Default::default(), - graphics::FilterMode::Linear, - ) - .expect("Draw text"); - - if let Some(debug_mesh) = self.debug_mesh.take() { - let mesh = - debug_mesh.build(self.context).expect("Build debug mesh"); - - graphics::draw(self.context, &mesh, graphics::DrawParam::default()) - .expect("Draw debug mesh"); - } - } -} - -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/src/iced_ggez/renderer/button.rs b/examples/tour/src/iced_ggez/renderer/button.rs deleted file mode 100644 index 78a5de07..00000000 --- a/examples/tour/src/iced_ggez/renderer/button.rs +++ /dev/null @@ -1,154 +0,0 @@ -use super::Renderer; -use ggez::graphics::{ - self, Align, Color, DrawParam, Rect, Scale, Text, TextFragment, WHITE, -}; -use iced_native::{button, Button, Layout, Length, MouseCursor, Node, Style}; - -const LEFT: Rect = Rect { - x: 0.0, - y: 34.0, - w: 6.0, - h: 49.0, -}; - -const BACKGROUND: Rect = Rect { - x: LEFT.w, - y: LEFT.y, - w: 1.0, - h: LEFT.h, -}; - -const RIGHT: Rect = Rect { - x: LEFT.h - LEFT.w, - y: LEFT.y, - w: LEFT.w, - h: LEFT.h, -}; - -impl button::Renderer for Renderer<'_> { - fn node(&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( - &mut self, - 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 button.state.is_pressed() { - bounds.y += 4.0; - state_offset = RIGHT.x + RIGHT.w; - } else { - bounds.y -= 1.0; - } - } - - let class_index = match button.class { - button::Class::Primary => 0, - button::Class::Secondary => 1, - button::Class::Positive => 2, - }; - - let width = self.spritesheet.width() as f32; - let height = self.spritesheet.height() as f32; - - self.sprites.add(DrawParam { - src: Rect { - x: (LEFT.x + state_offset) / width, - y: (LEFT.y + class_index as f32 * LEFT.h) / height, - w: LEFT.w / width, - h: LEFT.h / height, - }, - dest: ggez::mint::Point2 { - x: bounds.x, - y: bounds.y, - }, - ..DrawParam::default() - }); - - self.sprites.add(DrawParam { - src: Rect { - x: (BACKGROUND.x + state_offset) / width, - y: (BACKGROUND.y + class_index as f32 * BACKGROUND.h) / height, - w: BACKGROUND.w / width, - h: BACKGROUND.h / height, - }, - dest: ggez::mint::Point2 { - x: bounds.x + LEFT.w, - y: bounds.y, - }, - scale: ggez::mint::Vector2 { - x: bounds.width - LEFT.w - RIGHT.w, - y: 1.0, - }, - ..DrawParam::default() - }); - - self.sprites.add(DrawParam { - src: Rect { - x: (RIGHT.x + state_offset) / width, - y: (RIGHT.y + class_index as f32 * RIGHT.h) / height, - w: RIGHT.w / width, - h: RIGHT.h / height, - }, - dest: ggez::mint::Point2 { - x: bounds.x + bounds.width - RIGHT.w, - y: bounds.y, - }, - ..DrawParam::default() - }); - - let mut text = Text::new(TextFragment { - text: button.label.clone(), - font: Some(self.font), - scale: Some(Scale { x: 20.0, y: 20.0 }), - ..Default::default() - }); - - text.set_bounds( - ggez::mint::Point2 { - x: bounds.width, - y: bounds.height, - }, - Align::Center, - ); - - graphics::queue_text( - self.context, - &text, - ggez::mint::Point2 { - x: bounds.x, - y: bounds.y + BACKGROUND.h / 4.0, - }, - Some(if mouse_over { - WHITE - } else { - Color { - r: 0.9, - g: 0.9, - b: 0.9, - a: 1.0, - } - }), - ); - - if mouse_over { - MouseCursor::Pointer - } else { - MouseCursor::OutOfBounds - } - } -} diff --git a/examples/tour/src/iced_ggez/renderer/checkbox.rs b/examples/tour/src/iced_ggez/renderer/checkbox.rs deleted file mode 100644 index 807185d9..00000000 --- a/examples/tour/src/iced_ggez/renderer/checkbox.rs +++ /dev/null @@ -1,94 +0,0 @@ -use super::Renderer; - -use ggez::graphics::{DrawParam, Rect}; -use iced_native::{ - checkbox, text, Align, Checkbox, Column, Layout, Length, MouseCursor, Node, - Row, Text, Widget, -}; - -const SPRITE: Rect = Rect { - x: 98.0, - y: 0.0, - w: 28.0, - h: 28.0, -}; - -impl checkbox::Renderer for Renderer<'_> -where - Self: text::Renderer, -{ - fn node(&mut self, checkbox: &Checkbox) -> 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( - &mut self, - checkbox: &Checkbox, - 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); - - let width = self.spritesheet.width() as f32; - let height = self.spritesheet.height() as f32; - - self.sprites.add(DrawParam { - src: Rect { - x: (SPRITE.x + (if mouse_over { SPRITE.w } else { 0.0 })) - / width, - y: SPRITE.y / height, - w: SPRITE.w / width, - h: SPRITE.h / height, - }, - dest: ggez::mint::Point2 { - x: bounds.x, - y: bounds.y, - }, - ..DrawParam::default() - }); - - if checkbox.is_checked { - self.sprites.add(DrawParam { - src: Rect { - x: (SPRITE.x + SPRITE.w * 2.0) / width, - y: SPRITE.y / height, - w: SPRITE.w / width, - h: SPRITE.h / height, - }, - dest: ggez::mint::Point2 { - x: bounds.x, - y: bounds.y, - }, - ..DrawParam::default() - }); - } - - if mouse_over { - MouseCursor::Pointer - } else { - MouseCursor::OutOfBounds - } - } -} diff --git a/examples/tour/src/iced_ggez/renderer/debugger.rs b/examples/tour/src/iced_ggez/renderer/debugger.rs deleted file mode 100644 index ffb658af..00000000 --- a/examples/tour/src/iced_ggez/renderer/debugger.rs +++ /dev/null @@ -1,32 +0,0 @@ -use super::{into_color, Renderer}; -use ggez::graphics::{DrawMode, MeshBuilder, Rect}; - -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 = - self.debug_mesh.take().unwrap_or(MeshBuilder::new()); - - debug_mesh.rectangle( - DrawMode::stroke(1.0), - Rect { - x: bounds.x, - y: bounds.y, - w: bounds.width, - h: bounds.height, - }, - into_color(color), - ); - - self.debug_mesh = Some(debug_mesh); - - for child in layout.children() { - self.explain(&child, color); - } - } -} diff --git a/examples/tour/src/iced_ggez/renderer/image.rs b/examples/tour/src/iced_ggez/renderer/image.rs deleted file mode 100644 index b12b65c3..00000000 --- a/examples/tour/src/iced_ggez/renderer/image.rs +++ /dev/null @@ -1,76 +0,0 @@ -use super::Renderer; - -use ggez::{graphics, nalgebra}; -use iced_native::{image, Image, Layout, Length, Style}; - -pub struct Cache { - images: std::collections::HashMap, -} - -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/src/iced_ggez/renderer/radio.rs b/examples/tour/src/iced_ggez/renderer/radio.rs deleted file mode 100644 index dbd29ecd..00000000 --- a/examples/tour/src/iced_ggez/renderer/radio.rs +++ /dev/null @@ -1,92 +0,0 @@ -use super::Renderer; - -use ggez::graphics::{DrawParam, Rect}; -use iced_native::{ - radio, text, Align, Column, Layout, Length, MouseCursor, Node, Point, - Radio, Row, Text, Widget, -}; - -const SPRITE: Rect = Rect { - x: 98.0, - y: 28.0, - w: 28.0, - h: 28.0, -}; - -impl radio::Renderer for Renderer<'_> -where - Self: text::Renderer, -{ - fn node(&mut self, radio: &Radio) -> 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( - &mut self, - radio: &Radio, - layout: Layout<'_>, - cursor_position: Point, - ) -> MouseCursor { - 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; - - self.sprites.add(DrawParam { - src: Rect { - x: (SPRITE.x + (if mouse_over { SPRITE.w } else { 0.0 })) - / width, - y: SPRITE.y / height, - w: SPRITE.w / width, - h: SPRITE.h / height, - }, - dest: ggez::mint::Point2 { - x: bounds.x, - y: bounds.y, - }, - ..DrawParam::default() - }); - - if radio.is_selected { - self.sprites.add(DrawParam { - src: Rect { - x: (SPRITE.x + SPRITE.w * 2.0) / width, - y: SPRITE.y / height, - w: SPRITE.w / width, - h: SPRITE.h / height, - }, - dest: ggez::mint::Point2 { - x: bounds.x, - y: bounds.y, - }, - ..DrawParam::default() - }); - } - - if mouse_over { - MouseCursor::Pointer - } else { - MouseCursor::OutOfBounds - } - } -} diff --git a/examples/tour/src/iced_ggez/renderer/slider.rs b/examples/tour/src/iced_ggez/renderer/slider.rs deleted file mode 100644 index 60c40c55..00000000 --- a/examples/tour/src/iced_ggez/renderer/slider.rs +++ /dev/null @@ -1,93 +0,0 @@ -use super::Renderer; - -use ggez::graphics::{DrawParam, Rect}; -use iced_native::{ - slider, Layout, Length, MouseCursor, Node, Point, Slider, Style, -}; - -const RAIL: Rect = Rect { - x: 98.0, - y: 56.0, - w: 1.0, - h: 4.0, -}; - -const MARKER: Rect = Rect { - x: RAIL.x + 28.0, - y: RAIL.y, - w: 16.0, - h: 24.0, -}; - -impl slider::Renderer for Renderer<'_> { - fn node(&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( - &mut self, - slider: &Slider<'_, Message>, - layout: Layout<'_>, - cursor_position: Point, - ) -> MouseCursor { - let bounds = layout.bounds(); - let width = self.spritesheet.width() as f32; - let height = self.spritesheet.height() as f32; - - self.sprites.add(DrawParam { - src: Rect { - x: RAIL.x / width, - y: RAIL.y / height, - w: RAIL.w / width, - h: RAIL.h / height, - }, - dest: ggez::mint::Point2 { - x: bounds.x + MARKER.w as f32 / 2.0, - y: bounds.y + 12.5, - }, - scale: ggez::mint::Vector2 { - x: bounds.width - MARKER.w as f32, - y: 1.0, - }, - ..DrawParam::default() - }); - - let (range_start, range_end) = slider.range.clone().into_inner(); - - let marker_offset = (bounds.width - MARKER.w as f32) - * ((slider.value - range_start) - / (range_end - range_start).max(1.0)); - - let mouse_over = bounds.contains(cursor_position); - let is_active = slider.state.is_dragging() || mouse_over; - - self.sprites.add(DrawParam { - src: Rect { - x: (MARKER.x + (if is_active { MARKER.w } else { 0.0 })) - / width, - y: MARKER.y / height, - w: MARKER.w / width, - h: MARKER.h / height, - }, - dest: ggez::mint::Point2 { - x: bounds.x + marker_offset.round(), - y: bounds.y - + (if slider.state.is_dragging() { 2.0 } else { 0.0 }), - }, - ..DrawParam::default() - }); - - if slider.state.is_dragging() { - MouseCursor::Grabbing - } else if mouse_over { - MouseCursor::Grab - } else { - MouseCursor::OutOfBounds - } - } -} diff --git a/examples/tour/src/iced_ggez/renderer/text.rs b/examples/tour/src/iced_ggez/renderer/text.rs deleted file mode 100644 index b51cc220..00000000 --- a/examples/tour/src/iced_ggez/renderer/text.rs +++ /dev/null @@ -1,110 +0,0 @@ -use super::{into_color, Renderer}; -use ggez::graphics::{self, mint, Align, Scale, Text, TextFragment}; - -use iced_native::{text, Layout, Node, Style}; -use std::cell::RefCell; -use std::f32; - -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(&text.content); - - // TODO: Investigate why stretch tries to measure this MANY times - // with every ancestor's bounds. - // Bug? Using the library wrong? I should probably open an issue on - // the stretch repository. - // 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 = text.size.map(f32::from).unwrap_or(self.font_size); - - 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_native::Number::Undefined => f32::INFINITY, - iced_native::Number::Defined(w) => w, - }, - match bounds.height { - iced_native::Number::Undefined => f32::INFINITY, - iced_native::Number::Defined(h) => h, - }, - ); - - let mut text = Text::new(TextFragment { - text: content.clone(), - font: Some(font), - scale: Some(Scale { x: size, y: size }), - ..Default::default() - }); - - text.set_bounds( - mint::Point2 { - x: bounds.0, - y: bounds.1, - }, - Align::Left, - ); - - let (width, height) = font_cache.dimensions(&text); - - let size = iced_native::Size { - width: width as f32, - height: height as f32, - }; - - // If the text has no width boundary we avoid caching as the - // layout engine may just be measuring text in a row. - if bounds.0 == f32::INFINITY { - return size; - } else { - *measure = Some(size); - } - } - - measure.unwrap() - }) - } - - 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() - }); - - ggez_text.set_bounds( - mint::Point2 { - x: bounds.width, - y: bounds.height, - }, - match text.horizontal_alignment { - text::HorizontalAlignment::Left => graphics::Align::Left, - text::HorizontalAlignment::Center => graphics::Align::Center, - text::HorizontalAlignment::Right => graphics::Align::Right, - }, - ); - - graphics::queue_text( - self.context, - &ggez_text, - mint::Point2 { - x: bounds.x, - y: bounds.y, - }, - 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 deleted file mode 100644 index 948f9fc6..00000000 --- a/examples/tour/src/iced_ggez/widget.rs +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index eb41fcd9..00000000 --- a/examples/tour/src/lib.rs +++ /dev/null @@ -1,11 +0,0 @@ -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/src/main.rs b/examples/tour/src/main.rs index a34d3298..5017041a 100644 --- a/examples/tour/src/main.rs +++ b/examples/tour/src/main.rs @@ -1,191 +1,573 @@ -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 { - env_logger::init(); - - 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) -} +use iced::{ + button, slider, text::HorizontalAlignment, Align, Button, Checkbox, Color, + Column, Element, Image, Length, Radio, Row, Slider, Text, UserInterface, +}; -struct Game { - spritesheet: graphics::Image, - font: graphics::Font, - images: iced_ggez::ImageCache, - tour: Tour, +pub fn main() { + let tour = Tour::new(); - events: Vec, - cache: Option, + tour.run(); } -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(), +pub struct Tour { + steps: Steps, + back_button: button::State, + next_button: button::State, + debug: bool, +} - events: Vec::new(), - cache: Some(iced_native::Cache::default()), - }) +impl Tour { + pub fn new() -> Tour { + Tour { + steps: Steps::new(), + back_button: button::State::new(), + next_button: button::State::new(), + debug: false, + } } } -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, +impl UserInterface for Tour { + type Message = Message; + + fn update(&mut self, event: Message) { + match event { + Message::BackPressed => { + self.steps.go_back(); + } + Message::NextPressed => { + self.steps.advance(); + } + Message::StepMessage(step_msg) => { + self.steps.update(step_msg, &mut self.debug); + } + } + } + + fn view(&mut self) -> Element { + let Tour { + steps, + back_button, + next_button, + .. + } = self; + + let mut controls = Row::new(); + + if steps.has_previous() { + controls = controls.push( + Button::new(back_button, "Back") + .on_press(Message::BackPressed) + .class(button::Class::Secondary), ); + } + + controls = controls.push(Column::new()); - let mut ui = iced_native::UserInterface::build( - content, - self.cache.take().unwrap(), - renderer, + if steps.can_continue() { + controls = controls.push( + Button::new(next_button, "Next").on_press(Message::NextPressed), ); + } - let messages = ui.update(self.events.drain(..)); - let cursor = ui.draw(renderer); + let element: Element<_> = Column::new() + .max_width(Length::Units(500)) + .spacing(20) + .push(steps.view(self.debug).map(Message::StepMessage)) + .push(controls) + .into(); - self.cache = Some(ui.into_cache()); + if self.debug { + element.explain(Color::BLACK) + } else { + element + } + } +} - renderer.flush(); +#[derive(Debug, Clone, Copy)] +pub enum Message { + BackPressed, + NextPressed, + StepMessage(StepMessage), +} - (messages, cursor) - }; +struct Steps { + steps: Vec, + current: usize, +} - for message in messages { - self.tour.update(message); +impl Steps { + fn new() -> Steps { + Steps { + steps: vec![ + Step::Welcome, + Step::Slider { + state: slider::State::new(), + value: 50, + }, + Step::RowsAndColumns { + layout: Layout::Row, + spacing_slider: slider::State::new(), + spacing: 20, + }, + Step::Text { + size_slider: slider::State::new(), + size: 30, + color_sliders: [slider::State::new(); 3], + color: Color::BLACK, + }, + Step::Radio { selection: None }, + Step::Image { + width: 300, + slider: slider::State::new(), + }, + Step::Debugger, + Step::End, + ], + current: 0, } + } - let cursor_type = into_cursor_type(cursor); + fn update(&mut self, msg: StepMessage, debug: &mut bool) { + self.steps[self.current].update(msg, debug); + } + + fn view(&mut self, debug: bool) -> Element { + self.steps[self.current].view(debug) + } + + fn advance(&mut self) { + if self.can_continue() { + self.current += 1; + } + } - if mouse::cursor_type(context) != cursor_type { - mouse::set_cursor_type(context, cursor_type); + fn go_back(&mut self) { + if self.has_previous() { + self.current -= 1; } + } - graphics::present(context)?; - Ok(()) + fn has_previous(&self) -> bool { + self.current > 0 } + + fn can_continue(&self) -> bool { + self.current + 1 < self.steps.len() + && self.steps[self.current].can_continue() + } +} + +enum Step { + Welcome, + Slider { + state: slider::State, + value: u16, + }, + RowsAndColumns { + layout: Layout, + spacing_slider: slider::State, + spacing: u16, + }, + Text { + size_slider: slider::State, + size: u16, + color_sliders: [slider::State; 3], + color: Color, + }, + Radio { + selection: Option, + }, + Image { + width: u16, + slider: slider::State, + }, + Debugger, + End, +} + +#[derive(Debug, Clone, Copy)] +pub enum StepMessage { + SliderChanged(f32), + LayoutChanged(Layout), + SpacingChanged(f32), + TextSizeChanged(f32), + TextColorChanged(Color), + LanguageSelected(Language), + ImageWidthChanged(f32), + DebugToggled(bool), } -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, +impl<'a> Step { + fn update(&mut self, msg: StepMessage, debug: &mut bool) { + match msg { + StepMessage::DebugToggled(value) => { + if let Step::Debugger = self { + *debug = value; + } + } + StepMessage::LanguageSelected(language) => { + if let Step::Radio { selection } = self { + *selection = Some(language); + } + } + StepMessage::SliderChanged(new_value) => { + if let Step::Slider { value, .. } = self { + *value = new_value.round() as u16; + } + } + StepMessage::TextSizeChanged(new_size) => { + if let Step::Text { size, .. } = self { + *size = new_size.round() as u16; + } + } + StepMessage::TextColorChanged(new_color) => { + if let Step::Text { color, .. } = self { + *color = new_color; + } + } + StepMessage::LayoutChanged(new_layout) => { + if let Step::RowsAndColumns { layout, .. } = self { + *layout = new_layout; + } + } + StepMessage::SpacingChanged(new_spacing) => { + if let Step::RowsAndColumns { spacing, .. } = self { + *spacing = new_spacing.round() as u16; + } + } + StepMessage::ImageWidthChanged(new_width) => { + if let Step::Image { width, .. } = self { + *width = new_width.round() as u16; + } + } + }; + } + + fn can_continue(&self) -> bool { + match self { + Step::Welcome => true, + Step::Radio { selection } => *selection == Some(Language::Rust), + Step::Slider { .. } => true, + Step::Text { .. } => true, + Step::Image { .. } => true, + Step::RowsAndColumns { .. } => true, + Step::Debugger => true, + Step::End => false, + } + } + + fn view(&mut self, debug: bool) -> Element { + match self { + Step::Welcome => Self::welcome().into(), + Step::Radio { selection } => Self::radio(*selection).into(), + Step::Slider { state, value } => Self::slider(state, *value).into(), + Step::Text { + size_slider, + size, + color_sliders, + color, + } => Self::text(size_slider, *size, color_sliders, *color).into(), + Step::Image { width, slider } => Self::image(*width, slider).into(), + Step::RowsAndColumns { + layout, + spacing_slider, + spacing, + } => { + Self::rows_and_columns(*layout, spacing_slider, *spacing).into() + } + Step::Debugger => Self::debugger(debug).into(), + Step::End => Self::end().into(), + } } + + fn container(title: &str) -> Column<'a, StepMessage> { + Column::new() + .spacing(20) + .align_items(Align::Stretch) + .push(Text::new(title).size(50)) + } + + fn welcome() -> Column<'a, StepMessage> { + Self::container("Welcome!") + .push(Text::new( + "This a simple tour meant to showcase a bunch of widgets that \ + can be easily implemented on top of Iced.", + )) + .push(Text::new( + "Iced is a renderer-agnostic GUI library for Rust focused on \ + simplicity and type-safety. It is heavily inspired by Elm.", + )) + .push(Text::new( + "It was originally born as part of Coffee, an opinionated \ + 2D game engine for Rust.", + )) + .push(Text::new( + "Iced does not provide a built-in renderer. This example runs \ + on WebAssembly using dodrio, an experimental VDOM library \ + for Rust.", + )) + .push(Text::new( + "You will need to interact with the UI in order to reach the \ + end!", + )) + } + + fn slider( + state: &'a mut slider::State, + value: u16, + ) -> Column<'a, StepMessage> { + Self::container("Slider") + .push(Text::new( + "A slider allows you to smoothly select a value from a range \ + of values.", + )) + .push(Text::new( + "The following slider lets you choose an integer from \ + 0 to 100:", + )) + .push(Slider::new( + state, + 0.0..=100.0, + value as f32, + StepMessage::SliderChanged, + )) + .push( + Text::new(&value.to_string()) + .horizontal_alignment(HorizontalAlignment::Center), + ) + } + + fn rows_and_columns( + layout: Layout, + spacing_slider: &'a mut slider::State, + spacing: u16, + ) -> Column<'a, StepMessage> { + let row_radio = Radio::new( + Layout::Row, + "Row", + Some(layout), + StepMessage::LayoutChanged, + ); + + let column_radio = Radio::new( + Layout::Column, + "Column", + Some(layout), + StepMessage::LayoutChanged, + ); + + let layout_section: Element<_> = match layout { + Layout::Row => Row::new() + .spacing(spacing) + .push(row_radio) + .push(column_radio) + .into(), + Layout::Column => Column::new() + .spacing(spacing) + .push(row_radio) + .push(column_radio) + .into(), + }; + + let spacing_section = Column::new() + .spacing(10) + .push(Slider::new( + spacing_slider, + 0.0..=80.0, + spacing as f32, + StepMessage::SpacingChanged, + )) + .push( + Text::new(&format!("{} px", spacing)) + .horizontal_alignment(HorizontalAlignment::Center), + ); + + Self::container("Rows and columns") + .spacing(spacing) + .push(Text::new( + "Iced uses a layout model based on flexbox to position UI \ + elements.", + )) + .push(Text::new( + "Rows and columns can be used to distribute content \ + horizontally or vertically, respectively.", + )) + .push(layout_section) + .push(Text::new( + "You can also easily change the spacing between elements:", + )) + .push(spacing_section) + } + + fn text( + size_slider: &'a mut slider::State, + size: u16, + color_sliders: &'a mut [slider::State; 3], + color: Color, + ) -> Column<'a, StepMessage> { + let size_section = Column::new() + .padding(20) + .spacing(20) + .push(Text::new("You can change its size:")) + .push( + Text::new(&format!("This text is {} pixels", size)).size(size), + ) + .push(Slider::new( + size_slider, + 10.0..=70.0, + size as f32, + StepMessage::TextSizeChanged, + )); + + let [red, green, blue] = color_sliders; + let color_section = Column::new() + .padding(20) + .spacing(20) + .push(Text::new("And its color:")) + .push(Text::new(&format!("{:?}", color)).color(color)) + .push( + Row::new() + .spacing(10) + .push(Slider::new(red, 0.0..=1.0, color.r, move |r| { + StepMessage::TextColorChanged(Color { r, ..color }) + })) + .push(Slider::new(green, 0.0..=1.0, color.g, move |g| { + StepMessage::TextColorChanged(Color { g, ..color }) + })) + .push(Slider::new(blue, 0.0..=1.0, color.b, move |b| { + StepMessage::TextColorChanged(Color { b, ..color }) + })), + ); + + Self::container("Text") + .push(Text::new( + "Text is probably the most essential widget for your UI. \ + It will try to adapt to the dimensions of its container.", + )) + .push(size_section) + .push(color_section) + } + + fn radio(selection: Option) -> Column<'a, StepMessage> { + let question = Column::new() + .padding(20) + .spacing(10) + .push(Text::new("Iced is written in...").size(24)) + .push(Language::all().iter().cloned().fold( + Column::new().padding(10).spacing(20), + |choices, language| { + choices.push(Radio::new( + language, + language.into(), + selection, + StepMessage::LanguageSelected, + )) + }, + )); + + Self::container("Radio button") + .push(Text::new( + "A radio button is normally used to represent a choice... \ + Surprise test!", + )) + .push(question) + .push(Text::new( + "Iced works very well with iterators! The list above is \ + basically created by folding a column over the different \ + choices, creating a radio button for each one of them!", + )) + } + + fn image( + width: u16, + slider: &'a mut slider::State, + ) -> Column<'a, StepMessage> { + Self::container("Image") + .push(Text::new("An image that tries to keep its aspect ratio.")) + .push( + Image::new("resources/ferris.png") + .width(Length::Units(width)) + .align_self(Align::Center), + ) + .push(Slider::new( + slider, + 100.0..=500.0, + width as f32, + StepMessage::ImageWidthChanged, + )) + .push( + Text::new(&format!("Width: {} px", width.to_string())) + .horizontal_alignment(HorizontalAlignment::Center), + ) + } + + fn debugger(debug: bool) -> Column<'a, StepMessage> { + Self::container("Debugger") + .push(Text::new( + "You can ask Iced to visually explain the layouting of the \ + different elements comprising your UI!", + )) + .push(Text::new( + "Give it a shot! Check the following checkbox to be able to \ + see element boundaries.", + )) + .push(Checkbox::new( + debug, + "Explain layout", + StepMessage::DebugToggled, + )) + .push(Text::new("Feel free to go back and take a look.")) + } + + fn end() -> Column<'a, StepMessage> { + Self::container("You reached the end!") + .push(Text::new( + "This tour will be updated as more features are added.", + )) + .push(Text::new("Make sure to keep an eye on it!")) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Language { + Rust, + Elm, + Ruby, + Haskell, + C, + Other, +} + +impl Language { + fn all() -> [Language; 6] { + [ + Language::C, + Language::Elm, + Language::Ruby, + Language::Haskell, + Language::Rust, + Language::Other, + ] + } +} + +impl From for &str { + fn from(language: Language) -> &'static str { + match language { + Language::Rust => "Rust", + Language::Elm => "Elm", + Language::Ruby => "Ruby", + Language::Haskell => "Haskell", + Language::C => "C", + Language::Other => "Other", + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Layout { + Row, + Column, } diff --git a/examples/tour/src/tour.rs b/examples/tour/src/tour.rs deleted file mode 100644 index 04740fce..00000000 --- a/examples/tour/src/tour.rs +++ /dev/null @@ -1,563 +0,0 @@ -use crate::widget::{ - button, slider, text::HorizontalAlignment, Align, Button, Checkbox, Color, - Column, Element, Image, Length, Radio, Row, Slider, Text, -}; - -pub struct Tour { - steps: Steps, - back_button: button::State, - next_button: button::State, - debug: bool, -} - -impl Tour { - pub fn new() -> Tour { - Tour { - steps: Steps::new(), - back_button: button::State::new(), - next_button: button::State::new(), - debug: false, - } - } - - pub fn update(&mut self, event: Message) { - match event { - Message::BackPressed => { - self.steps.go_back(); - } - Message::NextPressed => { - self.steps.advance(); - } - Message::StepMessage(step_msg) => { - self.steps.update(step_msg, &mut self.debug); - } - } - } - - pub fn view(&mut self) -> Element { - let Tour { - steps, - back_button, - next_button, - .. - } = self; - - let mut controls = Row::new(); - - if steps.has_previous() { - controls = controls.push( - Button::new(back_button, "Back") - .on_press(Message::BackPressed) - .class(button::Class::Secondary), - ); - } - - controls = controls.push(Column::new()); - - if steps.can_continue() { - controls = controls.push( - Button::new(next_button, "Next").on_press(Message::NextPressed), - ); - } - - let element: Element<_> = Column::new() - .max_width(Length::Units(500)) - .spacing(20) - .push(steps.view(self.debug).map(Message::StepMessage)) - .push(controls) - .into(); - - if self.debug { - element.explain(Color::BLACK) - } else { - element - } - } -} - -#[derive(Debug, Clone, Copy)] -pub enum Message { - BackPressed, - NextPressed, - StepMessage(StepMessage), -} - -struct Steps { - steps: Vec, - current: usize, -} - -impl Steps { - fn new() -> Steps { - Steps { - steps: vec![ - Step::Welcome, - Step::Slider { - state: slider::State::new(), - value: 50, - }, - Step::RowsAndColumns { - layout: Layout::Row, - spacing_slider: slider::State::new(), - spacing: 20, - }, - Step::Text { - size_slider: slider::State::new(), - size: 30, - color_sliders: [slider::State::new(); 3], - color: Color::BLACK, - }, - Step::Radio { selection: None }, - Step::Image { - width: 300, - slider: slider::State::new(), - }, - Step::Debugger, - Step::End, - ], - current: 0, - } - } - - fn update(&mut self, msg: StepMessage, debug: &mut bool) { - self.steps[self.current].update(msg, debug); - } - - fn view(&mut self, debug: bool) -> Element { - self.steps[self.current].view(debug) - } - - fn advance(&mut self) { - if self.can_continue() { - self.current += 1; - } - } - - fn go_back(&mut self) { - if self.has_previous() { - self.current -= 1; - } - } - - fn has_previous(&self) -> bool { - self.current > 0 - } - - fn can_continue(&self) -> bool { - self.current + 1 < self.steps.len() - && self.steps[self.current].can_continue() - } -} - -enum Step { - Welcome, - Slider { - state: slider::State, - value: u16, - }, - RowsAndColumns { - layout: Layout, - spacing_slider: slider::State, - spacing: u16, - }, - Text { - size_slider: slider::State, - size: u16, - color_sliders: [slider::State; 3], - color: Color, - }, - Radio { - selection: Option, - }, - Image { - width: u16, - slider: slider::State, - }, - Debugger, - End, -} - -#[derive(Debug, Clone, Copy)] -pub enum StepMessage { - SliderChanged(f32), - LayoutChanged(Layout), - SpacingChanged(f32), - TextSizeChanged(f32), - TextColorChanged(Color), - LanguageSelected(Language), - ImageWidthChanged(f32), - DebugToggled(bool), -} - -impl<'a> Step { - fn update(&mut self, msg: StepMessage, debug: &mut bool) { - match msg { - StepMessage::DebugToggled(value) => { - if let Step::Debugger = self { - *debug = value; - } - } - StepMessage::LanguageSelected(language) => { - if let Step::Radio { selection } = self { - *selection = Some(language); - } - } - StepMessage::SliderChanged(new_value) => { - if let Step::Slider { value, .. } = self { - *value = new_value.round() as u16; - } - } - StepMessage::TextSizeChanged(new_size) => { - if let Step::Text { size, .. } = self { - *size = new_size.round() as u16; - } - } - StepMessage::TextColorChanged(new_color) => { - if let Step::Text { color, .. } = self { - *color = new_color; - } - } - StepMessage::LayoutChanged(new_layout) => { - if let Step::RowsAndColumns { layout, .. } = self { - *layout = new_layout; - } - } - StepMessage::SpacingChanged(new_spacing) => { - if let Step::RowsAndColumns { spacing, .. } = self { - *spacing = new_spacing.round() as u16; - } - } - StepMessage::ImageWidthChanged(new_width) => { - if let Step::Image { width, .. } = self { - *width = new_width.round() as u16; - } - } - }; - } - - fn can_continue(&self) -> bool { - match self { - Step::Welcome => true, - Step::Radio { selection } => *selection == Some(Language::Rust), - Step::Slider { .. } => true, - Step::Text { .. } => true, - Step::Image { .. } => true, - Step::RowsAndColumns { .. } => true, - Step::Debugger => true, - Step::End => false, - } - } - - fn view(&mut self, debug: bool) -> Element { - match self { - Step::Welcome => Self::welcome().into(), - Step::Radio { selection } => Self::radio(*selection).into(), - Step::Slider { state, value } => Self::slider(state, *value).into(), - Step::Text { - size_slider, - size, - color_sliders, - color, - } => Self::text(size_slider, *size, color_sliders, *color).into(), - Step::Image { width, slider } => Self::image(*width, slider).into(), - Step::RowsAndColumns { - layout, - spacing_slider, - spacing, - } => { - Self::rows_and_columns(*layout, spacing_slider, *spacing).into() - } - Step::Debugger => Self::debugger(debug).into(), - Step::End => Self::end().into(), - } - } - - fn container(title: &str) -> Column<'a, StepMessage> { - Column::new() - .spacing(20) - .align_items(Align::Stretch) - .push(Text::new(title).size(50)) - } - - fn welcome() -> Column<'a, StepMessage> { - Self::container("Welcome!") - .push(Text::new( - "This a simple tour meant to showcase a bunch of widgets that \ - can be easily implemented on top of Iced.", - )) - .push(Text::new( - "Iced is a renderer-agnostic GUI library for Rust focused on \ - simplicity and type-safety. It is heavily inspired by Elm.", - )) - .push(Text::new( - "It was originally born as part of Coffee, an opinionated \ - 2D game engine for Rust.", - )) - .push(Text::new( - "Iced does not provide a built-in renderer. This example runs \ - on WebAssembly using dodrio, an experimental VDOM library \ - for Rust.", - )) - .push(Text::new( - "You will need to interact with the UI in order to reach the \ - end!", - )) - } - - fn slider( - state: &'a mut slider::State, - value: u16, - ) -> Column<'a, StepMessage> { - Self::container("Slider") - .push(Text::new( - "A slider allows you to smoothly select a value from a range \ - of values.", - )) - .push(Text::new( - "The following slider lets you choose an integer from \ - 0 to 100:", - )) - .push(Slider::new( - state, - 0.0..=100.0, - value as f32, - StepMessage::SliderChanged, - )) - .push( - Text::new(&value.to_string()) - .horizontal_alignment(HorizontalAlignment::Center), - ) - } - - fn rows_and_columns( - layout: Layout, - spacing_slider: &'a mut slider::State, - spacing: u16, - ) -> Column<'a, StepMessage> { - let row_radio = Radio::new( - Layout::Row, - "Row", - Some(layout), - StepMessage::LayoutChanged, - ); - - let column_radio = Radio::new( - Layout::Column, - "Column", - Some(layout), - StepMessage::LayoutChanged, - ); - - let layout_section: Element<_> = match layout { - Layout::Row => Row::new() - .spacing(spacing) - .push(row_radio) - .push(column_radio) - .into(), - Layout::Column => Column::new() - .spacing(spacing) - .push(row_radio) - .push(column_radio) - .into(), - }; - - let spacing_section = Column::new() - .spacing(10) - .push(Slider::new( - spacing_slider, - 0.0..=80.0, - spacing as f32, - StepMessage::SpacingChanged, - )) - .push( - Text::new(&format!("{} px", spacing)) - .horizontal_alignment(HorizontalAlignment::Center), - ); - - Self::container("Rows and columns") - .spacing(spacing) - .push(Text::new( - "Iced uses a layout model based on flexbox to position UI \ - elements.", - )) - .push(Text::new( - "Rows and columns can be used to distribute content \ - horizontally or vertically, respectively.", - )) - .push(layout_section) - .push(Text::new( - "You can also easily change the spacing between elements:", - )) - .push(spacing_section) - } - - fn text( - size_slider: &'a mut slider::State, - size: u16, - color_sliders: &'a mut [slider::State; 3], - color: Color, - ) -> Column<'a, StepMessage> { - let size_section = Column::new() - .padding(20) - .spacing(20) - .push(Text::new("You can change its size:")) - .push( - Text::new(&format!("This text is {} pixels", size)).size(size), - ) - .push(Slider::new( - size_slider, - 10.0..=70.0, - size as f32, - StepMessage::TextSizeChanged, - )); - - let [red, green, blue] = color_sliders; - let color_section = Column::new() - .padding(20) - .spacing(20) - .push(Text::new("And its color:")) - .push(Text::new(&format!("{:?}", color)).color(color)) - .push( - Row::new() - .spacing(10) - .push(Slider::new(red, 0.0..=1.0, color.r, move |r| { - StepMessage::TextColorChanged(Color { r, ..color }) - })) - .push(Slider::new(green, 0.0..=1.0, color.g, move |g| { - StepMessage::TextColorChanged(Color { g, ..color }) - })) - .push(Slider::new(blue, 0.0..=1.0, color.b, move |b| { - StepMessage::TextColorChanged(Color { b, ..color }) - })), - ); - - Self::container("Text") - .push(Text::new( - "Text is probably the most essential widget for your UI. \ - It will try to adapt to the dimensions of its container.", - )) - .push(size_section) - .push(color_section) - } - - fn radio(selection: Option) -> Column<'a, StepMessage> { - let question = Column::new() - .padding(20) - .spacing(10) - .push(Text::new("Iced is written in...").size(24)) - .push(Language::all().iter().cloned().fold( - Column::new().padding(10).spacing(20), - |choices, language| { - choices.push(Radio::new( - language, - language.into(), - selection, - StepMessage::LanguageSelected, - )) - }, - )); - - Self::container("Radio button") - .push(Text::new( - "A radio button is normally used to represent a choice... \ - Surprise test!", - )) - .push(question) - .push(Text::new( - "Iced works very well with iterators! The list above is \ - basically created by folding a column over the different \ - choices, creating a radio button for each one of them!", - )) - } - - fn image( - width: u16, - slider: &'a mut slider::State, - ) -> Column<'a, StepMessage> { - Self::container("Image") - .push(Text::new("An image that tries to keep its aspect ratio.")) - .push( - Image::new("resources/ferris.png") - .width(Length::Units(width)) - .align_self(Align::Center), - ) - .push(Slider::new( - slider, - 100.0..=500.0, - width as f32, - StepMessage::ImageWidthChanged, - )) - .push( - Text::new(&format!("Width: {} px", width.to_string())) - .horizontal_alignment(HorizontalAlignment::Center), - ) - } - - fn debugger(debug: bool) -> Column<'a, StepMessage> { - Self::container("Debugger") - .push(Text::new( - "You can ask Iced to visually explain the layouting of the \ - different elements comprising your UI!", - )) - .push(Text::new( - "Give it a shot! Check the following checkbox to be able to \ - see element boundaries.", - )) - .push(Checkbox::new( - debug, - "Explain layout", - StepMessage::DebugToggled, - )) - .push(Text::new("Feel free to go back and take a look.")) - } - - fn end() -> Column<'a, StepMessage> { - Self::container("You reached the end!") - .push(Text::new( - "This tour will be updated as more features are added.", - )) - .push(Text::new("Make sure to keep an eye on it!")) - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Language { - Rust, - Elm, - Ruby, - Haskell, - C, - Other, -} - -impl Language { - fn all() -> [Language; 6] { - [ - Language::C, - Language::Elm, - Language::Ruby, - Language::Haskell, - Language::Rust, - Language::Other, - ] - } -} - -impl From for &str { - fn from(language: Language) -> &'static str { - match language { - Language::Rust => "Rust", - Language::Elm => "Elm", - Language::Ruby => "Ruby", - Language::Haskell => "Haskell", - Language::C => "C", - Language::Other => "Other", - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Layout { - Row, - Column, -} diff --git a/examples/tour/src/web.rs b/examples/tour/src/web.rs deleted file mode 100644 index a0a3060f..00000000 --- a/examples/tour/src/web.rs +++ /dev/null @@ -1,33 +0,0 @@ -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>> { - self.update(message); - - None - } - - fn view(&mut self) -> iced_web::Element { - self.view() - } -} diff --git a/examples/tour/src/widget.rs b/examples/tour/src/widget.rs deleted file mode 100644 index 9c2c4d5b..00000000 --- a/examples/tour/src/widget.rs +++ /dev/null @@ -1,5 +0,0 @@ -#[cfg(target_arch = "wasm32")] -pub use iced_web::*; - -#[cfg(not(target_arch = "wasm32"))] -pub use crate::iced_ggez::*; diff --git a/src/lib.rs b/src/lib.rs index e69de29b..d1e98c4c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -0,0 +1,23 @@ +pub use iced_wgpu::Renderer; +pub use iced_winit::{ + button, slider, text, Align, Button, Checkbox, Color, Image, Justify, + Length, Radio, Slider, Text, +}; + +pub type Element<'a, Message> = iced_winit::Element<'a, Message, Renderer>; +pub type Row<'a, Message> = iced_winit::Row<'a, Message, Renderer>; +pub type Column<'a, Message> = iced_winit::Column<'a, Message, Renderer>; + +pub trait UserInterface { + type Message; + + fn update(&mut self, message: Self::Message); + + fn view(&mut self) -> Element; + + fn run(self) + where + Self: Sized, + { + } +} diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml new file mode 100644 index 00000000..0ab7a955 --- /dev/null +++ b/wgpu/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "iced_wgpu" +version = "0.1.0-alpha" +authors = ["Héctor Ramón Jiménez "] +edition = "2018" +description = "A wgpu renderer for Iced" +license = "MIT" +repository = "https://github.com/hecrj/iced" + +[dependencies] +iced_native = { version = "0.1.0-alpha", path = "../native" } +wgpu = "0.3" diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs new file mode 100644 index 00000000..7aa053bc --- /dev/null +++ b/wgpu/src/lib.rs @@ -0,0 +1,87 @@ +use iced_native::{ + button, checkbox, image, radio, renderer::Debugger, slider, text, Button, + Checkbox, Color, Image, Layout, MouseCursor, Node, Point, Radio, Slider, + Style, Text, +}; + +pub struct Renderer; + +impl text::Renderer for Renderer { + fn node(&self, _text: &Text) -> Node { + Node::new(Style::default()) + } + + fn draw(&mut self, _text: &Text, _layout: Layout<'_>) {} +} + +impl checkbox::Renderer for Renderer { + fn node(&mut self, _checkbox: &Checkbox) -> Node { + Node::new(Style::default()) + } + + fn draw( + &mut self, + _checkbox: &Checkbox, + _layout: Layout<'_>, + _cursor_position: Point, + ) -> MouseCursor { + MouseCursor::OutOfBounds + } +} + +impl radio::Renderer for Renderer { + fn node(&mut self, _checkbox: &Radio) -> Node { + Node::new(Style::default()) + } + + fn draw( + &mut self, + _radio: &Radio, + _layout: Layout<'_>, + _cursor_position: Point, + ) -> MouseCursor { + MouseCursor::OutOfBounds + } +} + +impl slider::Renderer for Renderer { + fn node(&self, _slider: &Slider) -> Node { + Node::new(Style::default()) + } + + fn draw( + &mut self, + _slider: &Slider, + _layout: Layout<'_>, + _cursor_position: Point, + ) -> MouseCursor { + MouseCursor::OutOfBounds + } +} + +impl image::Renderer<&str> for Renderer { + fn node(&mut self, _image: &Image<&str>) -> Node { + Node::new(Style::default()) + } + + fn draw(&mut self, _checkbox: &Image<&str>, _layout: Layout<'_>) {} +} + +impl button::Renderer for Renderer { + fn node(&self, _button: &Button) -> Node { + Node::new(Style::default()) + } + + fn draw( + &mut self, + _button: &Button, + _layout: Layout<'_>, + _cursor_position: Point, + ) -> MouseCursor { + MouseCursor::OutOfBounds + } +} + +impl Debugger for Renderer { + fn explain(&mut self, _layout: &Layout<'_>, _color: Color) {} +} diff --git a/winit/Cargo.toml b/winit/Cargo.toml new file mode 100644 index 00000000..d7f61503 --- /dev/null +++ b/winit/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "iced_winit" +version = "0.1.0-alpha" +authors = ["Héctor Ramón Jiménez "] +edition = "2018" +description = "A winit runtime for Iced" +license = "MIT" +repository = "https://github.com/hecrj/iced" + +[dependencies] +iced_native = { version = "0.1.0-alpha", path = "../native" } +winit = "0.20.0-alpha3" diff --git a/winit/src/lib.rs b/winit/src/lib.rs new file mode 100644 index 00000000..0e5846c5 --- /dev/null +++ b/winit/src/lib.rs @@ -0,0 +1 @@ +pub use iced_native::*; -- cgit From 63294088ad6e1523c6b7623c08f82af2812a5531 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 3 Oct 2019 00:08:16 +0200 Subject: Open a window using `winit` --- src/lib.rs | 31 +++++++++++++++++++++++++++++-- winit/src/lib.rs | 1 + 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d1e98c4c..4344a50b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ pub use iced_wgpu::Renderer; pub use iced_winit::{ - button, slider, text, Align, Button, Checkbox, Color, Image, Justify, - Length, Radio, Slider, Text, + button, slider, text, winit, Align, Button, Checkbox, Color, Image, + Justify, Length, Radio, Slider, Text, }; pub type Element<'a, Message> = iced_winit::Element<'a, Message, Renderer>; @@ -19,5 +19,32 @@ pub trait UserInterface { where Self: Sized, { + use winit::{ + event::{Event, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::WindowBuilder, + }; + + let event_loop = EventLoop::new(); + + // TODO: Ask for window settings and configure this properly + let window = WindowBuilder::new() + .build(&event_loop) + .expect("Open window"); + + event_loop.run(move |event, _, control_flow| match event { + Event::EventsCleared => { + window.request_redraw(); + } + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => { + *control_flow = ControlFlow::Exit; + } + _ => { + *control_flow = ControlFlow::Poll; + } + }) } } diff --git a/winit/src/lib.rs b/winit/src/lib.rs index 0e5846c5..54a0bd9a 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -1 +1,2 @@ pub use iced_native::*; +pub use winit; -- cgit From 8bb33cd5a0b876a5e24108604be2cecd4efad3ef Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 3 Oct 2019 00:23:08 +0200 Subject: Initialize `wgpu` We only enable the `vulkan` feature for now. --- Cargo.toml | 3 +++ src/lib.rs | 2 ++ wgpu/Cargo.toml | 4 ++++ wgpu/src/lib.rs | 39 ++++++++++++++++++++++++++++++++++++++- 4 files changed, 47 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4792b3ad..08d51a0e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,9 @@ members = [ "examples/tour", ] +[features] +vulkan = ["iced_wgpu/vulkan"] + [target.'cfg(not(target_arch = "wasm32"))'.dependencies] iced_winit = { version = "0.1.0-alpha", path = "winit" } iced_wgpu = { version = "0.1.0-alpha", path = "wgpu" } diff --git a/src/lib.rs b/src/lib.rs index 4344a50b..197212e7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,6 +32,8 @@ pub trait UserInterface { .build(&event_loop) .expect("Open window"); + let renderer = Renderer::new(&window); + event_loop.run(move |event, _, control_flow| match event { Event::EventsCleared => { window.request_redraw(); diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 0ab7a955..879def28 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -7,6 +7,10 @@ description = "A wgpu renderer for Iced" license = "MIT" repository = "https://github.com/hecrj/iced" +[features] +vulkan = ["wgpu/vulkan"] + [dependencies] iced_native = { version = "0.1.0-alpha", path = "../native" } wgpu = "0.3" +raw-window-handle = "0.1" diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 7aa053bc..502a2614 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -4,7 +4,44 @@ use iced_native::{ Style, Text, }; -pub struct Renderer; +use raw_window_handle::HasRawWindowHandle; +use wgpu::{ + Adapter, Device, DeviceDescriptor, Extensions, Instance, Limits, + PowerPreference, RequestAdapterOptions, Surface, +}; + +pub struct Renderer { + instance: Instance, + surface: Surface, + adapter: Adapter, + device: Device, +} + +impl Renderer { + pub fn new(window: &W) -> Self { + let instance = Instance::new(); + + let adapter = instance.request_adapter(&RequestAdapterOptions { + power_preference: PowerPreference::LowPower, + }); + + let device = adapter.request_device(&DeviceDescriptor { + extensions: Extensions { + anisotropic_filtering: false, + }, + limits: Limits { max_bind_groups: 1 }, + }); + + let surface = instance.create_surface(window.raw_window_handle()); + + Self { + instance, + surface, + adapter, + device, + } + } +} impl text::Renderer for Renderer { fn node(&self, _text: &Text) -> Node { -- cgit From fc38119be3ffccc35c90971e956e8866e8b97e85 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 3 Oct 2019 00:34:15 +0200 Subject: Clear the window properly on redraw --- src/lib.rs | 16 ++++++++++++++-- wgpu/src/lib.rs | 50 +++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 61 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 197212e7..74048fd8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,12 +32,24 @@ pub trait UserInterface { .build(&event_loop) .expect("Open window"); - let renderer = Renderer::new(&window); + let size = window.inner_size().to_physical(window.hidpi_factor());; + + let mut renderer = + Renderer::new(&window, size.width as u32, size.height as u32); + + window.request_redraw(); event_loop.run(move |event, _, control_flow| match event { Event::EventsCleared => { window.request_redraw(); } + Event::WindowEvent { + event: WindowEvent::RedrawRequested, + .. + } => { + println!("Redrawing"); + renderer.draw(); + } Event::WindowEvent { event: WindowEvent::CloseRequested, .. @@ -45,7 +57,7 @@ pub trait UserInterface { *control_flow = ControlFlow::Exit; } _ => { - *control_flow = ControlFlow::Poll; + *control_flow = ControlFlow::Wait; } }) } diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 502a2614..1e11749b 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -6,8 +6,9 @@ use iced_native::{ use raw_window_handle::HasRawWindowHandle; use wgpu::{ - Adapter, Device, DeviceDescriptor, Extensions, Instance, Limits, - PowerPreference, RequestAdapterOptions, Surface, + Adapter, CommandEncoderDescriptor, Device, DeviceDescriptor, Extensions, + Instance, Limits, PowerPreference, RequestAdapterOptions, Surface, + SwapChain, SwapChainDescriptor, TextureFormat, TextureUsage, }; pub struct Renderer { @@ -15,10 +16,15 @@ pub struct Renderer { surface: Surface, adapter: Adapter, device: Device, + swap_chain: SwapChain, } impl Renderer { - pub fn new(window: &W) -> Self { + pub fn new( + window: &W, + width: u32, + height: u32, + ) -> Self { let instance = Instance::new(); let adapter = instance.request_adapter(&RequestAdapterOptions { @@ -34,13 +40,51 @@ impl Renderer { let surface = instance.create_surface(window.raw_window_handle()); + let swap_chain = device.create_swap_chain( + &surface, + &SwapChainDescriptor { + usage: TextureUsage::OUTPUT_ATTACHMENT, + format: TextureFormat::Bgra8UnormSrgb, + width, + height, + present_mode: wgpu::PresentMode::Vsync, + }, + ); + Self { instance, surface, adapter, device, + swap_chain, } } + + pub fn draw(&mut self) { + let frame = self.swap_chain.get_next_texture(); + + let mut encoder = self + .device + .create_command_encoder(&CommandEncoderDescriptor { todo: 0 }); + + let _ = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor { + attachment: &frame.view, + resolve_target: None, + load_op: wgpu::LoadOp::Clear, + store_op: wgpu::StoreOp::Store, + clear_color: wgpu::Color { + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + }, + }], + depth_stencil_attachment: None, + }); + + self.device.get_queue().submit(&[encoder.finish()]); + } } impl text::Renderer for Renderer { -- cgit From ebb8dbb065c2cb5390e35237eda122cbe9ef5f42 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 3 Oct 2019 01:59:16 +0200 Subject: Draft UI event loop --- src/lib.rs | 41 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 74048fd8..3a7825bc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,9 +15,9 @@ pub trait UserInterface { fn view(&mut self) -> Element; - fn run(self) + fn run(mut self) where - Self: Sized, + Self: 'static + Sized, { use winit::{ event::{Event, WindowEvent}, @@ -37,10 +37,47 @@ pub trait UserInterface { let mut renderer = Renderer::new(&window, size.width as u32, size.height as u32); + let mut cache = Some(iced_winit::Cache::default()); + let mut events = Vec::new(); + window.request_redraw(); event_loop.run(move |event, _, control_flow| match event { Event::EventsCleared => { + // TODO: Once we remove lifetimes from widgets, we will be able + // to keep user interfaces alive between events. + // + // This will allow us to only rebuild when a message is handled. + let mut user_interface = iced_winit::UserInterface::build( + self.view(), + cache.take().unwrap(), + &mut renderer, + ); + + let messages = user_interface.update(events.drain(..)); + + if messages.is_empty() { + let _ = user_interface.draw(&mut renderer); + + cache = Some(user_interface.into_cache()); + } else { + let temp_cache = user_interface.into_cache(); + + for message in messages { + self.update(message); + } + + let user_interface = iced_winit::UserInterface::build( + self.view(), + temp_cache, + &mut renderer, + ); + + let _ = user_interface.draw(&mut renderer); + + cache = Some(user_interface.into_cache()); + }; + window.request_redraw(); } Event::WindowEvent { -- cgit From 5204cc8c8bc8361a11815d7c711b7da10a6ca8e5 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 4 Oct 2019 22:46:57 +0200 Subject: Count redraws and add a couple of TODOs --- src/lib.rs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 3a7825bc..93f8110e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,15 +39,19 @@ pub trait UserInterface { let mut cache = Some(iced_winit::Cache::default()); let mut events = Vec::new(); + let mut redraws = 0; window.request_redraw(); event_loop.run(move |event, _, control_flow| match event { Event::EventsCleared => { - // TODO: Once we remove lifetimes from widgets, we will be able - // to keep user interfaces alive between events. + // TODO: We should find out a way to keep a user interface + // alive between events while still being able to drop it and + // rebuild it only when a message is handled. // - // This will allow us to only rebuild when a message is handled. + // The borrow checker does not seem to like it when I try this, + // even though I am not technically double borrowing at any + // point. let mut user_interface = iced_winit::UserInterface::build( self.view(), cache.take().unwrap(), @@ -61,6 +65,8 @@ pub trait UserInterface { cache = Some(user_interface.into_cache()); } else { + // When there are messages, we are forced to rebuild twice + // for now :^) let temp_cache = user_interface.into_cache(); for message in messages { @@ -76,7 +82,7 @@ pub trait UserInterface { let _ = user_interface.draw(&mut renderer); cache = Some(user_interface.into_cache()); - }; + } window.request_redraw(); } @@ -84,8 +90,13 @@ pub trait UserInterface { event: WindowEvent::RedrawRequested, .. } => { - println!("Redrawing"); + println!("Redrawing {}", redraws); renderer.draw(); + + redraws += 1; + + // TODO: Handle animations! + // Maybe we can use `ControlFlow::WaitUntil` for this. } Event::WindowEvent { event: WindowEvent::CloseRequested, -- cgit From a7d11944039a1b5ea5b72256e8d15367d99e6010 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 5 Oct 2019 03:56:18 +0200 Subject: Add `Renderer` and `Primitive` concepts --- native/src/element.rs | 48 ++++++++++++++++++----------- native/src/lib.rs | 11 ++++--- native/src/renderer.rs | 16 ++++++++-- native/src/user_interface.rs | 62 ++++++++++++++++++++++++++++++++------ native/src/widget.rs | 17 +++++------ native/src/widget/button.rs | 8 ++--- native/src/widget/checkbox.rs | 8 ++--- native/src/widget/column.rs | 34 ++++++++++----------- native/src/widget/image.rs | 11 ++++--- native/src/widget/radio.rs | 8 ++--- native/src/widget/row.rs | 30 +++++++++---------- native/src/widget/slider.rs | 6 ++-- native/src/widget/text.rs | 12 ++++---- src/lib.rs | 10 +++---- wgpu/src/lib.rs | 70 ++++++++++++++++++++++++++++++++++--------- 15 files changed, 226 insertions(+), 125 deletions(-) diff --git a/native/src/element.rs b/native/src/element.rs index dd5ce621..4ea2379f 100644 --- a/native/src/element.rs +++ b/native/src/element.rs @@ -1,8 +1,6 @@ use stretch::{geometry, result}; -use crate::{ - renderer, Color, Event, Hasher, Layout, MouseCursor, Node, Point, Widget, -}; +use crate::{renderer, Color, Event, Hasher, Layout, Node, Point, Widget}; /// A generic [`Widget`]. /// @@ -27,7 +25,10 @@ impl<'a, Message, Renderer> std::fmt::Debug for Element<'a, Message, Renderer> { } } -impl<'a, Message, Renderer> Element<'a, Message, Renderer> { +impl<'a, Message, Renderer> Element<'a, Message, Renderer> +where + Renderer: crate::Renderer, +{ /// Create a new [`Element`] containing the given [`Widget`]. /// /// [`Element`]: struct.Element.html @@ -102,10 +103,21 @@ impl<'a, Message, Renderer> Element<'a, Message, Renderer> { /// # /// # mod iced_wgpu { /// # use iced_native::{ - /// # button, Button, MouseCursor, Node, Point, Rectangle, Style, Layout + /// # button, row, Button, MouseCursor, Node, Point, Rectangle, Style, Layout, Row /// # }; /// # pub struct Renderer; /// # + /// # impl iced_native::Renderer for Renderer { type Primitive = (); } + /// # + /// # impl iced_native::row::Renderer for Renderer { + /// # fn draw( + /// # &mut self, + /// # _column: &Row<'_, Message, Self>, + /// # _layout: Layout<'_>, + /// # _cursor_position: Point, + /// # ) {} + /// # } + /// # /// # impl button::Renderer for Renderer { /// # fn node(&self, _button: &Button<'_, Message>) -> Node { /// # Node::new(Style::default()) @@ -116,9 +128,7 @@ impl<'a, Message, Renderer> Element<'a, Message, Renderer> { /// # _button: &Button<'_, Message>, /// # _layout: Layout<'_>, /// # _cursor_position: Point, - /// # ) -> MouseCursor { - /// # MouseCursor::OutOfBounds - /// # } + /// # ) {} /// # } /// # } /// # @@ -268,6 +278,7 @@ impl<'a, A, B, Renderer> Map<'a, A, B, Renderer> { impl<'a, A, B, Renderer> Widget for Map<'a, A, B, Renderer> where A: Copy, + Renderer: crate::Renderer, { fn node(&self, renderer: &mut Renderer) -> Node { self.widget.node(renderer) @@ -300,7 +311,7 @@ where renderer: &mut Renderer, layout: Layout<'_>, cursor_position: Point, - ) -> MouseCursor { + ) -> Renderer::Primitive { self.widget.draw(renderer, layout, cursor_position) } @@ -309,14 +320,14 @@ where } } -struct Explain<'a, Message, Renderer: renderer::Debugger> { +struct Explain<'a, Message, Renderer: crate::Renderer> { element: Element<'a, Message, Renderer>, color: Color, } impl<'a, Message, Renderer> std::fmt::Debug for Explain<'a, Message, Renderer> where - Renderer: renderer::Debugger, + Renderer: crate::Renderer, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Explain") @@ -327,7 +338,7 @@ where impl<'a, Message, Renderer> Explain<'a, Message, Renderer> where - Renderer: renderer::Debugger, + Renderer: crate::Renderer, { fn new(element: Element<'a, Message, Renderer>, color: Color) -> Self { Explain { element, color } @@ -337,7 +348,7 @@ where impl<'a, Message, Renderer> Widget for Explain<'a, Message, Renderer> where - Renderer: renderer::Debugger, + Renderer: crate::Renderer + renderer::Debugger, { fn node(&self, renderer: &mut Renderer) -> Node { self.element.widget.node(renderer) @@ -360,10 +371,13 @@ where renderer: &mut Renderer, layout: Layout<'_>, cursor_position: Point, - ) -> MouseCursor { - renderer.explain(&layout, self.color); - - self.element.widget.draw(renderer, layout, cursor_position) + ) -> Renderer::Primitive { + renderer.explain( + self.element.widget.as_ref(), + layout, + cursor_position, + self.color, + ) } fn hash_layout(&self, state: &mut Hasher) { diff --git a/native/src/lib.rs b/native/src/lib.rs index 39da4943..f6b6f807 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -83,6 +83,10 @@ //! # //! # pub struct Renderer {} //! # +//! # impl iced_native::Renderer for Renderer { +//! # type Primitive = (); +//! # } +//! # //! # impl button::Renderer for Renderer { //! # fn node( //! # &self, @@ -96,9 +100,7 @@ //! # _button: &Button<'_, Message>, //! # _layout: Layout<'_>, //! # _cursor_position: Point, -//! # ) -> MouseCursor { -//! # MouseCursor::OutOfBounds -//! # } +//! # ) {} //! # } //! # //! # impl text::Renderer for Renderer { @@ -192,7 +194,7 @@ //! [documentation]: https://docs.rs/iced //! [examples]: https://github.com/hecrj/iced/tree/master/examples //! [`UserInterface`]: struct.UserInterface.html -#![deny(missing_docs)] +//#![deny(missing_docs)] #![deny(missing_debug_implementations)] #![deny(unused_results)] #![deny(unsafe_code)] @@ -223,6 +225,7 @@ pub use hasher::Hasher; pub use layout::Layout; pub use mouse_cursor::MouseCursor; pub use node::Node; +pub use renderer::Renderer; pub use style::Style; pub use user_interface::{Cache, UserInterface}; pub use widget::*; diff --git a/native/src/renderer.rs b/native/src/renderer.rs index 2244f00b..d16a0289 100644 --- a/native/src/renderer.rs +++ b/native/src/renderer.rs @@ -17,12 +17,16 @@ //! [`text::Renderer`]: ../widget/text/trait.Renderer.html //! [`Checkbox`]: ../widget/checkbox/struct.Checkbox.html //! [`checkbox::Renderer`]: ../widget/checkbox/trait.Renderer.html -use crate::{Color, Layout}; +use crate::{Color, Layout, Point, Widget}; + +pub trait Renderer { + type Primitive; +} /// A renderer able to graphically explain a [`Layout`]. /// /// [`Layout`]: ../struct.Layout.html -pub trait Debugger { +pub trait Debugger: Renderer { /// Explains the [`Layout`] of an [`Element`] for debugging purposes. /// /// This will be called when [`Element::explain`] has been used. It should @@ -34,5 +38,11 @@ pub trait Debugger { /// [`Layout`]: struct.Layout.html /// [`Element`]: struct.Element.html /// [`Element::explain`]: struct.Element.html#method.explain - fn explain(&mut self, layout: &Layout<'_>, color: Color); + fn explain( + &mut self, + widget: &dyn Widget, + layout: Layout<'_>, + cursor_position: Point, + color: Color, + ) -> Self::Primitive; } diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 4bfacb2e..812ad598 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -1,7 +1,7 @@ -use crate::{input::mouse, Column, Element, Event, Layout, MouseCursor, Point}; +use crate::{input::mouse, Element, Event, Layout, Point}; use std::hash::Hasher; -use stretch::result; +use stretch::{geometry, result}; /// A set of interactive graphical elements with a specific [`Layout`]. /// @@ -19,7 +19,10 @@ pub struct UserInterface<'a, Message, Renderer> { cursor_position: Point, } -impl<'a, Message, Renderer> UserInterface<'a, Message, Renderer> { +impl<'a, Message, Renderer> UserInterface<'a, Message, Renderer> +where + Renderer: crate::Renderer, +{ /// Builds a user interface for an [`Element`]. /// /// It is able to avoid expensive computations when using a [`Cache`] @@ -44,6 +47,19 @@ impl<'a, Message, Renderer> UserInterface<'a, Message, Renderer> { /// # impl Renderer { /// # pub fn new() -> Self { Renderer } /// # } + /// # + /// # impl iced_native::Renderer for Renderer { type Primitive = (); } + /// # + /// # impl iced_native::column::Renderer for Renderer { + /// # fn draw( + /// # &mut self, + /// # _column: &iced_native::Column<'_, Message, Self>, + /// # _layout: iced_native::Layout<'_>, + /// # _cursor_position: iced_native::Point, + /// # ) -> Self::Primitive { + /// # () + /// # } + /// # } /// # } /// # /// # use iced_native::Column; @@ -127,6 +143,19 @@ impl<'a, Message, Renderer> UserInterface<'a, Message, Renderer> { /// # impl Renderer { /// # pub fn new() -> Self { Renderer } /// # } + /// # + /// # impl iced_native::Renderer for Renderer { type Primitive = (); } + /// # + /// # impl iced_native::column::Renderer for Renderer { + /// # fn draw( + /// # &mut self, + /// # _column: &iced_native::Column<'_, Message, Self>, + /// # _layout: iced_native::Layout<'_>, + /// # _cursor_position: iced_native::Point, + /// # ) -> Self::Primitive { + /// # () + /// # } + /// # } /// # } /// # /// # use iced_native::Column; @@ -212,6 +241,19 @@ impl<'a, Message, Renderer> UserInterface<'a, Message, Renderer> { /// # impl Renderer { /// # pub fn new() -> Self { Renderer } /// # } + /// # + /// # impl iced_native::Renderer for Renderer { type Primitive = (); } + /// # + /// # impl iced_native::column::Renderer for Renderer { + /// # fn draw( + /// # &mut self, + /// # _column: &iced_native::Column<'_, Message, Self>, + /// # _layout: iced_native::Layout<'_>, + /// # _cursor_position: iced_native::Point, + /// # ) -> Self::Primitive { + /// # () + /// # } + /// # } /// # } /// # /// # use iced_native::Column; @@ -254,7 +296,7 @@ impl<'a, Message, Renderer> UserInterface<'a, Message, Renderer> { /// // Flush rendering operations... /// } /// ``` - pub fn draw(&self, renderer: &mut Renderer) -> MouseCursor { + pub fn draw(&self, renderer: &mut Renderer) -> Renderer::Primitive { self.root.widget.draw( renderer, Layout::new(&self.layout), @@ -295,14 +337,16 @@ impl Cache { /// [`Cache`]: struct.Cache.html /// [`UserInterface`]: struct.UserInterface.html pub fn new() -> Cache { - let root: Element<'_, (), ()> = Column::new().into(); + use crate::{Node, Style}; - let hasher = &mut crate::Hasher::default(); - root.hash_layout(hasher); + let empty_node = Node::new(Style::default()); Cache { - hash: hasher.finish(), - layout: root.compute_layout(&mut ()), + hash: 0, + layout: empty_node + .0 + .compute_layout(geometry::Size::undefined()) + .unwrap(), cursor_position: Point::new(0.0, 0.0), } } diff --git a/native/src/widget.rs b/native/src/widget.rs index 9b770454..eff098a6 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -20,13 +20,12 @@ //! //! [`Widget`]: trait.Widget.html //! [renderer]: ../renderer/index.html -mod column; -mod row; - pub mod button; pub mod checkbox; +pub mod column; pub mod image; pub mod radio; +pub mod row; pub mod slider; pub mod text; @@ -47,7 +46,7 @@ pub use slider::Slider; #[doc(no_inline)] pub use text::Text; -use crate::{Event, Hasher, Layout, MouseCursor, Node, Point}; +use crate::{Event, Hasher, Layout, Node, Point}; /// A component that displays information and allows interaction. /// @@ -56,7 +55,10 @@ use crate::{Event, Hasher, Layout, MouseCursor, Node, Point}; /// /// [`Widget`]: trait.Widget.html /// [`Element`]: ../struct.Element.html -pub trait Widget: std::fmt::Debug { +pub trait Widget: std::fmt::Debug +where + Renderer: crate::Renderer, +{ /// Returns the [`Node`] of the [`Widget`]. /// /// This [`Node`] is used by the runtime to compute the [`Layout`] of the @@ -69,16 +71,13 @@ pub trait Widget: std::fmt::Debug { /// Draws the [`Widget`] using the associated `Renderer`. /// - /// It must return the [`MouseCursor`] state for the [`Widget`]. - /// /// [`Widget`]: trait.Widget.html - /// [`MouseCursor`]: ../enum.MouseCursor.html fn draw( &self, renderer: &mut Renderer, layout: Layout<'_>, cursor_position: Point, - ) -> MouseCursor; + ) -> Renderer::Primitive; /// Computes the _layout_ hash of the [`Widget`]. /// diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs index 7b5c4a86..5ae4e045 100644 --- a/native/src/widget/button.rs +++ b/native/src/widget/button.rs @@ -7,7 +7,7 @@ //! [`Class`]: enum.Class.html use crate::input::{mouse, ButtonState}; -use crate::{Element, Event, Hasher, Layout, MouseCursor, Node, Point, Widget}; +use crate::{Element, Event, Hasher, Layout, Node, Point, Widget}; use std::hash::Hash; pub use iced_core::button::*; @@ -63,7 +63,7 @@ where renderer: &mut Renderer, layout: Layout<'_>, cursor_position: Point, - ) -> MouseCursor { + ) -> Renderer::Primitive { renderer.draw(&self, layout, cursor_position) } @@ -81,7 +81,7 @@ where /// /// [`Button`]: struct.Button.html /// [renderer]: ../../renderer/index.html -pub trait Renderer { +pub trait Renderer: crate::Renderer { /// Creates a [`Node`] for the provided [`Button`]. /// /// [`Node`]: ../../struct.Node.html @@ -96,7 +96,7 @@ pub trait Renderer { button: &Button<'_, Message>, layout: Layout<'_>, cursor_position: Point, - ) -> MouseCursor; + ) -> Self::Primitive; } impl<'a, Message, Renderer> From> diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs index 3e307f64..1954305a 100644 --- a/native/src/widget/checkbox.rs +++ b/native/src/widget/checkbox.rs @@ -2,7 +2,7 @@ use std::hash::Hash; use crate::input::{mouse, ButtonState}; -use crate::{Element, Event, Hasher, Layout, MouseCursor, Node, Point, Widget}; +use crate::{Element, Event, Hasher, Layout, Node, Point, Widget}; pub use iced_core::Checkbox; @@ -43,7 +43,7 @@ where renderer: &mut Renderer, layout: Layout<'_>, cursor_position: Point, - ) -> MouseCursor { + ) -> Renderer::Primitive { renderer.draw(&self, layout, cursor_position) } @@ -59,7 +59,7 @@ where /// /// [`Checkbox`]: struct.Checkbox.html /// [renderer]: ../../renderer/index.html -pub trait Renderer { +pub trait Renderer: crate::Renderer { /// Creates a [`Node`] for the provided [`Checkbox`]. /// /// [`Node`]: ../../struct.Node.html @@ -80,7 +80,7 @@ pub trait Renderer { checkbox: &Checkbox, layout: Layout<'_>, cursor_position: Point, - ) -> MouseCursor; + ) -> Self::Primitive; } impl<'a, Message, Renderer> From> diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs index 9da2e161..6228d711 100644 --- a/native/src/widget/column.rs +++ b/native/src/widget/column.rs @@ -1,8 +1,6 @@ use std::hash::Hash; -use crate::{ - Element, Event, Hasher, Layout, MouseCursor, Node, Point, Style, Widget, -}; +use crate::{Element, Event, Hasher, Layout, Node, Point, Style, Widget}; /// A container that distributes its contents vertically. pub type Column<'a, Message, Renderer> = @@ -10,6 +8,8 @@ pub type Column<'a, Message, Renderer> = impl<'a, Message, Renderer> Widget for Column<'a, Message, Renderer> +where + Renderer: self::Renderer, { fn node(&self, renderer: &mut Renderer) -> Node { let mut children: Vec = self @@ -70,21 +70,8 @@ impl<'a, Message, Renderer> Widget renderer: &mut Renderer, layout: Layout<'_>, cursor_position: Point, - ) -> MouseCursor { - let mut cursor = MouseCursor::OutOfBounds; - - self.children.iter().zip(layout.children()).for_each( - |(child, layout)| { - let new_cursor = - child.widget.draw(renderer, layout, cursor_position); - - if new_cursor != MouseCursor::OutOfBounds { - cursor = new_cursor; - } - }, - ); - - cursor + ) -> Renderer::Primitive { + renderer.draw(&self, layout, cursor_position) } fn hash_layout(&self, state: &mut Hasher) { @@ -104,10 +91,19 @@ impl<'a, Message, Renderer> Widget } } +pub trait Renderer: crate::Renderer + Sized { + fn draw( + &mut self, + row: &Column<'_, Message, Self>, + layout: Layout<'_>, + cursor_position: Point, + ) -> Self::Primitive; +} + impl<'a, Message, Renderer> From> for Element<'a, Message, Renderer> where - Renderer: 'a, + Renderer: 'a + self::Renderer, Message: 'static, { fn from( diff --git a/native/src/widget/image.rs b/native/src/widget/image.rs index 81f99acb..5b224158 100644 --- a/native/src/widget/image.rs +++ b/native/src/widget/image.rs @@ -20,10 +20,8 @@ where renderer: &mut Renderer, layout: Layout<'_>, _cursor_position: Point, - ) -> MouseCursor { - renderer.draw(&self, layout); - - MouseCursor::OutOfBounds + ) -> Renderer::Primitive { + renderer.draw(&self, layout) } fn hash_layout(&self, state: &mut Hasher) { @@ -40,7 +38,7 @@ where /// /// [`Image`]: struct.Image.html /// [renderer]: ../../renderer/index.html -pub trait Renderer { +pub trait Renderer: crate::Renderer { /// Creates a [`Node`] for the provided [`Image`]. /// /// You should probably keep the original aspect ratio, if possible. @@ -52,7 +50,8 @@ pub trait Renderer { /// Draws an [`Image`]. /// /// [`Image`]: struct.Image.html - fn draw(&mut self, image: &Image, layout: Layout<'_>); + fn draw(&mut self, image: &Image, layout: Layout<'_>) + -> Self::Primitive; } impl<'a, I, Message, Renderer> From> for Element<'a, Message, Renderer> diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs index 33d42e61..1bc052aa 100644 --- a/native/src/widget/radio.rs +++ b/native/src/widget/radio.rs @@ -1,6 +1,6 @@ //! Create choices using radio buttons. use crate::input::{mouse, ButtonState}; -use crate::{Element, Event, Hasher, Layout, MouseCursor, Node, Point, Widget}; +use crate::{Element, Event, Hasher, Layout, Node, Point, Widget}; use std::hash::Hash; @@ -40,7 +40,7 @@ where renderer: &mut Renderer, layout: Layout<'_>, cursor_position: Point, - ) -> MouseCursor { + ) -> Renderer::Primitive { renderer.draw(&self, layout, cursor_position) } @@ -56,7 +56,7 @@ where /// /// [`Radio`]: struct.Radio.html /// [renderer]: ../../renderer/index.html -pub trait Renderer { +pub trait Renderer: crate::Renderer { /// Creates a [`Node`] for the provided [`Radio`]. /// /// [`Node`]: ../../struct.Node.html @@ -77,7 +77,7 @@ pub trait Renderer { radio: &Radio, layout: Layout<'_>, cursor_position: Point, - ) -> MouseCursor; + ) -> Self::Primitive; } impl<'a, Message, Renderer> From> diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs index 3cd451b7..04fc8163 100644 --- a/native/src/widget/row.rs +++ b/native/src/widget/row.rs @@ -10,6 +10,8 @@ pub type Row<'a, Message, Renderer> = impl<'a, Message, Renderer> Widget for Row<'a, Message, Renderer> +where + Renderer: self::Renderer, { fn node(&self, renderer: &mut Renderer) -> Node { let mut children: Vec = self @@ -70,21 +72,8 @@ impl<'a, Message, Renderer> Widget renderer: &mut Renderer, layout: Layout<'_>, cursor_position: Point, - ) -> MouseCursor { - let mut cursor = MouseCursor::OutOfBounds; - - self.children.iter().zip(layout.children()).for_each( - |(child, layout)| { - let new_cursor = - child.widget.draw(renderer, layout, cursor_position); - - if new_cursor != MouseCursor::OutOfBounds { - cursor = new_cursor; - } - }, - ); - - cursor + ) -> Renderer::Primitive { + renderer.draw(&self, layout, cursor_position) } fn hash_layout(&self, state: &mut Hasher) { @@ -105,10 +94,19 @@ impl<'a, Message, Renderer> Widget } } +pub trait Renderer: crate::Renderer + Sized { + fn draw( + &mut self, + row: &Row<'_, Message, Self>, + layout: Layout<'_>, + cursor_position: Point, + ) -> Self::Primitive; +} + impl<'a, Message, Renderer> From> for Element<'a, Message, Renderer> where - Renderer: 'a, + Renderer: 'a + self::Renderer, Message: 'static, { fn from(row: Row<'a, Message, Renderer>) -> Element<'a, Message, Renderer> { diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs index 481296bd..cd36c4b9 100644 --- a/native/src/widget/slider.rs +++ b/native/src/widget/slider.rs @@ -71,7 +71,7 @@ where renderer: &mut Renderer, layout: Layout<'_>, cursor_position: Point, - ) -> MouseCursor { + ) -> Renderer::Primitive { renderer.draw(&self, layout, cursor_position) } @@ -87,7 +87,7 @@ where /// /// [`Slider`]: struct.Slider.html /// [renderer]: ../../renderer/index.html -pub trait Renderer { +pub trait Renderer: crate::Renderer { /// Creates a [`Node`] for the provided [`Radio`]. /// /// [`Node`]: ../../struct.Node.html @@ -111,7 +111,7 @@ pub trait Renderer { slider: &Slider<'_, Message>, layout: Layout<'_>, cursor_position: Point, - ) -> MouseCursor; + ) -> Self::Primitive; } impl<'a, Message, Renderer> From> diff --git a/native/src/widget/text.rs b/native/src/widget/text.rs index 5ca6ebf3..a032b4fc 100644 --- a/native/src/widget/text.rs +++ b/native/src/widget/text.rs @@ -1,5 +1,5 @@ //! Write some text for your users to read. -use crate::{Element, Hasher, Layout, MouseCursor, Node, Point, Widget}; +use crate::{Element, Hasher, Layout, Node, Point, Widget}; use std::hash::Hash; @@ -18,10 +18,8 @@ where renderer: &mut Renderer, layout: Layout<'_>, _cursor_position: Point, - ) -> MouseCursor { - renderer.draw(&self, layout); - - MouseCursor::OutOfBounds + ) -> Renderer::Primitive { + renderer.draw(&self, layout) } fn hash_layout(&self, state: &mut Hasher) { @@ -40,7 +38,7 @@ where /// [`Text`]: struct.Text.html /// [renderer]: ../../renderer/index.html /// [`UserInterface`]: ../../struct.UserInterface.html -pub trait Renderer { +pub trait Renderer: crate::Renderer { /// Creates a [`Node`] with the given [`Style`] for the provided [`Text`] /// contents and size. /// @@ -66,7 +64,7 @@ pub trait Renderer { /// [`Text`]: struct.Text.html /// [`HorizontalAlignment`]: enum.HorizontalAlignment.html /// [`VerticalAlignment`]: enum.VerticalAlignment.html - fn draw(&mut self, text: &Text, layout: Layout<'_>); + fn draw(&mut self, text: &Text, layout: Layout<'_>) -> Self::Primitive; } impl<'a, Message, Renderer> From for Element<'a, Message, Renderer> diff --git a/src/lib.rs b/src/lib.rs index 93f8110e..5272549f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -45,13 +45,11 @@ pub trait UserInterface { event_loop.run(move |event, _, control_flow| match event { Event::EventsCleared => { - // TODO: We should find out a way to keep a user interface - // alive between events while still being able to drop it and - // rebuild it only when a message is handled. + // TODO: We should be able to keep a user interface alive + // between events once we remove state references. // - // The borrow checker does not seem to like it when I try this, - // even though I am not technically double borrowing at any - // point. + // This will allow us to rebuild it only when a message is + // handled. let mut user_interface = iced_winit::UserInterface::build( self.view(), cache.take().unwrap(), diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 1e11749b..28964858 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -1,7 +1,7 @@ use iced_native::{ - button, checkbox, image, radio, renderer::Debugger, slider, text, Button, - Checkbox, Color, Image, Layout, MouseCursor, Node, Point, Radio, Slider, - Style, Text, + button, checkbox, column, image, radio, renderer::Debugger, row, slider, + text, Button, Checkbox, Color, Column, Image, Layout, Node, Point, Radio, + Row, Slider, Style, Text, Widget, }; use raw_window_handle::HasRawWindowHandle; @@ -87,12 +87,36 @@ impl Renderer { } } +impl column::Renderer for Renderer { + fn draw( + &mut self, + _column: &Column<'_, Message, Self>, + _layout: Layout<'_>, + _cursor_position: Point, + ) -> Self::Primitive { + () + } +} + +impl row::Renderer for Renderer { + fn draw( + &mut self, + _column: &Row<'_, Message, Self>, + _layout: Layout<'_>, + _cursor_position: Point, + ) -> Self::Primitive { + () + } +} + impl text::Renderer for Renderer { fn node(&self, _text: &Text) -> Node { Node::new(Style::default()) } - fn draw(&mut self, _text: &Text, _layout: Layout<'_>) {} + fn draw(&mut self, _text: &Text, _layout: Layout<'_>) -> Self::Primitive { + () + } } impl checkbox::Renderer for Renderer { @@ -105,8 +129,8 @@ impl checkbox::Renderer for Renderer { _checkbox: &Checkbox, _layout: Layout<'_>, _cursor_position: Point, - ) -> MouseCursor { - MouseCursor::OutOfBounds + ) -> Self::Primitive { + () } } @@ -120,8 +144,8 @@ impl radio::Renderer for Renderer { _radio: &Radio, _layout: Layout<'_>, _cursor_position: Point, - ) -> MouseCursor { - MouseCursor::OutOfBounds + ) -> Self::Primitive { + () } } @@ -135,8 +159,8 @@ impl slider::Renderer for Renderer { _slider: &Slider, _layout: Layout<'_>, _cursor_position: Point, - ) -> MouseCursor { - MouseCursor::OutOfBounds + ) -> Self::Primitive { + () } } @@ -145,7 +169,13 @@ impl image::Renderer<&str> for Renderer { Node::new(Style::default()) } - fn draw(&mut self, _checkbox: &Image<&str>, _layout: Layout<'_>) {} + fn draw( + &mut self, + _checkbox: &Image<&str>, + _layout: Layout<'_>, + ) -> Self::Primitive { + () + } } impl button::Renderer for Renderer { @@ -158,11 +188,23 @@ impl button::Renderer for Renderer { _button: &Button, _layout: Layout<'_>, _cursor_position: Point, - ) -> MouseCursor { - MouseCursor::OutOfBounds + ) -> Self::Primitive { + () } } +impl iced_native::Renderer for Renderer { + type Primitive = (); +} + impl Debugger for Renderer { - fn explain(&mut self, _layout: &Layout<'_>, _color: Color) {} + fn explain( + &mut self, + widget: &dyn Widget, + layout: Layout<'_>, + cursor_position: Point, + _color: Color, + ) -> Self::Primitive { + widget.draw(self, layout, cursor_position) + } } -- cgit From a90f7fcb987f667a80038a5e72f379abbd59d932 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 5 Oct 2019 03:58:23 +0200 Subject: Move `MouseCursor` to `iced_wgpu` --- native/src/element.rs | 2 +- native/src/lib.rs | 5 +---- native/src/mouse_cursor.rs | 35 ----------------------------------- native/src/widget/image.rs | 2 +- native/src/widget/row.rs | 4 +--- native/src/widget/slider.rs | 2 +- wgpu/src/mouse_cursor.rs | 35 +++++++++++++++++++++++++++++++++++ 7 files changed, 40 insertions(+), 45 deletions(-) delete mode 100644 native/src/mouse_cursor.rs create mode 100644 wgpu/src/mouse_cursor.rs diff --git a/native/src/element.rs b/native/src/element.rs index 4ea2379f..8d14070a 100644 --- a/native/src/element.rs +++ b/native/src/element.rs @@ -103,7 +103,7 @@ where /// # /// # mod iced_wgpu { /// # use iced_native::{ - /// # button, row, Button, MouseCursor, Node, Point, Rectangle, Style, Layout, Row + /// # button, row, Button, Node, Point, Rectangle, Style, Layout, Row /// # }; /// # pub struct Renderer; /// # diff --git a/native/src/lib.rs b/native/src/lib.rs index f6b6f807..6067f49d 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -77,8 +77,7 @@ //! # //! # mod iced_wgpu { //! # use iced_native::{ -//! # button, text, Button, Text, -//! # MouseCursor, Node, Point, Rectangle, Style, Color, Layout +//! # button, text, Button, Text, Node, Point, Rectangle, Style, Color, Layout //! # }; //! # //! # pub struct Renderer {} @@ -207,7 +206,6 @@ mod element; mod event; mod hasher; mod layout; -mod mouse_cursor; mod node; mod style; mod user_interface; @@ -223,7 +221,6 @@ pub use element::Element; pub use event::Event; pub use hasher::Hasher; pub use layout::Layout; -pub use mouse_cursor::MouseCursor; pub use node::Node; pub use renderer::Renderer; pub use style::Style; diff --git a/native/src/mouse_cursor.rs b/native/src/mouse_cursor.rs deleted file mode 100644 index 4ef6361a..00000000 --- a/native/src/mouse_cursor.rs +++ /dev/null @@ -1,35 +0,0 @@ -/// The state of the mouse cursor. -#[derive(Debug, Eq, PartialEq, Clone, Copy)] -pub enum MouseCursor { - /// The cursor is out of the bounds of the user interface. - OutOfBounds, - - /// The cursor is over a non-interactive widget. - Idle, - - /// The cursor is over a clickable widget. - Pointer, - - /// The cursor is over a busy widget. - Working, - - /// The cursor is over a grabbable widget. - Grab, - - /// The cursor is grabbing a widget. - Grabbing, -} - -#[cfg(feature = "winit")] -impl From for winit::window::CursorIcon { - fn from(mouse_cursor: MouseCursor) -> winit::window::CursorIcon { - match mouse_cursor { - MouseCursor::OutOfBounds => winit::window::CursorIcon::Default, - MouseCursor::Idle => winit::window::CursorIcon::Default, - MouseCursor::Pointer => winit::window::CursorIcon::Hand, - MouseCursor::Working => winit::window::CursorIcon::Progress, - MouseCursor::Grab => winit::window::CursorIcon::Grab, - MouseCursor::Grabbing => winit::window::CursorIcon::Grabbing, - } - } -} diff --git a/native/src/widget/image.rs b/native/src/widget/image.rs index 5b224158..2bce36c2 100644 --- a/native/src/widget/image.rs +++ b/native/src/widget/image.rs @@ -1,6 +1,6 @@ //! Display images in your user interface. -use crate::{Element, Hasher, Layout, MouseCursor, Node, Point, Widget}; +use crate::{Element, Hasher, Layout, Node, Point, Widget}; use std::hash::Hash; diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs index 04fc8163..9d023210 100644 --- a/native/src/widget/row.rs +++ b/native/src/widget/row.rs @@ -1,8 +1,6 @@ use std::hash::Hash; -use crate::{ - Element, Event, Hasher, Layout, MouseCursor, Node, Point, Style, Widget, -}; +use crate::{Element, Event, Hasher, Layout, Node, Point, Style, Widget}; /// A container that distributes its contents horizontally. pub type Row<'a, Message, Renderer> = diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs index cd36c4b9..77095cb7 100644 --- a/native/src/widget/slider.rs +++ b/native/src/widget/slider.rs @@ -7,7 +7,7 @@ use std::hash::Hash; use crate::input::{mouse, ButtonState}; -use crate::{Element, Event, Hasher, Layout, MouseCursor, Node, Point, Widget}; +use crate::{Element, Event, Hasher, Layout, Node, Point, Widget}; pub use iced_core::slider::*; diff --git a/wgpu/src/mouse_cursor.rs b/wgpu/src/mouse_cursor.rs new file mode 100644 index 00000000..4ef6361a --- /dev/null +++ b/wgpu/src/mouse_cursor.rs @@ -0,0 +1,35 @@ +/// The state of the mouse cursor. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum MouseCursor { + /// The cursor is out of the bounds of the user interface. + OutOfBounds, + + /// The cursor is over a non-interactive widget. + Idle, + + /// The cursor is over a clickable widget. + Pointer, + + /// The cursor is over a busy widget. + Working, + + /// The cursor is over a grabbable widget. + Grab, + + /// The cursor is grabbing a widget. + Grabbing, +} + +#[cfg(feature = "winit")] +impl From for winit::window::CursorIcon { + fn from(mouse_cursor: MouseCursor) -> winit::window::CursorIcon { + match mouse_cursor { + MouseCursor::OutOfBounds => winit::window::CursorIcon::Default, + MouseCursor::Idle => winit::window::CursorIcon::Default, + MouseCursor::Pointer => winit::window::CursorIcon::Hand, + MouseCursor::Working => winit::window::CursorIcon::Progress, + MouseCursor::Grab => winit::window::CursorIcon::Grab, + MouseCursor::Grabbing => winit::window::CursorIcon::Grabbing, + } + } +} -- cgit From 0c3f78713d24b263e94cf6aebb8862926feaff23 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 5 Oct 2019 05:12:36 +0200 Subject: Draft basic text rendering using `wgpu_glyph` --- native/src/element.rs | 9 ++ src/lib.rs | 34 ++++++-- wgpu/Cargo.toml | 1 + wgpu/src/lib.rs | 226 +++++++++++++++++++++++++++++++++++++++++--------- 4 files changed, 221 insertions(+), 49 deletions(-) diff --git a/native/src/element.rs b/native/src/element.rs index 8d14070a..417e3463 100644 --- a/native/src/element.rs +++ b/native/src/element.rs @@ -41,6 +41,15 @@ where } } + pub fn draw( + &self, + renderer: &mut Renderer, + layout: Layout<'_>, + cursor_position: Point, + ) -> Renderer::Primitive { + self.widget.draw(renderer, layout, cursor_position) + } + /// Applies a transformation to the produced message of the [`Element`]. /// /// This method is useful when you want to decouple different parts of your diff --git a/src/lib.rs b/src/lib.rs index 5272549f..f9860b37 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -pub use iced_wgpu::Renderer; +pub use iced_wgpu::{Primitive, Renderer}; pub use iced_winit::{ button, slider, text, winit, Align, Button, Checkbox, Color, Image, Justify, Length, Radio, Slider, Text, @@ -33,13 +33,15 @@ pub trait UserInterface { .expect("Open window"); let size = window.inner_size().to_physical(window.hidpi_factor());; + let (width, height) = (size.width as u16, size.height as u16); - let mut renderer = - Renderer::new(&window, size.width as u32, size.height as u32); + let mut renderer = Renderer::new(&window); + let mut target = renderer.target(width, height); let mut cache = Some(iced_winit::Cache::default()); let mut events = Vec::new(); let mut redraws = 0; + let mut primitive = Primitive::None; window.request_redraw(); @@ -51,7 +53,7 @@ pub trait UserInterface { // This will allow us to rebuild it only when a message is // handled. let mut user_interface = iced_winit::UserInterface::build( - self.view(), + document(&mut self, width, height), cache.take().unwrap(), &mut renderer, ); @@ -59,7 +61,7 @@ pub trait UserInterface { let messages = user_interface.update(events.drain(..)); if messages.is_empty() { - let _ = user_interface.draw(&mut renderer); + primitive = user_interface.draw(&mut renderer); cache = Some(user_interface.into_cache()); } else { @@ -72,12 +74,12 @@ pub trait UserInterface { } let user_interface = iced_winit::UserInterface::build( - self.view(), + document(&mut self, width, height), temp_cache, &mut renderer, ); - let _ = user_interface.draw(&mut renderer); + primitive = user_interface.draw(&mut renderer); cache = Some(user_interface.into_cache()); } @@ -89,7 +91,7 @@ pub trait UserInterface { .. } => { println!("Redrawing {}", redraws); - renderer.draw(); + renderer.draw(&mut target, &primitive); redraws += 1; @@ -108,3 +110,19 @@ pub trait UserInterface { }) } } + +fn document( + user_interface: &mut UserInterface, + width: u16, + height: u16, +) -> Element +where + UserInterface: self::UserInterface, + UserInterface::Message: 'static, +{ + Column::new() + .width(Length::Units(width)) + .height(Length::Units(height)) + .push(user_interface.view()) + .into() +} diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 879def28..79661baa 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -13,4 +13,5 @@ vulkan = ["wgpu/vulkan"] [dependencies] iced_native = { version = "0.1.0-alpha", path = "../native" } wgpu = "0.3" +wgpu_glyph = "0.4" raw-window-handle = "0.1" diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 28964858..d8f727cd 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -1,7 +1,7 @@ use iced_native::{ button, checkbox, column, image, radio, renderer::Debugger, row, slider, text, Button, Checkbox, Color, Column, Image, Layout, Node, Point, Radio, - Row, Slider, Style, Text, Widget, + Rectangle, Row, Slider, Style, Text, Widget, }; use raw_window_handle::HasRawWindowHandle; @@ -10,28 +10,34 @@ use wgpu::{ Instance, Limits, PowerPreference, RequestAdapterOptions, Surface, SwapChain, SwapChainDescriptor, TextureFormat, TextureUsage, }; +use wgpu_glyph::{GlyphBrush, GlyphBrushBuilder, GlyphCruncher, Section}; + +use std::f32; +use std::{cell::RefCell, rc::Rc}; pub struct Renderer { instance: Instance, surface: Surface, adapter: Adapter, device: Device, + glyph_brush: Rc>>, +} + +pub struct Target { + width: u16, + height: u16, swap_chain: SwapChain, } impl Renderer { - pub fn new( - window: &W, - width: u32, - height: u32, - ) -> Self { + pub fn new(window: &W) -> Self { let instance = Instance::new(); let adapter = instance.request_adapter(&RequestAdapterOptions { power_preference: PowerPreference::LowPower, }); - let device = adapter.request_device(&DeviceDescriptor { + let mut device = adapter.request_device(&DeviceDescriptor { extensions: Extensions { anisotropic_filtering: false, }, @@ -40,28 +46,40 @@ impl Renderer { let surface = instance.create_surface(window.raw_window_handle()); - let swap_chain = device.create_swap_chain( - &surface, - &SwapChainDescriptor { - usage: TextureUsage::OUTPUT_ATTACHMENT, - format: TextureFormat::Bgra8UnormSrgb, - width, - height, - present_mode: wgpu::PresentMode::Vsync, - }, - ); + let font: &[u8] = + include_bytes!("../../examples/tour/resources/Roboto-Regular.ttf"); + + let glyph_brush = GlyphBrushBuilder::using_font_bytes(font) + .build(&mut device, TextureFormat::Bgra8UnormSrgb); Self { instance, surface, adapter, device, - swap_chain, + glyph_brush: Rc::new(RefCell::new(glyph_brush)), } } - pub fn draw(&mut self) { - let frame = self.swap_chain.get_next_texture(); + pub fn target(&self, width: u16, height: u16) -> Target { + Target { + width, + height, + swap_chain: self.device.create_swap_chain( + &self.surface, + &SwapChainDescriptor { + usage: TextureUsage::OUTPUT_ATTACHMENT, + format: TextureFormat::Bgra8UnormSrgb, + width: u32::from(width), + height: u32::from(height), + present_mode: wgpu::PresentMode::Vsync, + }, + ), + } + } + + pub fn draw(&mut self, target: &mut Target, primitive: &Primitive) { + let frame = target.swap_chain.get_next_texture(); let mut encoder = self .device @@ -83,39 +101,169 @@ impl Renderer { depth_stencil_attachment: None, }); + self.draw_primitive(primitive); + + self.glyph_brush + .borrow_mut() + .draw_queued( + &mut self.device, + &mut encoder, + &frame.view, + u32::from(target.width), + u32::from(target.height), + ) + .expect("Draw text"); + self.device.get_queue().submit(&[encoder.finish()]); } + + fn draw_primitive(&mut self, primitive: &Primitive) { + match primitive { + Primitive::None => {} + Primitive::Group { primitives } => { + for primitive in primitives { + self.draw_primitive(primitive) + } + } + Primitive::Text { + content, + bounds, + size, + } => self.glyph_brush.borrow_mut().queue(Section { + text: &content, + screen_position: (bounds.x, bounds.y), + bounds: (bounds.width, bounds.height), + scale: wgpu_glyph::Scale { x: *size, y: *size }, + ..Default::default() + }), + } + } +} + +#[derive(Debug, Clone)] +pub enum Primitive { + None, + Group { + primitives: Vec, + }, + Text { + content: String, + bounds: Rectangle, + size: f32, + }, +} + +impl iced_native::Renderer for Renderer { + type Primitive = Primitive; } impl column::Renderer for Renderer { fn draw( &mut self, - _column: &Column<'_, Message, Self>, - _layout: Layout<'_>, - _cursor_position: Point, + column: &Column<'_, Message, Self>, + layout: Layout<'_>, + cursor_position: Point, ) -> Self::Primitive { - () + Primitive::Group { + primitives: column + .children + .iter() + .zip(layout.children()) + .map(|(child, layout)| { + child.draw(self, layout, cursor_position) + }) + .collect(), + } } } impl row::Renderer for Renderer { fn draw( &mut self, - _column: &Row<'_, Message, Self>, - _layout: Layout<'_>, - _cursor_position: Point, + row: &Row<'_, Message, Self>, + layout: Layout<'_>, + cursor_position: Point, ) -> Self::Primitive { - () + Primitive::Group { + primitives: row + .children + .iter() + .zip(layout.children()) + .map(|(child, layout)| { + child.draw(self, layout, cursor_position) + }) + .collect(), + } } } impl text::Renderer for Renderer { - fn node(&self, _text: &Text) -> Node { - Node::new(Style::default()) + fn node(&self, text: &Text) -> Node { + let glyph_brush = self.glyph_brush.clone(); + let content = text.content.clone(); + + // TODO: Investigate why stretch tries to measure this MANY times + // with every ancestor's bounds. + // Bug? Using the library wrong? I should probably open an issue on + // the stretch repository. + // 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 = text.size.map(f32::from).unwrap_or(20.0); + + 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_native::Number::Undefined => f32::INFINITY, + iced_native::Number::Defined(w) => w, + }, + match bounds.height { + iced_native::Number::Undefined => f32::INFINITY, + iced_native::Number::Defined(h) => h, + }, + ); + + let text = Section { + text: &content, + scale: wgpu_glyph::Scale { x: size, y: size }, + bounds, + ..Default::default() + }; + + let (width, height) = if let Some(bounds) = + glyph_brush.borrow_mut().glyph_bounds(&text) + { + (bounds.width(), bounds.height()) + } else { + (0.0, 0.0) + }; + + let size = iced_native::Size { width, height }; + + // If the text has no width boundary we avoid caching as the + // layout engine may just be measuring text in a row. + if bounds.0 == f32::INFINITY { + return size; + } else { + *measure = Some(size); + } + } + + measure.unwrap() + }) } - fn draw(&mut self, _text: &Text, _layout: Layout<'_>) -> Self::Primitive { - () + fn draw(&mut self, text: &Text, layout: Layout<'_>) -> Self::Primitive { + Primitive::Text { + content: text.content.clone(), + size: f32::from(text.size.unwrap_or(20)), + bounds: layout.bounds(), + } } } @@ -130,7 +278,7 @@ impl checkbox::Renderer for Renderer { _layout: Layout<'_>, _cursor_position: Point, ) -> Self::Primitive { - () + Primitive::None } } @@ -145,7 +293,7 @@ impl radio::Renderer for Renderer { _layout: Layout<'_>, _cursor_position: Point, ) -> Self::Primitive { - () + Primitive::None } } @@ -160,7 +308,7 @@ impl slider::Renderer for Renderer { _layout: Layout<'_>, _cursor_position: Point, ) -> Self::Primitive { - () + Primitive::None } } @@ -174,7 +322,7 @@ impl image::Renderer<&str> for Renderer { _checkbox: &Image<&str>, _layout: Layout<'_>, ) -> Self::Primitive { - () + Primitive::None } } @@ -189,14 +337,10 @@ impl button::Renderer for Renderer { _layout: Layout<'_>, _cursor_position: Point, ) -> Self::Primitive { - () + Primitive::None } } -impl iced_native::Renderer for Renderer { - type Primitive = (); -} - impl Debugger for Renderer { fn explain( &mut self, -- cgit From ef498eb1af687baf673fb38a29b5cd1c1b1224d7 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 5 Oct 2019 05:17:59 +0200 Subject: Add some padding to the `tour` example --- examples/tour/src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs index 5017041a..790a1390 100644 --- a/examples/tour/src/main.rs +++ b/examples/tour/src/main.rs @@ -73,6 +73,7 @@ impl UserInterface for Tour { let element: Element<_> = Column::new() .max_width(Length::Units(500)) .spacing(20) + .padding(20) .push(steps.view(self.debug).map(Message::StepMessage)) .push(controls) .into(); -- cgit From ae56edc8ccd3462766071cc00c12e92feccf1274 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 5 Oct 2019 19:02:16 +0200 Subject: Center `tour` example --- examples/tour/src/main.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs index 2b4a0e80..0b88ccd5 100644 --- a/examples/tour/src/main.rs +++ b/examples/tour/src/main.rs @@ -1,6 +1,7 @@ use iced::{ button, slider, text::HorizontalAlignment, Align, Button, Checkbox, Color, - Column, Element, Image, Length, Radio, Row, Slider, Text, UserInterface, + Column, Element, Image, Justify, Length, Radio, Row, Slider, Text, + UserInterface, }; pub fn main() { @@ -78,11 +79,19 @@ impl UserInterface for Tour { .push(controls) .into(); - if self.debug { + let element = if self.debug { element.explain(Color::BLACK) } else { element - } + }; + + Column::new() + .width(Length::Fill) + .height(Length::Fill) + .align_items(Align::Center) + .justify_content(Justify::Center) + .push(element) + .into() } } -- cgit From 5a5ca34b5fcab9266359d3f0885782969f8c058e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 5 Oct 2019 19:22:51 +0200 Subject: Modularize `iced_wgpu` --- wgpu/src/lib.rs | 359 +----------------------------------------- wgpu/src/primitive.rs | 14 ++ wgpu/src/renderer.rs | 167 ++++++++++++++++++++ wgpu/src/renderer/button.rs | 18 +++ wgpu/src/renderer/checkbox.rs | 18 +++ wgpu/src/renderer/column.rs | 22 +++ wgpu/src/renderer/image.rs | 16 ++ wgpu/src/renderer/radio.rs | 17 ++ wgpu/src/renderer/row.rs | 22 +++ wgpu/src/renderer/slider.rs | 17 ++ wgpu/src/renderer/text.rs | 77 +++++++++ 11 files changed, 394 insertions(+), 353 deletions(-) create mode 100644 wgpu/src/primitive.rs create mode 100644 wgpu/src/renderer.rs create mode 100644 wgpu/src/renderer/button.rs create mode 100644 wgpu/src/renderer/checkbox.rs create mode 100644 wgpu/src/renderer/column.rs create mode 100644 wgpu/src/renderer/image.rs create mode 100644 wgpu/src/renderer/radio.rs create mode 100644 wgpu/src/renderer/row.rs create mode 100644 wgpu/src/renderer/slider.rs create mode 100644 wgpu/src/renderer/text.rs diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index d8f727cd..9a2a336f 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -1,354 +1,7 @@ -use iced_native::{ - button, checkbox, column, image, radio, renderer::Debugger, row, slider, - text, Button, Checkbox, Color, Column, Image, Layout, Node, Point, Radio, - Rectangle, Row, Slider, Style, Text, Widget, -}; +mod mouse_cursor; +mod primitive; +mod renderer; -use raw_window_handle::HasRawWindowHandle; -use wgpu::{ - Adapter, CommandEncoderDescriptor, Device, DeviceDescriptor, Extensions, - Instance, Limits, PowerPreference, RequestAdapterOptions, Surface, - SwapChain, SwapChainDescriptor, TextureFormat, TextureUsage, -}; -use wgpu_glyph::{GlyphBrush, GlyphBrushBuilder, GlyphCruncher, Section}; - -use std::f32; -use std::{cell::RefCell, rc::Rc}; - -pub struct Renderer { - instance: Instance, - surface: Surface, - adapter: Adapter, - device: Device, - glyph_brush: Rc>>, -} - -pub struct Target { - width: u16, - height: u16, - swap_chain: SwapChain, -} - -impl Renderer { - pub fn new(window: &W) -> Self { - let instance = Instance::new(); - - let adapter = instance.request_adapter(&RequestAdapterOptions { - power_preference: PowerPreference::LowPower, - }); - - let mut device = adapter.request_device(&DeviceDescriptor { - extensions: Extensions { - anisotropic_filtering: false, - }, - limits: Limits { max_bind_groups: 1 }, - }); - - let surface = instance.create_surface(window.raw_window_handle()); - - let font: &[u8] = - include_bytes!("../../examples/tour/resources/Roboto-Regular.ttf"); - - let glyph_brush = GlyphBrushBuilder::using_font_bytes(font) - .build(&mut device, TextureFormat::Bgra8UnormSrgb); - - Self { - instance, - surface, - adapter, - device, - glyph_brush: Rc::new(RefCell::new(glyph_brush)), - } - } - - pub fn target(&self, width: u16, height: u16) -> Target { - Target { - width, - height, - swap_chain: self.device.create_swap_chain( - &self.surface, - &SwapChainDescriptor { - usage: TextureUsage::OUTPUT_ATTACHMENT, - format: TextureFormat::Bgra8UnormSrgb, - width: u32::from(width), - height: u32::from(height), - present_mode: wgpu::PresentMode::Vsync, - }, - ), - } - } - - pub fn draw(&mut self, target: &mut Target, primitive: &Primitive) { - let frame = target.swap_chain.get_next_texture(); - - let mut encoder = self - .device - .create_command_encoder(&CommandEncoderDescriptor { todo: 0 }); - - let _ = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor { - attachment: &frame.view, - resolve_target: None, - load_op: wgpu::LoadOp::Clear, - store_op: wgpu::StoreOp::Store, - clear_color: wgpu::Color { - r: 1.0, - g: 1.0, - b: 1.0, - a: 1.0, - }, - }], - depth_stencil_attachment: None, - }); - - self.draw_primitive(primitive); - - self.glyph_brush - .borrow_mut() - .draw_queued( - &mut self.device, - &mut encoder, - &frame.view, - u32::from(target.width), - u32::from(target.height), - ) - .expect("Draw text"); - - self.device.get_queue().submit(&[encoder.finish()]); - } - - fn draw_primitive(&mut self, primitive: &Primitive) { - match primitive { - Primitive::None => {} - Primitive::Group { primitives } => { - for primitive in primitives { - self.draw_primitive(primitive) - } - } - Primitive::Text { - content, - bounds, - size, - } => self.glyph_brush.borrow_mut().queue(Section { - text: &content, - screen_position: (bounds.x, bounds.y), - bounds: (bounds.width, bounds.height), - scale: wgpu_glyph::Scale { x: *size, y: *size }, - ..Default::default() - }), - } - } -} - -#[derive(Debug, Clone)] -pub enum Primitive { - None, - Group { - primitives: Vec, - }, - Text { - content: String, - bounds: Rectangle, - size: f32, - }, -} - -impl iced_native::Renderer for Renderer { - type Primitive = Primitive; -} - -impl column::Renderer for Renderer { - fn draw( - &mut self, - column: &Column<'_, Message, Self>, - layout: Layout<'_>, - cursor_position: Point, - ) -> Self::Primitive { - Primitive::Group { - primitives: column - .children - .iter() - .zip(layout.children()) - .map(|(child, layout)| { - child.draw(self, layout, cursor_position) - }) - .collect(), - } - } -} - -impl row::Renderer for Renderer { - fn draw( - &mut self, - row: &Row<'_, Message, Self>, - layout: Layout<'_>, - cursor_position: Point, - ) -> Self::Primitive { - Primitive::Group { - primitives: row - .children - .iter() - .zip(layout.children()) - .map(|(child, layout)| { - child.draw(self, layout, cursor_position) - }) - .collect(), - } - } -} - -impl text::Renderer for Renderer { - fn node(&self, text: &Text) -> Node { - let glyph_brush = self.glyph_brush.clone(); - let content = text.content.clone(); - - // TODO: Investigate why stretch tries to measure this MANY times - // with every ancestor's bounds. - // Bug? Using the library wrong? I should probably open an issue on - // the stretch repository. - // 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 = text.size.map(f32::from).unwrap_or(20.0); - - 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_native::Number::Undefined => f32::INFINITY, - iced_native::Number::Defined(w) => w, - }, - match bounds.height { - iced_native::Number::Undefined => f32::INFINITY, - iced_native::Number::Defined(h) => h, - }, - ); - - let text = Section { - text: &content, - scale: wgpu_glyph::Scale { x: size, y: size }, - bounds, - ..Default::default() - }; - - let (width, height) = if let Some(bounds) = - glyph_brush.borrow_mut().glyph_bounds(&text) - { - (bounds.width(), bounds.height()) - } else { - (0.0, 0.0) - }; - - let size = iced_native::Size { width, height }; - - // If the text has no width boundary we avoid caching as the - // layout engine may just be measuring text in a row. - if bounds.0 == f32::INFINITY { - return size; - } else { - *measure = Some(size); - } - } - - measure.unwrap() - }) - } - - fn draw(&mut self, text: &Text, layout: Layout<'_>) -> Self::Primitive { - Primitive::Text { - content: text.content.clone(), - size: f32::from(text.size.unwrap_or(20)), - bounds: layout.bounds(), - } - } -} - -impl checkbox::Renderer for Renderer { - fn node(&mut self, _checkbox: &Checkbox) -> Node { - Node::new(Style::default()) - } - - fn draw( - &mut self, - _checkbox: &Checkbox, - _layout: Layout<'_>, - _cursor_position: Point, - ) -> Self::Primitive { - Primitive::None - } -} - -impl radio::Renderer for Renderer { - fn node(&mut self, _checkbox: &Radio) -> Node { - Node::new(Style::default()) - } - - fn draw( - &mut self, - _radio: &Radio, - _layout: Layout<'_>, - _cursor_position: Point, - ) -> Self::Primitive { - Primitive::None - } -} - -impl slider::Renderer for Renderer { - fn node(&self, _slider: &Slider) -> Node { - Node::new(Style::default()) - } - - fn draw( - &mut self, - _slider: &Slider, - _layout: Layout<'_>, - _cursor_position: Point, - ) -> Self::Primitive { - Primitive::None - } -} - -impl image::Renderer<&str> for Renderer { - fn node(&mut self, _image: &Image<&str>) -> Node { - Node::new(Style::default()) - } - - fn draw( - &mut self, - _checkbox: &Image<&str>, - _layout: Layout<'_>, - ) -> Self::Primitive { - Primitive::None - } -} - -impl button::Renderer for Renderer { - fn node(&self, _button: &Button) -> Node { - Node::new(Style::default()) - } - - fn draw( - &mut self, - _button: &Button, - _layout: Layout<'_>, - _cursor_position: Point, - ) -> Self::Primitive { - Primitive::None - } -} - -impl Debugger for Renderer { - fn explain( - &mut self, - widget: &dyn Widget, - layout: Layout<'_>, - cursor_position: Point, - _color: Color, - ) -> Self::Primitive { - widget.draw(self, layout, cursor_position) - } -} +pub use mouse_cursor::MouseCursor; +pub use primitive::Primitive; +pub use renderer::{Renderer, Target}; diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs new file mode 100644 index 00000000..04b2e99f --- /dev/null +++ b/wgpu/src/primitive.rs @@ -0,0 +1,14 @@ +use iced_native::Rectangle; + +#[derive(Debug, Clone)] +pub enum Primitive { + None, + Group { + primitives: Vec, + }, + Text { + content: String, + bounds: Rectangle, + size: f32, + }, +} diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs new file mode 100644 index 00000000..df8887e3 --- /dev/null +++ b/wgpu/src/renderer.rs @@ -0,0 +1,167 @@ +use crate::Primitive; +use iced_native::{renderer::Debugger, Color, Layout, Point, Widget}; + +use raw_window_handle::HasRawWindowHandle; +use wgpu::{ + Adapter, CommandEncoderDescriptor, Device, DeviceDescriptor, Extensions, + Instance, Limits, PowerPreference, RequestAdapterOptions, Surface, + SwapChain, SwapChainDescriptor, TextureFormat, TextureUsage, +}; +use wgpu_glyph::{GlyphBrush, GlyphBrushBuilder, Section}; + +use std::{cell::RefCell, rc::Rc}; + +mod button; +mod checkbox; +mod column; +mod image; +mod radio; +mod row; +mod slider; +mod text; + +pub struct Renderer { + instance: Instance, + surface: Surface, + adapter: Adapter, + device: Device, + glyph_brush: Rc>>, +} + +pub struct Target { + width: u16, + height: u16, + swap_chain: SwapChain, +} + +impl Renderer { + pub fn new(window: &W) -> Self { + let instance = Instance::new(); + + let adapter = instance.request_adapter(&RequestAdapterOptions { + power_preference: PowerPreference::LowPower, + }); + + let mut device = adapter.request_device(&DeviceDescriptor { + extensions: Extensions { + anisotropic_filtering: false, + }, + limits: Limits { max_bind_groups: 1 }, + }); + + let surface = instance.create_surface(window.raw_window_handle()); + + // TODO: Think about font loading strategy + // Loading system fonts with fallback may be a good idea + let font: &[u8] = + include_bytes!("../../examples/tour/resources/Roboto-Regular.ttf"); + + let glyph_brush = GlyphBrushBuilder::using_font_bytes(font) + .build(&mut device, TextureFormat::Bgra8UnormSrgb); + + Self { + instance, + surface, + adapter, + device, + glyph_brush: Rc::new(RefCell::new(glyph_brush)), + } + } + + pub fn target(&self, width: u16, height: u16) -> Target { + Target { + width, + height, + swap_chain: self.device.create_swap_chain( + &self.surface, + &SwapChainDescriptor { + usage: TextureUsage::OUTPUT_ATTACHMENT, + format: TextureFormat::Bgra8UnormSrgb, + width: u32::from(width), + height: u32::from(height), + present_mode: wgpu::PresentMode::Vsync, + }, + ), + } + } + + pub fn draw(&mut self, target: &mut Target, primitive: &Primitive) { + let frame = target.swap_chain.get_next_texture(); + + let mut encoder = self + .device + .create_command_encoder(&CommandEncoderDescriptor { todo: 0 }); + + let _ = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor { + attachment: &frame.view, + resolve_target: None, + load_op: wgpu::LoadOp::Clear, + store_op: wgpu::StoreOp::Store, + clear_color: wgpu::Color { + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + }, + }], + depth_stencil_attachment: None, + }); + + self.draw_primitive(primitive); + + self.glyph_brush + .borrow_mut() + .draw_queued( + &mut self.device, + &mut encoder, + &frame.view, + u32::from(target.width), + u32::from(target.height), + ) + .expect("Draw text"); + + self.device.get_queue().submit(&[encoder.finish()]); + } + + fn draw_primitive(&mut self, primitive: &Primitive) { + match primitive { + Primitive::None => {} + Primitive::Group { primitives } => { + // TODO: Inspect a bit and regroup (?) + for primitive in primitives { + self.draw_primitive(primitive) + } + } + Primitive::Text { + content, + bounds, + size, + } => self.glyph_brush.borrow_mut().queue(Section { + text: &content, + screen_position: (bounds.x, bounds.y), + bounds: (bounds.width, bounds.height), + scale: wgpu_glyph::Scale { x: *size, y: *size }, + ..Default::default() + }), + } + } +} + +impl iced_native::Renderer for Renderer { + // TODO: Add `MouseCursor` here (?) + type Primitive = Primitive; +} + +impl Debugger for Renderer { + fn explain( + &mut self, + widget: &dyn Widget, + layout: Layout<'_>, + cursor_position: Point, + _color: Color, + ) -> Self::Primitive { + // TODO: Include a bordered box to display layout bounds + widget.draw(self, layout, cursor_position) + } +} diff --git a/wgpu/src/renderer/button.rs b/wgpu/src/renderer/button.rs new file mode 100644 index 00000000..24bc1de6 --- /dev/null +++ b/wgpu/src/renderer/button.rs @@ -0,0 +1,18 @@ +use crate::{Primitive, Renderer}; +use iced_native::{button, Button, Layout, Node, Point, Style}; + +impl button::Renderer for Renderer { + fn node(&self, _button: &Button) -> Node { + Node::new(Style::default()) + } + + fn draw( + &mut self, + _button: &Button, + _layout: Layout<'_>, + _cursor_position: Point, + ) -> Self::Primitive { + // TODO + Primitive::None + } +} diff --git a/wgpu/src/renderer/checkbox.rs b/wgpu/src/renderer/checkbox.rs new file mode 100644 index 00000000..c94a2157 --- /dev/null +++ b/wgpu/src/renderer/checkbox.rs @@ -0,0 +1,18 @@ +use crate::{Primitive, Renderer}; +use iced_native::{checkbox, Checkbox, Layout, Node, Point, Style}; + +impl checkbox::Renderer for Renderer { + fn node(&mut self, _checkbox: &Checkbox) -> Node { + Node::new(Style::default()) + } + + fn draw( + &mut self, + _checkbox: &Checkbox, + _layout: Layout<'_>, + _cursor_position: Point, + ) -> Self::Primitive { + // TODO + Primitive::None + } +} diff --git a/wgpu/src/renderer/column.rs b/wgpu/src/renderer/column.rs new file mode 100644 index 00000000..1b9adad6 --- /dev/null +++ b/wgpu/src/renderer/column.rs @@ -0,0 +1,22 @@ +use crate::{Primitive, Renderer}; +use iced_native::{column, Column, Layout, Point}; + +impl column::Renderer for Renderer { + fn draw( + &mut self, + column: &Column<'_, Message, Self>, + layout: Layout<'_>, + cursor_position: Point, + ) -> Self::Primitive { + Primitive::Group { + primitives: column + .children + .iter() + .zip(layout.children()) + .map(|(child, layout)| { + child.draw(self, layout, cursor_position) + }) + .collect(), + } + } +} diff --git a/wgpu/src/renderer/image.rs b/wgpu/src/renderer/image.rs new file mode 100644 index 00000000..6ff39d30 --- /dev/null +++ b/wgpu/src/renderer/image.rs @@ -0,0 +1,16 @@ +use crate::{Primitive, Renderer}; +use iced_native::{image, Image, Layout, Node, Style}; + +impl image::Renderer<&str> for Renderer { + fn node(&mut self, _image: &Image<&str>) -> Node { + Node::new(Style::default()) + } + + fn draw( + &mut self, + _image: &Image<&str>, + _layout: Layout<'_>, + ) -> Self::Primitive { + Primitive::None + } +} diff --git a/wgpu/src/renderer/radio.rs b/wgpu/src/renderer/radio.rs new file mode 100644 index 00000000..ce419ae0 --- /dev/null +++ b/wgpu/src/renderer/radio.rs @@ -0,0 +1,17 @@ +use crate::{Primitive, Renderer}; +use iced_native::{radio, Layout, Node, Point, Radio, Style}; + +impl radio::Renderer for Renderer { + fn node(&mut self, _checkbox: &Radio) -> Node { + Node::new(Style::default()) + } + + fn draw( + &mut self, + _radio: &Radio, + _layout: Layout<'_>, + _cursor_position: Point, + ) -> Self::Primitive { + Primitive::None + } +} diff --git a/wgpu/src/renderer/row.rs b/wgpu/src/renderer/row.rs new file mode 100644 index 00000000..be9e4ede --- /dev/null +++ b/wgpu/src/renderer/row.rs @@ -0,0 +1,22 @@ +use crate::{Primitive, Renderer}; +use iced_native::{row, Layout, Point, Row}; + +impl row::Renderer for Renderer { + fn draw( + &mut self, + row: &Row<'_, Message, Self>, + layout: Layout<'_>, + cursor_position: Point, + ) -> Self::Primitive { + Primitive::Group { + primitives: row + .children + .iter() + .zip(layout.children()) + .map(|(child, layout)| { + child.draw(self, layout, cursor_position) + }) + .collect(), + } + } +} diff --git a/wgpu/src/renderer/slider.rs b/wgpu/src/renderer/slider.rs new file mode 100644 index 00000000..2e76022d --- /dev/null +++ b/wgpu/src/renderer/slider.rs @@ -0,0 +1,17 @@ +use crate::{Primitive, Renderer}; +use iced_native::{slider, Layout, Node, Point, Slider, Style}; + +impl slider::Renderer for Renderer { + fn node(&self, _slider: &Slider) -> Node { + Node::new(Style::default()) + } + + fn draw( + &mut self, + _slider: &Slider, + _layout: Layout<'_>, + _cursor_position: Point, + ) -> Self::Primitive { + Primitive::None + } +} diff --git a/wgpu/src/renderer/text.rs b/wgpu/src/renderer/text.rs new file mode 100644 index 00000000..4434cc22 --- /dev/null +++ b/wgpu/src/renderer/text.rs @@ -0,0 +1,77 @@ +use crate::{Primitive, Renderer}; +use iced_native::{text, Layout, Node, Style, Text}; + +use wgpu_glyph::{GlyphCruncher, Section}; + +use std::cell::RefCell; +use std::f32; + +impl text::Renderer for Renderer { + fn node(&self, text: &Text) -> Node { + let glyph_brush = self.glyph_brush.clone(); + let content = text.content.clone(); + + // TODO: Investigate why stretch tries to measure this MANY times + // with every ancestor's bounds. + // Bug? Using the library wrong? I should probably open an issue on + // the stretch repository. + // 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 = text.size.map(f32::from).unwrap_or(20.0); + + 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_native::Number::Undefined => f32::INFINITY, + iced_native::Number::Defined(w) => w, + }, + match bounds.height { + iced_native::Number::Undefined => f32::INFINITY, + iced_native::Number::Defined(h) => h, + }, + ); + + let text = Section { + text: &content, + scale: wgpu_glyph::Scale { x: size, y: size }, + bounds, + ..Default::default() + }; + + let (width, height) = if let Some(bounds) = + glyph_brush.borrow_mut().glyph_bounds(&text) + { + (bounds.width(), bounds.height()) + } else { + (0.0, 0.0) + }; + + let size = iced_native::Size { width, height }; + + // If the text has no width boundary we avoid caching as the + // layout engine may just be measuring text in a row. + if bounds.0 == f32::INFINITY { + return size; + } else { + *measure = Some(size); + } + } + + measure.unwrap() + }) + } + + fn draw(&mut self, text: &Text, layout: Layout<'_>) -> Self::Primitive { + Primitive::Text { + content: text.content.clone(), + size: f32::from(text.size.unwrap_or(20)), + bounds: layout.bounds(), + } + } +} -- cgit From 7765e6da50a3448501ee1b639e580c94a4fbda8a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 6 Oct 2019 19:22:25 +0200 Subject: Draft `Box` primitive --- wgpu/src/lib.rs | 2 +- wgpu/src/primitive.rs | 11 ++++++++++- wgpu/src/renderer.rs | 3 +++ wgpu/src/renderer/button.rs | 38 ++++++++++++++++++++++++++++++-------- 4 files changed, 44 insertions(+), 10 deletions(-) diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 9a2a336f..d5cfee64 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -3,5 +3,5 @@ mod primitive; mod renderer; pub use mouse_cursor::MouseCursor; -pub use primitive::Primitive; +pub use primitive::{Background, Primitive}; pub use renderer::{Renderer, Target}; diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs index 04b2e99f..e9ab100e 100644 --- a/wgpu/src/primitive.rs +++ b/wgpu/src/primitive.rs @@ -1,4 +1,4 @@ -use iced_native::Rectangle; +use iced_native::{Color, Rectangle}; #[derive(Debug, Clone)] pub enum Primitive { @@ -11,4 +11,13 @@ pub enum Primitive { bounds: Rectangle, size: f32, }, + Box { + bounds: Rectangle, + background: Background, + }, +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Background { + Color(Color), } diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index df8887e3..56986bd1 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -144,6 +144,9 @@ impl Renderer { scale: wgpu_glyph::Scale { x: *size, y: *size }, ..Default::default() }), + Primitive::Box { bounds, background } => { + // TODO: Batch boxes and draw them all at once + } } } } diff --git a/wgpu/src/renderer/button.rs b/wgpu/src/renderer/button.rs index 24bc1de6..b9dfb9ac 100644 --- a/wgpu/src/renderer/button.rs +++ b/wgpu/src/renderer/button.rs @@ -1,18 +1,40 @@ -use crate::{Primitive, Renderer}; -use iced_native::{button, Button, Layout, Node, Point, Style}; +use crate::{Background, Primitive, Renderer}; +use iced_native::{button, Button, Color, Layout, Length, Node, Point, Style}; impl button::Renderer for Renderer { - fn node(&self, _button: &Button) -> Node { - Node::new(Style::default()) + fn node(&self, button: &Button) -> Node { + let style = Style::default() + .width(button.width) + .min_height(Length::Units(30)) + .min_width(Length::Units(100)) + .align_self(button.align_self); + + Node::new(style) } fn draw( &mut self, - _button: &Button, - _layout: Layout<'_>, + button: &Button, + layout: Layout<'_>, _cursor_position: Point, ) -> Self::Primitive { - // TODO - Primitive::None + Primitive::Group { + primitives: vec![ + Primitive::Box { + bounds: layout.bounds(), + background: Background::Color(Color { + r: 0.0, + b: 1.0, + g: 0.0, + a: 1.0, + }), + }, + Primitive::Text { + content: button.label.clone(), + size: 20.0, + bounds: layout.bounds(), + }, + ], + } } } -- cgit From 5286ef36b6a5eb6846b5675a7a4aced72601df3b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 6 Oct 2019 20:06:13 +0200 Subject: Make `tour` a simple example instead of a crate --- Cargo.toml | 1 - examples/resources/Roboto-LICENSE | 202 ++++++++++ examples/resources/Roboto-Regular.ttf | Bin 0 -> 171272 bytes examples/resources/ferris.png | Bin 0 -> 33061 bytes examples/resources/ui.png | Bin 0 -> 16691 bytes examples/tour.html | 13 + examples/tour.rs | 587 +++++++++++++++++++++++++++++ examples/tour/Cargo.toml | 12 - examples/tour/README.md | 66 ---- examples/tour/index.html | 13 - examples/tour/resources/Roboto-LICENSE | 202 ---------- examples/tour/resources/Roboto-Regular.ttf | Bin 171272 -> 0 bytes examples/tour/resources/ferris.png | Bin 33061 -> 0 bytes examples/tour/resources/ui.png | Bin 16691 -> 0 bytes examples/tour/src/main.rs | 587 ----------------------------- wgpu/src/renderer.rs | 2 +- 16 files changed, 803 insertions(+), 882 deletions(-) create mode 100644 examples/resources/Roboto-LICENSE create mode 100644 examples/resources/Roboto-Regular.ttf create mode 100644 examples/resources/ferris.png create mode 100644 examples/resources/ui.png create mode 100644 examples/tour.html create mode 100644 examples/tour.rs delete mode 100644 examples/tour/Cargo.toml delete mode 100644 examples/tour/README.md delete mode 100644 examples/tour/index.html delete mode 100644 examples/tour/resources/Roboto-LICENSE delete mode 100644 examples/tour/resources/Roboto-Regular.ttf delete mode 100644 examples/tour/resources/ferris.png delete mode 100644 examples/tour/resources/ui.png delete mode 100644 examples/tour/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 08d51a0e..4773898b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,6 @@ members = [ "core", "native", "web", - "examples/tour", ] [features] diff --git a/examples/resources/Roboto-LICENSE b/examples/resources/Roboto-LICENSE new file mode 100644 index 00000000..75b52484 --- /dev/null +++ b/examples/resources/Roboto-LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/examples/resources/Roboto-Regular.ttf b/examples/resources/Roboto-Regular.ttf new file mode 100644 index 00000000..2b6392ff Binary files /dev/null and b/examples/resources/Roboto-Regular.ttf differ diff --git a/examples/resources/ferris.png b/examples/resources/ferris.png new file mode 100644 index 00000000..ebce1a14 Binary files /dev/null and b/examples/resources/ferris.png differ diff --git a/examples/resources/ui.png b/examples/resources/ui.png new file mode 100644 index 00000000..4fd3beb3 Binary files /dev/null and b/examples/resources/ui.png differ diff --git a/examples/tour.html b/examples/tour.html new file mode 100644 index 00000000..b17ac4a2 --- /dev/null +++ b/examples/tour.html @@ -0,0 +1,13 @@ + + + + + Tour - Iced + + + + + diff --git a/examples/tour.rs b/examples/tour.rs new file mode 100644 index 00000000..0b88ccd5 --- /dev/null +++ b/examples/tour.rs @@ -0,0 +1,587 @@ +use iced::{ + button, slider, text::HorizontalAlignment, Align, Button, Checkbox, Color, + Column, Element, Image, Justify, Length, Radio, Row, Slider, Text, + UserInterface, +}; + +pub fn main() { + let tour = Tour::new(); + + tour.run(); +} + +pub struct Tour { + steps: Steps, + back_button: button::State, + next_button: button::State, + debug: bool, +} + +impl Tour { + pub fn new() -> Tour { + Tour { + steps: Steps::new(), + back_button: button::State::new(), + next_button: button::State::new(), + debug: false, + } + } +} + +impl UserInterface for Tour { + type Message = Message; + + fn update(&mut self, event: Message) { + match event { + Message::BackPressed => { + self.steps.go_back(); + } + Message::NextPressed => { + self.steps.advance(); + } + Message::StepMessage(step_msg) => { + self.steps.update(step_msg, &mut self.debug); + } + } + } + + fn view(&mut self) -> Element { + let Tour { + steps, + back_button, + next_button, + .. + } = self; + + let mut controls = Row::new(); + + if steps.has_previous() { + controls = controls.push( + Button::new(back_button, "Back") + .on_press(Message::BackPressed) + .class(button::Class::Secondary), + ); + } + + controls = controls.push(Column::new()); + + if steps.can_continue() { + controls = controls.push( + Button::new(next_button, "Next").on_press(Message::NextPressed), + ); + } + + let element: Element<_> = Column::new() + .max_width(Length::Units(500)) + .spacing(20) + .padding(20) + .push(steps.view(self.debug).map(Message::StepMessage)) + .push(controls) + .into(); + + let element = if self.debug { + element.explain(Color::BLACK) + } else { + element + }; + + Column::new() + .width(Length::Fill) + .height(Length::Fill) + .align_items(Align::Center) + .justify_content(Justify::Center) + .push(element) + .into() + } +} + +#[derive(Debug, Clone, Copy)] +pub enum Message { + BackPressed, + NextPressed, + StepMessage(StepMessage), +} + +struct Steps { + steps: Vec, + current: usize, +} + +impl Steps { + fn new() -> Steps { + Steps { + steps: vec![ + Step::Welcome, + Step::Slider { + state: slider::State::new(), + value: 50, + }, + Step::RowsAndColumns { + layout: Layout::Row, + spacing_slider: slider::State::new(), + spacing: 20, + }, + Step::Text { + size_slider: slider::State::new(), + size: 30, + color_sliders: [slider::State::new(); 3], + color: Color::BLACK, + }, + Step::Radio { selection: None }, + Step::Image { + width: 300, + slider: slider::State::new(), + }, + Step::Debugger, + Step::End, + ], + current: 0, + } + } + + fn update(&mut self, msg: StepMessage, debug: &mut bool) { + self.steps[self.current].update(msg, debug); + } + + fn view(&mut self, debug: bool) -> Element { + self.steps[self.current].view(debug) + } + + fn advance(&mut self) { + if self.can_continue() { + self.current += 1; + } + } + + fn go_back(&mut self) { + if self.has_previous() { + self.current -= 1; + } + } + + fn has_previous(&self) -> bool { + self.current > 0 + } + + fn can_continue(&self) -> bool { + self.current + 1 < self.steps.len() + && self.steps[self.current].can_continue() + } +} + +enum Step { + Welcome, + Slider { + state: slider::State, + value: u16, + }, + RowsAndColumns { + layout: Layout, + spacing_slider: slider::State, + spacing: u16, + }, + Text { + size_slider: slider::State, + size: u16, + color_sliders: [slider::State; 3], + color: Color, + }, + Radio { + selection: Option, + }, + Image { + width: u16, + slider: slider::State, + }, + Debugger, + End, +} + +#[derive(Debug, Clone, Copy)] +pub enum StepMessage { + SliderChanged(f32), + LayoutChanged(Layout), + SpacingChanged(f32), + TextSizeChanged(f32), + TextColorChanged(Color), + LanguageSelected(Language), + ImageWidthChanged(f32), + DebugToggled(bool), +} + +impl<'a> Step { + fn update(&mut self, msg: StepMessage, debug: &mut bool) { + match msg { + StepMessage::DebugToggled(value) => { + if let Step::Debugger = self { + *debug = value; + } + } + StepMessage::LanguageSelected(language) => { + if let Step::Radio { selection } = self { + *selection = Some(language); + } + } + StepMessage::SliderChanged(new_value) => { + if let Step::Slider { value, .. } = self { + *value = new_value.round() as u16; + } + } + StepMessage::TextSizeChanged(new_size) => { + if let Step::Text { size, .. } = self { + *size = new_size.round() as u16; + } + } + StepMessage::TextColorChanged(new_color) => { + if let Step::Text { color, .. } = self { + *color = new_color; + } + } + StepMessage::LayoutChanged(new_layout) => { + if let Step::RowsAndColumns { layout, .. } = self { + *layout = new_layout; + } + } + StepMessage::SpacingChanged(new_spacing) => { + if let Step::RowsAndColumns { spacing, .. } = self { + *spacing = new_spacing.round() as u16; + } + } + StepMessage::ImageWidthChanged(new_width) => { + if let Step::Image { width, .. } = self { + *width = new_width.round() as u16; + } + } + }; + } + + fn can_continue(&self) -> bool { + match self { + Step::Welcome => true, + Step::Radio { selection } => *selection == Some(Language::Rust), + Step::Slider { .. } => true, + Step::Text { .. } => true, + Step::Image { .. } => true, + Step::RowsAndColumns { .. } => true, + Step::Debugger => true, + Step::End => false, + } + } + + fn view(&mut self, debug: bool) -> Element { + match self { + Step::Welcome => Self::welcome().into(), + Step::Radio { selection } => Self::radio(*selection).into(), + Step::Slider { state, value } => Self::slider(state, *value).into(), + Step::Text { + size_slider, + size, + color_sliders, + color, + } => Self::text(size_slider, *size, color_sliders, *color).into(), + Step::Image { width, slider } => Self::image(*width, slider).into(), + Step::RowsAndColumns { + layout, + spacing_slider, + spacing, + } => { + Self::rows_and_columns(*layout, spacing_slider, *spacing).into() + } + Step::Debugger => Self::debugger(debug).into(), + Step::End => Self::end().into(), + } + } + + fn container(title: &str) -> Column<'a, StepMessage> { + Column::new() + .spacing(20) + .align_items(Align::Stretch) + .push(Text::new(title).size(50)) + } + + fn welcome() -> Column<'a, StepMessage> { + Self::container("Welcome!") + .push(Text::new( + "This a simple tour meant to showcase a bunch of widgets that \ + can be easily implemented on top of Iced.", + )) + .push(Text::new( + "Iced is a renderer-agnostic GUI library for Rust focused on \ + simplicity and type-safety. It is heavily inspired by Elm.", + )) + .push(Text::new( + "It was originally born as part of Coffee, an opinionated \ + 2D game engine for Rust.", + )) + .push(Text::new( + "Iced does not provide a built-in renderer. On native \ + platforms, this example runs on a fairly simple renderer \ + built on top of ggez, another game library.", + )) + .push(Text::new( + "Additionally, this tour can also run on WebAssembly thanks \ + to dodrio, an experimental VDOM library for Rust.", + )) + .push(Text::new( + "You will need to interact with the UI in order to reach the \ + end!", + )) + } + + fn slider( + state: &'a mut slider::State, + value: u16, + ) -> Column<'a, StepMessage> { + Self::container("Slider") + .push(Text::new( + "A slider allows you to smoothly select a value from a range \ + of values.", + )) + .push(Text::new( + "The following slider lets you choose an integer from \ + 0 to 100:", + )) + .push(Slider::new( + state, + 0.0..=100.0, + value as f32, + StepMessage::SliderChanged, + )) + .push( + Text::new(&value.to_string()) + .horizontal_alignment(HorizontalAlignment::Center), + ) + } + + fn rows_and_columns( + layout: Layout, + spacing_slider: &'a mut slider::State, + spacing: u16, + ) -> Column<'a, StepMessage> { + let row_radio = Radio::new( + Layout::Row, + "Row", + Some(layout), + StepMessage::LayoutChanged, + ); + + let column_radio = Radio::new( + Layout::Column, + "Column", + Some(layout), + StepMessage::LayoutChanged, + ); + + let layout_section: Element<_> = match layout { + Layout::Row => Row::new() + .spacing(spacing) + .push(row_radio) + .push(column_radio) + .into(), + Layout::Column => Column::new() + .spacing(spacing) + .push(row_radio) + .push(column_radio) + .into(), + }; + + let spacing_section = Column::new() + .spacing(10) + .push(Slider::new( + spacing_slider, + 0.0..=80.0, + spacing as f32, + StepMessage::SpacingChanged, + )) + .push( + Text::new(&format!("{} px", spacing)) + .horizontal_alignment(HorizontalAlignment::Center), + ); + + Self::container("Rows and columns") + .spacing(spacing) + .push(Text::new( + "Iced uses a layout model based on flexbox to position UI \ + elements.", + )) + .push(Text::new( + "Rows and columns can be used to distribute content \ + horizontally or vertically, respectively.", + )) + .push(layout_section) + .push(Text::new( + "You can also easily change the spacing between elements:", + )) + .push(spacing_section) + } + + fn text( + size_slider: &'a mut slider::State, + size: u16, + color_sliders: &'a mut [slider::State; 3], + color: Color, + ) -> Column<'a, StepMessage> { + let size_section = Column::new() + .padding(20) + .spacing(20) + .push(Text::new("You can change its size:")) + .push( + Text::new(&format!("This text is {} pixels", size)).size(size), + ) + .push(Slider::new( + size_slider, + 10.0..=70.0, + size as f32, + StepMessage::TextSizeChanged, + )); + + let [red, green, blue] = color_sliders; + let color_section = Column::new() + .padding(20) + .spacing(20) + .push(Text::new("And its color:")) + .push(Text::new(&format!("{:?}", color)).color(color)) + .push( + Row::new() + .spacing(10) + .push(Slider::new(red, 0.0..=1.0, color.r, move |r| { + StepMessage::TextColorChanged(Color { r, ..color }) + })) + .push(Slider::new(green, 0.0..=1.0, color.g, move |g| { + StepMessage::TextColorChanged(Color { g, ..color }) + })) + .push(Slider::new(blue, 0.0..=1.0, color.b, move |b| { + StepMessage::TextColorChanged(Color { b, ..color }) + })), + ); + + Self::container("Text") + .push(Text::new( + "Text is probably the most essential widget for your UI. \ + It will try to adapt to the dimensions of its container.", + )) + .push(size_section) + .push(color_section) + } + + fn radio(selection: Option) -> Column<'a, StepMessage> { + let question = Column::new() + .padding(20) + .spacing(10) + .push(Text::new("Iced is written in...").size(24)) + .push(Language::all().iter().cloned().fold( + Column::new().padding(10).spacing(20), + |choices, language| { + choices.push(Radio::new( + language, + language.into(), + selection, + StepMessage::LanguageSelected, + )) + }, + )); + + Self::container("Radio button") + .push(Text::new( + "A radio button is normally used to represent a choice... \ + Surprise test!", + )) + .push(question) + .push(Text::new( + "Iced works very well with iterators! The list above is \ + basically created by folding a column over the different \ + choices, creating a radio button for each one of them!", + )) + } + + fn image( + width: u16, + slider: &'a mut slider::State, + ) -> Column<'a, StepMessage> { + Self::container("Image") + .push(Text::new("An image that tries to keep its aspect ratio.")) + .push( + Image::new("resources/ferris.png") + .width(Length::Units(width)) + .align_self(Align::Center), + ) + .push(Slider::new( + slider, + 100.0..=500.0, + width as f32, + StepMessage::ImageWidthChanged, + )) + .push( + Text::new(&format!("Width: {} px", width.to_string())) + .horizontal_alignment(HorizontalAlignment::Center), + ) + } + + fn debugger(debug: bool) -> Column<'a, StepMessage> { + Self::container("Debugger") + .push(Text::new( + "You can ask Iced to visually explain the layouting of the \ + different elements comprising your UI!", + )) + .push(Text::new( + "Give it a shot! Check the following checkbox to be able to \ + see element boundaries.", + )) + .push(Checkbox::new( + debug, + "Explain layout", + StepMessage::DebugToggled, + )) + .push(Text::new("Feel free to go back and take a look.")) + } + + fn end() -> Column<'a, StepMessage> { + Self::container("You reached the end!") + .push(Text::new( + "This tour will be updated as more features are added.", + )) + .push(Text::new("Make sure to keep an eye on it!")) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Language { + Rust, + Elm, + Ruby, + Haskell, + C, + Other, +} + +impl Language { + fn all() -> [Language; 6] { + [ + Language::C, + Language::Elm, + Language::Ruby, + Language::Haskell, + Language::Rust, + Language::Other, + ] + } +} + +impl From for &str { + fn from(language: Language) -> &'static str { + match language { + Language::Rust => "Rust", + Language::Elm => "Elm", + Language::Ruby => "Ruby", + Language::Haskell => "Haskell", + Language::C => "C", + Language::Other => "Other", + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Layout { + Row, + Column, +} diff --git a/examples/tour/Cargo.toml b/examples/tour/Cargo.toml deleted file mode 100644 index 8b3d7765..00000000 --- a/examples/tour/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "iced_tour" -version = "0.0.0" -authors = ["Héctor Ramón Jiménez "] -description = "Tour example for Iced" -license = "MIT" -repository = "https://github.com/hecrj/iced" -edition = "2018" -publish = false - -[dependencies] -iced = { version = "0.1.0-alpha.1", path = "../.." } diff --git a/examples/tour/README.md b/examples/tour/README.md deleted file mode 100644 index 7ef1a212..00000000 --- a/examples/tour/README.md +++ /dev/null @@ -1,66 +0,0 @@ -# Tour - -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 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 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 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: - -``` -python3 -m http.server -``` - -[![Tour - Iced][gui_gif]][gui_gfycat] - -[`ggez`]: https://github.com/ggez/ggez -[`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 -[gui_gif]: https://thumbs.gfycat.com/VeneratedSourAurochs-small.gif -[gui_gfycat]: https://gfycat.com/veneratedsouraurochs diff --git a/examples/tour/index.html b/examples/tour/index.html deleted file mode 100644 index b17ac4a2..00000000 --- a/examples/tour/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - Tour - Iced - - - - - diff --git a/examples/tour/resources/Roboto-LICENSE b/examples/tour/resources/Roboto-LICENSE deleted file mode 100644 index 75b52484..00000000 --- a/examples/tour/resources/Roboto-LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/examples/tour/resources/Roboto-Regular.ttf b/examples/tour/resources/Roboto-Regular.ttf deleted file mode 100644 index 2b6392ff..00000000 Binary files a/examples/tour/resources/Roboto-Regular.ttf and /dev/null differ diff --git a/examples/tour/resources/ferris.png b/examples/tour/resources/ferris.png deleted file mode 100644 index ebce1a14..00000000 Binary files a/examples/tour/resources/ferris.png and /dev/null differ diff --git a/examples/tour/resources/ui.png b/examples/tour/resources/ui.png deleted file mode 100644 index 4fd3beb3..00000000 Binary files a/examples/tour/resources/ui.png and /dev/null differ diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs deleted file mode 100644 index 0b88ccd5..00000000 --- a/examples/tour/src/main.rs +++ /dev/null @@ -1,587 +0,0 @@ -use iced::{ - button, slider, text::HorizontalAlignment, Align, Button, Checkbox, Color, - Column, Element, Image, Justify, Length, Radio, Row, Slider, Text, - UserInterface, -}; - -pub fn main() { - let tour = Tour::new(); - - tour.run(); -} - -pub struct Tour { - steps: Steps, - back_button: button::State, - next_button: button::State, - debug: bool, -} - -impl Tour { - pub fn new() -> Tour { - Tour { - steps: Steps::new(), - back_button: button::State::new(), - next_button: button::State::new(), - debug: false, - } - } -} - -impl UserInterface for Tour { - type Message = Message; - - fn update(&mut self, event: Message) { - match event { - Message::BackPressed => { - self.steps.go_back(); - } - Message::NextPressed => { - self.steps.advance(); - } - Message::StepMessage(step_msg) => { - self.steps.update(step_msg, &mut self.debug); - } - } - } - - fn view(&mut self) -> Element { - let Tour { - steps, - back_button, - next_button, - .. - } = self; - - let mut controls = Row::new(); - - if steps.has_previous() { - controls = controls.push( - Button::new(back_button, "Back") - .on_press(Message::BackPressed) - .class(button::Class::Secondary), - ); - } - - controls = controls.push(Column::new()); - - if steps.can_continue() { - controls = controls.push( - Button::new(next_button, "Next").on_press(Message::NextPressed), - ); - } - - let element: Element<_> = Column::new() - .max_width(Length::Units(500)) - .spacing(20) - .padding(20) - .push(steps.view(self.debug).map(Message::StepMessage)) - .push(controls) - .into(); - - let element = if self.debug { - element.explain(Color::BLACK) - } else { - element - }; - - Column::new() - .width(Length::Fill) - .height(Length::Fill) - .align_items(Align::Center) - .justify_content(Justify::Center) - .push(element) - .into() - } -} - -#[derive(Debug, Clone, Copy)] -pub enum Message { - BackPressed, - NextPressed, - StepMessage(StepMessage), -} - -struct Steps { - steps: Vec, - current: usize, -} - -impl Steps { - fn new() -> Steps { - Steps { - steps: vec![ - Step::Welcome, - Step::Slider { - state: slider::State::new(), - value: 50, - }, - Step::RowsAndColumns { - layout: Layout::Row, - spacing_slider: slider::State::new(), - spacing: 20, - }, - Step::Text { - size_slider: slider::State::new(), - size: 30, - color_sliders: [slider::State::new(); 3], - color: Color::BLACK, - }, - Step::Radio { selection: None }, - Step::Image { - width: 300, - slider: slider::State::new(), - }, - Step::Debugger, - Step::End, - ], - current: 0, - } - } - - fn update(&mut self, msg: StepMessage, debug: &mut bool) { - self.steps[self.current].update(msg, debug); - } - - fn view(&mut self, debug: bool) -> Element { - self.steps[self.current].view(debug) - } - - fn advance(&mut self) { - if self.can_continue() { - self.current += 1; - } - } - - fn go_back(&mut self) { - if self.has_previous() { - self.current -= 1; - } - } - - fn has_previous(&self) -> bool { - self.current > 0 - } - - fn can_continue(&self) -> bool { - self.current + 1 < self.steps.len() - && self.steps[self.current].can_continue() - } -} - -enum Step { - Welcome, - Slider { - state: slider::State, - value: u16, - }, - RowsAndColumns { - layout: Layout, - spacing_slider: slider::State, - spacing: u16, - }, - Text { - size_slider: slider::State, - size: u16, - color_sliders: [slider::State; 3], - color: Color, - }, - Radio { - selection: Option, - }, - Image { - width: u16, - slider: slider::State, - }, - Debugger, - End, -} - -#[derive(Debug, Clone, Copy)] -pub enum StepMessage { - SliderChanged(f32), - LayoutChanged(Layout), - SpacingChanged(f32), - TextSizeChanged(f32), - TextColorChanged(Color), - LanguageSelected(Language), - ImageWidthChanged(f32), - DebugToggled(bool), -} - -impl<'a> Step { - fn update(&mut self, msg: StepMessage, debug: &mut bool) { - match msg { - StepMessage::DebugToggled(value) => { - if let Step::Debugger = self { - *debug = value; - } - } - StepMessage::LanguageSelected(language) => { - if let Step::Radio { selection } = self { - *selection = Some(language); - } - } - StepMessage::SliderChanged(new_value) => { - if let Step::Slider { value, .. } = self { - *value = new_value.round() as u16; - } - } - StepMessage::TextSizeChanged(new_size) => { - if let Step::Text { size, .. } = self { - *size = new_size.round() as u16; - } - } - StepMessage::TextColorChanged(new_color) => { - if let Step::Text { color, .. } = self { - *color = new_color; - } - } - StepMessage::LayoutChanged(new_layout) => { - if let Step::RowsAndColumns { layout, .. } = self { - *layout = new_layout; - } - } - StepMessage::SpacingChanged(new_spacing) => { - if let Step::RowsAndColumns { spacing, .. } = self { - *spacing = new_spacing.round() as u16; - } - } - StepMessage::ImageWidthChanged(new_width) => { - if let Step::Image { width, .. } = self { - *width = new_width.round() as u16; - } - } - }; - } - - fn can_continue(&self) -> bool { - match self { - Step::Welcome => true, - Step::Radio { selection } => *selection == Some(Language::Rust), - Step::Slider { .. } => true, - Step::Text { .. } => true, - Step::Image { .. } => true, - Step::RowsAndColumns { .. } => true, - Step::Debugger => true, - Step::End => false, - } - } - - fn view(&mut self, debug: bool) -> Element { - match self { - Step::Welcome => Self::welcome().into(), - Step::Radio { selection } => Self::radio(*selection).into(), - Step::Slider { state, value } => Self::slider(state, *value).into(), - Step::Text { - size_slider, - size, - color_sliders, - color, - } => Self::text(size_slider, *size, color_sliders, *color).into(), - Step::Image { width, slider } => Self::image(*width, slider).into(), - Step::RowsAndColumns { - layout, - spacing_slider, - spacing, - } => { - Self::rows_and_columns(*layout, spacing_slider, *spacing).into() - } - Step::Debugger => Self::debugger(debug).into(), - Step::End => Self::end().into(), - } - } - - fn container(title: &str) -> Column<'a, StepMessage> { - Column::new() - .spacing(20) - .align_items(Align::Stretch) - .push(Text::new(title).size(50)) - } - - fn welcome() -> Column<'a, StepMessage> { - Self::container("Welcome!") - .push(Text::new( - "This a simple tour meant to showcase a bunch of widgets that \ - can be easily implemented on top of Iced.", - )) - .push(Text::new( - "Iced is a renderer-agnostic GUI library for Rust focused on \ - simplicity and type-safety. It is heavily inspired by Elm.", - )) - .push(Text::new( - "It was originally born as part of Coffee, an opinionated \ - 2D game engine for Rust.", - )) - .push(Text::new( - "Iced does not provide a built-in renderer. On native \ - platforms, this example runs on a fairly simple renderer \ - built on top of ggez, another game library.", - )) - .push(Text::new( - "Additionally, this tour can also run on WebAssembly thanks \ - to dodrio, an experimental VDOM library for Rust.", - )) - .push(Text::new( - "You will need to interact with the UI in order to reach the \ - end!", - )) - } - - fn slider( - state: &'a mut slider::State, - value: u16, - ) -> Column<'a, StepMessage> { - Self::container("Slider") - .push(Text::new( - "A slider allows you to smoothly select a value from a range \ - of values.", - )) - .push(Text::new( - "The following slider lets you choose an integer from \ - 0 to 100:", - )) - .push(Slider::new( - state, - 0.0..=100.0, - value as f32, - StepMessage::SliderChanged, - )) - .push( - Text::new(&value.to_string()) - .horizontal_alignment(HorizontalAlignment::Center), - ) - } - - fn rows_and_columns( - layout: Layout, - spacing_slider: &'a mut slider::State, - spacing: u16, - ) -> Column<'a, StepMessage> { - let row_radio = Radio::new( - Layout::Row, - "Row", - Some(layout), - StepMessage::LayoutChanged, - ); - - let column_radio = Radio::new( - Layout::Column, - "Column", - Some(layout), - StepMessage::LayoutChanged, - ); - - let layout_section: Element<_> = match layout { - Layout::Row => Row::new() - .spacing(spacing) - .push(row_radio) - .push(column_radio) - .into(), - Layout::Column => Column::new() - .spacing(spacing) - .push(row_radio) - .push(column_radio) - .into(), - }; - - let spacing_section = Column::new() - .spacing(10) - .push(Slider::new( - spacing_slider, - 0.0..=80.0, - spacing as f32, - StepMessage::SpacingChanged, - )) - .push( - Text::new(&format!("{} px", spacing)) - .horizontal_alignment(HorizontalAlignment::Center), - ); - - Self::container("Rows and columns") - .spacing(spacing) - .push(Text::new( - "Iced uses a layout model based on flexbox to position UI \ - elements.", - )) - .push(Text::new( - "Rows and columns can be used to distribute content \ - horizontally or vertically, respectively.", - )) - .push(layout_section) - .push(Text::new( - "You can also easily change the spacing between elements:", - )) - .push(spacing_section) - } - - fn text( - size_slider: &'a mut slider::State, - size: u16, - color_sliders: &'a mut [slider::State; 3], - color: Color, - ) -> Column<'a, StepMessage> { - let size_section = Column::new() - .padding(20) - .spacing(20) - .push(Text::new("You can change its size:")) - .push( - Text::new(&format!("This text is {} pixels", size)).size(size), - ) - .push(Slider::new( - size_slider, - 10.0..=70.0, - size as f32, - StepMessage::TextSizeChanged, - )); - - let [red, green, blue] = color_sliders; - let color_section = Column::new() - .padding(20) - .spacing(20) - .push(Text::new("And its color:")) - .push(Text::new(&format!("{:?}", color)).color(color)) - .push( - Row::new() - .spacing(10) - .push(Slider::new(red, 0.0..=1.0, color.r, move |r| { - StepMessage::TextColorChanged(Color { r, ..color }) - })) - .push(Slider::new(green, 0.0..=1.0, color.g, move |g| { - StepMessage::TextColorChanged(Color { g, ..color }) - })) - .push(Slider::new(blue, 0.0..=1.0, color.b, move |b| { - StepMessage::TextColorChanged(Color { b, ..color }) - })), - ); - - Self::container("Text") - .push(Text::new( - "Text is probably the most essential widget for your UI. \ - It will try to adapt to the dimensions of its container.", - )) - .push(size_section) - .push(color_section) - } - - fn radio(selection: Option) -> Column<'a, StepMessage> { - let question = Column::new() - .padding(20) - .spacing(10) - .push(Text::new("Iced is written in...").size(24)) - .push(Language::all().iter().cloned().fold( - Column::new().padding(10).spacing(20), - |choices, language| { - choices.push(Radio::new( - language, - language.into(), - selection, - StepMessage::LanguageSelected, - )) - }, - )); - - Self::container("Radio button") - .push(Text::new( - "A radio button is normally used to represent a choice... \ - Surprise test!", - )) - .push(question) - .push(Text::new( - "Iced works very well with iterators! The list above is \ - basically created by folding a column over the different \ - choices, creating a radio button for each one of them!", - )) - } - - fn image( - width: u16, - slider: &'a mut slider::State, - ) -> Column<'a, StepMessage> { - Self::container("Image") - .push(Text::new("An image that tries to keep its aspect ratio.")) - .push( - Image::new("resources/ferris.png") - .width(Length::Units(width)) - .align_self(Align::Center), - ) - .push(Slider::new( - slider, - 100.0..=500.0, - width as f32, - StepMessage::ImageWidthChanged, - )) - .push( - Text::new(&format!("Width: {} px", width.to_string())) - .horizontal_alignment(HorizontalAlignment::Center), - ) - } - - fn debugger(debug: bool) -> Column<'a, StepMessage> { - Self::container("Debugger") - .push(Text::new( - "You can ask Iced to visually explain the layouting of the \ - different elements comprising your UI!", - )) - .push(Text::new( - "Give it a shot! Check the following checkbox to be able to \ - see element boundaries.", - )) - .push(Checkbox::new( - debug, - "Explain layout", - StepMessage::DebugToggled, - )) - .push(Text::new("Feel free to go back and take a look.")) - } - - fn end() -> Column<'a, StepMessage> { - Self::container("You reached the end!") - .push(Text::new( - "This tour will be updated as more features are added.", - )) - .push(Text::new("Make sure to keep an eye on it!")) - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Language { - Rust, - Elm, - Ruby, - Haskell, - C, - Other, -} - -impl Language { - fn all() -> [Language; 6] { - [ - Language::C, - Language::Elm, - Language::Ruby, - Language::Haskell, - Language::Rust, - Language::Other, - ] - } -} - -impl From for &str { - fn from(language: Language) -> &'static str { - match language { - Language::Rust => "Rust", - Language::Elm => "Elm", - Language::Ruby => "Ruby", - Language::Haskell => "Haskell", - Language::C => "C", - Language::Other => "Other", - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Layout { - Row, - Column, -} diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index 56986bd1..f8f000f0 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -54,7 +54,7 @@ impl Renderer { // TODO: Think about font loading strategy // Loading system fonts with fallback may be a good idea let font: &[u8] = - include_bytes!("../../examples/tour/resources/Roboto-Regular.ttf"); + include_bytes!("../../examples/resources/Roboto-Regular.ttf"); let glyph_brush = GlyphBrushBuilder::using_font_bytes(font) .build(&mut device, TextureFormat::Bgra8UnormSrgb); -- cgit From 70c17b053b10741f6018b2559bb46c5f289cadb9 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 7 Oct 2019 02:17:40 +0200 Subject: Rename `Box` primitive to `Quad` --- wgpu/src/primitive.rs | 2 +- wgpu/src/renderer.rs | 4 ++-- wgpu/src/renderer/button.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs index e9ab100e..91cdfc85 100644 --- a/wgpu/src/primitive.rs +++ b/wgpu/src/primitive.rs @@ -11,7 +11,7 @@ pub enum Primitive { bounds: Rectangle, size: f32, }, - Box { + Quad { bounds: Rectangle, background: Background, }, diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index f8f000f0..5db47a8e 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -144,8 +144,8 @@ impl Renderer { scale: wgpu_glyph::Scale { x: *size, y: *size }, ..Default::default() }), - Primitive::Box { bounds, background } => { - // TODO: Batch boxes and draw them all at once + Primitive::Quad { bounds, background } => { + // TODO: Batch quads and draw them all at once } } } diff --git a/wgpu/src/renderer/button.rs b/wgpu/src/renderer/button.rs index b9dfb9ac..fd874832 100644 --- a/wgpu/src/renderer/button.rs +++ b/wgpu/src/renderer/button.rs @@ -20,7 +20,7 @@ impl button::Renderer for Renderer { ) -> Self::Primitive { Primitive::Group { primitives: vec![ - Primitive::Box { + Primitive::Quad { bounds: layout.bounds(), background: Background::Color(Color { r: 0.0, -- cgit From c9510db551893775d3233340dd114d971e24323a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 7 Oct 2019 03:56:16 +0200 Subject: Render colored quads --- Cargo.toml | 3 + core/src/color.rs | 19 +++ examples/tour.rs | 2 + wgpu/src/lib.rs | 5 + wgpu/src/primitive.rs | 1 + wgpu/src/quad.rs | 269 ++++++++++++++++++++++++++++++++++++++++++ wgpu/src/renderer.rs | 30 ++++- wgpu/src/renderer/button.rs | 10 +- wgpu/src/shader/quad.frag | 9 ++ wgpu/src/shader/quad.frag.spv | Bin 0 -> 372 bytes wgpu/src/shader/quad.vert | 24 ++++ wgpu/src/shader/quad.vert.spv | Bin 0 -> 2188 bytes wgpu/src/transformation.rs | 30 +++++ 13 files changed, 396 insertions(+), 6 deletions(-) create mode 100644 wgpu/src/quad.rs create mode 100644 wgpu/src/shader/quad.frag create mode 100644 wgpu/src/shader/quad.frag.spv create mode 100644 wgpu/src/shader/quad.vert create mode 100644 wgpu/src/shader/quad.vert.spv create mode 100644 wgpu/src/transformation.rs diff --git a/Cargo.toml b/Cargo.toml index 4773898b..1a4e4043 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,3 +30,6 @@ iced_wgpu = { version = "0.1.0-alpha", path = "wgpu" } [target.'cfg(target_arch = "wasm32")'.dependencies] iced_web = { version = "0.1.0-alpha", path = "web" } + +[dev-dependencies] +env_logger = "0.7" diff --git a/core/src/color.rs b/core/src/color.rs index 5cc3a084..2b64c78d 100644 --- a/core/src/color.rs +++ b/core/src/color.rs @@ -16,4 +16,23 @@ impl Color { b: 0.0, a: 1.0, }; + + pub fn into_linear(self) -> [f32; 4] { + // As described in: + // https://en.wikipedia.org/wiki/SRGB#The_reverse_transformation + fn linear_component(u: f32) -> f32 { + if u < 0.04045 { + u / 12.92 + } else { + ((u + 0.055) / 1.055).powf(2.4) + } + } + + [ + linear_component(self.r), + linear_component(self.g), + linear_component(self.b), + self.a, + ] + } } diff --git a/examples/tour.rs b/examples/tour.rs index 0b88ccd5..5f24eb87 100644 --- a/examples/tour.rs +++ b/examples/tour.rs @@ -5,6 +5,8 @@ use iced::{ }; pub fn main() { + env_logger::init(); + let tour = Tour::new(); tour.run(); diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index d5cfee64..33d8f5ed 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -1,6 +1,11 @@ mod mouse_cursor; mod primitive; +mod quad; mod renderer; +mod transformation; + +pub(crate) use quad::Quad; +pub(crate) use transformation::Transformation; pub use mouse_cursor::MouseCursor; pub use primitive::{Background, Primitive}; diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs index 91cdfc85..f6730a1f 100644 --- a/wgpu/src/primitive.rs +++ b/wgpu/src/primitive.rs @@ -20,4 +20,5 @@ pub enum Primitive { #[derive(Debug, Clone, Copy, PartialEq)] pub enum Background { Color(Color), + // TODO: Add gradient and image variants } diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs new file mode 100644 index 00000000..d0126bbd --- /dev/null +++ b/wgpu/src/quad.rs @@ -0,0 +1,269 @@ +use crate::Transformation; + +use std::mem; + +pub struct Pipeline { + pipeline: wgpu::RenderPipeline, + constants: wgpu::BindGroup, + transform: wgpu::Buffer, + vertices: wgpu::Buffer, + indices: wgpu::Buffer, + instances: wgpu::Buffer, +} + +impl Pipeline { + pub fn new(device: &mut wgpu::Device) -> Pipeline { + let constant_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + bindings: &[wgpu::BindGroupLayoutBinding { + binding: 0, + visibility: wgpu::ShaderStage::VERTEX, + ty: wgpu::BindingType::UniformBuffer { dynamic: false }, + }], + }); + + let matrix: [f32; 16] = Transformation::identity().into(); + + let transform = device + .create_buffer_mapped( + 16, + wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST, + ) + .fill_from_slice(&matrix[..]); + + let constants = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &constant_layout, + bindings: &[wgpu::Binding { + binding: 0, + resource: wgpu::BindingResource::Buffer { + buffer: &transform, + range: 0..64, + }, + }], + }); + + let layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + bind_group_layouts: &[&constant_layout], + }); + + let vs = include_bytes!("shader/quad.vert.spv"); + let vs_module = device.create_shader_module( + &wgpu::read_spirv(std::io::Cursor::new(&vs[..])) + .expect("Read quad vertex shader as SPIR-V"), + ); + + let fs = include_bytes!("shader/quad.frag.spv"); + let fs_module = device.create_shader_module( + &wgpu::read_spirv(std::io::Cursor::new(&fs[..])) + .expect("Read quad fragment shader as SPIR-V"), + ); + + let pipeline = + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + layout: &layout, + vertex_stage: wgpu::ProgrammableStageDescriptor { + module: &vs_module, + entry_point: "main", + }, + fragment_stage: Some(wgpu::ProgrammableStageDescriptor { + module: &fs_module, + entry_point: "main", + }), + rasterization_state: Some(wgpu::RasterizationStateDescriptor { + front_face: wgpu::FrontFace::Cw, + cull_mode: wgpu::CullMode::None, + depth_bias: 0, + depth_bias_slope_scale: 0.0, + depth_bias_clamp: 0.0, + }), + primitive_topology: wgpu::PrimitiveTopology::TriangleList, + color_states: &[wgpu::ColorStateDescriptor { + format: wgpu::TextureFormat::Bgra8UnormSrgb, + color_blend: wgpu::BlendDescriptor { + src_factor: wgpu::BlendFactor::SrcAlpha, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + alpha_blend: wgpu::BlendDescriptor { + src_factor: wgpu::BlendFactor::One, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + write_mask: wgpu::ColorWrite::ALL, + }], + depth_stencil_state: None, + index_format: wgpu::IndexFormat::Uint16, + vertex_buffers: &[ + wgpu::VertexBufferDescriptor { + stride: mem::size_of::() as u64, + step_mode: wgpu::InputStepMode::Vertex, + attributes: &[wgpu::VertexAttributeDescriptor { + shader_location: 0, + format: wgpu::VertexFormat::Float2, + offset: 0, + }], + }, + wgpu::VertexBufferDescriptor { + stride: mem::size_of::() as u64, + step_mode: wgpu::InputStepMode::Instance, + attributes: &[ + wgpu::VertexAttributeDescriptor { + shader_location: 1, + format: wgpu::VertexFormat::Float2, + offset: 0, + }, + wgpu::VertexAttributeDescriptor { + shader_location: 2, + format: wgpu::VertexFormat::Float2, + offset: 4 * 2, + }, + wgpu::VertexAttributeDescriptor { + shader_location: 3, + format: wgpu::VertexFormat::Float4, + offset: 4 * (2 + 2), + }, + ], + }, + ], + sample_count: 1, + sample_mask: !0, + alpha_to_coverage_enabled: false, + }); + + let vertices = device + .create_buffer_mapped(QUAD_VERTS.len(), wgpu::BufferUsage::VERTEX) + .fill_from_slice(&QUAD_VERTS); + + let indices = device + .create_buffer_mapped(QUAD_INDICES.len(), wgpu::BufferUsage::INDEX) + .fill_from_slice(&QUAD_INDICES); + + let instances = device.create_buffer(&wgpu::BufferDescriptor { + size: mem::size_of::() as u64 * Quad::MAX as u64, + usage: wgpu::BufferUsage::VERTEX | wgpu::BufferUsage::COPY_DST, + }); + + Pipeline { + pipeline, + constants, + transform, + vertices, + indices, + instances, + } + } + + pub fn draw( + &mut self, + device: &mut wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + instances: &[Quad], + transformation: Transformation, + target: &wgpu::TextureView, + ) { + let matrix: [f32; 16] = transformation.into(); + + let transform_buffer = device + .create_buffer_mapped(16, wgpu::BufferUsage::COPY_SRC) + .fill_from_slice(&matrix[..]); + + encoder.copy_buffer_to_buffer( + &transform_buffer, + 0, + &self.transform, + 0, + 16 * 4, + ); + + let mut i = 0; + let total = instances.len(); + + while i < total { + let end = (i + Quad::MAX).min(total); + let amount = end - i; + + let instance_buffer = device + .create_buffer_mapped(amount, wgpu::BufferUsage::COPY_SRC) + .fill_from_slice(&instances[i..end]); + + encoder.copy_buffer_to_buffer( + &instance_buffer, + 0, + &self.instances, + 0, + (mem::size_of::() * amount) as u64, + ); + + { + let mut render_pass = + encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + color_attachments: &[ + wgpu::RenderPassColorAttachmentDescriptor { + attachment: target, + resolve_target: None, + load_op: wgpu::LoadOp::Load, + store_op: wgpu::StoreOp::Store, + clear_color: wgpu::Color { + r: 0.0, + g: 0.0, + b: 0.0, + a: 0.0, + }, + }, + ], + depth_stencil_attachment: None, + }); + + render_pass.set_pipeline(&self.pipeline); + render_pass.set_bind_group(0, &self.constants, &[]); + render_pass.set_index_buffer(&self.indices, 0); + render_pass.set_vertex_buffers( + 0, + &[(&self.vertices, 0), (&self.instances, 0)], + ); + + render_pass.draw_indexed( + 0..QUAD_INDICES.len() as u32, + 0, + 0..amount as u32, + ); + } + + i += Quad::MAX; + } + } +} + +#[derive(Clone, Copy)] +pub struct Vertex { + _position: [f32; 2], +} + +const QUAD_INDICES: [u16; 6] = [0, 1, 2, 0, 2, 3]; + +const QUAD_VERTS: [Vertex; 4] = [ + Vertex { + _position: [0.0, 0.0], + }, + Vertex { + _position: [1.0, 0.0], + }, + Vertex { + _position: [1.0, 1.0], + }, + Vertex { + _position: [0.0, 1.0], + }, +]; + +#[derive(Debug, Clone, Copy)] +pub struct Quad { + pub position: [f32; 2], + pub scale: [f32; 2], + pub color: [f32; 4], +} + +impl Quad { + const MAX: usize = 100_000; +} diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index 5db47a8e..8e69a91a 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -1,4 +1,4 @@ -use crate::Primitive; +use crate::{quad, Background, Primitive, Quad, Transformation}; use iced_native::{renderer::Debugger, Color, Layout, Point, Widget}; use raw_window_handle::HasRawWindowHandle; @@ -25,12 +25,16 @@ pub struct Renderer { surface: Surface, adapter: Adapter, device: Device, + quad_pipeline: quad::Pipeline, + + quads: Vec, glyph_brush: Rc>>, } pub struct Target { width: u16, height: u16, + transformation: Transformation, swap_chain: SwapChain, } @@ -59,11 +63,16 @@ impl Renderer { let glyph_brush = GlyphBrushBuilder::using_font_bytes(font) .build(&mut device, TextureFormat::Bgra8UnormSrgb); + let quad_pipeline = quad::Pipeline::new(&mut device); + Self { instance, surface, adapter, device, + quad_pipeline, + + quads: Vec::new(), glyph_brush: Rc::new(RefCell::new(glyph_brush)), } } @@ -72,6 +81,7 @@ impl Renderer { Target { width, height, + transformation: Transformation::orthographic(width, height), swap_chain: self.device.create_swap_chain( &self.surface, &SwapChainDescriptor { @@ -110,6 +120,16 @@ impl Renderer { self.draw_primitive(primitive); + self.quad_pipeline.draw( + &mut self.device, + &mut encoder, + &self.quads, + target.transformation, + &frame.view, + ); + + self.quads.clear(); + self.glyph_brush .borrow_mut() .draw_queued( @@ -145,7 +165,13 @@ impl Renderer { ..Default::default() }), Primitive::Quad { bounds, background } => { - // TODO: Batch quads and draw them all at once + self.quads.push(Quad { + position: [bounds.x, bounds.y], + scale: [bounds.width, bounds.height], + color: match background { + Background::Color(color) => color.into_linear(), + }, + }); } } } diff --git a/wgpu/src/renderer/button.rs b/wgpu/src/renderer/button.rs index fd874832..f75b44f7 100644 --- a/wgpu/src/renderer/button.rs +++ b/wgpu/src/renderer/button.rs @@ -18,14 +18,16 @@ impl button::Renderer for Renderer { layout: Layout<'_>, _cursor_position: Point, ) -> Self::Primitive { + let bounds = layout.bounds(); + Primitive::Group { primitives: vec![ Primitive::Quad { - bounds: layout.bounds(), + bounds, background: Background::Color(Color { - r: 0.0, - b: 1.0, - g: 0.0, + r: 0.8, + b: 0.8, + g: 0.8, a: 1.0, }), }, diff --git a/wgpu/src/shader/quad.frag b/wgpu/src/shader/quad.frag new file mode 100644 index 00000000..1aca250f --- /dev/null +++ b/wgpu/src/shader/quad.frag @@ -0,0 +1,9 @@ +#version 450 + +layout(location = 0) in vec4 v_Color; + +layout(location = 0) out vec4 o_Color; + +void main() { + o_Color = v_Color; +} diff --git a/wgpu/src/shader/quad.frag.spv b/wgpu/src/shader/quad.frag.spv new file mode 100644 index 00000000..33218c83 Binary files /dev/null and b/wgpu/src/shader/quad.frag.spv differ diff --git a/wgpu/src/shader/quad.vert b/wgpu/src/shader/quad.vert new file mode 100644 index 00000000..3392d43d --- /dev/null +++ b/wgpu/src/shader/quad.vert @@ -0,0 +1,24 @@ +#version 450 + +layout(location = 0) in vec2 v_Pos; +layout(location = 1) in vec2 i_Pos; +layout(location = 2) in vec2 i_Scale; +layout(location = 3) in vec4 i_Color; + +layout (set = 0, binding = 0) uniform Globals { + mat4 u_Transform; +}; + +layout(location = 0) out vec4 o_Color; + +void main() { + mat4 i_Transform = mat4( + vec4(i_Scale.x, 0.0, 0.0, 0.0), + vec4(0.0, i_Scale.y, 0.0, 0.0), + vec4(0.0, 0.0, 1.0, 0.0), + vec4(i_Pos, 0.0, 1.0) + ); + + o_Color = i_Color; + gl_Position = u_Transform * i_Transform * vec4(v_Pos, 0.0, 1.0); +} diff --git a/wgpu/src/shader/quad.vert.spv b/wgpu/src/shader/quad.vert.spv new file mode 100644 index 00000000..d517796b Binary files /dev/null and b/wgpu/src/shader/quad.vert.spv differ diff --git a/wgpu/src/transformation.rs b/wgpu/src/transformation.rs new file mode 100644 index 00000000..1101e135 --- /dev/null +++ b/wgpu/src/transformation.rs @@ -0,0 +1,30 @@ +#[derive(Debug, Clone, Copy)] +pub struct Transformation([f32; 16]); + +impl Transformation { + #[rustfmt::skip] + pub fn identity() -> Self { + Transformation([ + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0, + ]) + } + + #[rustfmt::skip] + pub fn orthographic(width: u16, height: u16) -> Self { + Transformation([ + 2.0 / width as f32, 0.0, 0.0, 0.0, + 0.0, 2.0 / height as f32, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + -1.0, -1.0, 0.0, 1.0, + ]) + } +} + +impl From for [f32; 16] { + fn from(transformation: Transformation) -> [f32; 16] { + transformation.0 + } +} -- cgit From c9da3a10d9c4fcc9504b25eed873708406e3a9c7 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 7 Oct 2019 04:05:40 +0200 Subject: Use `log` crate instead of `dbg!` --- src/lib.rs | 4 ---- wgpu/Cargo.toml | 1 + wgpu/src/renderer.rs | 2 ++ 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f9860b37..77da5146 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,7 +40,6 @@ pub trait UserInterface { let mut cache = Some(iced_winit::Cache::default()); let mut events = Vec::new(); - let mut redraws = 0; let mut primitive = Primitive::None; window.request_redraw(); @@ -90,11 +89,8 @@ pub trait UserInterface { event: WindowEvent::RedrawRequested, .. } => { - println!("Redrawing {}", redraws); renderer.draw(&mut target, &primitive); - redraws += 1; - // TODO: Handle animations! // Maybe we can use `ControlFlow::WaitUntil` for this. } diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 79661baa..20fe9ae9 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -15,3 +15,4 @@ iced_native = { version = "0.1.0-alpha", path = "../native" } wgpu = "0.3" wgpu_glyph = "0.4" raw-window-handle = "0.1" +log = "0.4" diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index 8e69a91a..926f12ae 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -96,6 +96,8 @@ impl Renderer { } pub fn draw(&mut self, target: &mut Target, primitive: &Primitive) { + log::debug!("Drawing"); + let frame = target.swap_chain.get_next_texture(); let mut encoder = self -- cgit From 67f8da4b34da9bae4858e335c37ddc18e1a6e2b7 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 7 Oct 2019 04:12:39 +0200 Subject: Initialize renderer primitive properly --- src/lib.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 77da5146..c5a34dcd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,9 +38,15 @@ pub trait UserInterface { let mut renderer = Renderer::new(&window); let mut target = renderer.target(width, height); - let mut cache = Some(iced_winit::Cache::default()); + let user_interface = iced_winit::UserInterface::build( + document(&mut self, width, height), + iced_winit::Cache::default(), + &mut renderer, + ); + + let mut primitive = user_interface.draw(&mut renderer); + let mut cache = Some(user_interface.into_cache()); let mut events = Vec::new(); - let mut primitive = Primitive::None; window.request_redraw(); -- cgit From 0995950526bb605ddef5621c6e0590bb3232c1cb Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 7 Oct 2019 19:50:04 +0200 Subject: Use latest `wgpu` --- Cargo.toml | 3 --- wgpu/Cargo.toml | 7 ++----- wgpu/src/renderer.rs | 22 +++++++++++----------- 3 files changed, 13 insertions(+), 19 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1a4e4043..dbe24606 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,9 +21,6 @@ members = [ "web", ] -[features] -vulkan = ["iced_wgpu/vulkan"] - [target.'cfg(not(target_arch = "wasm32"))'.dependencies] iced_winit = { version = "0.1.0-alpha", path = "winit" } iced_wgpu = { version = "0.1.0-alpha", path = "wgpu" } diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 20fe9ae9..dbc1ddb9 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -7,12 +7,9 @@ description = "A wgpu renderer for Iced" license = "MIT" repository = "https://github.com/hecrj/iced" -[features] -vulkan = ["wgpu/vulkan"] - [dependencies] iced_native = { version = "0.1.0-alpha", path = "../native" } -wgpu = "0.3" -wgpu_glyph = "0.4" +wgpu = { version = "0.3", git = "https://github.com/gfx-rs/wgpu-rs", rev = "cb25914b95b58fee0dc139b400867e7a731d98f4" } +wgpu_glyph = { version = "0.4", git = "https://github.com/hecrj/wgpu_glyph", branch = "improvement/update-wgpu" } raw-window-handle = "0.1" log = "0.4" diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index 926f12ae..9883943b 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -3,8 +3,8 @@ use iced_native::{renderer::Debugger, Color, Layout, Point, Widget}; use raw_window_handle::HasRawWindowHandle; use wgpu::{ - Adapter, CommandEncoderDescriptor, Device, DeviceDescriptor, Extensions, - Instance, Limits, PowerPreference, RequestAdapterOptions, Surface, + Adapter, BackendBit, CommandEncoderDescriptor, Device, DeviceDescriptor, + Extensions, Limits, PowerPreference, Queue, RequestAdapterOptions, Surface, SwapChain, SwapChainDescriptor, TextureFormat, TextureUsage, }; use wgpu_glyph::{GlyphBrush, GlyphBrushBuilder, Section}; @@ -21,10 +21,10 @@ mod slider; mod text; pub struct Renderer { - instance: Instance, surface: Surface, adapter: Adapter, device: Device, + queue: Queue, quad_pipeline: quad::Pipeline, quads: Vec, @@ -40,20 +40,20 @@ pub struct Target { impl Renderer { pub fn new(window: &W) -> Self { - let instance = Instance::new(); - - let adapter = instance.request_adapter(&RequestAdapterOptions { + let adapter = Adapter::request(&RequestAdapterOptions { power_preference: PowerPreference::LowPower, - }); + backends: BackendBit::all(), + }) + .expect("Request adapter"); - let mut device = adapter.request_device(&DeviceDescriptor { + let (mut device, queue) = adapter.request_device(&DeviceDescriptor { extensions: Extensions { anisotropic_filtering: false, }, limits: Limits { max_bind_groups: 1 }, }); - let surface = instance.create_surface(window.raw_window_handle()); + let surface = Surface::create(window); // TODO: Think about font loading strategy // Loading system fonts with fallback may be a good idea @@ -66,10 +66,10 @@ impl Renderer { let quad_pipeline = quad::Pipeline::new(&mut device); Self { - instance, surface, adapter, device, + queue, quad_pipeline, quads: Vec::new(), @@ -143,7 +143,7 @@ impl Renderer { ) .expect("Draw text"); - self.device.get_queue().submit(&[encoder.finish()]); + self.queue.submit(&[encoder.finish()]); } fn draw_primitive(&mut self, primitive: &Primitive) { -- cgit From a0234d5bcea5b25f575af01d3a8e0296b2d0395c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 8 Oct 2019 00:14:11 +0200 Subject: Draft fragment shader for rounded rectangles --- wgpu/src/shader/quad.frag | 26 +++++++++++++++++++++++++- wgpu/src/shader/quad.frag.spv | Bin 372 -> 2900 bytes wgpu/src/shader/quad.vert | 5 +++++ wgpu/src/shader/quad.vert.spv | Bin 2188 -> 2364 bytes 4 files changed, 30 insertions(+), 1 deletion(-) diff --git a/wgpu/src/shader/quad.frag b/wgpu/src/shader/quad.frag index 1aca250f..1fc28bc1 100644 --- a/wgpu/src/shader/quad.frag +++ b/wgpu/src/shader/quad.frag @@ -1,9 +1,33 @@ #version 450 layout(location = 0) in vec4 v_Color; +layout(location = 1) in vec2 v_Pos; +layout(location = 2) in vec2 v_Scale; layout(location = 0) out vec4 o_Color; +float rounded(in vec2 frag_coord, in vec2 position, in vec2 size, float radius, float s) +{ + vec2 inner_size = size - vec2(radius, radius) * 2.0; + vec2 top_left = position + vec2(radius, radius); + vec2 bottom_right = top_left + inner_size; + + vec2 top_left_distance = top_left - frag_coord; + vec2 bottom_right_distance = frag_coord - bottom_right; + + vec2 distance = vec2( + max(max(top_left_distance.x, bottom_right_distance.x), 0), + max(max(top_left_distance.y, bottom_right_distance.y), 0) + ); + + float d = sqrt(distance.x * distance.x + distance.y * distance.y); + + return 1.0 - smoothstep(radius - s, radius + s, d); +} + void main() { - o_Color = v_Color; + o_Color = vec4( + v_Color.xyz, + v_Color.w * rounded(gl_FragCoord.xy, v_Pos, v_Scale, 5.0, 1.0) + ); } diff --git a/wgpu/src/shader/quad.frag.spv b/wgpu/src/shader/quad.frag.spv index 33218c83..19733ecf 100644 Binary files a/wgpu/src/shader/quad.frag.spv and b/wgpu/src/shader/quad.frag.spv differ diff --git a/wgpu/src/shader/quad.vert b/wgpu/src/shader/quad.vert index 3392d43d..87f6cc53 100644 --- a/wgpu/src/shader/quad.vert +++ b/wgpu/src/shader/quad.vert @@ -10,6 +10,8 @@ layout (set = 0, binding = 0) uniform Globals { }; layout(location = 0) out vec4 o_Color; +layout(location = 1) out vec2 o_Pos; +layout(location = 2) out vec2 o_Scale; void main() { mat4 i_Transform = mat4( @@ -20,5 +22,8 @@ void main() { ); o_Color = i_Color; + o_Pos = i_Pos; + o_Scale = i_Scale; + gl_Position = u_Transform * i_Transform * vec4(v_Pos, 0.0, 1.0); } diff --git a/wgpu/src/shader/quad.vert.spv b/wgpu/src/shader/quad.vert.spv index d517796b..0d13df6c 100644 Binary files a/wgpu/src/shader/quad.vert.spv and b/wgpu/src/shader/quad.vert.spv differ -- cgit From 10e10e5e06841574425d2633f1c2916733f7b4ff Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 8 Oct 2019 03:13:41 +0200 Subject: Make `iced_core::Button` customizable Now it supports: - Any kind of content - Custom border radius - Custom background --- Cargo.toml | 2 + core/src/background.rs | 7 +++ core/src/color.rs | 8 ++++ core/src/lib.rs | 2 + core/src/widget/button.rs | 96 +++++++++++++++--------------------------- core/src/widget/text.rs | 2 +- examples/tour.rs | 52 ++++++++++++++++++++--- native/src/element.rs | 27 ++++++------ native/src/lib.rs | 12 +++--- native/src/style.rs | 4 +- native/src/widget.rs | 2 +- native/src/widget/button.rs | 26 +++++++----- native/src/widget/checkbox.rs | 4 +- native/src/widget/column.rs | 2 +- native/src/widget/image.rs | 4 +- native/src/widget/radio.rs | 4 +- native/src/widget/row.rs | 2 +- native/src/widget/slider.rs | 2 +- native/src/widget/text.rs | 2 +- src/lib.rs | 3 +- web/src/element.rs | 8 ++++ web/src/widget/button.rs | 10 +++-- wgpu/src/lib.rs | 2 +- wgpu/src/primitive.rs | 12 +++--- wgpu/src/quad.rs | 6 +++ wgpu/src/renderer.rs | 76 ++++++++++++++++++++++++++++----- wgpu/src/renderer/button.rs | 45 +++++++++++--------- wgpu/src/renderer/checkbox.rs | 2 +- wgpu/src/renderer/image.rs | 2 +- wgpu/src/renderer/radio.rs | 2 +- wgpu/src/renderer/text.rs | 5 ++- wgpu/src/shader/quad.frag | 12 ++++-- wgpu/src/shader/quad.frag.spv | Bin 2900 -> 3196 bytes wgpu/src/shader/quad.vert | 3 ++ wgpu/src/shader/quad.vert.spv | Bin 2364 -> 2544 bytes 35 files changed, 288 insertions(+), 160 deletions(-) create mode 100644 core/src/background.rs diff --git a/Cargo.toml b/Cargo.toml index dbe24606..52fc483f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,8 @@ members = [ "core", "native", "web", + "wgpu", + "winit", ] [target.'cfg(not(target_arch = "wasm32"))'.dependencies] diff --git a/core/src/background.rs b/core/src/background.rs new file mode 100644 index 00000000..59b67a2c --- /dev/null +++ b/core/src/background.rs @@ -0,0 +1,7 @@ +use crate::Color; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Background { + Color(Color), + // TODO: Add gradient and image variants +} diff --git a/core/src/color.rs b/core/src/color.rs index 2b64c78d..79910dd8 100644 --- a/core/src/color.rs +++ b/core/src/color.rs @@ -17,6 +17,14 @@ impl Color { a: 1.0, }; + /// The white color. + pub const WHITE: Color = Color { + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + }; + pub fn into_linear(self) -> [f32; 4] { // As described in: // https://en.wikipedia.org/wiki/SRGB#The_reverse_transformation diff --git a/core/src/lib.rs b/core/src/lib.rs index 1f43b2b7..877a8f85 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -1,6 +1,7 @@ pub mod widget; mod align; +mod background; mod color; mod justify; mod length; @@ -9,6 +10,7 @@ mod rectangle; mod vector; pub use align::Align; +pub use background::Background; pub use color::Color; pub use justify::Justify; pub use length::Length; diff --git a/core/src/widget/button.rs b/core/src/widget/button.rs index b98bb443..a57f2dd8 100644 --- a/core/src/widget/button.rs +++ b/core/src/widget/button.rs @@ -5,68 +5,58 @@ //! [`Button`]: struct.Button.html //! [`State`]: struct.State.html -use crate::{Align, Length}; +use crate::{Align, Background, Length}; /// A generic widget that produces a message when clicked. -/// -/// # Example -/// -/// ``` -/// use iced_core::{button, Button}; -/// -/// pub enum Message { -/// ButtonClicked, -/// } -/// -/// let state = &mut button::State::new(); -/// -/// Button::new(state, "Click me!") -/// .on_press(Message::ButtonClicked); -/// ``` -/// -/// ![Button drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/button.png?raw=true) -pub struct Button<'a, Message> { +pub struct Button<'a, Message, Element> { /// The current state of the button pub state: &'a mut State, - /// The label of the button - pub label: String, + pub content: Element, /// The message to produce when the button is pressed pub on_press: Option, - pub class: Class, - pub width: Length, + pub padding: u16, + + pub background: Option, + + pub border_radius: u16, + pub align_self: Option, } -impl<'a, Message> std::fmt::Debug for Button<'a, Message> +impl<'a, Message, Element> std::fmt::Debug for Button<'a, Message, Element> where Message: std::fmt::Debug, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Button") .field("state", &self.state) - .field("label", &self.label) .field("on_press", &self.on_press) .finish() } } -impl<'a, Message> Button<'a, Message> { +impl<'a, Message, Element> Button<'a, Message, Element> { /// Creates a new [`Button`] with some local [`State`] and the given label. /// /// [`Button`]: struct.Button.html /// [`State`]: struct.State.html - pub fn new(state: &'a mut State, label: &str) -> Self { + pub fn new(state: &'a mut State, content: E) -> Self + where + E: Into, + { Button { state, - label: String::from(label), + content: content.into(), on_press: None, - class: Class::Primary, width: Length::Shrink, + padding: 0, + background: None, + border_radius: 0, align_self: None, } } @@ -79,6 +69,21 @@ impl<'a, Message> Button<'a, Message> { self } + pub fn padding(mut self, padding: u16) -> Self { + self.padding = padding; + self + } + + pub fn background(mut self, background: Background) -> Self { + self.background = Some(background); + self + } + + pub fn border_radius(mut self, border_radius: u16) -> Self { + self.border_radius = border_radius; + self + } + /// Sets the alignment of the [`Button`] itself. /// /// This is useful if you want to override the default alignment given by @@ -90,16 +95,6 @@ impl<'a, Message> Button<'a, Message> { self } - /// Sets the [`Class`] of the [`Button`]. - /// - /// - /// [`Button`]: struct.Button.html - /// [`Class`]: enum.Class.html - pub fn class(mut self, class: Class) -> Self { - self.class = class; - self - } - /// Sets the message that will be produced when the [`Button`] is pressed. /// /// [`Button`]: struct.Button.html @@ -133,26 +128,3 @@ impl State { self.is_pressed } } - -/// The type of a [`Button`]. -/// -/// ![Different buttons drawn by the built-in renderer in Coffee](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/button_classes.png?raw=true) -/// -/// [`Button`]: struct.Button.html -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Class { - /// The [`Button`] performs the main action. - /// - /// [`Button`]: struct.Button.html - Primary, - - /// The [`Button`] performs an alternative action. - /// - /// [`Button`]: struct.Button.html - Secondary, - - /// The [`Button`] performs a productive action. - /// - /// [`Button`]: struct.Button.html - Positive, -} diff --git a/core/src/widget/text.rs b/core/src/widget/text.rs index 427d9471..cd94dbb2 100644 --- a/core/src/widget/text.rs +++ b/core/src/widget/text.rs @@ -1,5 +1,5 @@ //! Write some text for your users to read. -use crate::{Color, Length}; +use crate::{Align, Color, Length}; /// A paragraph of text. /// diff --git a/examples/tour.rs b/examples/tour.rs index 5f24eb87..96590c0e 100644 --- a/examples/tour.rs +++ b/examples/tour.rs @@ -1,7 +1,7 @@ use iced::{ - button, slider, text::HorizontalAlignment, Align, Button, Checkbox, Color, - Column, Element, Image, Justify, Length, Radio, Row, Slider, Text, - UserInterface, + button, slider, text::HorizontalAlignment, Align, Background, Button, + Checkbox, Color, Column, Element, Image, Justify, Length, Radio, Row, + Slider, Text, UserInterface, }; pub fn main() { @@ -59,9 +59,8 @@ impl UserInterface for Tour { if steps.has_previous() { controls = controls.push( - Button::new(back_button, "Back") - .on_press(Message::BackPressed) - .class(button::Class::Secondary), + secondary_button(back_button, "Back") + .on_press(Message::BackPressed), ); } @@ -69,7 +68,8 @@ impl UserInterface for Tour { if steps.can_continue() { controls = controls.push( - Button::new(next_button, "Next").on_press(Message::NextPressed), + primary_button(next_button, "Next") + .on_press(Message::NextPressed), ); } @@ -546,6 +546,44 @@ impl<'a> Step { } } +fn button<'a, Message>( + state: &'a mut button::State, + label: &str, +) -> Button<'a, Message> { + Button::new( + state, + Text::new(label) + .color(Color::WHITE) + .horizontal_alignment(HorizontalAlignment::Center), + ) + .padding(10) + .border_radius(10) +} + +fn primary_button<'a, Message>( + state: &'a mut button::State, + label: &str, +) -> Button<'a, Message> { + button(state, label).background(Background::Color(Color { + r: 0.3, + g: 0.3, + b: 0.8, + a: 1.0, + })) +} + +fn secondary_button<'a, Message>( + state: &'a mut button::State, + label: &str, +) -> Button<'a, Message> { + button(state, label).background(Background::Color(Color { + r: 0.8, + g: 0.8, + b: 0.8, + a: 1.0, + })) +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Language { Rust, diff --git a/native/src/element.rs b/native/src/element.rs index 417e3463..cf96b7ea 100644 --- a/native/src/element.rs +++ b/native/src/element.rs @@ -41,6 +41,10 @@ where } } + pub fn node(&self, renderer: &Renderer) -> Node { + self.widget.node(renderer) + } + pub fn draw( &self, renderer: &mut Renderer, @@ -97,22 +101,22 @@ where /// /// ``` /// # mod counter { - /// # use iced_native::{button, Button}; + /// # use iced_native::{text, Text}; /// # /// # #[derive(Debug, Clone, Copy)] /// # pub enum Message {} - /// # pub struct Counter(button::State); + /// # pub struct Counter; /// # /// # impl Counter { - /// # pub fn view(&mut self) -> Button { - /// # Button::new(&mut self.0, "_") + /// # pub fn view(&mut self) -> Text { + /// # Text::new("") /// # } /// # } /// # } /// # /// # mod iced_wgpu { /// # use iced_native::{ - /// # button, row, Button, Node, Point, Rectangle, Style, Layout, Row + /// # text, row, Text, Node, Point, Rectangle, Style, Layout, Row /// # }; /// # pub struct Renderer; /// # @@ -127,16 +131,15 @@ where /// # ) {} /// # } /// # - /// # impl button::Renderer for Renderer { - /// # fn node(&self, _button: &Button<'_, Message>) -> Node { + /// # impl text::Renderer for Renderer { + /// # fn node(&self, _text: &Text) -> Node { /// # Node::new(Style::default()) /// # } /// # - /// # fn draw( + /// # fn draw( /// # &mut self, - /// # _button: &Button<'_, Message>, + /// # _text: &Text, /// # _layout: Layout<'_>, - /// # _cursor_position: Point, /// # ) {} /// # } /// # } @@ -289,7 +292,7 @@ where A: Copy, Renderer: crate::Renderer, { - fn node(&self, renderer: &mut Renderer) -> Node { + fn node(&self, renderer: &Renderer) -> Node { self.widget.node(renderer) } @@ -359,7 +362,7 @@ impl<'a, Message, Renderer> Widget where Renderer: crate::Renderer + renderer::Debugger, { - fn node(&self, renderer: &mut Renderer) -> Node { + fn node(&self, renderer: &Renderer) -> Node { self.element.widget.node(renderer) } diff --git a/native/src/lib.rs b/native/src/lib.rs index 6067f49d..18ce3a37 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -89,14 +89,14 @@ //! # impl button::Renderer for Renderer { //! # fn node( //! # &self, -//! # _button: &Button<'_, Message> +//! # _button: &Button<'_, Message, Self> //! # ) -> Node { //! # Node::new(Style::default()) //! # } //! # //! # fn draw( //! # &mut self, -//! # _button: &Button<'_, Message>, +//! # _button: &Button<'_, Message, Self>, //! # _layout: Layout<'_>, //! # _cursor_position: Point, //! # ) {} @@ -125,7 +125,7 @@ //! .push( //! // The increment button. We tell it to produce an //! // `IncrementPressed` message when pressed -//! Button::new(&mut self.increment_button, "+") +//! Button::new(&mut self.increment_button, Text::new("+")) //! .on_press(Message::IncrementPressed), //! ) //! .push( @@ -135,7 +135,7 @@ //! .push( //! // The decrement button. We tell it to produce a //! // `DecrementPressed` message when pressed -//! Button::new(&mut self.decrement_button, "-") +//! Button::new(&mut self.decrement_button, Text::new("-")) //! .on_press(Message::DecrementPressed), //! ) //! } @@ -212,7 +212,9 @@ mod user_interface; pub(crate) use iced_core::Vector; -pub use iced_core::{Align, Color, Justify, Length, Point, Rectangle}; +pub use iced_core::{ + Align, Background, Color, Justify, Length, Point, Rectangle, +}; #[doc(no_inline)] pub use stretch::{geometry::Size, number::Number}; diff --git a/native/src/style.rs b/native/src/style.rs index b1c49fd4..70a7ff74 100644 --- a/native/src/style.rs +++ b/native/src/style.rs @@ -74,12 +74,12 @@ impl Style { self } - pub(crate) fn align_items(mut self, align: Align) -> Self { + pub fn align_items(mut self, align: Align) -> Self { self.0.align_items = into_align_items(align); self } - pub(crate) fn justify_content(mut self, justify: Justify) -> Self { + pub fn justify_content(mut self, justify: Justify) -> Self { self.0.justify_content = into_justify_content(justify); self } diff --git a/native/src/widget.rs b/native/src/widget.rs index eff098a6..b7181c1b 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -67,7 +67,7 @@ where /// [`Node`]: ../struct.Node.html /// [`Widget`]: trait.Widget.html /// [`Layout`]: ../struct.Layout.html - fn node(&self, renderer: &mut Renderer) -> Node; + fn node(&self, renderer: &Renderer) -> Node; /// Draws the [`Widget`] using the associated `Renderer`. /// diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs index 5ae4e045..1f881660 100644 --- a/native/src/widget/button.rs +++ b/native/src/widget/button.rs @@ -10,14 +10,18 @@ use crate::input::{mouse, ButtonState}; use crate::{Element, Event, Hasher, Layout, Node, Point, Widget}; use std::hash::Hash; -pub use iced_core::button::*; +pub use iced_core::button::State; -impl<'a, Message, Renderer> Widget for Button<'a, Message> +pub type Button<'a, Message, Renderer> = + iced_core::Button<'a, Message, Element<'a, Message, Renderer>>; + +impl<'a, Message, Renderer> Widget + for Button<'a, Message, Renderer> where Renderer: self::Renderer, Message: Copy + std::fmt::Debug, { - fn node(&self, renderer: &mut Renderer) -> Node { + fn node(&self, renderer: &Renderer) -> Node { renderer.node(&self) } @@ -68,9 +72,9 @@ where } fn hash_layout(&self, state: &mut Hasher) { - self.label.hash(state); self.width.hash(state); self.align_self.hash(state); + self.content.hash_layout(state); } } @@ -81,31 +85,33 @@ where /// /// [`Button`]: struct.Button.html /// [renderer]: ../../renderer/index.html -pub trait Renderer: crate::Renderer { +pub trait Renderer: crate::Renderer + Sized { /// Creates a [`Node`] for the provided [`Button`]. /// /// [`Node`]: ../../struct.Node.html /// [`Button`]: struct.Button.html - fn node(&self, button: &Button<'_, Message>) -> Node; + fn node(&self, button: &Button<'_, Message, Self>) -> Node; /// Draws a [`Button`]. /// /// [`Button`]: struct.Button.html fn draw( &mut self, - button: &Button<'_, Message>, + button: &Button<'_, Message, Self>, layout: Layout<'_>, cursor_position: Point, ) -> Self::Primitive; } -impl<'a, Message, Renderer> From> +impl<'a, Message, Renderer> From> for Element<'a, Message, Renderer> where - Renderer: self::Renderer, + Renderer: 'static + self::Renderer, Message: 'static + Copy + std::fmt::Debug, { - fn from(button: Button<'a, Message>) -> Element<'a, Message, Renderer> { + fn from( + button: Button<'a, Message, Renderer>, + ) -> Element<'a, Message, Renderer> { Element::new(button) } } diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs index 1954305a..c069bfdc 100644 --- a/native/src/widget/checkbox.rs +++ b/native/src/widget/checkbox.rs @@ -10,7 +10,7 @@ impl Widget for Checkbox where Renderer: self::Renderer, { - fn node(&self, renderer: &mut Renderer) -> Node { + fn node(&self, renderer: &Renderer) -> Node { renderer.node(&self) } @@ -64,7 +64,7 @@ pub trait Renderer: crate::Renderer { /// /// [`Node`]: ../../struct.Node.html /// [`Checkbox`]: struct.Checkbox.html - fn node(&mut self, checkbox: &Checkbox) -> Node; + fn node(&self, checkbox: &Checkbox) -> Node; /// Draws a [`Checkbox`]. /// diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs index 6228d711..7e10e662 100644 --- a/native/src/widget/column.rs +++ b/native/src/widget/column.rs @@ -11,7 +11,7 @@ impl<'a, Message, Renderer> Widget where Renderer: self::Renderer, { - fn node(&self, renderer: &mut Renderer) -> Node { + fn node(&self, renderer: &Renderer) -> Node { let mut children: Vec = self .children .iter() diff --git a/native/src/widget/image.rs b/native/src/widget/image.rs index 2bce36c2..5197d5b1 100644 --- a/native/src/widget/image.rs +++ b/native/src/widget/image.rs @@ -11,7 +11,7 @@ where Renderer: self::Renderer, I: Clone, { - fn node(&self, renderer: &mut Renderer) -> Node { + fn node(&self, renderer: &Renderer) -> Node { renderer.node(&self) } @@ -45,7 +45,7 @@ pub trait Renderer: crate::Renderer { /// /// [`Node`]: ../../struct.Node.html /// [`Image`]: struct.Image.html - fn node(&mut self, image: &Image) -> Node; + fn node(&self, image: &Image) -> Node; /// Draws an [`Image`]. /// diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs index 1bc052aa..22308f81 100644 --- a/native/src/widget/radio.rs +++ b/native/src/widget/radio.rs @@ -11,7 +11,7 @@ where Renderer: self::Renderer, Message: Copy + std::fmt::Debug, { - fn node(&self, renderer: &mut Renderer) -> Node { + fn node(&self, renderer: &Renderer) -> Node { renderer.node(&self) } @@ -61,7 +61,7 @@ pub trait Renderer: crate::Renderer { /// /// [`Node`]: ../../struct.Node.html /// [`Radio`]: struct.Radio.html - fn node(&mut self, radio: &Radio) -> Node; + fn node(&self, radio: &Radio) -> Node; /// Draws a [`Radio`] button. /// diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs index 9d023210..b1d4a5b2 100644 --- a/native/src/widget/row.rs +++ b/native/src/widget/row.rs @@ -11,7 +11,7 @@ impl<'a, Message, Renderer> Widget where Renderer: self::Renderer, { - fn node(&self, renderer: &mut Renderer) -> Node { + fn node(&self, renderer: &Renderer) -> Node { let mut children: Vec = self .children .iter() diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs index 77095cb7..643efdf4 100644 --- a/native/src/widget/slider.rs +++ b/native/src/widget/slider.rs @@ -15,7 +15,7 @@ impl<'a, Message, Renderer> Widget for Slider<'a, Message> where Renderer: self::Renderer, { - fn node(&self, renderer: &mut Renderer) -> Node { + fn node(&self, renderer: &Renderer) -> Node { renderer.node(&self) } diff --git a/native/src/widget/text.rs b/native/src/widget/text.rs index a032b4fc..62f2d7b7 100644 --- a/native/src/widget/text.rs +++ b/native/src/widget/text.rs @@ -9,7 +9,7 @@ impl Widget for Text where Renderer: self::Renderer, { - fn node(&self, renderer: &mut Renderer) -> Node { + fn node(&self, renderer: &Renderer) -> Node { renderer.node(&self) } diff --git a/src/lib.rs b/src/lib.rs index c5a34dcd..2e65ed6c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,12 +1,13 @@ pub use iced_wgpu::{Primitive, Renderer}; pub use iced_winit::{ - button, slider, text, winit, Align, Button, Checkbox, Color, Image, + button, slider, text, winit, Align, Background, Checkbox, Color, Image, Justify, Length, Radio, Slider, Text, }; pub type Element<'a, Message> = iced_winit::Element<'a, Message, Renderer>; pub type Row<'a, Message> = iced_winit::Row<'a, Message, Renderer>; pub type Column<'a, Message> = iced_winit::Column<'a, Message, Renderer>; +pub type Button<'a, Message> = iced_winit::Button<'a, Message, Renderer>; pub trait UserInterface { type Message; diff --git a/web/src/element.rs b/web/src/element.rs index 8270d8db..a2b78c69 100644 --- a/web/src/element.rs +++ b/web/src/element.rs @@ -14,6 +14,14 @@ impl<'a, Message> Element<'a, Message> { } } + pub fn node<'b>( + &self, + bump: &'b bumpalo::Bump, + bus: &Bus, + ) -> dodrio::Node<'b> { + self.widget.node(bump, bus) + } + pub fn explain(self, _color: Color) -> Element<'a, Message> { self } diff --git a/web/src/widget/button.rs b/web/src/widget/button.rs index 23a4165a..257034a7 100644 --- a/web/src/widget/button.rs +++ b/web/src/widget/button.rs @@ -2,7 +2,10 @@ use crate::{Bus, Element, Widget}; use dodrio::bumpalo; -pub use iced_core::button::*; +pub use iced_core::button::State; + +pub type Button<'a, Message> = + iced_core::Button<'a, Message, Element<'a, Message>>; impl<'a, Message> Widget for Button<'a, Message> where @@ -15,9 +18,8 @@ where ) -> dodrio::Node<'b> { use dodrio::builder::*; - let label = bumpalo::format!(in bump, "{}", self.label); - - let mut node = button(bump).children(vec![text(label.into_bump_str())]); + let mut node = + button(bump).children(vec![self.content.node(bump, bus)]); if let Some(on_press) = self.on_press { let event_bus = bus.clone(); diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 33d8f5ed..8f8d50e9 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -8,5 +8,5 @@ pub(crate) use quad::Quad; pub(crate) use transformation::Transformation; pub use mouse_cursor::MouseCursor; -pub use primitive::{Background, Primitive}; +pub use primitive::Primitive; pub use renderer::{Renderer, Target}; diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs index f6730a1f..b664689b 100644 --- a/wgpu/src/primitive.rs +++ b/wgpu/src/primitive.rs @@ -1,4 +1,4 @@ -use iced_native::{Color, Rectangle}; +use iced_native::{text, Background, Color, Rectangle}; #[derive(Debug, Clone)] pub enum Primitive { @@ -9,16 +9,14 @@ pub enum Primitive { Text { content: String, bounds: Rectangle, + color: Color, size: f32, + horizontal_alignment: text::HorizontalAlignment, + vertical_alignment: text::VerticalAlignment, }, Quad { bounds: Rectangle, background: Background, + border_radius: u16, }, } - -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum Background { - Color(Color), - // TODO: Add gradient and image variants -} diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index d0126bbd..adb294f0 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -123,6 +123,11 @@ impl Pipeline { format: wgpu::VertexFormat::Float4, offset: 4 * (2 + 2), }, + wgpu::VertexAttributeDescriptor { + shader_location: 4, + format: wgpu::VertexFormat::Uint, + offset: 4 * (2 + 2 + 4), + }, ], }, ], @@ -262,6 +267,7 @@ pub struct Quad { pub position: [f32; 2], pub scale: [f32; 2], pub color: [f32; 4], + pub border_radius: u32, } impl Quad { diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index 9883943b..ae5692e3 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -1,5 +1,7 @@ -use crate::{quad, Background, Primitive, Quad, Transformation}; -use iced_native::{renderer::Debugger, Color, Layout, Point, Widget}; +use crate::{quad, Primitive, Quad, Transformation}; +use iced_native::{ + renderer::Debugger, Background, Color, Layout, Point, Widget, +}; use raw_window_handle::HasRawWindowHandle; use wgpu::{ @@ -159,20 +161,74 @@ impl Renderer { content, bounds, size, - } => self.glyph_brush.borrow_mut().queue(Section { - text: &content, - screen_position: (bounds.x, bounds.y), - bounds: (bounds.width, bounds.height), - scale: wgpu_glyph::Scale { x: *size, y: *size }, - ..Default::default() - }), - Primitive::Quad { bounds, background } => { + color, + horizontal_alignment, + vertical_alignment, + } => { + let x = match horizontal_alignment { + iced_native::text::HorizontalAlignment::Left => bounds.x, + iced_native::text::HorizontalAlignment::Center => { + bounds.x + bounds.width / 2.0 + } + iced_native::text::HorizontalAlignment::Right => { + bounds.x + bounds.width + } + }; + + let y = match vertical_alignment { + iced_native::text::VerticalAlignment::Top => bounds.y, + iced_native::text::VerticalAlignment::Center => { + bounds.y + bounds.height / 2.0 + } + iced_native::text::VerticalAlignment::Bottom => { + bounds.y + bounds.height + } + }; + + self.glyph_brush.borrow_mut().queue(Section { + text: &content, + screen_position: (x, y), + bounds: (bounds.width, bounds.height), + scale: wgpu_glyph::Scale { x: *size, y: *size }, + color: color.into_linear(), + layout: wgpu_glyph::Layout::default() + .h_align(match horizontal_alignment { + iced_native::text::HorizontalAlignment::Left => { + wgpu_glyph::HorizontalAlign::Left + } + iced_native::text::HorizontalAlignment::Center => { + wgpu_glyph::HorizontalAlign::Center + } + iced_native::text::HorizontalAlignment::Right => { + wgpu_glyph::HorizontalAlign::Right + } + }) + .v_align(match vertical_alignment { + iced_native::text::VerticalAlignment::Top => { + wgpu_glyph::VerticalAlign::Top + } + iced_native::text::VerticalAlignment::Center => { + wgpu_glyph::VerticalAlign::Center + } + iced_native::text::VerticalAlignment::Bottom => { + wgpu_glyph::VerticalAlign::Bottom + } + }), + ..Default::default() + }) + } + Primitive::Quad { + bounds, + background, + border_radius, + } => { self.quads.push(Quad { position: [bounds.x, bounds.y], scale: [bounds.width, bounds.height], color: match background { Background::Color(color) => color.into_linear(), }, + border_radius: u32::from(*border_radius), }); } } diff --git a/wgpu/src/renderer/button.rs b/wgpu/src/renderer/button.rs index f75b44f7..00fcd0eb 100644 --- a/wgpu/src/renderer/button.rs +++ b/wgpu/src/renderer/button.rs @@ -1,22 +1,26 @@ -use crate::{Background, Primitive, Renderer}; -use iced_native::{button, Button, Color, Layout, Length, Node, Point, Style}; +use crate::{Primitive, Renderer}; +use iced_native::{ + button, Align, Background, Button, Color, Layout, Length, Node, Point, + Style, +}; impl button::Renderer for Renderer { - fn node(&self, button: &Button) -> Node { + fn node(&self, button: &Button) -> Node { let style = Style::default() .width(button.width) - .min_height(Length::Units(30)) + .padding(button.padding) .min_width(Length::Units(100)) - .align_self(button.align_self); + .align_self(button.align_self) + .align_items(Align::Stretch); - Node::new(style) + Node::with_children(style, vec![button.content.node(self)]) } fn draw( &mut self, - button: &Button, + button: &Button, layout: Layout<'_>, - _cursor_position: Point, + cursor_position: Point, ) -> Self::Primitive { let bounds = layout.bounds(); @@ -24,18 +28,21 @@ impl button::Renderer for Renderer { primitives: vec![ Primitive::Quad { bounds, - background: Background::Color(Color { - r: 0.8, - b: 0.8, - g: 0.8, - a: 1.0, - }), - }, - Primitive::Text { - content: button.label.clone(), - size: 20.0, - bounds: layout.bounds(), + background: button.background.unwrap_or(Background::Color( + Color { + r: 0.8, + b: 0.8, + g: 0.8, + a: 1.0, + }, + )), + border_radius: button.border_radius, }, + button.content.draw( + self, + layout.children().next().unwrap(), + cursor_position, + ), ], } } diff --git a/wgpu/src/renderer/checkbox.rs b/wgpu/src/renderer/checkbox.rs index c94a2157..16d5734f 100644 --- a/wgpu/src/renderer/checkbox.rs +++ b/wgpu/src/renderer/checkbox.rs @@ -2,7 +2,7 @@ use crate::{Primitive, Renderer}; use iced_native::{checkbox, Checkbox, Layout, Node, Point, Style}; impl checkbox::Renderer for Renderer { - fn node(&mut self, _checkbox: &Checkbox) -> Node { + fn node(&self, _checkbox: &Checkbox) -> Node { Node::new(Style::default()) } diff --git a/wgpu/src/renderer/image.rs b/wgpu/src/renderer/image.rs index 6ff39d30..bacc430d 100644 --- a/wgpu/src/renderer/image.rs +++ b/wgpu/src/renderer/image.rs @@ -2,7 +2,7 @@ use crate::{Primitive, Renderer}; use iced_native::{image, Image, Layout, Node, Style}; impl image::Renderer<&str> for Renderer { - fn node(&mut self, _image: &Image<&str>) -> Node { + fn node(&self, _image: &Image<&str>) -> Node { Node::new(Style::default()) } diff --git a/wgpu/src/renderer/radio.rs b/wgpu/src/renderer/radio.rs index ce419ae0..fdc0a0fc 100644 --- a/wgpu/src/renderer/radio.rs +++ b/wgpu/src/renderer/radio.rs @@ -2,7 +2,7 @@ use crate::{Primitive, Renderer}; use iced_native::{radio, Layout, Node, Point, Radio, Style}; impl radio::Renderer for Renderer { - fn node(&mut self, _checkbox: &Radio) -> Node { + fn node(&self, _checkbox: &Radio) -> Node { Node::new(Style::default()) } diff --git a/wgpu/src/renderer/text.rs b/wgpu/src/renderer/text.rs index 4434cc22..c89c0b3e 100644 --- a/wgpu/src/renderer/text.rs +++ b/wgpu/src/renderer/text.rs @@ -1,5 +1,5 @@ use crate::{Primitive, Renderer}; -use iced_native::{text, Layout, Node, Style, Text}; +use iced_native::{text, Color, Layout, Node, Style, Text}; use wgpu_glyph::{GlyphCruncher, Section}; @@ -72,6 +72,9 @@ impl text::Renderer for Renderer { content: text.content.clone(), size: f32::from(text.size.unwrap_or(20)), bounds: layout.bounds(), + color: text.color.unwrap_or(Color::BLACK), + horizontal_alignment: text.horizontal_alignment, + vertical_alignment: text.vertical_alignment, } } } diff --git a/wgpu/src/shader/quad.frag b/wgpu/src/shader/quad.frag index 1fc28bc1..987744db 100644 --- a/wgpu/src/shader/quad.frag +++ b/wgpu/src/shader/quad.frag @@ -3,6 +3,7 @@ layout(location = 0) in vec4 v_Color; layout(location = 1) in vec2 v_Pos; layout(location = 2) in vec2 v_Scale; +layout(location = 3) in flat uint v_BorderRadius; layout(location = 0) out vec4 o_Color; @@ -26,8 +27,11 @@ float rounded(in vec2 frag_coord, in vec2 position, in vec2 size, float radius, } void main() { - o_Color = vec4( - v_Color.xyz, - v_Color.w * rounded(gl_FragCoord.xy, v_Pos, v_Scale, 5.0, 1.0) - ); + float radius_alpha = 1.0; + + if(v_BorderRadius > 0.0) { + radius_alpha = rounded(gl_FragCoord.xy, v_Pos, v_Scale, v_BorderRadius, 1.0); + } + + o_Color = vec4(v_Color.xyz, v_Color.w * radius_alpha); } diff --git a/wgpu/src/shader/quad.frag.spv b/wgpu/src/shader/quad.frag.spv index 19733ecf..063287b3 100644 Binary files a/wgpu/src/shader/quad.frag.spv and b/wgpu/src/shader/quad.frag.spv differ diff --git a/wgpu/src/shader/quad.vert b/wgpu/src/shader/quad.vert index 87f6cc53..b7c5cf3e 100644 --- a/wgpu/src/shader/quad.vert +++ b/wgpu/src/shader/quad.vert @@ -4,6 +4,7 @@ layout(location = 0) in vec2 v_Pos; layout(location = 1) in vec2 i_Pos; layout(location = 2) in vec2 i_Scale; layout(location = 3) in vec4 i_Color; +layout(location = 4) in uint i_BorderRadius; layout (set = 0, binding = 0) uniform Globals { mat4 u_Transform; @@ -12,6 +13,7 @@ layout (set = 0, binding = 0) uniform Globals { layout(location = 0) out vec4 o_Color; layout(location = 1) out vec2 o_Pos; layout(location = 2) out vec2 o_Scale; +layout(location = 3) out uint o_BorderRadius; void main() { mat4 i_Transform = mat4( @@ -24,6 +26,7 @@ void main() { o_Color = i_Color; o_Pos = i_Pos; o_Scale = i_Scale; + o_BorderRadius = i_BorderRadius; gl_Position = u_Transform * i_Transform * vec4(v_Pos, 0.0, 1.0); } diff --git a/wgpu/src/shader/quad.vert.spv b/wgpu/src/shader/quad.vert.spv index 0d13df6c..f62a160c 100644 Binary files a/wgpu/src/shader/quad.vert.spv and b/wgpu/src/shader/quad.vert.spv differ -- cgit From a92a0b73ed7ed935df762d06c4249894fd35b227 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 9 Oct 2019 05:36:49 +0200 Subject: Move `winit` logic from `iced` to `iced_winit` - Added new `renderer::Windowed` trait. This shoud allow users to easily try different renderers by simply changing one line. - Renamed `UserInterface` traits to `Application`, as the `run` method takes total control of the current thread. - Moved `MouseCursor` back to `iced_native`. The new `renderer::Windowed` trait returns one on `draw`. - Split `iced_native` renderer in multiple modules, for consistency. --- examples/tour.rs | 8 +-- native/Cargo.toml | 1 + native/src/lib.rs | 2 + native/src/mouse_cursor.rs | 35 +++++++++++ native/src/renderer.rs | 39 ++++--------- native/src/renderer/debugger.rs | 25 ++++++++ native/src/renderer/windowed.rs | 17 ++++++ src/lib.rs | 122 ++++++-------------------------------- web/src/bus.rs | 4 +- web/src/lib.rs | 18 +++--- wgpu/src/lib.rs | 2 - wgpu/src/mouse_cursor.rs | 35 ----------- wgpu/src/renderer.rs | 36 ++++++++++-- winit/src/lib.rs | 126 ++++++++++++++++++++++++++++++++++++++++ 14 files changed, 280 insertions(+), 190 deletions(-) create mode 100644 native/src/mouse_cursor.rs create mode 100644 native/src/renderer/debugger.rs create mode 100644 native/src/renderer/windowed.rs delete mode 100644 wgpu/src/mouse_cursor.rs diff --git a/examples/tour.rs b/examples/tour.rs index 542efc66..6ce1e9f1 100644 --- a/examples/tour.rs +++ b/examples/tour.rs @@ -1,7 +1,7 @@ use iced::{ - button, slider, text::HorizontalAlignment, Align, Background, Button, - Checkbox, Color, Column, Element, Image, Justify, Length, Radio, Row, - Slider, Text, UserInterface, + button, slider, text::HorizontalAlignment, Align, Application, Background, + Button, Checkbox, Color, Column, Element, Image, Justify, Length, Radio, + Row, Slider, Text, }; pub fn main() { @@ -30,7 +30,7 @@ impl Tour { } } -impl UserInterface for Tour { +impl Application for Tour { type Message = Message; fn update(&mut self, event: Message) { diff --git a/native/Cargo.toml b/native/Cargo.toml index 5f7e5e41..07c14535 100644 --- a/native/Cargo.toml +++ b/native/Cargo.toml @@ -14,6 +14,7 @@ features = ["winit"] iced_core = { version = "0.1.0-alpha", path = "../core" } stretch = "0.2" twox-hash = "1.5" +raw-window-handle = "0.1" # Enable to obtain conversion traits winit = { version = "0.20.0-alpha3", optional = true } diff --git a/native/src/lib.rs b/native/src/lib.rs index 18ce3a37..cada56f9 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -206,6 +206,7 @@ mod element; mod event; mod hasher; mod layout; +mod mouse_cursor; mod node; mod style; mod user_interface; @@ -223,6 +224,7 @@ pub use element::Element; pub use event::Event; pub use hasher::Hasher; pub use layout::Layout; +pub use mouse_cursor::MouseCursor; pub use node::Node; pub use renderer::Renderer; pub use style::Style; diff --git a/native/src/mouse_cursor.rs b/native/src/mouse_cursor.rs new file mode 100644 index 00000000..4ef6361a --- /dev/null +++ b/native/src/mouse_cursor.rs @@ -0,0 +1,35 @@ +/// The state of the mouse cursor. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum MouseCursor { + /// The cursor is out of the bounds of the user interface. + OutOfBounds, + + /// The cursor is over a non-interactive widget. + Idle, + + /// The cursor is over a clickable widget. + Pointer, + + /// The cursor is over a busy widget. + Working, + + /// The cursor is over a grabbable widget. + Grab, + + /// The cursor is grabbing a widget. + Grabbing, +} + +#[cfg(feature = "winit")] +impl From for winit::window::CursorIcon { + fn from(mouse_cursor: MouseCursor) -> winit::window::CursorIcon { + match mouse_cursor { + MouseCursor::OutOfBounds => winit::window::CursorIcon::Default, + MouseCursor::Idle => winit::window::CursorIcon::Default, + MouseCursor::Pointer => winit::window::CursorIcon::Hand, + MouseCursor::Working => winit::window::CursorIcon::Progress, + MouseCursor::Grab => winit::window::CursorIcon::Grab, + MouseCursor::Grabbing => winit::window::CursorIcon::Grabbing, + } + } +} diff --git a/native/src/renderer.rs b/native/src/renderer.rs index d16a0289..a56d7b76 100644 --- a/native/src/renderer.rs +++ b/native/src/renderer.rs @@ -1,8 +1,10 @@ //! Write your own renderer. //! -//! There is not a common entrypoint or trait for a __renderer__ in Iced. -//! Instead, every [`Widget`] constrains its generic `Renderer` type as -//! necessary. +//! You will need to implement the `Renderer` trait first. It simply contains +//! a `Primitive` associated type. +//! +//! There is no common trait to draw all the widgets. Instead, every [`Widget`] +//! constrains its generic `Renderer` type as necessary. //! //! This approach is flexible and composable. For instance, the //! [`Text`] widget only needs a [`text::Renderer`] while a [`Checkbox`] widget @@ -17,32 +19,13 @@ //! [`text::Renderer`]: ../widget/text/trait.Renderer.html //! [`Checkbox`]: ../widget/checkbox/struct.Checkbox.html //! [`checkbox::Renderer`]: ../widget/checkbox/trait.Renderer.html -use crate::{Color, Layout, Point, Widget}; + +mod debugger; +mod windowed; + +pub use debugger::Debugger; +pub use windowed::Windowed; pub trait Renderer { type Primitive; } - -/// A renderer able to graphically explain a [`Layout`]. -/// -/// [`Layout`]: ../struct.Layout.html -pub trait Debugger: Renderer { - /// Explains the [`Layout`] of an [`Element`] for debugging purposes. - /// - /// This will be called when [`Element::explain`] has been used. It should - /// _explain_ the given [`Layout`] graphically. - /// - /// A common approach consists in recursively rendering the bounds of the - /// [`Layout`] and its children. - /// - /// [`Layout`]: struct.Layout.html - /// [`Element`]: struct.Element.html - /// [`Element::explain`]: struct.Element.html#method.explain - fn explain( - &mut self, - widget: &dyn Widget, - layout: Layout<'_>, - cursor_position: Point, - color: Color, - ) -> Self::Primitive; -} diff --git a/native/src/renderer/debugger.rs b/native/src/renderer/debugger.rs new file mode 100644 index 00000000..cb472dd4 --- /dev/null +++ b/native/src/renderer/debugger.rs @@ -0,0 +1,25 @@ +use crate::{Color, Layout, Point, Widget}; + +/// A renderer able to graphically explain a [`Layout`]. +/// +/// [`Layout`]: ../struct.Layout.html +pub trait Debugger: super::Renderer { + /// Explains the [`Layout`] of an [`Element`] for debugging purposes. + /// + /// This will be called when [`Element::explain`] has been used. It should + /// _explain_ the given [`Layout`] graphically. + /// + /// A common approach consists in recursively rendering the bounds of the + /// [`Layout`] and its children. + /// + /// [`Layout`]: struct.Layout.html + /// [`Element`]: struct.Element.html + /// [`Element::explain`]: struct.Element.html#method.explain + fn explain( + &mut self, + widget: &dyn Widget, + layout: Layout<'_>, + cursor_position: Point, + color: Color, + ) -> Self::Primitive; +} diff --git a/native/src/renderer/windowed.rs b/native/src/renderer/windowed.rs new file mode 100644 index 00000000..f89da40b --- /dev/null +++ b/native/src/renderer/windowed.rs @@ -0,0 +1,17 @@ +use crate::MouseCursor; + +use raw_window_handle::HasRawWindowHandle; + +pub trait Windowed: super::Renderer { + type Target; + + fn new(window: &W) -> Self; + + fn target(&self, width: u16, height: u16) -> Self::Target; + + fn draw( + &mut self, + target: &mut Self::Target, + primitive: &Self::Primitive, + ) -> MouseCursor; +} diff --git a/src/lib.rs b/src/lib.rs index 2e65ed6c..46574285 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,123 +9,35 @@ pub type Row<'a, Message> = iced_winit::Row<'a, Message, Renderer>; pub type Column<'a, Message> = iced_winit::Column<'a, Message, Renderer>; pub type Button<'a, Message> = iced_winit::Button<'a, Message, Renderer>; -pub trait UserInterface { +pub trait Application { type Message; fn update(&mut self, message: Self::Message); fn view(&mut self) -> Element; - fn run(mut self) + fn run(self) where Self: 'static + Sized, { - use winit::{ - event::{Event, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - window::WindowBuilder, - }; - - let event_loop = EventLoop::new(); - - // TODO: Ask for window settings and configure this properly - let window = WindowBuilder::new() - .build(&event_loop) - .expect("Open window"); - - let size = window.inner_size().to_physical(window.hidpi_factor());; - let (width, height) = (size.width as u16, size.height as u16); - - let mut renderer = Renderer::new(&window); - let mut target = renderer.target(width, height); - - let user_interface = iced_winit::UserInterface::build( - document(&mut self, width, height), - iced_winit::Cache::default(), - &mut renderer, - ); - - let mut primitive = user_interface.draw(&mut renderer); - let mut cache = Some(user_interface.into_cache()); - let mut events = Vec::new(); - - window.request_redraw(); - - event_loop.run(move |event, _, control_flow| match event { - Event::EventsCleared => { - // TODO: We should be able to keep a user interface alive - // between events once we remove state references. - // - // This will allow us to rebuild it only when a message is - // handled. - let mut user_interface = iced_winit::UserInterface::build( - document(&mut self, width, height), - cache.take().unwrap(), - &mut renderer, - ); - - let messages = user_interface.update(events.drain(..)); - - if messages.is_empty() { - primitive = user_interface.draw(&mut renderer); - - cache = Some(user_interface.into_cache()); - } else { - // When there are messages, we are forced to rebuild twice - // for now :^) - let temp_cache = user_interface.into_cache(); - - for message in messages { - self.update(message); - } - - let user_interface = iced_winit::UserInterface::build( - document(&mut self, width, height), - temp_cache, - &mut renderer, - ); - - primitive = user_interface.draw(&mut renderer); - - cache = Some(user_interface.into_cache()); - } - - window.request_redraw(); - } - Event::WindowEvent { - event: WindowEvent::RedrawRequested, - .. - } => { - renderer.draw(&mut target, &primitive); - - // TODO: Handle animations! - // Maybe we can use `ControlFlow::WaitUntil` for this. - } - Event::WindowEvent { - event: WindowEvent::CloseRequested, - .. - } => { - *control_flow = ControlFlow::Exit; - } - _ => { - *control_flow = ControlFlow::Wait; - } - }) + iced_winit::Application::run(Instance(self)) } } -fn document( - user_interface: &mut UserInterface, - width: u16, - height: u16, -) -> Element +struct Instance(A); + +impl iced_winit::Application for Instance where - UserInterface: self::UserInterface, - UserInterface::Message: 'static, + A: Application, { - Column::new() - .width(Length::Units(width)) - .height(Length::Units(height)) - .push(user_interface.view()) - .into() + type Renderer = Renderer; + type Message = A::Message; + + fn update(&mut self, message: Self::Message) { + self.0.update(message); + } + + fn view(&mut self) -> Element { + self.0.view() + } } diff --git a/web/src/bus.rs b/web/src/bus.rs index d76466f5..b4fd67c7 100644 --- a/web/src/bus.rs +++ b/web/src/bus.rs @@ -1,4 +1,4 @@ -use crate::Application; +use crate::Instance; use std::rc::Rc; @@ -14,7 +14,7 @@ where pub fn new() -> Self { Self { publish: Rc::new(Box::new(|message, root| { - let app = root.unwrap_mut::>(); + let app = root.unwrap_mut::>(); app.update(message) })), diff --git a/web/src/lib.rs b/web/src/lib.rs index caf17df5..04848d07 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -11,7 +11,7 @@ pub use element::Element; pub use iced_core::{Align, Color, Justify, Length}; pub use widget::*; -pub trait UserInterface { +pub trait Application { type Message; fn update( @@ -25,23 +25,23 @@ pub trait UserInterface { where Self: 'static + Sized, { + let app = Instance::new(self); + let window = web_sys::window().unwrap(); let document = window.document().unwrap(); let body = document.body().unwrap(); - - let app = Application::new(self); - let vdom = dodrio::Vdom::new(&body, app); + vdom.forget(); } } -struct Application { - ui: RefCell>>, +struct Instance { + ui: RefCell>>, } -impl Application { - fn new(ui: impl UserInterface + 'static) -> Self { +impl Instance { + fn new(ui: impl Application + 'static) -> Self { Self { ui: RefCell::new(Box::new(ui)), } @@ -55,7 +55,7 @@ impl Application { } } -impl dodrio::Render for Application +impl dodrio::Render for Instance where Message: 'static, { diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 8f8d50e9..46849aab 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -1,4 +1,3 @@ -mod mouse_cursor; mod primitive; mod quad; mod renderer; @@ -7,6 +6,5 @@ mod transformation; pub(crate) use quad::Quad; pub(crate) use transformation::Transformation; -pub use mouse_cursor::MouseCursor; pub use primitive::Primitive; pub use renderer::{Renderer, Target}; diff --git a/wgpu/src/mouse_cursor.rs b/wgpu/src/mouse_cursor.rs deleted file mode 100644 index 4ef6361a..00000000 --- a/wgpu/src/mouse_cursor.rs +++ /dev/null @@ -1,35 +0,0 @@ -/// The state of the mouse cursor. -#[derive(Debug, Eq, PartialEq, Clone, Copy)] -pub enum MouseCursor { - /// The cursor is out of the bounds of the user interface. - OutOfBounds, - - /// The cursor is over a non-interactive widget. - Idle, - - /// The cursor is over a clickable widget. - Pointer, - - /// The cursor is over a busy widget. - Working, - - /// The cursor is over a grabbable widget. - Grab, - - /// The cursor is grabbing a widget. - Grabbing, -} - -#[cfg(feature = "winit")] -impl From for winit::window::CursorIcon { - fn from(mouse_cursor: MouseCursor) -> winit::window::CursorIcon { - match mouse_cursor { - MouseCursor::OutOfBounds => winit::window::CursorIcon::Default, - MouseCursor::Idle => winit::window::CursorIcon::Default, - MouseCursor::Pointer => winit::window::CursorIcon::Hand, - MouseCursor::Working => winit::window::CursorIcon::Progress, - MouseCursor::Grab => winit::window::CursorIcon::Grab, - MouseCursor::Grabbing => winit::window::CursorIcon::Grabbing, - } - } -} diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index ae5692e3..036efd27 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -1,6 +1,7 @@ use crate::{quad, Primitive, Quad, Transformation}; use iced_native::{ - renderer::Debugger, Background, Color, Layout, Point, Widget, + renderer::Debugger, renderer::Windowed, Background, Color, Layout, + MouseCursor, Point, Widget, }; use raw_window_handle::HasRawWindowHandle; @@ -41,7 +42,7 @@ pub struct Target { } impl Renderer { - pub fn new(window: &W) -> Self { + fn new(window: &W) -> Self { let adapter = Adapter::request(&RequestAdapterOptions { power_preference: PowerPreference::LowPower, backends: BackendBit::all(), @@ -79,7 +80,7 @@ impl Renderer { } } - pub fn target(&self, width: u16, height: u16) -> Target { + fn target(&self, width: u16, height: u16) -> Target { Target { width, height, @@ -97,7 +98,11 @@ impl Renderer { } } - pub fn draw(&mut self, target: &mut Target, primitive: &Primitive) { + fn draw( + &mut self, + target: &mut Target, + primitive: &Primitive, + ) -> MouseCursor { log::debug!("Drawing"); let frame = target.swap_chain.get_next_texture(); @@ -146,8 +151,9 @@ impl Renderer { .expect("Draw text"); self.queue.submit(&[encoder.finish()]); - } + MouseCursor::OutOfBounds + } fn draw_primitive(&mut self, primitive: &Primitive) { match primitive { Primitive::None => {} @@ -240,6 +246,26 @@ impl iced_native::Renderer for Renderer { type Primitive = Primitive; } +impl Windowed for Renderer { + type Target = Target; + + fn new(window: &W) -> Self { + Self::new(window) + } + + fn target(&self, width: u16, height: u16) -> Target { + self.target(width, height) + } + + fn draw( + &mut self, + target: &mut Target, + primitive: &Primitive, + ) -> MouseCursor { + self.draw(target, primitive) + } +} + impl Debugger for Renderer { fn explain( &mut self, diff --git a/winit/src/lib.rs b/winit/src/lib.rs index 54a0bd9a..93e922d5 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -1,2 +1,128 @@ pub use iced_native::*; pub use winit; + +pub use iced_native::renderer::Windowed; + +pub trait Application { + type Renderer: iced_native::renderer::Windowed + + iced_native::column::Renderer; + + type Message; + + fn update(&mut self, message: Self::Message); + + fn view(&mut self) -> Element; + + fn run(mut self) + where + Self: 'static + Sized, + { + use winit::{ + event::{Event, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::WindowBuilder, + }; + + let event_loop = EventLoop::new(); + + // TODO: Ask for window settings and configure this properly + let window = WindowBuilder::new() + .build(&event_loop) + .expect("Open window"); + + let size = window.inner_size().to_physical(window.hidpi_factor());; + let (width, height) = (size.width as u16, size.height as u16); + + let mut renderer = Self::Renderer::new(&window); + let mut target = renderer.target(width, height); + + let user_interface = UserInterface::build( + document(&mut self, width, height), + Cache::default(), + &mut renderer, + ); + + let mut primitive = user_interface.draw(&mut renderer); + let mut cache = Some(user_interface.into_cache()); + let mut events = Vec::new(); + + window.request_redraw(); + + event_loop.run(move |event, _, control_flow| match event { + Event::EventsCleared => { + // TODO: We should be able to keep a user interface alive + // between events once we remove state references. + // + // This will allow us to rebuild it only when a message is + // handled. + let mut user_interface = UserInterface::build( + document(&mut self, width, height), + cache.take().unwrap(), + &mut renderer, + ); + + let messages = user_interface.update(events.drain(..)); + + if messages.is_empty() { + primitive = user_interface.draw(&mut renderer); + + cache = Some(user_interface.into_cache()); + } else { + // When there are messages, we are forced to rebuild twice + // for now :^) + let temp_cache = user_interface.into_cache(); + + for message in messages { + self.update(message); + } + + let user_interface = UserInterface::build( + document(&mut self, width, height), + temp_cache, + &mut renderer, + ); + + primitive = user_interface.draw(&mut renderer); + + cache = Some(user_interface.into_cache()); + } + + window.request_redraw(); + } + Event::WindowEvent { + event: WindowEvent::RedrawRequested, + .. + } => { + renderer.draw(&mut target, &primitive); + + // TODO: Handle animations! + // Maybe we can use `ControlFlow::WaitUntil` for this. + } + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => { + *control_flow = ControlFlow::Exit; + } + _ => { + *control_flow = ControlFlow::Wait; + } + }) + } +} + +fn document( + application: &mut Application, + width: u16, + height: u16, +) -> Element +where + Application: self::Application, + Application::Message: 'static, +{ + Column::new() + .width(Length::Units(width)) + .height(Length::Units(height)) + .push(application.view()) + .into() +} -- cgit From fb8b50ab793bd54976bb8c5b4b890c9f97f17c54 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 10 Oct 2019 05:44:33 +0200 Subject: Move `winit` conversions to `iced_winit` --- native/Cargo.toml | 3 - native/src/input/button_state.rs | 15 --- native/src/input/keyboard/key_code.rs | 176 ------------------------------ native/src/input/mouse/button.rs | 17 --- native/src/mouse_cursor.rs | 14 --- winit/src/conversion.rs | 199 ++++++++++++++++++++++++++++++++++ winit/src/lib.rs | 2 + 7 files changed, 201 insertions(+), 225 deletions(-) create mode 100644 winit/src/conversion.rs diff --git a/native/Cargo.toml b/native/Cargo.toml index 07c14535..6870649a 100644 --- a/native/Cargo.toml +++ b/native/Cargo.toml @@ -15,6 +15,3 @@ iced_core = { version = "0.1.0-alpha", path = "../core" } stretch = "0.2" twox-hash = "1.5" raw-window-handle = "0.1" - -# Enable to obtain conversion traits -winit = { version = "0.20.0-alpha3", optional = true } diff --git a/native/src/input/button_state.rs b/native/src/input/button_state.rs index e9dc05d7..988043ba 100644 --- a/native/src/input/button_state.rs +++ b/native/src/input/button_state.rs @@ -1,9 +1,4 @@ /// The state of a button. -/// -/// If you are using [`winit`], consider enabling the `winit` feature to get -/// conversion implementations for free! -/// -/// [`winit`]: https://docs.rs/winit/0.20.0-alpha3/winit/ #[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy)] pub enum ButtonState { /// The button is pressed. @@ -12,13 +7,3 @@ pub enum ButtonState { /// The button is __not__ pressed. Released, } - -#[cfg(feature = "winit")] -impl From for ButtonState { - fn from(element_state: winit::event::ElementState) -> Self { - match element_state { - winit::event::ElementState::Pressed => ButtonState::Pressed, - winit::event::ElementState::Released => ButtonState::Released, - } - } -} diff --git a/native/src/input/keyboard/key_code.rs b/native/src/input/keyboard/key_code.rs index 207ddeac..26020a57 100644 --- a/native/src/input/keyboard/key_code.rs +++ b/native/src/input/keyboard/key_code.rs @@ -2,9 +2,6 @@ /// /// This is mostly the `KeyCode` type found in [`winit`]. /// -/// If you are using [`winit`], consider enabling the `winit` feature to get -/// conversion implementations for free! -/// /// [`winit`]: https://docs.rs/winit/0.20.0-alpha3/winit/ #[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy)] #[repr(u32)] @@ -199,176 +196,3 @@ pub enum KeyCode { Paste, Cut, } - -#[cfg(feature = "winit")] -impl From for KeyCode { - fn from(virtual_keycode: winit::event::VirtualKeyCode) -> Self { - match virtual_keycode { - winit::event::VirtualKeyCode::Key1 => KeyCode::Key1, - winit::event::VirtualKeyCode::Key2 => KeyCode::Key2, - winit::event::VirtualKeyCode::Key3 => KeyCode::Key3, - winit::event::VirtualKeyCode::Key4 => KeyCode::Key4, - winit::event::VirtualKeyCode::Key5 => KeyCode::Key5, - winit::event::VirtualKeyCode::Key6 => KeyCode::Key6, - winit::event::VirtualKeyCode::Key7 => KeyCode::Key7, - winit::event::VirtualKeyCode::Key8 => KeyCode::Key8, - winit::event::VirtualKeyCode::Key9 => KeyCode::Key9, - winit::event::VirtualKeyCode::Key0 => KeyCode::Key0, - winit::event::VirtualKeyCode::A => KeyCode::A, - winit::event::VirtualKeyCode::B => KeyCode::B, - winit::event::VirtualKeyCode::C => KeyCode::C, - winit::event::VirtualKeyCode::D => KeyCode::D, - winit::event::VirtualKeyCode::E => KeyCode::E, - winit::event::VirtualKeyCode::F => KeyCode::F, - winit::event::VirtualKeyCode::G => KeyCode::G, - winit::event::VirtualKeyCode::H => KeyCode::H, - winit::event::VirtualKeyCode::I => KeyCode::I, - winit::event::VirtualKeyCode::J => KeyCode::J, - winit::event::VirtualKeyCode::K => KeyCode::K, - winit::event::VirtualKeyCode::L => KeyCode::L, - winit::event::VirtualKeyCode::M => KeyCode::M, - winit::event::VirtualKeyCode::N => KeyCode::N, - winit::event::VirtualKeyCode::O => KeyCode::O, - winit::event::VirtualKeyCode::P => KeyCode::P, - winit::event::VirtualKeyCode::Q => KeyCode::Q, - winit::event::VirtualKeyCode::R => KeyCode::R, - winit::event::VirtualKeyCode::S => KeyCode::S, - winit::event::VirtualKeyCode::T => KeyCode::T, - winit::event::VirtualKeyCode::U => KeyCode::U, - winit::event::VirtualKeyCode::V => KeyCode::V, - winit::event::VirtualKeyCode::W => KeyCode::W, - winit::event::VirtualKeyCode::X => KeyCode::X, - winit::event::VirtualKeyCode::Y => KeyCode::Y, - winit::event::VirtualKeyCode::Z => KeyCode::Z, - winit::event::VirtualKeyCode::Escape => KeyCode::Escape, - winit::event::VirtualKeyCode::F1 => KeyCode::F1, - winit::event::VirtualKeyCode::F2 => KeyCode::F2, - winit::event::VirtualKeyCode::F3 => KeyCode::F3, - winit::event::VirtualKeyCode::F4 => KeyCode::F4, - winit::event::VirtualKeyCode::F5 => KeyCode::F5, - winit::event::VirtualKeyCode::F6 => KeyCode::F6, - winit::event::VirtualKeyCode::F7 => KeyCode::F7, - winit::event::VirtualKeyCode::F8 => KeyCode::F8, - winit::event::VirtualKeyCode::F9 => KeyCode::F9, - winit::event::VirtualKeyCode::F10 => KeyCode::F10, - winit::event::VirtualKeyCode::F11 => KeyCode::F11, - winit::event::VirtualKeyCode::F12 => KeyCode::F12, - winit::event::VirtualKeyCode::F13 => KeyCode::F13, - winit::event::VirtualKeyCode::F14 => KeyCode::F14, - winit::event::VirtualKeyCode::F15 => KeyCode::F15, - winit::event::VirtualKeyCode::F16 => KeyCode::F16, - winit::event::VirtualKeyCode::F17 => KeyCode::F17, - winit::event::VirtualKeyCode::F18 => KeyCode::F18, - winit::event::VirtualKeyCode::F19 => KeyCode::F19, - winit::event::VirtualKeyCode::F20 => KeyCode::F20, - winit::event::VirtualKeyCode::F21 => KeyCode::F21, - winit::event::VirtualKeyCode::F22 => KeyCode::F22, - winit::event::VirtualKeyCode::F23 => KeyCode::F23, - winit::event::VirtualKeyCode::F24 => KeyCode::F24, - winit::event::VirtualKeyCode::Snapshot => KeyCode::Snapshot, - winit::event::VirtualKeyCode::Scroll => KeyCode::Scroll, - winit::event::VirtualKeyCode::Pause => KeyCode::Pause, - winit::event::VirtualKeyCode::Insert => KeyCode::Insert, - winit::event::VirtualKeyCode::Home => KeyCode::Home, - winit::event::VirtualKeyCode::Delete => KeyCode::Delete, - winit::event::VirtualKeyCode::End => KeyCode::End, - winit::event::VirtualKeyCode::PageDown => KeyCode::PageDown, - winit::event::VirtualKeyCode::PageUp => KeyCode::PageUp, - winit::event::VirtualKeyCode::Left => KeyCode::Left, - winit::event::VirtualKeyCode::Up => KeyCode::Up, - winit::event::VirtualKeyCode::Right => KeyCode::Right, - winit::event::VirtualKeyCode::Down => KeyCode::Down, - winit::event::VirtualKeyCode::Back => KeyCode::Backspace, - winit::event::VirtualKeyCode::Return => KeyCode::Enter, - winit::event::VirtualKeyCode::Space => KeyCode::Space, - winit::event::VirtualKeyCode::Compose => KeyCode::Compose, - winit::event::VirtualKeyCode::Caret => KeyCode::Caret, - winit::event::VirtualKeyCode::Numlock => KeyCode::Numlock, - winit::event::VirtualKeyCode::Numpad0 => KeyCode::Numpad0, - winit::event::VirtualKeyCode::Numpad1 => KeyCode::Numpad1, - winit::event::VirtualKeyCode::Numpad2 => KeyCode::Numpad2, - winit::event::VirtualKeyCode::Numpad3 => KeyCode::Numpad3, - winit::event::VirtualKeyCode::Numpad4 => KeyCode::Numpad4, - winit::event::VirtualKeyCode::Numpad5 => KeyCode::Numpad5, - winit::event::VirtualKeyCode::Numpad6 => KeyCode::Numpad6, - winit::event::VirtualKeyCode::Numpad7 => KeyCode::Numpad7, - winit::event::VirtualKeyCode::Numpad8 => KeyCode::Numpad8, - winit::event::VirtualKeyCode::Numpad9 => KeyCode::Numpad9, - winit::event::VirtualKeyCode::AbntC1 => KeyCode::AbntC1, - winit::event::VirtualKeyCode::AbntC2 => KeyCode::AbntC2, - winit::event::VirtualKeyCode::Add => KeyCode::Add, - winit::event::VirtualKeyCode::Apostrophe => KeyCode::Apostrophe, - winit::event::VirtualKeyCode::Apps => KeyCode::Apps, - winit::event::VirtualKeyCode::At => KeyCode::At, - winit::event::VirtualKeyCode::Ax => KeyCode::Ax, - winit::event::VirtualKeyCode::Backslash => KeyCode::Backslash, - winit::event::VirtualKeyCode::Calculator => KeyCode::Calculator, - winit::event::VirtualKeyCode::Capital => KeyCode::Capital, - winit::event::VirtualKeyCode::Colon => KeyCode::Colon, - winit::event::VirtualKeyCode::Comma => KeyCode::Comma, - winit::event::VirtualKeyCode::Convert => KeyCode::Convert, - winit::event::VirtualKeyCode::Decimal => KeyCode::Decimal, - winit::event::VirtualKeyCode::Divide => KeyCode::Divide, - winit::event::VirtualKeyCode::Equals => KeyCode::Equals, - winit::event::VirtualKeyCode::Grave => KeyCode::Grave, - winit::event::VirtualKeyCode::Kana => KeyCode::Kana, - winit::event::VirtualKeyCode::Kanji => KeyCode::Kanji, - winit::event::VirtualKeyCode::LAlt => KeyCode::LAlt, - winit::event::VirtualKeyCode::LBracket => KeyCode::LBracket, - winit::event::VirtualKeyCode::LControl => KeyCode::LControl, - winit::event::VirtualKeyCode::LShift => KeyCode::LShift, - winit::event::VirtualKeyCode::LWin => KeyCode::LWin, - winit::event::VirtualKeyCode::Mail => KeyCode::Mail, - winit::event::VirtualKeyCode::MediaSelect => KeyCode::MediaSelect, - winit::event::VirtualKeyCode::MediaStop => KeyCode::MediaStop, - winit::event::VirtualKeyCode::Minus => KeyCode::Minus, - winit::event::VirtualKeyCode::Multiply => KeyCode::Multiply, - winit::event::VirtualKeyCode::Mute => KeyCode::Mute, - winit::event::VirtualKeyCode::MyComputer => KeyCode::MyComputer, - winit::event::VirtualKeyCode::NavigateForward => { - KeyCode::NavigateForward - } - winit::event::VirtualKeyCode::NavigateBackward => { - KeyCode::NavigateBackward - } - winit::event::VirtualKeyCode::NextTrack => KeyCode::NextTrack, - winit::event::VirtualKeyCode::NoConvert => KeyCode::NoConvert, - winit::event::VirtualKeyCode::NumpadComma => KeyCode::NumpadComma, - winit::event::VirtualKeyCode::NumpadEnter => KeyCode::NumpadEnter, - winit::event::VirtualKeyCode::NumpadEquals => KeyCode::NumpadEquals, - winit::event::VirtualKeyCode::OEM102 => KeyCode::OEM102, - winit::event::VirtualKeyCode::Period => KeyCode::Period, - winit::event::VirtualKeyCode::PlayPause => KeyCode::PlayPause, - winit::event::VirtualKeyCode::Power => KeyCode::Power, - winit::event::VirtualKeyCode::PrevTrack => KeyCode::PrevTrack, - winit::event::VirtualKeyCode::RAlt => KeyCode::RAlt, - winit::event::VirtualKeyCode::RBracket => KeyCode::RBracket, - winit::event::VirtualKeyCode::RControl => KeyCode::RControl, - winit::event::VirtualKeyCode::RShift => KeyCode::RShift, - winit::event::VirtualKeyCode::RWin => KeyCode::RWin, - winit::event::VirtualKeyCode::Semicolon => KeyCode::Semicolon, - winit::event::VirtualKeyCode::Slash => KeyCode::Slash, - winit::event::VirtualKeyCode::Sleep => KeyCode::Sleep, - winit::event::VirtualKeyCode::Stop => KeyCode::Stop, - winit::event::VirtualKeyCode::Subtract => KeyCode::Subtract, - winit::event::VirtualKeyCode::Sysrq => KeyCode::Sysrq, - winit::event::VirtualKeyCode::Tab => KeyCode::Tab, - winit::event::VirtualKeyCode::Underline => KeyCode::Underline, - winit::event::VirtualKeyCode::Unlabeled => KeyCode::Unlabeled, - winit::event::VirtualKeyCode::VolumeDown => KeyCode::VolumeDown, - winit::event::VirtualKeyCode::VolumeUp => KeyCode::VolumeUp, - winit::event::VirtualKeyCode::Wake => KeyCode::Wake, - winit::event::VirtualKeyCode::WebBack => KeyCode::WebBack, - winit::event::VirtualKeyCode::WebFavorites => KeyCode::WebFavorites, - winit::event::VirtualKeyCode::WebForward => KeyCode::WebForward, - winit::event::VirtualKeyCode::WebHome => KeyCode::WebHome, - winit::event::VirtualKeyCode::WebRefresh => KeyCode::WebRefresh, - winit::event::VirtualKeyCode::WebSearch => KeyCode::WebSearch, - winit::event::VirtualKeyCode::WebStop => KeyCode::WebStop, - winit::event::VirtualKeyCode::Yen => KeyCode::Yen, - winit::event::VirtualKeyCode::Copy => KeyCode::Copy, - winit::event::VirtualKeyCode::Paste => KeyCode::Paste, - winit::event::VirtualKeyCode::Cut => KeyCode::Cut, - } - } -} diff --git a/native/src/input/mouse/button.rs b/native/src/input/mouse/button.rs index 6320d701..aeb8a55d 100644 --- a/native/src/input/mouse/button.rs +++ b/native/src/input/mouse/button.rs @@ -1,9 +1,4 @@ /// The button of a mouse. -/// -/// If you are using [`winit`], consider enabling the `winit` feature to get -/// conversion implementations for free! -/// -/// [`winit`]: https://docs.rs/winit/0.20.0-alpha3/winit/ #[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)] pub enum Button { /// The left mouse button. @@ -18,15 +13,3 @@ pub enum Button { /// Some other button. Other(u8), } - -#[cfg(feature = "winit")] -impl From for super::Button { - fn from(mouse_button: winit::event::MouseButton) -> Self { - match mouse_button { - winit::event::MouseButton::Left => Button::Left, - winit::event::MouseButton::Right => Button::Right, - winit::event::MouseButton::Middle => Button::Middle, - winit::event::MouseButton::Other(other) => Button::Other(other), - } - } -} diff --git a/native/src/mouse_cursor.rs b/native/src/mouse_cursor.rs index 4ef6361a..f6a68c54 100644 --- a/native/src/mouse_cursor.rs +++ b/native/src/mouse_cursor.rs @@ -19,17 +19,3 @@ pub enum MouseCursor { /// The cursor is grabbing a widget. Grabbing, } - -#[cfg(feature = "winit")] -impl From for winit::window::CursorIcon { - fn from(mouse_cursor: MouseCursor) -> winit::window::CursorIcon { - match mouse_cursor { - MouseCursor::OutOfBounds => winit::window::CursorIcon::Default, - MouseCursor::Idle => winit::window::CursorIcon::Default, - MouseCursor::Pointer => winit::window::CursorIcon::Hand, - MouseCursor::Working => winit::window::CursorIcon::Progress, - MouseCursor::Grab => winit::window::CursorIcon::Grab, - MouseCursor::Grabbing => winit::window::CursorIcon::Grabbing, - } - } -} diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs new file mode 100644 index 00000000..bb0d252e --- /dev/null +++ b/winit/src/conversion.rs @@ -0,0 +1,199 @@ +use crate::input::{keyboard::KeyCode, mouse, ButtonState}; +use crate::MouseCursor; + +pub fn mouse_cursor(mouse_cursor: MouseCursor) -> winit::window::CursorIcon { + match mouse_cursor { + MouseCursor::OutOfBounds => winit::window::CursorIcon::Default, + MouseCursor::Idle => winit::window::CursorIcon::Default, + MouseCursor::Pointer => winit::window::CursorIcon::Hand, + MouseCursor::Working => winit::window::CursorIcon::Progress, + MouseCursor::Grab => winit::window::CursorIcon::Grab, + MouseCursor::Grabbing => winit::window::CursorIcon::Grabbing, + } +} + +pub fn mouse_button(mouse_button: winit::event::MouseButton) -> mouse::Button { + match mouse_button { + winit::event::MouseButton::Left => mouse::Button::Left, + winit::event::MouseButton::Right => mouse::Button::Right, + winit::event::MouseButton::Middle => mouse::Button::Middle, + winit::event::MouseButton::Other(other) => mouse::Button::Other(other), + } +} + +pub fn button_state(element_state: winit::event::ElementState) -> ButtonState { + match element_state { + winit::event::ElementState::Pressed => ButtonState::Pressed, + winit::event::ElementState::Released => ButtonState::Released, + } +} + +pub fn key_code(virtual_keycode: winit::event::VirtualKeyCode) -> KeyCode { + match virtual_keycode { + winit::event::VirtualKeyCode::Key1 => KeyCode::Key1, + winit::event::VirtualKeyCode::Key2 => KeyCode::Key2, + winit::event::VirtualKeyCode::Key3 => KeyCode::Key3, + winit::event::VirtualKeyCode::Key4 => KeyCode::Key4, + winit::event::VirtualKeyCode::Key5 => KeyCode::Key5, + winit::event::VirtualKeyCode::Key6 => KeyCode::Key6, + winit::event::VirtualKeyCode::Key7 => KeyCode::Key7, + winit::event::VirtualKeyCode::Key8 => KeyCode::Key8, + winit::event::VirtualKeyCode::Key9 => KeyCode::Key9, + winit::event::VirtualKeyCode::Key0 => KeyCode::Key0, + winit::event::VirtualKeyCode::A => KeyCode::A, + winit::event::VirtualKeyCode::B => KeyCode::B, + winit::event::VirtualKeyCode::C => KeyCode::C, + winit::event::VirtualKeyCode::D => KeyCode::D, + winit::event::VirtualKeyCode::E => KeyCode::E, + winit::event::VirtualKeyCode::F => KeyCode::F, + winit::event::VirtualKeyCode::G => KeyCode::G, + winit::event::VirtualKeyCode::H => KeyCode::H, + winit::event::VirtualKeyCode::I => KeyCode::I, + winit::event::VirtualKeyCode::J => KeyCode::J, + winit::event::VirtualKeyCode::K => KeyCode::K, + winit::event::VirtualKeyCode::L => KeyCode::L, + winit::event::VirtualKeyCode::M => KeyCode::M, + winit::event::VirtualKeyCode::N => KeyCode::N, + winit::event::VirtualKeyCode::O => KeyCode::O, + winit::event::VirtualKeyCode::P => KeyCode::P, + winit::event::VirtualKeyCode::Q => KeyCode::Q, + winit::event::VirtualKeyCode::R => KeyCode::R, + winit::event::VirtualKeyCode::S => KeyCode::S, + winit::event::VirtualKeyCode::T => KeyCode::T, + winit::event::VirtualKeyCode::U => KeyCode::U, + winit::event::VirtualKeyCode::V => KeyCode::V, + winit::event::VirtualKeyCode::W => KeyCode::W, + winit::event::VirtualKeyCode::X => KeyCode::X, + winit::event::VirtualKeyCode::Y => KeyCode::Y, + winit::event::VirtualKeyCode::Z => KeyCode::Z, + winit::event::VirtualKeyCode::Escape => KeyCode::Escape, + winit::event::VirtualKeyCode::F1 => KeyCode::F1, + winit::event::VirtualKeyCode::F2 => KeyCode::F2, + winit::event::VirtualKeyCode::F3 => KeyCode::F3, + winit::event::VirtualKeyCode::F4 => KeyCode::F4, + winit::event::VirtualKeyCode::F5 => KeyCode::F5, + winit::event::VirtualKeyCode::F6 => KeyCode::F6, + winit::event::VirtualKeyCode::F7 => KeyCode::F7, + winit::event::VirtualKeyCode::F8 => KeyCode::F8, + winit::event::VirtualKeyCode::F9 => KeyCode::F9, + winit::event::VirtualKeyCode::F10 => KeyCode::F10, + winit::event::VirtualKeyCode::F11 => KeyCode::F11, + winit::event::VirtualKeyCode::F12 => KeyCode::F12, + winit::event::VirtualKeyCode::F13 => KeyCode::F13, + winit::event::VirtualKeyCode::F14 => KeyCode::F14, + winit::event::VirtualKeyCode::F15 => KeyCode::F15, + winit::event::VirtualKeyCode::F16 => KeyCode::F16, + winit::event::VirtualKeyCode::F17 => KeyCode::F17, + winit::event::VirtualKeyCode::F18 => KeyCode::F18, + winit::event::VirtualKeyCode::F19 => KeyCode::F19, + winit::event::VirtualKeyCode::F20 => KeyCode::F20, + winit::event::VirtualKeyCode::F21 => KeyCode::F21, + winit::event::VirtualKeyCode::F22 => KeyCode::F22, + winit::event::VirtualKeyCode::F23 => KeyCode::F23, + winit::event::VirtualKeyCode::F24 => KeyCode::F24, + winit::event::VirtualKeyCode::Snapshot => KeyCode::Snapshot, + winit::event::VirtualKeyCode::Scroll => KeyCode::Scroll, + winit::event::VirtualKeyCode::Pause => KeyCode::Pause, + winit::event::VirtualKeyCode::Insert => KeyCode::Insert, + winit::event::VirtualKeyCode::Home => KeyCode::Home, + winit::event::VirtualKeyCode::Delete => KeyCode::Delete, + winit::event::VirtualKeyCode::End => KeyCode::End, + winit::event::VirtualKeyCode::PageDown => KeyCode::PageDown, + winit::event::VirtualKeyCode::PageUp => KeyCode::PageUp, + winit::event::VirtualKeyCode::Left => KeyCode::Left, + winit::event::VirtualKeyCode::Up => KeyCode::Up, + winit::event::VirtualKeyCode::Right => KeyCode::Right, + winit::event::VirtualKeyCode::Down => KeyCode::Down, + winit::event::VirtualKeyCode::Back => KeyCode::Backspace, + winit::event::VirtualKeyCode::Return => KeyCode::Enter, + winit::event::VirtualKeyCode::Space => KeyCode::Space, + winit::event::VirtualKeyCode::Compose => KeyCode::Compose, + winit::event::VirtualKeyCode::Caret => KeyCode::Caret, + winit::event::VirtualKeyCode::Numlock => KeyCode::Numlock, + winit::event::VirtualKeyCode::Numpad0 => KeyCode::Numpad0, + winit::event::VirtualKeyCode::Numpad1 => KeyCode::Numpad1, + winit::event::VirtualKeyCode::Numpad2 => KeyCode::Numpad2, + winit::event::VirtualKeyCode::Numpad3 => KeyCode::Numpad3, + winit::event::VirtualKeyCode::Numpad4 => KeyCode::Numpad4, + winit::event::VirtualKeyCode::Numpad5 => KeyCode::Numpad5, + winit::event::VirtualKeyCode::Numpad6 => KeyCode::Numpad6, + winit::event::VirtualKeyCode::Numpad7 => KeyCode::Numpad7, + winit::event::VirtualKeyCode::Numpad8 => KeyCode::Numpad8, + winit::event::VirtualKeyCode::Numpad9 => KeyCode::Numpad9, + winit::event::VirtualKeyCode::AbntC1 => KeyCode::AbntC1, + winit::event::VirtualKeyCode::AbntC2 => KeyCode::AbntC2, + winit::event::VirtualKeyCode::Add => KeyCode::Add, + winit::event::VirtualKeyCode::Apostrophe => KeyCode::Apostrophe, + winit::event::VirtualKeyCode::Apps => KeyCode::Apps, + winit::event::VirtualKeyCode::At => KeyCode::At, + winit::event::VirtualKeyCode::Ax => KeyCode::Ax, + winit::event::VirtualKeyCode::Backslash => KeyCode::Backslash, + winit::event::VirtualKeyCode::Calculator => KeyCode::Calculator, + winit::event::VirtualKeyCode::Capital => KeyCode::Capital, + winit::event::VirtualKeyCode::Colon => KeyCode::Colon, + winit::event::VirtualKeyCode::Comma => KeyCode::Comma, + winit::event::VirtualKeyCode::Convert => KeyCode::Convert, + winit::event::VirtualKeyCode::Decimal => KeyCode::Decimal, + winit::event::VirtualKeyCode::Divide => KeyCode::Divide, + winit::event::VirtualKeyCode::Equals => KeyCode::Equals, + winit::event::VirtualKeyCode::Grave => KeyCode::Grave, + winit::event::VirtualKeyCode::Kana => KeyCode::Kana, + winit::event::VirtualKeyCode::Kanji => KeyCode::Kanji, + winit::event::VirtualKeyCode::LAlt => KeyCode::LAlt, + winit::event::VirtualKeyCode::LBracket => KeyCode::LBracket, + winit::event::VirtualKeyCode::LControl => KeyCode::LControl, + winit::event::VirtualKeyCode::LShift => KeyCode::LShift, + winit::event::VirtualKeyCode::LWin => KeyCode::LWin, + winit::event::VirtualKeyCode::Mail => KeyCode::Mail, + winit::event::VirtualKeyCode::MediaSelect => KeyCode::MediaSelect, + winit::event::VirtualKeyCode::MediaStop => KeyCode::MediaStop, + winit::event::VirtualKeyCode::Minus => KeyCode::Minus, + winit::event::VirtualKeyCode::Multiply => KeyCode::Multiply, + winit::event::VirtualKeyCode::Mute => KeyCode::Mute, + winit::event::VirtualKeyCode::MyComputer => KeyCode::MyComputer, + winit::event::VirtualKeyCode::NavigateForward => { + KeyCode::NavigateForward + } + winit::event::VirtualKeyCode::NavigateBackward => { + KeyCode::NavigateBackward + } + winit::event::VirtualKeyCode::NextTrack => KeyCode::NextTrack, + winit::event::VirtualKeyCode::NoConvert => KeyCode::NoConvert, + winit::event::VirtualKeyCode::NumpadComma => KeyCode::NumpadComma, + winit::event::VirtualKeyCode::NumpadEnter => KeyCode::NumpadEnter, + winit::event::VirtualKeyCode::NumpadEquals => KeyCode::NumpadEquals, + winit::event::VirtualKeyCode::OEM102 => KeyCode::OEM102, + winit::event::VirtualKeyCode::Period => KeyCode::Period, + winit::event::VirtualKeyCode::PlayPause => KeyCode::PlayPause, + winit::event::VirtualKeyCode::Power => KeyCode::Power, + winit::event::VirtualKeyCode::PrevTrack => KeyCode::PrevTrack, + winit::event::VirtualKeyCode::RAlt => KeyCode::RAlt, + winit::event::VirtualKeyCode::RBracket => KeyCode::RBracket, + winit::event::VirtualKeyCode::RControl => KeyCode::RControl, + winit::event::VirtualKeyCode::RShift => KeyCode::RShift, + winit::event::VirtualKeyCode::RWin => KeyCode::RWin, + winit::event::VirtualKeyCode::Semicolon => KeyCode::Semicolon, + winit::event::VirtualKeyCode::Slash => KeyCode::Slash, + winit::event::VirtualKeyCode::Sleep => KeyCode::Sleep, + winit::event::VirtualKeyCode::Stop => KeyCode::Stop, + winit::event::VirtualKeyCode::Subtract => KeyCode::Subtract, + winit::event::VirtualKeyCode::Sysrq => KeyCode::Sysrq, + winit::event::VirtualKeyCode::Tab => KeyCode::Tab, + winit::event::VirtualKeyCode::Underline => KeyCode::Underline, + winit::event::VirtualKeyCode::Unlabeled => KeyCode::Unlabeled, + winit::event::VirtualKeyCode::VolumeDown => KeyCode::VolumeDown, + winit::event::VirtualKeyCode::VolumeUp => KeyCode::VolumeUp, + winit::event::VirtualKeyCode::Wake => KeyCode::Wake, + winit::event::VirtualKeyCode::WebBack => KeyCode::WebBack, + winit::event::VirtualKeyCode::WebFavorites => KeyCode::WebFavorites, + winit::event::VirtualKeyCode::WebForward => KeyCode::WebForward, + winit::event::VirtualKeyCode::WebHome => KeyCode::WebHome, + winit::event::VirtualKeyCode::WebRefresh => KeyCode::WebRefresh, + winit::event::VirtualKeyCode::WebSearch => KeyCode::WebSearch, + winit::event::VirtualKeyCode::WebStop => KeyCode::WebStop, + winit::event::VirtualKeyCode::Yen => KeyCode::Yen, + winit::event::VirtualKeyCode::Copy => KeyCode::Copy, + winit::event::VirtualKeyCode::Paste => KeyCode::Paste, + winit::event::VirtualKeyCode::Cut => KeyCode::Cut, + } +} diff --git a/winit/src/lib.rs b/winit/src/lib.rs index 93e922d5..73745794 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -1,6 +1,8 @@ pub use iced_native::*; pub use winit; +pub mod conversion; + pub use iced_native::renderer::Windowed; pub trait Application { -- cgit From ab34ef45e0d9a17d6aee3eadf52b753fce73e3b6 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 10 Oct 2019 05:50:01 +0200 Subject: Move `winit::Application` to its own module --- winit/src/application.rs | 125 ++++++++++++++++++++++++++++++++++++++++++++++ winit/src/lib.rs | 126 +---------------------------------------------- 2 files changed, 127 insertions(+), 124 deletions(-) create mode 100644 winit/src/application.rs diff --git a/winit/src/application.rs b/winit/src/application.rs new file mode 100644 index 00000000..9e23cd40 --- /dev/null +++ b/winit/src/application.rs @@ -0,0 +1,125 @@ +use crate::renderer::Windowed; +use crate::{Cache, Column, Element, Length, UserInterface}; + +pub trait Application { + type Renderer: Windowed + iced_native::column::Renderer; + + type Message; + + fn update(&mut self, message: Self::Message); + + fn view(&mut self) -> Element; + + fn run(mut self) + where + Self: 'static + Sized, + { + use winit::{ + event::{Event, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::WindowBuilder, + }; + + let event_loop = EventLoop::new(); + + // TODO: Ask for window settings and configure this properly + let window = WindowBuilder::new() + .build(&event_loop) + .expect("Open window"); + + let size = window.inner_size().to_physical(window.hidpi_factor());; + let (width, height) = (size.width as u16, size.height as u16); + + let mut renderer = Self::Renderer::new(&window); + let mut target = renderer.target(width, height); + + let user_interface = UserInterface::build( + document(&mut self, width, height), + Cache::default(), + &mut renderer, + ); + + let mut primitive = user_interface.draw(&mut renderer); + let mut cache = Some(user_interface.into_cache()); + let mut events = Vec::new(); + + window.request_redraw(); + + event_loop.run(move |event, _, control_flow| match event { + Event::EventsCleared => { + // TODO: We should be able to keep a user interface alive + // between events once we remove state references. + // + // This will allow us to rebuild it only when a message is + // handled. + let mut user_interface = UserInterface::build( + document(&mut self, width, height), + cache.take().unwrap(), + &mut renderer, + ); + + let messages = user_interface.update(events.drain(..)); + + if messages.is_empty() { + primitive = user_interface.draw(&mut renderer); + + cache = Some(user_interface.into_cache()); + } else { + // When there are messages, we are forced to rebuild twice + // for now :^) + let temp_cache = user_interface.into_cache(); + + for message in messages { + self.update(message); + } + + let user_interface = UserInterface::build( + document(&mut self, width, height), + temp_cache, + &mut renderer, + ); + + primitive = user_interface.draw(&mut renderer); + + cache = Some(user_interface.into_cache()); + } + + window.request_redraw(); + } + Event::WindowEvent { + event: WindowEvent::RedrawRequested, + .. + } => { + renderer.draw(&mut target, &primitive); + + // TODO: Handle animations! + // Maybe we can use `ControlFlow::WaitUntil` for this. + } + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => { + *control_flow = ControlFlow::Exit; + } + _ => { + *control_flow = ControlFlow::Wait; + } + }) + } +} + +fn document( + application: &mut Application, + width: u16, + height: u16, +) -> Element +where + Application: self::Application, + Application::Message: 'static, +{ + Column::new() + .width(Length::Units(width)) + .height(Length::Units(height)) + .push(application.view()) + .into() +} diff --git a/winit/src/lib.rs b/winit/src/lib.rs index 73745794..b08fcb6c 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -3,128 +3,6 @@ pub use winit; pub mod conversion; -pub use iced_native::renderer::Windowed; +mod application; -pub trait Application { - type Renderer: iced_native::renderer::Windowed - + iced_native::column::Renderer; - - type Message; - - fn update(&mut self, message: Self::Message); - - fn view(&mut self) -> Element; - - fn run(mut self) - where - Self: 'static + Sized, - { - use winit::{ - event::{Event, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - window::WindowBuilder, - }; - - let event_loop = EventLoop::new(); - - // TODO: Ask for window settings and configure this properly - let window = WindowBuilder::new() - .build(&event_loop) - .expect("Open window"); - - let size = window.inner_size().to_physical(window.hidpi_factor());; - let (width, height) = (size.width as u16, size.height as u16); - - let mut renderer = Self::Renderer::new(&window); - let mut target = renderer.target(width, height); - - let user_interface = UserInterface::build( - document(&mut self, width, height), - Cache::default(), - &mut renderer, - ); - - let mut primitive = user_interface.draw(&mut renderer); - let mut cache = Some(user_interface.into_cache()); - let mut events = Vec::new(); - - window.request_redraw(); - - event_loop.run(move |event, _, control_flow| match event { - Event::EventsCleared => { - // TODO: We should be able to keep a user interface alive - // between events once we remove state references. - // - // This will allow us to rebuild it only when a message is - // handled. - let mut user_interface = UserInterface::build( - document(&mut self, width, height), - cache.take().unwrap(), - &mut renderer, - ); - - let messages = user_interface.update(events.drain(..)); - - if messages.is_empty() { - primitive = user_interface.draw(&mut renderer); - - cache = Some(user_interface.into_cache()); - } else { - // When there are messages, we are forced to rebuild twice - // for now :^) - let temp_cache = user_interface.into_cache(); - - for message in messages { - self.update(message); - } - - let user_interface = UserInterface::build( - document(&mut self, width, height), - temp_cache, - &mut renderer, - ); - - primitive = user_interface.draw(&mut renderer); - - cache = Some(user_interface.into_cache()); - } - - window.request_redraw(); - } - Event::WindowEvent { - event: WindowEvent::RedrawRequested, - .. - } => { - renderer.draw(&mut target, &primitive); - - // TODO: Handle animations! - // Maybe we can use `ControlFlow::WaitUntil` for this. - } - Event::WindowEvent { - event: WindowEvent::CloseRequested, - .. - } => { - *control_flow = ControlFlow::Exit; - } - _ => { - *control_flow = ControlFlow::Wait; - } - }) - } -} - -fn document( - application: &mut Application, - width: u16, - height: u16, -) -> Element -where - Application: self::Application, - Application::Message: 'static, -{ - Column::new() - .width(Length::Units(width)) - .height(Length::Units(height)) - .push(application.view()) - .into() -} +pub use application::Application; -- cgit From ae585eb9cb043f2f6565bbe9c80c50cb7ded8bac Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 10 Oct 2019 05:52:35 +0200 Subject: Process `winit` mouse input and cursor movement --- winit/Cargo.toml | 1 + winit/src/application.rs | 55 ++++++++++++++++++++++++++++++++---------------- 2 files changed, 38 insertions(+), 18 deletions(-) diff --git a/winit/Cargo.toml b/winit/Cargo.toml index d7f61503..15158e0e 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -10,3 +10,4 @@ repository = "https://github.com/hecrj/iced" [dependencies] iced_native = { version = "0.1.0-alpha", path = "../native" } winit = "0.20.0-alpha3" +log = "0.4" diff --git a/winit/src/application.rs b/winit/src/application.rs index 9e23cd40..d09aad7a 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -1,8 +1,10 @@ -use crate::renderer::Windowed; -use crate::{Cache, Column, Element, Length, UserInterface}; +use crate::{ + column, conversion, input::mouse, renderer::Windowed, Cache, Column, + Element, Event, Length, UserInterface, +}; pub trait Application { - type Renderer: Windowed + iced_native::column::Renderer; + type Renderer: Windowed + column::Renderer; type Message; @@ -15,7 +17,7 @@ pub trait Application { Self: 'static + Sized, { use winit::{ - event::{Event, WindowEvent}, + event::{self, WindowEvent}, event_loop::{ControlFlow, EventLoop}, window::WindowBuilder, }; @@ -46,7 +48,7 @@ pub trait Application { window.request_redraw(); event_loop.run(move |event, _, control_flow| match event { - Event::EventsCleared => { + event::Event::EventsCleared => { // TODO: We should be able to keep a user interface alive // between events once we remove state references. // @@ -70,6 +72,8 @@ pub trait Application { let temp_cache = user_interface.into_cache(); for message in messages { + log::debug!("Updating"); + self.update(message); } @@ -86,21 +90,36 @@ pub trait Application { window.request_redraw(); } - Event::WindowEvent { - event: WindowEvent::RedrawRequested, + event::Event::WindowEvent { + event: window_event, .. - } => { - renderer.draw(&mut target, &primitive); + } => match window_event { + WindowEvent::RedrawRequested => { + renderer.draw(&mut target, &primitive); - // TODO: Handle animations! - // Maybe we can use `ControlFlow::WaitUntil` for this. - } - Event::WindowEvent { - event: WindowEvent::CloseRequested, - .. - } => { - *control_flow = ControlFlow::Exit; - } + // TODO: Handle animations! + // Maybe we can use `ControlFlow::WaitUntil` for this. + } + WindowEvent::CursorMoved { position, .. } => { + let physical_position = + position.to_physical(window.hidpi_factor()); + + events.push(Event::Mouse(mouse::Event::CursorMoved { + x: physical_position.x as f32, + y: physical_position.y as f32, + })); + } + WindowEvent::MouseInput { button, state, .. } => { + events.push(Event::Mouse(mouse::Event::Input { + button: conversion::mouse_button(button), + state: conversion::button_state(state), + })); + } + WindowEvent::CloseRequested => { + *control_flow = ControlFlow::Exit; + } + _ => {} + }, _ => { *control_flow = ControlFlow::Wait; } -- cgit From 2fe01a0b1ee930759d2180fe25666c0701bffd40 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 10 Oct 2019 05:53:57 +0200 Subject: Use improved `RedrawRequested` API in `iced_winit` --- winit/Cargo.toml | 2 +- winit/src/application.rs | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/winit/Cargo.toml b/winit/Cargo.toml index 15158e0e..fa5d6adf 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -9,5 +9,5 @@ repository = "https://github.com/hecrj/iced" [dependencies] iced_native = { version = "0.1.0-alpha", path = "../native" } -winit = "0.20.0-alpha3" +winit = { version = "0.20.0-alpha3", git = "https://github.com/hecrj/winit", branch = "redraw-requested-2.0" } log = "0.4" diff --git a/winit/src/application.rs b/winit/src/application.rs index d09aad7a..ab6a43f5 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -48,7 +48,7 @@ pub trait Application { window.request_redraw(); event_loop.run(move |event, _, control_flow| match event { - event::Event::EventsCleared => { + event::Event::MainEventsCleared => { // TODO: We should be able to keep a user interface alive // between events once we remove state references. // @@ -90,16 +90,16 @@ pub trait Application { window.request_redraw(); } + event::Event::RedrawRequested(_) => { + renderer.draw(&mut target, &primitive); + + // TODO: Handle animations! + // Maybe we can use `ControlFlow::WaitUntil` for this. + } event::Event::WindowEvent { event: window_event, .. } => match window_event { - WindowEvent::RedrawRequested => { - renderer.draw(&mut target, &primitive); - - // TODO: Handle animations! - // Maybe we can use `ControlFlow::WaitUntil` for this. - } WindowEvent::CursorMoved { position, .. } => { let physical_position = position.to_physical(window.hidpi_factor()); -- cgit From 1f60ca3ab4287b5f9bf605fcb7b3cdcf20e17074 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 10 Oct 2019 05:58:42 +0200 Subject: Update mouse cursor icon properly in `iced_winit` --- winit/src/application.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/winit/src/application.rs b/winit/src/application.rs index ab6a43f5..ac3a4619 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -1,6 +1,6 @@ use crate::{ column, conversion, input::mouse, renderer::Windowed, Cache, Column, - Element, Event, Length, UserInterface, + Element, Event, Length, MouseCursor, UserInterface, }; pub trait Application { @@ -44,6 +44,7 @@ pub trait Application { let mut primitive = user_interface.draw(&mut renderer); let mut cache = Some(user_interface.into_cache()); let mut events = Vec::new(); + let mut mouse_cursor = MouseCursor::OutOfBounds; window.request_redraw(); @@ -91,7 +92,15 @@ pub trait Application { window.request_redraw(); } event::Event::RedrawRequested(_) => { - renderer.draw(&mut target, &primitive); + let new_mouse_cursor = renderer.draw(&mut target, &primitive); + + if new_mouse_cursor != mouse_cursor { + window.set_cursor_icon(conversion::mouse_cursor( + new_mouse_cursor, + )); + + mouse_cursor = new_mouse_cursor; + } // TODO: Handle animations! // Maybe we can use `ControlFlow::WaitUntil` for this. -- cgit From 650d020fde6e684bf3c865de823ace08194b5220 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 11 Oct 2019 21:16:36 +0200 Subject: Handle window resizes in `iced_winit` --- winit/src/application.rs | 48 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/winit/src/application.rs b/winit/src/application.rs index ac3a4619..54c31126 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -29,14 +29,17 @@ pub trait Application { .build(&event_loop) .expect("Open window"); - let size = window.inner_size().to_physical(window.hidpi_factor());; - let (width, height) = (size.width as u16, size.height as u16); + let mut size: Size = window + .inner_size() + .to_physical(window.hidpi_factor()) + .into(); + let mut new_size: Option = None; let mut renderer = Self::Renderer::new(&window); - let mut target = renderer.target(width, height); + let mut target = renderer.target(size.width, size.height); let user_interface = UserInterface::build( - document(&mut self, width, height), + document(&mut self, size), Cache::default(), &mut renderer, ); @@ -56,7 +59,7 @@ pub trait Application { // This will allow us to rebuild it only when a message is // handled. let mut user_interface = UserInterface::build( - document(&mut self, width, height), + document(&mut self, size), cache.take().unwrap(), &mut renderer, ); @@ -79,7 +82,7 @@ pub trait Application { } let user_interface = UserInterface::build( - document(&mut self, width, height), + document(&mut self, size), temp_cache, &mut renderer, ); @@ -92,6 +95,11 @@ pub trait Application { window.request_redraw(); } event::Event::RedrawRequested(_) => { + if let Some(new_size) = new_size.take() { + target = renderer.target(new_size.width, new_size.height); + size = new_size; + } + let new_mouse_cursor = renderer.draw(&mut target, &primitive); if new_mouse_cursor != mouse_cursor { @@ -127,6 +135,12 @@ pub trait Application { WindowEvent::CloseRequested => { *control_flow = ControlFlow::Exit; } + WindowEvent::Resized(size) => { + new_size = + Some(size.to_physical(window.hidpi_factor()).into()); + + log::debug!("Resized: {:?}", new_size); + } _ => {} }, _ => { @@ -136,18 +150,32 @@ pub trait Application { } } -fn document( - application: &mut Application, +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +struct Size { width: u16, height: u16, +} + +impl From for Size { + fn from(physical_size: winit::dpi::PhysicalSize) -> Self { + Self { + width: physical_size.width.round() as u16, + height: physical_size.height.round() as u16, + } + } +} + +fn document( + application: &mut Application, + size: Size, ) -> Element where Application: self::Application, Application::Message: 'static, { Column::new() - .width(Length::Units(width)) - .height(Length::Units(height)) + .width(Length::Units(size.width)) + .height(Length::Units(size.height)) .push(application.view()) .into() } -- cgit From 8846a239cf14edd464b1d09f6d6d57ad9b5c9fc7 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 11 Oct 2019 22:15:39 +0200 Subject: Rename `Renderer::Primitive` to `Renderer::Output` --- native/src/element.rs | 8 ++++---- native/src/lib.rs | 2 +- native/src/renderer.rs | 4 ++-- native/src/renderer/debugger.rs | 2 +- native/src/renderer/windowed.rs | 2 +- native/src/user_interface.rs | 14 +++++++------- native/src/widget.rs | 2 +- native/src/widget/button.rs | 4 ++-- native/src/widget/checkbox.rs | 4 ++-- native/src/widget/column.rs | 4 ++-- native/src/widget/image.rs | 5 ++--- native/src/widget/radio.rs | 4 ++-- native/src/widget/row.rs | 4 ++-- native/src/widget/slider.rs | 4 ++-- native/src/widget/text.rs | 4 ++-- wgpu/src/renderer.rs | 11 ++++++----- wgpu/src/renderer/button.rs | 2 +- wgpu/src/renderer/checkbox.rs | 2 +- wgpu/src/renderer/column.rs | 2 +- wgpu/src/renderer/image.rs | 2 +- wgpu/src/renderer/radio.rs | 2 +- wgpu/src/renderer/row.rs | 2 +- wgpu/src/renderer/slider.rs | 2 +- wgpu/src/renderer/text.rs | 2 +- winit/src/application.rs | 2 +- 25 files changed, 48 insertions(+), 48 deletions(-) diff --git a/native/src/element.rs b/native/src/element.rs index cf96b7ea..899089b3 100644 --- a/native/src/element.rs +++ b/native/src/element.rs @@ -50,7 +50,7 @@ where renderer: &mut Renderer, layout: Layout<'_>, cursor_position: Point, - ) -> Renderer::Primitive { + ) -> Renderer::Output { self.widget.draw(renderer, layout, cursor_position) } @@ -120,7 +120,7 @@ where /// # }; /// # pub struct Renderer; /// # - /// # impl iced_native::Renderer for Renderer { type Primitive = (); } + /// # impl iced_native::Renderer for Renderer { type Output = (); } /// # /// # impl iced_native::row::Renderer for Renderer { /// # fn draw( @@ -323,7 +323,7 @@ where renderer: &mut Renderer, layout: Layout<'_>, cursor_position: Point, - ) -> Renderer::Primitive { + ) -> Renderer::Output { self.widget.draw(renderer, layout, cursor_position) } @@ -383,7 +383,7 @@ where renderer: &mut Renderer, layout: Layout<'_>, cursor_position: Point, - ) -> Renderer::Primitive { + ) -> Renderer::Output { renderer.explain( self.element.widget.as_ref(), layout, diff --git a/native/src/lib.rs b/native/src/lib.rs index cada56f9..fa72a553 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -83,7 +83,7 @@ //! # pub struct Renderer {} //! # //! # impl iced_native::Renderer for Renderer { -//! # type Primitive = (); +//! # type Output = (); //! # } //! # //! # impl button::Renderer for Renderer { diff --git a/native/src/renderer.rs b/native/src/renderer.rs index a56d7b76..afe1b09a 100644 --- a/native/src/renderer.rs +++ b/native/src/renderer.rs @@ -1,7 +1,7 @@ //! Write your own renderer. //! //! You will need to implement the `Renderer` trait first. It simply contains -//! a `Primitive` associated type. +//! an `Output` associated type. //! //! There is no common trait to draw all the widgets. Instead, every [`Widget`] //! constrains its generic `Renderer` type as necessary. @@ -27,5 +27,5 @@ pub use debugger::Debugger; pub use windowed::Windowed; pub trait Renderer { - type Primitive; + type Output; } diff --git a/native/src/renderer/debugger.rs b/native/src/renderer/debugger.rs index cb472dd4..4cc50661 100644 --- a/native/src/renderer/debugger.rs +++ b/native/src/renderer/debugger.rs @@ -21,5 +21,5 @@ pub trait Debugger: super::Renderer { layout: Layout<'_>, cursor_position: Point, color: Color, - ) -> Self::Primitive; + ) -> Self::Output; } diff --git a/native/src/renderer/windowed.rs b/native/src/renderer/windowed.rs index f89da40b..bcf37964 100644 --- a/native/src/renderer/windowed.rs +++ b/native/src/renderer/windowed.rs @@ -11,7 +11,7 @@ pub trait Windowed: super::Renderer { fn draw( &mut self, + output: &Self::Output, target: &mut Self::Target, - primitive: &Self::Primitive, ) -> MouseCursor; } diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 812ad598..44aaf9e3 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -48,7 +48,7 @@ where /// # pub fn new() -> Self { Renderer } /// # } /// # - /// # impl iced_native::Renderer for Renderer { type Primitive = (); } + /// # impl iced_native::Renderer for Renderer { type Output = (); } /// # /// # impl iced_native::column::Renderer for Renderer { /// # fn draw( @@ -56,7 +56,7 @@ where /// # _column: &iced_native::Column<'_, Message, Self>, /// # _layout: iced_native::Layout<'_>, /// # _cursor_position: iced_native::Point, - /// # ) -> Self::Primitive { + /// # ) -> Self::Output { /// # () /// # } /// # } @@ -144,7 +144,7 @@ where /// # pub fn new() -> Self { Renderer } /// # } /// # - /// # impl iced_native::Renderer for Renderer { type Primitive = (); } + /// # impl iced_native::Renderer for Renderer { type Output = (); } /// # /// # impl iced_native::column::Renderer for Renderer { /// # fn draw( @@ -152,7 +152,7 @@ where /// # _column: &iced_native::Column<'_, Message, Self>, /// # _layout: iced_native::Layout<'_>, /// # _cursor_position: iced_native::Point, - /// # ) -> Self::Primitive { + /// # ) -> Self::Output { /// # () /// # } /// # } @@ -242,7 +242,7 @@ where /// # pub fn new() -> Self { Renderer } /// # } /// # - /// # impl iced_native::Renderer for Renderer { type Primitive = (); } + /// # impl iced_native::Renderer for Renderer { type Output = (); } /// # /// # impl iced_native::column::Renderer for Renderer { /// # fn draw( @@ -250,7 +250,7 @@ where /// # _column: &iced_native::Column<'_, Message, Self>, /// # _layout: iced_native::Layout<'_>, /// # _cursor_position: iced_native::Point, - /// # ) -> Self::Primitive { + /// # ) -> Self::Output { /// # () /// # } /// # } @@ -296,7 +296,7 @@ where /// // Flush rendering operations... /// } /// ``` - pub fn draw(&self, renderer: &mut Renderer) -> Renderer::Primitive { + pub fn draw(&self, renderer: &mut Renderer) -> Renderer::Output { self.root.widget.draw( renderer, Layout::new(&self.layout), diff --git a/native/src/widget.rs b/native/src/widget.rs index b7181c1b..bcef2665 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -77,7 +77,7 @@ where renderer: &mut Renderer, layout: Layout<'_>, cursor_position: Point, - ) -> Renderer::Primitive; + ) -> Renderer::Output; /// Computes the _layout_ hash of the [`Widget`]. /// diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs index 1f881660..4ab59f7f 100644 --- a/native/src/widget/button.rs +++ b/native/src/widget/button.rs @@ -67,7 +67,7 @@ where renderer: &mut Renderer, layout: Layout<'_>, cursor_position: Point, - ) -> Renderer::Primitive { + ) -> Renderer::Output { renderer.draw(&self, layout, cursor_position) } @@ -100,7 +100,7 @@ pub trait Renderer: crate::Renderer + Sized { button: &Button<'_, Message, Self>, layout: Layout<'_>, cursor_position: Point, - ) -> Self::Primitive; + ) -> Self::Output; } impl<'a, Message, Renderer> From> diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs index c069bfdc..cb2baedd 100644 --- a/native/src/widget/checkbox.rs +++ b/native/src/widget/checkbox.rs @@ -43,7 +43,7 @@ where renderer: &mut Renderer, layout: Layout<'_>, cursor_position: Point, - ) -> Renderer::Primitive { + ) -> Renderer::Output { renderer.draw(&self, layout, cursor_position) } @@ -80,7 +80,7 @@ pub trait Renderer: crate::Renderer { checkbox: &Checkbox, layout: Layout<'_>, cursor_position: Point, - ) -> Self::Primitive; + ) -> Self::Output; } impl<'a, Message, Renderer> From> diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs index 7e10e662..7995cf5d 100644 --- a/native/src/widget/column.rs +++ b/native/src/widget/column.rs @@ -70,7 +70,7 @@ where renderer: &mut Renderer, layout: Layout<'_>, cursor_position: Point, - ) -> Renderer::Primitive { + ) -> Renderer::Output { renderer.draw(&self, layout, cursor_position) } @@ -97,7 +97,7 @@ pub trait Renderer: crate::Renderer + Sized { row: &Column<'_, Message, Self>, layout: Layout<'_>, cursor_position: Point, - ) -> Self::Primitive; + ) -> Self::Output; } impl<'a, Message, Renderer> From> diff --git a/native/src/widget/image.rs b/native/src/widget/image.rs index 5197d5b1..178dd709 100644 --- a/native/src/widget/image.rs +++ b/native/src/widget/image.rs @@ -20,7 +20,7 @@ where renderer: &mut Renderer, layout: Layout<'_>, _cursor_position: Point, - ) -> Renderer::Primitive { + ) -> Renderer::Output { renderer.draw(&self, layout) } @@ -50,8 +50,7 @@ pub trait Renderer: crate::Renderer { /// Draws an [`Image`]. /// /// [`Image`]: struct.Image.html - fn draw(&mut self, image: &Image, layout: Layout<'_>) - -> Self::Primitive; + fn draw(&mut self, image: &Image, layout: Layout<'_>) -> Self::Output; } impl<'a, I, Message, Renderer> From> for Element<'a, Message, Renderer> diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs index 22308f81..27b8f8a8 100644 --- a/native/src/widget/radio.rs +++ b/native/src/widget/radio.rs @@ -40,7 +40,7 @@ where renderer: &mut Renderer, layout: Layout<'_>, cursor_position: Point, - ) -> Renderer::Primitive { + ) -> Renderer::Output { renderer.draw(&self, layout, cursor_position) } @@ -77,7 +77,7 @@ pub trait Renderer: crate::Renderer { radio: &Radio, layout: Layout<'_>, cursor_position: Point, - ) -> Self::Primitive; + ) -> Self::Output; } impl<'a, Message, Renderer> From> diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs index b1d4a5b2..5ec27159 100644 --- a/native/src/widget/row.rs +++ b/native/src/widget/row.rs @@ -70,7 +70,7 @@ where renderer: &mut Renderer, layout: Layout<'_>, cursor_position: Point, - ) -> Renderer::Primitive { + ) -> Renderer::Output { renderer.draw(&self, layout, cursor_position) } @@ -98,7 +98,7 @@ pub trait Renderer: crate::Renderer + Sized { row: &Row<'_, Message, Self>, layout: Layout<'_>, cursor_position: Point, - ) -> Self::Primitive; + ) -> Self::Output; } impl<'a, Message, Renderer> From> diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs index 643efdf4..d643d902 100644 --- a/native/src/widget/slider.rs +++ b/native/src/widget/slider.rs @@ -71,7 +71,7 @@ where renderer: &mut Renderer, layout: Layout<'_>, cursor_position: Point, - ) -> Renderer::Primitive { + ) -> Renderer::Output { renderer.draw(&self, layout, cursor_position) } @@ -111,7 +111,7 @@ pub trait Renderer: crate::Renderer { slider: &Slider<'_, Message>, layout: Layout<'_>, cursor_position: Point, - ) -> Self::Primitive; + ) -> Self::Output; } impl<'a, Message, Renderer> From> diff --git a/native/src/widget/text.rs b/native/src/widget/text.rs index 62f2d7b7..e389e1d9 100644 --- a/native/src/widget/text.rs +++ b/native/src/widget/text.rs @@ -18,7 +18,7 @@ where renderer: &mut Renderer, layout: Layout<'_>, _cursor_position: Point, - ) -> Renderer::Primitive { + ) -> Renderer::Output { renderer.draw(&self, layout) } @@ -64,7 +64,7 @@ pub trait Renderer: crate::Renderer { /// [`Text`]: struct.Text.html /// [`HorizontalAlignment`]: enum.HorizontalAlignment.html /// [`VerticalAlignment`]: enum.VerticalAlignment.html - fn draw(&mut self, text: &Text, layout: Layout<'_>) -> Self::Primitive; + fn draw(&mut self, text: &Text, layout: Layout<'_>) -> Self::Output; } impl<'a, Message, Renderer> From for Element<'a, Message, Renderer> diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index 036efd27..cdb44554 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -100,8 +100,8 @@ impl Renderer { fn draw( &mut self, - target: &mut Target, primitive: &Primitive, + target: &mut Target, ) -> MouseCursor { log::debug!("Drawing"); @@ -154,6 +154,7 @@ impl Renderer { MouseCursor::OutOfBounds } + fn draw_primitive(&mut self, primitive: &Primitive) { match primitive { Primitive::None => {} @@ -243,7 +244,7 @@ impl Renderer { impl iced_native::Renderer for Renderer { // TODO: Add `MouseCursor` here (?) - type Primitive = Primitive; + type Output = Primitive; } impl Windowed for Renderer { @@ -259,10 +260,10 @@ impl Windowed for Renderer { fn draw( &mut self, + output: &Self::Output, target: &mut Target, - primitive: &Primitive, ) -> MouseCursor { - self.draw(target, primitive) + self.draw(output, target) } } @@ -273,7 +274,7 @@ impl Debugger for Renderer { layout: Layout<'_>, cursor_position: Point, _color: Color, - ) -> Self::Primitive { + ) -> Self::Output { // TODO: Include a bordered box to display layout bounds widget.draw(self, layout, cursor_position) } diff --git a/wgpu/src/renderer/button.rs b/wgpu/src/renderer/button.rs index 00fcd0eb..33789379 100644 --- a/wgpu/src/renderer/button.rs +++ b/wgpu/src/renderer/button.rs @@ -21,7 +21,7 @@ impl button::Renderer for Renderer { button: &Button, layout: Layout<'_>, cursor_position: Point, - ) -> Self::Primitive { + ) -> Self::Output { let bounds = layout.bounds(); Primitive::Group { diff --git a/wgpu/src/renderer/checkbox.rs b/wgpu/src/renderer/checkbox.rs index 16d5734f..003df7e5 100644 --- a/wgpu/src/renderer/checkbox.rs +++ b/wgpu/src/renderer/checkbox.rs @@ -11,7 +11,7 @@ impl checkbox::Renderer for Renderer { _checkbox: &Checkbox, _layout: Layout<'_>, _cursor_position: Point, - ) -> Self::Primitive { + ) -> Self::Output { // TODO Primitive::None } diff --git a/wgpu/src/renderer/column.rs b/wgpu/src/renderer/column.rs index 1b9adad6..c83a7de1 100644 --- a/wgpu/src/renderer/column.rs +++ b/wgpu/src/renderer/column.rs @@ -7,7 +7,7 @@ impl column::Renderer for Renderer { column: &Column<'_, Message, Self>, layout: Layout<'_>, cursor_position: Point, - ) -> Self::Primitive { + ) -> Self::Output { Primitive::Group { primitives: column .children diff --git a/wgpu/src/renderer/image.rs b/wgpu/src/renderer/image.rs index bacc430d..f77a95db 100644 --- a/wgpu/src/renderer/image.rs +++ b/wgpu/src/renderer/image.rs @@ -10,7 +10,7 @@ impl image::Renderer<&str> for Renderer { &mut self, _image: &Image<&str>, _layout: Layout<'_>, - ) -> Self::Primitive { + ) -> Self::Output { Primitive::None } } diff --git a/wgpu/src/renderer/radio.rs b/wgpu/src/renderer/radio.rs index fdc0a0fc..0a34fee9 100644 --- a/wgpu/src/renderer/radio.rs +++ b/wgpu/src/renderer/radio.rs @@ -11,7 +11,7 @@ impl radio::Renderer for Renderer { _radio: &Radio, _layout: Layout<'_>, _cursor_position: Point, - ) -> Self::Primitive { + ) -> Self::Output { Primitive::None } } diff --git a/wgpu/src/renderer/row.rs b/wgpu/src/renderer/row.rs index be9e4ede..b6baf61f 100644 --- a/wgpu/src/renderer/row.rs +++ b/wgpu/src/renderer/row.rs @@ -7,7 +7,7 @@ impl row::Renderer for Renderer { row: &Row<'_, Message, Self>, layout: Layout<'_>, cursor_position: Point, - ) -> Self::Primitive { + ) -> Self::Output { Primitive::Group { primitives: row .children diff --git a/wgpu/src/renderer/slider.rs b/wgpu/src/renderer/slider.rs index 2e76022d..2eacdc89 100644 --- a/wgpu/src/renderer/slider.rs +++ b/wgpu/src/renderer/slider.rs @@ -11,7 +11,7 @@ impl slider::Renderer for Renderer { _slider: &Slider, _layout: Layout<'_>, _cursor_position: Point, - ) -> Self::Primitive { + ) -> Self::Output { Primitive::None } } diff --git a/wgpu/src/renderer/text.rs b/wgpu/src/renderer/text.rs index c89c0b3e..61349533 100644 --- a/wgpu/src/renderer/text.rs +++ b/wgpu/src/renderer/text.rs @@ -67,7 +67,7 @@ impl text::Renderer for Renderer { }) } - fn draw(&mut self, text: &Text, layout: Layout<'_>) -> Self::Primitive { + fn draw(&mut self, text: &Text, layout: Layout<'_>) -> Self::Output { Primitive::Text { content: text.content.clone(), size: f32::from(text.size.unwrap_or(20)), diff --git a/winit/src/application.rs b/winit/src/application.rs index 54c31126..2ea52e5f 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -100,7 +100,7 @@ pub trait Application { size = new_size; } - let new_mouse_cursor = renderer.draw(&mut target, &primitive); + let new_mouse_cursor = renderer.draw(&primitive, &mut target); if new_mouse_cursor != mouse_cursor { window.set_cursor_icon(conversion::mouse_cursor( -- cgit From a031a6f2130b3913a2419e4cea859c22aa388213 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 11 Oct 2019 23:30:53 +0200 Subject: Handle mouse cursor in `iced_wgpu` --- native/src/mouse_cursor.rs | 2 +- wgpu/src/renderer.rs | 7 +++--- wgpu/src/renderer/button.rs | 55 +++++++++++++++++++++++++------------------ wgpu/src/renderer/checkbox.rs | 6 +++-- wgpu/src/renderer/column.rs | 34 +++++++++++++++++--------- wgpu/src/renderer/image.rs | 4 ++-- wgpu/src/renderer/radio.rs | 4 ++-- wgpu/src/renderer/row.rs | 34 +++++++++++++++++--------- wgpu/src/renderer/slider.rs | 4 ++-- wgpu/src/renderer/text.rs | 21 ++++++++++------- 10 files changed, 104 insertions(+), 67 deletions(-) diff --git a/native/src/mouse_cursor.rs b/native/src/mouse_cursor.rs index f6a68c54..291e57a6 100644 --- a/native/src/mouse_cursor.rs +++ b/native/src/mouse_cursor.rs @@ -1,5 +1,5 @@ /// The state of the mouse cursor. -#[derive(Debug, Eq, PartialEq, Clone, Copy)] +#[derive(Debug, Eq, PartialEq, Clone, Copy, PartialOrd, Ord)] pub enum MouseCursor { /// The cursor is out of the bounds of the user interface. OutOfBounds, diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index cdb44554..8d54b2c7 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -100,7 +100,7 @@ impl Renderer { fn draw( &mut self, - primitive: &Primitive, + (primitive, mouse_cursor): &(Primitive, MouseCursor), target: &mut Target, ) -> MouseCursor { log::debug!("Drawing"); @@ -152,7 +152,7 @@ impl Renderer { self.queue.submit(&[encoder.finish()]); - MouseCursor::OutOfBounds + *mouse_cursor } fn draw_primitive(&mut self, primitive: &Primitive) { @@ -243,8 +243,7 @@ impl Renderer { } impl iced_native::Renderer for Renderer { - // TODO: Add `MouseCursor` here (?) - type Output = Primitive; + type Output = (Primitive, MouseCursor); } impl Windowed for Renderer { diff --git a/wgpu/src/renderer/button.rs b/wgpu/src/renderer/button.rs index 33789379..275c870f 100644 --- a/wgpu/src/renderer/button.rs +++ b/wgpu/src/renderer/button.rs @@ -1,7 +1,7 @@ use crate::{Primitive, Renderer}; use iced_native::{ - button, Align, Background, Button, Color, Layout, Length, Node, Point, - Style, + button, Align, Background, Button, Color, Layout, Length, MouseCursor, + Node, Point, Style, }; impl button::Renderer for Renderer { @@ -24,26 +24,35 @@ impl button::Renderer for Renderer { ) -> Self::Output { let bounds = layout.bounds(); - Primitive::Group { - primitives: vec![ - Primitive::Quad { - bounds, - background: button.background.unwrap_or(Background::Color( - Color { - r: 0.8, - b: 0.8, - g: 0.8, - a: 1.0, - }, - )), - border_radius: button.border_radius, - }, - button.content.draw( - self, - layout.children().next().unwrap(), - cursor_position, - ), - ], - } + let (content, _) = button.content.draw( + self, + layout.children().next().unwrap(), + cursor_position, + ); + + ( + Primitive::Group { + primitives: vec![ + Primitive::Quad { + bounds, + background: button.background.unwrap_or( + Background::Color(Color { + r: 0.8, + b: 0.8, + g: 0.8, + a: 1.0, + }), + ), + border_radius: button.border_radius, + }, + content, + ], + }, + if bounds.contains(cursor_position) { + MouseCursor::Pointer + } else { + MouseCursor::OutOfBounds + }, + ) } } diff --git a/wgpu/src/renderer/checkbox.rs b/wgpu/src/renderer/checkbox.rs index 003df7e5..7b06d397 100644 --- a/wgpu/src/renderer/checkbox.rs +++ b/wgpu/src/renderer/checkbox.rs @@ -1,5 +1,7 @@ use crate::{Primitive, Renderer}; -use iced_native::{checkbox, Checkbox, Layout, Node, Point, Style}; +use iced_native::{ + checkbox, Checkbox, Layout, MouseCursor, Node, Point, Style, +}; impl checkbox::Renderer for Renderer { fn node(&self, _checkbox: &Checkbox) -> Node { @@ -13,6 +15,6 @@ impl checkbox::Renderer for Renderer { _cursor_position: Point, ) -> Self::Output { // TODO - Primitive::None + (Primitive::None, MouseCursor::OutOfBounds) } } diff --git a/wgpu/src/renderer/column.rs b/wgpu/src/renderer/column.rs index c83a7de1..cac6da77 100644 --- a/wgpu/src/renderer/column.rs +++ b/wgpu/src/renderer/column.rs @@ -1,5 +1,5 @@ use crate::{Primitive, Renderer}; -use iced_native::{column, Column, Layout, Point}; +use iced_native::{column, Column, Layout, MouseCursor, Point}; impl column::Renderer for Renderer { fn draw( @@ -8,15 +8,27 @@ impl column::Renderer for Renderer { layout: Layout<'_>, cursor_position: Point, ) -> Self::Output { - Primitive::Group { - primitives: column - .children - .iter() - .zip(layout.children()) - .map(|(child, layout)| { - child.draw(self, layout, cursor_position) - }) - .collect(), - } + let mut mouse_cursor = MouseCursor::OutOfBounds; + + ( + Primitive::Group { + primitives: column + .children + .iter() + .zip(layout.children()) + .map(|(child, layout)| { + let (primitive, new_mouse_cursor) = + child.draw(self, layout, cursor_position); + + if new_mouse_cursor > mouse_cursor { + mouse_cursor = new_mouse_cursor; + } + + primitive + }) + .collect(), + }, + mouse_cursor, + ) } } diff --git a/wgpu/src/renderer/image.rs b/wgpu/src/renderer/image.rs index f77a95db..a29a3d49 100644 --- a/wgpu/src/renderer/image.rs +++ b/wgpu/src/renderer/image.rs @@ -1,5 +1,5 @@ use crate::{Primitive, Renderer}; -use iced_native::{image, Image, Layout, Node, Style}; +use iced_native::{image, Image, Layout, MouseCursor, Node, Style}; impl image::Renderer<&str> for Renderer { fn node(&self, _image: &Image<&str>) -> Node { @@ -11,6 +11,6 @@ impl image::Renderer<&str> for Renderer { _image: &Image<&str>, _layout: Layout<'_>, ) -> Self::Output { - Primitive::None + (Primitive::None, MouseCursor::OutOfBounds) } } diff --git a/wgpu/src/renderer/radio.rs b/wgpu/src/renderer/radio.rs index 0a34fee9..712a7104 100644 --- a/wgpu/src/renderer/radio.rs +++ b/wgpu/src/renderer/radio.rs @@ -1,5 +1,5 @@ use crate::{Primitive, Renderer}; -use iced_native::{radio, Layout, Node, Point, Radio, Style}; +use iced_native::{radio, Layout, MouseCursor, Node, Point, Radio, Style}; impl radio::Renderer for Renderer { fn node(&self, _checkbox: &Radio) -> Node { @@ -12,6 +12,6 @@ impl radio::Renderer for Renderer { _layout: Layout<'_>, _cursor_position: Point, ) -> Self::Output { - Primitive::None + (Primitive::None, MouseCursor::OutOfBounds) } } diff --git a/wgpu/src/renderer/row.rs b/wgpu/src/renderer/row.rs index b6baf61f..bbfef9a1 100644 --- a/wgpu/src/renderer/row.rs +++ b/wgpu/src/renderer/row.rs @@ -1,5 +1,5 @@ use crate::{Primitive, Renderer}; -use iced_native::{row, Layout, Point, Row}; +use iced_native::{row, Layout, MouseCursor, Point, Row}; impl row::Renderer for Renderer { fn draw( @@ -8,15 +8,27 @@ impl row::Renderer for Renderer { layout: Layout<'_>, cursor_position: Point, ) -> Self::Output { - Primitive::Group { - primitives: row - .children - .iter() - .zip(layout.children()) - .map(|(child, layout)| { - child.draw(self, layout, cursor_position) - }) - .collect(), - } + let mut mouse_cursor = MouseCursor::OutOfBounds; + + ( + Primitive::Group { + primitives: row + .children + .iter() + .zip(layout.children()) + .map(|(child, layout)| { + let (primitive, new_mouse_cursor) = + child.draw(self, layout, cursor_position); + + if new_mouse_cursor > mouse_cursor { + mouse_cursor = new_mouse_cursor; + } + + primitive + }) + .collect(), + }, + mouse_cursor, + ) } } diff --git a/wgpu/src/renderer/slider.rs b/wgpu/src/renderer/slider.rs index 2eacdc89..d1a30244 100644 --- a/wgpu/src/renderer/slider.rs +++ b/wgpu/src/renderer/slider.rs @@ -1,5 +1,5 @@ use crate::{Primitive, Renderer}; -use iced_native::{slider, Layout, Node, Point, Slider, Style}; +use iced_native::{slider, Layout, MouseCursor, Node, Point, Slider, Style}; impl slider::Renderer for Renderer { fn node(&self, _slider: &Slider) -> Node { @@ -12,6 +12,6 @@ impl slider::Renderer for Renderer { _layout: Layout<'_>, _cursor_position: Point, ) -> Self::Output { - Primitive::None + (Primitive::None, MouseCursor::OutOfBounds) } } diff --git a/wgpu/src/renderer/text.rs b/wgpu/src/renderer/text.rs index 61349533..8fbade4e 100644 --- a/wgpu/src/renderer/text.rs +++ b/wgpu/src/renderer/text.rs @@ -1,5 +1,5 @@ use crate::{Primitive, Renderer}; -use iced_native::{text, Color, Layout, Node, Style, Text}; +use iced_native::{text, Color, Layout, MouseCursor, Node, Style, Text}; use wgpu_glyph::{GlyphCruncher, Section}; @@ -68,13 +68,16 @@ impl text::Renderer for Renderer { } fn draw(&mut self, text: &Text, layout: Layout<'_>) -> Self::Output { - Primitive::Text { - content: text.content.clone(), - size: f32::from(text.size.unwrap_or(20)), - bounds: layout.bounds(), - color: text.color.unwrap_or(Color::BLACK), - horizontal_alignment: text.horizontal_alignment, - vertical_alignment: text.vertical_alignment, - } + ( + Primitive::Text { + content: text.content.clone(), + size: f32::from(text.size.unwrap_or(20)), + bounds: layout.bounds(), + color: text.color.unwrap_or(Color::BLACK), + horizontal_alignment: text.horizontal_alignment, + vertical_alignment: text.vertical_alignment, + }, + MouseCursor::OutOfBounds, + ) } } -- cgit From c63bdacaad7d923358863e3b6b2524893788d91c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 11 Oct 2019 23:45:01 +0200 Subject: Make `Renderer` immutable in `UserInterface::build` --- native/src/element.rs | 5 +---- native/src/user_interface.rs | 2 +- winit/src/application.rs | 6 +++--- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/native/src/element.rs b/native/src/element.rs index 899089b3..bbedd942 100644 --- a/native/src/element.rs +++ b/native/src/element.rs @@ -247,10 +247,7 @@ where } } - pub(crate) fn compute_layout( - &self, - renderer: &mut Renderer, - ) -> result::Layout { + pub(crate) fn compute_layout(&self, renderer: &Renderer) -> result::Layout { let node = self.widget.node(renderer); node.0.compute_layout(geometry::Size::undefined()).unwrap() diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 44aaf9e3..5675076d 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -98,7 +98,7 @@ where pub fn build>>( root: E, cache: Cache, - renderer: &mut Renderer, + renderer: &Renderer, ) -> Self { let root = root.into(); diff --git a/winit/src/application.rs b/winit/src/application.rs index 2ea52e5f..8345a5ed 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -41,7 +41,7 @@ pub trait Application { let user_interface = UserInterface::build( document(&mut self, size), Cache::default(), - &mut renderer, + &renderer, ); let mut primitive = user_interface.draw(&mut renderer); @@ -61,7 +61,7 @@ pub trait Application { let mut user_interface = UserInterface::build( document(&mut self, size), cache.take().unwrap(), - &mut renderer, + &renderer, ); let messages = user_interface.update(events.drain(..)); @@ -84,7 +84,7 @@ pub trait Application { let user_interface = UserInterface::build( document(&mut self, size), temp_cache, - &mut renderer, + &renderer, ); primitive = user_interface.draw(&mut renderer); -- cgit From e74f1179769cc4dc3e91cb0b5794526b3a8c0dcd Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 12 Oct 2019 02:32:16 +0200 Subject: Add a slight shadow to buttons for feedback --- examples/tour.rs | 16 ++++++++-------- wgpu/src/renderer/button.rs | 30 ++++++++++++++++++++++++++++-- 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/examples/tour.rs b/examples/tour.rs index 6ce1e9f1..f77dc241 100644 --- a/examples/tour.rs +++ b/examples/tour.rs @@ -556,8 +556,8 @@ fn button<'a, Message>( .color(Color::WHITE) .horizontal_alignment(HorizontalAlignment::Center), ) - .padding(10) - .border_radius(10) + .padding(12) + .border_radius(12) } fn primary_button<'a, Message>( @@ -565,9 +565,9 @@ fn primary_button<'a, Message>( label: &str, ) -> Button<'a, Message> { button(state, label).background(Background::Color(Color { - r: 0.3, - g: 0.3, - b: 0.8, + r: 0.11, + g: 0.42, + b: 0.87, a: 1.0, })) } @@ -577,9 +577,9 @@ fn secondary_button<'a, Message>( label: &str, ) -> Button<'a, Message> { button(state, label).background(Background::Color(Color { - r: 0.8, - g: 0.8, - b: 0.8, + r: 0.4, + g: 0.4, + b: 0.4, a: 1.0, })) } diff --git a/wgpu/src/renderer/button.rs b/wgpu/src/renderer/button.rs index 275c870f..aa48da93 100644 --- a/wgpu/src/renderer/button.rs +++ b/wgpu/src/renderer/button.rs @@ -1,7 +1,7 @@ use crate::{Primitive, Renderer}; use iced_native::{ button, Align, Background, Button, Color, Layout, Length, MouseCursor, - Node, Point, Style, + Node, Point, Rectangle, Style, }; impl button::Renderer for Renderer { @@ -30,9 +30,35 @@ impl button::Renderer for Renderer { cursor_position, ); + let is_hover = bounds.contains(cursor_position); + + // TODO: Render proper shadows + // TODO: Make hovering and pressed styles configurable + let shadow_offset = if button.state.is_pressed { + 0.0 + } else if is_hover { + 2.0 + } else { + 1.0 + }; + ( Primitive::Group { primitives: vec![ + Primitive::Quad { + bounds: Rectangle { + x: bounds.x + 1.0, + y: bounds.y + shadow_offset, + ..bounds + }, + background: Background::Color(Color { + r: 0.0, + b: 0.0, + g: 0.0, + a: 0.5, + }), + border_radius: button.border_radius, + }, Primitive::Quad { bounds, background: button.background.unwrap_or( @@ -48,7 +74,7 @@ impl button::Renderer for Renderer { content, ], }, - if bounds.contains(cursor_position) { + if is_hover { MouseCursor::Pointer } else { MouseCursor::OutOfBounds -- cgit From afacb35f9bf87ae10f59091b18b001a4c114a589 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 12 Oct 2019 05:07:00 +0200 Subject: Draw sliders in `iced_wgpu` --- wgpu/src/renderer/slider.rs | 125 +++++++++++++++++++++++++++++++++++++++--- wgpu/src/shader/quad.frag | 2 +- wgpu/src/shader/quad.frag.spv | Bin 3196 -> 3212 bytes 3 files changed, 119 insertions(+), 8 deletions(-) diff --git a/wgpu/src/renderer/slider.rs b/wgpu/src/renderer/slider.rs index d1a30244..4ae3abc4 100644 --- a/wgpu/src/renderer/slider.rs +++ b/wgpu/src/renderer/slider.rs @@ -1,17 +1,128 @@ use crate::{Primitive, Renderer}; -use iced_native::{slider, Layout, MouseCursor, Node, Point, Slider, Style}; +use iced_native::{ + slider, Background, Color, Layout, Length, MouseCursor, Node, Point, + Rectangle, Slider, Style, +}; + +const HANDLE_WIDTH: f32 = 8.0; +const HANDLE_HEIGHT: f32 = 22.0; impl slider::Renderer for Renderer { - fn node(&self, _slider: &Slider) -> Node { - Node::new(Style::default()) + fn node(&self, slider: &Slider) -> Node { + let style = Style::default() + .width(slider.width) + .height(Length::Units(HANDLE_HEIGHT as u16)) + .min_width(Length::Units(100)); + + Node::new(style) } fn draw( &mut self, - _slider: &Slider, - _layout: Layout<'_>, - _cursor_position: Point, + slider: &Slider, + layout: Layout<'_>, + cursor_position: Point, ) -> Self::Output { - (Primitive::None, MouseCursor::OutOfBounds) + let bounds = layout.bounds(); + + let is_mouse_over = bounds.contains(cursor_position); + + let rail_y = bounds.y + (bounds.height / 2.0).round(); + + let (rail_top, rail_bottom) = ( + Primitive::Quad { + bounds: Rectangle { + x: bounds.x, + y: rail_y, + width: bounds.width, + height: 2.0, + }, + background: Background::Color(Color { + r: 0.6, + g: 0.6, + b: 0.6, + a: 1.0, + }), + border_radius: 0, + }, + Primitive::Quad { + bounds: Rectangle { + x: bounds.x, + y: rail_y + 2.0, + width: bounds.width, + height: 2.0, + }, + background: Background::Color(Color::WHITE), + border_radius: 0, + }, + ); + + let (range_start, range_end) = slider.range.clone().into_inner(); + + let handle_offset = (bounds.width - HANDLE_WIDTH) + * ((slider.value - range_start) + / (range_end - range_start).max(1.0)); + + let (handle_border, handle) = ( + Primitive::Quad { + bounds: Rectangle { + x: bounds.x + handle_offset.round() - 1.0, + y: rail_y - HANDLE_HEIGHT / 2.0 - 1.0, + width: HANDLE_WIDTH + 2.0, + height: HANDLE_HEIGHT + 2.0, + }, + background: Background::Color(Color { + r: 0.6, + g: 0.6, + b: 0.6, + a: 1.0, + }), + border_radius: 5, + }, + Primitive::Quad { + bounds: Rectangle { + x: bounds.x + handle_offset.round(), + y: rail_y - HANDLE_HEIGHT / 2.0, + width: HANDLE_WIDTH, + height: HANDLE_HEIGHT, + }, + background: Background::Color(if slider.state.is_dragging() { + Color { + r: 0.85, + g: 0.85, + b: 0.85, + a: 1.0, + } + } else if is_mouse_over { + Color { + r: 0.9, + g: 0.9, + b: 0.9, + a: 1.0, + } + } else { + Color { + r: 0.95, + g: 0.95, + b: 0.95, + a: 1.0, + } + }), + border_radius: 4, + }, + ); + + ( + Primitive::Group { + primitives: vec![rail_top, rail_bottom, handle_border, handle], + }, + if slider.state.is_dragging() { + MouseCursor::Grabbing + } else if is_mouse_over { + MouseCursor::Grab + } else { + MouseCursor::OutOfBounds + }, + ) } } diff --git a/wgpu/src/shader/quad.frag b/wgpu/src/shader/quad.frag index 987744db..849f581e 100644 --- a/wgpu/src/shader/quad.frag +++ b/wgpu/src/shader/quad.frag @@ -30,7 +30,7 @@ void main() { float radius_alpha = 1.0; if(v_BorderRadius > 0.0) { - radius_alpha = rounded(gl_FragCoord.xy, v_Pos, v_Scale, v_BorderRadius, 1.0); + radius_alpha = rounded(gl_FragCoord.xy, v_Pos, v_Scale, v_BorderRadius, 0.5); } o_Color = vec4(v_Color.xyz, v_Color.w * radius_alpha); diff --git a/wgpu/src/shader/quad.frag.spv b/wgpu/src/shader/quad.frag.spv index 063287b3..71b91b44 100644 Binary files a/wgpu/src/shader/quad.frag.spv and b/wgpu/src/shader/quad.frag.spv differ -- cgit From a444819799345d12ab74b09fc8c82ba360b9eeeb Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 12 Oct 2019 05:07:29 +0200 Subject: Fix button shadow feedback in `iced_wgpu` --- wgpu/src/renderer/button.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/wgpu/src/renderer/button.rs b/wgpu/src/renderer/button.rs index aa48da93..ad2186d6 100644 --- a/wgpu/src/renderer/button.rs +++ b/wgpu/src/renderer/button.rs @@ -30,14 +30,16 @@ impl button::Renderer for Renderer { cursor_position, ); - let is_hover = bounds.contains(cursor_position); + let is_mouse_over = bounds.contains(cursor_position); // TODO: Render proper shadows // TODO: Make hovering and pressed styles configurable - let shadow_offset = if button.state.is_pressed { - 0.0 - } else if is_hover { - 2.0 + let shadow_offset = if is_mouse_over { + if button.state.is_pressed { + 0.0 + } else { + 2.0 + } } else { 1.0 }; @@ -74,7 +76,7 @@ impl button::Renderer for Renderer { content, ], }, - if is_hover { + if is_mouse_over { MouseCursor::Pointer } else { MouseCursor::OutOfBounds -- cgit From 8c3dabb5a1640ed77c35f895ca866262bb4f885c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 12 Oct 2019 18:48:35 +0200 Subject: Draw radio buttons in `iced_wgpu` --- wgpu/src/renderer/radio.rs | 106 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 99 insertions(+), 7 deletions(-) diff --git a/wgpu/src/renderer/radio.rs b/wgpu/src/renderer/radio.rs index 712a7104..97b4f70e 100644 --- a/wgpu/src/renderer/radio.rs +++ b/wgpu/src/renderer/radio.rs @@ -1,17 +1,109 @@ use crate::{Primitive, Renderer}; -use iced_native::{radio, Layout, MouseCursor, Node, Point, Radio, Style}; +use iced_native::{ + radio, text, Align, Background, Color, Column, Layout, Length, MouseCursor, + Node, Point, Radio, Rectangle, Row, Text, Widget, +}; + +const SIZE: f32 = 28.0; +const DOT_SIZE: f32 = SIZE / 2.0; impl radio::Renderer for Renderer { - fn node(&self, _checkbox: &Radio) -> Node { - Node::new(Style::default()) + fn node(&self, radio: &Radio) -> Node { + Row::<(), Self>::new() + .spacing(15) + .align_items(Align::Center) + .push( + Column::new() + .width(Length::Units(SIZE as u16)) + .height(Length::Units(SIZE as u16)), + ) + .push(Text::new(&radio.label)) + .node(self) } fn draw( &mut self, - _radio: &Radio, - _layout: Layout<'_>, - _cursor_position: Point, + radio: &Radio, + layout: Layout<'_>, + cursor_position: Point, ) -> Self::Output { - (Primitive::None, MouseCursor::OutOfBounds) + let bounds = layout.bounds(); + let mut children = layout.children(); + + let radio_bounds = children.next().unwrap().bounds(); + let label_layout = children.next().unwrap(); + + let (label, _) = + text::Renderer::draw(self, &Text::new(&radio.label), label_layout); + + let is_mouse_over = bounds.contains(cursor_position); + + let (radio_border, radio_box) = ( + Primitive::Quad { + bounds: radio_bounds, + background: Background::Color(Color { + r: 0.6, + g: 0.6, + b: 0.6, + a: 1.0, + }), + border_radius: (SIZE / 2.0) as u16, + }, + Primitive::Quad { + bounds: Rectangle { + x: radio_bounds.x + 1.0, + y: radio_bounds.y + 1.0, + width: radio_bounds.width - 2.0, + height: radio_bounds.height - 2.0, + }, + background: Background::Color(if is_mouse_over { + Color { + r: 0.90, + g: 0.90, + b: 0.90, + a: 1.0, + } + } else { + Color { + r: 0.95, + g: 0.95, + b: 0.95, + a: 1.0, + } + }), + border_radius: (SIZE / 2.0 - 1.0) as u16, + }, + ); + + ( + Primitive::Group { + primitives: if radio.is_selected { + let radio_circle = Primitive::Quad { + bounds: Rectangle { + x: radio_bounds.x + DOT_SIZE / 2.0, + y: radio_bounds.y + DOT_SIZE / 2.0, + width: radio_bounds.width - DOT_SIZE, + height: radio_bounds.height - DOT_SIZE, + }, + background: Background::Color(Color { + r: 0.30, + g: 0.30, + b: 0.30, + a: 1.0, + }), + border_radius: (DOT_SIZE / 2.0) as u16, + }; + + vec![radio_border, radio_box, radio_circle, label] + } else { + vec![radio_border, radio_box, label] + }, + }, + if is_mouse_over { + MouseCursor::Pointer + } else { + MouseCursor::OutOfBounds + }, + ) } } -- cgit From ccc463a7c051b1096bc8a9f17ec64c2912a11247 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 13 Oct 2019 18:10:12 +0200 Subject: Draw checkbox in `iced_wgpu` --- native/src/widget/checkbox.rs | 4 +- wgpu/src/renderer/checkbox.rs | 102 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 95 insertions(+), 11 deletions(-) diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs index cb2baedd..5393417e 100644 --- a/native/src/widget/checkbox.rs +++ b/native/src/widget/checkbox.rs @@ -26,9 +26,7 @@ where button: mouse::Button::Left, state: ButtonState::Pressed, }) => { - let mouse_over = layout - .children() - .any(|child| child.bounds().contains(cursor_position)); + let mouse_over = layout.bounds().contains(cursor_position); if mouse_over { messages.push((self.on_toggle)(!self.is_checked)); diff --git a/wgpu/src/renderer/checkbox.rs b/wgpu/src/renderer/checkbox.rs index 7b06d397..fd3f08b1 100644 --- a/wgpu/src/renderer/checkbox.rs +++ b/wgpu/src/renderer/checkbox.rs @@ -1,20 +1,106 @@ use crate::{Primitive, Renderer}; use iced_native::{ - checkbox, Checkbox, Layout, MouseCursor, Node, Point, Style, + checkbox, text, text::HorizontalAlignment, text::VerticalAlignment, Align, + Background, Checkbox, Color, Column, Layout, Length, MouseCursor, Node, + Point, Rectangle, Row, Text, Widget, }; +const SIZE: f32 = 28.0; + impl checkbox::Renderer for Renderer { - fn node(&self, _checkbox: &Checkbox) -> Node { - Node::new(Style::default()) + fn node(&self, checkbox: &Checkbox) -> Node { + Row::<(), Self>::new() + .spacing(15) + .align_items(Align::Center) + .push( + Column::new() + .width(Length::Units(SIZE as u16)) + .height(Length::Units(SIZE as u16)), + ) + .push(Text::new(&checkbox.label)) + .node(self) } fn draw( &mut self, - _checkbox: &Checkbox, - _layout: Layout<'_>, - _cursor_position: Point, + checkbox: &Checkbox, + layout: Layout<'_>, + cursor_position: Point, ) -> Self::Output { - // TODO - (Primitive::None, MouseCursor::OutOfBounds) + let bounds = layout.bounds(); + let mut children = layout.children(); + + let checkbox_layout = children.next().unwrap(); + let label_layout = children.next().unwrap(); + let checkbox_bounds = checkbox_layout.bounds(); + + let (label, _) = text::Renderer::draw( + self, + &Text::new(&checkbox.label), + label_layout, + ); + + let is_mouse_over = bounds.contains(cursor_position); + + let (checkbox_border, checkbox_box) = ( + Primitive::Quad { + bounds: checkbox_bounds, + background: Background::Color(Color { + r: 0.6, + g: 0.6, + b: 0.6, + a: 1.0, + }), + border_radius: 6, + }, + Primitive::Quad { + bounds: Rectangle { + x: checkbox_bounds.x + 1.0, + y: checkbox_bounds.y + 1.0, + width: checkbox_bounds.width - 2.0, + height: checkbox_bounds.height - 2.0, + }, + background: Background::Color(if is_mouse_over { + Color { + r: 0.90, + g: 0.90, + b: 0.90, + a: 1.0, + } + } else { + Color { + r: 0.95, + g: 0.95, + b: 0.95, + a: 1.0, + } + }), + border_radius: 6, + }, + ); + + ( + Primitive::Group { + primitives: if checkbox.is_checked { + // TODO: Draw an actual icon + let (check, _) = text::Renderer::draw( + self, + &Text::new("X") + .horizontal_alignment(HorizontalAlignment::Center) + .vertical_alignment(VerticalAlignment::Center), + checkbox_layout, + ); + + vec![checkbox_border, checkbox_box, check, label] + } else { + vec![checkbox_border, checkbox_box, label] + }, + }, + if is_mouse_over { + MouseCursor::Pointer + } else { + MouseCursor::OutOfBounds + }, + ) } } -- cgit From 734e80dea6cd39923aaa0d2f27c7368c9cbc5d62 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 13 Oct 2019 18:22:26 +0200 Subject: Draft `Debugger` implementation in `iced_wgpu` --- wgpu/src/renderer.rs | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index 8d54b2c7..8930e9df 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -272,9 +272,36 @@ impl Debugger for Renderer { widget: &dyn Widget, layout: Layout<'_>, cursor_position: Point, - _color: Color, + color: Color, ) -> Self::Output { - // TODO: Include a bordered box to display layout bounds - widget.draw(self, layout, cursor_position) + let mut primitives = Vec::new(); + let (primitive, cursor) = widget.draw(self, layout, cursor_position); + + explain_layout(layout, color, &mut primitives); + primitives.push(primitive); + + (Primitive::Group { primitives }, cursor) + } +} + +fn explain_layout( + layout: Layout, + color: Color, + primitives: &mut Vec, +) { + // TODO: Draw borders instead + primitives.push(Primitive::Quad { + bounds: layout.bounds(), + background: Background::Color(Color { + r: 0.0, + g: 0.0, + b: 0.0, + a: 0.05, + }), + border_radius: 0, + }); + + for child in layout.children() { + explain_layout(child, color, primitives); } } -- cgit From 665b1d4e1b926909e6a4ccd96226b9cefe2a1ad0 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 13 Oct 2019 18:51:39 +0200 Subject: Update examples `README` --- examples/README.md | 80 +++++++++++++++++++----------------------------------- 1 file changed, 28 insertions(+), 52 deletions(-) diff --git a/examples/README.md b/examples/README.md index 4e83faf1..fe69b34a 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,78 +1,54 @@ # Examples - __Iced moves fast and the `master` branch can contain breaking changes!__ If you want to learn about a specific release, check out [the release list]. [the release list]: https://github.com/hecrj/iced/releases -## [Tour](tour) - -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. +## [Tour](tour.rs) +A simple UI tour showcasing different widgets that can be built using Iced. 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]. +[![Tour - Iced][gui_gif]][gui_gfycat] -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). +[gui_gif]: https://thumbs.gfycat.com/VeneratedSourAurochs-small.gif +[gui_gfycat]: https://gfycat.com/veneratedsouraurochs -The implementation consists of different modules: - - __[`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 native [`renderer`]. - - __[`lib`]__ exposes the [`tour`] types and conditionally implements the - WebAssembly entrypoint in the [`web`] module. +On native, the example uses: + - [`iced_winit`], as a bridge between [`iced_native`] and [`winit`]. + - [`iced_wgpu`], a WIP Iced renderer built on top of [`wgpu`] and supporting + Vulkan, Metal, D3D11, and D3D12 (OpenGL and WebGL soon!). -The conditional compilation awkwardness from targetting both native and web -platforms should be handled seamlessly by the `iced` crate in the near future! +The web version uses [`iced_web`], which is still a work in progress. In +particular, the styling of elements is not finished yet (text color, alignment, +sizing, etc). -If you want to run it as a native app: +The __[`tour`]__ file contains all the code of the example! All the +cross-platform GUI is defined in terms of __state__, __messages__, +__update logic__ and __view logic__. -``` -cd examples/tour -cargo run -``` +[`tour`]: tour.rs +[`iced_winit`]: ../winit +[`iced_native`]: ../native +[`iced_wgpu`]: ../wgpu +[`iced_web`]: ../web +[`winit`]: https://github.com/rust-windowing/winit +[`wgpu`]: https://github.com/gfx-rs/wgpu-rs -If you want to run it on web, you will need [`wasm-pack`]: +#### Running the native version +Simply use [Cargo](https://doc.rust-lang.org/cargo/reference/manifest.html#examples) +to run the example: ``` -cd examples/tour -wasm-pack build --target web +cargo run --example tour ``` -Then, simply serve the directory with any HTTP server. For instance: - +#### Running the web version ``` -python3 -m http.server +TODO ``` -[![Tour - Iced][gui_gif]][gui_gfycat] - -[`ggez`]: https://github.com/ggez/ggez -[`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 -[gui_gif]: https://thumbs.gfycat.com/VeneratedSourAurochs-small.gif -[gui_gfycat]: https://gfycat.com/veneratedsouraurochs - ## [Coffee] -- cgit From 7916e6c92e8cc4620a1e964c2fd4832cbca8fde5 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 13 Oct 2019 18:54:02 +0200 Subject: Fix integration CI workflow The `iced_tour` crate has become a simple example. --- .github/workflows/integration.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index eb54d170..54ca2e33 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -24,6 +24,3 @@ jobs: - uses: actions/checkout@master - name: Run tests run: cargo test --verbose --all --all-features - - name: Build tour for WebAssembly - if: matrix.targets == 'wasm32-unknown-unknown' - run: cargo build --verbose --package iced_tour --lib --target wasm32-unknown-unknown -- cgit From 523736f08b1d452e4d3405a68b267c6f44adc22b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 13 Oct 2019 18:57:34 +0200 Subject: Fix `wgpu_glyph` dependency in `iced_wgpu` --- wgpu/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index dbc1ddb9..781abec2 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -10,6 +10,6 @@ repository = "https://github.com/hecrj/iced" [dependencies] iced_native = { version = "0.1.0-alpha", path = "../native" } wgpu = { version = "0.3", git = "https://github.com/gfx-rs/wgpu-rs", rev = "cb25914b95b58fee0dc139b400867e7a731d98f4" } -wgpu_glyph = { version = "0.4", git = "https://github.com/hecrj/wgpu_glyph", branch = "improvement/update-wgpu" } +wgpu_glyph = { version = "0.4", git = "https://github.com/hecrj/wgpu_glyph", rev = "48daa98f5f785963838b4345e86ac40eac095ba9" } raw-window-handle = "0.1" log = "0.4" -- cgit From f8a232c8af4c50557fbf0c2e0b2ba46fb63f6adc Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 22 Oct 2019 23:20:24 +0200 Subject: Remove generic handle in `Image` For now, we will simply assume images will be loaded from a given path. --- core/src/widget/image.rs | 28 +++++++++------------------- native/src/widget/image.rs | 18 ++++++++---------- web/src/widget/image.rs | 10 +++++----- wgpu/src/renderer/image.rs | 10 +++------- 4 files changed, 25 insertions(+), 41 deletions(-) diff --git a/core/src/widget/image.rs b/core/src/widget/image.rs index 110ba99a..6e410dce 100644 --- a/core/src/widget/image.rs +++ b/core/src/widget/image.rs @@ -9,12 +9,12 @@ use crate::{Align, Length, Rectangle}; /// ``` /// use iced_core::Image; /// -/// # let my_handle = String::from("some_handle"); -/// let image = Image::new(my_handle); +/// let image = Image::new("resources/ferris.png"); /// ``` -pub struct Image { - /// The image handle - pub handle: I, +#[derive(Debug)] +pub struct Image { + /// The image path + pub path: String, /// The part of the image to show pub clip: Option>, @@ -28,23 +28,13 @@ pub struct Image { pub align_self: Option, } -impl std::fmt::Debug for Image { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Image") - .field("clip", &self.clip) - .field("width", &self.width) - .field("height", &self.height) - .finish() - } -} - -impl Image { - /// Creates a new [`Image`] with given image handle. +impl Image { + /// Creates a new [`Image`] with the given path. /// /// [`Image`]: struct.Image.html - pub fn new(handle: I) -> Self { + pub fn new>(path: T) -> Self { Image { - handle, + path: path.into(), clip: None, width: Length::Shrink, height: Length::Shrink, diff --git a/native/src/widget/image.rs b/native/src/widget/image.rs index 178dd709..6255a7b5 100644 --- a/native/src/widget/image.rs +++ b/native/src/widget/image.rs @@ -6,10 +6,9 @@ use std::hash::Hash; pub use iced_core::Image; -impl Widget for Image +impl Widget for Image where - Renderer: self::Renderer, - I: Clone, + Renderer: self::Renderer, { fn node(&self, renderer: &Renderer) -> Node { renderer.node(&self) @@ -38,27 +37,26 @@ where /// /// [`Image`]: struct.Image.html /// [renderer]: ../../renderer/index.html -pub trait Renderer: crate::Renderer { +pub trait Renderer: crate::Renderer { /// Creates a [`Node`] for the provided [`Image`]. /// /// You should probably keep the original aspect ratio, if possible. /// /// [`Node`]: ../../struct.Node.html /// [`Image`]: struct.Image.html - fn node(&self, image: &Image) -> Node; + fn node(&self, image: &Image) -> Node; /// Draws an [`Image`]. /// /// [`Image`]: struct.Image.html - fn draw(&mut self, image: &Image, layout: Layout<'_>) -> Self::Output; + fn draw(&mut self, image: &Image, layout: Layout<'_>) -> Self::Output; } -impl<'a, I, Message, Renderer> From> for Element<'a, Message, Renderer> +impl<'a, Message, Renderer> From for Element<'a, Message, Renderer> where - Renderer: self::Renderer, - I: Clone + 'a, + Renderer: self::Renderer, { - fn from(image: Image) -> Element<'a, Message, Renderer> { + fn from(image: Image) -> Element<'a, Message, Renderer> { Element::new(image) } } diff --git a/web/src/widget/image.rs b/web/src/widget/image.rs index fd4ff0df..bd3e5daf 100644 --- a/web/src/widget/image.rs +++ b/web/src/widget/image.rs @@ -2,9 +2,9 @@ use crate::{Bus, Element, Length, Widget}; use dodrio::bumpalo; -pub type Image<'a> = iced_core::Image<&'a str>; +pub use iced_core::Image; -impl<'a, Message> Widget for Image<'a> { +impl Widget for Image { fn node<'b>( &self, bump: &'b bumpalo::Bump, @@ -12,7 +12,7 @@ impl<'a, Message> Widget for Image<'a> { ) -> dodrio::Node<'b> { use dodrio::builder::*; - let src = bumpalo::format!(in bump, "{}", self.handle); + let src = bumpalo::format!(in bump, "{}", self.path); let mut image = img(bump).attr("src", src.into_bump_str()); @@ -35,8 +35,8 @@ impl<'a, Message> Widget for Image<'a> { } } -impl<'a, Message> From> for Element<'a, Message> { - fn from(image: Image<'a>) -> Element<'a, Message> { +impl<'a, Message> From for Element<'a, Message> { + fn from(image: Image) -> Element<'a, Message> { Element::new(image) } } diff --git a/wgpu/src/renderer/image.rs b/wgpu/src/renderer/image.rs index a29a3d49..85ac3ad5 100644 --- a/wgpu/src/renderer/image.rs +++ b/wgpu/src/renderer/image.rs @@ -1,16 +1,12 @@ use crate::{Primitive, Renderer}; use iced_native::{image, Image, Layout, MouseCursor, Node, Style}; -impl image::Renderer<&str> for Renderer { - fn node(&self, _image: &Image<&str>) -> Node { +impl image::Renderer for Renderer { + fn node(&self, _image: &Image) -> Node { Node::new(Style::default()) } - fn draw( - &mut self, - _image: &Image<&str>, - _layout: Layout<'_>, - ) -> Self::Output { + fn draw(&mut self, _image: &Image, _layout: Layout<'_>) -> Self::Output { (Primitive::None, MouseCursor::OutOfBounds) } } -- cgit From 38b6c84e7761c049b17d178deb9c866386a53946 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 23 Oct 2019 01:21:23 +0200 Subject: Implement basic image rendering in `iced_wgpu` --- examples/tour.rs | 17 +- wgpu/Cargo.toml | 1 + wgpu/src/image.rs | 438 +++++++++++++++++++++++++++++++++++++++++ wgpu/src/lib.rs | 2 + wgpu/src/primitive.rs | 4 + wgpu/src/renderer.rs | 24 ++- wgpu/src/renderer/image.rs | 31 ++- wgpu/src/shader/image.frag | 12 ++ wgpu/src/shader/image.frag.spv | Bin 0 -> 684 bytes wgpu/src/shader/image.vert | 24 +++ wgpu/src/shader/image.vert.spv | Bin 0 -> 2136 bytes 11 files changed, 543 insertions(+), 10 deletions(-) create mode 100644 wgpu/src/image.rs create mode 100644 wgpu/src/shader/image.frag create mode 100644 wgpu/src/shader/image.frag.spv create mode 100644 wgpu/src/shader/image.vert create mode 100644 wgpu/src/shader/image.vert.spv diff --git a/examples/tour.rs b/examples/tour.rs index f77dc241..06be4766 100644 --- a/examples/tour.rs +++ b/examples/tour.rs @@ -74,7 +74,7 @@ impl Application for Tour { } let element: Element<_> = Column::new() - .max_width(Length::Units(500)) + .max_width(Length::Units(540)) .spacing(20) .padding(20) .push(steps.view(self.debug).map(Message::StepMessage)) @@ -503,9 +503,18 @@ impl<'a> Step { Self::container("Image") .push(Text::new("An image that tries to keep its aspect ratio.")) .push( - Image::new("resources/ferris.png") - .width(Length::Units(width)) - .align_self(Align::Center), + // This should go away once we unify resource loading on native + // platforms + if cfg!(target_arch = "wasm32") { + Image::new("resources/ferris.png") + } else { + Image::new(format!( + "{}/examples/resources/ferris.png", + env!("CARGO_MANIFEST_DIR") + )) + } + .width(Length::Units(width)) + .align_self(Align::Center), ) .push(Slider::new( slider, diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 781abec2..cac5e113 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -12,4 +12,5 @@ iced_native = { version = "0.1.0-alpha", path = "../native" } wgpu = { version = "0.3", git = "https://github.com/gfx-rs/wgpu-rs", rev = "cb25914b95b58fee0dc139b400867e7a731d98f4" } wgpu_glyph = { version = "0.4", git = "https://github.com/hecrj/wgpu_glyph", rev = "48daa98f5f785963838b4345e86ac40eac095ba9" } raw-window-handle = "0.1" +image = "0.22" log = "0.4" diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs new file mode 100644 index 00000000..18889faf --- /dev/null +++ b/wgpu/src/image.rs @@ -0,0 +1,438 @@ +use crate::Transformation; + +use std::cell::RefCell; +use std::collections::HashMap; +use std::mem; +use std::rc::Rc; + +pub struct Pipeline { + cache: RefCell>, + + pipeline: wgpu::RenderPipeline, + transform: wgpu::Buffer, + vertices: wgpu::Buffer, + indices: wgpu::Buffer, + instances: wgpu::Buffer, + constants: wgpu::BindGroup, + texture_layout: wgpu::BindGroupLayout, +} + +impl Pipeline { + pub fn new(device: &wgpu::Device) -> Self { + let sampler = device.create_sampler(&wgpu::SamplerDescriptor { + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Linear, + mipmap_filter: wgpu::FilterMode::Linear, + lod_min_clamp: -100.0, + lod_max_clamp: 100.0, + compare_function: wgpu::CompareFunction::Always, + }); + + let constant_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + bindings: &[ + wgpu::BindGroupLayoutBinding { + binding: 0, + visibility: wgpu::ShaderStage::VERTEX, + ty: wgpu::BindingType::UniformBuffer { dynamic: false }, + }, + wgpu::BindGroupLayoutBinding { + binding: 1, + visibility: wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Sampler, + }, + ], + }); + + let matrix: [f32; 16] = Transformation::identity().into(); + + let transform_buffer = device + .create_buffer_mapped( + 16, + wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST, + ) + .fill_from_slice(&matrix[..]); + + let constant_bind_group = + device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &constant_layout, + bindings: &[ + wgpu::Binding { + binding: 0, + resource: wgpu::BindingResource::Buffer { + buffer: &transform_buffer, + range: 0..64, + }, + }, + wgpu::Binding { + binding: 1, + resource: wgpu::BindingResource::Sampler(&sampler), + }, + ], + }); + + let texture_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + bindings: &[wgpu::BindGroupLayoutBinding { + binding: 0, + visibility: wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::SampledTexture { + multisampled: false, + dimension: wgpu::TextureViewDimension::D2, + }, + }], + }); + + let layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + bind_group_layouts: &[&constant_layout, &texture_layout], + }); + + let vs = include_bytes!("shader/image.vert.spv"); + let vs_module = device.create_shader_module( + &wgpu::read_spirv(std::io::Cursor::new(&vs[..])) + .expect("Read image vertex shader as SPIR-V"), + ); + + let fs = include_bytes!("shader/image.frag.spv"); + let fs_module = device.create_shader_module( + &wgpu::read_spirv(std::io::Cursor::new(&fs[..])) + .expect("Read image fragment shader as SPIR-V"), + ); + + let pipeline = + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + layout: &layout, + vertex_stage: wgpu::ProgrammableStageDescriptor { + module: &vs_module, + entry_point: "main", + }, + fragment_stage: Some(wgpu::ProgrammableStageDescriptor { + module: &fs_module, + entry_point: "main", + }), + rasterization_state: Some(wgpu::RasterizationStateDescriptor { + front_face: wgpu::FrontFace::Cw, + cull_mode: wgpu::CullMode::None, + depth_bias: 0, + depth_bias_slope_scale: 0.0, + depth_bias_clamp: 0.0, + }), + primitive_topology: wgpu::PrimitiveTopology::TriangleList, + color_states: &[wgpu::ColorStateDescriptor { + format: wgpu::TextureFormat::Bgra8UnormSrgb, + color_blend: wgpu::BlendDescriptor { + src_factor: wgpu::BlendFactor::SrcAlpha, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + alpha_blend: wgpu::BlendDescriptor { + src_factor: wgpu::BlendFactor::One, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + write_mask: wgpu::ColorWrite::ALL, + }], + depth_stencil_state: None, + index_format: wgpu::IndexFormat::Uint16, + vertex_buffers: &[ + wgpu::VertexBufferDescriptor { + stride: mem::size_of::() as u64, + step_mode: wgpu::InputStepMode::Vertex, + attributes: &[wgpu::VertexAttributeDescriptor { + shader_location: 0, + format: wgpu::VertexFormat::Float2, + offset: 0, + }], + }, + wgpu::VertexBufferDescriptor { + stride: mem::size_of::() as u64, + step_mode: wgpu::InputStepMode::Instance, + attributes: &[ + wgpu::VertexAttributeDescriptor { + shader_location: 1, + format: wgpu::VertexFormat::Float2, + offset: 0, + }, + wgpu::VertexAttributeDescriptor { + shader_location: 2, + format: wgpu::VertexFormat::Float2, + offset: 4 * 2, + }, + ], + }, + ], + sample_count: 1, + sample_mask: !0, + alpha_to_coverage_enabled: false, + }); + + let vertices = device + .create_buffer_mapped(QUAD_VERTS.len(), wgpu::BufferUsage::VERTEX) + .fill_from_slice(&QUAD_VERTS); + + let indices = device + .create_buffer_mapped(QUAD_INDICES.len(), wgpu::BufferUsage::INDEX) + .fill_from_slice(&QUAD_INDICES); + + let instances = device.create_buffer(&wgpu::BufferDescriptor { + size: mem::size_of::() as u64, + usage: wgpu::BufferUsage::VERTEX | wgpu::BufferUsage::COPY_DST, + }); + + Pipeline { + cache: RefCell::new(HashMap::new()), + + pipeline, + transform: transform_buffer, + vertices, + indices, + instances, + constants: constant_bind_group, + texture_layout, + } + } + + pub fn dimensions(&self, path: &str) -> (u32, u32) { + self.load(path); + + self.cache.borrow().get(path).unwrap().dimensions() + } + + fn load(&self, path: &str) { + if !self.cache.borrow().contains_key(path) { + let image = image::open(path).expect("Load image").to_bgra(); + + self.cache + .borrow_mut() + .insert(path.to_string(), Memory::Host { image }); + } + } + + pub fn draw( + &mut self, + device: &mut wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + instances: &[Image], + transformation: Transformation, + target: &wgpu::TextureView, + ) { + let matrix: [f32; 16] = transformation.into(); + + let transform_buffer = device + .create_buffer_mapped(16, wgpu::BufferUsage::COPY_SRC) + .fill_from_slice(&matrix[..]); + + encoder.copy_buffer_to_buffer( + &transform_buffer, + 0, + &self.transform, + 0, + 16 * 4, + ); + + // TODO: Batch draw calls using a texture atlas + // Guillotière[1] by @nical can help us a lot here. + // + // [1]: https://github.com/nical/guillotiere + for image in instances { + self.load(&image.path); + + let texture = self + .cache + .borrow_mut() + .get_mut(&image.path) + .unwrap() + .upload(device, encoder, &self.texture_layout); + + let instance_buffer = device + .create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC) + .fill_from_slice(&[Instance { + position: image.position, + scale: image.scale, + }]); + + encoder.copy_buffer_to_buffer( + &instance_buffer, + 0, + &self.instances, + 0, + mem::size_of::() as u64, + ); + + { + let mut render_pass = + encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + color_attachments: &[ + wgpu::RenderPassColorAttachmentDescriptor { + attachment: target, + resolve_target: None, + load_op: wgpu::LoadOp::Load, + store_op: wgpu::StoreOp::Store, + clear_color: wgpu::Color { + r: 0.0, + g: 0.0, + b: 0.0, + a: 0.0, + }, + }, + ], + depth_stencil_attachment: None, + }); + + render_pass.set_pipeline(&self.pipeline); + render_pass.set_bind_group(0, &self.constants, &[]); + render_pass.set_bind_group(1, &texture, &[]); + render_pass.set_index_buffer(&self.indices, 0); + render_pass.set_vertex_buffers( + 0, + &[(&self.vertices, 0), (&self.instances, 0)], + ); + + render_pass.draw_indexed( + 0..QUAD_INDICES.len() as u32, + 0, + 0..1 as u32, + ); + } + } + } +} + +enum Memory { + Host { + image: image::ImageBuffer, Vec>, + }, + Device { + bind_group: Rc, + width: u32, + height: u32, + }, +} + +impl Memory { + fn dimensions(&self) -> (u32, u32) { + match self { + Memory::Host { image } => image.dimensions(), + Memory::Device { width, height, .. } => (*width, *height), + } + } + + fn upload( + &mut self, + device: &wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + texture_layout: &wgpu::BindGroupLayout, + ) -> Rc { + match self { + Memory::Host { image } => { + let (width, height) = image.dimensions(); + + let extent = wgpu::Extent3d { + width, + height, + depth: 1, + }; + + let texture = device.create_texture(&wgpu::TextureDescriptor { + size: extent, + array_layer_count: 1, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Bgra8UnormSrgb, + usage: wgpu::TextureUsage::COPY_DST + | wgpu::TextureUsage::SAMPLED, + }); + + let slice = image.clone().into_raw(); + + let temp_buf = device + .create_buffer_mapped( + slice.len(), + wgpu::BufferUsage::COPY_SRC, + ) + .fill_from_slice(&slice[..]); + + encoder.copy_buffer_to_texture( + wgpu::BufferCopyView { + buffer: &temp_buf, + offset: 0, + row_pitch: 4 * width as u32, + image_height: height as u32, + }, + wgpu::TextureCopyView { + texture: &texture, + array_layer: 0, + mip_level: 0, + origin: wgpu::Origin3d { + x: 0.0, + y: 0.0, + z: 0.0, + }, + }, + extent, + ); + + let bind_group = + device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: texture_layout, + bindings: &[wgpu::Binding { + binding: 0, + resource: wgpu::BindingResource::TextureView( + &texture.create_default_view(), + ), + }], + }); + + let bind_group = Rc::new(bind_group); + + *self = Memory::Device { + bind_group: bind_group.clone(), + width, + height, + }; + + bind_group + } + Memory::Device { bind_group, .. } => bind_group.clone(), + } + } +} + +pub struct Image { + pub path: String, + pub position: [f32; 2], + pub scale: [f32; 2], +} + +#[derive(Clone, Copy)] +pub struct Vertex { + _position: [f32; 2], +} + +const QUAD_INDICES: [u16; 6] = [0, 1, 2, 0, 2, 3]; + +const QUAD_VERTS: [Vertex; 4] = [ + Vertex { + _position: [0.0, 0.0], + }, + Vertex { + _position: [1.0, 0.0], + }, + Vertex { + _position: [1.0, 1.0], + }, + Vertex { + _position: [0.0, 1.0], + }, +]; + +#[derive(Clone, Copy)] +struct Instance { + position: [f32; 2], + scale: [f32; 2], +} diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 46849aab..01dc4c20 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -1,8 +1,10 @@ +mod image; mod primitive; mod quad; mod renderer; mod transformation; +pub(crate) use crate::image::Image; pub(crate) use quad::Quad; pub(crate) use transformation::Transformation; diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs index b664689b..0b9e2c41 100644 --- a/wgpu/src/primitive.rs +++ b/wgpu/src/primitive.rs @@ -19,4 +19,8 @@ pub enum Primitive { background: Background, border_radius: u16, }, + Image { + path: String, + bounds: Rectangle, + }, } diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index 8930e9df..ab6f744f 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -1,4 +1,4 @@ -use crate::{quad, Primitive, Quad, Transformation}; +use crate::{quad, Image, Primitive, Quad, Transformation}; use iced_native::{ renderer::Debugger, renderer::Windowed, Background, Color, Layout, MouseCursor, Point, Widget, @@ -29,8 +29,10 @@ pub struct Renderer { device: Device, queue: Queue, quad_pipeline: quad::Pipeline, + image_pipeline: crate::image::Pipeline, quads: Vec, + images: Vec, glyph_brush: Rc>>, } @@ -67,6 +69,7 @@ impl Renderer { .build(&mut device, TextureFormat::Bgra8UnormSrgb); let quad_pipeline = quad::Pipeline::new(&mut device); + let image_pipeline = crate::image::Pipeline::new(&mut device); Self { surface, @@ -74,8 +77,10 @@ impl Renderer { device, queue, quad_pipeline, + image_pipeline, quads: Vec::new(), + images: Vec::new(), glyph_brush: Rc::new(RefCell::new(glyph_brush)), } } @@ -139,6 +144,16 @@ impl Renderer { self.quads.clear(); + self.image_pipeline.draw( + &mut self.device, + &mut encoder, + &self.images, + target.transformation, + &frame.view, + ); + + self.images.clear(); + self.glyph_brush .borrow_mut() .draw_queued( @@ -238,6 +253,13 @@ impl Renderer { border_radius: u32::from(*border_radius), }); } + Primitive::Image { path, bounds } => { + self.images.push(Image { + path: path.clone(), + position: [bounds.x, bounds.y], + scale: [bounds.width, bounds.height], + }); + } } } } diff --git a/wgpu/src/renderer/image.rs b/wgpu/src/renderer/image.rs index 85ac3ad5..1a9b01bb 100644 --- a/wgpu/src/renderer/image.rs +++ b/wgpu/src/renderer/image.rs @@ -1,12 +1,33 @@ use crate::{Primitive, Renderer}; -use iced_native::{image, Image, Layout, MouseCursor, Node, Style}; +use iced_native::{image, Image, Layout, Length, MouseCursor, Node, Style}; impl image::Renderer for Renderer { - fn node(&self, _image: &Image) -> Node { - Node::new(Style::default()) + fn node(&self, image: &Image) -> Node { + let (width, height) = self.image_pipeline.dimensions(&image.path); + + let aspect_ratio = width as f32 / 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(width as u16)) + .height(Length::Units(height as u16)), + }; + + Node::new(style) } - fn draw(&mut self, _image: &Image, _layout: Layout<'_>) -> Self::Output { - (Primitive::None, MouseCursor::OutOfBounds) + fn draw(&mut self, image: &Image, layout: Layout<'_>) -> Self::Output { + ( + Primitive::Image { + path: image.path.clone(), + bounds: layout.bounds(), + }, + MouseCursor::OutOfBounds, + ) } } diff --git a/wgpu/src/shader/image.frag b/wgpu/src/shader/image.frag new file mode 100644 index 00000000..e35e455a --- /dev/null +++ b/wgpu/src/shader/image.frag @@ -0,0 +1,12 @@ +#version 450 + +layout(location = 0) in vec2 v_Uv; + +layout(set = 0, binding = 1) uniform sampler u_Sampler; +layout(set = 1, binding = 0) uniform texture2D u_Texture; + +layout(location = 0) out vec4 o_Color; + +void main() { + o_Color = texture(sampler2D(u_Texture, u_Sampler), v_Uv); +} diff --git a/wgpu/src/shader/image.frag.spv b/wgpu/src/shader/image.frag.spv new file mode 100644 index 00000000..ebee82ac Binary files /dev/null and b/wgpu/src/shader/image.frag.spv differ diff --git a/wgpu/src/shader/image.vert b/wgpu/src/shader/image.vert new file mode 100644 index 00000000..688c2311 --- /dev/null +++ b/wgpu/src/shader/image.vert @@ -0,0 +1,24 @@ +#version 450 + +layout(location = 0) in vec2 v_Pos; +layout(location = 1) in vec2 i_Pos; +layout(location = 2) in vec2 i_Scale; + +layout (set = 0, binding = 0) uniform Globals { + mat4 u_Transform; +}; + +layout(location = 0) out vec2 o_Uv; + +void main() { + o_Uv = v_Pos; + + mat4 i_Transform = mat4( + vec4(i_Scale.x, 0.0, 0.0, 0.0), + vec4(0.0, i_Scale.y, 0.0, 0.0), + vec4(0.0, 0.0, 1.0, 0.0), + vec4(i_Pos, 0.0, 1.0) + ); + + gl_Position = u_Transform * i_Transform * vec4(v_Pos, 0.0, 1.0); +} diff --git a/wgpu/src/shader/image.vert.spv b/wgpu/src/shader/image.vert.spv new file mode 100644 index 00000000..9ba702bc Binary files /dev/null and b/wgpu/src/shader/image.vert.spv differ -- cgit From 871eb414303804233ed50d43bb9b98a7037cbd4c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 23 Oct 2019 01:34:58 +0200 Subject: Add `TODO` to `image::Renderer::node` --- wgpu/src/renderer/image.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/wgpu/src/renderer/image.rs b/wgpu/src/renderer/image.rs index 1a9b01bb..0e312706 100644 --- a/wgpu/src/renderer/image.rs +++ b/wgpu/src/renderer/image.rs @@ -9,6 +9,7 @@ impl image::Renderer for Renderer { let mut style = Style::default().align_self(image.align_self); + // TODO: Deal with additional cases style = match (image.width, image.height) { (Length::Units(width), _) => style.width(image.width).height( Length::Units((width as f32 / aspect_ratio).round() as u16), -- cgit From c7ef9d0da705d8f27011fe41d2103c73aae42d2d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 23 Oct 2019 02:33:07 +0200 Subject: Connect `iced_web` with `iced` properly --- src/lib.rs | 36 ++++++++++++++++++++++++++---------- src/web.rs | 1 + src/winit.rs | 11 +++++++++++ web/Cargo.toml | 3 +-- web/src/lib.rs | 13 +++---------- 5 files changed, 42 insertions(+), 22 deletions(-) create mode 100644 src/web.rs create mode 100644 src/winit.rs diff --git a/src/lib.rs b/src/lib.rs index 46574285..1bcdada2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,13 +1,8 @@ -pub use iced_wgpu::{Primitive, Renderer}; -pub use iced_winit::{ - button, slider, text, winit, Align, Background, Checkbox, Color, Image, - Justify, Length, Radio, Slider, Text, -}; +#[cfg_attr(target_arch = "wasm32", path = "web.rs")] +#[cfg_attr(not(target_arch = "wasm32"), path = "winit.rs")] +mod platform; -pub type Element<'a, Message> = iced_winit::Element<'a, Message, Renderer>; -pub type Row<'a, Message> = iced_winit::Row<'a, Message, Renderer>; -pub type Column<'a, Message> = iced_winit::Column<'a, Message, Renderer>; -pub type Button<'a, Message> = iced_winit::Button<'a, Message, Renderer>; +pub use platform::*; pub trait Application { type Message; @@ -20,12 +15,17 @@ pub trait Application { where Self: 'static + Sized, { - iced_winit::Application::run(Instance(self)) + #[cfg(not(target_arch = "wasm32"))] + iced_winit::Application::run(Instance(self)); + + #[cfg(target_arch = "wasm32")] + iced_web::Application::run(Instance(self)); } } struct Instance(A); +#[cfg(not(target_arch = "wasm32"))] impl iced_winit::Application for Instance where A: Application, @@ -41,3 +41,19 @@ where self.0.view() } } + +#[cfg(target_arch = "wasm32")] +impl iced_web::Application for Instance +where + A: Application, +{ + type Message = A::Message; + + fn update(&mut self, message: Self::Message) { + self.0.update(message); + } + + fn view(&mut self) -> Element { + self.0.view() + } +} diff --git a/src/web.rs b/src/web.rs new file mode 100644 index 00000000..31f1a6fc --- /dev/null +++ b/src/web.rs @@ -0,0 +1 @@ +pub use iced_web::*; diff --git a/src/winit.rs b/src/winit.rs new file mode 100644 index 00000000..64e301f4 --- /dev/null +++ b/src/winit.rs @@ -0,0 +1,11 @@ +pub use iced_wgpu::{Primitive, Renderer}; + +pub use iced_winit::{ + button, slider, text, winit, Align, Background, Checkbox, Color, Image, + Justify, Length, Radio, Slider, Text, +}; + +pub type Element<'a, Message> = iced_winit::Element<'a, Message, Renderer>; +pub type Row<'a, Message> = iced_winit::Row<'a, Message, Renderer>; +pub type Column<'a, Message> = iced_winit::Column<'a, Message, Renderer>; +pub type Button<'a, Message> = iced_winit::Button<'a, Message, Renderer>; diff --git a/web/Cargo.toml b/web/Cargo.toml index d5a987b0..473bde17 100644 --- a/web/Cargo.toml +++ b/web/Cargo.toml @@ -17,8 +17,7 @@ maintenance = { status = "actively-developed" } [dependencies] iced_core = { version = "0.1.0-alpha", path = "../core" } dodrio = "0.1.0" -futures-preview = "=0.3.0-alpha.18" -wasm-bindgen = "0.2.50" +wasm-bindgen = "0.2.51" [dependencies.web-sys] version = "0.3.27" diff --git a/web/src/lib.rs b/web/src/lib.rs index 04848d07..559a5af0 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -1,5 +1,4 @@ use dodrio::bumpalo; -use futures::Future; use std::cell::RefCell; mod bus; @@ -8,16 +7,13 @@ pub mod widget; pub use bus::Bus; pub use element::Element; -pub use iced_core::{Align, Color, Justify, Length}; +pub use iced_core::{Align, Background, Color, Justify, Length}; pub use widget::*; pub trait Application { type Message; - fn update( - &mut self, - message: Self::Message, - ) -> Option>>; + fn update(&mut self, message: Self::Message); fn view(&mut self) -> Element; @@ -48,10 +44,7 @@ impl Instance { } fn update(&mut self, message: Message) { - let mut ui = self.ui.borrow_mut(); - - // TODO: Resolve futures and publish resulting messages - let _ = ui.update(message); + self.ui.borrow_mut().update(message); } } -- cgit From 614514081640df35edb9a04bc10c42e951a48f61 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 23 Oct 2019 02:34:11 +0200 Subject: Make `tour` example work on Wasm again --- Cargo.toml | 3 +++ examples/tour.html | 5 +++-- examples/tour.rs | 21 +++++++++++++++++---- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 52fc483f..e8b53066 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,3 +32,6 @@ iced_web = { version = "0.1.0-alpha", path = "web" } [dev-dependencies] env_logger = "0.7" + +[target.'cfg(target_arch = "wasm32")'.dev-dependencies] +wasm-bindgen = "0.2.51" diff --git a/examples/tour.html b/examples/tour.html index b17ac4a2..35360e59 100644 --- a/examples/tour.html +++ b/examples/tour.html @@ -6,8 +6,9 @@ diff --git a/examples/tour.rs b/examples/tour.rs index 06be4766..59a8c525 100644 --- a/examples/tour.rs +++ b/examples/tour.rs @@ -308,7 +308,7 @@ impl<'a> Step { that can be easily implemented on top of Iced.", )) .push(Text::new( - "Iced is a renderer-agnostic GUI library for Rust focused on \ + "Iced is a cross-platform GUI library for Rust focused on \ simplicity and type-safety. It is heavily inspired by Elm.", )) .push(Text::new( @@ -316,9 +316,9 @@ impl<'a> Step { 2D game engine for Rust.", )) .push(Text::new( - "Iced does not provide a built-in renderer. On native \ - platforms, this example runs on a fairly simple renderer \ - built on top of ggez, another game library.", + "On native platforms, Iced provides by default a renderer \ + built on top of wgpu, a graphics library supporting Vulkan, \ + Metal, DX11, and DX12.", )) .push(Text::new( "Additionally, this tour can also run on WebAssembly thanks \ @@ -634,3 +634,16 @@ pub enum Layout { Row, Column, } + +// This should be gracefully handled by Iced in the future. Probably using our +// own proc macro, or maybe the whole process is streamlined by `wasm-pack` at +// some point. +#[cfg(target_arch = "wasm32")] +mod wasm { + use wasm_bindgen::prelude::*; + + #[wasm_bindgen(start)] + pub fn run() { + super::main() + } +} -- cgit From 2f8e9dbe59e0d91832ab14f8fc0ad7e18ff61ebe Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 23 Oct 2019 02:34:30 +0200 Subject: Remove warnings in `wgpu::image` --- wgpu/src/image.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index 18889faf..c883eaa8 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -251,8 +251,8 @@ impl Pipeline { let instance_buffer = device .create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC) .fill_from_slice(&[Instance { - position: image.position, - scale: image.scale, + _position: image.position, + _scale: image.scale, }]); encoder.copy_buffer_to_buffer( @@ -433,6 +433,6 @@ const QUAD_VERTS: [Vertex; 4] = [ #[derive(Clone, Copy)] struct Instance { - position: [f32; 2], - scale: [f32; 2], + _position: [f32; 2], + _scale: [f32; 2], } -- cgit From 7fdd7f595269eb043bbd7f392c2750783fa0a4b7 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 23 Oct 2019 02:35:08 +0200 Subject: Remove unused `Align` in `core::Text` --- core/src/widget/text.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/widget/text.rs b/core/src/widget/text.rs index cd94dbb2..427d9471 100644 --- a/core/src/widget/text.rs +++ b/core/src/widget/text.rs @@ -1,5 +1,5 @@ //! Write some text for your users to read. -use crate::{Align, Color, Length}; +use crate::{Color, Length}; /// A paragraph of text. /// -- cgit From 1788d5d21e9416edc470c910aceab4b416844829 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 23 Oct 2019 02:35:24 +0200 Subject: Update `README`s --- README.md | 3 +-- examples/README.md | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f7035fb4..fafa7b15 100644 --- a/README.md +++ b/README.md @@ -87,10 +87,9 @@ __view logic__: ```rust use iced::{Button, Column, Text}; -use iced_wgpu::Renderer; // Iced does not include a renderer! We need to bring our own! impl Counter { - pub fn view(&mut self) -> Column { + pub fn view(&mut self) -> Column { // We use a column: a simple vertical layout Column::new() .push( diff --git a/examples/README.md b/examples/README.md index fe69b34a..ec2a9396 100644 --- a/examples/README.md +++ b/examples/README.md @@ -37,7 +37,7 @@ __update logic__ and __view logic__. [`wgpu`]: https://github.com/gfx-rs/wgpu-rs #### Running the native version -Simply use [Cargo](https://doc.rust-lang.org/cargo/reference/manifest.html#examples) +Use [Cargo](https://doc.rust-lang.org/cargo/reference/manifest.html#examples) to run the example: ``` @@ -45,10 +45,20 @@ cargo run --example tour ``` #### Running the web version +Build using the `wasm32-unknown-unknown` target and use the [`wasm-bindgen`] CLI +to generate appropriate bindings in a `tour` directory. + ``` -TODO +cd examples +cargo build --example tour --target wasm32-unknown-unknown +wasm-bindgen ../target/wasm32-unknown-unknown/debug/examples/tour.wasm --out-dir tour --web ``` +Finally, serve the `examples` directory using an HTTP server, and access the +`tour.html` file. + +[`wasm-bindgen`]: https://github.com/rustwasm/wasm-bindgen + ## [Coffee] -- cgit From ac611ab837e228ebc78be69077bafeffd62e1a55 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 23 Oct 2019 02:47:05 +0200 Subject: Remove unnecessary comma --- examples/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/README.md b/examples/README.md index ec2a9396..0a06a012 100644 --- a/examples/README.md +++ b/examples/README.md @@ -54,7 +54,7 @@ cargo build --example tour --target wasm32-unknown-unknown wasm-bindgen ../target/wasm32-unknown-unknown/debug/examples/tour.wasm --out-dir tour --web ``` -Finally, serve the `examples` directory using an HTTP server, and access the +Finally, serve the `examples` directory using an HTTP server and access the `tour.html` file. [`wasm-bindgen`]: https://github.com/rustwasm/wasm-bindgen -- cgit From f6fc0b714cc87c2afd302c447cb5e966ab5df717 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 23 Oct 2019 02:50:57 +0200 Subject: Remove leftover `winit` feature for `doc.rs` --- native/Cargo.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/native/Cargo.toml b/native/Cargo.toml index 6870649a..8cabe94c 100644 --- a/native/Cargo.toml +++ b/native/Cargo.toml @@ -7,9 +7,6 @@ description = "A renderer-agnostic library for native GUIs" license = "MIT" repository = "https://github.com/hecrj/iced" -[package.metadata.docs.rs] -features = ["winit"] - [dependencies] iced_core = { version = "0.1.0-alpha", path = "../core" } stretch = "0.2" -- cgit From 99e1a3780a1ea3ccb173d1fb4cbe889bb08b9643 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 23 Oct 2019 04:44:33 +0200 Subject: Set initial window size to 1280x1024 for now This will be configurable when calling `Application::run` in the future. --- winit/src/application.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/winit/src/application.rs b/winit/src/application.rs index 8345a5ed..418ee3c4 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -26,6 +26,10 @@ pub trait Application { // TODO: Ask for window settings and configure this properly let window = WindowBuilder::new() + .with_inner_size(winit::dpi::LogicalSize { + width: 1280.0, + height: 1024.0, + }) .build(&event_loop) .expect("Open window"); -- cgit