diff --git a/core/src/background.rs b/core/src/background.rs index 2f75e45c..e1a37ddc 100644 --- a/core/src/background.rs +++ b/core/src/background.rs @@ -12,4 +12,4 @@ impl From for Background { fn from(color: Color) -> Self { Background::Color(color) } -} \ No newline at end of file +} diff --git a/core/src/length.rs b/core/src/length.rs index 10873e89..06d8cf0a 100644 --- a/core/src/length.rs +++ b/core/src/length.rs @@ -4,6 +4,15 @@ pub enum Length { /// Fill all the remaining space Fill, + /// Fill a portion of the remaining space relative to other elements. + /// + /// Let's say we have two elements: one with `FillPortion(2)` and one with + /// `FillPortion(3)`. The first will get 2 portions of the available space, + /// while the second one would get 3. + /// + /// `Length::Fill` is equivalent to `Length::FillPortion(1)`. + FillPortion(u16), + /// Fill the least amount of space Shrink, @@ -22,6 +31,7 @@ impl Length { pub fn fill_factor(&self) -> u16 { match self { Length::Fill => 1, + Length::FillPortion(factor) => *factor, Length::Shrink => 0, Length::Units(_) => 0, } diff --git a/examples/geometry.rs b/examples/geometry.rs new file mode 100644 index 00000000..ae6c9ca0 --- /dev/null +++ b/examples/geometry.rs @@ -0,0 +1,209 @@ +//! This example showcases a simple native custom widget that renders using +//! arbitrary low-level geometry. +mod rainbow { + // For now, to implement a custom native widget you will need to add + // `iced_native` and `iced_wgpu` to your dependencies. + // + // Then, you simply need to define your widget type and implement the + // `iced_native::Widget` trait with the `iced_wgpu::Renderer`. + // + // Of course, you can choose to make the implementation renderer-agnostic, + // if you wish to, by creating your own `Renderer` trait, which could be + // implemented by `iced_wgpu` and other renderers. + use iced_native::{ + layout, Element, Hasher, Layout, Length, MouseCursor, Point, Size, + Widget, + }; + use iced_wgpu::{ + triangle::{Mesh2D, Vertex2D}, + Primitive, Renderer, + }; + + pub struct Rainbow; + + impl Rainbow { + pub fn new() -> Self { + Self + } + } + + impl Widget for Rainbow { + fn width(&self) -> Length { + Length::Fill + } + + fn height(&self) -> Length { + Length::Shrink + } + + fn layout( + &self, + _renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + let size = limits.width(Length::Fill).resolve(Size::ZERO); + + layout::Node::new(Size::new(size.width, size.width)) + } + + fn hash_layout(&self, _state: &mut Hasher) {} + + fn draw( + &self, + _renderer: &mut Renderer, + layout: Layout<'_>, + cursor_position: Point, + ) -> (Primitive, MouseCursor) { + let b = layout.bounds(); + + // R O Y G B I V + let color_r = [1.0, 0.0, 0.0, 1.0]; + let color_o = [1.0, 0.5, 0.0, 1.0]; + let color_y = [1.0, 1.0, 0.0, 1.0]; + let color_g = [0.0, 1.0, 0.0, 1.0]; + let color_gb = [0.0, 1.0, 0.5, 1.0]; + let color_b = [0.0, 0.2, 1.0, 1.0]; + let color_i = [0.5, 0.0, 1.0, 1.0]; + let color_v = [0.75, 0.0, 0.5, 1.0]; + + let posn_center = { + if b.contains(cursor_position) { + [cursor_position.x, cursor_position.y] + } else { + [b.x + (b.width / 2.0), b.y + (b.height / 2.0)] + } + }; + + let posn_tl = [b.x, b.y]; + let posn_t = [b.x + (b.width / 2.0), b.y]; + let posn_tr = [b.x + b.width, b.y]; + let posn_r = [b.x + b.width, b.y + (b.height / 2.0)]; + let posn_br = [b.x + b.width, b.y + b.height]; + let posn_b = [b.x + (b.width / 2.0), b.y + b.height]; + let posn_bl = [b.x, b.y + b.height]; + let posn_l = [b.x, b.y + (b.height / 2.0)]; + + ( + Primitive::Mesh2D(std::sync::Arc::new(Mesh2D { + vertices: vec![ + Vertex2D { + position: posn_center, + color: [1.0, 1.0, 1.0, 1.0], + }, + Vertex2D { + position: posn_tl, + color: color_r, + }, + Vertex2D { + position: posn_t, + color: color_o, + }, + Vertex2D { + position: posn_tr, + color: color_y, + }, + Vertex2D { + position: posn_r, + color: color_g, + }, + Vertex2D { + position: posn_br, + color: color_gb, + }, + Vertex2D { + position: posn_b, + color: color_b, + }, + Vertex2D { + position: posn_bl, + color: color_i, + }, + Vertex2D { + position: posn_l, + color: color_v, + }, + ], + indices: vec![ + 0, 1, 2, // TL + 0, 2, 3, // T + 0, 3, 4, // TR + 0, 4, 5, // R + 0, 5, 6, // BR + 0, 6, 7, // B + 0, 7, 8, // BL + 0, 8, 1, // L + ], + })), + MouseCursor::OutOfBounds, + ) + } + } + + impl<'a, Message> Into> for Rainbow { + fn into(self) -> Element<'a, Message, Renderer> { + Element::new(self) + } + } +} + +use iced::{ + scrollable, Align, Column, Container, Element, Length, Sandbox, Scrollable, + Settings, Text, +}; +use rainbow::Rainbow; + +pub fn main() { + Example::run(Settings::default()) +} + +struct Example { + scroll: scrollable::State, +} + +impl Sandbox for Example { + type Message = (); + + fn new() -> Self { + Example { + scroll: scrollable::State::new(), + } + } + + fn title(&self) -> String { + String::from("Custom 2D geometry - Iced") + } + + fn update(&mut self, _: ()) {} + + fn view(&mut self) -> Element<()> { + let content = Column::new() + .padding(20) + .spacing(20) + .max_width(500) + .align_items(Align::Start) + .push(Rainbow::new()) + .push(Text::new( + "In this example we draw a custom widget Rainbow, using \ + the Mesh2D primitive. This primitive supplies a list of \ + triangles, expressed as vertices and indices.", + )) + .push(Text::new( + "Move your cursor over it, and see the center vertex \ + follow you!", + )) + .push(Text::new( + "Every Vertex2D defines its own color. You could use the \ + Mesh2D primitive to render virtually any two-dimensional \ + geometry for your widget.", + )); + + let scrollable = Scrollable::new(&mut self.scroll) + .push(Container::new(content).width(Length::Fill).center_x()); + + Container::new(scrollable) + .width(Length::Fill) + .height(Length::Fill) + .center_y() + .into() + } +} diff --git a/examples/progress_bar.rs b/examples/progress_bar.rs new file mode 100644 index 00000000..525019b4 --- /dev/null +++ b/examples/progress_bar.rs @@ -0,0 +1,63 @@ +use iced::{ + settings::Window, slider, Background, Color, Column, Element, Length, + ProgressBar, Sandbox, Settings, Slider, +}; + +pub fn main() { + Progress::run(Settings { + window: Window { + size: (700, 300), + resizable: true, + decorations: true, + }, + }) +} + +#[derive(Default)] +struct Progress { + value: f32, + progress_bar_slider: slider::State, +} + +#[derive(Debug, Clone, Copy)] +enum Message { + SliderChanged(f32), +} + +impl Sandbox for Progress { + type Message = Message; + + fn new() -> Self { + Self::default() + } + + fn title(&self) -> String { + String::from("A simple Progressbar") + } + + fn update(&mut self, message: Message) { + match message { + Message::SliderChanged(x) => self.value = x, + } + } + + fn view(&mut self) -> Element { + Column::new() + .padding(20) + .push( + ProgressBar::new(0.0..=100.0, self.value) + .background(Background::Color(Color::from_rgb( + 0.6, 0.6, 0.6, + ))) + .active_color(Color::from_rgb(0.0, 0.95, 0.0)) + .height(Length::Units(30)), + ) + .push(Slider::new( + &mut self.progress_bar_slider, + 0.0..=100.0, + self.value, + Message::SliderChanged, + )) + .into() + } +} diff --git a/examples/tour.rs b/examples/tour.rs index dc5ccb5d..7fe8f4da 100644 --- a/examples/tour.rs +++ b/examples/tour.rs @@ -1,7 +1,7 @@ use iced::{ button, scrollable, slider, text_input, Button, Checkbox, Color, Column, Container, Element, HorizontalAlignment, Image, Length, Radio, Row, - Sandbox, Scrollable, Settings, Slider, Text, TextInput, + Sandbox, Scrollable, Settings, Slider, Space, Text, TextInput, }; pub fn main() { @@ -67,7 +67,7 @@ impl Sandbox for Tour { ); } - controls = controls.push(Column::new().width(Length::Fill)); + controls = controls.push(Space::with_width(Length::Fill)); if steps.can_continue() { controls = controls.push( diff --git a/native/src/layout/limits.rs b/native/src/layout/limits.rs index 2705a47d..a35f7ff7 100644 --- a/native/src/layout/limits.rs +++ b/native/src/layout/limits.rs @@ -52,7 +52,7 @@ impl Limits { Length::Shrink => { self.fill.width = self.min.width; } - Length::Fill => { + Length::Fill | Length::FillPortion(_) => { self.fill.width = self.fill.width.min(self.max.width); } Length::Units(units) => { @@ -76,7 +76,7 @@ impl Limits { Length::Shrink => { self.fill.height = self.min.height; } - Length::Fill => { + Length::Fill | Length::FillPortion(_) => { self.fill.height = self.fill.height.min(self.max.height); } Length::Units(units) => { diff --git a/native/src/mouse_cursor.rs b/native/src/mouse_cursor.rs index a4740a27..c7297e0e 100644 --- a/native/src/mouse_cursor.rs +++ b/native/src/mouse_cursor.rs @@ -22,3 +22,9 @@ pub enum MouseCursor { /// The cursor is over a text widget. Text, } + +impl Default for MouseCursor { + fn default() -> MouseCursor { + MouseCursor::OutOfBounds + } +} diff --git a/native/src/widget.rs b/native/src/widget.rs index 26889280..ccc9b47e 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -25,10 +25,12 @@ pub mod checkbox; pub mod column; pub mod container; pub mod image; +pub mod progress_bar; pub mod radio; pub mod row; pub mod scrollable; pub mod slider; +pub mod space; pub mod svg; pub mod text; pub mod text_input; @@ -44,6 +46,8 @@ pub use container::Container; #[doc(no_inline)] pub use image::Image; #[doc(no_inline)] +pub use progress_bar::ProgressBar; +#[doc(no_inline)] pub use radio::Radio; #[doc(no_inline)] pub use row::Row; @@ -52,6 +56,8 @@ pub use scrollable::Scrollable; #[doc(no_inline)] pub use slider::Slider; #[doc(no_inline)] +pub use space::Space; +#[doc(no_inline)] pub use svg::Svg; #[doc(no_inline)] pub use text::Text; diff --git a/native/src/widget/progress_bar.rs b/native/src/widget/progress_bar.rs new file mode 100644 index 00000000..b1d4fd92 --- /dev/null +++ b/native/src/widget/progress_bar.rs @@ -0,0 +1,173 @@ +//! Provide progress feedback to your users. +use crate::{ + layout, Background, Color, Element, Hasher, Layout, Length, Point, + Rectangle, Size, Widget, +}; + +use std::{hash::Hash, ops::RangeInclusive}; + +/// A bar that displays progress. +/// +/// # Example +/// ``` +/// # use iced_native::ProgressBar; +/// # +/// let value = 50.0; +/// +/// ProgressBar::new(0.0..=100.0, value); +/// ``` +/// +/// ![Progress bar drawn with `iced_wgpu`](https://user-images.githubusercontent.com/18618951/71662391-a316c200-2d51-11ea-9cef-52758cab85e3.png) +#[allow(missing_debug_implementations)] +pub struct ProgressBar { + range: RangeInclusive, + value: f32, + width: Length, + height: Option, + background: Option, + active_color: Option, +} + +impl ProgressBar { + /// Creates a new [`ProgressBar`]. + /// + /// It expects: + /// * an inclusive range of possible values + /// * the current value of the [`ProgressBar`] + /// + /// [`ProgressBar`]: struct.ProgressBar.html + pub fn new(range: RangeInclusive, value: f32) -> Self { + ProgressBar { + value: value.max(*range.start()).min(*range.end()), + range, + width: Length::Fill, + height: None, + background: None, + active_color: None, + } + } + + /// Sets the width of the [`ProgressBar`]. + /// + /// [`ProgressBar`]: struct.ProgressBar.html + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + /// Sets the height of the [`ProgressBar`]. + /// + /// [`ProgressBar`]: struct.ProgressBar.html + pub fn height(mut self, height: Length) -> Self { + self.height = Some(height); + self + } + + /// Sets the background of the [`ProgressBar`]. + /// + /// [`ProgressBar`]: struct.ProgressBar.html + pub fn background(mut self, background: Background) -> Self { + self.background = Some(background); + self + } + + /// Sets the active color of the [`ProgressBar`]. + /// + /// [`ProgressBar`]: struct.ProgressBar.html + pub fn active_color(mut self, active_color: Color) -> Self { + self.active_color = Some(active_color); + self + } +} + +impl Widget for ProgressBar +where + Renderer: self::Renderer, +{ + fn width(&self) -> Length { + self.width + } + + fn height(&self) -> Length { + self.height + .unwrap_or(Length::Units(Renderer::DEFAULT_HEIGHT)) + } + + fn layout( + &self, + _renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + let limits = limits.width(self.width).height( + self.height + .unwrap_or(Length::Units(Renderer::DEFAULT_HEIGHT)), + ); + + let size = limits.resolve(Size::ZERO); + + layout::Node::new(size) + } + + fn draw( + &self, + renderer: &mut Renderer, + layout: Layout<'_>, + _cursor_position: Point, + ) -> Renderer::Output { + renderer.draw( + layout.bounds(), + self.range.clone(), + self.value, + self.background, + self.active_color, + ) + } + + fn hash_layout(&self, state: &mut Hasher) { + self.width.hash(state); + self.height.hash(state); + } +} + +/// The renderer of a [`ProgressBar`]. +/// +/// Your [renderer] will need to implement this trait before being +/// able to use a [`ProgressBar`] in your user interface. +/// +/// [`ProgressBar`]: struct.ProgressBar.html +/// [renderer]: ../../renderer/index.html +pub trait Renderer: crate::Renderer { + /// The default height of a [`ProgressBar`]. + /// + /// [`ProgressBar`]: struct.ProgressBar.html + const DEFAULT_HEIGHT: u16; + + /// Draws a [`ProgressBar`]. + /// + /// It receives: + /// * the bounds of the [`ProgressBar`] + /// * the range of values of the [`ProgressBar`] + /// * the current value of the [`ProgressBar`] + /// * maybe a specific background of the [`ProgressBar`] + /// * maybe a specific active color of the [`ProgressBar`] + /// + /// [`ProgressBar`]: struct.ProgressBar.html + fn draw( + &self, + bounds: Rectangle, + range: RangeInclusive, + value: f32, + background: Option, + active_color: Option, + ) -> Self::Output; +} + +impl<'a, Message, Renderer> From for Element<'a, Message, Renderer> +where + Renderer: self::Renderer, + Message: 'static, +{ + fn from(progress_bar: ProgressBar) -> Element<'a, Message, Renderer> { + Element::new(progress_bar) + } +} diff --git a/native/src/widget/space.rs b/native/src/widget/space.rs new file mode 100644 index 00000000..2029c52f --- /dev/null +++ b/native/src/widget/space.rs @@ -0,0 +1,104 @@ +//! Distribute content vertically. +use std::hash::Hash; + +use crate::{ + layout, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget, +}; + +/// An amount of empty space. +/// +/// It can be useful if you want to fill some space with nothing. +#[derive(Debug)] +pub struct Space { + width: Length, + height: Length, +} + +impl Space { + /// Creates an amount of empty [`Space`] with the given width and height. + /// + /// [`Space`]: struct.Space.html + pub fn new(width: Length, height: Length) -> Self { + Space { width, height } + } + + /// Creates an amount of horizontal [`Space`]. + /// + /// [`Space`]: struct.Space.html + pub fn with_width(width: Length) -> Self { + Space { + width, + height: Length::Shrink, + } + } + + /// Creates an amount of vertical [`Space`]. + /// + /// [`Space`]: struct.Space.html + pub fn with_height(height: Length) -> Self { + Space { + width: Length::Shrink, + height, + } + } +} + +impl<'a, Message, Renderer> Widget for Space +where + Renderer: self::Renderer, +{ + fn width(&self) -> Length { + self.width + } + + fn height(&self) -> Length { + self.height + } + + fn layout( + &self, + _renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + let limits = limits.width(self.width).height(self.height); + + layout::Node::new(limits.resolve(Size::ZERO)) + } + + fn draw( + &self, + renderer: &mut Renderer, + layout: Layout<'_>, + _cursor_position: Point, + ) -> Renderer::Output { + renderer.draw(layout.bounds()) + } + + fn hash_layout(&self, state: &mut Hasher) { + std::any::TypeId::of::().hash(state); + self.width.hash(state); + self.height.hash(state); + } +} + +/// The renderer of an amount of [`Space`]. +/// +/// [`Space`]: struct.Space.html +pub trait Renderer: crate::Renderer { + /// Draws an amount of empty [`Space`]. + /// + /// You should most likely return an empty primitive here. + /// + /// [`Space`]: struct.Space.html + fn draw(&mut self, bounds: Rectangle) -> Self::Output; +} + +impl<'a, Message, Renderer> From for Element<'a, Message, Renderer> +where + Renderer: self::Renderer, + Message: 'static, +{ + fn from(space: Space) -> Element<'a, Message, Renderer> { + Element::new(space) + } +} diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index d3c45fba..f744da27 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -633,7 +633,8 @@ impl Value { .unwrap_or(self.len()) } - /// Returns a new [`Value`] containing the graphemes until the given `index`. + /// Returns a new [`Value`] containing the graphemes until the given + /// `index`. /// /// [`Value`]: struct.Value.html pub fn until(&self, index: usize) -> Self { diff --git a/src/native.rs b/src/native.rs index f06f1c99..022cf337 100644 --- a/src/native.rs +++ b/src/native.rs @@ -1,6 +1,6 @@ pub use iced_winit::{ Align, Background, Color, Command, Font, HorizontalAlignment, Length, - Subscription, VerticalAlignment, + Space, Subscription, VerticalAlignment, }; pub mod widget { @@ -85,7 +85,7 @@ pub mod widget { pub use iced_winit::svg::{Handle, Svg}; } - pub use iced_winit::{Checkbox, Radio, Text}; + pub use iced_winit::{Checkbox, ProgressBar, Radio, Text}; #[doc(no_inline)] pub use { diff --git a/web/README.md b/web/README.md index 762f6c83..6a3da7b4 100644 --- a/web/README.md +++ b/web/README.md @@ -39,7 +39,11 @@ cargo build --example tour --target wasm32-unknown-unknown wasm-bindgen ../target/wasm32-unknown-unknown/debug/examples/tour.wasm --out-dir tour --web ``` -Then, we need to create an `.html` file to load our application: +*__Note:__ Keep in mind that Iced is still in early exploration stages and most of the work needs to happen on the native side of the ecosystem. At this stage, it is important to be able to batch work without having to constantly jump back and forth. Because of this, there is currently no requirement for the `master` branch to contain a cross-platform API at all times. If you hit an issue when building an example and want to help, it may be a good way to [start contributing]!* + +[start contributing]: ../CONTRIBUTING.md + +Once the example is compiled, we need to create an `.html` file to load our application: ```html diff --git a/web/src/style.rs b/web/src/style.rs index 2fb8602a..4f72b22c 100644 --- a/web/src/style.rs +++ b/web/src/style.rs @@ -139,7 +139,7 @@ pub fn length(length: Length) -> String { match length { Length::Shrink => String::from("auto"), Length::Units(px) => format!("{}px", px), - Length::Fill => String::from("100%"), + Length::Fill | Length::FillPortion(_) => String::from("100%"), } } diff --git a/web/src/widget.rs b/web/src/widget.rs index b0e16692..0ac536bd 100644 --- a/web/src/widget.rs +++ b/web/src/widget.rs @@ -28,6 +28,7 @@ mod container; mod image; mod radio; mod row; +mod space; mod text; #[doc(no_inline)] @@ -47,6 +48,7 @@ pub use container::Container; pub use image::Image; pub use radio::Radio; pub use row::Row; +pub use space::Space; /// A component that displays information and allows interaction. /// diff --git a/web/src/widget/image.rs b/web/src/widget/image.rs index ed8b7ecf..413b663e 100644 --- a/web/src/widget/image.rs +++ b/web/src/widget/image.rs @@ -67,7 +67,7 @@ impl Widget for Image { match self.width { Length::Shrink => {} - Length::Fill => { + Length::Fill | Length::FillPortion(_) => { image = image.attr("width", "100%"); } Length::Units(px) => { diff --git a/web/src/widget/space.rs b/web/src/widget/space.rs new file mode 100644 index 00000000..baf4c80b --- /dev/null +++ b/web/src/widget/space.rs @@ -0,0 +1,69 @@ +use crate::{style, Bus, Element, Length, Widget}; +use dodrio::bumpalo; + +/// An amount of empty space. +/// +/// It can be useful if you want to fill some space with nothing. +#[derive(Debug)] +pub struct Space { + width: Length, + height: Length, +} + +impl Space { + /// Creates an amount of empty [`Space`] with the given width and height. + /// + /// [`Space`]: struct.Space.html + pub fn new(width: Length, height: Length) -> Self { + Space { width, height } + } + + /// Creates an amount of horizontal [`Space`]. + /// + /// [`Space`]: struct.Space.html + pub fn with_width(width: Length) -> Self { + Space { + width, + height: Length::Shrink, + } + } + + /// Creates an amount of vertical [`Space`]. + /// + /// [`Space`]: struct.Space.html + pub fn with_height(height: Length) -> Self { + Space { + width: Length::Shrink, + height, + } + } +} + +impl<'a, Message> Widget for Space { + fn node<'b>( + &self, + bump: &'b bumpalo::Bump, + _publish: &Bus, + _style_sheet: &mut style::Sheet<'b>, + ) -> dodrio::Node<'b> { + use dodrio::builder::*; + + let width = style::length(self.width); + let height = style::length(self.height); + + let style = bumpalo::format!( + in bump, + "width: {}; height: {};", + width, + height + ); + + div(bump).attr("style", style.into_bump_str()).finish() + } +} + +impl<'a, Message> From for Element<'a, Message> { + fn from(space: Space) -> Element<'a, Message> { + Element::new(space) + } +} diff --git a/web/src/widget/text_input.rs b/web/src/widget/text_input.rs index 99792c84..eedc25bc 100644 --- a/web/src/widget/text_input.rs +++ b/web/src/widget/text_input.rs @@ -32,6 +32,7 @@ pub struct TextInput<'a, Message> { _state: &'a mut State, placeholder: String, value: String, + is_secure: bool, width: Length, max_width: Length, padding: u16, @@ -64,6 +65,7 @@ impl<'a, Message> TextInput<'a, Message> { _state: state, placeholder: String::from(placeholder), value: String::from(value), + is_secure: false, width: Length::Fill, max_width: Length::Shrink, padding: 0, @@ -73,6 +75,14 @@ impl<'a, Message> TextInput<'a, Message> { } } + /// Converts the [`TextInput`] into a secure password input. + /// + /// [`TextInput`]: struct.TextInput.html + pub fn password(mut self) -> Self { + self.is_secure = true; + self + } + /// Sets the width of the [`TextInput`]. /// /// [`TextInput`]: struct.TextInput.html @@ -161,6 +171,10 @@ where "value", bumpalo::format!(in bump, "{}", self.value).into_bump_str(), ) + .attr( + "type", + bumpalo::format!(in bump, "{}", if self.is_secure { "password" } else { "text" }).into_bump_str(), + ) .on("input", move |root, vdom, event| { let text_input = match event.target().and_then(|t| { t.dyn_into::().ok() diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 9f9ed8db..972f56af 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -24,6 +24,8 @@ #![deny(unused_results)] #![deny(unsafe_code)] #![deny(rust_2018_idioms)] +pub mod triangle; + mod image; mod primitive; mod quad; diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs index 958cc17f..815ba3b0 100644 --- a/wgpu/src/primitive.rs +++ b/wgpu/src/primitive.rs @@ -3,6 +3,9 @@ use iced_native::{ Vector, VerticalAlignment, }; +use crate::triangle; +use std::sync::Arc; + /// A rendering primitive. #[derive(Debug, Clone)] pub enum Primitive { @@ -63,4 +66,14 @@ pub enum Primitive { /// The content of the clip content: Box, }, + /// A low-level primitive to render a mesh of triangles. + /// + /// It can be used to render many kinds of geometry freely. + Mesh2D(Arc), +} + +impl Default for Primitive { + fn default() -> Primitive { + Primitive::None + } } diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index 365ef1ef..050daca9 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -1,9 +1,11 @@ -use crate::{image, quad, text, Image, Primitive, Quad, Transformation}; +use crate::{ + image, quad, text, triangle, Image, Primitive, Quad, Transformation, +}; use iced_native::{ renderer::{Debugger, Windowed}, Background, Color, Layout, MouseCursor, Point, Rectangle, Vector, Widget, }; - +use std::sync::Arc; use wgpu::{ Adapter, BackendBit, CommandEncoderDescriptor, Device, DeviceDescriptor, Extensions, Limits, PowerPreference, Queue, RequestAdapterOptions, @@ -24,6 +26,7 @@ pub struct Renderer { quad_pipeline: quad::Pipeline, image_pipeline: crate::image::Pipeline, text_pipeline: text::Pipeline, + triangle_pipeline: crate::triangle::Pipeline, } struct Layer<'a> { @@ -31,6 +34,7 @@ struct Layer<'a> { offset: Vector, quads: Vec, images: Vec, + meshes: Vec>, text: Vec>, } @@ -42,6 +46,7 @@ impl<'a> Layer<'a> { quads: Vec::new(), images: Vec::new(), text: Vec::new(), + meshes: Vec::new(), } } } @@ -64,6 +69,7 @@ impl Renderer { let text_pipeline = text::Pipeline::new(&mut device); let quad_pipeline = quad::Pipeline::new(&mut device); let image_pipeline = crate::image::Pipeline::new(&mut device); + let triangle_pipeline = triangle::Pipeline::new(&mut device); Self { device, @@ -71,6 +77,7 @@ impl Renderer { quad_pipeline, image_pipeline, text_pipeline, + triangle_pipeline, } } @@ -244,6 +251,9 @@ impl Renderer { scale: [bounds.width, bounds.height], }); } + Primitive::Mesh2D(mesh) => { + layer.meshes.push(mesh.clone()); + } Primitive::Clip { bounds, offset, @@ -322,6 +332,24 @@ impl Renderer { ) { let bounds = layer.bounds * dpi; + if layer.meshes.len() > 0 { + let translated = transformation + * Transformation::translate( + -(layer.offset.x as f32) * dpi, + -(layer.offset.y as f32) * dpi, + ); + + self.triangle_pipeline.draw( + &mut self.device, + encoder, + target, + translated, + dpi, + &layer.meshes, + bounds, + ); + } + if layer.quads.len() > 0 { self.quad_pipeline.draw( &mut self.device, diff --git a/wgpu/src/renderer/widget.rs b/wgpu/src/renderer/widget.rs index 91f107e8..32187c10 100644 --- a/wgpu/src/renderer/widget.rs +++ b/wgpu/src/renderer/widget.rs @@ -2,10 +2,12 @@ mod button; mod checkbox; mod column; mod image; +mod progress_bar; mod radio; mod row; mod scrollable; mod slider; +mod space; mod text; mod text_input; diff --git a/wgpu/src/renderer/widget/progress_bar.rs b/wgpu/src/renderer/widget/progress_bar.rs new file mode 100644 index 00000000..8ed4bab7 --- /dev/null +++ b/wgpu/src/renderer/widget/progress_bar.rs @@ -0,0 +1,47 @@ +use crate::{Primitive, Renderer}; +use iced_native::{progress_bar, Background, Color, MouseCursor, Rectangle}; + +impl progress_bar::Renderer for Renderer { + const DEFAULT_HEIGHT: u16 = 30; + + fn draw( + &self, + bounds: Rectangle, + range: std::ops::RangeInclusive, + value: f32, + background: Option, + active_color: Option, + ) -> Self::Output { + let (range_start, range_end) = range.into_inner(); + let active_progress_width = bounds.width + * ((value - range_start) / (range_end - range_start).max(1.0)); + + let background = Primitive::Group { + primitives: vec![Primitive::Quad { + bounds: Rectangle { ..bounds }, + background: background + .unwrap_or(Background::Color([0.6, 0.6, 0.6].into())) + .into(), + border_radius: 5, + }], + }; + + let active_progress = Primitive::Quad { + bounds: Rectangle { + width: active_progress_width, + ..bounds + }, + background: Background::Color( + active_color.unwrap_or([0.0, 0.95, 0.0].into()), + ), + border_radius: 5, + }; + + ( + Primitive::Group { + primitives: vec![background, active_progress], + }, + MouseCursor::OutOfBounds, + ) + } +} diff --git a/wgpu/src/renderer/widget/space.rs b/wgpu/src/renderer/widget/space.rs new file mode 100644 index 00000000..28e05437 --- /dev/null +++ b/wgpu/src/renderer/widget/space.rs @@ -0,0 +1,8 @@ +use crate::{Primitive, Renderer}; +use iced_native::{space, MouseCursor, Rectangle}; + +impl space::Renderer for Renderer { + fn draw(&mut self, _bounds: Rectangle) -> Self::Output { + (Primitive::None, MouseCursor::OutOfBounds) + } +} diff --git a/wgpu/src/shader/triangle.frag b/wgpu/src/shader/triangle.frag new file mode 100644 index 00000000..e39c45e7 --- /dev/null +++ b/wgpu/src/shader/triangle.frag @@ -0,0 +1,8 @@ +#version 450 + +layout(location = 0) in vec4 i_Color; +layout(location = 0) out vec4 o_Color; + +void main() { + o_Color = i_Color; +} diff --git a/wgpu/src/shader/triangle.frag.spv b/wgpu/src/shader/triangle.frag.spv new file mode 100644 index 00000000..11201872 Binary files /dev/null and b/wgpu/src/shader/triangle.frag.spv differ diff --git a/wgpu/src/shader/triangle.vert b/wgpu/src/shader/triangle.vert new file mode 100644 index 00000000..fd86ecd6 --- /dev/null +++ b/wgpu/src/shader/triangle.vert @@ -0,0 +1,17 @@ +#version 450 + +layout(location = 0) in vec2 i_Position; +layout(location = 1) in vec4 i_Color; + +layout(location = 0) out vec4 o_Color; + +layout (set = 0, binding = 0) uniform Globals { + mat4 u_Transform; + float u_Scale; +}; + +void main() { + vec2 p_Position = i_Position * u_Scale; + gl_Position = u_Transform * vec4(p_Position, 0.0, 1.0); + o_Color = i_Color; +} diff --git a/wgpu/src/shader/triangle.vert.spv b/wgpu/src/shader/triangle.vert.spv new file mode 100644 index 00000000..bc39c451 Binary files /dev/null and b/wgpu/src/shader/triangle.vert.spv differ diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs new file mode 100644 index 00000000..38157d00 --- /dev/null +++ b/wgpu/src/triangle.rs @@ -0,0 +1,239 @@ +//! Draw meshes of triangles. +use crate::Transformation; +use iced_native::Rectangle; +use std::{mem, sync::Arc}; + +#[derive(Debug)] +pub(crate) struct Pipeline { + pipeline: wgpu::RenderPipeline, + constants: wgpu::BindGroup, + constants_buffer: 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 constants_buffer = device + .create_buffer_mapped( + 1, + wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST, + ) + .fill_from_slice(&[Uniforms::default()]); + + let constant_bind_group = + device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &constant_layout, + bindings: &[wgpu::Binding { + binding: 0, + resource: wgpu::BindingResource::Buffer { + buffer: &constants_buffer, + range: 0..std::mem::size_of::() as u64, + }, + }], + }); + + let layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + bind_group_layouts: &[&constant_layout], + }); + + let vs = include_bytes!("shader/triangle.vert.spv"); + let vs_module = device.create_shader_module( + &wgpu::read_spirv(std::io::Cursor::new(&vs[..])) + .expect("Read triangle vertex shader as SPIR-V"), + ); + + let fs = include_bytes!("shader/triangle.frag.spv"); + let fs_module = device.create_shader_module( + &wgpu::read_spirv(std::io::Cursor::new(&fs[..])) + .expect("Read triangle fragment shader as SPIR-V"), + ); + + let pipeline = + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + layout: &layout, + vertex_stage: wgpu::ProgrammableStageDescriptor { + module: &vs_module, + entry_point: "main", + }, + fragment_stage: Some(wgpu::ProgrammableStageDescriptor { + module: &fs_module, + entry_point: "main", + }), + rasterization_state: Some(wgpu::RasterizationStateDescriptor { + front_face: wgpu::FrontFace::Cw, + cull_mode: wgpu::CullMode::None, + depth_bias: 0, + depth_bias_slope_scale: 0.0, + depth_bias_clamp: 0.0, + }), + primitive_topology: wgpu::PrimitiveTopology::TriangleList, + color_states: &[wgpu::ColorStateDescriptor { + format: wgpu::TextureFormat::Bgra8UnormSrgb, + color_blend: wgpu::BlendDescriptor { + src_factor: wgpu::BlendFactor::SrcAlpha, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + alpha_blend: wgpu::BlendDescriptor { + src_factor: wgpu::BlendFactor::One, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + write_mask: wgpu::ColorWrite::ALL, + }], + depth_stencil_state: None, + index_format: wgpu::IndexFormat::Uint16, + vertex_buffers: &[wgpu::VertexBufferDescriptor { + stride: mem::size_of::() as u64, + step_mode: wgpu::InputStepMode::Vertex, + attributes: &[ + // Position + wgpu::VertexAttributeDescriptor { + shader_location: 0, + format: wgpu::VertexFormat::Float2, + offset: 0, + }, + // Color + wgpu::VertexAttributeDescriptor { + shader_location: 1, + format: wgpu::VertexFormat::Float4, + offset: 4 * 2, + }, + ], + }], + sample_count: 1, + sample_mask: !0, + alpha_to_coverage_enabled: false, + }); + + Pipeline { + pipeline, + constants: constant_bind_group, + constants_buffer, + } + } + + pub fn draw( + &mut self, + device: &mut wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + target: &wgpu::TextureView, + transformation: Transformation, + scale: f32, + meshes: &Vec>, + bounds: Rectangle, + ) { + let uniforms = Uniforms { + transform: transformation.into(), + scale, + }; + + let constants_buffer = device + .create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC) + .fill_from_slice(&[uniforms]); + + encoder.copy_buffer_to_buffer( + &constants_buffer, + 0, + &self.constants_buffer, + 0, + std::mem::size_of::() as u64, + ); + + let mut render_pass = + encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + color_attachments: &[ + wgpu::RenderPassColorAttachmentDescriptor { + attachment: target, + resolve_target: None, + load_op: wgpu::LoadOp::Load, + store_op: wgpu::StoreOp::Store, + clear_color: wgpu::Color { + r: 0.0, + g: 0.0, + b: 0.0, + a: 0.0, + }, + }, + ], + depth_stencil_attachment: None, + }); + + for mesh in meshes { + let vertices_buffer = device + .create_buffer_mapped( + mesh.vertices.len(), + wgpu::BufferUsage::VERTEX, + ) + .fill_from_slice(&mesh.vertices); + + let indices_buffer = device + .create_buffer_mapped( + mesh.indices.len(), + wgpu::BufferUsage::INDEX, + ) + .fill_from_slice(&mesh.indices); + + render_pass.set_pipeline(&self.pipeline); + render_pass.set_bind_group(0, &self.constants, &[]); + render_pass.set_index_buffer(&indices_buffer, 0); + render_pass.set_vertex_buffers(0, &[(&vertices_buffer, 0)]); + render_pass.set_scissor_rect( + bounds.x, + bounds.y, + bounds.width, + bounds.height, + ); + + render_pass.draw_indexed(0..mesh.indices.len() as u32, 0, 0..1); + } + } +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +struct Uniforms { + transform: [f32; 16], + scale: f32, +} + +impl Default for Uniforms { + fn default() -> Self { + Self { + transform: *Transformation::identity().as_ref(), + scale: 1.0, + } + } +} + +/// A two-dimensional vertex with some color in __linear__ RGBA. +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct Vertex2D { + /// The vertex position + pub position: [f32; 2], + /// The vertex color in __linear__ RGBA. + pub color: [f32; 4], +} + +/// A set of [`Vertex2D`] and indices representing a list of triangles. +/// +/// [`Vertex2D`]: struct.Vertex2D.html +#[derive(Clone, Debug)] +pub struct Mesh2D { + /// The vertices of the mesh + pub vertices: Vec, + /// The list of vertex indices that defines the triangles of the mesh. + /// + /// Therefore, this list should always have a length that is a multiple of 3. + pub indices: Vec, +} diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index 0537d853..279b975a 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -50,7 +50,8 @@ pub fn button_state(element_state: winit::event::ElementState) -> ButtonState { } } -/// Convert some `ModifiersState` from [`winit`] to an [`iced_native`] modifiers state. +/// Convert some `ModifiersState` from [`winit`] to an [`iced_native`] modifiers +/// state. /// /// [`winit`]: https://github.com/rust-windowing/winit /// [`iced_native`]: https://github.com/hecrj/iced/tree/master/native diff --git a/winit/src/debug/basic.rs b/winit/src/debug/basic.rs index c9da392c..d46edba6 100644 --- a/winit/src/debug/basic.rs +++ b/winit/src/debug/basic.rs @@ -1,5 +1,4 @@ -use std::collections::VecDeque; -use std::time; +use std::{collections::VecDeque, time}; #[derive(Debug)] pub struct Debug {