diff options
66 files changed, 1460 insertions, 1317 deletions
@@ -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 <hector0193@gmail.com>"] edition = "2018" description = "A cross-platform GUI library inspired by Elm" @@ -19,5 +19,16 @@ members = [ "core", "native", "web", - "examples/tour", + "wgpu", + "winit", ] + +[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" } + +[dev-dependencies] +env_logger = "0.7" 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 5cc3a084..79910dd8 100644 --- a/core/src/color.rs +++ b/core/src/color.rs @@ -16,4 +16,31 @@ impl Color { b: 0.0, 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 + 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/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); -/// ``` -/// -///  -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<Message>, - pub class: Class, - pub width: Length, + pub padding: u16, + + pub background: Option<Background>, + + pub border_radius: u16, + pub align_self: Option<Align>, } -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<E>(state: &'a mut State, content: E) -> Self + where + E: Into<Element>, + { 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`]. -/// -///  -/// -/// [`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/resources/Roboto-LICENSE b/examples/resources/Roboto-LICENSE index 75b52484..75b52484 100644 --- a/examples/tour/resources/Roboto-LICENSE +++ b/examples/resources/Roboto-LICENSE diff --git a/examples/tour/resources/Roboto-Regular.ttf b/examples/resources/Roboto-Regular.ttf Binary files differindex 2b6392ff..2b6392ff 100644 --- a/examples/tour/resources/Roboto-Regular.ttf +++ b/examples/resources/Roboto-Regular.ttf diff --git a/examples/tour/resources/ferris.png b/examples/resources/ferris.png Binary files differindex ebce1a14..ebce1a14 100644 --- a/examples/tour/resources/ferris.png +++ b/examples/resources/ferris.png diff --git a/examples/tour/resources/ui.png b/examples/resources/ui.png Binary files differindex 4fd3beb3..4fd3beb3 100644 --- a/examples/tour/resources/ui.png +++ b/examples/resources/ui.png diff --git a/examples/tour/index.html b/examples/tour.html index b17ac4a2..b17ac4a2 100644 --- a/examples/tour/index.html +++ b/examples/tour.html diff --git a/examples/tour/src/tour.rs b/examples/tour.rs index 60a251d2..542efc66 100644 --- a/examples/tour/src/tour.rs +++ b/examples/tour.rs @@ -1,8 +1,17 @@ -use crate::widget::{ - button, slider, text::HorizontalAlignment, Align, Button, Checkbox, Color, - Column, Element, Image, Length, Radio, Row, Slider, Text, +use iced::{ + button, slider, text::HorizontalAlignment, Align, Background, Button, + Checkbox, Color, Column, Element, Image, Justify, Length, Radio, Row, + Slider, Text, UserInterface, }; +pub fn main() { + env_logger::init(); + + let tour = Tour::new(); + + tour.run(); +} + pub struct Tour { steps: Steps, back_button: button::State, @@ -19,8 +28,12 @@ impl Tour { debug: false, } } +} - pub fn update(&mut self, event: Message) { +impl UserInterface for Tour { + type Message = Message; + + fn update(&mut self, event: Message) { match event { Message::BackPressed => { self.steps.go_back(); @@ -34,7 +47,7 @@ impl Tour { } } - pub fn view(&mut self) -> Element<Message> { + fn view(&mut self) -> Element<Message> { let Tour { steps, back_button, @@ -46,9 +59,8 @@ impl 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), ); } @@ -56,22 +68,32 @@ impl 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), ); } 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(); - 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() } } @@ -304,7 +326,7 @@ impl<'a> Step { )) .push(Text::new( "You will need to interact with the UI in order to reach the \ - end of this tour!", + end!", )) } @@ -524,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/examples/tour/Cargo.toml b/examples/tour/Cargo.toml deleted file mode 100644 index 2c79cbf7..00000000 --- a/examples/tour/Cargo.toml +++ /dev/null @@ -1,33 +0,0 @@ -[package] -name = "iced_tour" -version = "0.0.0" -authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] -description = "Tour example for Iced" -license = "MIT" -repository = "https://github.com/hecrj/iced" -edition = "2018" -publish = false - -[lib] -crate-type = ["cdylib", "rlib"] - -[[bin]] -name = "main" -path = "src/main.rs" - -[dependencies] -futures-preview = "=0.3.0-alpha.18" - -[target.'cfg(not(target_arch = "wasm32"))'.dependencies] -iced_native = { version = "0.1.0-alpha", path = "../../native" } -# A personal `ggez` fork that introduces a `FontCache` type to measure text -# efficiently and fixes HiDPI issues. -ggez = { version = "0.5", git = "https://github.com/hecrj/ggez.git" } -env_logger = "0.6" - -[target.'cfg(target_arch = "wasm32")'.dependencies] -iced_web = { path = "../../web" } -wasm-bindgen = "0.2.50" -log = "0.4" -console_error_panic_hook = "0.1.6" -console_log = "0.1.2" diff --git a/examples/tour/README.md b/examples/tour/README.md 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/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<MeshBuilder>, -} - -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<Message>(&self, button: &Button<'_, Message>) -> Node { - let style = Style::default() - .width(button.width) - .height(Length::Units(LEFT.h as u16)) - .min_width(Length::Units(100)) - .align_self(button.align_self); - - Node::new(style) - } - - fn draw<Message>( - &mut self, - 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<Message>(&mut self, checkbox: &Checkbox<Message>) -> Node { - Row::<(), Self>::new() - .spacing(15) - .align_items(Align::Center) - .push( - Column::new() - .width(Length::Units(SPRITE.w as u16)) - .height(Length::Units(SPRITE.h as u16)), - ) - .push(Text::new(&checkbox.label)) - .node(self) - } - - fn draw<Message>( - &mut self, - checkbox: &Checkbox<Message>, - layout: Layout<'_>, - cursor_position: iced_native::Point, - ) -> MouseCursor { - let bounds = layout.bounds(); - let children: Vec<_> = layout.children().collect(); - let text_bounds = children[1].bounds(); - - let mut text = Text::new(&checkbox.label); - - if let Some(label_color) = checkbox.label_color { - text = text.color(label_color); - } - - text::Renderer::draw(self, &text, children[1]); - - let mouse_over = bounds.contains(cursor_position) - || text_bounds.contains(cursor_position); - - 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<String, graphics::Image>, -} - -impl Cache { - pub fn new() -> Self { - Self { - images: std::collections::HashMap::new(), - } - } - - fn get<'a>( - &mut self, - name: &'a str, - context: &mut ggez::Context, - ) -> graphics::Image { - if let Some(image) = self.images.get(name) { - return image.clone(); - } - - let mut image = graphics::Image::new(context, &format!("/{}", name)) - .expect("Load ferris image"); - - image.set_filter(graphics::FilterMode::Linear); - - self.images.insert(name.to_string(), image.clone()); - - image - } -} - -impl<'a> image::Renderer<&'a str> for Renderer<'_> { - fn node(&mut self, image: &Image<&'a str>) -> iced_native::Node { - let ggez_image = self.images.get(image.handle, self.context); - - let aspect_ratio = - ggez_image.width() as f32 / ggez_image.height() as f32; - - let mut style = Style::default().align_self(image.align_self); - - style = match (image.width, image.height) { - (Length::Units(width), _) => style.width(image.width).height( - Length::Units((width as f32 / aspect_ratio).round() as u16), - ), - (_, _) => style - .width(Length::Units(ggez_image.width())) - .height(Length::Units(ggez_image.height())), - }; - - iced_native::Node::new(style) - } - - fn draw(&mut self, image: &Image<&'a str>, layout: Layout<'_>) { - let image = self.images.get(image.handle, self.context); - let bounds = layout.bounds(); - - // We should probably use batches to draw images efficiently and keep - // draw side-effect free, but this is good enough for the example. - graphics::draw( - self.context, - &image, - graphics::DrawParam::new() - .dest(nalgebra::Point2::new(bounds.x, bounds.y)) - .scale(nalgebra::Vector2::new( - bounds.width / image.width() as f32, - bounds.height / image.height() as f32, - )), - ) - .expect("Draw image"); - } -} diff --git a/examples/tour/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<Message>(&mut self, radio: &Radio<Message>) -> Node { - Row::<(), Self>::new() - .spacing(15) - .align_items(Align::Center) - .push( - Column::new() - .width(Length::Units(SPRITE.w as u16)) - .height(Length::Units(SPRITE.h as u16)), - ) - .push(Text::new(&radio.label)) - .node(self) - } - - fn draw<Message>( - &mut self, - radio: &Radio<Message>, - layout: Layout<'_>, - cursor_position: Point, - ) -> 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<Message>(&self, slider: &Slider<'_, Message>) -> Node { - let style = Style::default() - .width(slider.width) - .height(Length::Units(25)) - .min_width(Length::Units(100)); - - Node::new(style) - } - - fn draw<Message>( - &mut self, - slider: &Slider<'_, Message>, - layout: Layout<'_>, - cursor_position: Point, - ) -> 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 deleted file mode 100644 index a34d3298..00000000 --- a/examples/tour/src/main.rs +++ /dev/null @@ -1,191 +0,0 @@ -use iced_tour::{iced_ggez, Tour}; - -use ggez; -use ggez::event; -use ggez::filesystem; -use ggez::graphics; -use ggez::input::mouse; - -pub fn main() -> ggez::GameResult { - 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) -} - -struct Game { - spritesheet: graphics::Image, - font: graphics::Font, - images: iced_ggez::ImageCache, - tour: Tour, - - events: Vec<iced_native::Event>, - cache: Option<iced_native::Cache>, -} - -impl Game { - fn new(context: &mut ggez::Context) -> ggez::GameResult<Game> { - graphics::set_default_filter(context, graphics::FilterMode::Nearest); - - Ok(Game { - spritesheet: graphics::Image::new(context, "/resources/ui.png") - .unwrap(), - font: graphics::Font::new(context, "/resources/Roboto-Regular.ttf") - .unwrap(), - images: iced_ggez::ImageCache::new(), - tour: Tour::new(), - - events: Vec::new(), - cache: Some(iced_native::Cache::default()), - }) - } -} - -impl event::EventHandler for Game { - fn update(&mut self, _ctx: &mut ggez::Context) -> ggez::GameResult { - Ok(()) - } - - fn mouse_button_down_event( - &mut self, - _context: &mut ggez::Context, - _button: mouse::MouseButton, - _x: f32, - _y: f32, - ) { - self.events.push(iced_native::Event::Mouse( - iced_native::input::mouse::Event::Input { - state: iced_native::input::ButtonState::Pressed, - button: iced_native::input::mouse::Button::Left, // TODO: Map `button` - }, - )); - } - - fn mouse_button_up_event( - &mut self, - _context: &mut ggez::Context, - _button: mouse::MouseButton, - _x: f32, - _y: f32, - ) { - self.events.push(iced_native::Event::Mouse( - iced_native::input::mouse::Event::Input { - state: iced_native::input::ButtonState::Released, - button: iced_native::input::mouse::Button::Left, // TODO: Map `button` - }, - )); - } - - fn mouse_motion_event( - &mut self, - _context: &mut ggez::Context, - x: f32, - y: f32, - _dx: f32, - _dy: f32, - ) { - self.events.push(iced_native::Event::Mouse( - iced_native::input::mouse::Event::CursorMoved { x, y }, - )); - } - - fn resize_event( - &mut self, - context: &mut ggez::Context, - width: f32, - height: f32, - ) { - graphics::set_screen_coordinates( - context, - graphics::Rect { - x: 0.0, - y: 0.0, - w: width, - h: height, - }, - ) - .expect("Set screen coordinates"); - } - - fn draw(&mut self, context: &mut ggez::Context) -> ggez::GameResult { - graphics::clear(context, graphics::WHITE); - - let screen = graphics::screen_coordinates(context); - - let (messages, cursor) = { - let view = self.tour.view(); - - let content = iced_ggez::Column::new() - .width(iced_native::Length::Units(screen.w as u16)) - .height(iced_native::Length::Units(screen.h as u16)) - .padding(20) - .align_items(iced_native::Align::Center) - .justify_content(iced_native::Justify::Center) - .push(view); - - let renderer = &mut iced_ggez::Renderer::new( - context, - &mut self.images, - self.spritesheet.clone(), - self.font, - ); - - let mut ui = iced_native::UserInterface::build( - content, - self.cache.take().unwrap(), - renderer, - ); - - let messages = ui.update(self.events.drain(..)); - let cursor = ui.draw(renderer); - - self.cache = Some(ui.into_cache()); - - renderer.flush(); - - (messages, cursor) - }; - - for message in messages { - self.tour.update(message); - } - - let cursor_type = into_cursor_type(cursor); - - if mouse::cursor_type(context) != cursor_type { - mouse::set_cursor_type(context, cursor_type); - } - - graphics::present(context)?; - Ok(()) - } -} - -fn into_cursor_type(cursor: iced_native::MouseCursor) -> mouse::MouseCursor { - match cursor { - iced_native::MouseCursor::OutOfBounds => mouse::MouseCursor::Default, - iced_native::MouseCursor::Idle => mouse::MouseCursor::Default, - iced_native::MouseCursor::Pointer => mouse::MouseCursor::Hand, - iced_native::MouseCursor::Working => mouse::MouseCursor::Progress, - iced_native::MouseCursor::Grab => mouse::MouseCursor::Grab, - iced_native::MouseCursor::Grabbing => mouse::MouseCursor::Grabbing, - } -} diff --git a/examples/tour/src/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<Box<dyn Future<Output = tour::Message>>> { - self.update(message); - - None - } - - fn view(&mut self) -> iced_web::Element<tour::Message> { - self.view() - } -} diff --git a/examples/tour/src/widget.rs b/examples/tour/src/widget.rs 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/native/src/element.rs b/native/src/element.rs index dd5ce621..cf96b7ea 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 @@ -40,6 +41,19 @@ impl<'a, Message, Renderer> Element<'a, Message, Renderer> { } } + pub fn node(&self, renderer: &Renderer) -> Node { + self.widget.node(renderer) + } + + 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 @@ -87,38 +101,46 @@ impl<'a, Message, Renderer> Element<'a, Message, Renderer> { /// /// ``` /// # 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<Message> { - /// # Button::new(&mut self.0, "_") + /// # pub fn view(&mut self) -> Text { + /// # Text::new("") /// # } /// # } /// # } /// # /// # mod iced_wgpu { /// # use iced_native::{ - /// # button, Button, MouseCursor, Node, Point, Rectangle, Style, Layout + /// # text, row, Text, Node, Point, Rectangle, Style, Layout, Row /// # }; /// # pub struct Renderer; /// # - /// # impl button::Renderer for Renderer { - /// # fn node<Message>(&self, _button: &Button<'_, Message>) -> Node { - /// # Node::new(Style::default()) - /// # } + /// # impl iced_native::Renderer for Renderer { type Primitive = (); } /// # + /// # impl iced_native::row::Renderer for Renderer { /// # fn draw<Message>( /// # &mut self, - /// # _button: &Button<'_, Message>, + /// # _column: &Row<'_, Message, Self>, /// # _layout: Layout<'_>, /// # _cursor_position: Point, - /// # ) -> MouseCursor { - /// # MouseCursor::OutOfBounds + /// # ) {} + /// # } + /// # + /// # impl text::Renderer for Renderer { + /// # fn node(&self, _text: &Text) -> Node { + /// # Node::new(Style::default()) /// # } + /// # + /// # fn draw( + /// # &mut self, + /// # _text: &Text, + /// # _layout: Layout<'_>, + /// # ) {} /// # } /// # } /// # @@ -268,8 +290,9 @@ impl<'a, A, B, Renderer> Map<'a, A, B, Renderer> { impl<'a, A, B, Renderer> Widget<B, Renderer> for Map<'a, A, B, Renderer> where A: Copy, + Renderer: crate::Renderer, { - fn node(&self, renderer: &mut Renderer) -> Node { + fn node(&self, renderer: &Renderer) -> Node { self.widget.node(renderer) } @@ -300,7 +323,7 @@ where renderer: &mut Renderer, layout: Layout<'_>, cursor_position: Point, - ) -> MouseCursor { + ) -> Renderer::Primitive { self.widget.draw(renderer, layout, cursor_position) } @@ -309,14 +332,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 +350,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,9 +360,9 @@ where impl<'a, Message, Renderer> Widget<Message, Renderer> for Explain<'a, Message, Renderer> where - Renderer: renderer::Debugger, + Renderer: crate::Renderer + renderer::Debugger, { - fn node(&self, renderer: &mut Renderer) -> Node { + fn node(&self, renderer: &Renderer) -> Node { self.element.widget.node(renderer) } @@ -360,10 +383,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..18ce3a37 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -77,28 +77,29 @@ //! # //! # 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 {} //! # +//! # impl iced_native::Renderer for Renderer { +//! # type Primitive = (); +//! # } +//! # //! # impl button::Renderer for Renderer { //! # fn node<Message>( //! # &self, -//! # _button: &Button<'_, Message> +//! # _button: &Button<'_, Message, Self> //! # ) -> Node { //! # Node::new(Style::default()) //! # } //! # //! # fn draw<Message>( //! # &mut self, -//! # _button: &Button<'_, Message>, +//! # _button: &Button<'_, Message, Self>, //! # _layout: Layout<'_>, //! # _cursor_position: Point, -//! # ) -> MouseCursor { -//! # MouseCursor::OutOfBounds -//! # } +//! # ) {} //! # } //! # //! # impl text::Renderer for Renderer { @@ -124,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( @@ -134,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), //! ) //! } @@ -192,7 +193,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)] @@ -205,14 +206,15 @@ mod element; mod event; mod hasher; mod layout; -mod mouse_cursor; mod node; mod style; 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}; @@ -221,8 +223,8 @@ 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; 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<Message>( + &mut self, + widget: &dyn Widget<Message, Self>, + layout: Layout<'_>, + cursor_position: Point, + color: Color, + ) -> Self::Primitive; } 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/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<Message>( + /// # &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<Message>( + /// # &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<Message>( + /// # &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..b7181c1b 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<Message, Renderer>: std::fmt::Debug { +pub trait Widget<Message, Renderer>: 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 @@ -65,20 +67,17 @@ pub trait Widget<Message, Renderer>: std::fmt::Debug { /// [`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`. /// - /// 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..1f881660 100644 --- a/native/src/widget/button.rs +++ b/native/src/widget/button.rs @@ -7,17 +7,21 @@ //! [`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::*; +pub use iced_core::button::State; -impl<'a, Message, Renderer> Widget<Message, Renderer> for Button<'a, Message> +pub type Button<'a, Message, Renderer> = + iced_core::Button<'a, Message, Element<'a, Message, Renderer>>; + +impl<'a, Message, Renderer> Widget<Message, Renderer> + 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) } @@ -63,14 +67,14 @@ where renderer: &mut Renderer, layout: Layout<'_>, cursor_position: Point, - ) -> MouseCursor { + ) -> Renderer::Primitive { renderer.draw(&self, layout, cursor_position) } 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 { +pub trait Renderer: crate::Renderer + Sized { /// Creates a [`Node`] for the provided [`Button`]. /// /// [`Node`]: ../../struct.Node.html /// [`Button`]: struct.Button.html - fn node<Message>(&self, button: &Button<'_, Message>) -> Node; + fn node<Message>(&self, button: &Button<'_, Message, Self>) -> Node; /// Draws a [`Button`]. /// /// [`Button`]: struct.Button.html fn draw<Message>( &mut self, - button: &Button<'_, Message>, + button: &Button<'_, Message, Self>, layout: Layout<'_>, cursor_position: Point, - ) -> MouseCursor; + ) -> Self::Primitive; } -impl<'a, Message, Renderer> From<Button<'a, Message>> +impl<'a, Message, Renderer> From<Button<'a, Message, Renderer>> 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 3e307f64..c069bfdc 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; @@ -10,7 +10,7 @@ impl<Message, Renderer> Widget<Message, Renderer> for Checkbox<Message> where Renderer: self::Renderer, { - fn node(&self, renderer: &mut Renderer) -> Node { + fn node(&self, renderer: &Renderer) -> Node { renderer.node(&self) } @@ -43,7 +43,7 @@ where renderer: &mut Renderer, layout: Layout<'_>, cursor_position: Point, - ) -> MouseCursor { + ) -> Renderer::Primitive { renderer.draw(&self, layout, cursor_position) } @@ -59,12 +59,12 @@ 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 /// [`Checkbox`]: struct.Checkbox.html - fn node<Message>(&mut self, checkbox: &Checkbox<Message>) -> Node; + fn node<Message>(&self, checkbox: &Checkbox<Message>) -> Node; /// Draws a [`Checkbox`]. /// @@ -80,7 +80,7 @@ pub trait Renderer { checkbox: &Checkbox<Message>, layout: Layout<'_>, cursor_position: Point, - ) -> MouseCursor; + ) -> Self::Primitive; } impl<'a, Message, Renderer> From<Checkbox<Message>> diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs index 9da2e161..7e10e662 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,8 +8,10 @@ pub type Column<'a, Message, Renderer> = impl<'a, Message, Renderer> Widget<Message, Renderer> for Column<'a, Message, Renderer> +where + Renderer: self::Renderer, { - fn node(&self, renderer: &mut Renderer) -> Node { + fn node(&self, renderer: &Renderer) -> Node { let mut children: Vec<Node> = self .children .iter() @@ -70,21 +70,8 @@ impl<'a, Message, Renderer> Widget<Message, Renderer> 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<Message, Renderer> } } +pub trait Renderer: crate::Renderer + Sized { + fn draw<Message>( + &mut self, + row: &Column<'_, Message, Self>, + layout: Layout<'_>, + cursor_position: Point, + ) -> Self::Primitive; +} + impl<'a, Message, Renderer> From<Column<'a, Message, Renderer>> 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..5197d5b1 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; @@ -11,7 +11,7 @@ where Renderer: self::Renderer<I>, I: Clone, { - fn node(&self, renderer: &mut Renderer) -> Node { + fn node(&self, renderer: &Renderer) -> Node { renderer.node(&self) } @@ -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,19 +38,20 @@ where /// /// [`Image`]: struct.Image.html /// [renderer]: ../../renderer/index.html -pub trait Renderer<I> { +pub trait Renderer<I>: 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(&mut self, image: &Image<I>) -> Node; + fn node(&self, image: &Image<I>) -> Node; /// Draws an [`Image`]. /// /// [`Image`]: struct.Image.html - fn draw(&mut self, image: &Image<I>, layout: Layout<'_>); + fn draw(&mut self, image: &Image<I>, layout: Layout<'_>) + -> Self::Primitive; } impl<'a, I, Message, Renderer> From<Image<I>> for Element<'a, Message, Renderer> diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs index 33d42e61..22308f81 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; @@ -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) } @@ -40,7 +40,7 @@ where renderer: &mut Renderer, layout: Layout<'_>, cursor_position: Point, - ) -> MouseCursor { + ) -> Renderer::Primitive { renderer.draw(&self, layout, cursor_position) } @@ -56,12 +56,12 @@ 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 /// [`Radio`]: struct.Radio.html - fn node<Message>(&mut self, radio: &Radio<Message>) -> Node; + fn node<Message>(&self, radio: &Radio<Message>) -> Node; /// Draws a [`Radio`] button. /// @@ -77,7 +77,7 @@ pub trait Renderer { radio: &Radio<Message>, layout: Layout<'_>, cursor_position: Point, - ) -> MouseCursor; + ) -> Self::Primitive; } impl<'a, Message, Renderer> From<Radio<Message>> diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs index 3cd451b7..b1d4a5b2 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> = @@ -10,8 +8,10 @@ pub type Row<'a, Message, Renderer> = impl<'a, Message, Renderer> Widget<Message, Renderer> for Row<'a, Message, Renderer> +where + Renderer: self::Renderer, { - fn node(&self, renderer: &mut Renderer) -> Node { + fn node(&self, renderer: &Renderer) -> Node { let mut children: Vec<Node> = self .children .iter() @@ -70,21 +70,8 @@ impl<'a, Message, Renderer> Widget<Message, Renderer> 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 +92,19 @@ impl<'a, Message, Renderer> Widget<Message, Renderer> } } +pub trait Renderer: crate::Renderer + Sized { + fn draw<Message>( + &mut self, + row: &Row<'_, Message, Self>, + layout: Layout<'_>, + cursor_position: Point, + ) -> Self::Primitive; +} + impl<'a, Message, Renderer> From<Row<'a, Message, Renderer>> 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..643efdf4 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::*; @@ -15,7 +15,7 @@ impl<'a, Message, Renderer> Widget<Message, Renderer> for Slider<'a, Message> where Renderer: self::Renderer, { - fn node(&self, renderer: &mut Renderer) -> Node { + fn node(&self, renderer: &Renderer) -> Node { renderer.node(&self) } @@ -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<Slider<'a, Message>> diff --git a/native/src/widget/text.rs b/native/src/widget/text.rs index 5ca6ebf3..62f2d7b7 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; @@ -9,7 +9,7 @@ impl<Message, Renderer> Widget<Message, Renderer> for Text where Renderer: self::Renderer, { - fn node(&self, renderer: &mut Renderer) -> Node { + fn node(&self, renderer: &Renderer) -> Node { renderer.node(&self) } @@ -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<Text> for Element<'a, Message, Renderer> @@ -0,0 +1,131 @@ +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>; + +pub trait UserInterface { + type Message; + + fn update(&mut self, message: Self::Message); + + fn view(&mut self) -> Element<Self::Message>; + + 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 = 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; + } + }) + } +} + +fn document<UserInterface>( + user_interface: &mut UserInterface, + width: u16, + height: u16, +) -> Element<UserInterface::Message> +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/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<Message>, + ) -> 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<Message> 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/Cargo.toml b/wgpu/Cargo.toml new file mode 100644 index 00000000..dbc1ddb9 --- /dev/null +++ b/wgpu/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "iced_wgpu" +version = "0.1.0-alpha" +authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] +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 = { 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/lib.rs b/wgpu/src/lib.rs new file mode 100644 index 00000000..8f8d50e9 --- /dev/null +++ b/wgpu/src/lib.rs @@ -0,0 +1,12 @@ +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::Primitive; +pub use renderer::{Renderer, Target}; diff --git a/native/src/mouse_cursor.rs b/wgpu/src/mouse_cursor.rs index 4ef6361a..4ef6361a 100644 --- a/native/src/mouse_cursor.rs +++ b/wgpu/src/mouse_cursor.rs diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs new file mode 100644 index 00000000..b664689b --- /dev/null +++ b/wgpu/src/primitive.rs @@ -0,0 +1,22 @@ +use iced_native::{text, Background, Color, Rectangle}; + +#[derive(Debug, Clone)] +pub enum Primitive { + None, + Group { + primitives: Vec<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, + }, +} diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs new file mode 100644 index 00000000..adb294f0 --- /dev/null +++ b/wgpu/src/quad.rs @@ -0,0 +1,275 @@ +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::<Vertex>() 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::<Quad>() 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), + }, + wgpu::VertexAttributeDescriptor { + shader_location: 4, + format: wgpu::VertexFormat::Uint, + offset: 4 * (2 + 2 + 4), + }, + ], + }, + ], + 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::<Quad>() 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::<Quad>() * 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], + pub border_radius: u32, +} + +impl Quad { + const MAX: usize = 100_000; +} diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs new file mode 100644 index 00000000..ae5692e3 --- /dev/null +++ b/wgpu/src/renderer.rs @@ -0,0 +1,254 @@ +use crate::{quad, Primitive, Quad, Transformation}; +use iced_native::{ + renderer::Debugger, Background, Color, Layout, Point, Widget, +}; + +use raw_window_handle::HasRawWindowHandle; +use wgpu::{ + Adapter, BackendBit, CommandEncoderDescriptor, Device, DeviceDescriptor, + Extensions, Limits, PowerPreference, Queue, 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 { + surface: Surface, + adapter: Adapter, + device: Device, + queue: Queue, + quad_pipeline: quad::Pipeline, + + quads: Vec<Quad>, + glyph_brush: Rc<RefCell<GlyphBrush<'static, ()>>>, +} + +pub struct Target { + width: u16, + height: u16, + transformation: Transformation, + swap_chain: SwapChain, +} + +impl Renderer { + pub fn new<W: HasRawWindowHandle>(window: &W) -> Self { + let adapter = Adapter::request(&RequestAdapterOptions { + power_preference: PowerPreference::LowPower, + backends: BackendBit::all(), + }) + .expect("Request adapter"); + + let (mut device, queue) = adapter.request_device(&DeviceDescriptor { + extensions: Extensions { + anisotropic_filtering: false, + }, + limits: Limits { max_bind_groups: 1 }, + }); + + let surface = Surface::create(window); + + // TODO: Think about font loading strategy + // Loading system fonts with fallback may be a good idea + let font: &[u8] = + include_bytes!("../../examples/resources/Roboto-Regular.ttf"); + + let glyph_brush = GlyphBrushBuilder::using_font_bytes(font) + .build(&mut device, TextureFormat::Bgra8UnormSrgb); + + let quad_pipeline = quad::Pipeline::new(&mut device); + + Self { + surface, + adapter, + device, + queue, + quad_pipeline, + + quads: Vec::new(), + glyph_brush: Rc::new(RefCell::new(glyph_brush)), + } + } + + pub fn target(&self, width: u16, height: u16) -> Target { + Target { + width, + height, + transformation: Transformation::orthographic(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) { + log::debug!("Drawing"); + + 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.quad_pipeline.draw( + &mut self.device, + &mut encoder, + &self.quads, + target.transformation, + &frame.view, + ); + + self.quads.clear(); + + 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.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, + 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), + }); + } + } + } +} + +impl iced_native::Renderer for Renderer { + // TODO: Add `MouseCursor` here (?) + type Primitive = Primitive; +} + +impl Debugger for Renderer { + fn explain<Message>( + &mut self, + widget: &dyn Widget<Message, Self>, + 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..00fcd0eb --- /dev/null +++ b/wgpu/src/renderer/button.rs @@ -0,0 +1,49 @@ +use crate::{Primitive, Renderer}; +use iced_native::{ + button, Align, Background, Button, Color, Layout, Length, Node, Point, + Style, +}; + +impl button::Renderer for Renderer { + fn node<Message>(&self, button: &Button<Message, Self>) -> Node { + let style = Style::default() + .width(button.width) + .padding(button.padding) + .min_width(Length::Units(100)) + .align_self(button.align_self) + .align_items(Align::Stretch); + + Node::with_children(style, vec![button.content.node(self)]) + } + + fn draw<Message>( + &mut self, + button: &Button<Message, Self>, + layout: Layout<'_>, + cursor_position: Point, + ) -> Self::Primitive { + 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, + ), + ], + } + } +} diff --git a/wgpu/src/renderer/checkbox.rs b/wgpu/src/renderer/checkbox.rs new file mode 100644 index 00000000..16d5734f --- /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<Message>(&self, _checkbox: &Checkbox<Message>) -> Node { + Node::new(Style::default()) + } + + fn draw<Message>( + &mut self, + _checkbox: &Checkbox<Message>, + _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<Message>( + &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..bacc430d --- /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(&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..fdc0a0fc --- /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<Message>(&self, _checkbox: &Radio<Message>) -> Node { + Node::new(Style::default()) + } + + fn draw<Message>( + &mut self, + _radio: &Radio<Message>, + _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<Message>( + &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<Message>(&self, _slider: &Slider<Message>) -> Node { + Node::new(Style::default()) + } + + fn draw<Message>( + &mut self, + _slider: &Slider<Message>, + _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..c89c0b3e --- /dev/null +++ b/wgpu/src/renderer/text.rs @@ -0,0 +1,80 @@ +use crate::{Primitive, Renderer}; +use iced_native::{text, Color, 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(), + 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 new file mode 100644 index 00000000..987744db --- /dev/null +++ b/wgpu/src/shader/quad.frag @@ -0,0 +1,37 @@ +#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 = 3) in flat uint v_BorderRadius; + +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() { + 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 Binary files differnew file mode 100644 index 00000000..063287b3 --- /dev/null +++ b/wgpu/src/shader/quad.frag.spv diff --git a/wgpu/src/shader/quad.vert b/wgpu/src/shader/quad.vert new file mode 100644 index 00000000..b7c5cf3e --- /dev/null +++ b/wgpu/src/shader/quad.vert @@ -0,0 +1,32 @@ +#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(location = 4) in uint i_BorderRadius; + +layout (set = 0, binding = 0) uniform Globals { + mat4 u_Transform; +}; + +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( + 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; + 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 Binary files differnew file mode 100644 index 00000000..f62a160c --- /dev/null +++ b/wgpu/src/shader/quad.vert.spv 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<Transformation> for [f32; 16] { + fn from(transformation: Transformation) -> [f32; 16] { + transformation.0 + } +} 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 <hector0193@gmail.com>"] +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..54a0bd9a --- /dev/null +++ b/winit/src/lib.rs @@ -0,0 +1,2 @@ +pub use iced_native::*; +pub use winit; |