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.ttfBinary files differ index 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.pngBinary files differ index 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.pngBinary files differ index 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.spvBinary files differ new 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.spvBinary files differ new 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; | 
