From 0b5028b1ab47707a469176e9bf20cacdd3a19861 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 19 Apr 2020 14:39:30 +0200 Subject: [PATCH 01/73] Draft `Program` interactivity for `Canvas` --- core/src/rectangle.rs | 10 +++- examples/clock/src/main.rs | 5 +- examples/solar_system/src/main.rs | 5 +- wgpu/src/lib.rs | 2 +- wgpu/src/widget/canvas.rs | 82 ++++++++++++++++++--------- wgpu/src/widget/canvas/drawable.rs | 6 ++ wgpu/src/widget/canvas/event.rs | 6 ++ wgpu/src/widget/canvas/layer/cache.rs | 16 +++++- wgpu/src/widget/canvas/program.rs | 16 ++++++ 9 files changed, 112 insertions(+), 36 deletions(-) create mode 100644 wgpu/src/widget/canvas/event.rs create mode 100644 wgpu/src/widget/canvas/program.rs diff --git a/core/src/rectangle.rs b/core/src/rectangle.rs index aead6e9a55..db8ebfc839 100644 --- a/core/src/rectangle.rs +++ b/core/src/rectangle.rs @@ -1,4 +1,4 @@ -use crate::Point; +use crate::{Point, Size}; /// A rectangle. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] @@ -43,6 +43,14 @@ impl Rectangle { self.y + self.height / 2.0 } + /// Returns the [`Size`] of the [`Rectangle`]. + /// + /// [`Size`]: struct.Size.html + /// [`Rectangle`]: struct.Rectangle.html + pub fn size(&self) -> Size { + Size::new(self.width, self.height) + } + /// Returns true if the given [`Point`] is contained in the [`Rectangle`]. /// /// [`Point`]: struct.Point.html diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs index 827379fadd..2407db652d 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -59,10 +59,9 @@ impl Application for Clock { } fn view(&mut self) -> Element { - let canvas = Canvas::new() + let canvas = Canvas::new(&mut self.clock, &self.now) .width(Length::Units(400)) - .height(Length::Units(400)) - .push(self.clock.with(&self.now)); + .height(Length::Units(400)); Container::new(canvas) .width(Length::Fill) diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs index bcd1dc71df..eeb0796a67 100644 --- a/examples/solar_system/src/main.rs +++ b/examples/solar_system/src/main.rs @@ -66,10 +66,9 @@ impl Application for SolarSystem { } fn view(&mut self) -> Element { - let canvas = Canvas::new() + let canvas = Canvas::new(&mut self.solar_system, &self.state) .width(Length::Fill) - .height(Length::Fill) - .push(self.solar_system.with(&self.state)); + .height(Length::Fill); Container::new(canvas) .width(Length::Fill) diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 799c1f34d3..30b5bb4add 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -20,7 +20,7 @@ //! [`wgpu`]: https://github.com/gfx-rs/wgpu-rs //! [WebGPU API]: https://gpuweb.github.io/gpuweb/ //! [`wgpu_glyph`]: https://github.com/hecrj/wgpu_glyph -#![deny(missing_docs)] +//#![deny(missing_docs)] #![deny(missing_debug_implementations)] #![deny(unused_results)] #![forbid(unsafe_code)] diff --git a/wgpu/src/widget/canvas.rs b/wgpu/src/widget/canvas.rs index 325f90ce6f..de4f02037e 100644 --- a/wgpu/src/widget/canvas.rs +++ b/wgpu/src/widget/canvas.rs @@ -9,7 +9,8 @@ use crate::{Defaults, Primitive, Renderer}; use iced_native::{ - layout, Element, Hasher, Layout, Length, MouseCursor, Point, Size, Widget, + input::mouse, layout, Clipboard, Element, Hasher, Layout, Length, + MouseCursor, Point, Size, Widget, }; use std::hash::Hash; @@ -17,16 +18,20 @@ pub mod layer; pub mod path; mod drawable; +mod event; mod fill; mod frame; +mod program; mod stroke; mod text; pub use drawable::Drawable; +pub use event::Event; pub use fill::Fill; pub use frame::Frame; pub use layer::Layer; pub use path::Path; +pub use program::Program; pub use stroke::{LineCap, LineJoin, Stroke}; pub use text::Text; @@ -81,31 +86,31 @@ pub use text::Text; /// } /// /// // We can use a `Cache` to avoid unnecessary re-tessellation -/// let cache: layer::Cache = layer::Cache::new(); +/// let mut cache: layer::Cache = layer::Cache::new(); /// -/// // Finally, we simply provide the data to our `Cache` and push the resulting -/// // layer into a `Canvas` -/// let canvas = Canvas::new() -/// .push(cache.with(&Circle { radius: 50.0 })); +/// // Finally, we simply use our `Cache` to create the `Canvas`! +/// let canvas = Canvas::new(&mut cache, &Circle { radius: 50.0 }); /// ``` #[derive(Debug)] -pub struct Canvas<'a> { +pub struct Canvas<'a, P: Program> { width: Length, height: Length, - layers: Vec>, + program: &'a mut P, + input: &'a P::Input, } -impl<'a> Canvas<'a> { +impl<'a, P: Program> Canvas<'a, P> { const DEFAULT_SIZE: u16 = 100; /// Creates a new [`Canvas`] with no layers. /// /// [`Canvas`]: struct.Canvas.html - pub fn new() -> Self { + pub fn new(program: &'a mut P, input: &'a P::Input) -> Self { Canvas { width: Length::Units(Self::DEFAULT_SIZE), height: Length::Units(Self::DEFAULT_SIZE), - layers: Vec::new(), + program, + input, } } @@ -124,20 +129,9 @@ impl<'a> Canvas<'a> { self.height = height; self } - - /// Adds a [`Layer`] to the [`Canvas`]. - /// - /// It will be drawn on top of previous layers. - /// - /// [`Layer`]: layer/trait.Layer.html - /// [`Canvas`]: struct.Canvas.html - pub fn push(mut self, layer: impl Layer + 'a) -> Self { - self.layers.push(Box::new(layer)); - self - } } -impl<'a, Message> Widget for Canvas<'a> { +impl<'a, Message, P: Program> Widget for Canvas<'a, P> { fn width(&self) -> Length { self.width } @@ -157,6 +151,37 @@ impl<'a, Message> Widget for Canvas<'a> { layout::Node::new(size) } + fn on_event( + &mut self, + event: iced_native::Event, + layout: Layout<'_>, + cursor_position: Point, + _messages: &mut Vec, + _renderer: &Renderer, + _clipboard: Option<&dyn Clipboard>, + ) { + let bounds = layout.bounds(); + + let canvas_event = match event { + iced_native::Event::Mouse(mouse_event) => { + Some(Event::Mouse(match mouse_event { + mouse::Event::CursorMoved { .. } => { + mouse::Event::CursorMoved { + x: cursor_position.x - bounds.x, + y: cursor_position.y - bounds.y, + } + } + _ => mouse_event, + })) + } + _ => None, + }; + + if let Some(canvas_event) = canvas_event { + self.program.update(canvas_event, bounds.size(), self.input) + } + } + fn draw( &self, _renderer: &mut Renderer, @@ -171,7 +196,8 @@ impl<'a, Message> Widget for Canvas<'a> { ( Primitive::Group { primitives: self - .layers + .program + .layers(self.input) .iter() .map(|layer| Primitive::Cached { origin, @@ -184,18 +210,20 @@ impl<'a, Message> Widget for Canvas<'a> { } fn hash_layout(&self, state: &mut Hasher) { - std::any::TypeId::of::>().hash(state); + struct Marker; + std::any::TypeId::of::().hash(state); self.width.hash(state); self.height.hash(state); } } -impl<'a, Message> From> for Element<'a, Message, Renderer> +impl<'a, Message, P: Program> From> + for Element<'a, Message, Renderer> where Message: 'static, { - fn from(canvas: Canvas<'a>) -> Element<'a, Message, Renderer> { + fn from(canvas: Canvas<'a, P>) -> Element<'a, Message, Renderer> { Element::new(canvas) } } diff --git a/wgpu/src/widget/canvas/drawable.rs b/wgpu/src/widget/canvas/drawable.rs index 6c74071c14..48ba6b4c1f 100644 --- a/wgpu/src/widget/canvas/drawable.rs +++ b/wgpu/src/widget/canvas/drawable.rs @@ -10,3 +10,9 @@ pub trait Drawable { /// [`Frame`]: struct.Frame.html fn draw(&self, frame: &mut Frame); } + +impl<'a> Drawable for dyn Fn(&mut Frame) + 'a { + fn draw(&self, frame: &mut Frame) { + self(frame) + } +} diff --git a/wgpu/src/widget/canvas/event.rs b/wgpu/src/widget/canvas/event.rs new file mode 100644 index 0000000000..7a8b082982 --- /dev/null +++ b/wgpu/src/widget/canvas/event.rs @@ -0,0 +1,6 @@ +use iced_native::input::mouse; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Event { + Mouse(mouse::Event), +} diff --git a/wgpu/src/widget/canvas/layer/cache.rs b/wgpu/src/widget/canvas/layer/cache.rs index 4f8c2bece3..2e87297c11 100644 --- a/wgpu/src/widget/canvas/layer/cache.rs +++ b/wgpu/src/widget/canvas/layer/cache.rs @@ -1,5 +1,5 @@ use crate::{ - canvas::{Drawable, Frame, Layer}, + canvas::{Drawable, Frame, Layer, Program}, Primitive, }; @@ -79,6 +79,20 @@ where } } +impl Program for Cache +where + T: Drawable + std::fmt::Debug, +{ + type Input = T; + + fn layers<'a>( + &'a self, + input: &'a Self::Input, + ) -> Vec> { + vec![Box::new(self.with(input))] + } +} + #[derive(Debug)] struct Bind<'a, T: Drawable> { cache: &'a Cache, diff --git a/wgpu/src/widget/canvas/program.rs b/wgpu/src/widget/canvas/program.rs new file mode 100644 index 0000000000..c65a078b1a --- /dev/null +++ b/wgpu/src/widget/canvas/program.rs @@ -0,0 +1,16 @@ +use crate::canvas::{Event, Layer, Size}; + +pub trait Program { + type Input; + + fn layers<'a>(&'a self, input: &'a Self::Input) + -> Vec>; + + fn update<'a>( + &'a mut self, + _event: Event, + _bounds: Size, + _input: &'a Self::Input, + ) { + } +} From 8ade09a0f6fd6bfdcefebb92ccbbff25d741062a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 19 Apr 2020 14:41:25 +0200 Subject: [PATCH 02/73] Simplify `Canvas` example in documentation --- wgpu/src/widget/canvas.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wgpu/src/widget/canvas.rs b/wgpu/src/widget/canvas.rs index de4f02037e..326bc7d57c 100644 --- a/wgpu/src/widget/canvas.rs +++ b/wgpu/src/widget/canvas.rs @@ -78,7 +78,7 @@ pub use text::Text; /// impl Drawable for Circle { /// fn draw(&self, frame: &mut Frame) { /// // We create a `Path` representing a simple circle -/// let circle = Path::new(|p| p.circle(frame.center(), self.radius)); +/// let circle = Path::circle(frame.center(), self.radius); /// /// // And fill it with some color /// frame.fill(&circle, Fill::Color(Color::BLACK)); From a97acd8fa854a470f26e7c39a62b90f2e2c2c664 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 19 Apr 2020 17:59:32 +0200 Subject: [PATCH 03/73] Use `Borrow` when binding in `layer::Cache` --- wgpu/src/widget/canvas/layer/cache.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/wgpu/src/widget/canvas/layer/cache.rs b/wgpu/src/widget/canvas/layer/cache.rs index 2e87297c11..e8d62b631c 100644 --- a/wgpu/src/widget/canvas/layer/cache.rs +++ b/wgpu/src/widget/canvas/layer/cache.rs @@ -4,7 +4,7 @@ use crate::{ }; use iced_native::Size; -use std::{cell::RefCell, marker::PhantomData, sync::Arc}; +use std::{borrow::Borrow, cell::RefCell, marker::PhantomData, sync::Arc}; enum State { Empty, @@ -71,7 +71,10 @@ where /// [`Cache`]: struct.Cache.html /// [`Layer`]: ../trait.Layer.html /// [`Canvas`]: ../../struct.Canvas.html - pub fn with<'a>(&'a self, input: &'a T) -> impl Layer + 'a { + pub fn with<'a>( + &'a self, + input: impl Borrow + std::fmt::Debug + 'a, + ) -> impl Layer + 'a { Bind { cache: self, input: input, @@ -94,14 +97,15 @@ where } #[derive(Debug)] -struct Bind<'a, T: Drawable> { +struct Bind<'a, T: Drawable, I: Borrow + 'a> { cache: &'a Cache, - input: &'a T, + input: I, } -impl<'a, T> Layer for Bind<'a, T> +impl<'a, T, I> Layer for Bind<'a, T, I> where T: Drawable + std::fmt::Debug, + I: Borrow + std::fmt::Debug + 'a, { fn draw(&self, current_bounds: Size) -> Arc { use std::ops::Deref; @@ -115,7 +119,7 @@ where } let mut frame = Frame::new(current_bounds.width, current_bounds.height); - self.input.draw(&mut frame); + self.input.borrow().draw(&mut frame); let primitive = Arc::new(frame.into_primitive()); From bb424e54c5083402225a0fdda6130de575592dca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 19 Apr 2020 18:48:30 +0200 Subject: [PATCH 04/73] Add interactivity to `solar_system` example --- examples/solar_system/src/main.rs | 87 +++++++++++++++++++++------ wgpu/src/widget/canvas/layer/cache.rs | 50 ++++----------- 2 files changed, 81 insertions(+), 56 deletions(-) diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs index eeb0796a67..8870fe52c9 100644 --- a/examples/solar_system/src/main.rs +++ b/examples/solar_system/src/main.rs @@ -10,6 +10,7 @@ use iced::{ canvas, executor, window, Application, Canvas, Color, Command, Container, Element, Length, Point, Settings, Size, Subscription, Vector, }; +use iced_native::input::{self, mouse}; use std::time::Instant; @@ -22,7 +23,7 @@ pub fn main() { struct SolarSystem { state: State, - solar_system: canvas::layer::Cache, + now: Instant, } #[derive(Debug, Clone, Copy)] @@ -39,7 +40,7 @@ impl Application for SolarSystem { ( SolarSystem { state: State::new(), - solar_system: Default::default(), + now: Instant::now(), }, Command::none(), ) @@ -52,8 +53,8 @@ impl Application for SolarSystem { fn update(&mut self, message: Message) -> Command { match message { Message::Tick(instant) => { - self.state.update(instant); - self.solar_system.clear(); + self.now = instant; + self.state.clear(); } } @@ -66,7 +67,7 @@ impl Application for SolarSystem { } fn view(&mut self) -> Element { - let canvas = Canvas::new(&mut self.solar_system, &self.state) + let canvas = Canvas::new(&mut self.state, &self.now) .width(Length::Fill) .height(Length::Fill); @@ -81,25 +82,21 @@ impl Application for SolarSystem { #[derive(Debug)] struct State { + cache: canvas::layer::Cache, + cursor_position: Point, start: Instant, - current: Instant, stars: Vec<(Point, f32)>, } impl State { - const SUN_RADIUS: f32 = 70.0; - const ORBIT_RADIUS: f32 = 150.0; - const EARTH_RADIUS: f32 = 12.0; - const MOON_RADIUS: f32 = 4.0; - const MOON_DISTANCE: f32 = 28.0; - pub fn new() -> State { let now = Instant::now(); let (width, height) = window::Settings::default().size; State { + cache: Default::default(), + cursor_position: Point::ORIGIN, start: now, - current: now, stars: { use rand::Rng; @@ -120,12 +117,66 @@ impl State { } } - pub fn update(&mut self, now: Instant) { - self.current = now; + pub fn clear(&mut self) { + self.cache.clear(); } } -impl canvas::Drawable for State { +impl canvas::Program for State { + type Input = Instant; + + fn update( + &mut self, + event: canvas::Event, + _bounds: Size, + _input: &Instant, + ) { + match event { + canvas::Event::Mouse(mouse_event) => match mouse_event { + mouse::Event::CursorMoved { x, y } => { + self.cursor_position = Point::new(x, y); + } + mouse::Event::Input { + button: mouse::Button::Left, + state: input::ButtonState::Released, + } => { + self.stars.push((self.cursor_position, 2.0)); + } + _ => {} + }, + } + } + + fn layers<'a>( + &'a self, + now: &'a Instant, + ) -> Vec> { + let system = System { + stars: &self.stars, + start: &self.start, + now, + }; + + vec![Box::new(self.cache.with(system))] + } +} + +#[derive(Debug)] +struct System<'a> { + stars: &'a [(Point, f32)], + start: &'a Instant, + now: &'a Instant, +} + +impl System<'_> { + const SUN_RADIUS: f32 = 70.0; + const ORBIT_RADIUS: f32 = 150.0; + const EARTH_RADIUS: f32 = 12.0; + const MOON_RADIUS: f32 = 4.0; + const MOON_DISTANCE: f32 = 28.0; +} + +impl<'a> canvas::Drawable for System<'a> { fn draw(&self, frame: &mut canvas::Frame) { use canvas::{Path, Stroke}; use std::f32::consts::PI; @@ -135,7 +186,7 @@ impl canvas::Drawable for State { let space = Path::rectangle(Point::new(0.0, 0.0), frame.size()); let stars = Path::new(|path| { - for (p, size) in &self.stars { + for (p, size) in self.stars { path.rectangle(*p, Size::new(*size, *size)); } }); @@ -155,7 +206,7 @@ impl canvas::Drawable for State { }, ); - let elapsed = self.current - self.start; + let elapsed = *self.now - *self.start; let elapsed_seconds = elapsed.as_secs() as f32; let elapsed_millis = elapsed.subsec_millis() as f32; diff --git a/wgpu/src/widget/canvas/layer/cache.rs b/wgpu/src/widget/canvas/layer/cache.rs index e8d62b631c..4ecebb480a 100644 --- a/wgpu/src/widget/canvas/layer/cache.rs +++ b/wgpu/src/widget/canvas/layer/cache.rs @@ -1,5 +1,5 @@ use crate::{ - canvas::{Drawable, Frame, Layer, Program}, + canvas::{Drawable, Frame, Layer}, Primitive, }; @@ -26,34 +26,17 @@ impl Default for State { /// /// [`Layer`]: ../trait.Layer.html /// [`Cache`]: struct.Cache.html -#[derive(Debug)] -pub struct Cache { - input: PhantomData, +#[derive(Debug, Default)] +pub struct Cache { state: RefCell, } -impl Default for Cache -where - T: Drawable, -{ - fn default() -> Self { - Self { - input: PhantomData, - state: Default::default(), - } - } -} - -impl Cache -where - T: Drawable + std::fmt::Debug, -{ +impl Cache { /// Creates a new empty [`Cache`]. /// /// [`Cache`]: struct.Cache.html pub fn new() -> Self { Cache { - input: PhantomData, state: Default::default(), } } @@ -71,35 +54,26 @@ where /// [`Cache`]: struct.Cache.html /// [`Layer`]: ../trait.Layer.html /// [`Canvas`]: ../../struct.Canvas.html - pub fn with<'a>( + pub fn with<'a, T>( &'a self, input: impl Borrow + std::fmt::Debug + 'a, - ) -> impl Layer + 'a { + ) -> impl Layer + 'a + where + T: Drawable + std::fmt::Debug + 'a, + { Bind { cache: self, input: input, + drawable: PhantomData, } } } -impl Program for Cache -where - T: Drawable + std::fmt::Debug, -{ - type Input = T; - - fn layers<'a>( - &'a self, - input: &'a Self::Input, - ) -> Vec> { - vec![Box::new(self.with(input))] - } -} - #[derive(Debug)] struct Bind<'a, T: Drawable, I: Borrow + 'a> { - cache: &'a Cache, + cache: &'a Cache, input: I, + drawable: PhantomData, } impl<'a, T, I> Layer for Bind<'a, T, I> From 592cc685067c36cbba87e4db14f4ebc71d65b951 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 19 Apr 2020 21:55:23 +0200 Subject: [PATCH 05/73] Remove `Layer` trait and simplify `Canvas` --- examples/clock/src/main.rs | 4 +- examples/solar_system/src/main.rs | 138 +++++++++++--------- wgpu/src/widget/canvas.rs | 46 +++---- wgpu/src/widget/canvas/{layer => }/cache.rs | 76 +++++------ wgpu/src/widget/canvas/drawable.rs | 9 ++ wgpu/src/widget/canvas/geometry.rs | 15 +++ wgpu/src/widget/canvas/layer.rs | 25 ---- wgpu/src/widget/canvas/program.rs | 16 --- wgpu/src/widget/canvas/state.rs | 20 +++ 9 files changed, 178 insertions(+), 171 deletions(-) rename wgpu/src/widget/canvas/{layer => }/cache.rs (56%) create mode 100644 wgpu/src/widget/canvas/geometry.rs delete mode 100644 wgpu/src/widget/canvas/layer.rs delete mode 100644 wgpu/src/widget/canvas/program.rs create mode 100644 wgpu/src/widget/canvas/state.rs diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs index 2407db652d..8b3a92c759 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -12,7 +12,7 @@ pub fn main() { struct Clock { now: LocalTime, - clock: canvas::layer::Cache, + clock: canvas::Cache, } #[derive(Debug, Clone, Copy)] @@ -59,7 +59,7 @@ impl Application for Clock { } fn view(&mut self) -> Element { - let canvas = Canvas::new(&mut self.clock, &self.now) + let canvas = Canvas::new(self.clock.with(&self.now)) .width(Length::Units(400)) .height(Length::Units(400)); diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs index 8870fe52c9..618f42063a 100644 --- a/examples/solar_system/src/main.rs +++ b/examples/solar_system/src/main.rs @@ -23,7 +23,6 @@ pub fn main() { struct SolarSystem { state: State, - now: Instant, } #[derive(Debug, Clone, Copy)] @@ -40,7 +39,6 @@ impl Application for SolarSystem { ( SolarSystem { state: State::new(), - now: Instant::now(), }, Command::none(), ) @@ -53,8 +51,7 @@ impl Application for SolarSystem { fn update(&mut self, message: Message) -> Command { match message { Message::Tick(instant) => { - self.now = instant; - self.state.clear(); + self.state.update(instant); } } @@ -67,7 +64,7 @@ impl Application for SolarSystem { } fn view(&mut self) -> Element { - let canvas = Canvas::new(&mut self.state, &self.now) + let canvas = Canvas::new(&mut self.state) .width(Length::Fill) .height(Length::Fill); @@ -82,9 +79,11 @@ impl Application for SolarSystem { #[derive(Debug)] struct State { - cache: canvas::layer::Cache, + space_cache: canvas::Cache, + system_cache: canvas::Cache, cursor_position: Point, start: Instant, + now: Instant, stars: Vec<(Point, f32)>, } @@ -94,43 +93,52 @@ impl State { let (width, height) = window::Settings::default().size; State { - cache: Default::default(), + space_cache: Default::default(), + system_cache: Default::default(), cursor_position: Point::ORIGIN, start: now, - stars: { - use rand::Rng; - - let mut rng = rand::thread_rng(); - - (0..100) - .map(|_| { - ( - Point::new( - rng.gen_range(0.0, width as f32), - rng.gen_range(0.0, height as f32), - ), - rng.gen_range(0.5, 1.0), - ) - }) - .collect() - }, + now, + stars: Self::generate_stars(width, height), } } - pub fn clear(&mut self) { - self.cache.clear(); + pub fn space(&self) -> Space<'_> { + Space { stars: &self.stars } } -} -impl canvas::Program for State { - type Input = Instant; + pub fn system(&self) -> System { + System { + start: self.start, + now: self.now, + } + } + + pub fn update(&mut self, now: Instant) { + self.now = now; + self.system_cache.clear(); + } + + fn generate_stars(width: u32, height: u32) -> Vec<(Point, f32)> { + use rand::Rng; + + let mut rng = rand::thread_rng(); + + (0..100) + .map(|_| { + ( + Point::new( + rng.gen_range(0.0, width as f32), + rng.gen_range(0.0, height as f32), + ), + rng.gen_range(0.5, 1.0), + ) + }) + .collect() + } +} - fn update( - &mut self, - event: canvas::Event, - _bounds: Size, - _input: &Instant, - ) { +impl canvas::State for State { + fn update(&mut self, event: canvas::Event, _bounds: Size) { match event { canvas::Event::Mouse(mouse_event) => match mouse_event { mouse::Event::CursorMoved { x, y } => { @@ -141,34 +149,50 @@ impl canvas::Program for State { state: input::ButtonState::Released, } => { self.stars.push((self.cursor_position, 2.0)); + self.space_cache.clear(); } _ => {} }, } } - fn layers<'a>( - &'a self, - now: &'a Instant, - ) -> Vec> { - let system = System { - stars: &self.stars, - start: &self.start, - now, - }; - - vec![Box::new(self.cache.with(system))] + fn draw(&self, bounds: Size) -> Vec { + vec![ + self.space_cache.draw(bounds, self.space()), + self.system_cache.draw(bounds, self.system()), + ] } } #[derive(Debug)] -struct System<'a> { +struct Space<'a> { stars: &'a [(Point, f32)], - start: &'a Instant, - now: &'a Instant, } -impl System<'_> { +impl canvas::Drawable for Space<'_> { + fn draw(&self, frame: &mut canvas::Frame) { + use canvas::Path; + + let space = Path::rectangle(Point::new(0.0, 0.0), frame.size()); + + let stars = Path::new(|path| { + for (p, size) in self.stars { + path.rectangle(*p, Size::new(*size, *size)); + } + }); + + frame.fill(&space, Color::BLACK); + frame.fill(&stars, Color::WHITE); + } +} + +#[derive(Debug)] +struct System { + start: Instant, + now: Instant, +} + +impl System { const SUN_RADIUS: f32 = 70.0; const ORBIT_RADIUS: f32 = 150.0; const EARTH_RADIUS: f32 = 12.0; @@ -176,26 +200,16 @@ impl System<'_> { const MOON_DISTANCE: f32 = 28.0; } -impl<'a> canvas::Drawable for System<'a> { +impl canvas::Drawable for System { fn draw(&self, frame: &mut canvas::Frame) { use canvas::{Path, Stroke}; use std::f32::consts::PI; let center = frame.center(); - let space = Path::rectangle(Point::new(0.0, 0.0), frame.size()); - - let stars = Path::new(|path| { - for (p, size) in self.stars { - path.rectangle(*p, Size::new(*size, *size)); - } - }); - let sun = Path::circle(center, Self::SUN_RADIUS); let orbit = Path::circle(center, Self::ORBIT_RADIUS); - frame.fill(&space, Color::BLACK); - frame.fill(&stars, Color::WHITE); frame.fill(&sun, Color::from_rgb8(0xF9, 0xD7, 0x1C)); frame.stroke( &orbit, @@ -206,7 +220,7 @@ impl<'a> canvas::Drawable for System<'a> { }, ); - let elapsed = *self.now - *self.start; + let elapsed = self.now - self.start; let elapsed_seconds = elapsed.as_secs() as f32; let elapsed_millis = elapsed.subsec_millis() as f32; diff --git a/wgpu/src/widget/canvas.rs b/wgpu/src/widget/canvas.rs index 326bc7d57c..2b485e18f9 100644 --- a/wgpu/src/widget/canvas.rs +++ b/wgpu/src/widget/canvas.rs @@ -14,24 +14,26 @@ use iced_native::{ }; use std::hash::Hash; -pub mod layer; pub mod path; +mod cache; mod drawable; mod event; mod fill; mod frame; -mod program; +mod geometry; +mod state; mod stroke; mod text; +pub use cache::Cache; pub use drawable::Drawable; pub use event::Event; pub use fill::Fill; pub use frame::Frame; -pub use layer::Layer; +pub use geometry::Geometry; pub use path::Path; -pub use program::Program; +pub use state::State; pub use stroke::{LineCap, LineJoin, Stroke}; pub use text::Text; @@ -65,7 +67,7 @@ pub use text::Text; /// # pub use iced_wgpu::canvas; /// # pub use iced_native::Color; /// # } -/// use iced::canvas::{self, layer, Canvas, Drawable, Fill, Frame, Path}; +/// use iced::canvas::{self, Cache, Canvas, Drawable, Fill, Frame, Path}; /// use iced::Color; /// /// // First, we define the data we need for drawing @@ -86,31 +88,29 @@ pub use text::Text; /// } /// /// // We can use a `Cache` to avoid unnecessary re-tessellation -/// let mut cache: layer::Cache = layer::Cache::new(); +/// let cache = Cache::new(); /// /// // Finally, we simply use our `Cache` to create the `Canvas`! -/// let canvas = Canvas::new(&mut cache, &Circle { radius: 50.0 }); +/// let canvas = Canvas::new(cache.with(Circle { radius: 50.0 })); /// ``` #[derive(Debug)] -pub struct Canvas<'a, P: Program> { +pub struct Canvas { width: Length, height: Length, - program: &'a mut P, - input: &'a P::Input, + state: S, } -impl<'a, P: Program> Canvas<'a, P> { +impl Canvas { const DEFAULT_SIZE: u16 = 100; /// Creates a new [`Canvas`] with no layers. /// /// [`Canvas`]: struct.Canvas.html - pub fn new(program: &'a mut P, input: &'a P::Input) -> Self { + pub fn new(state: S) -> Self { Canvas { width: Length::Units(Self::DEFAULT_SIZE), height: Length::Units(Self::DEFAULT_SIZE), - program, - input, + state, } } @@ -131,7 +131,7 @@ impl<'a, P: Program> Canvas<'a, P> { } } -impl<'a, Message, P: Program> Widget for Canvas<'a, P> { +impl Widget for Canvas { fn width(&self) -> Length { self.width } @@ -178,7 +178,7 @@ impl<'a, Message, P: Program> Widget for Canvas<'a, P> { }; if let Some(canvas_event) = canvas_event { - self.program.update(canvas_event, bounds.size(), self.input) + self.state.update(canvas_event, bounds.size()) } } @@ -196,12 +196,12 @@ impl<'a, Message, P: Program> Widget for Canvas<'a, P> { ( Primitive::Group { primitives: self - .program - .layers(self.input) - .iter() - .map(|layer| Primitive::Cached { + .state + .draw(size) + .into_iter() + .map(|geometry| Primitive::Cached { origin, - cache: layer.draw(size), + cache: geometry.into_primitive(), }) .collect(), }, @@ -218,12 +218,12 @@ impl<'a, Message, P: Program> Widget for Canvas<'a, P> { } } -impl<'a, Message, P: Program> From> +impl<'a, Message, S: State + 'a> From> for Element<'a, Message, Renderer> where Message: 'static, { - fn from(canvas: Canvas<'a, P>) -> Element<'a, Message, Renderer> { + fn from(canvas: Canvas) -> Element<'a, Message, Renderer> { Element::new(canvas) } } diff --git a/wgpu/src/widget/canvas/layer/cache.rs b/wgpu/src/widget/canvas/cache.rs similarity index 56% rename from wgpu/src/widget/canvas/layer/cache.rs rename to wgpu/src/widget/canvas/cache.rs index 4ecebb480a..c88239af88 100644 --- a/wgpu/src/widget/canvas/layer/cache.rs +++ b/wgpu/src/widget/canvas/cache.rs @@ -1,10 +1,10 @@ use crate::{ - canvas::{Drawable, Frame, Layer}, + canvas::{Drawable, Frame, Geometry}, Primitive, }; use iced_native::Size; -use std::{borrow::Borrow, cell::RefCell, marker::PhantomData, sync::Arc}; +use std::{cell::RefCell, sync::Arc}; enum State { Empty, @@ -48,61 +48,51 @@ impl Cache { *self.state.borrow_mut() = State::Empty; } - /// Binds the [`Cache`] with some data, producing a [`Layer`] that can be - /// added to a [`Canvas`]. - /// - /// [`Cache`]: struct.Cache.html - /// [`Layer`]: ../trait.Layer.html - /// [`Canvas`]: ../../struct.Canvas.html - pub fn with<'a, T>( - &'a self, - input: impl Borrow + std::fmt::Debug + 'a, - ) -> impl Layer + 'a + pub fn draw(&self, new_bounds: Size, input: T) -> Geometry where - T: Drawable + std::fmt::Debug + 'a, + T: Drawable + std::fmt::Debug, { - Bind { - cache: self, - input: input, - drawable: PhantomData, - } - } -} - -#[derive(Debug)] -struct Bind<'a, T: Drawable, I: Borrow + 'a> { - cache: &'a Cache, - input: I, - drawable: PhantomData, -} - -impl<'a, T, I> Layer for Bind<'a, T, I> -where - T: Drawable + std::fmt::Debug, - I: Borrow + std::fmt::Debug + 'a, -{ - fn draw(&self, current_bounds: Size) -> Arc { use std::ops::Deref; - if let State::Filled { bounds, primitive } = - self.cache.state.borrow().deref() + if let State::Filled { bounds, primitive } = self.state.borrow().deref() { - if *bounds == current_bounds { - return primitive.clone(); + if *bounds == new_bounds { + return Geometry::from_primitive(primitive.clone()); } } - let mut frame = Frame::new(current_bounds.width, current_bounds.height); - self.input.borrow().draw(&mut frame); + let mut frame = Frame::new(new_bounds.width, new_bounds.height); + input.draw(&mut frame); let primitive = Arc::new(frame.into_primitive()); - *self.cache.state.borrow_mut() = State::Filled { - bounds: current_bounds, + *self.state.borrow_mut() = State::Filled { + bounds: new_bounds, primitive: primitive.clone(), }; - primitive + Geometry::from_primitive(primitive) + } + + pub fn with<'a, T>(&'a self, input: T) -> impl crate::canvas::State + 'a + where + T: Drawable + std::fmt::Debug + 'a, + { + Bind { cache: self, input } + } +} + +struct Bind<'a, T> { + cache: &'a Cache, + input: T, +} + +impl<'a, T> crate::canvas::State for Bind<'a, T> +where + T: Drawable + std::fmt::Debug + 'a, +{ + fn draw(&self, bounds: Size) -> Vec { + vec![self.cache.draw(bounds, &self.input)] } } diff --git a/wgpu/src/widget/canvas/drawable.rs b/wgpu/src/widget/canvas/drawable.rs index 48ba6b4c1f..32258b71bc 100644 --- a/wgpu/src/widget/canvas/drawable.rs +++ b/wgpu/src/widget/canvas/drawable.rs @@ -16,3 +16,12 @@ impl<'a> Drawable for dyn Fn(&mut Frame) + 'a { self(frame) } } + +impl Drawable for &T +where + T: Drawable, +{ + fn draw(&self, frame: &mut Frame) { + T::draw(self, frame) + } +} diff --git a/wgpu/src/widget/canvas/geometry.rs b/wgpu/src/widget/canvas/geometry.rs new file mode 100644 index 0000000000..db7b40544b --- /dev/null +++ b/wgpu/src/widget/canvas/geometry.rs @@ -0,0 +1,15 @@ +use crate::Primitive; +use std::sync::Arc; + +#[derive(Debug)] +pub struct Geometry(Arc); + +impl Geometry { + pub(crate) fn from_primitive(primitive: Arc) -> Self { + Self(primitive) + } + + pub(crate) fn into_primitive(self) -> Arc { + self.0 + } +} diff --git a/wgpu/src/widget/canvas/layer.rs b/wgpu/src/widget/canvas/layer.rs deleted file mode 100644 index a46b7fb147..0000000000 --- a/wgpu/src/widget/canvas/layer.rs +++ /dev/null @@ -1,25 +0,0 @@ -//! Produce, store, and reuse geometry. -mod cache; - -pub use cache::Cache; - -use crate::Primitive; -use iced_native::Size; - -use std::sync::Arc; - -/// A layer that can be presented at a [`Canvas`]. -/// -/// [`Canvas`]: ../struct.Canvas.html -pub trait Layer: std::fmt::Debug { - /// Draws the [`Layer`] in the given bounds and produces a [`Primitive`] as - /// a result. - /// - /// The [`Layer`] may choose to store the produced [`Primitive`] locally and - /// only recompute it when the bounds change, its contents change, or is - /// otherwise explicitly cleared by other means. - /// - /// [`Layer`]: trait.Layer.html - /// [`Primitive`]: ../../../enum.Primitive.html - fn draw(&self, bounds: Size) -> Arc; -} diff --git a/wgpu/src/widget/canvas/program.rs b/wgpu/src/widget/canvas/program.rs deleted file mode 100644 index c65a078b1a..0000000000 --- a/wgpu/src/widget/canvas/program.rs +++ /dev/null @@ -1,16 +0,0 @@ -use crate::canvas::{Event, Layer, Size}; - -pub trait Program { - type Input; - - fn layers<'a>(&'a self, input: &'a Self::Input) - -> Vec>; - - fn update<'a>( - &'a mut self, - _event: Event, - _bounds: Size, - _input: &'a Self::Input, - ) { - } -} diff --git a/wgpu/src/widget/canvas/state.rs b/wgpu/src/widget/canvas/state.rs new file mode 100644 index 0000000000..8388f94d3e --- /dev/null +++ b/wgpu/src/widget/canvas/state.rs @@ -0,0 +1,20 @@ +use crate::canvas::{Event, Geometry, Size}; + +pub trait State { + fn update(&mut self, _event: Event, _bounds: Size) {} + + fn draw(&self, bounds: Size) -> Vec; +} + +impl State for &mut T +where + T: State, +{ + fn update(&mut self, event: Event, bounds: Size) { + T::update(self, event, bounds); + } + + fn draw(&self, bounds: Size) -> Vec { + T::draw(self, bounds) + } +} From f59832e88e9e6c72cd65d51de6fbf91bddf4a9d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 19 Apr 2020 21:55:33 +0200 Subject: [PATCH 06/73] Fix alignment in triangle pipeline of `iced_wgpu` --- wgpu/src/triangle.rs | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 86c74fcd75..99365a4bcb 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -231,11 +231,9 @@ impl Pipeline { // We upload everything upfront for (origin, mesh) in meshes { - let transform = Uniforms { - transform: (transformation - * Transformation::translate(origin.x, origin.y)) - .into(), - }; + let transform = (transformation + * Transformation::translate(origin.x, origin.y)) + .into(); let vertex_buffer = device.create_buffer_with_data( mesh.vertices.as_bytes(), @@ -361,12 +359,28 @@ impl Pipeline { #[derive(Debug, Clone, Copy, AsBytes)] struct Uniforms { transform: [f32; 16], + // We need to align this to 256 bytes to please `wgpu`... + // TODO: Be smarter and stop wasting memory! + _padding_a: [f32; 32], + _padding_b: [f32; 16], } impl Default for Uniforms { fn default() -> Self { Self { transform: *Transformation::identity().as_ref(), + _padding_a: [0.0; 32], + _padding_b: [0.0; 16], + } + } +} + +impl From for Uniforms { + fn from(transformation: Transformation) -> Uniforms { + Self { + transform: transformation.into(), + _padding_a: [0.0; 32], + _padding_b: [0.0; 16], } } } From dc97d6f33e1e8f8c276fd1cf1d4ed12892ce3ec9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 28 Apr 2020 01:10:59 +0200 Subject: [PATCH 07/73] Remove interaction from `solar_system` example --- examples/solar_system/src/main.rs | 57 +++++++++---------------------- 1 file changed, 17 insertions(+), 40 deletions(-) diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs index 618f42063a..5392f712a2 100644 --- a/examples/solar_system/src/main.rs +++ b/examples/solar_system/src/main.rs @@ -7,10 +7,9 @@ //! //! [1]: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_animations#An_animated_solar_system use iced::{ - canvas, executor, window, Application, Canvas, Color, Command, Container, - Element, Length, Point, Settings, Size, Subscription, Vector, + canvas, executor, window, Application, Canvas, Color, Command, Element, + Length, Point, Settings, Size, Subscription, Vector, }; -use iced_native::input::{self, mouse}; use std::time::Instant; @@ -64,15 +63,9 @@ impl Application for SolarSystem { } fn view(&mut self) -> Element { - let canvas = Canvas::new(&mut self.state) - .width(Length::Fill) - .height(Length::Fill); - - Container::new(canvas) + Canvas::new(&mut self.state) .width(Length::Fill) .height(Length::Fill) - .center_x() - .center_y() .into() } } @@ -127,8 +120,14 @@ impl State { .map(|_| { ( Point::new( - rng.gen_range(0.0, width as f32), - rng.gen_range(0.0, height as f32), + rng.gen_range( + -(width as f32) / 2.0, + width as f32 / 2.0, + ), + rng.gen_range( + -(height as f32) / 2.0, + height as f32 / 2.0, + ), ), rng.gen_range(0.5, 1.0), ) @@ -138,24 +137,6 @@ impl State { } impl canvas::State for State { - fn update(&mut self, event: canvas::Event, _bounds: Size) { - match event { - canvas::Event::Mouse(mouse_event) => match mouse_event { - mouse::Event::CursorMoved { x, y } => { - self.cursor_position = Point::new(x, y); - } - mouse::Event::Input { - button: mouse::Button::Left, - state: input::ButtonState::Released, - } => { - self.stars.push((self.cursor_position, 2.0)); - self.space_cache.clear(); - } - _ => {} - }, - } - } - fn draw(&self, bounds: Size) -> Vec { vec![ self.space_cache.draw(bounds, self.space()), @@ -182,6 +163,8 @@ impl canvas::Drawable for Space<'_> { }); frame.fill(&space, Color::BLACK); + + frame.translate(frame.center() - Point::ORIGIN); frame.fill(&stars, Color::WHITE); } } @@ -221,15 +204,12 @@ impl canvas::Drawable for System { ); let elapsed = self.now - self.start; - let elapsed_seconds = elapsed.as_secs() as f32; - let elapsed_millis = elapsed.subsec_millis() as f32; + let rotation = (2.0 * PI / 60.0) * elapsed.as_secs() as f32 + + (2.0 * PI / 60_000.0) * elapsed.subsec_millis() as f32; frame.with_save(|frame| { frame.translate(Vector::new(center.x, center.y)); - frame.rotate( - (2.0 * PI / 60.0) * elapsed_seconds - + (2.0 * PI / 60_000.0) * elapsed_millis, - ); + frame.rotate(rotation); frame.translate(Vector::new(Self::ORBIT_RADIUS, 0.0)); let earth = Path::circle(Point::ORIGIN, Self::EARTH_RADIUS); @@ -241,10 +221,7 @@ impl canvas::Drawable for System { frame.fill(&earth, Color::from_rgb8(0x6B, 0x93, 0xD6)); frame.with_save(|frame| { - frame.rotate( - ((2.0 * PI) / 6.0) * elapsed_seconds - + ((2.0 * PI) / 6_000.0) * elapsed_millis, - ); + frame.rotate(rotation * 10.0); frame.translate(Vector::new(0.0, Self::MOON_DISTANCE)); let moon = Path::circle(Point::ORIGIN, Self::MOON_RADIUS); From 6c2e28d20e498526d1c6d624b4018e9392d0fb80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 28 Apr 2020 01:12:27 +0200 Subject: [PATCH 08/73] Implement `std::ops::Sub` for `Point` --- core/src/point.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/core/src/point.rs b/core/src/point.rs index 2b5ad15471..a31bf96788 100644 --- a/core/src/point.rs +++ b/core/src/point.rs @@ -67,3 +67,11 @@ impl std::ops::Sub for Point { } } } + +impl std::ops::Sub for Point { + type Output = Vector; + + fn sub(self, point: Point) -> Vector { + Vector::new(self.x - point.x, self.y - point.y) + } +} From 20d79a43cce5a1bf0cb48a7668ac90d0ac82dfdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 28 Apr 2020 03:06:35 +0200 Subject: [PATCH 09/73] Implement `Default` for `Point` --- core/src/point.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/point.rs b/core/src/point.rs index a31bf96788..3714aa2f04 100644 --- a/core/src/point.rs +++ b/core/src/point.rs @@ -1,7 +1,7 @@ use crate::Vector; /// A 2D point. -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Default)] pub struct Point { /// The X coordinate. pub x: f32, From 56dbd683269b82da16d8eae3f98f352301750bf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 28 Apr 2020 03:11:01 +0200 Subject: [PATCH 10/73] Move reusable `mouse` types to `iced_core` --- {native/src/input => core/src}/button_state.rs | 0 core/src/lib.rs | 3 +++ core/src/mouse.rs | 6 ++++++ {native/src/input => core/src}/mouse/button.rs | 0 {native/src/input => core/src}/mouse/event.rs | 8 ++++---- native/src/input.rs | 4 +--- native/src/input/keyboard.rs | 2 +- native/src/input/mouse.rs | 5 +---- src/lib.rs | 4 ++++ src/mouse.rs | 3 +++ 10 files changed, 23 insertions(+), 12 deletions(-) rename {native/src/input => core/src}/button_state.rs (100%) create mode 100644 core/src/mouse.rs rename {native/src/input => core/src}/mouse/button.rs (100%) rename {native/src/input => core/src}/mouse/event.rs (97%) create mode 100644 src/mouse.rs diff --git a/native/src/input/button_state.rs b/core/src/button_state.rs similarity index 100% rename from native/src/input/button_state.rs rename to core/src/button_state.rs diff --git a/core/src/lib.rs b/core/src/lib.rs index c2887a0be2..606c1b8b6e 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -15,9 +15,11 @@ #![forbid(unsafe_code)] #![forbid(rust_2018_idioms)] pub mod keyboard; +pub mod mouse; mod align; mod background; +mod button_state; mod color; mod font; mod length; @@ -28,6 +30,7 @@ mod vector; pub use align::{Align, HorizontalAlignment, VerticalAlignment}; pub use background::Background; +pub use button_state::ButtonState; pub use color::Color; pub use font::Font; pub use length::Length; diff --git a/core/src/mouse.rs b/core/src/mouse.rs new file mode 100644 index 0000000000..101e04d50c --- /dev/null +++ b/core/src/mouse.rs @@ -0,0 +1,6 @@ +//! Reuse basic mouse types. +mod button; +mod event; + +pub use button::Button; +pub use event::{Event, ScrollDelta}; diff --git a/native/src/input/mouse/button.rs b/core/src/mouse/button.rs similarity index 100% rename from native/src/input/mouse/button.rs rename to core/src/mouse/button.rs diff --git a/native/src/input/mouse/event.rs b/core/src/mouse/event.rs similarity index 97% rename from native/src/input/mouse/event.rs rename to core/src/mouse/event.rs index aafc4fe3b3..52e9d851d9 100644 --- a/native/src/input/mouse/event.rs +++ b/core/src/mouse/event.rs @@ -1,5 +1,5 @@ use super::Button; -use crate::input::ButtonState; +use crate::ButtonState; /// A mouse event. /// @@ -26,11 +26,11 @@ pub enum Event { /// A mouse button was pressed or released. Input { - /// The state of the button - state: ButtonState, - /// The button identifier button: Button, + + /// The state of the button + state: ButtonState, }, /// The mouse wheel was scrolled. diff --git a/native/src/input.rs b/native/src/input.rs index 097fa73071..7f5114c32d 100644 --- a/native/src/input.rs +++ b/native/src/input.rs @@ -2,6 +2,4 @@ pub mod keyboard; pub mod mouse; -mod button_state; - -pub use button_state::ButtonState; +pub use iced_core::ButtonState; diff --git a/native/src/input/keyboard.rs b/native/src/input/keyboard.rs index 928bf49279..220b7f1744 100644 --- a/native/src/input/keyboard.rs +++ b/native/src/input/keyboard.rs @@ -2,4 +2,4 @@ mod event; pub use event::Event; -pub use iced_core::keyboard::{KeyCode, ModifiersState}; +pub use iced_core::keyboard::*; diff --git a/native/src/input/mouse.rs b/native/src/input/mouse.rs index 7198b233e9..ae3f15963b 100644 --- a/native/src/input/mouse.rs +++ b/native/src/input/mouse.rs @@ -1,9 +1,6 @@ //! Build mouse events. -mod button; -mod event; pub mod click; -pub use button::Button; pub use click::Click; -pub use event::{Event, ScrollDelta}; +pub use iced_core::mouse::*; diff --git a/src/lib.rs b/src/lib.rs index 4f66cc73ef..770449847e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -185,6 +185,7 @@ mod sandbox; pub mod executor; pub mod keyboard; +pub mod mouse; pub mod settings; pub mod widget; pub mod window; @@ -208,3 +209,6 @@ pub use runtime::{ futures, Align, Background, Color, Command, Font, HorizontalAlignment, Length, Point, Size, Subscription, Vector, VerticalAlignment, }; + +#[cfg(not(target_arch = "wasm32"))] +pub use runtime::input::ButtonState; diff --git a/src/mouse.rs b/src/mouse.rs new file mode 100644 index 0000000000..8be36d3725 --- /dev/null +++ b/src/mouse.rs @@ -0,0 +1,3 @@ +//! Listen and react to mouse events. +#[cfg(not(target_arch = "wasm32"))] +pub use iced_winit::input::mouse::{Button, Event, ScrollDelta}; From b0825ce38bb9d496193f40e5d2cc7a4654455396 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 28 Apr 2020 03:14:05 +0200 Subject: [PATCH 11/73] Add convenient builder methods to `canvas::Stroke` --- wgpu/src/widget/canvas/stroke.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/wgpu/src/widget/canvas/stroke.rs b/wgpu/src/widget/canvas/stroke.rs index 46d669c42f..e20379cd12 100644 --- a/wgpu/src/widget/canvas/stroke.rs +++ b/wgpu/src/widget/canvas/stroke.rs @@ -14,6 +14,24 @@ pub struct Stroke { pub line_join: LineJoin, } +impl Stroke { + pub fn with_color(self, color: Color) -> Stroke { + Stroke { color, ..self } + } + + pub fn with_width(self, width: f32) -> Stroke { + Stroke { width, ..self } + } + + pub fn with_line_cap(self, line_cap: LineCap) -> Stroke { + Stroke { line_cap, ..self } + } + + pub fn with_line_join(self, line_join: LineJoin) -> Stroke { + Stroke { line_join, ..self } + } +} + impl Default for Stroke { fn default() -> Stroke { Stroke { From 2381a9310c56f60698653f5fd13f5a0d80fa4f67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 28 Apr 2020 03:16:18 +0200 Subject: [PATCH 12/73] Ask for a `Size` in `Frame::new` --- wgpu/src/widget/canvas/cache.rs | 2 +- wgpu/src/widget/canvas/frame.rs | 16 +++++++--------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/wgpu/src/widget/canvas/cache.rs b/wgpu/src/widget/canvas/cache.rs index c88239af88..310bc1d3bf 100644 --- a/wgpu/src/widget/canvas/cache.rs +++ b/wgpu/src/widget/canvas/cache.rs @@ -61,7 +61,7 @@ impl Cache { } } - let mut frame = Frame::new(new_bounds.width, new_bounds.height); + let mut frame = Frame::new(new_bounds); input.draw(&mut frame); let primitive = Arc::new(frame.into_primitive()); diff --git a/wgpu/src/widget/canvas/frame.rs b/wgpu/src/widget/canvas/frame.rs index de4717f163..a951a02901 100644 --- a/wgpu/src/widget/canvas/frame.rs +++ b/wgpu/src/widget/canvas/frame.rs @@ -10,8 +10,7 @@ use crate::{ /// [`Canvas`]: struct.Canvas.html #[derive(Debug)] pub struct Frame { - width: f32, - height: f32, + size: Size, buffers: lyon::tessellation::VertexBuffers, primitives: Vec, transforms: Transforms, @@ -36,10 +35,9 @@ impl Frame { /// top-left corner of its bounds. /// /// [`Frame`]: struct.Frame.html - pub fn new(width: f32, height: f32) -> Frame { + pub fn new(size: Size) -> Frame { Frame { - width, - height, + size, buffers: lyon::tessellation::VertexBuffers::new(), primitives: Vec::new(), transforms: Transforms { @@ -57,7 +55,7 @@ impl Frame { /// [`Frame`]: struct.Frame.html #[inline] pub fn width(&self) -> f32 { - self.width + self.size.width } /// Returns the width of the [`Frame`]. @@ -65,7 +63,7 @@ impl Frame { /// [`Frame`]: struct.Frame.html #[inline] pub fn height(&self) -> f32 { - self.height + self.size.height } /// Returns the dimensions of the [`Frame`]. @@ -73,7 +71,7 @@ impl Frame { /// [`Frame`]: struct.Frame.html #[inline] pub fn size(&self) -> Size { - Size::new(self.width, self.height) + self.size } /// Returns the coordinate of the center of the [`Frame`]. @@ -81,7 +79,7 @@ impl Frame { /// [`Frame`]: struct.Frame.html #[inline] pub fn center(&self) -> Point { - Point::new(self.width / 2.0, self.height / 2.0) + Point::new(self.size.width / 2.0, self.size.height / 2.0) } /// Draws the given [`Path`] on the [`Frame`] by filling it with the From 59b1e90661ee9e479f404bae71029db824cc7b46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 28 Apr 2020 03:18:31 +0200 Subject: [PATCH 13/73] Introduce `Translate` primitive in `iced_wgpu` --- examples/bezier_tool/src/main.rs | 14 ++-- examples/geometry/src/main.rs | 108 +++++++++++++------------- wgpu/src/primitive.rs | 16 ++-- wgpu/src/renderer.rs | 20 +++-- wgpu/src/renderer/widget/pane_grid.rs | 6 +- wgpu/src/triangle.rs | 4 +- wgpu/src/widget/canvas.rs | 24 +++--- wgpu/src/widget/canvas/cache.rs | 12 ++- wgpu/src/widget/canvas/frame.rs | 12 +-- wgpu/src/widget/canvas/geometry.rs | 15 ++-- 10 files changed, 127 insertions(+), 104 deletions(-) diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs index fcb7733c14..8012ea0a11 100644 --- a/examples/bezier_tool/src/main.rs +++ b/examples/bezier_tool/src/main.rs @@ -172,12 +172,14 @@ mod bezier { ) .unwrap(); - let mesh = Primitive::Mesh2D { - origin: Point::new(bounds.x, bounds.y), - buffers: Mesh2D { - vertices: buffer.vertices, - indices: buffer.indices, - }, + let mesh = Primitive::Translate { + translation: Vector::new(bounds.x, bounds.y), + content: Box::new(Primitive::Mesh2D { + buffers: Mesh2D { + vertices: buffer.vertices, + indices: buffer.indices, + }, + }), }; ( diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs index 13a687ab8a..63e1bacddc 100644 --- a/examples/geometry/src/main.rs +++ b/examples/geometry/src/main.rs @@ -12,7 +12,7 @@ mod rainbow { // implemented by `iced_wgpu` and other renderers. use iced_native::{ layout, Element, Hasher, Layout, Length, MouseCursor, Point, Size, - Widget, + Vector, Widget, }; use iced_wgpu::{ triangle::{Mesh2D, Vertex2D}, @@ -85,58 +85,60 @@ mod rainbow { let posn_l = [0.0, b.height / 2.0]; ( - Primitive::Mesh2D { - origin: Point::new(b.x, b.y), - buffers: 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 - ], - }, + Primitive::Translate { + translation: Vector::new(b.x, b.y), + content: Box::new(Primitive::Mesh2D { + buffers: 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, ) diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs index 46d9e6243a..e9c0bf464b 100644 --- a/wgpu/src/primitive.rs +++ b/wgpu/src/primitive.rs @@ -1,5 +1,5 @@ use iced_native::{ - image, svg, Background, Color, Font, HorizontalAlignment, Point, Rectangle, + image, svg, Background, Color, Font, HorizontalAlignment, Rectangle, Vector, VerticalAlignment, }; @@ -70,13 +70,18 @@ pub enum Primitive { /// The content of the clip content: Box, }, + /// A primitive that applies a translation + Translate { + /// The top-left coordinate of the mesh + translation: Vector, + + /// The primitive to translate + content: Box, + }, /// A low-level primitive to render a mesh of triangles. /// /// It can be used to render many kinds of geometry freely. Mesh2D { - /// The top-left coordinate of the mesh - origin: Point, - /// The vertex and index buffers of the mesh buffers: triangle::Mesh2D, }, @@ -85,9 +90,6 @@ pub enum Primitive { /// This can be useful if you are implementing a widget where primitive /// generation is expensive. Cached { - /// The origin of the coordinate system of the cached primitives - origin: Point, - /// The cached primitive cache: Arc, }, diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index 481b310c3e..e93532bcf3 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -29,7 +29,7 @@ pub struct Renderer { struct Layer<'a> { bounds: Rectangle, quads: Vec, - meshes: Vec<(Point, &'a triangle::Mesh2D)>, + meshes: Vec<(Vector, &'a triangle::Mesh2D)>, text: Vec>, #[cfg(any(feature = "image", feature = "svg"))] @@ -214,10 +214,10 @@ impl Renderer { border_color: border_color.into_linear(), }); } - Primitive::Mesh2D { origin, buffers } => { + Primitive::Mesh2D { buffers } => { let layer = layers.last_mut().unwrap(); - layer.meshes.push((*origin + translation, buffers)); + layer.meshes.push((translation, buffers)); } Primitive::Clip { bounds, @@ -249,15 +249,21 @@ impl Renderer { layers.push(new_layer); } } - - Primitive::Cached { origin, cache } => { + Primitive::Translate { + translation: new_translation, + content, + } => { self.draw_primitive( - translation + Vector::new(origin.x, origin.y), - &cache, + translation + *new_translation, + &content, layers, ); } + Primitive::Cached { cache } => { + self.draw_primitive(translation, &cache, layers); + } + #[cfg(feature = "image")] Primitive::Image { handle, bounds } => { let layer = layers.last_mut().unwrap(); diff --git a/wgpu/src/renderer/widget/pane_grid.rs b/wgpu/src/renderer/widget/pane_grid.rs index 2d201fec6b..11ba63479e 100644 --- a/wgpu/src/renderer/widget/pane_grid.rs +++ b/wgpu/src/renderer/widget/pane_grid.rs @@ -59,12 +59,12 @@ impl pane_grid::Renderer for Renderer { height: bounds.height + 0.5, }, offset: Vector::new(0, 0), - content: Box::new(Primitive::Cached { - origin: Point::new( + content: Box::new(Primitive::Translate { + translation: Vector::new( cursor_position.x - bounds.x - bounds.width / 2.0, cursor_position.y - bounds.y - bounds.height / 2.0, ), - cache: std::sync::Arc::new(pane), + content: Box::new(pane), }), }; diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 99365a4bcb..b58cc03c47 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -1,6 +1,6 @@ //! Draw meshes of triangles. use crate::{settings, Transformation}; -use iced_native::{Point, Rectangle}; +use iced_native::{Rectangle, Vector}; use std::mem; use zerocopy::AsBytes; @@ -201,7 +201,7 @@ impl Pipeline { target_width: u32, target_height: u32, transformation: Transformation, - meshes: &[(Point, &Mesh2D)], + meshes: &[(Vector, &Mesh2D)], bounds: Rectangle, ) { // This looks a bit crazy, but we are just counting how many vertices diff --git a/wgpu/src/widget/canvas.rs b/wgpu/src/widget/canvas.rs index 2b485e18f9..744d901e05 100644 --- a/wgpu/src/widget/canvas.rs +++ b/wgpu/src/widget/canvas.rs @@ -10,7 +10,7 @@ use crate::{Defaults, Primitive, Renderer}; use iced_native::{ input::mouse, layout, Clipboard, Element, Hasher, Layout, Length, - MouseCursor, Point, Size, Widget, + MouseCursor, Point, Size, Vector, Widget, }; use std::hash::Hash; @@ -190,20 +190,20 @@ impl Widget for Canvas { _cursor_position: Point, ) -> (Primitive, MouseCursor) { let bounds = layout.bounds(); - let origin = Point::new(bounds.x, bounds.y); + let translation = Vector::new(bounds.x, bounds.y); let size = Size::new(bounds.width, bounds.height); ( - Primitive::Group { - primitives: self - .state - .draw(size) - .into_iter() - .map(|geometry| Primitive::Cached { - origin, - cache: geometry.into_primitive(), - }) - .collect(), + Primitive::Translate { + translation, + content: Box::new(Primitive::Group { + primitives: self + .state + .draw(size) + .into_iter() + .map(Geometry::into_primitive) + .collect(), + }), }, MouseCursor::Idle, ) diff --git a/wgpu/src/widget/canvas/cache.rs b/wgpu/src/widget/canvas/cache.rs index 310bc1d3bf..12cc6442ae 100644 --- a/wgpu/src/widget/canvas/cache.rs +++ b/wgpu/src/widget/canvas/cache.rs @@ -57,21 +57,27 @@ impl Cache { if let State::Filled { bounds, primitive } = self.state.borrow().deref() { if *bounds == new_bounds { - return Geometry::from_primitive(primitive.clone()); + return Geometry::from_primitive(Primitive::Cached { + cache: primitive.clone(), + }); } } let mut frame = Frame::new(new_bounds); input.draw(&mut frame); - let primitive = Arc::new(frame.into_primitive()); + let primitive = { + let geometry = frame.into_geometry(); + + Arc::new(geometry.into_primitive()) + }; *self.state.borrow_mut() = State::Filled { bounds: new_bounds, primitive: primitive.clone(), }; - Geometry::from_primitive(primitive) + Geometry::from_primitive(Primitive::Cached { cache: primitive }) } pub fn with<'a, T>(&'a self, input: T) -> impl crate::canvas::State + 'a diff --git a/wgpu/src/widget/canvas/frame.rs b/wgpu/src/widget/canvas/frame.rs index a951a02901..8623ce4dab 100644 --- a/wgpu/src/widget/canvas/frame.rs +++ b/wgpu/src/widget/canvas/frame.rs @@ -1,7 +1,7 @@ use iced_native::{Point, Rectangle, Size, Vector}; use crate::{ - canvas::{Fill, Path, Stroke, Text}, + canvas::{Fill, Geometry, Path, Stroke, Text}, triangle, Primitive, }; @@ -260,13 +260,13 @@ impl Frame { self.transforms.current.is_identity = false; } - /// Produces the primitive representing everything drawn on the [`Frame`]. + /// Produces the [`Geometry`] representing everything drawn on the [`Frame`]. /// /// [`Frame`]: struct.Frame.html - pub fn into_primitive(mut self) -> Primitive { + /// [`Geometry`]: struct.Geometry.html + pub fn into_geometry(mut self) -> Geometry { if !self.buffers.indices.is_empty() { self.primitives.push(Primitive::Mesh2D { - origin: Point::ORIGIN, buffers: triangle::Mesh2D { vertices: self.buffers.vertices, indices: self.buffers.indices, @@ -274,9 +274,9 @@ impl Frame { }); } - Primitive::Group { + Geometry::from_primitive(Primitive::Group { primitives: self.primitives, - } + }) } } diff --git a/wgpu/src/widget/canvas/geometry.rs b/wgpu/src/widget/canvas/geometry.rs index db7b40544b..12ef828f42 100644 --- a/wgpu/src/widget/canvas/geometry.rs +++ b/wgpu/src/widget/canvas/geometry.rs @@ -1,15 +1,20 @@ use crate::Primitive; -use std::sync::Arc; -#[derive(Debug)] -pub struct Geometry(Arc); +#[derive(Debug, Clone)] +pub struct Geometry(Primitive); impl Geometry { - pub(crate) fn from_primitive(primitive: Arc) -> Self { + pub(crate) fn from_primitive(primitive: Primitive) -> Self { Self(primitive) } - pub(crate) fn into_primitive(self) -> Arc { + pub fn into_primitive(self) -> Primitive { self.0 } } + +impl From for Primitive { + fn from(geometry: Geometry) -> Primitive { + geometry.0 + } +} From 2ca73036ab5946b451fd2b184541ae4dc6eedb24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 28 Apr 2020 03:19:29 +0200 Subject: [PATCH 14/73] Implement `Drawable` for slices of drawables --- wgpu/src/widget/canvas/drawable.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/wgpu/src/widget/canvas/drawable.rs b/wgpu/src/widget/canvas/drawable.rs index 32258b71bc..5209fa6f4c 100644 --- a/wgpu/src/widget/canvas/drawable.rs +++ b/wgpu/src/widget/canvas/drawable.rs @@ -25,3 +25,12 @@ where T::draw(self, frame) } } + +impl Drawable for &[T] +where + T: Drawable, +{ + fn draw(&self, frame: &mut Frame) { + self.iter().for_each(|drawable| drawable.draw(frame)); + } +} From e4eb0553de13053c9828fd5454c281e27e598d65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 28 Apr 2020 03:46:03 +0200 Subject: [PATCH 15/73] Allow `canvas::State` to produce messages --- examples/solar_system/src/main.rs | 2 +- wgpu/src/widget/canvas.rs | 25 +++++++++++++++++-------- wgpu/src/widget/canvas/cache.rs | 7 +++++-- wgpu/src/widget/canvas/state.rs | 14 ++++++++------ 4 files changed, 31 insertions(+), 17 deletions(-) diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs index 5392f712a2..9337c7b5dc 100644 --- a/examples/solar_system/src/main.rs +++ b/examples/solar_system/src/main.rs @@ -136,7 +136,7 @@ impl State { } } -impl canvas::State for State { +impl canvas::State for State { fn draw(&self, bounds: Size) -> Vec { vec![ self.space_cache.draw(bounds, self.space()), diff --git a/wgpu/src/widget/canvas.rs b/wgpu/src/widget/canvas.rs index 744d901e05..044bc3eca4 100644 --- a/wgpu/src/widget/canvas.rs +++ b/wgpu/src/widget/canvas.rs @@ -13,6 +13,7 @@ use iced_native::{ MouseCursor, Point, Size, Vector, Widget, }; use std::hash::Hash; +use std::marker::PhantomData; pub mod path; @@ -91,16 +92,17 @@ pub use text::Text; /// let cache = Cache::new(); /// /// // Finally, we simply use our `Cache` to create the `Canvas`! -/// let canvas = Canvas::new(cache.with(Circle { radius: 50.0 })); +/// let canvas: Canvas<_, ()> = Canvas::new(cache.with(Circle { radius: 50.0 })); /// ``` #[derive(Debug)] -pub struct Canvas { +pub struct Canvas, Message> { width: Length, height: Length, state: S, + phantom: PhantomData, } -impl Canvas { +impl> Canvas { const DEFAULT_SIZE: u16 = 100; /// Creates a new [`Canvas`] with no layers. @@ -111,6 +113,7 @@ impl Canvas { width: Length::Units(Self::DEFAULT_SIZE), height: Length::Units(Self::DEFAULT_SIZE), state, + phantom: PhantomData, } } @@ -131,7 +134,9 @@ impl Canvas { } } -impl Widget for Canvas { +impl> Widget + for Canvas +{ fn width(&self) -> Length { self.width } @@ -156,7 +161,7 @@ impl Widget for Canvas { event: iced_native::Event, layout: Layout<'_>, cursor_position: Point, - _messages: &mut Vec, + messages: &mut Vec, _renderer: &Renderer, _clipboard: Option<&dyn Clipboard>, ) { @@ -178,7 +183,11 @@ impl Widget for Canvas { }; if let Some(canvas_event) = canvas_event { - self.state.update(canvas_event, bounds.size()) + if let Some(message) = + self.state.update(canvas_event, bounds.size()) + { + messages.push(message); + } } } @@ -218,12 +227,12 @@ impl Widget for Canvas { } } -impl<'a, Message, S: State + 'a> From> +impl<'a, Message, S: State + 'a> From> for Element<'a, Message, Renderer> where Message: 'static, { - fn from(canvas: Canvas) -> Element<'a, Message, Renderer> { + fn from(canvas: Canvas) -> Element<'a, Message, Renderer> { Element::new(canvas) } } diff --git a/wgpu/src/widget/canvas/cache.rs b/wgpu/src/widget/canvas/cache.rs index 12cc6442ae..2beed0f77b 100644 --- a/wgpu/src/widget/canvas/cache.rs +++ b/wgpu/src/widget/canvas/cache.rs @@ -80,7 +80,10 @@ impl Cache { Geometry::from_primitive(Primitive::Cached { cache: primitive }) } - pub fn with<'a, T>(&'a self, input: T) -> impl crate::canvas::State + 'a + pub fn with<'a, T, Message>( + &'a self, + input: T, + ) -> impl crate::canvas::State + 'a where T: Drawable + std::fmt::Debug + 'a, { @@ -93,7 +96,7 @@ struct Bind<'a, T> { input: T, } -impl<'a, T> crate::canvas::State for Bind<'a, T> +impl<'a, T, Message> crate::canvas::State for Bind<'a, T> where T: Drawable + std::fmt::Debug + 'a, { diff --git a/wgpu/src/widget/canvas/state.rs b/wgpu/src/widget/canvas/state.rs index 8388f94d3e..ab433dd1a0 100644 --- a/wgpu/src/widget/canvas/state.rs +++ b/wgpu/src/widget/canvas/state.rs @@ -1,17 +1,19 @@ use crate::canvas::{Event, Geometry, Size}; -pub trait State { - fn update(&mut self, _event: Event, _bounds: Size) {} +pub trait State { + fn update(&mut self, _event: Event, _bounds: Size) -> Option { + None + } fn draw(&self, bounds: Size) -> Vec; } -impl State for &mut T +impl State for &mut T where - T: State, + T: State, { - fn update(&mut self, event: Event, bounds: Size) { - T::update(self, event, bounds); + fn update(&mut self, event: Event, bounds: Size) -> Option { + T::update(self, event, bounds) } fn draw(&self, bounds: Size) -> Vec { From fd1ceac3633c4f60852eb9f75da9fbb5e1f35df3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 28 Apr 2020 03:47:41 +0200 Subject: [PATCH 16/73] Port `bezier_tool` example to use `Canvas` --- examples/README.md | 2 +- examples/bezier_tool/Cargo.toml | 5 +- examples/bezier_tool/README.md | 3 +- examples/bezier_tool/src/main.rs | 439 +++++++++++-------------------- 4 files changed, 150 insertions(+), 299 deletions(-) diff --git a/examples/README.md b/examples/README.md index 5aea51ebf1..5d880d7169 100644 --- a/examples/README.md +++ b/examples/README.md @@ -69,7 +69,7 @@ cargo run --package styling ## Extras A bunch of simpler examples exist: -- [`bezier_tool`](bezier_tool), a Paint-like tool for drawing Bézier curves using [`lyon`]. +- [`bezier_tool`](bezier_tool), a Paint-like tool for drawing Bézier curves using the `Canvas` widget. - [`clock`](clock), an application that uses the `Canvas` widget to draw a clock and its hands to display the current time. - [`counter`](counter), the classic counter example explained in the [`README`](../README.md). - [`custom_widget`](custom_widget), a demonstration of how to build a custom widget that draws a circle. diff --git a/examples/bezier_tool/Cargo.toml b/examples/bezier_tool/Cargo.toml index b13a0aa54d..a88975a7de 100644 --- a/examples/bezier_tool/Cargo.toml +++ b/examples/bezier_tool/Cargo.toml @@ -6,7 +6,4 @@ edition = "2018" publish = false [dependencies] -iced = { path = "../.." } -iced_native = { path = "../../native" } -iced_wgpu = { path = "../../wgpu" } -lyon = "0.15" +iced = { path = "../..", features = ["canvas"] } diff --git a/examples/bezier_tool/README.md b/examples/bezier_tool/README.md index 933f21200e..ebbb12cce3 100644 --- a/examples/bezier_tool/README.md +++ b/examples/bezier_tool/README.md @@ -1,6 +1,6 @@ ## Bézier tool -A Paint-like tool for drawing Bézier curves using [`lyon`]. +A Paint-like tool for drawing Bézier curves using the `Canvas` widget. The __[`main`]__ file contains all the code of the example. @@ -16,4 +16,3 @@ cargo run --package bezier_tool ``` [`main`]: src/main.rs -[`lyon`]: https://github.com/nical/lyon diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs index 8012ea0a11..a00a100513 100644 --- a/examples/bezier_tool/src/main.rs +++ b/examples/bezier_tool/src/main.rs @@ -1,290 +1,4 @@ -//! This example showcases a simple native custom widget that renders arbitrary -//! path with `lyon`. -mod bezier { - // 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::{ - input, layout, Clipboard, Color, Element, Event, Font, Hasher, - HorizontalAlignment, Layout, Length, MouseCursor, Point, Rectangle, - Size, Vector, VerticalAlignment, Widget, - }; - use iced_wgpu::{ - triangle::{Mesh2D, Vertex2D}, - Defaults, Primitive, Renderer, - }; - use lyon::tessellation::{ - basic_shapes, BuffersBuilder, StrokeAttributes, StrokeOptions, - StrokeTessellator, VertexBuffers, - }; - - pub struct Bezier<'a, Message> { - state: &'a mut State, - curves: &'a [Curve], - // [from, to, ctrl] - on_click: Box Message>, - } - - #[derive(Debug, Clone, Copy)] - pub struct Curve { - from: Point, - to: Point, - control: Point, - } - - #[derive(Default)] - pub struct State { - pending: Option, - } - - enum Pending { - One { from: Point }, - Two { from: Point, to: Point }, - } - - impl<'a, Message> Bezier<'a, Message> { - pub fn new( - state: &'a mut State, - curves: &'a [Curve], - on_click: F, - ) -> Self - where - F: 'static + Fn(Curve) -> Message, - { - Self { - state, - curves, - on_click: Box::new(on_click), - } - } - } - - impl<'a, Message> Widget for Bezier<'a, Message> { - fn width(&self) -> Length { - Length::Fill - } - - fn height(&self) -> Length { - Length::Fill - } - - fn layout( - &self, - _renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - let size = limits - .height(Length::Fill) - .width(Length::Fill) - .resolve(Size::ZERO); - layout::Node::new(size) - } - - fn draw( - &self, - _renderer: &mut Renderer, - defaults: &Defaults, - layout: Layout<'_>, - cursor_position: Point, - ) -> (Primitive, MouseCursor) { - let mut buffer: VertexBuffers = VertexBuffers::new(); - let mut path_builder = lyon::path::Path::builder(); - - let bounds = layout.bounds(); - - // Draw rectangle border with lyon. - basic_shapes::stroke_rectangle( - &lyon::math::Rect::new( - lyon::math::Point::new(0.5, 0.5), - lyon::math::Size::new( - bounds.width - 1.0, - bounds.height - 1.0, - ), - ), - &StrokeOptions::default().with_line_width(1.0), - &mut BuffersBuilder::new( - &mut buffer, - |pos: lyon::math::Point, _: StrokeAttributes| Vertex2D { - position: pos.to_array(), - color: [0.0, 0.0, 0.0, 1.0], - }, - ), - ) - .unwrap(); - - for curve in self.curves { - path_builder.move_to(lyon::math::Point::new( - curve.from.x, - curve.from.y, - )); - - path_builder.quadratic_bezier_to( - lyon::math::Point::new(curve.control.x, curve.control.y), - lyon::math::Point::new(curve.to.x, curve.to.y), - ); - } - - match self.state.pending { - None => {} - Some(Pending::One { from }) => { - path_builder - .move_to(lyon::math::Point::new(from.x, from.y)); - path_builder.line_to(lyon::math::Point::new( - cursor_position.x - bounds.x, - cursor_position.y - bounds.y, - )); - } - Some(Pending::Two { from, to }) => { - path_builder - .move_to(lyon::math::Point::new(from.x, from.y)); - path_builder.quadratic_bezier_to( - lyon::math::Point::new( - cursor_position.x - bounds.x, - cursor_position.y - bounds.y, - ), - lyon::math::Point::new(to.x, to.y), - ); - } - } - - let mut tessellator = StrokeTessellator::new(); - - // Draw strokes with lyon. - tessellator - .tessellate( - &path_builder.build(), - &StrokeOptions::default().with_line_width(3.0), - &mut BuffersBuilder::new( - &mut buffer, - |pos: lyon::math::Point, _: StrokeAttributes| { - Vertex2D { - position: pos.to_array(), - color: [0.0, 0.0, 0.0, 1.0], - } - }, - ), - ) - .unwrap(); - - let mesh = Primitive::Translate { - translation: Vector::new(bounds.x, bounds.y), - content: Box::new(Primitive::Mesh2D { - buffers: Mesh2D { - vertices: buffer.vertices, - indices: buffer.indices, - }, - }), - }; - - ( - Primitive::Clip { - bounds, - offset: Vector::new(0, 0), - content: Box::new( - if self.curves.is_empty() - && self.state.pending.is_none() - { - let instructions = Primitive::Text { - bounds: Rectangle { - x: bounds.center_x(), - y: bounds.center_y(), - ..bounds - }, - color: Color { - a: defaults.text.color.a * 0.7, - ..defaults.text.color - }, - content: String::from( - "Click to create bezier curves!", - ), - font: Font::Default, - size: 30.0, - horizontal_alignment: - HorizontalAlignment::Center, - vertical_alignment: VerticalAlignment::Center, - }; - - Primitive::Group { - primitives: vec![mesh, instructions], - } - } else { - mesh - }, - ), - }, - MouseCursor::OutOfBounds, - ) - } - - fn hash_layout(&self, _state: &mut Hasher) {} - - fn on_event( - &mut self, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - messages: &mut Vec, - _renderer: &Renderer, - _clipboard: Option<&dyn Clipboard>, - ) { - let bounds = layout.bounds(); - - if bounds.contains(cursor_position) { - match event { - Event::Mouse(input::mouse::Event::Input { - state: input::ButtonState::Pressed, - .. - }) => { - let new_point = Point::new( - cursor_position.x - bounds.x, - cursor_position.y - bounds.y, - ); - - match self.state.pending { - None => { - self.state.pending = - Some(Pending::One { from: new_point }); - } - Some(Pending::One { from }) => { - self.state.pending = Some(Pending::Two { - from, - to: new_point, - }); - } - Some(Pending::Two { from, to }) => { - self.state.pending = None; - - messages.push((self.on_click)(Curve { - from, - to, - control: new_point, - })); - } - } - } - _ => {} - } - } - } - } - - impl<'a, Message> Into> for Bezier<'a, Message> - where - Message: 'static, - { - fn into(self) -> Element<'a, Message, Renderer> { - Element::new(self) - } - } -} - -use bezier::Bezier; +//! This example showcases an interactive `Canvas` for drawing Bézier curves. use iced::{ button, Align, Button, Column, Container, Element, Length, Sandbox, Settings, Text, @@ -325,6 +39,7 @@ impl Sandbox for Example { match message { Message::AddCurve(curve) => { self.curves.push(curve); + self.bezier.request_redraw(); } Message::Clear => { self.bezier = bezier::State::default(); @@ -343,11 +58,7 @@ impl Sandbox for Example { .width(Length::Shrink) .size(50), ) - .push(Bezier::new( - &mut self.bezier, - self.curves.as_slice(), - Message::AddCurve, - )) + .push(self.bezier.view(&self.curves).map(Message::AddCurve)) .push( Button::new(&mut self.button_state, Text::new("Clear")) .padding(8) @@ -362,3 +73,147 @@ impl Sandbox for Example { .into() } } + +mod bezier { + use iced::{ + canvas::{ + self, Canvas, Drawable, Event, Frame, Geometry, Path, Stroke, + }, + mouse, ButtonState, Element, Length, Point, Size, + }; + + #[derive(Default)] + pub struct State { + pending: Option, + cursor_position: Point, + cache: canvas::Cache, + } + + impl State { + pub fn view<'a>( + &'a mut self, + curves: &'a [Curve], + ) -> Element<'a, Curve> { + Canvas::new(Bezier { + state: self, + curves, + }) + .width(Length::Fill) + .height(Length::Fill) + .into() + } + + pub fn request_redraw(&mut self) { + self.cache.clear() + } + } + + struct Bezier<'a> { + state: &'a mut State, + curves: &'a [Curve], + } + + impl<'a> canvas::State for Bezier<'a> { + fn update(&mut self, event: Event, _bounds: Size) -> Option { + match event { + Event::Mouse(mouse_event) => match mouse_event { + mouse::Event::CursorMoved { x, y } => { + self.state.cursor_position = Point::new(x, y); + + None + } + mouse::Event::Input { + button: mouse::Button::Left, + state: ButtonState::Pressed, + } => match self.state.pending { + None => { + self.state.pending = Some(Pending::One { + from: self.state.cursor_position, + }); + None + } + Some(Pending::One { from }) => { + self.state.pending = Some(Pending::Two { + from, + to: self.state.cursor_position, + }); + + None + } + Some(Pending::Two { from, to }) => { + self.state.pending = None; + + Some(Curve { + from, + to, + control: self.state.cursor_position, + }) + } + }, + _ => None, + }, + } + } + + fn draw(&self, bounds: Size) -> Vec { + let curves = self.state.cache.draw(bounds, &self.curves); + + if let Some(pending) = &self.state.pending { + let pending_curve = + pending.draw(bounds, self.state.cursor_position); + + vec![curves, pending_curve] + } else { + vec![curves] + } + } + } + + #[derive(Debug, Clone, Copy)] + pub struct Curve { + from: Point, + to: Point, + control: Point, + } + + impl Drawable for Curve { + fn draw(&self, frame: &mut Frame) { + let curve = Path::new(|p| { + p.move_to(self.from); + p.quadratic_curve_to(self.control, self.to); + }); + + frame.stroke(&curve, Stroke::default().with_width(2.0)); + } + } + + #[derive(Debug, Clone, Copy)] + enum Pending { + One { from: Point }, + Two { from: Point, to: Point }, + } + + impl Pending { + fn draw(&self, bounds: Size, cursor_position: Point) -> Geometry { + let mut frame = Frame::new(bounds); + + match *self { + Pending::One { from } => { + let line = Path::line(from, cursor_position); + frame.stroke(&line, Stroke::default().with_width(2.0)); + } + Pending::Two { from, to } => { + let curve = Curve { + from, + to, + control: cursor_position, + }; + + curve.draw(&mut frame); + } + }; + + frame.into_geometry() + } + } +} From 5d5e60a5cc647b3227f78e1f3a3b31b2c27a3cc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 28 Apr 2020 04:39:59 +0200 Subject: [PATCH 17/73] Implement `Rectangle::new` --- core/src/rectangle.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/core/src/rectangle.rs b/core/src/rectangle.rs index db8ebfc839..b9d0d5d233 100644 --- a/core/src/rectangle.rs +++ b/core/src/rectangle.rs @@ -17,6 +17,21 @@ pub struct Rectangle { } impl Rectangle { + /// Creates a new [`Rectangle`] with its top-left corner in the given + /// [`Point`] and with the provided [`Size`]. + /// + /// [`Rectangle`]: struct.Rectangle.html + /// [`Point`]: struct.Point.html + /// [`Size`]: struct.Size.html + pub fn new(top_left: Point, size: Size) -> Self { + Self { + x: top_left.x, + y: top_left.y, + width: size.width, + height: size.height, + } + } + /// Returns the [`Point`] at the center of the [`Rectangle`]. /// /// [`Point`]: struct.Point.html From 69c60d372c18c20856f733efd337ac302b7de926 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 28 Apr 2020 04:40:23 +0200 Subject: [PATCH 18/73] Implement `std::ops::Add` for `Rectangle` --- core/src/rectangle.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/core/src/rectangle.rs b/core/src/rectangle.rs index b9d0d5d233..87046df418 100644 --- a/core/src/rectangle.rs +++ b/core/src/rectangle.rs @@ -1,4 +1,4 @@ -use crate::{Point, Size}; +use crate::{Point, Size, Vector}; /// A rectangle. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] @@ -140,3 +140,18 @@ impl From> for Rectangle { } } } + +impl std::ops::Add> for Rectangle +where + T: std::ops::Add, +{ + type Output = Rectangle; + + fn add(self, translation: Vector) -> Self { + Rectangle { + x: self.x + translation.x, + y: self.y + translation.y, + ..self + } + } +} From e65585ae17bf2fae1bbad1cde839d6f767a29b82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 28 Apr 2020 04:41:09 +0200 Subject: [PATCH 19/73] Clip and cull `Mesh2D` primitives in `iced_wgpu` --- examples/geometry/src/main.rs | 1 + wgpu/src/primitive.rs | 9 +++++++-- wgpu/src/renderer.rs | 36 +++++++++++++++++++++------------ wgpu/src/triangle.rs | 23 ++++++++++++--------- wgpu/src/widget/canvas/frame.rs | 1 + 5 files changed, 45 insertions(+), 25 deletions(-) diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs index 63e1bacddc..795cac2751 100644 --- a/examples/geometry/src/main.rs +++ b/examples/geometry/src/main.rs @@ -88,6 +88,7 @@ mod rainbow { Primitive::Translate { translation: Vector::new(b.x, b.y), content: Box::new(Primitive::Mesh2D { + size: b.size(), buffers: Mesh2D { vertices: vec![ Vertex2D { diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs index e9c0bf464b..e73227efcf 100644 --- a/wgpu/src/primitive.rs +++ b/wgpu/src/primitive.rs @@ -1,5 +1,5 @@ use iced_native::{ - image, svg, Background, Color, Font, HorizontalAlignment, Rectangle, + image, svg, Background, Color, Font, HorizontalAlignment, Rectangle, Size, Vector, VerticalAlignment, }; @@ -72,7 +72,7 @@ pub enum Primitive { }, /// A primitive that applies a translation Translate { - /// The top-left coordinate of the mesh + /// The translation vector translation: Vector, /// The primitive to translate @@ -82,6 +82,11 @@ pub enum Primitive { /// /// It can be used to render many kinds of geometry freely. Mesh2D { + /// The size of the drawable region of the mesh. + /// + /// Any geometry that falls out of this region will be clipped. + size: Size, + /// The vertex and index buffers of the mesh buffers: triangle::Mesh2D, }, diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index e93532bcf3..5c38ce61b2 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -29,7 +29,7 @@ pub struct Renderer { struct Layer<'a> { bounds: Rectangle, quads: Vec, - meshes: Vec<(Vector, &'a triangle::Mesh2D)>, + meshes: Vec<(Vector, Rectangle, &'a triangle::Mesh2D)>, text: Vec>, #[cfg(any(feature = "image", feature = "svg"))] @@ -48,6 +48,12 @@ impl<'a> Layer<'a> { images: Vec::new(), } } + + pub fn intersection(&self, rectangle: Rectangle) -> Option> { + let layer_bounds: Rectangle = self.bounds.into(); + + layer_bounds.intersection(&rectangle).map(Into::into) + } } impl Renderer { @@ -214,10 +220,20 @@ impl Renderer { border_color: border_color.into_linear(), }); } - Primitive::Mesh2D { buffers } => { + Primitive::Mesh2D { size, buffers } => { let layer = layers.last_mut().unwrap(); - layer.meshes.push((translation, buffers)); + // Only draw visible content + if let Some(clip_bounds) = layer.intersection(Rectangle::new( + Point::new(translation.x, translation.y), + *size, + )) { + layer.meshes.push(( + translation, + clip_bounds.into(), + buffers, + )); + } } Primitive::Clip { bounds, @@ -226,16 +242,10 @@ impl Renderer { } => { let layer = layers.last_mut().unwrap(); - let layer_bounds: Rectangle = layer.bounds.into(); - - let clip = Rectangle { - x: bounds.x + translation.x, - y: bounds.y + translation.y, - ..*bounds - }; - // Only draw visible content - if let Some(clip_bounds) = layer_bounds.intersection(&clip) { + if let Some(clip_bounds) = + layer.intersection(*bounds + translation) + { let clip_layer = Layer::new(clip_bounds.into()); let new_layer = Layer::new(layer.bounds); @@ -356,8 +366,8 @@ impl Renderer { target_width, target_height, scaled, + scale_factor, &layer.meshes, - bounds, ); } diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index b58cc03c47..246dc7ce66 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -201,15 +201,15 @@ impl Pipeline { target_width: u32, target_height: u32, transformation: Transformation, - meshes: &[(Vector, &Mesh2D)], - bounds: Rectangle, + scale_factor: f32, + meshes: &[(Vector, Rectangle, &Mesh2D)], ) { // This looks a bit crazy, but we are just counting how many vertices // and indices we will need to handle. // TODO: Improve readability let (total_vertices, total_indices) = meshes .iter() - .map(|(_, mesh)| (mesh.vertices.len(), mesh.indices.len())) + .map(|(_, _, mesh)| (mesh.vertices.len(), mesh.indices.len())) .fold((0, 0), |(total_v, total_i), (v, i)| { (total_v + v, total_i + i) }); @@ -230,7 +230,7 @@ impl Pipeline { let mut last_index = 0; // We upload everything upfront - for (origin, mesh) in meshes { + for (origin, _, mesh) in meshes { let transform = (transformation * Transformation::translate(origin.x, origin.y)) .into(); @@ -316,16 +316,19 @@ impl Pipeline { }); render_pass.set_pipeline(&self.pipeline); - render_pass.set_scissor_rect( - bounds.x, - bounds.y, - bounds.width, - bounds.height, - ); for (i, (vertex_offset, index_offset, indices)) in offsets.into_iter().enumerate() { + let bounds = meshes[i].1 * scale_factor; + + render_pass.set_scissor_rect( + bounds.x, + bounds.y, + bounds.width, + bounds.height, + ); + render_pass.set_bind_group( 0, &self.constants, diff --git a/wgpu/src/widget/canvas/frame.rs b/wgpu/src/widget/canvas/frame.rs index 8623ce4dab..1c4a038a2b 100644 --- a/wgpu/src/widget/canvas/frame.rs +++ b/wgpu/src/widget/canvas/frame.rs @@ -267,6 +267,7 @@ impl Frame { pub fn into_geometry(mut self) -> Geometry { if !self.buffers.indices.is_empty() { self.primitives.push(Primitive::Mesh2D { + size: self.size, buffers: triangle::Mesh2D { vertices: self.buffers.vertices, indices: self.buffers.indices, From 7f1e7aea07bb448471470093a47898b059d940d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 28 Apr 2020 04:41:25 +0200 Subject: [PATCH 20/73] Remove unnecessary `Container` in `bezier_tool` --- examples/bezier_tool/src/main.rs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs index a00a100513..5473bc0733 100644 --- a/examples/bezier_tool/src/main.rs +++ b/examples/bezier_tool/src/main.rs @@ -1,7 +1,6 @@ //! This example showcases an interactive `Canvas` for drawing Bézier curves. use iced::{ - button, Align, Button, Column, Container, Element, Length, Sandbox, - Settings, Text, + button, Align, Button, Column, Element, Length, Sandbox, Settings, Text, }; pub fn main() { @@ -49,7 +48,7 @@ impl Sandbox for Example { } fn view(&mut self) -> Element { - let content = Column::new() + Column::new() .padding(20) .spacing(20) .align_items(Align::Center) @@ -63,13 +62,7 @@ impl Sandbox for Example { Button::new(&mut self.button_state, Text::new("Clear")) .padding(8) .on_press(Message::Clear), - ); - - Container::new(content) - .width(Length::Fill) - .height(Length::Fill) - .center_x() - .center_y() + ) .into() } } From 2539042b71d70afd4d8f262783d441e768811ee9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 28 Apr 2020 06:24:12 +0200 Subject: [PATCH 21/73] Remove `Drawable` and rename `State` to `Program` --- core/src/rectangle.rs | 4 +- examples/bezier_tool/src/main.rs | 91 +++++----- examples/clock/Cargo.toml | 3 - examples/clock/src/main.rs | 98 +++++------ examples/solar_system/Cargo.toml | 3 - examples/solar_system/src/main.rs | 158 +++++++----------- src/lib.rs | 2 +- wgpu/src/widget/canvas.rs | 58 +++---- wgpu/src/widget/canvas/cache.rs | 37 +--- wgpu/src/widget/canvas/drawable.rs | 36 ---- .../widget/canvas/{state.rs => program.rs} | 6 +- 11 files changed, 197 insertions(+), 299 deletions(-) delete mode 100644 wgpu/src/widget/canvas/drawable.rs rename wgpu/src/widget/canvas/{state.rs => program.rs} (80%) diff --git a/core/src/rectangle.rs b/core/src/rectangle.rs index 87046df418..e8d0538a1d 100644 --- a/core/src/rectangle.rs +++ b/core/src/rectangle.rs @@ -135,8 +135,8 @@ impl From> for Rectangle { Rectangle { x: rectangle.x as u32, y: rectangle.y as u32, - width: rectangle.width.ceil() as u32, - height: rectangle.height.ceil() as u32, + width: (rectangle.width + 0.5).round() as u32, + height: (rectangle.height + 0.5).round() as u32, } } } diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs index 5473bc0733..2112f66254 100644 --- a/examples/bezier_tool/src/main.rs +++ b/examples/bezier_tool/src/main.rs @@ -69,10 +69,8 @@ impl Sandbox for Example { mod bezier { use iced::{ - canvas::{ - self, Canvas, Drawable, Event, Frame, Geometry, Path, Stroke, - }, - mouse, ButtonState, Element, Length, Point, Size, + canvas::{self, Canvas, Event, Frame, Geometry, Path, Stroke}, + mouse, ButtonState, Element, Length, Point, Rectangle, Size, }; #[derive(Default)] @@ -106,8 +104,10 @@ mod bezier { curves: &'a [Curve], } - impl<'a> canvas::State for Bezier<'a> { - fn update(&mut self, event: Event, _bounds: Size) -> Option { + impl<'a> canvas::Program for Bezier<'a> { + fn update(&mut self, event: Event, bounds: Size) -> Option { + let bounds = Rectangle::new(Point::ORIGIN, bounds); + match event { Event::Mouse(mouse_event) => match mouse_event { mouse::Event::CursorMoved { x, y } => { @@ -118,46 +118,55 @@ mod bezier { mouse::Event::Input { button: mouse::Button::Left, state: ButtonState::Pressed, - } => match self.state.pending { - None => { - self.state.pending = Some(Pending::One { - from: self.state.cursor_position, - }); - None - } - Some(Pending::One { from }) => { - self.state.pending = Some(Pending::Two { - from, - to: self.state.cursor_position, - }); - - None - } - Some(Pending::Two { from, to }) => { - self.state.pending = None; - - Some(Curve { - from, - to, - control: self.state.cursor_position, - }) + } if bounds.contains(self.state.cursor_position) => { + match self.state.pending { + None => { + self.state.pending = Some(Pending::One { + from: self.state.cursor_position, + }); + None + } + Some(Pending::One { from }) => { + self.state.pending = Some(Pending::Two { + from, + to: self.state.cursor_position, + }); + + None + } + Some(Pending::Two { from, to }) => { + self.state.pending = None; + + Some(Curve { + from, + to, + control: self.state.cursor_position, + }) + } } - }, + } _ => None, }, } } fn draw(&self, bounds: Size) -> Vec { - let curves = self.state.cache.draw(bounds, &self.curves); + let content = self.state.cache.draw(bounds, |frame: &mut Frame| { + Curve::draw_all(self.curves, frame); + + frame.stroke( + &Path::rectangle(Point::ORIGIN, frame.size()), + Stroke::default(), + ); + }); if let Some(pending) = &self.state.pending { let pending_curve = pending.draw(bounds, self.state.cursor_position); - vec![curves, pending_curve] + vec![content, pending_curve] } else { - vec![curves] + vec![content] } } } @@ -169,14 +178,16 @@ mod bezier { control: Point, } - impl Drawable for Curve { - fn draw(&self, frame: &mut Frame) { - let curve = Path::new(|p| { - p.move_to(self.from); - p.quadratic_curve_to(self.control, self.to); + impl Curve { + fn draw_all(curves: &[Curve], frame: &mut Frame) { + let curves = Path::new(|p| { + for curve in curves { + p.move_to(curve.from); + p.quadratic_curve_to(curve.control, curve.to); + } }); - frame.stroke(&curve, Stroke::default().with_width(2.0)); + frame.stroke(&curves, Stroke::default().with_width(2.0)); } } @@ -202,7 +213,7 @@ mod bezier { control: cursor_position, }; - curve.draw(&mut frame); + Curve::draw_all(&[curve], &mut frame); } }; diff --git a/examples/clock/Cargo.toml b/examples/clock/Cargo.toml index 308cbfbb0a..ab7714055d 100644 --- a/examples/clock/Cargo.toml +++ b/examples/clock/Cargo.toml @@ -5,9 +5,6 @@ authors = ["Héctor Ramón Jiménez "] edition = "2018" publish = false -[features] -canvas = [] - [dependencies] iced = { path = "../..", features = ["canvas", "async-std", "debug"] } iced_native = { path = "../../native" } diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs index 8b3a92c759..c2beddef8a 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -1,6 +1,7 @@ use iced::{ - canvas, executor, Application, Canvas, Color, Command, Container, Element, - Length, Point, Settings, Subscription, Vector, + canvas::{self, Cache, Canvas, Geometry, LineCap, Path, Stroke}, + executor, Application, Color, Command, Container, Element, Length, Point, + Settings, Size, Subscription, Vector, }; pub fn main() { @@ -11,8 +12,8 @@ pub fn main() { } struct Clock { - now: LocalTime, - clock: canvas::Cache, + now: chrono::DateTime, + clock: Cache, } #[derive(Debug, Clone, Copy)] @@ -28,7 +29,7 @@ impl Application for Clock { fn new(_flags: ()) -> (Self, Command) { ( Clock { - now: chrono::Local::now().into(), + now: chrono::Local::now(), clock: Default::default(), }, Command::none(), @@ -59,7 +60,7 @@ impl Application for Clock { } fn view(&mut self) -> Element { - let canvas = Canvas::new(self.clock.with(&self.now)) + let canvas = Canvas::new(self) .width(Length::Units(400)) .height(Length::Units(400)); @@ -73,69 +74,54 @@ impl Application for Clock { } } -#[derive(Debug, PartialEq, Eq)] -struct LocalTime { - hour: u32, - minute: u32, - second: u32, -} - -impl From> for LocalTime { - fn from(date_time: chrono::DateTime) -> LocalTime { +impl canvas::Program for Clock { + fn draw(&self, bounds: Size) -> Vec { use chrono::Timelike; - LocalTime { - hour: date_time.hour(), - minute: date_time.minute(), - second: date_time.second(), - } - } -} - -impl canvas::Drawable for LocalTime { - fn draw(&self, frame: &mut canvas::Frame) { - use canvas::Path; + let clock = self.clock.draw(bounds, |frame| { + let center = frame.center(); + let radius = frame.width().min(frame.height()) / 2.0; - let center = frame.center(); - let radius = frame.width().min(frame.height()) / 2.0; + let background = Path::circle(center, radius); + frame.fill(&background, Color::from_rgb8(0x12, 0x93, 0xD8)); - let clock = Path::circle(center, radius); - frame.fill(&clock, Color::from_rgb8(0x12, 0x93, 0xD8)); + let short_hand = + Path::line(Point::ORIGIN, Point::new(0.0, -0.5 * radius)); - let short_hand = - Path::line(Point::ORIGIN, Point::new(0.0, -0.5 * radius)); + let long_hand = + Path::line(Point::ORIGIN, Point::new(0.0, -0.8 * radius)); - let long_hand = - Path::line(Point::ORIGIN, Point::new(0.0, -0.8 * radius)); + let thin_stroke = Stroke { + width: radius / 100.0, + color: Color::WHITE, + line_cap: LineCap::Round, + ..Stroke::default() + }; - let thin_stroke = canvas::Stroke { - width: radius / 100.0, - color: Color::WHITE, - line_cap: canvas::LineCap::Round, - ..canvas::Stroke::default() - }; + let wide_stroke = Stroke { + width: thin_stroke.width * 3.0, + ..thin_stroke + }; - let wide_stroke = canvas::Stroke { - width: thin_stroke.width * 3.0, - ..thin_stroke - }; + frame.translate(Vector::new(center.x, center.y)); - frame.translate(Vector::new(center.x, center.y)); + frame.with_save(|frame| { + frame.rotate(hand_rotation(self.now.hour(), 12)); + frame.stroke(&short_hand, wide_stroke); + }); - frame.with_save(|frame| { - frame.rotate(hand_rotation(self.hour, 12)); - frame.stroke(&short_hand, wide_stroke); - }); + frame.with_save(|frame| { + frame.rotate(hand_rotation(self.now.minute(), 60)); + frame.stroke(&long_hand, wide_stroke); + }); - frame.with_save(|frame| { - frame.rotate(hand_rotation(self.minute, 60)); - frame.stroke(&long_hand, wide_stroke); + frame.with_save(|frame| { + frame.rotate(hand_rotation(self.now.second(), 60)); + frame.stroke(&long_hand, thin_stroke); + }) }); - frame.with_save(|frame| { - frame.rotate(hand_rotation(self.second, 60)); - frame.stroke(&long_hand, thin_stroke); - }); + vec![clock] } } diff --git a/examples/solar_system/Cargo.toml b/examples/solar_system/Cargo.toml index c88cda5043..0555aa9633 100644 --- a/examples/solar_system/Cargo.toml +++ b/examples/solar_system/Cargo.toml @@ -5,9 +5,6 @@ authors = ["Héctor Ramón Jiménez "] edition = "2018" publish = false -[features] -canvas = [] - [dependencies] iced = { path = "../..", features = ["canvas", "async-std", "debug"] } iced_native = { path = "../../native" } diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs index 9337c7b5dc..e2f107bdbd 100644 --- a/examples/solar_system/src/main.rs +++ b/examples/solar_system/src/main.rs @@ -81,6 +81,12 @@ struct State { } impl State { + const SUN_RADIUS: f32 = 70.0; + const ORBIT_RADIUS: f32 = 150.0; + const EARTH_RADIUS: f32 = 12.0; + const MOON_RADIUS: f32 = 4.0; + const MOON_DISTANCE: f32 = 28.0; + pub fn new() -> State { let now = Instant::now(); let (width, height) = window::Settings::default().size; @@ -95,17 +101,6 @@ impl State { } } - pub fn space(&self) -> Space<'_> { - Space { stars: &self.stars } - } - - pub fn system(&self) -> System { - System { - start: self.start, - now: self.now, - } - } - pub fn update(&mut self, now: Instant) { self.now = now; self.system_cache.clear(); @@ -136,106 +131,81 @@ impl State { } } -impl canvas::State for State { +impl canvas::Program for State { fn draw(&self, bounds: Size) -> Vec { - vec![ - self.space_cache.draw(bounds, self.space()), - self.system_cache.draw(bounds, self.system()), - ] - } -} + use canvas::{Path, Stroke}; + use std::f32::consts::PI; -#[derive(Debug)] -struct Space<'a> { - stars: &'a [(Point, f32)], -} + let background = self.space_cache.draw(bounds, |frame| { + let space = Path::rectangle(Point::new(0.0, 0.0), frame.size()); -impl canvas::Drawable for Space<'_> { - fn draw(&self, frame: &mut canvas::Frame) { - use canvas::Path; + let stars = Path::new(|path| { + for (p, size) in &self.stars { + path.rectangle(*p, Size::new(*size, *size)); + } + }); - let space = Path::rectangle(Point::new(0.0, 0.0), frame.size()); + frame.fill(&space, Color::BLACK); - let stars = Path::new(|path| { - for (p, size) in self.stars { - path.rectangle(*p, Size::new(*size, *size)); - } + frame.translate(frame.center() - Point::ORIGIN); + frame.fill(&stars, Color::WHITE); }); - frame.fill(&space, Color::BLACK); + let system = self.system_cache.draw(bounds, |frame| { + let center = frame.center(); - frame.translate(frame.center() - Point::ORIGIN); - frame.fill(&stars, Color::WHITE); - } -} + let sun = Path::circle(center, Self::SUN_RADIUS); + let orbit = Path::circle(center, Self::ORBIT_RADIUS); -#[derive(Debug)] -struct System { - start: Instant, - now: Instant, -} - -impl System { - const SUN_RADIUS: f32 = 70.0; - const ORBIT_RADIUS: f32 = 150.0; - const EARTH_RADIUS: f32 = 12.0; - const MOON_RADIUS: f32 = 4.0; - const MOON_DISTANCE: f32 = 28.0; -} - -impl canvas::Drawable for System { - fn draw(&self, frame: &mut canvas::Frame) { - use canvas::{Path, Stroke}; - use std::f32::consts::PI; - - let center = frame.center(); - - let sun = Path::circle(center, Self::SUN_RADIUS); - let orbit = Path::circle(center, Self::ORBIT_RADIUS); - - frame.fill(&sun, Color::from_rgb8(0xF9, 0xD7, 0x1C)); - frame.stroke( - &orbit, - Stroke { - width: 1.0, - color: Color::from_rgba8(0, 153, 255, 0.1), - ..Stroke::default() - }, - ); + frame.fill(&sun, Color::from_rgb8(0xF9, 0xD7, 0x1C)); + frame.stroke( + &orbit, + Stroke { + width: 1.0, + color: Color::from_rgba8(0, 153, 255, 0.1), + ..Stroke::default() + }, + ); - let elapsed = self.now - self.start; - let rotation = (2.0 * PI / 60.0) * elapsed.as_secs() as f32 - + (2.0 * PI / 60_000.0) * elapsed.subsec_millis() as f32; + let elapsed = self.now - self.start; + let rotation = (2.0 * PI / 60.0) * elapsed.as_secs() as f32 + + (2.0 * PI / 60_000.0) * elapsed.subsec_millis() as f32; - frame.with_save(|frame| { - frame.translate(Vector::new(center.x, center.y)); - frame.rotate(rotation); - frame.translate(Vector::new(Self::ORBIT_RADIUS, 0.0)); + frame.with_save(|frame| { + frame.translate(Vector::new(center.x, center.y)); + frame.rotate(rotation); + frame.translate(Vector::new(Self::ORBIT_RADIUS, 0.0)); + + let earth = Path::circle(Point::ORIGIN, Self::EARTH_RADIUS); + let shadow = Path::rectangle( + Point::new(0.0, -Self::EARTH_RADIUS), + Size::new( + Self::EARTH_RADIUS * 4.0, + Self::EARTH_RADIUS * 2.0, + ), + ); - let earth = Path::circle(Point::ORIGIN, Self::EARTH_RADIUS); - let shadow = Path::rectangle( - Point::new(0.0, -Self::EARTH_RADIUS), - Size::new(Self::EARTH_RADIUS * 4.0, Self::EARTH_RADIUS * 2.0), - ); + frame.fill(&earth, Color::from_rgb8(0x6B, 0x93, 0xD6)); - frame.fill(&earth, Color::from_rgb8(0x6B, 0x93, 0xD6)); + frame.with_save(|frame| { + frame.rotate(rotation * 10.0); + frame.translate(Vector::new(0.0, Self::MOON_DISTANCE)); - frame.with_save(|frame| { - frame.rotate(rotation * 10.0); - frame.translate(Vector::new(0.0, Self::MOON_DISTANCE)); + let moon = Path::circle(Point::ORIGIN, Self::MOON_RADIUS); + frame.fill(&moon, Color::WHITE); + }); - let moon = Path::circle(Point::ORIGIN, Self::MOON_RADIUS); - frame.fill(&moon, Color::WHITE); + frame.fill( + &shadow, + Color { + a: 0.7, + ..Color::BLACK + }, + ); }); - - frame.fill( - &shadow, - Color { - a: 0.7, - ..Color::BLACK - }, - ); }); + + vec![background, system] } } diff --git a/src/lib.rs b/src/lib.rs index 770449847e..eee7a6ee7e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -207,7 +207,7 @@ use iced_web as runtime; pub use runtime::{ futures, Align, Background, Color, Command, Font, HorizontalAlignment, - Length, Point, Size, Subscription, Vector, VerticalAlignment, + Length, Point, Rectangle, Size, Subscription, Vector, VerticalAlignment, }; #[cfg(not(target_arch = "wasm32"))] diff --git a/wgpu/src/widget/canvas.rs b/wgpu/src/widget/canvas.rs index 044bc3eca4..c0506cf762 100644 --- a/wgpu/src/widget/canvas.rs +++ b/wgpu/src/widget/canvas.rs @@ -18,34 +18,27 @@ use std::marker::PhantomData; pub mod path; mod cache; -mod drawable; mod event; mod fill; mod frame; mod geometry; -mod state; +mod program; mod stroke; mod text; pub use cache::Cache; -pub use drawable::Drawable; pub use event::Event; pub use fill::Fill; pub use frame::Frame; pub use geometry::Geometry; pub use path::Path; -pub use state::State; +pub use program::Program; pub use stroke::{LineCap, LineJoin, Stroke}; pub use text::Text; /// A widget capable of drawing 2D graphics. /// -/// A [`Canvas`] may contain multiple layers. A [`Layer`] is drawn using the -/// painter's algorithm. In other words, layers will be drawn on top of each -/// other in the same order they are pushed into the [`Canvas`]. -/// /// [`Canvas`]: struct.Canvas.html -/// [`Layer`]: layer/trait.Layer.html /// /// # Examples /// The repository has a couple of [examples] showcasing how to use a @@ -66,10 +59,10 @@ pub use text::Text; /// ```no_run /// # mod iced { /// # pub use iced_wgpu::canvas; -/// # pub use iced_native::Color; +/// # pub use iced_native::{Color, Size}; /// # } -/// use iced::canvas::{self, Cache, Canvas, Drawable, Fill, Frame, Path}; -/// use iced::Color; +/// use iced::canvas::{self, Cache, Canvas, Fill, Frame, Geometry, Path, Program}; +/// use iced::{Color, Size}; /// /// // First, we define the data we need for drawing /// #[derive(Debug)] @@ -77,42 +70,45 @@ pub use text::Text; /// radius: f32, /// } /// -/// // Then, we implement the `Drawable` trait -/// impl Drawable for Circle { -/// fn draw(&self, frame: &mut Frame) { +/// // Then, we implement the `Program` trait +/// impl Program<()> for Circle { +/// fn draw(&self, bounds: Size) -> Vec{ +/// // We prepare a new `Frame` +/// let mut frame = Frame::new(bounds); +/// /// // We create a `Path` representing a simple circle /// let circle = Path::circle(frame.center(), self.radius); /// /// // And fill it with some color /// frame.fill(&circle, Fill::Color(Color::BLACK)); +/// +/// // Finally, we produce the geometry +/// vec![frame.into_geometry()] /// } /// } /// -/// // We can use a `Cache` to avoid unnecessary re-tessellation -/// let cache = Cache::new(); -/// /// // Finally, we simply use our `Cache` to create the `Canvas`! -/// let canvas: Canvas<_, ()> = Canvas::new(cache.with(Circle { radius: 50.0 })); +/// let canvas = Canvas::new(Circle { radius: 50.0 }); /// ``` #[derive(Debug)] -pub struct Canvas, Message> { +pub struct Canvas> { width: Length, height: Length, - state: S, + program: P, phantom: PhantomData, } -impl> Canvas { +impl> Canvas { const DEFAULT_SIZE: u16 = 100; - /// Creates a new [`Canvas`] with no layers. + /// Creates a new [`Canvas`]. /// /// [`Canvas`]: struct.Canvas.html - pub fn new(state: S) -> Self { + pub fn new(program: P) -> Self { Canvas { width: Length::Units(Self::DEFAULT_SIZE), height: Length::Units(Self::DEFAULT_SIZE), - state, + program, phantom: PhantomData, } } @@ -134,8 +130,8 @@ impl> Canvas { } } -impl> Widget - for Canvas +impl> Widget + for Canvas { fn width(&self) -> Length { self.width @@ -184,7 +180,7 @@ impl> Widget if let Some(canvas_event) = canvas_event { if let Some(message) = - self.state.update(canvas_event, bounds.size()) + self.program.update(canvas_event, bounds.size()) { messages.push(message); } @@ -207,7 +203,7 @@ impl> Widget translation, content: Box::new(Primitive::Group { primitives: self - .state + .program .draw(size) .into_iter() .map(Geometry::into_primitive) @@ -227,12 +223,12 @@ impl> Widget } } -impl<'a, Message, S: State + 'a> From> +impl<'a, Message, P: Program + 'a> From> for Element<'a, Message, Renderer> where Message: 'static, { - fn from(canvas: Canvas) -> Element<'a, Message, Renderer> { + fn from(canvas: Canvas) -> Element<'a, Message, Renderer> { Element::new(canvas) } } diff --git a/wgpu/src/widget/canvas/cache.rs b/wgpu/src/widget/canvas/cache.rs index 2beed0f77b..03643f7483 100644 --- a/wgpu/src/widget/canvas/cache.rs +++ b/wgpu/src/widget/canvas/cache.rs @@ -1,5 +1,5 @@ use crate::{ - canvas::{Drawable, Frame, Geometry}, + canvas::{Frame, Geometry}, Primitive, }; @@ -48,10 +48,11 @@ impl Cache { *self.state.borrow_mut() = State::Empty; } - pub fn draw(&self, new_bounds: Size, input: T) -> Geometry - where - T: Drawable + std::fmt::Debug, - { + pub fn draw( + &self, + new_bounds: Size, + draw_fn: impl Fn(&mut Frame), + ) -> Geometry { use std::ops::Deref; if let State::Filled { bounds, primitive } = self.state.borrow().deref() @@ -64,7 +65,7 @@ impl Cache { } let mut frame = Frame::new(new_bounds); - input.draw(&mut frame); + draw_fn(&mut frame); let primitive = { let geometry = frame.into_geometry(); @@ -79,30 +80,6 @@ impl Cache { Geometry::from_primitive(Primitive::Cached { cache: primitive }) } - - pub fn with<'a, T, Message>( - &'a self, - input: T, - ) -> impl crate::canvas::State + 'a - where - T: Drawable + std::fmt::Debug + 'a, - { - Bind { cache: self, input } - } -} - -struct Bind<'a, T> { - cache: &'a Cache, - input: T, -} - -impl<'a, T, Message> crate::canvas::State for Bind<'a, T> -where - T: Drawable + std::fmt::Debug + 'a, -{ - fn draw(&self, bounds: Size) -> Vec { - vec![self.cache.draw(bounds, &self.input)] - } } impl std::fmt::Debug for State { diff --git a/wgpu/src/widget/canvas/drawable.rs b/wgpu/src/widget/canvas/drawable.rs deleted file mode 100644 index 5209fa6f4c..0000000000 --- a/wgpu/src/widget/canvas/drawable.rs +++ /dev/null @@ -1,36 +0,0 @@ -use crate::canvas::Frame; - -/// A type that can be drawn on a [`Frame`]. -/// -/// [`Frame`]: struct.Frame.html -pub trait Drawable { - /// Draws the [`Drawable`] on the given [`Frame`]. - /// - /// [`Drawable`]: trait.Drawable.html - /// [`Frame`]: struct.Frame.html - fn draw(&self, frame: &mut Frame); -} - -impl<'a> Drawable for dyn Fn(&mut Frame) + 'a { - fn draw(&self, frame: &mut Frame) { - self(frame) - } -} - -impl Drawable for &T -where - T: Drawable, -{ - fn draw(&self, frame: &mut Frame) { - T::draw(self, frame) - } -} - -impl Drawable for &[T] -where - T: Drawable, -{ - fn draw(&self, frame: &mut Frame) { - self.iter().for_each(|drawable| drawable.draw(frame)); - } -} diff --git a/wgpu/src/widget/canvas/state.rs b/wgpu/src/widget/canvas/program.rs similarity index 80% rename from wgpu/src/widget/canvas/state.rs rename to wgpu/src/widget/canvas/program.rs index ab433dd1a0..8e35fdfbb2 100644 --- a/wgpu/src/widget/canvas/state.rs +++ b/wgpu/src/widget/canvas/program.rs @@ -1,6 +1,6 @@ use crate::canvas::{Event, Geometry, Size}; -pub trait State { +pub trait Program { fn update(&mut self, _event: Event, _bounds: Size) -> Option { None } @@ -8,9 +8,9 @@ pub trait State { fn draw(&self, bounds: Size) -> Vec; } -impl State for &mut T +impl Program for &mut T where - T: State, + T: Program, { fn update(&mut self, event: Event, bounds: Size) -> Option { T::update(self, event, bounds) From 85dc07c3b00b4f6c40fcba571c067b941c868098 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 28 Apr 2020 06:31:00 +0200 Subject: [PATCH 22/73] Expose `Rectangle` in `iced_web` --- web/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/lib.rs b/web/src/lib.rs index 395c0a25f5..cccecdc3a3 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -75,7 +75,7 @@ pub use element::Element; pub use hasher::Hasher; pub use iced_core::{ keyboard, Align, Background, Color, Font, HorizontalAlignment, Length, - Point, Size, Vector, VerticalAlignment, + Point, Rectangle, Size, Vector, VerticalAlignment, }; pub use iced_futures::{executor, futures, Command}; pub use subscription::Subscription; From 59403b6ca80081fa419fbef76c92397db68f1ab1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 29 Apr 2020 03:11:15 +0200 Subject: [PATCH 23/73] Remove `OutOfBounds` variant from `MouseCursor` --- examples/custom_widget/src/main.rs | 2 +- examples/geometry/src/main.rs | 2 +- examples/integration/src/main.rs | 2 +- native/src/mouse_cursor.rs | 5 +---- wgpu/src/renderer/widget/button.rs | 2 +- wgpu/src/renderer/widget/checkbox.rs | 2 +- wgpu/src/renderer/widget/column.rs | 2 +- wgpu/src/renderer/widget/image.rs | 2 +- wgpu/src/renderer/widget/pane_grid.rs | 2 +- wgpu/src/renderer/widget/progress_bar.rs | 2 +- wgpu/src/renderer/widget/radio.rs | 2 +- wgpu/src/renderer/widget/row.rs | 2 +- wgpu/src/renderer/widget/slider.rs | 2 +- wgpu/src/renderer/widget/space.rs | 2 +- wgpu/src/renderer/widget/svg.rs | 2 +- wgpu/src/renderer/widget/text.rs | 2 +- wgpu/src/renderer/widget/text_input.rs | 2 +- winit/src/application.rs | 2 +- winit/src/conversion.rs | 1 - 19 files changed, 18 insertions(+), 22 deletions(-) diff --git a/examples/custom_widget/src/main.rs b/examples/custom_widget/src/main.rs index 0a570745e1..d0bceb73ad 100644 --- a/examples/custom_widget/src/main.rs +++ b/examples/custom_widget/src/main.rs @@ -66,7 +66,7 @@ mod circle { border_width: 0, border_color: Color::TRANSPARENT, }, - MouseCursor::OutOfBounds, + MouseCursor::default(), ) } } diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs index 795cac2751..2a3efd4a93 100644 --- a/examples/geometry/src/main.rs +++ b/examples/geometry/src/main.rs @@ -141,7 +141,7 @@ mod rainbow { }, }), }, - MouseCursor::OutOfBounds, + MouseCursor::default(), ) } } diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs index 7203d4b6bd..da571ed1f0 100644 --- a/examples/integration/src/main.rs +++ b/examples/integration/src/main.rs @@ -63,7 +63,7 @@ pub fn main() { let mut events = Vec::new(); let mut cache = Some(Cache::default()); let mut renderer = Renderer::new(&mut device, Settings::default()); - let mut output = (Primitive::None, MouseCursor::OutOfBounds); + let mut output = (Primitive::None, MouseCursor::default()); let clipboard = Clipboard::new(&window); // Initialize scene and GUI controls diff --git a/native/src/mouse_cursor.rs b/native/src/mouse_cursor.rs index 0dad3edc72..8bfa2b44f9 100644 --- a/native/src/mouse_cursor.rs +++ b/native/src/mouse_cursor.rs @@ -1,9 +1,6 @@ /// The state of the mouse cursor. #[derive(Debug, Eq, PartialEq, Clone, Copy, PartialOrd, Ord)] pub enum MouseCursor { - /// The cursor is out of the bounds of the user interface. - OutOfBounds, - /// The cursor is over a non-interactive widget. Idle, @@ -31,6 +28,6 @@ pub enum MouseCursor { impl Default for MouseCursor { fn default() -> MouseCursor { - MouseCursor::OutOfBounds + MouseCursor::Idle } } diff --git a/wgpu/src/renderer/widget/button.rs b/wgpu/src/renderer/widget/button.rs index 359b4866ea..5e55873a5e 100644 --- a/wgpu/src/renderer/widget/button.rs +++ b/wgpu/src/renderer/widget/button.rs @@ -86,7 +86,7 @@ impl iced_native::button::Renderer for Renderer { if is_mouse_over { MouseCursor::Pointer } else { - MouseCursor::OutOfBounds + MouseCursor::default() }, ) } diff --git a/wgpu/src/renderer/widget/checkbox.rs b/wgpu/src/renderer/widget/checkbox.rs index c0f1bf2159..7f7f6de315 100644 --- a/wgpu/src/renderer/widget/checkbox.rs +++ b/wgpu/src/renderer/widget/checkbox.rs @@ -56,7 +56,7 @@ impl checkbox::Renderer for Renderer { if is_mouse_over { MouseCursor::Pointer } else { - MouseCursor::OutOfBounds + MouseCursor::default() }, ) } diff --git a/wgpu/src/renderer/widget/column.rs b/wgpu/src/renderer/widget/column.rs index 95a7463ad2..e6a9d8f037 100644 --- a/wgpu/src/renderer/widget/column.rs +++ b/wgpu/src/renderer/widget/column.rs @@ -9,7 +9,7 @@ impl column::Renderer for Renderer { layout: Layout<'_>, cursor_position: Point, ) -> Self::Output { - let mut mouse_cursor = MouseCursor::OutOfBounds; + let mut mouse_cursor = MouseCursor::default(); ( Primitive::Group { diff --git a/wgpu/src/renderer/widget/image.rs b/wgpu/src/renderer/widget/image.rs index 70dc5d97c0..6b7f1c60df 100644 --- a/wgpu/src/renderer/widget/image.rs +++ b/wgpu/src/renderer/widget/image.rs @@ -16,7 +16,7 @@ impl image::Renderer for Renderer { handle, bounds: layout.bounds(), }, - MouseCursor::OutOfBounds, + MouseCursor::default(), ) } } diff --git a/wgpu/src/renderer/widget/pane_grid.rs b/wgpu/src/renderer/widget/pane_grid.rs index 11ba63479e..80e2471f24 100644 --- a/wgpu/src/renderer/widget/pane_grid.rs +++ b/wgpu/src/renderer/widget/pane_grid.rs @@ -22,7 +22,7 @@ impl pane_grid::Renderer for Renderer { cursor_position }; - let mut mouse_cursor = MouseCursor::OutOfBounds; + let mut mouse_cursor = MouseCursor::default(); let mut dragged_pane = None; let mut panes: Vec<_> = content diff --git a/wgpu/src/renderer/widget/progress_bar.rs b/wgpu/src/renderer/widget/progress_bar.rs index 34e332769b..fe032fbf48 100644 --- a/wgpu/src/renderer/widget/progress_bar.rs +++ b/wgpu/src/renderer/widget/progress_bar.rs @@ -48,7 +48,7 @@ impl progress_bar::Renderer for Renderer { } else { background }, - MouseCursor::OutOfBounds, + MouseCursor::default(), ) } } diff --git a/wgpu/src/renderer/widget/radio.rs b/wgpu/src/renderer/widget/radio.rs index 564f066b76..551700c848 100644 --- a/wgpu/src/renderer/widget/radio.rs +++ b/wgpu/src/renderer/widget/radio.rs @@ -57,7 +57,7 @@ impl radio::Renderer for Renderer { if is_mouse_over { MouseCursor::Pointer } else { - MouseCursor::OutOfBounds + MouseCursor::default() }, ) } diff --git a/wgpu/src/renderer/widget/row.rs b/wgpu/src/renderer/widget/row.rs index bd9f1a040c..c6a10c07ae 100644 --- a/wgpu/src/renderer/widget/row.rs +++ b/wgpu/src/renderer/widget/row.rs @@ -9,7 +9,7 @@ impl row::Renderer for Renderer { layout: Layout<'_>, cursor_position: Point, ) -> Self::Output { - let mut mouse_cursor = MouseCursor::OutOfBounds; + let mut mouse_cursor = MouseCursor::default(); ( Primitive::Group { diff --git a/wgpu/src/renderer/widget/slider.rs b/wgpu/src/renderer/widget/slider.rs index c8ebd0daff..335e1b9271 100644 --- a/wgpu/src/renderer/widget/slider.rs +++ b/wgpu/src/renderer/widget/slider.rs @@ -99,7 +99,7 @@ impl slider::Renderer for Renderer { } else if is_mouse_over { MouseCursor::Grab } else { - MouseCursor::OutOfBounds + MouseCursor::default() }, ) } diff --git a/wgpu/src/renderer/widget/space.rs b/wgpu/src/renderer/widget/space.rs index 28e05437b9..9ec0ed6dbf 100644 --- a/wgpu/src/renderer/widget/space.rs +++ b/wgpu/src/renderer/widget/space.rs @@ -3,6 +3,6 @@ use iced_native::{space, MouseCursor, Rectangle}; impl space::Renderer for Renderer { fn draw(&mut self, _bounds: Rectangle) -> Self::Output { - (Primitive::None, MouseCursor::OutOfBounds) + (Primitive::None, MouseCursor::default()) } } diff --git a/wgpu/src/renderer/widget/svg.rs b/wgpu/src/renderer/widget/svg.rs index 67bc3fe1f0..4ee983ead9 100644 --- a/wgpu/src/renderer/widget/svg.rs +++ b/wgpu/src/renderer/widget/svg.rs @@ -16,7 +16,7 @@ impl svg::Renderer for Renderer { handle, bounds: layout.bounds(), }, - MouseCursor::OutOfBounds, + MouseCursor::default(), ) } } diff --git a/wgpu/src/renderer/widget/text.rs b/wgpu/src/renderer/widget/text.rs index 80bff574ce..3cf3242636 100644 --- a/wgpu/src/renderer/widget/text.rs +++ b/wgpu/src/renderer/widget/text.rs @@ -53,7 +53,7 @@ impl text::Renderer for Renderer { horizontal_alignment, vertical_alignment, }, - MouseCursor::OutOfBounds, + MouseCursor::default(), ) } } diff --git a/wgpu/src/renderer/widget/text_input.rs b/wgpu/src/renderer/widget/text_input.rs index 6f72db68e6..97eb011455 100644 --- a/wgpu/src/renderer/widget/text_input.rs +++ b/wgpu/src/renderer/widget/text_input.rs @@ -234,7 +234,7 @@ impl text_input::Renderer for Renderer { if is_mouse_over { MouseCursor::Text } else { - MouseCursor::OutOfBounds + MouseCursor::default() }, ) } diff --git a/winit/src/application.rs b/winit/src/application.rs index b974711c6e..ae9775f74d 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -205,7 +205,7 @@ pub trait Application: Sized { let mut cache = Some(user_interface.into_cache()); let mut events = Vec::new(); - let mut mouse_cursor = MouseCursor::OutOfBounds; + let mut mouse_cursor = MouseCursor::default(); let mut modifiers = winit::event::ModifiersState::default(); debug.startup_finished(); diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index eaa26ace85..ae416b4cbd 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -110,7 +110,6 @@ pub fn fullscreen( /// [`iced_native`]: https://github.com/hecrj/iced/tree/master/native pub fn mouse_cursor(mouse_cursor: MouseCursor) -> winit::window::CursorIcon { match mouse_cursor { - MouseCursor::OutOfBounds => winit::window::CursorIcon::Default, MouseCursor::Idle => winit::window::CursorIcon::Default, MouseCursor::Pointer => winit::window::CursorIcon::Hand, MouseCursor::Working => winit::window::CursorIcon::Progress, From 0509710cc59797ce492f55943a6db60c47a2d599 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 29 Apr 2020 03:14:59 +0200 Subject: [PATCH 24/73] Add `Crosshair` variant to `MouseCursor` --- native/src/mouse_cursor.rs | 9 ++++++--- winit/src/conversion.rs | 1 + 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/native/src/mouse_cursor.rs b/native/src/mouse_cursor.rs index 8bfa2b44f9..78ddb0ae0d 100644 --- a/native/src/mouse_cursor.rs +++ b/native/src/mouse_cursor.rs @@ -13,12 +13,15 @@ pub enum MouseCursor { /// The cursor is over a grabbable widget. Grab, - /// The cursor is grabbing a widget. - Grabbing, - /// The cursor is over a text widget. Text, + /// The cursor is over a widget that requires precision. + Crosshair, + + /// The cursor is grabbing a widget. + Grabbing, + /// The cursor is resizing a widget horizontally. ResizingHorizontally, diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index ae416b4cbd..a9d9b7cd38 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -115,6 +115,7 @@ pub fn mouse_cursor(mouse_cursor: MouseCursor) -> winit::window::CursorIcon { MouseCursor::Working => winit::window::CursorIcon::Progress, MouseCursor::Grab => winit::window::CursorIcon::Grab, MouseCursor::Grabbing => winit::window::CursorIcon::Grabbing, + MouseCursor::Crosshair => winit::window::CursorIcon::Crosshair, MouseCursor::Text => winit::window::CursorIcon::Text, MouseCursor::ResizingHorizontally => { winit::window::CursorIcon::EwResize From 52719c7076cafb7b01967edf4df11ea72ae45aff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 29 Apr 2020 03:16:03 +0200 Subject: [PATCH 25/73] Let a `canvas::Program` control the mouse cursor --- wgpu/src/widget/canvas.rs | 2 +- wgpu/src/widget/canvas/program.rs | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/wgpu/src/widget/canvas.rs b/wgpu/src/widget/canvas.rs index c0506cf762..0006ca8d2b 100644 --- a/wgpu/src/widget/canvas.rs +++ b/wgpu/src/widget/canvas.rs @@ -210,7 +210,7 @@ impl> Widget .collect(), }), }, - MouseCursor::Idle, + self.program.mouse_cursor(size), ) } diff --git a/wgpu/src/widget/canvas/program.rs b/wgpu/src/widget/canvas/program.rs index 8e35fdfbb2..9e4aca8957 100644 --- a/wgpu/src/widget/canvas/program.rs +++ b/wgpu/src/widget/canvas/program.rs @@ -1,4 +1,5 @@ use crate::canvas::{Event, Geometry, Size}; +use iced_native::MouseCursor; pub trait Program { fn update(&mut self, _event: Event, _bounds: Size) -> Option { @@ -6,6 +7,10 @@ pub trait Program { } fn draw(&self, bounds: Size) -> Vec; + + fn mouse_cursor(&self, _bounds: Size) -> MouseCursor { + MouseCursor::default() + } } impl Program for &mut T @@ -19,4 +24,8 @@ where fn draw(&self, bounds: Size) -> Vec { T::draw(self, bounds) } + + fn mouse_cursor(&self, bounds: Size) -> MouseCursor { + T::mouse_cursor(self, bounds) + } } From ec712c8032a25c5dc65152c3ab39bddaecbdce77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 29 Apr 2020 03:21:46 +0200 Subject: [PATCH 26/73] Move `MouseCursor` to `iced_core` --- core/src/lib.rs | 2 ++ {native => core}/src/mouse_cursor.rs | 0 native/src/lib.rs | 6 ++---- src/lib.rs | 3 ++- web/src/lib.rs | 2 +- 5 files changed, 7 insertions(+), 6 deletions(-) rename {native => core}/src/mouse_cursor.rs (100%) diff --git a/core/src/lib.rs b/core/src/lib.rs index 606c1b8b6e..f0072f6127 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -23,6 +23,7 @@ mod button_state; mod color; mod font; mod length; +mod mouse_cursor; mod point; mod rectangle; mod size; @@ -34,6 +35,7 @@ pub use button_state::ButtonState; pub use color::Color; pub use font::Font; pub use length::Length; +pub use mouse_cursor::MouseCursor; pub use point::Point; pub use rectangle::Rectangle; pub use size::Size; diff --git a/native/src/mouse_cursor.rs b/core/src/mouse_cursor.rs similarity index 100% rename from native/src/mouse_cursor.rs rename to core/src/mouse_cursor.rs diff --git a/native/src/lib.rs b/native/src/lib.rs index 896123912c..a3b581b349 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -50,13 +50,12 @@ mod clipboard; mod element; mod event; mod hasher; -mod mouse_cursor; mod runtime; mod user_interface; pub use iced_core::{ - Align, Background, Color, Font, HorizontalAlignment, Length, Point, - Rectangle, Size, Vector, VerticalAlignment, + Align, Background, Color, Font, HorizontalAlignment, Length, MouseCursor, + Point, Rectangle, Size, Vector, VerticalAlignment, }; pub use iced_futures::{executor, futures, Command}; @@ -68,7 +67,6 @@ pub use element::Element; pub use event::Event; pub use hasher::Hasher; pub use layout::Layout; -pub use mouse_cursor::MouseCursor; pub use renderer::Renderer; pub use runtime::Runtime; pub use subscription::Subscription; diff --git a/src/lib.rs b/src/lib.rs index eee7a6ee7e..c574a34544 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -207,7 +207,8 @@ use iced_web as runtime; pub use runtime::{ futures, Align, Background, Color, Command, Font, HorizontalAlignment, - Length, Point, Rectangle, Size, Subscription, Vector, VerticalAlignment, + Length, MouseCursor, Point, Rectangle, Size, Subscription, Vector, + VerticalAlignment, }; #[cfg(not(target_arch = "wasm32"))] diff --git a/web/src/lib.rs b/web/src/lib.rs index cccecdc3a3..c525021fe7 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -75,7 +75,7 @@ pub use element::Element; pub use hasher::Hasher; pub use iced_core::{ keyboard, Align, Background, Color, Font, HorizontalAlignment, Length, - Point, Rectangle, Size, Vector, VerticalAlignment, + MouseCursor, Point, Rectangle, Size, Vector, VerticalAlignment, }; pub use iced_futures::{executor, futures, Command}; pub use subscription::Subscription; From 475a2779a7a420ed6c1567a8dbc623114e2b7ec0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 29 Apr 2020 03:23:08 +0200 Subject: [PATCH 27/73] Implement `Rectangle::with_size` --- core/src/rectangle.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/core/src/rectangle.rs b/core/src/rectangle.rs index e8d0538a1d..6f9531377c 100644 --- a/core/src/rectangle.rs +++ b/core/src/rectangle.rs @@ -32,6 +32,20 @@ impl Rectangle { } } + /// Creates a new [`Rectangle`] with its top-left corner at the origin + /// and with the provided [`Size`]. + /// + /// [`Rectangle`]: struct.Rectangle.html + /// [`Size`]: struct.Size.html + pub fn with_size(size: Size) -> Self { + Self { + x: 0.0, + y: 0.0, + width: size.width, + height: size.height, + } + } + /// Returns the [`Point`] at the center of the [`Rectangle`]. /// /// [`Point`]: struct.Point.html From 5586034d6626e013cdd718aca1c4f19f6a060ff3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 29 Apr 2020 03:23:27 +0200 Subject: [PATCH 28/73] Display crosshair cursor in `bezier_tool` example --- examples/bezier_tool/src/main.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs index 2112f66254..8c9ebd7535 100644 --- a/examples/bezier_tool/src/main.rs +++ b/examples/bezier_tool/src/main.rs @@ -70,7 +70,8 @@ impl Sandbox for Example { mod bezier { use iced::{ canvas::{self, Canvas, Event, Frame, Geometry, Path, Stroke}, - mouse, ButtonState, Element, Length, Point, Rectangle, Size, + mouse, ButtonState, Element, Length, MouseCursor, Point, Rectangle, + Size, }; #[derive(Default)] @@ -106,8 +107,6 @@ mod bezier { impl<'a> canvas::Program for Bezier<'a> { fn update(&mut self, event: Event, bounds: Size) -> Option { - let bounds = Rectangle::new(Point::ORIGIN, bounds); - match event { Event::Mouse(mouse_event) => match mouse_event { mouse::Event::CursorMoved { x, y } => { @@ -118,7 +117,9 @@ mod bezier { mouse::Event::Input { button: mouse::Button::Left, state: ButtonState::Pressed, - } if bounds.contains(self.state.cursor_position) => { + } if Rectangle::with_size(bounds) + .contains(self.state.cursor_position) => + { match self.state.pending { None => { self.state.pending = Some(Pending::One { @@ -169,6 +170,15 @@ mod bezier { vec![content] } } + + fn mouse_cursor(&self, bounds: Size) -> MouseCursor { + if Rectangle::with_size(bounds).contains(self.state.cursor_position) + { + MouseCursor::Crosshair + } else { + MouseCursor::default() + } + } } #[derive(Debug, Clone, Copy)] From dc51080328caa12d2b1fc02febc72cab70bb9f50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 29 Apr 2020 04:25:49 +0200 Subject: [PATCH 29/73] Introduce `Cursor` type in `canvas` --- examples/bezier_tool/src/main.rs | 137 +++++++++++++++--------------- examples/clock/src/main.rs | 8 +- examples/solar_system/src/main.rs | 16 ++-- wgpu/src/widget/canvas.rs | 38 ++++----- wgpu/src/widget/canvas/cursor.rs | 50 +++++++++++ wgpu/src/widget/canvas/program.rs | 32 ++++--- 6 files changed, 171 insertions(+), 110 deletions(-) create mode 100644 wgpu/src/widget/canvas/cursor.rs diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs index 8c9ebd7535..6473db75df 100644 --- a/examples/bezier_tool/src/main.rs +++ b/examples/bezier_tool/src/main.rs @@ -69,15 +69,13 @@ impl Sandbox for Example { mod bezier { use iced::{ - canvas::{self, Canvas, Event, Frame, Geometry, Path, Stroke}, + canvas::{self, Canvas, Cursor, Event, Frame, Geometry, Path, Stroke}, mouse, ButtonState, Element, Length, MouseCursor, Point, Rectangle, - Size, }; #[derive(Default)] pub struct State { pending: Option, - cursor_position: Point, cache: canvas::Cache, } @@ -106,64 +104,62 @@ mod bezier { } impl<'a> canvas::Program for Bezier<'a> { - fn update(&mut self, event: Event, bounds: Size) -> Option { + fn update( + &mut self, + event: Event, + bounds: Rectangle, + cursor: Cursor, + ) -> Option { + let cursor_position = cursor.internal_position(&bounds)?; + match event { Event::Mouse(mouse_event) => match mouse_event { - mouse::Event::CursorMoved { x, y } => { - self.state.cursor_position = Point::new(x, y); - - None - } mouse::Event::Input { button: mouse::Button::Left, state: ButtonState::Pressed, - } if Rectangle::with_size(bounds) - .contains(self.state.cursor_position) => - { - match self.state.pending { - None => { - self.state.pending = Some(Pending::One { - from: self.state.cursor_position, - }); - None - } - Some(Pending::One { from }) => { - self.state.pending = Some(Pending::Two { - from, - to: self.state.cursor_position, - }); - - None - } - Some(Pending::Two { from, to }) => { - self.state.pending = None; - - Some(Curve { - from, - to, - control: self.state.cursor_position, - }) - } + } => match self.state.pending { + None => { + self.state.pending = Some(Pending::One { + from: cursor_position, + }); + None } - } + Some(Pending::One { from }) => { + self.state.pending = Some(Pending::Two { + from, + to: cursor_position, + }); + + None + } + Some(Pending::Two { from, to }) => { + self.state.pending = None; + + Some(Curve { + from, + to, + control: cursor_position, + }) + } + }, _ => None, }, } } - fn draw(&self, bounds: Size) -> Vec { - let content = self.state.cache.draw(bounds, |frame: &mut Frame| { - Curve::draw_all(self.curves, frame); + fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Vec { + let content = + self.state.cache.draw(bounds.size(), |frame: &mut Frame| { + Curve::draw_all(self.curves, frame); - frame.stroke( - &Path::rectangle(Point::ORIGIN, frame.size()), - Stroke::default(), - ); - }); + frame.stroke( + &Path::rectangle(Point::ORIGIN, frame.size()), + Stroke::default(), + ); + }); if let Some(pending) = &self.state.pending { - let pending_curve = - pending.draw(bounds, self.state.cursor_position); + let pending_curve = pending.draw(bounds, cursor); vec![content, pending_curve] } else { @@ -171,9 +167,12 @@ mod bezier { } } - fn mouse_cursor(&self, bounds: Size) -> MouseCursor { - if Rectangle::with_size(bounds).contains(self.state.cursor_position) - { + fn mouse_cursor( + &self, + bounds: Rectangle, + cursor: Cursor, + ) -> MouseCursor { + if cursor.is_over(&bounds) { MouseCursor::Crosshair } else { MouseCursor::default() @@ -208,24 +207,26 @@ mod bezier { } impl Pending { - fn draw(&self, bounds: Size, cursor_position: Point) -> Geometry { - let mut frame = Frame::new(bounds); - - match *self { - Pending::One { from } => { - let line = Path::line(from, cursor_position); - frame.stroke(&line, Stroke::default().with_width(2.0)); - } - Pending::Two { from, to } => { - let curve = Curve { - from, - to, - control: cursor_position, - }; - - Curve::draw_all(&[curve], &mut frame); - } - }; + fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Geometry { + let mut frame = Frame::new(bounds.size()); + + if let Some(cursor_position) = cursor.internal_position(&bounds) { + match *self { + Pending::One { from } => { + let line = Path::line(from, cursor_position); + frame.stroke(&line, Stroke::default().with_width(2.0)); + } + Pending::Two { from, to } => { + let curve = Curve { + from, + to, + control: cursor_position, + }; + + Curve::draw_all(&[curve], &mut frame); + } + }; + } frame.into_geometry() } diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs index c2beddef8a..e6b17d8a2a 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -1,7 +1,7 @@ use iced::{ - canvas::{self, Cache, Canvas, Geometry, LineCap, Path, Stroke}, + canvas::{self, Cache, Canvas, Cursor, Geometry, LineCap, Path, Stroke}, executor, Application, Color, Command, Container, Element, Length, Point, - Settings, Size, Subscription, Vector, + Rectangle, Settings, Subscription, Vector, }; pub fn main() { @@ -75,10 +75,10 @@ impl Application for Clock { } impl canvas::Program for Clock { - fn draw(&self, bounds: Size) -> Vec { + fn draw(&self, bounds: Rectangle, _cursor: Cursor) -> Vec { use chrono::Timelike; - let clock = self.clock.draw(bounds, |frame| { + let clock = self.clock.draw(bounds.size(), |frame| { let center = frame.center(); let radius = frame.width().min(frame.height()) / 2.0; diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs index e2f107bdbd..a25e43df9a 100644 --- a/examples/solar_system/src/main.rs +++ b/examples/solar_system/src/main.rs @@ -7,8 +7,9 @@ //! //! [1]: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_animations#An_animated_solar_system use iced::{ - canvas, executor, window, Application, Canvas, Color, Command, Element, - Length, Point, Settings, Size, Subscription, Vector, + canvas::{self, Cursor, Path, Stroke}, + executor, window, Application, Canvas, Color, Command, Element, Length, + Point, Rectangle, Settings, Size, Subscription, Vector, }; use std::time::Instant; @@ -132,11 +133,14 @@ impl State { } impl canvas::Program for State { - fn draw(&self, bounds: Size) -> Vec { - use canvas::{Path, Stroke}; + fn draw( + &self, + bounds: Rectangle, + _cursor: Cursor, + ) -> Vec { use std::f32::consts::PI; - let background = self.space_cache.draw(bounds, |frame| { + let background = self.space_cache.draw(bounds.size(), |frame| { let space = Path::rectangle(Point::new(0.0, 0.0), frame.size()); let stars = Path::new(|path| { @@ -151,7 +155,7 @@ impl canvas::Program for State { frame.fill(&stars, Color::WHITE); }); - let system = self.system_cache.draw(bounds, |frame| { + let system = self.system_cache.draw(bounds.size(), |frame| { let center = frame.center(); let sun = Path::circle(center, Self::SUN_RADIUS); diff --git a/wgpu/src/widget/canvas.rs b/wgpu/src/widget/canvas.rs index 0006ca8d2b..a5834330c2 100644 --- a/wgpu/src/widget/canvas.rs +++ b/wgpu/src/widget/canvas.rs @@ -9,8 +9,8 @@ use crate::{Defaults, Primitive, Renderer}; use iced_native::{ - input::mouse, layout, Clipboard, Element, Hasher, Layout, Length, - MouseCursor, Point, Size, Vector, Widget, + layout, Clipboard, Element, Hasher, Layout, Length, MouseCursor, Point, + Size, Vector, Widget, }; use std::hash::Hash; use std::marker::PhantomData; @@ -18,6 +18,7 @@ use std::marker::PhantomData; pub mod path; mod cache; +mod cursor; mod event; mod fill; mod frame; @@ -27,6 +28,7 @@ mod stroke; mod text; pub use cache::Cache; +pub use cursor::Cursor; pub use event::Event; pub use fill::Fill; pub use frame::Frame; @@ -59,10 +61,10 @@ pub use text::Text; /// ```no_run /// # mod iced { /// # pub use iced_wgpu::canvas; -/// # pub use iced_native::{Color, Size}; +/// # pub use iced_native::{Color, Rectangle}; /// # } -/// use iced::canvas::{self, Cache, Canvas, Fill, Frame, Geometry, Path, Program}; -/// use iced::{Color, Size}; +/// use iced::canvas::{self, Canvas, Cursor, Fill, Frame, Geometry, Path, Program}; +/// use iced::{Color, Rectangle}; /// /// // First, we define the data we need for drawing /// #[derive(Debug)] @@ -72,9 +74,9 @@ pub use text::Text; /// /// // Then, we implement the `Program` trait /// impl Program<()> for Circle { -/// fn draw(&self, bounds: Size) -> Vec{ +/// fn draw(&self, bounds: Rectangle, _cursor: Cursor) -> Vec{ /// // We prepare a new `Frame` -/// let mut frame = Frame::new(bounds); +/// let mut frame = Frame::new(bounds.size()); /// /// // We create a `Path` representing a simple circle /// let circle = Path::circle(frame.center(), self.radius); @@ -165,22 +167,16 @@ impl> Widget let canvas_event = match event { iced_native::Event::Mouse(mouse_event) => { - Some(Event::Mouse(match mouse_event { - mouse::Event::CursorMoved { .. } => { - mouse::Event::CursorMoved { - x: cursor_position.x - bounds.x, - y: cursor_position.y - bounds.y, - } - } - _ => mouse_event, - })) + Some(Event::Mouse(mouse_event)) } _ => None, }; + let cursor = Cursor::from_window_position(cursor_position); + if let Some(canvas_event) = canvas_event { if let Some(message) = - self.program.update(canvas_event, bounds.size()) + self.program.update(canvas_event, bounds, cursor) { messages.push(message); } @@ -192,11 +188,11 @@ impl> Widget _renderer: &mut Renderer, _defaults: &Defaults, layout: Layout<'_>, - _cursor_position: Point, + cursor_position: Point, ) -> (Primitive, MouseCursor) { let bounds = layout.bounds(); let translation = Vector::new(bounds.x, bounds.y); - let size = Size::new(bounds.width, bounds.height); + let cursor = Cursor::from_window_position(cursor_position); ( Primitive::Translate { @@ -204,13 +200,13 @@ impl> Widget content: Box::new(Primitive::Group { primitives: self .program - .draw(size) + .draw(bounds, cursor) .into_iter() .map(Geometry::into_primitive) .collect(), }), }, - self.program.mouse_cursor(size), + self.program.mouse_cursor(bounds, cursor), ) } diff --git a/wgpu/src/widget/canvas/cursor.rs b/wgpu/src/widget/canvas/cursor.rs new file mode 100644 index 0000000000..a559782a5d --- /dev/null +++ b/wgpu/src/widget/canvas/cursor.rs @@ -0,0 +1,50 @@ +use iced_native::{Point, Rectangle}; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Cursor { + Available(Point), + Unavailable, +} + +impl Cursor { + // TODO: Remove this once this type is used in `iced_native` to encode + // proper cursor availability + pub(crate) fn from_window_position(position: Point) -> Self { + if position.x < 0.0 || position.y < 0.0 { + Cursor::Unavailable + } else { + Cursor::Available(position) + } + } + + pub fn position(&self) -> Option { + match self { + Cursor::Available(position) => Some(*position), + Cursor::Unavailable => None, + } + } + + pub fn relative_position(&self, bounds: &Rectangle) -> Option { + match self { + Cursor::Available(position) => { + Some(Point::new(position.x - bounds.x, position.y - bounds.y)) + } + _ => None, + } + } + + pub fn internal_position(&self, bounds: &Rectangle) -> Option { + if self.is_over(bounds) { + self.relative_position(bounds) + } else { + None + } + } + + pub fn is_over(&self, bounds: &Rectangle) -> bool { + match self { + Cursor::Available(position) => bounds.contains(*position), + Cursor::Unavailable => false, + } + } +} diff --git a/wgpu/src/widget/canvas/program.rs b/wgpu/src/widget/canvas/program.rs index 9e4aca8957..f8e5451489 100644 --- a/wgpu/src/widget/canvas/program.rs +++ b/wgpu/src/widget/canvas/program.rs @@ -1,14 +1,19 @@ -use crate::canvas::{Event, Geometry, Size}; -use iced_native::MouseCursor; +use crate::canvas::{Cursor, Event, Geometry}; +use iced_native::{MouseCursor, Rectangle}; pub trait Program { - fn update(&mut self, _event: Event, _bounds: Size) -> Option { + fn update( + &mut self, + _event: Event, + _bounds: Rectangle, + _cursor: Cursor, + ) -> Option { None } - fn draw(&self, bounds: Size) -> Vec; + fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Vec; - fn mouse_cursor(&self, _bounds: Size) -> MouseCursor { + fn mouse_cursor(&self, _bounds: Rectangle, _cursor: Cursor) -> MouseCursor { MouseCursor::default() } } @@ -17,15 +22,20 @@ impl Program for &mut T where T: Program, { - fn update(&mut self, event: Event, bounds: Size) -> Option { - T::update(self, event, bounds) + fn update( + &mut self, + event: Event, + bounds: Rectangle, + cursor: Cursor, + ) -> Option { + T::update(self, event, bounds, cursor) } - fn draw(&self, bounds: Size) -> Vec { - T::draw(self, bounds) + fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Vec { + T::draw(self, bounds, cursor) } - fn mouse_cursor(&self, bounds: Size) -> MouseCursor { - T::mouse_cursor(self, bounds) + fn mouse_cursor(&self, bounds: Rectangle, cursor: Cursor) -> MouseCursor { + T::mouse_cursor(self, bounds, cursor) } } From afa0bca4fd1a5499fd24549eb49a44f9837597c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 29 Apr 2020 08:25:27 +0200 Subject: [PATCH 30/73] Implement `Rectangle::position` --- core/src/rectangle.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/src/rectangle.rs b/core/src/rectangle.rs index 6f9531377c..8bc89a4404 100644 --- a/core/src/rectangle.rs +++ b/core/src/rectangle.rs @@ -72,6 +72,13 @@ impl Rectangle { self.y + self.height / 2.0 } + /// Returns the position of the top left corner of the [`Rectangle`]. + /// + /// [`Rectangle`]: struct.Rectangle.html + pub fn position(&self) -> Point { + Point::new(self.x, self.y) + } + /// Returns the [`Size`] of the [`Rectangle`]. /// /// [`Size`]: struct.Size.html From 70f86f998b6db102d5b77f756750414efd53aa9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 29 Apr 2020 08:25:42 +0200 Subject: [PATCH 31/73] Add `game_of_life` example RIP John Conway --- Cargo.toml | 1 + examples/game_of_life/Cargo.toml | 12 + examples/game_of_life/README.md | 18 ++ examples/game_of_life/src/main.rs | 408 +++++++++++++++++++++++++++++ examples/game_of_life/src/style.rs | 96 +++++++ examples/game_of_life/src/time.rs | 34 +++ 6 files changed, 569 insertions(+) create mode 100644 examples/game_of_life/Cargo.toml create mode 100644 examples/game_of_life/README.md create mode 100644 examples/game_of_life/src/main.rs create mode 100644 examples/game_of_life/src/style.rs create mode 100644 examples/game_of_life/src/time.rs diff --git a/Cargo.toml b/Cargo.toml index 9c52ea8ff0..d394d516f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,7 @@ members = [ "examples/custom_widget", "examples/download_progress", "examples/events", + "examples/game_of_life", "examples/geometry", "examples/integration", "examples/pane_grid", diff --git a/examples/game_of_life/Cargo.toml b/examples/game_of_life/Cargo.toml new file mode 100644 index 0000000000..8855b3e837 --- /dev/null +++ b/examples/game_of_life/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "game_of_life" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez "] +edition = "2018" +publish = false + +[dependencies] +iced = { path = "../..", features = ["async-std", "canvas", "debug"] } +iced_native = { path = "../../native" } +async-std = { version = "1.0", features = ["unstable"] } +itertools = "0.9" diff --git a/examples/game_of_life/README.md b/examples/game_of_life/README.md new file mode 100644 index 0000000000..ebbb12cce3 --- /dev/null +++ b/examples/game_of_life/README.md @@ -0,0 +1,18 @@ +## Bézier tool + +A Paint-like tool for drawing Bézier curves using the `Canvas` widget. + +The __[`main`]__ file contains all the code of the example. + + + +You can run it with `cargo run`: +``` +cargo run --package bezier_tool +``` + +[`main`]: src/main.rs diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs new file mode 100644 index 0000000000..3e6848df06 --- /dev/null +++ b/examples/game_of_life/src/main.rs @@ -0,0 +1,408 @@ +//! This example showcases an interactive version of the Game of Life, invented +//! by John Conway. It leverages a `Canvas` together with other widgets. +mod style; +mod time; + +use grid::Grid; +use iced::{ + button::{self, Button}, + executor, + slider::{self, Slider}, + Align, Application, Column, Command, Container, Element, Length, Row, + Settings, Subscription, Text, +}; +use std::time::{Duration, Instant}; + +pub fn main() { + GameOfLife::run(Settings { + antialiasing: true, + ..Settings::default() + }) +} + +#[derive(Default)] +struct GameOfLife { + grid: Grid, + is_playing: bool, + speed: u64, + next_speed: Option, + toggle_button: button::State, + next_button: button::State, + clear_button: button::State, + speed_slider: slider::State, +} + +#[derive(Debug, Clone)] +enum Message { + Grid(grid::Message), + Tick(Instant), + Toggle, + Next, + Clear, + SpeedChanged(f32), +} + +impl Application for GameOfLife { + type Message = Message; + type Executor = executor::Default; + type Flags = (); + + fn new(_flags: ()) -> (Self, Command) { + ( + Self { + speed: 1, + ..Self::default() + }, + Command::none(), + ) + } + + fn title(&self) -> String { + String::from("Game of Life - Iced") + } + + fn update(&mut self, message: Message) -> Command { + match message { + Message::Grid(message) => { + self.grid.update(message); + } + Message::Tick(_) | Message::Next => { + self.grid.tick(); + + if let Some(speed) = self.next_speed.take() { + self.speed = speed; + } + } + Message::Toggle => { + self.is_playing = !self.is_playing; + } + Message::Clear => { + self.grid = Grid::default(); + } + Message::SpeedChanged(speed) => { + if self.is_playing { + self.next_speed = Some(speed.round() as u64); + } else { + self.speed = speed.round() as u64; + } + } + } + + Command::none() + } + + fn subscription(&self) -> Subscription { + if self.is_playing { + time::every(Duration::from_millis(1000 / self.speed)) + .map(Message::Tick) + } else { + Subscription::none() + } + } + + fn view(&mut self) -> Element { + let playback_controls = Row::new() + .spacing(10) + .push( + Button::new( + &mut self.toggle_button, + Text::new(if self.is_playing { "Pause" } else { "Play" }), + ) + .on_press(Message::Toggle) + .style(style::Button), + ) + .push( + Button::new(&mut self.next_button, Text::new("Next")) + .on_press(Message::Next) + .style(style::Button), + ) + .push( + Button::new(&mut self.clear_button, Text::new("Clear")) + .on_press(Message::Clear) + .style(style::Button), + ); + + let selected_speed = self.next_speed.unwrap_or(self.speed); + let speed_controls = Row::new() + .spacing(10) + .push( + Slider::new( + &mut self.speed_slider, + 1.0..=20.0, + selected_speed as f32, + Message::SpeedChanged, + ) + .width(Length::Units(200)) + .style(style::Slider), + ) + .push(Text::new(format!("x{}", selected_speed)).size(16)) + .align_items(Align::Center); + + let controls = Row::new() + .spacing(20) + .push(playback_controls) + .push(speed_controls); + + let content = Column::new() + .spacing(10) + .padding(10) + .align_items(Align::Center) + .push(self.grid.view().map(Message::Grid)) + .push(controls); + + Container::new(content) + .width(Length::Fill) + .height(Length::Fill) + .style(style::Container) + .into() + } +} + +mod grid { + use iced::{ + canvas::{self, Canvas, Cursor, Event, Frame, Geometry, Path}, + mouse, ButtonState, Color, Element, Length, MouseCursor, Point, + Rectangle, Size, Vector, + }; + + const SIZE: usize = 32; + + #[derive(Default)] + pub struct Grid { + cells: [[Cell; SIZE]; SIZE], + mouse_pressed: bool, + cache: canvas::Cache, + } + + impl Grid { + pub fn tick(&mut self) { + let mut populated_neighbors: [[usize; SIZE]; SIZE] = + [[0; SIZE]; SIZE]; + + for (i, row) in self.cells.iter().enumerate() { + for (j, _) in row.iter().enumerate() { + populated_neighbors[i][j] = self.populated_neighbors(i, j); + } + } + + for (i, row) in populated_neighbors.iter().enumerate() { + for (j, amount) in row.iter().enumerate() { + let is_populated = self.cells[i][j] == Cell::Populated; + + self.cells[i][j] = match amount { + 2 if is_populated => Cell::Populated, + 3 => Cell::Populated, + _ => Cell::Unpopulated, + }; + } + } + + self.cache.clear() + } + + pub fn update(&mut self, message: Message) { + match message { + Message::Populate { i, j } => { + self.cells[i][j] = Cell::Populated; + self.cache.clear() + } + } + } + + pub fn view<'a>(&'a mut self) -> Element<'a, Message> { + Canvas::new(self) + .width(Length::Fill) + .height(Length::Fill) + .into() + } + + fn populated_neighbors(&self, row: usize, column: usize) -> usize { + use itertools::Itertools; + + let rows = row.saturating_sub(1)..=row + 1; + let columns = column.saturating_sub(1)..=column + 1; + + let is_inside_bounds = |i: usize, j: usize| i < SIZE && j < SIZE; + let is_neighbor = |i: usize, j: usize| i != row || j != column; + + let is_populated = + |i: usize, j: usize| self.cells[i][j] == Cell::Populated; + + rows.cartesian_product(columns) + .filter(|&(i, j)| { + is_inside_bounds(i, j) + && is_neighbor(i, j) + && is_populated(i, j) + }) + .count() + } + + fn region(&self, size: Size) -> Rectangle { + let side = size.width.min(size.height); + + Rectangle { + x: (size.width - side) / 2.0, + y: (size.height - side) / 2.0, + width: side, + height: side, + } + } + + fn cell_at( + &self, + region: Rectangle, + position: Point, + ) -> Option<(usize, usize)> { + if region.contains(position) { + let cell_size = region.width / SIZE as f32; + + let i = ((position.y - region.y) / cell_size).ceil() as usize; + let j = ((position.x - region.x) / cell_size).ceil() as usize; + + Some((i.saturating_sub(1), j.saturating_sub(1))) + } else { + None + } + } + } + + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + enum Cell { + Unpopulated, + Populated, + } + + impl Default for Cell { + fn default() -> Cell { + Cell::Unpopulated + } + } + + #[derive(Debug, Clone, Copy)] + pub enum Message { + Populate { i: usize, j: usize }, + } + + impl<'a> canvas::Program for Grid { + fn update( + &mut self, + event: Event, + bounds: Rectangle, + cursor: Cursor, + ) -> Option { + if let Event::Mouse(mouse::Event::Input { + button: mouse::Button::Left, + state, + }) = event + { + self.mouse_pressed = state == ButtonState::Pressed; + } + + let cursor_position = cursor.internal_position(&bounds)?; + + let region = self.region(bounds.size()); + let (i, j) = self.cell_at(region, cursor_position)?; + + let populate = if self.cells[i][j] != Cell::Populated { + Some(Message::Populate { i, j }) + } else { + None + }; + + match event { + Event::Mouse(mouse::Event::Input { + button: mouse::Button::Left, + .. + }) if self.mouse_pressed => populate, + Event::Mouse(mouse::Event::CursorMoved { .. }) + if self.mouse_pressed => + { + populate + } + _ => None, + } + } + + fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Vec { + let region = self.region(bounds.size()); + let cell_size = Size::new(1.0, 1.0); + + let life = self.cache.draw(bounds.size(), |frame| { + let background = + Path::rectangle(region.position(), region.size()); + frame.fill( + &background, + Color::from_rgb( + 0x40 as f32 / 255.0, + 0x44 as f32 / 255.0, + 0x4B as f32 / 255.0, + ), + ); + + frame.with_save(|frame| { + frame.translate(Vector::new(region.x, region.y)); + frame.scale(region.width / SIZE as f32); + + let cells = Path::new(|p| { + for (i, row) in self.cells.iter().enumerate() { + for (j, cell) in row.iter().enumerate() { + if *cell == Cell::Populated { + p.rectangle( + Point::new(j as f32, i as f32), + cell_size, + ); + } + } + } + }); + frame.fill(&cells, Color::WHITE); + }); + }); + + let hovered_cell = { + let mut frame = Frame::new(bounds.size()); + + frame.translate(Vector::new(region.x, region.y)); + frame.scale(region.width / SIZE as f32); + + if let Some(cursor_position) = cursor.internal_position(&bounds) + { + if let Some((i, j)) = self.cell_at(region, cursor_position) + { + let interaction = Path::rectangle( + Point::new(j as f32, i as f32), + cell_size, + ); + + frame.fill( + &interaction, + Color { + a: 0.5, + ..Color::BLACK + }, + ); + } + } + + frame.into_geometry() + }; + + vec![life, hovered_cell] + } + + fn mouse_cursor( + &self, + bounds: Rectangle, + cursor: Cursor, + ) -> MouseCursor { + let region = self.region(bounds.size()); + + match cursor.internal_position(&bounds) { + Some(position) if region.contains(position) => { + MouseCursor::Crosshair + } + _ => MouseCursor::default(), + } + } + } +} diff --git a/examples/game_of_life/src/style.rs b/examples/game_of_life/src/style.rs new file mode 100644 index 0000000000..0becb5bef9 --- /dev/null +++ b/examples/game_of_life/src/style.rs @@ -0,0 +1,96 @@ +use iced::{button, container, slider, Background, Color}; + +const ACTIVE: Color = Color::from_rgb( + 0x72 as f32 / 255.0, + 0x89 as f32 / 255.0, + 0xDA as f32 / 255.0, +); + +const HOVERED: Color = Color::from_rgb( + 0x67 as f32 / 255.0, + 0x7B as f32 / 255.0, + 0xC4 as f32 / 255.0, +); + +pub struct Container; + +impl container::StyleSheet for Container { + fn style(&self) -> container::Style { + container::Style { + background: Some(Background::Color(Color::from_rgb8( + 0x36, 0x39, 0x3F, + ))), + text_color: Some(Color::WHITE), + ..container::Style::default() + } + } +} + +pub struct Button; + +impl button::StyleSheet for Button { + fn active(&self) -> button::Style { + button::Style { + background: Some(Background::Color(ACTIVE)), + border_radius: 3, + text_color: Color::WHITE, + ..button::Style::default() + } + } + + fn hovered(&self) -> button::Style { + button::Style { + background: Some(Background::Color(HOVERED)), + text_color: Color::WHITE, + ..self.active() + } + } + + fn pressed(&self) -> button::Style { + button::Style { + border_width: 1, + border_color: Color::WHITE, + ..self.hovered() + } + } +} + +pub struct Slider; + +impl slider::StyleSheet for Slider { + fn active(&self) -> slider::Style { + slider::Style { + rail_colors: (ACTIVE, Color { a: 0.1, ..ACTIVE }), + handle: slider::Handle { + shape: slider::HandleShape::Circle { radius: 9 }, + color: ACTIVE, + border_width: 0, + border_color: Color::TRANSPARENT, + }, + } + } + + fn hovered(&self) -> slider::Style { + let active = self.active(); + + slider::Style { + handle: slider::Handle { + color: HOVERED, + ..active.handle + }, + ..active + } + } + + fn dragging(&self) -> slider::Style { + let active = self.active(); + + slider::Style { + handle: slider::Handle { + color: Color::from_rgb(0.85, 0.85, 0.85), + ..active.handle + }, + ..active + } + } +} diff --git a/examples/game_of_life/src/time.rs b/examples/game_of_life/src/time.rs new file mode 100644 index 0000000000..7b475ecd39 --- /dev/null +++ b/examples/game_of_life/src/time.rs @@ -0,0 +1,34 @@ +use iced::futures; + +pub fn every( + duration: std::time::Duration, +) -> iced::Subscription { + iced::Subscription::from_recipe(Every(duration)) +} + +struct Every(std::time::Duration); + +impl iced_native::subscription::Recipe for Every +where + H: std::hash::Hasher, +{ + type Output = std::time::Instant; + + fn hash(&self, state: &mut H) { + use std::hash::Hash; + + std::any::TypeId::of::().hash(state); + self.0.hash(state); + } + + fn stream( + self: Box, + _input: futures::stream::BoxStream<'static, I>, + ) -> futures::stream::BoxStream<'static, Self::Output> { + use futures::stream::StreamExt; + + async_std::stream::interval(self.0) + .map(|_| std::time::Instant::now()) + .boxed() + } +} From 5d12e194f45b4a01034f3f52fae16c10bc0192dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 29 Apr 2020 20:58:59 +0200 Subject: [PATCH 32/73] Rename `Cursor::*_position` methods in `canvas` --- examples/bezier_tool/src/main.rs | 4 ++-- examples/game_of_life/src/main.rs | 7 +++---- wgpu/src/widget/canvas/cursor.rs | 22 +++++++++++----------- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs index 6473db75df..3cecd05853 100644 --- a/examples/bezier_tool/src/main.rs +++ b/examples/bezier_tool/src/main.rs @@ -110,7 +110,7 @@ mod bezier { bounds: Rectangle, cursor: Cursor, ) -> Option { - let cursor_position = cursor.internal_position(&bounds)?; + let cursor_position = cursor.position_in(&bounds)?; match event { Event::Mouse(mouse_event) => match mouse_event { @@ -210,7 +210,7 @@ mod bezier { fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Geometry { let mut frame = Frame::new(bounds.size()); - if let Some(cursor_position) = cursor.internal_position(&bounds) { + if let Some(cursor_position) = cursor.position_in(&bounds) { match *self { Pending::One { from } => { let line = Path::line(from, cursor_position); diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index 3e6848df06..a2628594e2 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -298,7 +298,7 @@ mod grid { self.mouse_pressed = state == ButtonState::Pressed; } - let cursor_position = cursor.internal_position(&bounds)?; + let cursor_position = cursor.position_in(&bounds)?; let region = self.region(bounds.size()); let (i, j) = self.cell_at(region, cursor_position)?; @@ -365,8 +365,7 @@ mod grid { frame.translate(Vector::new(region.x, region.y)); frame.scale(region.width / SIZE as f32); - if let Some(cursor_position) = cursor.internal_position(&bounds) - { + if let Some(cursor_position) = cursor.position_in(&bounds) { if let Some((i, j)) = self.cell_at(region, cursor_position) { let interaction = Path::rectangle( @@ -397,7 +396,7 @@ mod grid { ) -> MouseCursor { let region = self.region(bounds.size()); - match cursor.internal_position(&bounds) { + match cursor.position_in(&bounds) { Some(position) if region.contains(position) => { MouseCursor::Crosshair } diff --git a/wgpu/src/widget/canvas/cursor.rs b/wgpu/src/widget/canvas/cursor.rs index a559782a5d..7ab58b87a9 100644 --- a/wgpu/src/widget/canvas/cursor.rs +++ b/wgpu/src/widget/canvas/cursor.rs @@ -24,23 +24,23 @@ impl Cursor { } } - pub fn relative_position(&self, bounds: &Rectangle) -> Option { - match self { - Cursor::Available(position) => { - Some(Point::new(position.x - bounds.x, position.y - bounds.y)) - } - _ => None, - } - } - - pub fn internal_position(&self, bounds: &Rectangle) -> Option { + pub fn position_in(&self, bounds: &Rectangle) -> Option { if self.is_over(bounds) { - self.relative_position(bounds) + self.position_from(bounds.position()) } else { None } } + pub fn position_from(&self, origin: Point) -> Option { + match self { + Cursor::Available(position) => { + Some(Point::new(position.x - origin.x, position.y - origin.y)) + } + Cursor::Unavailable => None, + } + } + pub fn is_over(&self, bounds: &Rectangle) -> bool { match self { Cursor::Available(position) => bounds.contains(*position), From 38c4dd5fdbdb299c1c5b0e64f44855e98a0db85f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 29 Apr 2020 23:49:37 +0200 Subject: [PATCH 33/73] Reduce initial size of `triangle` buffers in `iced_wgpu` --- wgpu/src/triangle.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 246dc7ce66..3e68a26999 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -7,8 +7,8 @@ use zerocopy::AsBytes; mod msaa; const UNIFORM_BUFFER_SIZE: usize = 100; -const VERTEX_BUFFER_SIZE: usize = 100_000; -const INDEX_BUFFER_SIZE: usize = 100_000; +const VERTEX_BUFFER_SIZE: usize = 10_000; +const INDEX_BUFFER_SIZE: usize = 10_000; #[derive(Debug)] pub(crate) struct Pipeline { From 5e014a70e864964570e4c1568926b8c647f73c59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 29 Apr 2020 23:50:15 +0200 Subject: [PATCH 34/73] Use sparse grid representation in `game_of_life` --- examples/game_of_life/src/main.rs | 137 +++++++++++++++--------------- 1 file changed, 69 insertions(+), 68 deletions(-) diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index a2628594e2..fb12afa162 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -14,10 +14,7 @@ use iced::{ use std::time::{Duration, Instant}; pub fn main() { - GameOfLife::run(Settings { - antialiasing: true, - ..Settings::default() - }) + GameOfLife::run(Settings::default()) } #[derive(Default)] @@ -164,36 +161,55 @@ mod grid { mouse, ButtonState, Color, Element, Length, MouseCursor, Point, Rectangle, Size, Vector, }; + use std::collections::{HashMap, HashSet}; - const SIZE: usize = 32; + const CELL_SIZE: usize = 20; #[derive(Default)] pub struct Grid { - cells: [[Cell; SIZE]; SIZE], + alive_cells: HashSet<(usize, usize)>, mouse_pressed: bool, cache: canvas::Cache, } impl Grid { - pub fn tick(&mut self) { - let mut populated_neighbors: [[usize; SIZE]; SIZE] = - [[0; SIZE]; SIZE]; + fn with_neighbors( + i: usize, + j: usize, + ) -> impl Iterator { + use itertools::Itertools; - for (i, row) in self.cells.iter().enumerate() { - for (j, _) in row.iter().enumerate() { - populated_neighbors[i][j] = self.populated_neighbors(i, j); - } - } + let rows = i.saturating_sub(1)..=i.saturating_add(1); + let columns = j.saturating_sub(1)..=j.saturating_add(1); - for (i, row) in populated_neighbors.iter().enumerate() { - for (j, amount) in row.iter().enumerate() { - let is_populated = self.cells[i][j] == Cell::Populated; + rows.cartesian_product(columns) + } - self.cells[i][j] = match amount { - 2 if is_populated => Cell::Populated, - 3 => Cell::Populated, - _ => Cell::Unpopulated, - }; + pub fn tick(&mut self) { + use itertools::Itertools; + + let populated_neighbors: HashMap<(usize, usize), usize> = self + .alive_cells + .iter() + .flat_map(|&(i, j)| Self::with_neighbors(i, j)) + .unique() + .map(|(i, j)| ((i, j), self.populated_neighbors(i, j))) + .collect(); + + for (&(i, j), amount) in populated_neighbors.iter() { + let is_populated = self.alive_cells.contains(&(i, j)); + + match amount { + 2 if is_populated => {} + 3 => { + if !is_populated { + self.alive_cells.insert((i, j)); + } + } + _ if is_populated => { + self.alive_cells.remove(&(i, j)); + } + _ => {} } } @@ -202,8 +218,8 @@ mod grid { pub fn update(&mut self, message: Message) { match message { - Message::Populate { i, j } => { - self.cells[i][j] = Cell::Populated; + Message::Populate { cell } => { + self.alive_cells.insert(cell); self.cache.clear() } } @@ -217,34 +233,28 @@ mod grid { } fn populated_neighbors(&self, row: usize, column: usize) -> usize { - use itertools::Itertools; + let with_neighbors = Self::with_neighbors(row, column); - let rows = row.saturating_sub(1)..=row + 1; - let columns = column.saturating_sub(1)..=column + 1; - - let is_inside_bounds = |i: usize, j: usize| i < SIZE && j < SIZE; let is_neighbor = |i: usize, j: usize| i != row || j != column; - let is_populated = - |i: usize, j: usize| self.cells[i][j] == Cell::Populated; + |i: usize, j: usize| self.alive_cells.contains(&(i, j)); - rows.cartesian_product(columns) - .filter(|&(i, j)| { - is_inside_bounds(i, j) - && is_neighbor(i, j) - && is_populated(i, j) - }) + with_neighbors + .filter(|&(i, j)| is_neighbor(i, j) && is_populated(i, j)) .count() } fn region(&self, size: Size) -> Rectangle { - let side = size.width.min(size.height); + let width = + (size.width / CELL_SIZE as f32).floor() * CELL_SIZE as f32; + let height = + (size.height / CELL_SIZE as f32).floor() * CELL_SIZE as f32; Rectangle { - x: (size.width - side) / 2.0, - y: (size.height - side) / 2.0, - width: side, - height: side, + x: (size.width - width) / 2.0, + y: (size.height - height) / 2.0, + width, + height, } } @@ -254,10 +264,10 @@ mod grid { position: Point, ) -> Option<(usize, usize)> { if region.contains(position) { - let cell_size = region.width / SIZE as f32; - - let i = ((position.y - region.y) / cell_size).ceil() as usize; - let j = ((position.x - region.x) / cell_size).ceil() as usize; + let i = ((position.y - region.y) / CELL_SIZE as f32).ceil() + as usize; + let j = ((position.x - region.x) / CELL_SIZE as f32).ceil() + as usize; Some((i.saturating_sub(1), j.saturating_sub(1))) } else { @@ -266,21 +276,9 @@ mod grid { } } - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - enum Cell { - Unpopulated, - Populated, - } - - impl Default for Cell { - fn default() -> Cell { - Cell::Unpopulated - } - } - #[derive(Debug, Clone, Copy)] pub enum Message { - Populate { i: usize, j: usize }, + Populate { cell: (usize, usize) }, } impl<'a> canvas::Program for Grid { @@ -301,12 +299,12 @@ mod grid { let cursor_position = cursor.position_in(&bounds)?; let region = self.region(bounds.size()); - let (i, j) = self.cell_at(region, cursor_position)?; + let cell = self.cell_at(region, cursor_position)?; - let populate = if self.cells[i][j] != Cell::Populated { - Some(Message::Populate { i, j }) - } else { + let populate = if self.alive_cells.contains(&cell) { None + } else { + Some(Message::Populate { cell }) }; match event { @@ -339,14 +337,17 @@ mod grid { ), ); + let visible_rows = region.height as usize / CELL_SIZE; + let visible_columns = region.width as usize / CELL_SIZE; + frame.with_save(|frame| { frame.translate(Vector::new(region.x, region.y)); - frame.scale(region.width / SIZE as f32); + frame.scale(CELL_SIZE as f32); let cells = Path::new(|p| { - for (i, row) in self.cells.iter().enumerate() { - for (j, cell) in row.iter().enumerate() { - if *cell == Cell::Populated { + for i in 0..visible_rows { + for j in 0..visible_columns { + if self.alive_cells.contains(&(i, j)) { p.rectangle( Point::new(j as f32, i as f32), cell_size, @@ -363,7 +364,7 @@ mod grid { let mut frame = Frame::new(bounds.size()); frame.translate(Vector::new(region.x, region.y)); - frame.scale(region.width / SIZE as f32); + frame.scale(CELL_SIZE as f32); if let Some(cursor_position) = cursor.position_in(&bounds) { if let Some((i, j)) = self.cell_at(region, cursor_position) From 611d9e399c95268a3daf41bd6cbcc55391ccff87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 29 Apr 2020 23:55:15 +0200 Subject: [PATCH 35/73] Clarify `tick` logic in `game_of_life` --- examples/game_of_life/src/main.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index fb12afa162..3989e3ea68 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -200,14 +200,12 @@ mod grid { let is_populated = self.alive_cells.contains(&(i, j)); match amount { - 2 if is_populated => {} + 2 | 3 if is_populated => {} 3 => { - if !is_populated { - self.alive_cells.insert((i, j)); - } + let _ = self.alive_cells.insert((i, j)); } _ if is_populated => { - self.alive_cells.remove(&(i, j)); + let _ = self.alive_cells.remove(&(i, j)); } _ => {} } From af95d3972e4ab6bf4ace54ddd44379ffcebbcff6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 30 Apr 2020 04:12:13 +0200 Subject: [PATCH 36/73] Implement camera panning in `game_of_life` example --- examples/game_of_life/src/main.rs | 165 ++++++++++++++++-------------- 1 file changed, 90 insertions(+), 75 deletions(-) diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index 3989e3ea68..983d6cb499 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -136,13 +136,13 @@ impl Application for GameOfLife { .align_items(Align::Center); let controls = Row::new() + .padding(10) .spacing(20) .push(playback_controls) .push(speed_controls); let content = Column::new() .spacing(10) - .padding(10) .align_items(Align::Center) .push(self.grid.view().map(Message::Grid)) .push(controls); @@ -167,16 +167,27 @@ mod grid { #[derive(Default)] pub struct Grid { - alive_cells: HashSet<(usize, usize)>, - mouse_pressed: bool, + alive_cells: HashSet<(isize, isize)>, + interaction: Option, cache: canvas::Cache, + translation: Vector, + } + + #[derive(Debug, Clone, Copy)] + pub enum Message { + Populate { cell: (isize, isize) }, + } + + enum Interaction { + Drawing, + Panning { translation: Vector, start: Point }, } impl Grid { fn with_neighbors( - i: usize, - j: usize, - ) -> impl Iterator { + i: isize, + j: isize, + ) -> impl Iterator { use itertools::Itertools; let rows = i.saturating_sub(1)..=i.saturating_add(1); @@ -188,7 +199,7 @@ mod grid { pub fn tick(&mut self) { use itertools::Itertools; - let populated_neighbors: HashMap<(usize, usize), usize> = self + let populated_neighbors: HashMap<(isize, isize), usize> = self .alive_cells .iter() .flat_map(|&(i, j)| Self::with_neighbors(i, j)) @@ -230,55 +241,26 @@ mod grid { .into() } - fn populated_neighbors(&self, row: usize, column: usize) -> usize { + fn populated_neighbors(&self, row: isize, column: isize) -> usize { let with_neighbors = Self::with_neighbors(row, column); - let is_neighbor = |i: usize, j: usize| i != row || j != column; + let is_neighbor = |i: isize, j: isize| i != row || j != column; let is_populated = - |i: usize, j: usize| self.alive_cells.contains(&(i, j)); + |i: isize, j: isize| self.alive_cells.contains(&(i, j)); with_neighbors .filter(|&(i, j)| is_neighbor(i, j) && is_populated(i, j)) .count() } - fn region(&self, size: Size) -> Rectangle { - let width = - (size.width / CELL_SIZE as f32).floor() * CELL_SIZE as f32; - let height = - (size.height / CELL_SIZE as f32).floor() * CELL_SIZE as f32; - - Rectangle { - x: (size.width - width) / 2.0, - y: (size.height - height) / 2.0, - width, - height, - } - } + fn cell_at(&self, position: Point) -> Option<(isize, isize)> { + let i = (position.y / CELL_SIZE as f32).ceil() as isize; + let j = (position.x / CELL_SIZE as f32).ceil() as isize; - fn cell_at( - &self, - region: Rectangle, - position: Point, - ) -> Option<(usize, usize)> { - if region.contains(position) { - let i = ((position.y - region.y) / CELL_SIZE as f32).ceil() - as usize; - let j = ((position.x - region.x) / CELL_SIZE as f32).ceil() - as usize; - - Some((i.saturating_sub(1), j.saturating_sub(1))) - } else { - None - } + Some((i.saturating_sub(1), j.saturating_sub(1))) } } - #[derive(Debug, Clone, Copy)] - pub enum Message { - Populate { cell: (usize, usize) }, - } - impl<'a> canvas::Program for Grid { fn update( &mut self, @@ -287,17 +269,15 @@ mod grid { cursor: Cursor, ) -> Option { if let Event::Mouse(mouse::Event::Input { - button: mouse::Button::Left, - state, + state: ButtonState::Released, + .. }) = event { - self.mouse_pressed = state == ButtonState::Pressed; + self.interaction = None; } let cursor_position = cursor.position_in(&bounds)?; - - let region = self.region(bounds.size()); - let cell = self.cell_at(region, cursor_position)?; + let cell = self.cell_at(cursor_position - self.translation)?; let populate = if self.alive_cells.contains(&cell) { None @@ -306,26 +286,53 @@ mod grid { }; match event { - Event::Mouse(mouse::Event::Input { - button: mouse::Button::Left, - .. - }) if self.mouse_pressed => populate, - Event::Mouse(mouse::Event::CursorMoved { .. }) - if self.mouse_pressed => - { - populate - } - _ => None, + Event::Mouse(mouse_event) => match mouse_event { + mouse::Event::Input { + button, + state: ButtonState::Pressed, + } => match button { + mouse::Button::Left => { + self.interaction = Some(Interaction::Drawing); + + populate + } + mouse::Button::Right => { + self.interaction = Some(Interaction::Panning { + translation: self.translation, + start: cursor_position, + }); + + None + } + _ => None, + }, + mouse::Event::CursorMoved { .. } => { + match self.interaction { + Some(Interaction::Drawing) => populate, + Some(Interaction::Panning { + translation, + start, + }) => { + self.translation = + translation + (cursor_position - start); + + self.cache.clear(); + + None + } + _ => None, + } + } + _ => None, + }, } } fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Vec { - let region = self.region(bounds.size()); let cell_size = Size::new(1.0, 1.0); let life = self.cache.draw(bounds.size(), |frame| { - let background = - Path::rectangle(region.position(), region.size()); + let background = Path::rectangle(Point::ORIGIN, frame.size()); frame.fill( &background, Color::from_rgb( @@ -335,16 +342,25 @@ mod grid { ), ); - let visible_rows = region.height as usize / CELL_SIZE; - let visible_columns = region.width as usize / CELL_SIZE; + let first_row = + (-self.translation.y / CELL_SIZE as f32).floor() as isize; + let first_column = + (-self.translation.x / CELL_SIZE as f32).floor() as isize; + + let visible_rows = + (frame.height() / CELL_SIZE as f32).ceil() as isize; + let visible_columns = + (frame.width() / CELL_SIZE as f32).ceil() as isize; frame.with_save(|frame| { - frame.translate(Vector::new(region.x, region.y)); + frame.translate(self.translation); frame.scale(CELL_SIZE as f32); let cells = Path::new(|p| { - for i in 0..visible_rows { - for j in 0..visible_columns { + for i in first_row..=(first_row + visible_rows) { + for j in + first_column..=(first_column + visible_columns) + { if self.alive_cells.contains(&(i, j)) { p.rectangle( Point::new(j as f32, i as f32), @@ -361,11 +377,12 @@ mod grid { let hovered_cell = { let mut frame = Frame::new(bounds.size()); - frame.translate(Vector::new(region.x, region.y)); + frame.translate(self.translation); frame.scale(CELL_SIZE as f32); if let Some(cursor_position) = cursor.position_in(&bounds) { - if let Some((i, j)) = self.cell_at(region, cursor_position) + if let Some((i, j)) = + self.cell_at(cursor_position - self.translation) { let interaction = Path::rectangle( Point::new(j as f32, i as f32), @@ -393,12 +410,10 @@ mod grid { bounds: Rectangle, cursor: Cursor, ) -> MouseCursor { - let region = self.region(bounds.size()); - - match cursor.position_in(&bounds) { - Some(position) if region.contains(position) => { - MouseCursor::Crosshair - } + match self.interaction { + Some(Interaction::Drawing) => MouseCursor::Crosshair, + Some(Interaction::Panning { .. }) => MouseCursor::Grabbing, + None if cursor.is_over(&bounds) => MouseCursor::Crosshair, _ => MouseCursor::default(), } } From e139aae1439d362ada017a05c9554eaae0883888 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 30 Apr 2020 04:34:29 +0200 Subject: [PATCH 37/73] Split `Input` keyboard event by `ButtonState` --- native/src/input/keyboard/event.rs | 15 ++++++++++----- native/src/widget/pane_grid.rs | 26 +++++++++++++------------- native/src/widget/text_input.rs | 9 +++------ winit/src/conversion.rs | 22 ++++++++++++++++++---- 4 files changed, 44 insertions(+), 28 deletions(-) diff --git a/native/src/input/keyboard/event.rs b/native/src/input/keyboard/event.rs index 862f30c483..bc8437a8c8 100644 --- a/native/src/input/keyboard/event.rs +++ b/native/src/input/keyboard/event.rs @@ -1,5 +1,4 @@ use super::{KeyCode, ModifiersState}; -use crate::input::ButtonState; /// A keyboard event. /// @@ -9,11 +8,17 @@ use crate::input::ButtonState; /// [open an issue]: https://github.com/hecrj/iced/issues #[derive(Debug, Clone, Copy, PartialEq)] pub enum Event { - /// A keyboard key was pressed or released. - Input { - /// The state of the key - state: ButtonState, + /// A keyboard key was pressed. + KeyPressed { + /// The key identifier + key_code: KeyCode, + + /// The state of the modifier keys + modifiers: ModifiersState, + }, + /// A keyboard key was released. + KeyReleased { /// The key identifier key_code: KeyCode, diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index f84775eddb..0b735ad36d 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -532,24 +532,19 @@ where Event::Mouse(mouse::Event::CursorMoved { .. }) => { self.trigger_resize(layout, cursor_position, messages); } - Event::Keyboard(keyboard::Event::Input { + Event::Keyboard(keyboard::Event::KeyPressed { modifiers, key_code, - state, }) => { if let Some(on_key_press) = &self.on_key_press { // TODO: Discard when event is captured - if state == ButtonState::Pressed { - if let Some(_) = self.state.active_pane() { - if modifiers.matches(self.modifier_keys) { - if let Some(message) = - on_key_press(KeyPressEvent { - key_code, - modifiers, - }) - { - messages.push(message); - } + if let Some(_) = self.state.active_pane() { + if modifiers.matches(self.modifier_keys) { + if let Some(message) = on_key_press(KeyPressEvent { + key_code, + modifiers, + }) { + messages.push(message); } } } @@ -557,6 +552,11 @@ where *self.pressed_modifiers = modifiers; } + Event::Keyboard(keyboard::Event::KeyReleased { + modifiers, .. + }) => { + *self.pressed_modifiers = modifiers; + } _ => {} } diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index 7d1a7415c9..ea6921b5f8 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -327,9 +327,8 @@ where let message = (self.on_change)(editor.contents()); messages.push(message); } - Event::Keyboard(keyboard::Event::Input { + Event::Keyboard(keyboard::Event::KeyPressed { key_code, - state: ButtonState::Pressed, modifiers, }) if self.state.is_focused => match key_code { keyboard::KeyCode::Enter => { @@ -473,10 +472,8 @@ where } _ => {} }, - Event::Keyboard(keyboard::Event::Input { - key_code, - state: ButtonState::Released, - .. + Event::Keyboard(keyboard::Event::KeyReleased { + key_code, .. }) => match key_code { keyboard::KeyCode::V => { self.state.is_pasting = None; diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index a9d9b7cd38..30efc590f7 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -70,10 +70,24 @@ pub fn window_event( .. }, .. - } => Some(Event::Keyboard(keyboard::Event::Input { - key_code: key_code(*virtual_keycode), - state: button_state(*state), - modifiers: modifiers_state(modifiers), + } => Some(Event::Keyboard({ + let key_code = key_code(*virtual_keycode); + let modifiers = modifiers_state(modifiers); + + match state { + winit::event::ElementState::Pressed => { + keyboard::Event::KeyPressed { + key_code, + modifiers, + } + } + winit::event::ElementState::Released => { + keyboard::Event::KeyReleased { + key_code, + modifiers, + } + } + } })), WindowEvent::HoveredFile(path) => { Some(Event::Window(window::Event::FileHovered(path.clone()))) From e55cd9652e7c7aea4dc2c6ccb83769246d1a808e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 30 Apr 2020 04:53:15 +0200 Subject: [PATCH 38/73] Split `Input` mouse event by `ButtonState` --- core/src/mouse/event.rs | 12 +- examples/bezier_tool/src/main.rs | 55 +++++---- examples/game_of_life/src/main.rs | 15 +-- native/src/widget/button.rs | 35 +++--- native/src/widget/checkbox.rs | 8 +- native/src/widget/pane_grid.rs | 188 ++++++++++++++++-------------- native/src/widget/radio.rs | 8 +- native/src/widget/scrollable.rs | 20 ++-- native/src/widget/slider.rs | 25 ++-- native/src/widget/text_input.rs | 11 +- winit/src/conversion.rs | 12 +- 11 files changed, 183 insertions(+), 206 deletions(-) diff --git a/core/src/mouse/event.rs b/core/src/mouse/event.rs index 52e9d851d9..2f07b20727 100644 --- a/core/src/mouse/event.rs +++ b/core/src/mouse/event.rs @@ -1,5 +1,4 @@ use super::Button; -use crate::ButtonState; /// A mouse event. /// @@ -24,14 +23,11 @@ pub enum Event { y: f32, }, - /// A mouse button was pressed or released. - Input { - /// The button identifier - button: Button, + /// A mouse button was pressed. + ButtonPressed(Button), - /// The state of the button - state: ButtonState, - }, + /// A mouse button was released. + ButtonReleased(Button), /// The mouse wheel was scrolled. WheelScrolled { diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs index 3cecd05853..fe4136b4a5 100644 --- a/examples/bezier_tool/src/main.rs +++ b/examples/bezier_tool/src/main.rs @@ -70,7 +70,7 @@ impl Sandbox for Example { mod bezier { use iced::{ canvas::{self, Canvas, Cursor, Event, Frame, Geometry, Path, Stroke}, - mouse, ButtonState, Element, Length, MouseCursor, Point, Rectangle, + mouse, Element, Length, MouseCursor, Point, Rectangle, }; #[derive(Default)] @@ -114,34 +114,33 @@ mod bezier { match event { Event::Mouse(mouse_event) => match mouse_event { - mouse::Event::Input { - button: mouse::Button::Left, - state: ButtonState::Pressed, - } => match self.state.pending { - None => { - self.state.pending = Some(Pending::One { - from: cursor_position, - }); - None + mouse::Event::ButtonPressed(mouse::Button::Left) => { + match self.state.pending { + None => { + self.state.pending = Some(Pending::One { + from: cursor_position, + }); + None + } + Some(Pending::One { from }) => { + self.state.pending = Some(Pending::Two { + from, + to: cursor_position, + }); + + None + } + Some(Pending::Two { from, to }) => { + self.state.pending = None; + + Some(Curve { + from, + to, + control: cursor_position, + }) + } } - Some(Pending::One { from }) => { - self.state.pending = Some(Pending::Two { - from, - to: cursor_position, - }); - - None - } - Some(Pending::Two { from, to }) => { - self.state.pending = None; - - Some(Curve { - from, - to, - control: cursor_position, - }) - } - }, + } _ => None, }, } diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index 983d6cb499..9fb4c3e7b8 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -158,8 +158,8 @@ impl Application for GameOfLife { mod grid { use iced::{ canvas::{self, Canvas, Cursor, Event, Frame, Geometry, Path}, - mouse, ButtonState, Color, Element, Length, MouseCursor, Point, - Rectangle, Size, Vector, + mouse, Color, Element, Length, MouseCursor, Point, Rectangle, Size, + Vector, }; use std::collections::{HashMap, HashSet}; @@ -268,11 +268,7 @@ mod grid { bounds: Rectangle, cursor: Cursor, ) -> Option { - if let Event::Mouse(mouse::Event::Input { - state: ButtonState::Released, - .. - }) = event - { + if let Event::Mouse(mouse::Event::ButtonReleased(_)) = event { self.interaction = None; } @@ -287,10 +283,7 @@ mod grid { match event { Event::Mouse(mouse_event) => match mouse_event { - mouse::Event::Input { - button, - state: ButtonState::Pressed, - } => match button { + mouse::Event::ButtonPressed(button) => match button { mouse::Button::Left => { self.interaction = Some(Interaction::Drawing); diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs index 3cf4f78045..5d41402305 100644 --- a/native/src/widget/button.rs +++ b/native/src/widget/button.rs @@ -5,9 +5,8 @@ //! [`Button`]: struct.Button.html //! [`State`]: struct.State.html use crate::{ - input::{mouse, ButtonState}, - layout, Clipboard, Element, Event, Hasher, Layout, Length, Point, - Rectangle, Widget, + input::mouse, layout, Clipboard, Element, Event, Hasher, Layout, Length, + Point, Rectangle, Widget, }; use std::hash::Hash; @@ -185,28 +184,24 @@ where _clipboard: Option<&dyn Clipboard>, ) { match event { - Event::Mouse(mouse::Event::Input { - button: mouse::Button::Left, - state, - }) => { + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { + if self.on_press.is_some() { + let bounds = layout.bounds(); + + self.state.is_pressed = bounds.contains(cursor_position); + } + } + Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => { if let Some(on_press) = self.on_press.clone() { let bounds = layout.bounds(); - match state { - ButtonState::Pressed => { - self.state.is_pressed = - bounds.contains(cursor_position); - } - ButtonState::Released => { - let is_clicked = self.state.is_pressed - && bounds.contains(cursor_position); + let is_clicked = self.state.is_pressed + && bounds.contains(cursor_position); - self.state.is_pressed = false; + self.state.is_pressed = false; - if is_clicked { - messages.push(on_press); - } - } + if is_clicked { + messages.push(on_press); } } } diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs index 4d167df777..c49ac707f1 100644 --- a/native/src/widget/checkbox.rs +++ b/native/src/widget/checkbox.rs @@ -2,8 +2,7 @@ use std::hash::Hash; use crate::{ - input::{mouse, ButtonState}, - layout, row, text, Align, Clipboard, Element, Event, Hasher, + input::mouse, layout, row, text, Align, Clipboard, Element, Event, Hasher, HorizontalAlignment, Layout, Length, Point, Rectangle, Row, Text, VerticalAlignment, Widget, }; @@ -152,10 +151,7 @@ where _clipboard: Option<&dyn Clipboard>, ) { match event { - Event::Mouse(mouse::Event::Input { - button: mouse::Button::Left, - state: ButtonState::Pressed, - }) => { + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { let mouse_over = layout.bounds().contains(cursor_position); if mouse_over { diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 0b735ad36d..fe2bbe07e7 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -22,7 +22,7 @@ pub use split::Split; pub use state::{Focus, State}; use crate::{ - input::{keyboard, mouse, ButtonState}, + input::{keyboard, mouse}, layout, Clipboard, Element, Event, Hasher, Layout, Length, Point, Size, Widget, }; @@ -405,11 +405,8 @@ where clipboard: Option<&dyn Clipboard>, ) { match event { - Event::Mouse(mouse::Event::Input { - button: mouse::Button::Left, - state, - }) => match state { - ButtonState::Pressed => { + Event::Mouse(mouse_event) => match mouse_event { + mouse::Event::ButtonPressed(mouse::Button::Left) => { let mut clicked_region = self.elements.iter().zip(layout.children()).filter( |(_, layout)| { @@ -438,7 +435,7 @@ where self.state.unfocus(); } } - ButtonState::Released => { + mouse::Event::ButtonReleased(mouse::Button::Left) => { if let Some(pane) = self.state.picked_pane() { self.state.focus(&pane); @@ -465,97 +462,110 @@ where } } } - }, - Event::Mouse(mouse::Event::Input { - button: mouse::Button::Right, - state: ButtonState::Pressed, - }) if self.on_resize.is_some() - && self.state.picked_pane().is_none() - && self.pressed_modifiers.matches(self.modifier_keys) => - { - let bounds = layout.bounds(); + mouse::Event::ButtonPressed(mouse::Button::Right) + if self.on_resize.is_some() + && self.state.picked_pane().is_none() + && self + .pressed_modifiers + .matches(self.modifier_keys) => + { + let bounds = layout.bounds(); + + if bounds.contains(cursor_position) { + let relative_cursor = Point::new( + cursor_position.x - bounds.x, + cursor_position.y - bounds.y, + ); - if bounds.contains(cursor_position) { - let relative_cursor = Point::new( - cursor_position.x - bounds.x, - cursor_position.y - bounds.y, - ); - - let splits = self.state.splits( - f32::from(self.spacing), - Size::new(bounds.width, bounds.height), - ); - - let mut sorted_splits: Vec<_> = splits - .iter() - .filter(|(_, (axis, rectangle, _))| match axis { - Axis::Horizontal => { - relative_cursor.x > rectangle.x - && relative_cursor.x - < rectangle.x + rectangle.width - } - Axis::Vertical => { - relative_cursor.y > rectangle.y - && relative_cursor.y - < rectangle.y + rectangle.height - } - }) - .collect(); - - sorted_splits.sort_by_key( - |(_, (axis, rectangle, ratio))| { - let distance = match axis { - Axis::Horizontal => (relative_cursor.y - - (rectangle.y + rectangle.height * ratio)) - .abs(), - Axis::Vertical => (relative_cursor.x - - (rectangle.x + rectangle.width * ratio)) - .abs(), - }; + let splits = self.state.splits( + f32::from(self.spacing), + Size::new(bounds.width, bounds.height), + ); - distance.round() as u32 - }, - ); + let mut sorted_splits: Vec<_> = splits + .iter() + .filter(|(_, (axis, rectangle, _))| match axis { + Axis::Horizontal => { + relative_cursor.x > rectangle.x + && relative_cursor.x + < rectangle.x + rectangle.width + } + Axis::Vertical => { + relative_cursor.y > rectangle.y + && relative_cursor.y + < rectangle.y + rectangle.height + } + }) + .collect(); + + sorted_splits.sort_by_key( + |(_, (axis, rectangle, ratio))| { + let distance = match axis { + Axis::Horizontal => (relative_cursor.y + - (rectangle.y + + rectangle.height * ratio)) + .abs(), + Axis::Vertical => (relative_cursor.x + - (rectangle.x + + rectangle.width * ratio)) + .abs(), + }; + + distance.round() as u32 + }, + ); - if let Some((split, (axis, _, _))) = sorted_splits.first() { - self.state.pick_split(split, *axis); - self.trigger_resize(layout, cursor_position, messages); + if let Some((split, (axis, _, _))) = + sorted_splits.first() + { + self.state.pick_split(split, *axis); + self.trigger_resize( + layout, + cursor_position, + messages, + ); + } } } - } - Event::Mouse(mouse::Event::Input { - button: mouse::Button::Right, - state: ButtonState::Released, - }) if self.state.picked_split().is_some() => { - self.state.drop_split(); - } - Event::Mouse(mouse::Event::CursorMoved { .. }) => { - self.trigger_resize(layout, cursor_position, messages); - } - Event::Keyboard(keyboard::Event::KeyPressed { - modifiers, - key_code, - }) => { - if let Some(on_key_press) = &self.on_key_press { - // TODO: Discard when event is captured - if let Some(_) = self.state.active_pane() { - if modifiers.matches(self.modifier_keys) { - if let Some(message) = on_key_press(KeyPressEvent { - key_code, - modifiers, - }) { - messages.push(message); + mouse::Event::ButtonPressed(mouse::Button::Right) + if self.state.picked_split().is_some() => + { + self.state.drop_split(); + } + mouse::Event::CursorMoved { .. } => { + self.trigger_resize(layout, cursor_position, messages); + } + _ => {} + }, + Event::Keyboard(keyboard_event) => { + match keyboard_event { + keyboard::Event::KeyPressed { + modifiers, + key_code, + } => { + if let Some(on_key_press) = &self.on_key_press { + // TODO: Discard when event is captured + if let Some(_) = self.state.active_pane() { + if modifiers.matches(self.modifier_keys) { + if let Some(message) = + on_key_press(KeyPressEvent { + key_code, + modifiers, + }) + { + messages.push(message); + } + } } } + + *self.pressed_modifiers = modifiers; } + keyboard::Event::KeyReleased { modifiers, .. } => { + *self.pressed_modifiers = modifiers; + } + _ => {} } - - *self.pressed_modifiers = modifiers; - } - Event::Keyboard(keyboard::Event::KeyReleased { - modifiers, .. - }) => { - *self.pressed_modifiers = modifiers; } _ => {} } diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs index a13d86a0de..deea703450 100644 --- a/native/src/widget/radio.rs +++ b/native/src/widget/radio.rs @@ -1,7 +1,6 @@ //! Create choices using radio buttons. use crate::{ - input::{mouse, ButtonState}, - layout, row, text, Align, Clipboard, Element, Event, Hasher, + input::mouse, layout, row, text, Align, Clipboard, Element, Event, Hasher, HorizontalAlignment, Layout, Length, Point, Rectangle, Row, Text, VerticalAlignment, Widget, }; @@ -123,10 +122,7 @@ where _clipboard: Option<&dyn Clipboard>, ) { match event { - Event::Mouse(mouse::Event::Input { - button: mouse::Button::Left, - state: ButtonState::Pressed, - }) => { + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { if layout.bounds().contains(cursor_position) { messages.push(self.on_click.clone()); } diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 393095a486..da11c50ce6 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -1,9 +1,7 @@ //! Navigate an endless amount of content with a scrollbar. use crate::{ - column, - input::{mouse, ButtonState}, - layout, Align, Clipboard, Column, Element, Event, Hasher, Layout, Length, - Point, Rectangle, Size, Widget, + column, input::mouse, layout, Align, Clipboard, Column, Element, Event, + Hasher, Layout, Length, Point, Rectangle, Size, Widget, }; use std::{f32, hash::Hash, u32}; @@ -188,10 +186,9 @@ where if self.state.is_scroller_grabbed() { match event { - Event::Mouse(mouse::Event::Input { - button: mouse::Button::Left, - state: ButtonState::Released, - }) => { + Event::Mouse(mouse::Event::ButtonReleased( + mouse::Button::Left, + )) => { self.state.scroller_grabbed_at = None; } Event::Mouse(mouse::Event::CursorMoved { .. }) => { @@ -212,10 +209,9 @@ where } } else if is_mouse_over_scrollbar { match event { - Event::Mouse(mouse::Event::Input { - button: mouse::Button::Left, - state: ButtonState::Pressed, - }) => { + Event::Mouse(mouse::Event::ButtonPressed( + mouse::Button::Left, + )) => { if let Some(scrollbar) = scrollbar { if let Some(scroller_grabbed_at) = scrollbar.grab_scroller(cursor_position) diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs index 1feb782577..a00d7c8d5e 100644 --- a/native/src/widget/slider.rs +++ b/native/src/widget/slider.rs @@ -5,9 +5,8 @@ //! [`Slider`]: struct.Slider.html //! [`State`]: struct.State.html use crate::{ - input::{mouse, ButtonState}, - layout, Clipboard, Element, Event, Hasher, Layout, Length, Point, - Rectangle, Size, Widget, + input::mouse, layout, Clipboard, Element, Event, Hasher, Layout, Length, + Point, Rectangle, Size, Widget, }; use std::{hash::Hash, ops::RangeInclusive}; @@ -164,25 +163,23 @@ where }; match event { - Event::Mouse(mouse::Event::Input { - button: mouse::Button::Left, - state, - }) => match state { - ButtonState::Pressed => { + Event::Mouse(mouse_event) => match mouse_event { + mouse::Event::ButtonPressed(mouse::Button::Left) => { if layout.bounds().contains(cursor_position) { change(); self.state.is_dragging = true; } } - ButtonState::Released => { + mouse::Event::ButtonReleased(mouse::Button::Left) => { self.state.is_dragging = false; } - }, - Event::Mouse(mouse::Event::CursorMoved { .. }) => { - if self.state.is_dragging { - change(); + mouse::Event::CursorMoved { .. } => { + if self.state.is_dragging { + change(); + } } - } + _ => {} + }, _ => {} } } diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index ea6921b5f8..b11269db4a 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -18,7 +18,6 @@ use crate::{ input::{ keyboard, mouse::{self, click}, - ButtonState, }, layout, Clipboard, Element, Event, Font, Hasher, Layout, Length, Point, Rectangle, Size, Widget, @@ -212,10 +211,7 @@ where clipboard: Option<&dyn Clipboard>, ) { match event { - Event::Mouse(mouse::Event::Input { - button: mouse::Button::Left, - state: ButtonState::Pressed, - }) => { + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { let is_clicked = layout.bounds().contains(cursor_position); if is_clicked { @@ -280,10 +276,7 @@ where self.state.is_dragging = is_clicked; self.state.is_focused = is_clicked; } - Event::Mouse(mouse::Event::Input { - button: mouse::Button::Left, - state: ButtonState::Released, - }) => { + Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => { self.state.is_dragging = false; } Event::Mouse(mouse::Event::CursorMoved { x, .. }) => { diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index 30efc590f7..3d34497f52 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -36,9 +36,15 @@ pub fn window_event( })) } WindowEvent::MouseInput { button, state, .. } => { - Some(Event::Mouse(mouse::Event::Input { - button: mouse_button(*button), - state: button_state(*state), + let button = mouse_button(*button); + + Some(Event::Mouse(match state { + winit::event::ElementState::Pressed => { + mouse::Event::ButtonPressed(button) + } + winit::event::ElementState::Released => { + mouse::Event::ButtonReleased(button) + } })) } WindowEvent::MouseWheel { delta, .. } => match delta { From d8b9e03481301228ffeda4c6e48a021cb66f896c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 30 Apr 2020 04:54:49 +0200 Subject: [PATCH 39/73] Remove `ButtonState` --- core/src/button_state.rs | 9 --------- core/src/lib.rs | 2 -- native/src/input.rs | 2 -- src/lib.rs | 3 --- winit/src/conversion.rs | 13 +------------ 5 files changed, 1 insertion(+), 28 deletions(-) delete mode 100644 core/src/button_state.rs diff --git a/core/src/button_state.rs b/core/src/button_state.rs deleted file mode 100644 index 988043bafa..0000000000 --- a/core/src/button_state.rs +++ /dev/null @@ -1,9 +0,0 @@ -/// The state of a button. -#[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy)] -pub enum ButtonState { - /// The button is pressed. - Pressed, - - /// The button is __not__ pressed. - Released, -} diff --git a/core/src/lib.rs b/core/src/lib.rs index f0072f6127..ec1e185a5a 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -19,7 +19,6 @@ pub mod mouse; mod align; mod background; -mod button_state; mod color; mod font; mod length; @@ -31,7 +30,6 @@ mod vector; pub use align::{Align, HorizontalAlignment, VerticalAlignment}; pub use background::Background; -pub use button_state::ButtonState; pub use color::Color; pub use font::Font; pub use length::Length; diff --git a/native/src/input.rs b/native/src/input.rs index 7f5114c32d..ad8ed2527b 100644 --- a/native/src/input.rs +++ b/native/src/input.rs @@ -1,5 +1,3 @@ //! Map your system events into input events that the runtime can understand. pub mod keyboard; pub mod mouse; - -pub use iced_core::ButtonState; diff --git a/src/lib.rs b/src/lib.rs index c574a34544..e03cd7e9e2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -210,6 +210,3 @@ pub use runtime::{ Length, MouseCursor, Point, Rectangle, Size, Subscription, Vector, VerticalAlignment, }; - -#[cfg(not(target_arch = "wasm32"))] -pub use runtime::input::ButtonState; diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index 3d34497f52..8b98e2549e 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -5,7 +5,7 @@ use crate::{ input::{ keyboard::{self, KeyCode, ModifiersState}, - mouse, ButtonState, + mouse, }, window, Event, Mode, MouseCursor, }; @@ -157,17 +157,6 @@ pub fn mouse_button(mouse_button: winit::event::MouseButton) -> mouse::Button { } } -/// Converts an `ElementState` from [`winit`] to an [`iced_native`] button state. -/// -/// [`winit`]: https://github.com/rust-windowing/winit -/// [`iced_native`]: https://github.com/hecrj/iced/tree/master/native -pub fn button_state(element_state: winit::event::ElementState) -> ButtonState { - match element_state { - winit::event::ElementState::Pressed => ButtonState::Pressed, - winit::event::ElementState::Released => ButtonState::Released, - } -} - /// Converts some `ModifiersState` from [`winit`] to an [`iced_native`] /// modifiers state. /// From 137664ca88a9bf2398380fd1c04b00c62c868383 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 30 Apr 2020 04:59:07 +0200 Subject: [PATCH 40/73] Move `keyboard::Event` to `iced_core` --- core/src/keyboard.rs | 2 ++ {native/src/input => core/src}/keyboard/event.rs | 0 native/src/input/keyboard.rs | 5 +---- native/src/input/mouse.rs | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) rename {native/src/input => core/src}/keyboard/event.rs (100%) diff --git a/core/src/keyboard.rs b/core/src/keyboard.rs index d98b2989f8..b26bdb3d97 100644 --- a/core/src/keyboard.rs +++ b/core/src/keyboard.rs @@ -1,6 +1,8 @@ //! Reuse basic keyboard types. +mod event; mod key_code; mod modifiers_state; +pub use event::Event; pub use key_code::KeyCode; pub use modifiers_state::ModifiersState; diff --git a/native/src/input/keyboard/event.rs b/core/src/keyboard/event.rs similarity index 100% rename from native/src/input/keyboard/event.rs rename to core/src/keyboard/event.rs diff --git a/native/src/input/keyboard.rs b/native/src/input/keyboard.rs index 220b7f1744..012538e34c 100644 --- a/native/src/input/keyboard.rs +++ b/native/src/input/keyboard.rs @@ -1,5 +1,2 @@ -//! Build keyboard events. -mod event; - -pub use event::Event; +//! Track keyboard events. pub use iced_core::keyboard::*; diff --git a/native/src/input/mouse.rs b/native/src/input/mouse.rs index ae3f15963b..9ee406cfe2 100644 --- a/native/src/input/mouse.rs +++ b/native/src/input/mouse.rs @@ -1,4 +1,4 @@ -//! Build mouse events. +//! Track mouse events. pub mod click; From bb9ccc4f62ceea08dc1ef0c6c4d3d219897e44a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 30 Apr 2020 05:04:45 +0200 Subject: [PATCH 41/73] Remove inconsistent `input` module in `iced_native` --- native/src/event.rs | 5 +---- native/src/input.rs | 3 --- native/src/{input => }/keyboard.rs | 0 native/src/lib.rs | 3 ++- native/src/{input => }/mouse.rs | 0 native/src/{input => }/mouse/click.rs | 0 native/src/user_interface.rs | 4 +--- native/src/widget/button.rs | 4 ++-- native/src/widget/checkbox.rs | 2 +- native/src/widget/pane_grid.rs | 5 ++--- native/src/widget/pane_grid/state.rs | 2 +- native/src/widget/radio.rs | 2 +- native/src/widget/scrollable.rs | 4 ++-- native/src/widget/slider.rs | 4 ++-- native/src/widget/text_input.rs | 12 +++++------- src/keyboard.rs | 6 +----- src/mouse.rs | 3 +-- web/src/lib.rs | 4 ++-- wgpu/src/widget/canvas/event.rs | 2 +- winit/src/conversion.rs | 7 ++----- 20 files changed, 27 insertions(+), 45 deletions(-) delete mode 100644 native/src/input.rs rename native/src/{input => }/keyboard.rs (100%) rename native/src/{input => }/mouse.rs (100%) rename native/src/{input => }/mouse/click.rs (100%) diff --git a/native/src/event.rs b/native/src/event.rs index b2550eadf8..606a71d662 100644 --- a/native/src/event.rs +++ b/native/src/event.rs @@ -1,7 +1,4 @@ -use crate::{ - input::{keyboard, mouse}, - window, -}; +use crate::{keyboard, mouse, window}; /// A user interface event. /// diff --git a/native/src/input.rs b/native/src/input.rs deleted file mode 100644 index ad8ed2527b..0000000000 --- a/native/src/input.rs +++ /dev/null @@ -1,3 +0,0 @@ -//! Map your system events into input events that the runtime can understand. -pub mod keyboard; -pub mod mouse; diff --git a/native/src/input/keyboard.rs b/native/src/keyboard.rs similarity index 100% rename from native/src/input/keyboard.rs rename to native/src/keyboard.rs diff --git a/native/src/lib.rs b/native/src/lib.rs index a3b581b349..88bf44239c 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -39,8 +39,9 @@ #![deny(unused_results)] #![forbid(unsafe_code)] #![forbid(rust_2018_idioms)] -pub mod input; +pub mod keyboard; pub mod layout; +pub mod mouse; pub mod renderer; pub mod subscription; pub mod widget; diff --git a/native/src/input/mouse.rs b/native/src/mouse.rs similarity index 100% rename from native/src/input/mouse.rs rename to native/src/mouse.rs diff --git a/native/src/input/mouse/click.rs b/native/src/mouse/click.rs similarity index 100% rename from native/src/input/mouse/click.rs rename to native/src/mouse/click.rs diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 5d9221e98b..48cd611195 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -1,6 +1,4 @@ -use crate::{ - input::mouse, layout, Clipboard, Element, Event, Layout, Point, Size, -}; +use crate::{layout, mouse, Clipboard, Element, Event, Layout, Point, Size}; use std::hash::Hasher; diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs index 5d41402305..c932da2bb8 100644 --- a/native/src/widget/button.rs +++ b/native/src/widget/button.rs @@ -5,8 +5,8 @@ //! [`Button`]: struct.Button.html //! [`State`]: struct.State.html use crate::{ - input::mouse, layout, Clipboard, Element, Event, Hasher, Layout, Length, - Point, Rectangle, Widget, + layout, mouse, Clipboard, Element, Event, Hasher, Layout, Length, Point, + Rectangle, Widget, }; use std::hash::Hash; diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs index c49ac707f1..5fb13290bb 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, layout, row, text, Align, Clipboard, Element, Event, Hasher, + layout, mouse, row, text, Align, Clipboard, Element, Event, Hasher, HorizontalAlignment, Layout, Length, Point, Rectangle, Row, Text, VerticalAlignment, Widget, }; diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index fe2bbe07e7..c398a30bac 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -22,9 +22,8 @@ pub use split::Split; pub use state::{Focus, State}; use crate::{ - input::{keyboard, mouse}, - layout, Clipboard, Element, Event, Hasher, Layout, Length, Point, Size, - Widget, + keyboard, layout, mouse, Clipboard, Element, Event, Hasher, Layout, Length, + Point, Size, Widget, }; /// A collection of panes distributed using either vertical or horizontal splits diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs index 0a8b8419d8..ed2813b880 100644 --- a/native/src/widget/pane_grid/state.rs +++ b/native/src/widget/pane_grid/state.rs @@ -1,5 +1,5 @@ use crate::{ - input::keyboard, + keyboard, pane_grid::{node::Node, Axis, Direction, Pane, Split}, Hasher, Point, Rectangle, Size, }; diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs index deea703450..ab5bcf32f9 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, layout, row, text, Align, Clipboard, Element, Event, Hasher, + layout, mouse, row, text, Align, Clipboard, Element, Event, Hasher, HorizontalAlignment, Layout, Length, Point, Rectangle, Row, Text, VerticalAlignment, Widget, }; diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index da11c50ce6..3c8e5e5b99 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -1,7 +1,7 @@ //! Navigate an endless amount of content with a scrollbar. use crate::{ - column, input::mouse, layout, Align, Clipboard, Column, Element, Event, - Hasher, Layout, Length, Point, Rectangle, Size, Widget, + column, layout, mouse, Align, Clipboard, Column, Element, Event, Hasher, + Layout, Length, Point, Rectangle, Size, Widget, }; use std::{f32, hash::Hash, u32}; diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs index a00d7c8d5e..8cdfc3dea3 100644 --- a/native/src/widget/slider.rs +++ b/native/src/widget/slider.rs @@ -5,8 +5,8 @@ //! [`Slider`]: struct.Slider.html //! [`State`]: struct.State.html use crate::{ - input::mouse, layout, Clipboard, Element, Event, Hasher, Layout, Length, - Point, Rectangle, Size, Widget, + layout, mouse, Clipboard, Element, Event, Hasher, Layout, Length, Point, + Rectangle, Size, Widget, }; use std::{hash::Hash, ops::RangeInclusive}; diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index b11269db4a..1cdbe007cc 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -15,12 +15,10 @@ pub use value::Value; use editor::Editor; use crate::{ - input::{ - keyboard, - mouse::{self, click}, - }, - layout, Clipboard, Element, Event, Font, Hasher, Layout, Length, Point, - Rectangle, Size, Widget, + keyboard, layout, + mouse::{self, click}, + Clipboard, Element, Event, Font, Hasher, Layout, Length, Point, Rectangle, + Size, Widget, }; use std::u32; @@ -739,7 +737,7 @@ fn find_cursor_position( } mod platform { - use crate::input::keyboard; + use crate::keyboard; pub fn is_jump_modifier_pressed( modifiers: keyboard::ModifiersState, diff --git a/src/keyboard.rs b/src/keyboard.rs index 181dd97486..0b3e894d58 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -1,6 +1,2 @@ //! Listen and react to keyboard events. -#[cfg(not(target_arch = "wasm32"))] -pub use iced_winit::input::keyboard::{KeyCode, ModifiersState}; - -#[cfg(target_arch = "wasm32")] -pub use iced_web::keyboard::{KeyCode, ModifiersState}; +pub use crate::runtime::keyboard::{Event, KeyCode, ModifiersState}; diff --git a/src/mouse.rs b/src/mouse.rs index 8be36d3725..c511399b11 100644 --- a/src/mouse.rs +++ b/src/mouse.rs @@ -1,3 +1,2 @@ //! Listen and react to mouse events. -#[cfg(not(target_arch = "wasm32"))] -pub use iced_winit::input::mouse::{Button, Event, ScrollDelta}; +pub use crate::runtime::mouse::{Button, Event, ScrollDelta}; diff --git a/web/src/lib.rs b/web/src/lib.rs index c525021fe7..3fe98dfbd9 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -74,8 +74,8 @@ pub use dodrio; pub use element::Element; pub use hasher::Hasher; pub use iced_core::{ - keyboard, Align, Background, Color, Font, HorizontalAlignment, Length, - MouseCursor, Point, Rectangle, Size, Vector, VerticalAlignment, + keyboard, mouse, Align, Background, Color, Font, HorizontalAlignment, + Length, MouseCursor, Point, Rectangle, Size, Vector, VerticalAlignment, }; pub use iced_futures::{executor, futures, Command}; pub use subscription::Subscription; diff --git a/wgpu/src/widget/canvas/event.rs b/wgpu/src/widget/canvas/event.rs index 7a8b082982..4e7c186929 100644 --- a/wgpu/src/widget/canvas/event.rs +++ b/wgpu/src/widget/canvas/event.rs @@ -1,4 +1,4 @@ -use iced_native::input::mouse; +use iced_native::mouse; #[derive(Debug, Clone, Copy, PartialEq)] pub enum Event { diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index 8b98e2549e..93827e372d 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -3,11 +3,8 @@ //! [`winit`]: https://github.com/rust-windowing/winit //! [`iced_native`]: https://github.com/hecrj/iced/tree/master/native use crate::{ - input::{ - keyboard::{self, KeyCode, ModifiersState}, - mouse, - }, - window, Event, Mode, MouseCursor, + keyboard::{self, KeyCode, ModifiersState}, + mouse, window, Event, Mode, MouseCursor, }; /// Converts a winit window event into an iced event. From e2076612cb98d04a8a48add14d0068c2974d5653 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 30 Apr 2020 05:37:44 +0200 Subject: [PATCH 42/73] Implement `time::every` in `iced_futures` --- examples/clock/Cargo.toml | 4 +- examples/clock/src/main.rs | 46 +++----------------- examples/game_of_life/Cargo.toml | 4 +- examples/game_of_life/src/main.rs | 3 +- examples/game_of_life/src/time.rs | 34 --------------- examples/solar_system/Cargo.toml | 4 +- examples/solar_system/src/main.rs | 40 +----------------- examples/stopwatch/Cargo.toml | 5 +-- examples/stopwatch/src/main.rs | 44 ++----------------- futures/Cargo.toml | 3 +- futures/src/lib.rs | 4 ++ futures/src/time.rs | 70 +++++++++++++++++++++++++++++++ src/lib.rs | 4 ++ src/time.rs | 14 +++++++ 14 files changed, 110 insertions(+), 169 deletions(-) delete mode 100644 examples/game_of_life/src/time.rs create mode 100644 futures/src/time.rs create mode 100644 src/time.rs diff --git a/examples/clock/Cargo.toml b/examples/clock/Cargo.toml index ab7714055d..c6e32379e4 100644 --- a/examples/clock/Cargo.toml +++ b/examples/clock/Cargo.toml @@ -6,7 +6,5 @@ edition = "2018" publish = false [dependencies] -iced = { path = "../..", features = ["canvas", "async-std", "debug"] } -iced_native = { path = "../../native" } +iced = { path = "../..", features = ["canvas", "tokio", "debug"] } chrono = "0.4" -async-std = { version = "1.0", features = ["unstable"] } diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs index e6b17d8a2a..9c583c7876 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -1,7 +1,7 @@ use iced::{ canvas::{self, Cache, Canvas, Cursor, Geometry, LineCap, Path, Stroke}, - executor, Application, Color, Command, Container, Element, Length, Point, - Rectangle, Settings, Subscription, Vector, + executor, time, Application, Color, Command, Container, Element, Length, + Point, Rectangle, Settings, Subscription, Vector, }; pub fn main() { @@ -43,7 +43,7 @@ impl Application for Clock { fn update(&mut self, message: Message) -> Command { match message { Message::Tick(local_time) => { - let now = local_time.into(); + let now = local_time; if now != self.now { self.now = now; @@ -56,7 +56,8 @@ impl Application for Clock { } fn subscription(&self) -> Subscription { - time::every(std::time::Duration::from_millis(500)).map(Message::Tick) + time::every(std::time::Duration::from_millis(500)) + .map(|_| Message::Tick(chrono::Local::now())) } fn view(&mut self) -> Element { @@ -130,40 +131,3 @@ fn hand_rotation(n: u32, total: u32) -> f32 { 2.0 * std::f32::consts::PI * turns } - -mod time { - use iced::futures; - - pub fn every( - duration: std::time::Duration, - ) -> iced::Subscription> { - iced::Subscription::from_recipe(Every(duration)) - } - - struct Every(std::time::Duration); - - impl iced_native::subscription::Recipe for Every - where - H: std::hash::Hasher, - { - type Output = chrono::DateTime; - - fn hash(&self, state: &mut H) { - use std::hash::Hash; - - std::any::TypeId::of::().hash(state); - self.0.hash(state); - } - - fn stream( - self: Box, - _input: futures::stream::BoxStream<'static, I>, - ) -> futures::stream::BoxStream<'static, Self::Output> { - use futures::stream::StreamExt; - - async_std::stream::interval(self.0) - .map(|_| chrono::Local::now()) - .boxed() - } - } -} diff --git a/examples/game_of_life/Cargo.toml b/examples/game_of_life/Cargo.toml index 8855b3e837..413493ae77 100644 --- a/examples/game_of_life/Cargo.toml +++ b/examples/game_of_life/Cargo.toml @@ -6,7 +6,5 @@ edition = "2018" publish = false [dependencies] -iced = { path = "../..", features = ["async-std", "canvas", "debug"] } -iced_native = { path = "../../native" } -async-std = { version = "1.0", features = ["unstable"] } +iced = { path = "../..", features = ["canvas", "tokio"] } itertools = "0.9" diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index 9fb4c3e7b8..5a58a8cb06 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -1,14 +1,13 @@ //! This example showcases an interactive version of the Game of Life, invented //! by John Conway. It leverages a `Canvas` together with other widgets. mod style; -mod time; use grid::Grid; use iced::{ button::{self, Button}, executor, slider::{self, Slider}, - Align, Application, Column, Command, Container, Element, Length, Row, + time, Align, Application, Column, Command, Container, Element, Length, Row, Settings, Subscription, Text, }; use std::time::{Duration, Instant}; diff --git a/examples/game_of_life/src/time.rs b/examples/game_of_life/src/time.rs deleted file mode 100644 index 7b475ecd39..0000000000 --- a/examples/game_of_life/src/time.rs +++ /dev/null @@ -1,34 +0,0 @@ -use iced::futures; - -pub fn every( - duration: std::time::Duration, -) -> iced::Subscription { - iced::Subscription::from_recipe(Every(duration)) -} - -struct Every(std::time::Duration); - -impl iced_native::subscription::Recipe for Every -where - H: std::hash::Hasher, -{ - type Output = std::time::Instant; - - fn hash(&self, state: &mut H) { - use std::hash::Hash; - - std::any::TypeId::of::().hash(state); - self.0.hash(state); - } - - fn stream( - self: Box, - _input: futures::stream::BoxStream<'static, I>, - ) -> futures::stream::BoxStream<'static, Self::Output> { - use futures::stream::StreamExt; - - async_std::stream::interval(self.0) - .map(|_| std::time::Instant::now()) - .boxed() - } -} diff --git a/examples/solar_system/Cargo.toml b/examples/solar_system/Cargo.toml index 0555aa9633..44ced72946 100644 --- a/examples/solar_system/Cargo.toml +++ b/examples/solar_system/Cargo.toml @@ -6,7 +6,5 @@ edition = "2018" publish = false [dependencies] -iced = { path = "../..", features = ["canvas", "async-std", "debug"] } -iced_native = { path = "../../native" } -async-std = { version = "1.0", features = ["unstable"] } +iced = { path = "../..", features = ["canvas", "tokio", "debug"] } rand = "0.7" diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs index a25e43df9a..98bd3b2140 100644 --- a/examples/solar_system/src/main.rs +++ b/examples/solar_system/src/main.rs @@ -8,8 +8,8 @@ //! [1]: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_animations#An_animated_solar_system use iced::{ canvas::{self, Cursor, Path, Stroke}, - executor, window, Application, Canvas, Color, Command, Element, Length, - Point, Rectangle, Settings, Size, Subscription, Vector, + executor, time, window, Application, Canvas, Color, Command, Element, + Length, Point, Rectangle, Settings, Size, Subscription, Vector, }; use std::time::Instant; @@ -212,39 +212,3 @@ impl canvas::Program for State { vec![background, system] } } - -mod time { - use iced::futures; - use std::time::Instant; - - pub fn every(duration: std::time::Duration) -> iced::Subscription { - iced::Subscription::from_recipe(Every(duration)) - } - - struct Every(std::time::Duration); - - impl iced_native::subscription::Recipe for Every - where - H: std::hash::Hasher, - { - type Output = Instant; - - fn hash(&self, state: &mut H) { - use std::hash::Hash; - - std::any::TypeId::of::().hash(state); - self.0.hash(state); - } - - fn stream( - self: Box, - _input: futures::stream::BoxStream<'static, I>, - ) -> futures::stream::BoxStream<'static, Self::Output> { - use futures::stream::StreamExt; - - async_std::stream::interval(self.0) - .map(|_| Instant::now()) - .boxed() - } - } -} diff --git a/examples/stopwatch/Cargo.toml b/examples/stopwatch/Cargo.toml index 1dae3b83dc..075aa11111 100644 --- a/examples/stopwatch/Cargo.toml +++ b/examples/stopwatch/Cargo.toml @@ -6,7 +6,4 @@ edition = "2018" publish = false [dependencies] -iced = { path = "../.." } -iced_native = { path = "../../native" } -iced_futures = { path = "../../futures", features = ["async-std"] } -async-std = { version = "1.0", features = ["unstable"] } +iced = { path = "../..", features = ["tokio"] } diff --git a/examples/stopwatch/src/main.rs b/examples/stopwatch/src/main.rs index 5a54ed2bbd..9de6d39e37 100644 --- a/examples/stopwatch/src/main.rs +++ b/examples/stopwatch/src/main.rs @@ -1,6 +1,7 @@ use iced::{ - button, Align, Application, Button, Column, Command, Container, Element, - HorizontalAlignment, Length, Row, Settings, Subscription, Text, + button, executor, time, Align, Application, Button, Column, Command, + Container, Element, HorizontalAlignment, Length, Row, Settings, + Subscription, Text, }; use std::time::{Duration, Instant}; @@ -28,7 +29,7 @@ enum Message { } impl Application for Stopwatch { - type Executor = iced_futures::executor::AsyncStd; + type Executor = executor::Default; type Message = Message; type Flags = (); @@ -143,43 +144,6 @@ impl Application for Stopwatch { } } -mod time { - use iced::futures; - - pub fn every( - duration: std::time::Duration, - ) -> iced::Subscription { - iced::Subscription::from_recipe(Every(duration)) - } - - struct Every(std::time::Duration); - - impl iced_native::subscription::Recipe for Every - where - H: std::hash::Hasher, - { - type Output = std::time::Instant; - - fn hash(&self, state: &mut H) { - use std::hash::Hash; - - std::any::TypeId::of::().hash(state); - self.0.hash(state); - } - - fn stream( - self: Box, - _input: futures::stream::BoxStream<'static, I>, - ) -> futures::stream::BoxStream<'static, Self::Output> { - use futures::stream::StreamExt; - - async_std::stream::interval(self.0) - .map(|_| std::time::Instant::now()) - .boxed() - } - } -} - mod style { use iced::{button, Background, Color, Vector}; diff --git a/futures/Cargo.toml b/futures/Cargo.toml index f0e2a104c0..275d039140 100644 --- a/futures/Cargo.toml +++ b/futures/Cargo.toml @@ -22,11 +22,12 @@ version = "0.3" [target.'cfg(not(target_arch = "wasm32"))'.dependencies.tokio] version = "0.2" optional = true -features = ["rt-core", "rt-threaded"] +features = ["rt-core", "rt-threaded", "time", "stream"] [target.'cfg(not(target_arch = "wasm32"))'.dependencies.async-std] version = "1.0" optional = true +features = ["unstable"] [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen-futures = "0.4" diff --git a/futures/src/lib.rs b/futures/src/lib.rs index 966a9cdc9e..d1a149f75e 100644 --- a/futures/src/lib.rs +++ b/futures/src/lib.rs @@ -14,6 +14,10 @@ mod runtime; pub mod executor; pub mod subscription; +#[cfg(any(feature = "tokio", feature = "async-std"))] +#[cfg_attr(docsrs, doc(cfg(any(feature = "tokio", feature = "async-std"))))] +pub mod time; + pub use command::Command; pub use executor::Executor; pub use runtime::Runtime; diff --git a/futures/src/time.rs b/futures/src/time.rs new file mode 100644 index 0000000000..e87b4a83e4 --- /dev/null +++ b/futures/src/time.rs @@ -0,0 +1,70 @@ +//! Listen and react to time. +use crate::subscription::{self, Subscription}; + +/// Returns a [`Subscription`] that produces messages at a set interval. +/// +/// The first message is produced after a `duration`, and then continues to +/// produce more messages every `duration` after that. +/// +/// [`Subscription`]: ../subscription/struct.Subscription.html +pub fn every( + duration: std::time::Duration, +) -> Subscription { + Subscription::from_recipe(Every(duration)) +} + +struct Every(std::time::Duration); + +#[cfg(feature = "async-std")] +impl subscription::Recipe for Every +where + H: std::hash::Hasher, +{ + type Output = std::time::Instant; + + fn hash(&self, state: &mut H) { + use std::hash::Hash; + + std::any::TypeId::of::().hash(state); + self.0.hash(state); + } + + fn stream( + self: Box, + _input: futures::stream::BoxStream<'static, E>, + ) -> futures::stream::BoxStream<'static, Self::Output> { + use futures::stream::StreamExt; + + async_std::stream::interval(self.0) + .map(|_| std::time::Instant::now()) + .boxed() + } +} + +#[cfg(all(feature = "tokio", not(feature = "async-std")))] +impl subscription::Recipe for Every +where + H: std::hash::Hasher, +{ + type Output = std::time::Instant; + + fn hash(&self, state: &mut H) { + use std::hash::Hash; + + std::any::TypeId::of::().hash(state); + self.0.hash(state); + } + + fn stream( + self: Box, + _input: futures::stream::BoxStream<'static, E>, + ) -> futures::stream::BoxStream<'static, Self::Output> { + use futures::stream::StreamExt; + + let start = tokio::time::Instant::now() + self.0; + + tokio::time::interval_at(start, self.0) + .map(|_| std::time::Instant::now()) + .boxed() + } +} diff --git a/src/lib.rs b/src/lib.rs index e03cd7e9e2..d1e5fb94cf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -190,6 +190,10 @@ pub mod settings; pub mod widget; pub mod window; +#[cfg(any(feature = "tokio", feature = "async-std"))] +#[cfg_attr(docsrs, doc(cfg(any(feature = "tokio", feature = "async-std"))))] +pub mod time; + #[doc(no_inline)] pub use widget::*; diff --git a/src/time.rs b/src/time.rs new file mode 100644 index 0000000000..cd4424619e --- /dev/null +++ b/src/time.rs @@ -0,0 +1,14 @@ +//! Listen and react to time. +use crate::Subscription; + +/// Returns a [`Subscription`] that produces messages at a set interval. +/// +/// The first message is produced after a `duration`, and then continues to +/// produce more messages every `duration` after that. +/// +/// [`Subscription`]: ../subscription/struct.Subscription.html +pub fn every( + duration: std::time::Duration, +) -> Subscription { + iced_futures::time::every(duration) +} From 1501a93915b3704a1d57da4c390a8ebca4e35c8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 30 Apr 2020 05:51:41 +0200 Subject: [PATCH 43/73] Disable `time` module on Wasm for now --- futures/src/lib.rs | 5 ++++- src/lib.rs | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/futures/src/lib.rs b/futures/src/lib.rs index d1a149f75e..46fc59fcf4 100644 --- a/futures/src/lib.rs +++ b/futures/src/lib.rs @@ -14,7 +14,10 @@ mod runtime; pub mod executor; pub mod subscription; -#[cfg(any(feature = "tokio", feature = "async-std"))] +#[cfg(all( + any(feature = "tokio", feature = "async-std"), + not(target_arch = "wasm32") +))] #[cfg_attr(docsrs, doc(cfg(any(feature = "tokio", feature = "async-std"))))] pub mod time; diff --git a/src/lib.rs b/src/lib.rs index d1e5fb94cf..7885ee1219 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -190,7 +190,10 @@ pub mod settings; pub mod widget; pub mod window; -#[cfg(any(feature = "tokio", feature = "async-std"))] +#[cfg(all( + any(feature = "tokio", feature = "async-std"), + not(target_arch = "wasm32") +))] #[cfg_attr(docsrs, doc(cfg(any(feature = "tokio", feature = "async-std"))))] pub mod time; From d4c4198f7242f168de65146e0ca339e0c1cbfe9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 30 Apr 2020 07:38:46 +0200 Subject: [PATCH 44/73] Write documentation for the new `canvas` API --- wgpu/src/lib.rs | 2 +- wgpu/src/widget/canvas.rs | 11 +++++---- wgpu/src/widget/canvas/cache.rs | 36 ++++++++++++++++++++---------- wgpu/src/widget/canvas/cursor.rs | 22 ++++++++++++++++++ wgpu/src/widget/canvas/event.rs | 4 ++++ wgpu/src/widget/canvas/geometry.rs | 14 ++++++++++++ wgpu/src/widget/canvas/program.rs | 36 ++++++++++++++++++++++++++++++ wgpu/src/widget/canvas/stroke.rs | 14 ++++++++++++ 8 files changed, 122 insertions(+), 17 deletions(-) diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 30b5bb4add..799c1f34d3 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -20,7 +20,7 @@ //! [`wgpu`]: https://github.com/gfx-rs/wgpu-rs //! [WebGPU API]: https://gpuweb.github.io/gpuweb/ //! [`wgpu_glyph`]: https://github.com/hecrj/wgpu_glyph -//#![deny(missing_docs)] +#![deny(missing_docs)] #![deny(missing_debug_implementations)] #![deny(unused_results)] #![forbid(unsafe_code)] diff --git a/wgpu/src/widget/canvas.rs b/wgpu/src/widget/canvas.rs index a5834330c2..05306e67c0 100644 --- a/wgpu/src/widget/canvas.rs +++ b/wgpu/src/widget/canvas.rs @@ -48,12 +48,15 @@ pub use text::Text; /// /// - [`clock`], an application that uses the [`Canvas`] widget to draw a clock /// and its hands to display the current time. +/// - [`game_of_life`], an interactive version of the Game of Life, invented by +/// John Conway. /// - [`solar_system`], an animated solar system drawn using the [`Canvas`] widget /// and showcasing how to compose different transforms. /// -/// [examples]: https://github.com/hecrj/iced/tree/0.1/examples -/// [`clock`]: https://github.com/hecrj/iced/tree/0.1/examples/clock -/// [`solar_system`]: https://github.com/hecrj/iced/tree/0.1/examples/solar_system +/// [examples]: https://github.com/hecrj/iced/tree/master/examples +/// [`clock`]: https://github.com/hecrj/iced/tree/master/examples/clock +/// [`game_of_life`]: https://github.com/hecrj/iced/tree/master/examples/game_of_life +/// [`solar_system`]: https://github.com/hecrj/iced/tree/master/examples/solar_system /// /// ## Drawing a simple circle /// If you want to get a quick overview, here's how we can draw a simple circle: @@ -89,7 +92,7 @@ pub use text::Text; /// } /// } /// -/// // Finally, we simply use our `Cache` to create the `Canvas`! +/// // Finally, we simply use our `Circle` to create the `Canvas`! /// let canvas = Canvas::new(Circle { radius: 50.0 }); /// ``` #[derive(Debug)] diff --git a/wgpu/src/widget/canvas/cache.rs b/wgpu/src/widget/canvas/cache.rs index 03643f7483..4b28d164ad 100644 --- a/wgpu/src/widget/canvas/cache.rs +++ b/wgpu/src/widget/canvas/cache.rs @@ -19,13 +19,14 @@ impl Default for State { State::Empty } } -/// A simple cache that stores generated geometry to avoid recomputation. +/// A simple cache that stores generated [`Geometry`] to avoid recomputation. /// /// A [`Cache`] will not redraw its geometry unless the dimensions of its layer /// change or it is explicitly cleared. /// /// [`Layer`]: ../trait.Layer.html /// [`Cache`]: struct.Cache.html +/// [`Geometry`]: struct.Geometry.html #[derive(Debug, Default)] pub struct Cache { state: RefCell, @@ -41,30 +42,41 @@ impl Cache { } } - /// Clears the cache, forcing a redraw the next time it is used. + /// Clears the [`Cache`], forcing a redraw the next time it is used. /// - /// [`Cached`]: struct.Cached.html + /// [`Cache`]: struct.Cache.html pub fn clear(&mut self) { *self.state.borrow_mut() = State::Empty; } - pub fn draw( - &self, - new_bounds: Size, - draw_fn: impl Fn(&mut Frame), - ) -> Geometry { + /// Draws [`Geometry`] using the provided closure and stores it in the + /// [`Cache`]. + /// + /// The closure will only be called when + /// - the bounds have changed since the previous draw call. + /// - the [`Cache`] is empty or has been explicitly cleared. + /// + /// Otherwise, the previously stored [`Geometry`] will be returned. The + /// [`Cache`] is not cleared in this case. In other words, it will keep + /// returning the stored [`Geometry`] if needed. + /// + /// [`Cache`]: struct.Cache.html + pub fn draw(&self, bounds: Size, draw_fn: impl Fn(&mut Frame)) -> Geometry { use std::ops::Deref; - if let State::Filled { bounds, primitive } = self.state.borrow().deref() + if let State::Filled { + bounds: cached_bounds, + primitive, + } = self.state.borrow().deref() { - if *bounds == new_bounds { + if *cached_bounds == bounds { return Geometry::from_primitive(Primitive::Cached { cache: primitive.clone(), }); } } - let mut frame = Frame::new(new_bounds); + let mut frame = Frame::new(bounds); draw_fn(&mut frame); let primitive = { @@ -74,7 +86,7 @@ impl Cache { }; *self.state.borrow_mut() = State::Filled { - bounds: new_bounds, + bounds, primitive: primitive.clone(), }; diff --git a/wgpu/src/widget/canvas/cursor.rs b/wgpu/src/widget/canvas/cursor.rs index 7ab58b87a9..456760ea78 100644 --- a/wgpu/src/widget/canvas/cursor.rs +++ b/wgpu/src/widget/canvas/cursor.rs @@ -1,8 +1,12 @@ use iced_native::{Point, Rectangle}; +/// The mouse cursor state. #[derive(Debug, Clone, Copy, PartialEq)] pub enum Cursor { + /// The cursor has a defined position. Available(Point), + + /// The cursor is currently unavailable (i.e. out of bounds or busy). Unavailable, } @@ -17,6 +21,9 @@ impl Cursor { } } + /// Returns the absolute position of the [`Cursor`], if available. + /// + /// [`Cursor`]: enum.Cursor.html pub fn position(&self) -> Option { match self { Cursor::Available(position) => Some(*position), @@ -24,6 +31,13 @@ impl Cursor { } } + /// Returns the relative position of the [`Cursor`] inside the given bounds, + /// if available. + /// + /// If the [`Cursor`] is not over the provided bounds, this method will + /// return `None`. + /// + /// [`Cursor`]: enum.Cursor.html pub fn position_in(&self, bounds: &Rectangle) -> Option { if self.is_over(bounds) { self.position_from(bounds.position()) @@ -32,6 +46,10 @@ impl Cursor { } } + /// Returns the relative position of the [`Cursor`] from the given origin, + /// if available. + /// + /// [`Cursor`]: enum.Cursor.html pub fn position_from(&self, origin: Point) -> Option { match self { Cursor::Available(position) => { @@ -41,6 +59,10 @@ impl Cursor { } } + /// Returns whether the [`Cursor`] is currently over the provided bounds + /// or not. + /// + /// [`Cursor`]: enum.Cursor.html pub fn is_over(&self, bounds: &Rectangle) -> bool { match self { Cursor::Available(position) => bounds.contains(*position), diff --git a/wgpu/src/widget/canvas/event.rs b/wgpu/src/widget/canvas/event.rs index 4e7c186929..ad11f51e22 100644 --- a/wgpu/src/widget/canvas/event.rs +++ b/wgpu/src/widget/canvas/event.rs @@ -1,6 +1,10 @@ use iced_native::mouse; +/// A [`Canvas`] event. +/// +/// [`Canvas`]: struct.Event.html #[derive(Debug, Clone, Copy, PartialEq)] pub enum Event { + /// A mouse event. Mouse(mouse::Event), } diff --git a/wgpu/src/widget/canvas/geometry.rs b/wgpu/src/widget/canvas/geometry.rs index 12ef828f42..4cadee39c9 100644 --- a/wgpu/src/widget/canvas/geometry.rs +++ b/wgpu/src/widget/canvas/geometry.rs @@ -1,5 +1,13 @@ use crate::Primitive; +/// A bunch of shapes that can be drawn. +/// +/// [`Geometry`] can be easily generated with a [`Frame`] or stored in a +/// [`Cache`]. +/// +/// [`Geometry`]: struct.Geometry.html +/// [`Frame`]: struct.Frame.html +/// [`Cache`]: struct.Cache.html #[derive(Debug, Clone)] pub struct Geometry(Primitive); @@ -8,6 +16,12 @@ impl Geometry { Self(primitive) } + /// Turns the [`Geometry`] into a [`Primitive`]. + /// + /// This can be useful if you are building a custom widget. + /// + /// [`Geometry`]: struct.Geometry.html + /// [`Primitive`]: ../enum.Primitive.html pub fn into_primitive(self) -> Primitive { self.0 } diff --git a/wgpu/src/widget/canvas/program.rs b/wgpu/src/widget/canvas/program.rs index f8e5451489..5b995bfa14 100644 --- a/wgpu/src/widget/canvas/program.rs +++ b/wgpu/src/widget/canvas/program.rs @@ -1,7 +1,27 @@ use crate::canvas::{Cursor, Event, Geometry}; use iced_native::{MouseCursor, Rectangle}; +/// The state and logic of a [`Canvas`]. +/// +/// A [`Program`] can mutate internal state and produce messages for an +/// application. +/// +/// [`Canvas`]: struct.Canvas.html +/// [`Program`]: trait.Program.html pub trait Program { + /// Updates the state of the [`Program`]. + /// + /// When a [`Program`] is used in a [`Canvas`], the runtime will call this + /// method for each [`Event`]. + /// + /// This method can optionally return a `Message` to notify an application + /// of any meaningful interactions. + /// + /// By default, this method does and returns nothing. + /// + /// [`Program`]: trait.Program.html + /// [`Canvas`]: struct.Canvas.html + /// [`Event`]: enum.Event.html fn update( &mut self, _event: Event, @@ -11,8 +31,24 @@ pub trait Program { None } + /// Draws the state of the [`Program`], producing a bunch of [`Geometry`]. + /// + /// [`Geometry`] can be easily generated with a [`Frame`] or stored in a + /// [`Cache`]. + /// + /// [`Program`]: trait.Program.html + /// [`Geometry`]: struct.Geometry.html + /// [`Frame`]: struct.Frame.html + /// [`Cache`]: struct.Cache.html fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Vec; + /// Returns the mouse cursor state of the [`Program`]. + /// + /// The mouse cursor returned will be in effect even if the cursor position + /// is out of bounds of the program's [`Canvas`]. + /// + /// [`Program`]: trait.Program.html + /// [`Canvas`]: struct.Canvas.html fn mouse_cursor(&self, _bounds: Rectangle, _cursor: Cursor) -> MouseCursor { MouseCursor::default() } diff --git a/wgpu/src/widget/canvas/stroke.rs b/wgpu/src/widget/canvas/stroke.rs index e20379cd12..5b6fc56a58 100644 --- a/wgpu/src/widget/canvas/stroke.rs +++ b/wgpu/src/widget/canvas/stroke.rs @@ -15,18 +15,32 @@ pub struct Stroke { } impl Stroke { + /// Sets the color of the [`Stroke`]. + /// + /// [`Stroke`]: struct.Stroke.html pub fn with_color(self, color: Color) -> Stroke { Stroke { color, ..self } } + /// Sets the width of the [`Stroke`]. + /// + /// [`Stroke`]: struct.Stroke.html pub fn with_width(self, width: f32) -> Stroke { Stroke { width, ..self } } + /// Sets the [`LineCap`] of the [`Stroke`]. + /// + /// [`LineCap`]: enum.LineCap.html + /// [`Stroke`]: struct.Stroke.html pub fn with_line_cap(self, line_cap: LineCap) -> Stroke { Stroke { line_cap, ..self } } + /// Sets the [`LineJoin`] of the [`Stroke`]. + /// + /// [`LineJoin`]: enum.LineJoin.html + /// [`Stroke`]: struct.Stroke.html pub fn with_line_join(self, line_join: LineJoin) -> Stroke { Stroke { line_join, ..self } } From 98bc8cf2a7c4944d762a0148ca9f615d6ccc0d6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 30 Apr 2020 08:16:38 +0200 Subject: [PATCH 45/73] Rename `MouseCursor` to `mouse::Interaction` --- core/src/lib.rs | 2 -- core/src/mouse.rs | 2 ++ core/src/mouse/interaction.rs | 20 +++++++++++++ core/src/mouse_cursor.rs | 36 ------------------------ examples/bezier_tool/src/main.rs | 10 +++---- examples/custom_widget/src/main.rs | 8 +++--- examples/game_of_life/src/main.rs | 19 +++++++------ examples/geometry/src/main.rs | 8 +++--- examples/integration/src/main.rs | 14 +++++---- native/src/lib.rs | 4 +-- native/src/window/backend.rs | 4 +-- src/lib.rs | 3 +- src/mouse.rs | 2 +- web/src/lib.rs | 2 +- wgpu/src/renderer.rs | 11 ++++---- wgpu/src/renderer/widget/button.rs | 6 ++-- wgpu/src/renderer/widget/checkbox.rs | 6 ++-- wgpu/src/renderer/widget/column.rs | 12 ++++---- wgpu/src/renderer/widget/container.rs | 6 ++-- wgpu/src/renderer/widget/image.rs | 4 +-- wgpu/src/renderer/widget/pane_grid.rs | 19 +++++++------ wgpu/src/renderer/widget/progress_bar.rs | 4 +-- wgpu/src/renderer/widget/radio.rs | 6 ++-- wgpu/src/renderer/widget/row.rs | 12 ++++---- wgpu/src/renderer/widget/scrollable.rs | 10 +++---- wgpu/src/renderer/widget/slider.rs | 8 +++--- wgpu/src/renderer/widget/space.rs | 4 +-- wgpu/src/renderer/widget/svg.rs | 4 +-- wgpu/src/renderer/widget/text.rs | 4 +-- wgpu/src/renderer/widget/text_input.rs | 9 +++--- wgpu/src/widget/canvas.rs | 8 +++--- wgpu/src/widget/canvas/program.rs | 22 ++++++++++----- wgpu/src/window/backend.rs | 8 +++--- winit/src/application.rs | 16 +++++------ winit/src/conversion.rs | 28 ++++++++++-------- 35 files changed, 170 insertions(+), 171 deletions(-) create mode 100644 core/src/mouse/interaction.rs delete mode 100644 core/src/mouse_cursor.rs diff --git a/core/src/lib.rs b/core/src/lib.rs index ec1e185a5a..6b9e612e8a 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -22,7 +22,6 @@ mod background; mod color; mod font; mod length; -mod mouse_cursor; mod point; mod rectangle; mod size; @@ -33,7 +32,6 @@ pub use background::Background; pub use color::Color; pub use font::Font; pub use length::Length; -pub use mouse_cursor::MouseCursor; pub use point::Point; pub use rectangle::Rectangle; pub use size::Size; diff --git a/core/src/mouse.rs b/core/src/mouse.rs index 101e04d50c..25ce6ac32d 100644 --- a/core/src/mouse.rs +++ b/core/src/mouse.rs @@ -1,6 +1,8 @@ //! Reuse basic mouse types. mod button; mod event; +mod interaction; pub use button::Button; pub use event::{Event, ScrollDelta}; +pub use interaction::Interaction; diff --git a/core/src/mouse/interaction.rs b/core/src/mouse/interaction.rs new file mode 100644 index 0000000000..664147a71f --- /dev/null +++ b/core/src/mouse/interaction.rs @@ -0,0 +1,20 @@ +/// The interaction of a mouse cursor. +#[derive(Debug, Eq, PartialEq, Clone, Copy, PartialOrd, Ord)] +#[allow(missing_docs)] +pub enum Interaction { + Idle, + Pointer, + Grab, + Text, + Crosshair, + Working, + Grabbing, + ResizingHorizontally, + ResizingVertically, +} + +impl Default for Interaction { + fn default() -> Interaction { + Interaction::Idle + } +} diff --git a/core/src/mouse_cursor.rs b/core/src/mouse_cursor.rs deleted file mode 100644 index 78ddb0ae0d..0000000000 --- a/core/src/mouse_cursor.rs +++ /dev/null @@ -1,36 +0,0 @@ -/// The state of the mouse cursor. -#[derive(Debug, Eq, PartialEq, Clone, Copy, PartialOrd, Ord)] -pub enum MouseCursor { - /// The cursor is over a non-interactive widget. - Idle, - - /// The cursor is over a clickable widget. - Pointer, - - /// The cursor is over a busy widget. - Working, - - /// The cursor is over a grabbable widget. - Grab, - - /// The cursor is over a text widget. - Text, - - /// The cursor is over a widget that requires precision. - Crosshair, - - /// The cursor is grabbing a widget. - Grabbing, - - /// The cursor is resizing a widget horizontally. - ResizingHorizontally, - - /// The cursor is resizing a widget vertically. - ResizingVertically, -} - -impl Default for MouseCursor { - fn default() -> MouseCursor { - MouseCursor::Idle - } -} diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs index fe4136b4a5..fe41e1b2c9 100644 --- a/examples/bezier_tool/src/main.rs +++ b/examples/bezier_tool/src/main.rs @@ -70,7 +70,7 @@ impl Sandbox for Example { mod bezier { use iced::{ canvas::{self, Canvas, Cursor, Event, Frame, Geometry, Path, Stroke}, - mouse, Element, Length, MouseCursor, Point, Rectangle, + mouse, Element, Length, Point, Rectangle, }; #[derive(Default)] @@ -166,15 +166,15 @@ mod bezier { } } - fn mouse_cursor( + fn mouse_interaction( &self, bounds: Rectangle, cursor: Cursor, - ) -> MouseCursor { + ) -> mouse::Interaction { if cursor.is_over(&bounds) { - MouseCursor::Crosshair + mouse::Interaction::Crosshair } else { - MouseCursor::default() + mouse::Interaction::default() } } } diff --git a/examples/custom_widget/src/main.rs b/examples/custom_widget/src/main.rs index d0bceb73ad..f096fb5454 100644 --- a/examples/custom_widget/src/main.rs +++ b/examples/custom_widget/src/main.rs @@ -10,8 +10,8 @@ mod circle { // if you wish to, by creating your own `Renderer` trait, which could be // implemented by `iced_wgpu` and other renderers. use iced_native::{ - layout, Background, Color, Element, Hasher, Layout, Length, - MouseCursor, Point, Size, Widget, + layout, mouse, Background, Color, Element, Hasher, Layout, Length, + Point, Size, Widget, }; use iced_wgpu::{Defaults, Primitive, Renderer}; @@ -57,7 +57,7 @@ mod circle { _defaults: &Defaults, layout: Layout<'_>, _cursor_position: Point, - ) -> (Primitive, MouseCursor) { + ) -> (Primitive, mouse::Interaction) { ( Primitive::Quad { bounds: layout.bounds(), @@ -66,7 +66,7 @@ mod circle { border_width: 0, border_color: Color::TRANSPARENT, }, - MouseCursor::default(), + mouse::Interaction::default(), ) } } diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index 5a58a8cb06..f0891db1da 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -157,8 +157,7 @@ impl Application for GameOfLife { mod grid { use iced::{ canvas::{self, Canvas, Cursor, Event, Frame, Geometry, Path}, - mouse, Color, Element, Length, MouseCursor, Point, Rectangle, Size, - Vector, + mouse, Color, Element, Length, Point, Rectangle, Size, Vector, }; use std::collections::{HashMap, HashSet}; @@ -397,16 +396,20 @@ mod grid { vec![life, hovered_cell] } - fn mouse_cursor( + fn mouse_interaction( &self, bounds: Rectangle, cursor: Cursor, - ) -> MouseCursor { + ) -> mouse::Interaction { match self.interaction { - Some(Interaction::Drawing) => MouseCursor::Crosshair, - Some(Interaction::Panning { .. }) => MouseCursor::Grabbing, - None if cursor.is_over(&bounds) => MouseCursor::Crosshair, - _ => MouseCursor::default(), + Some(Interaction::Drawing) => mouse::Interaction::Crosshair, + Some(Interaction::Panning { .. }) => { + mouse::Interaction::Grabbing + } + None if cursor.is_over(&bounds) => { + mouse::Interaction::Crosshair + } + _ => mouse::Interaction::default(), } } } diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs index 2a3efd4a93..aabe6b2180 100644 --- a/examples/geometry/src/main.rs +++ b/examples/geometry/src/main.rs @@ -11,8 +11,8 @@ mod rainbow { // 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, - Vector, Widget, + layout, mouse, Element, Hasher, Layout, Length, Point, Size, Vector, + Widget, }; use iced_wgpu::{ triangle::{Mesh2D, Vertex2D}, @@ -54,7 +54,7 @@ mod rainbow { _defaults: &Defaults, layout: Layout<'_>, cursor_position: Point, - ) -> (Primitive, MouseCursor) { + ) -> (Primitive, mouse::Interaction) { let b = layout.bounds(); // R O Y G B I V @@ -141,7 +141,7 @@ mod rainbow { }, }), }, - MouseCursor::default(), + mouse::Interaction::default(), ) } } diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs index da571ed1f0..92d2fa8d7a 100644 --- a/examples/integration/src/main.rs +++ b/examples/integration/src/main.rs @@ -8,7 +8,7 @@ use iced_wgpu::{ wgpu, window::SwapChain, Primitive, Renderer, Settings, Target, }; use iced_winit::{ - futures, winit, Cache, Clipboard, MouseCursor, Size, UserInterface, + futures, mouse, winit, Cache, Clipboard, Size, UserInterface, }; use winit::{ @@ -63,7 +63,7 @@ pub fn main() { let mut events = Vec::new(); let mut cache = Some(Cache::default()); let mut renderer = Renderer::new(&mut device, Settings::default()); - let mut output = (Primitive::None, MouseCursor::default()); + let mut output = (Primitive::None, mouse::Interaction::default()); let clipboard = Clipboard::new(&window); // Initialize scene and GUI controls @@ -189,7 +189,7 @@ pub fn main() { scene.draw(&mut encoder, &frame.view); // And then iced on top - let mouse_cursor = renderer.draw( + let mouse_interaction = renderer.draw( &mut device, &mut encoder, Target { @@ -205,9 +205,11 @@ pub fn main() { queue.submit(&[encoder.finish()]); // And update the mouse cursor - window.set_cursor_icon(iced_winit::conversion::mouse_cursor( - mouse_cursor, - )); + window.set_cursor_icon( + iced_winit::conversion::mouse_interaction( + mouse_interaction, + ), + ); } _ => {} } diff --git a/native/src/lib.rs b/native/src/lib.rs index 88bf44239c..9882803f18 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -55,8 +55,8 @@ mod runtime; mod user_interface; pub use iced_core::{ - Align, Background, Color, Font, HorizontalAlignment, Length, MouseCursor, - Point, Rectangle, Size, Vector, VerticalAlignment, + Align, Background, Color, Font, HorizontalAlignment, Length, Point, + Rectangle, Size, Vector, VerticalAlignment, }; pub use iced_futures::{executor, futures, Command}; diff --git a/native/src/window/backend.rs b/native/src/window/backend.rs index 3bc691cdef..892d4bb9b7 100644 --- a/native/src/window/backend.rs +++ b/native/src/window/backend.rs @@ -1,4 +1,4 @@ -use crate::MouseCursor; +use crate::mouse; use raw_window_handle::HasRawWindowHandle; @@ -51,5 +51,5 @@ pub trait Backend: Sized { output: &::Output, scale_factor: f64, overlay: &[T], - ) -> MouseCursor; + ) -> mouse::Interaction; } diff --git a/src/lib.rs b/src/lib.rs index 7885ee1219..2c08268bcd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -214,6 +214,5 @@ use iced_web as runtime; pub use runtime::{ futures, Align, Background, Color, Command, Font, HorizontalAlignment, - Length, MouseCursor, Point, Rectangle, Size, Subscription, Vector, - VerticalAlignment, + Length, Point, Rectangle, Size, Subscription, Vector, VerticalAlignment, }; diff --git a/src/mouse.rs b/src/mouse.rs index c511399b11..d61ed09a06 100644 --- a/src/mouse.rs +++ b/src/mouse.rs @@ -1,2 +1,2 @@ //! Listen and react to mouse events. -pub use crate::runtime::mouse::{Button, Event, ScrollDelta}; +pub use crate::runtime::mouse::{Button, Event, Interaction, ScrollDelta}; diff --git a/web/src/lib.rs b/web/src/lib.rs index 3fe98dfbd9..53b54b7eb7 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -75,7 +75,7 @@ pub use element::Element; pub use hasher::Hasher; pub use iced_core::{ keyboard, mouse, Align, Background, Color, Font, HorizontalAlignment, - Length, MouseCursor, Point, Rectangle, Size, Vector, VerticalAlignment, + Length, Point, Rectangle, Size, Vector, VerticalAlignment, }; pub use iced_futures::{executor, futures, Command}; pub use subscription::Subscription; diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index dccd0d825c..71b4af382a 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -7,8 +7,7 @@ use crate::{ use crate::image::{self, Image}; use iced_native::{ - layout, Background, Color, Layout, MouseCursor, Point, Rectangle, Vector, - Widget, + layout, mouse, Background, Color, Layout, Point, Rectangle, Vector, Widget, }; mod widget; @@ -94,10 +93,10 @@ impl Renderer { device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, target: Target<'_>, - (primitive, mouse_cursor): &(Primitive, MouseCursor), + (primitive, mouse_interaction): &(Primitive, mouse::Interaction), scale_factor: f64, overlay: &[T], - ) -> MouseCursor { + ) -> mouse::Interaction { log::debug!("Drawing"); let (width, height) = target.viewport.dimensions(); @@ -132,7 +131,7 @@ impl Renderer { #[cfg(any(feature = "image", feature = "svg"))] self.image_pipeline.trim_cache(); - *mouse_cursor + *mouse_interaction } fn draw_primitive<'a>( @@ -453,7 +452,7 @@ impl Renderer { } impl iced_native::Renderer for Renderer { - type Output = (Primitive, MouseCursor); + type Output = (Primitive, mouse::Interaction); type Defaults = Defaults; fn layout<'a, Message>( diff --git a/wgpu/src/renderer/widget/button.rs b/wgpu/src/renderer/widget/button.rs index 5e55873a5e..eb2250388c 100644 --- a/wgpu/src/renderer/widget/button.rs +++ b/wgpu/src/renderer/widget/button.rs @@ -1,6 +1,6 @@ use crate::{button::StyleSheet, defaults, Defaults, Primitive, Renderer}; use iced_native::{ - Background, Color, Element, Layout, MouseCursor, Point, Rectangle, Vector, + mouse, Background, Color, Element, Layout, Point, Rectangle, Vector, }; impl iced_native::button::Renderer for Renderer { @@ -84,9 +84,9 @@ impl iced_native::button::Renderer for Renderer { content }, if is_mouse_over { - MouseCursor::Pointer + mouse::Interaction::Pointer } else { - MouseCursor::default() + mouse::Interaction::default() }, ) } diff --git a/wgpu/src/renderer/widget/checkbox.rs b/wgpu/src/renderer/widget/checkbox.rs index 7f7f6de315..0340bf6276 100644 --- a/wgpu/src/renderer/widget/checkbox.rs +++ b/wgpu/src/renderer/widget/checkbox.rs @@ -1,6 +1,6 @@ use crate::{checkbox::StyleSheet, Primitive, Renderer}; use iced_native::{ - checkbox, HorizontalAlignment, MouseCursor, Rectangle, VerticalAlignment, + checkbox, mouse, HorizontalAlignment, Rectangle, VerticalAlignment, }; impl checkbox::Renderer for Renderer { @@ -54,9 +54,9 @@ impl checkbox::Renderer for Renderer { }, }, if is_mouse_over { - MouseCursor::Pointer + mouse::Interaction::Pointer } else { - MouseCursor::default() + mouse::Interaction::default() }, ) } diff --git a/wgpu/src/renderer/widget/column.rs b/wgpu/src/renderer/widget/column.rs index e6a9d8f037..b853276daa 100644 --- a/wgpu/src/renderer/widget/column.rs +++ b/wgpu/src/renderer/widget/column.rs @@ -1,5 +1,5 @@ use crate::{Primitive, Renderer}; -use iced_native::{column, Element, Layout, MouseCursor, Point}; +use iced_native::{column, mouse, Element, Layout, Point}; impl column::Renderer for Renderer { fn draw( @@ -9,7 +9,7 @@ impl column::Renderer for Renderer { layout: Layout<'_>, cursor_position: Point, ) -> Self::Output { - let mut mouse_cursor = MouseCursor::default(); + let mut mouse_interaction = mouse::Interaction::default(); ( Primitive::Group { @@ -17,18 +17,18 @@ impl column::Renderer for Renderer { .iter() .zip(layout.children()) .map(|(child, layout)| { - let (primitive, new_mouse_cursor) = + let (primitive, new_mouse_interaction) = child.draw(self, defaults, layout, cursor_position); - if new_mouse_cursor > mouse_cursor { - mouse_cursor = new_mouse_cursor; + if new_mouse_interaction > mouse_interaction { + mouse_interaction = new_mouse_interaction; } primitive }) .collect(), }, - mouse_cursor, + mouse_interaction, ) } } diff --git a/wgpu/src/renderer/widget/container.rs b/wgpu/src/renderer/widget/container.rs index dda7dc8a54..30cc3f07d1 100644 --- a/wgpu/src/renderer/widget/container.rs +++ b/wgpu/src/renderer/widget/container.rs @@ -21,7 +21,7 @@ impl iced_native::container::Renderer for Renderer { }, }; - let (content, mouse_cursor) = + let (content, mouse_interaction) = content.draw(self, &defaults, content_layout, cursor_position); if style.background.is_some() || style.border_width > 0 { @@ -39,10 +39,10 @@ impl iced_native::container::Renderer for Renderer { Primitive::Group { primitives: vec![quad, content], }, - mouse_cursor, + mouse_interaction, ) } else { - (content, mouse_cursor) + (content, mouse_interaction) } } } diff --git a/wgpu/src/renderer/widget/image.rs b/wgpu/src/renderer/widget/image.rs index 6b7f1c60df..c4c0498497 100644 --- a/wgpu/src/renderer/widget/image.rs +++ b/wgpu/src/renderer/widget/image.rs @@ -1,5 +1,5 @@ use crate::{Primitive, Renderer}; -use iced_native::{image, Layout, MouseCursor}; +use iced_native::{image, mouse, Layout}; impl image::Renderer for Renderer { fn dimensions(&self, handle: &image::Handle) -> (u32, u32) { @@ -16,7 +16,7 @@ impl image::Renderer for Renderer { handle, bounds: layout.bounds(), }, - MouseCursor::default(), + mouse::Interaction::default(), ) } } diff --git a/wgpu/src/renderer/widget/pane_grid.rs b/wgpu/src/renderer/widget/pane_grid.rs index 80e2471f24..2253e4af93 100644 --- a/wgpu/src/renderer/widget/pane_grid.rs +++ b/wgpu/src/renderer/widget/pane_grid.rs @@ -1,7 +1,8 @@ use crate::{Primitive, Renderer}; use iced_native::{ + mouse, pane_grid::{self, Axis, Pane}, - Element, Layout, MouseCursor, Point, Rectangle, Vector, + Element, Layout, Point, Rectangle, Vector, }; impl pane_grid::Renderer for Renderer { @@ -22,7 +23,7 @@ impl pane_grid::Renderer for Renderer { cursor_position }; - let mut mouse_cursor = MouseCursor::default(); + let mut mouse_interaction = mouse::Interaction::default(); let mut dragged_pane = None; let mut panes: Vec<_> = content @@ -30,11 +31,11 @@ impl pane_grid::Renderer for Renderer { .zip(layout.children()) .enumerate() .map(|(i, ((id, pane), layout))| { - let (primitive, new_mouse_cursor) = + let (primitive, new_mouse_interaction) = pane.draw(self, defaults, layout, pane_cursor_position); - if new_mouse_cursor > mouse_cursor { - mouse_cursor = new_mouse_cursor; + if new_mouse_interaction > mouse_interaction { + mouse_interaction = new_mouse_interaction; } if Some(*id) == dragging { @@ -78,14 +79,14 @@ impl pane_grid::Renderer for Renderer { ( Primitive::Group { primitives }, if dragging.is_some() { - MouseCursor::Grabbing + mouse::Interaction::Grabbing } else if let Some(axis) = resizing { match axis { - Axis::Horizontal => MouseCursor::ResizingVertically, - Axis::Vertical => MouseCursor::ResizingHorizontally, + Axis::Horizontal => mouse::Interaction::ResizingVertically, + Axis::Vertical => mouse::Interaction::ResizingHorizontally, } } else { - mouse_cursor + mouse_interaction }, ) } diff --git a/wgpu/src/renderer/widget/progress_bar.rs b/wgpu/src/renderer/widget/progress_bar.rs index fe032fbf48..2baeeb147d 100644 --- a/wgpu/src/renderer/widget/progress_bar.rs +++ b/wgpu/src/renderer/widget/progress_bar.rs @@ -1,5 +1,5 @@ use crate::{progress_bar::StyleSheet, Primitive, Renderer}; -use iced_native::{progress_bar, Color, MouseCursor, Rectangle}; +use iced_native::{mouse, progress_bar, Color, Rectangle}; impl progress_bar::Renderer for Renderer { type Style = Box; @@ -48,7 +48,7 @@ impl progress_bar::Renderer for Renderer { } else { background }, - MouseCursor::default(), + mouse::Interaction::default(), ) } } diff --git a/wgpu/src/renderer/widget/radio.rs b/wgpu/src/renderer/widget/radio.rs index 551700c848..2f1461db4b 100644 --- a/wgpu/src/renderer/widget/radio.rs +++ b/wgpu/src/renderer/widget/radio.rs @@ -1,5 +1,5 @@ use crate::{radio::StyleSheet, Primitive, Renderer}; -use iced_native::{radio, Background, Color, MouseCursor, Rectangle}; +use iced_native::{mouse, radio, Background, Color, Rectangle}; const SIZE: f32 = 28.0; const DOT_SIZE: f32 = SIZE / 2.0; @@ -55,9 +55,9 @@ impl radio::Renderer for Renderer { }, }, if is_mouse_over { - MouseCursor::Pointer + mouse::Interaction::Pointer } else { - MouseCursor::default() + mouse::Interaction::default() }, ) } diff --git a/wgpu/src/renderer/widget/row.rs b/wgpu/src/renderer/widget/row.rs index c6a10c07ae..d0b7ef09eb 100644 --- a/wgpu/src/renderer/widget/row.rs +++ b/wgpu/src/renderer/widget/row.rs @@ -1,5 +1,5 @@ use crate::{Primitive, Renderer}; -use iced_native::{row, Element, Layout, MouseCursor, Point}; +use iced_native::{mouse, row, Element, Layout, Point}; impl row::Renderer for Renderer { fn draw( @@ -9,7 +9,7 @@ impl row::Renderer for Renderer { layout: Layout<'_>, cursor_position: Point, ) -> Self::Output { - let mut mouse_cursor = MouseCursor::default(); + let mut mouse_interaction = mouse::Interaction::default(); ( Primitive::Group { @@ -17,18 +17,18 @@ impl row::Renderer for Renderer { .iter() .zip(layout.children()) .map(|(child, layout)| { - let (primitive, new_mouse_cursor) = + let (primitive, new_mouse_interaction) = child.draw(self, defaults, layout, cursor_position); - if new_mouse_cursor > mouse_cursor { - mouse_cursor = new_mouse_cursor; + if new_mouse_interaction > mouse_interaction { + mouse_interaction = new_mouse_interaction; } primitive }) .collect(), }, - mouse_cursor, + mouse_interaction, ) } } diff --git a/wgpu/src/renderer/widget/scrollable.rs b/wgpu/src/renderer/widget/scrollable.rs index 732523e3f5..8a400b825e 100644 --- a/wgpu/src/renderer/widget/scrollable.rs +++ b/wgpu/src/renderer/widget/scrollable.rs @@ -1,7 +1,5 @@ use crate::{Primitive, Renderer}; -use iced_native::{ - scrollable, Background, Color, MouseCursor, Rectangle, Vector, -}; +use iced_native::{mouse, scrollable, Background, Color, Rectangle, Vector}; const SCROLLBAR_WIDTH: u16 = 10; const SCROLLBAR_MARGIN: u16 = 2; @@ -56,7 +54,7 @@ impl scrollable::Renderer for Renderer { scrollbar: Option, offset: u32, style_sheet: &Self::Style, - (content, mouse_cursor): Self::Output, + (content, mouse_interaction): Self::Output, ) -> Self::Output { ( if let Some(scrollbar) = scrollbar { @@ -118,9 +116,9 @@ impl scrollable::Renderer for Renderer { content }, if is_mouse_over_scrollbar || state.is_scroller_grabbed() { - MouseCursor::Idle + mouse::Interaction::Idle } else { - mouse_cursor + mouse_interaction }, ) } diff --git a/wgpu/src/renderer/widget/slider.rs b/wgpu/src/renderer/widget/slider.rs index 335e1b9271..220feace77 100644 --- a/wgpu/src/renderer/widget/slider.rs +++ b/wgpu/src/renderer/widget/slider.rs @@ -2,7 +2,7 @@ use crate::{ slider::{HandleShape, StyleSheet}, Primitive, Renderer, }; -use iced_native::{slider, Background, Color, MouseCursor, Point, Rectangle}; +use iced_native::{mouse, slider, Background, Color, Point, Rectangle}; const HANDLE_HEIGHT: f32 = 22.0; @@ -95,11 +95,11 @@ impl slider::Renderer for Renderer { primitives: vec![rail_top, rail_bottom, handle], }, if is_dragging { - MouseCursor::Grabbing + mouse::Interaction::Grabbing } else if is_mouse_over { - MouseCursor::Grab + mouse::Interaction::Grab } else { - MouseCursor::default() + mouse::Interaction::default() }, ) } diff --git a/wgpu/src/renderer/widget/space.rs b/wgpu/src/renderer/widget/space.rs index 9ec0ed6dbf..225f7e6cda 100644 --- a/wgpu/src/renderer/widget/space.rs +++ b/wgpu/src/renderer/widget/space.rs @@ -1,8 +1,8 @@ use crate::{Primitive, Renderer}; -use iced_native::{space, MouseCursor, Rectangle}; +use iced_native::{mouse, space, Rectangle}; impl space::Renderer for Renderer { fn draw(&mut self, _bounds: Rectangle) -> Self::Output { - (Primitive::None, MouseCursor::default()) + (Primitive::None, mouse::Interaction::default()) } } diff --git a/wgpu/src/renderer/widget/svg.rs b/wgpu/src/renderer/widget/svg.rs index 4ee983ead9..f6d6d0baaf 100644 --- a/wgpu/src/renderer/widget/svg.rs +++ b/wgpu/src/renderer/widget/svg.rs @@ -1,5 +1,5 @@ use crate::{Primitive, Renderer}; -use iced_native::{svg, Layout, MouseCursor}; +use iced_native::{mouse, svg, Layout}; impl svg::Renderer for Renderer { fn dimensions(&self, handle: &svg::Handle) -> (u32, u32) { @@ -16,7 +16,7 @@ impl svg::Renderer for Renderer { handle, bounds: layout.bounds(), }, - MouseCursor::default(), + mouse::Interaction::default(), ) } } diff --git a/wgpu/src/renderer/widget/text.rs b/wgpu/src/renderer/widget/text.rs index 4a4ecef4fe..4605ed066c 100644 --- a/wgpu/src/renderer/widget/text.rs +++ b/wgpu/src/renderer/widget/text.rs @@ -1,6 +1,6 @@ use crate::{Primitive, Renderer}; use iced_native::{ - text, Color, Font, HorizontalAlignment, MouseCursor, Rectangle, Size, + mouse, text, Color, Font, HorizontalAlignment, Rectangle, Size, VerticalAlignment, }; @@ -55,7 +55,7 @@ impl text::Renderer for Renderer { horizontal_alignment, vertical_alignment, }, - MouseCursor::default(), + mouse::Interaction::default(), ) } } diff --git a/wgpu/src/renderer/widget/text_input.rs b/wgpu/src/renderer/widget/text_input.rs index 97eb011455..57be6692d6 100644 --- a/wgpu/src/renderer/widget/text_input.rs +++ b/wgpu/src/renderer/widget/text_input.rs @@ -1,9 +1,10 @@ use crate::{text_input::StyleSheet, Primitive, Renderer}; use iced_native::{ + mouse, text_input::{self, cursor}, - Background, Color, Font, HorizontalAlignment, MouseCursor, Point, - Rectangle, Size, Vector, VerticalAlignment, + Background, Color, Font, HorizontalAlignment, Point, Rectangle, Size, + Vector, VerticalAlignment, }; use std::f32; @@ -232,9 +233,9 @@ impl text_input::Renderer for Renderer { primitives: vec![input, contents], }, if is_mouse_over { - MouseCursor::Text + mouse::Interaction::Text } else { - MouseCursor::default() + mouse::Interaction::default() }, ) } diff --git a/wgpu/src/widget/canvas.rs b/wgpu/src/widget/canvas.rs index 05306e67c0..2fc10ea08e 100644 --- a/wgpu/src/widget/canvas.rs +++ b/wgpu/src/widget/canvas.rs @@ -9,8 +9,8 @@ use crate::{Defaults, Primitive, Renderer}; use iced_native::{ - layout, Clipboard, Element, Hasher, Layout, Length, MouseCursor, Point, - Size, Vector, Widget, + layout, mouse, Clipboard, Element, Hasher, Layout, Length, Point, Size, + Vector, Widget, }; use std::hash::Hash; use std::marker::PhantomData; @@ -192,7 +192,7 @@ impl> Widget _defaults: &Defaults, layout: Layout<'_>, cursor_position: Point, - ) -> (Primitive, MouseCursor) { + ) -> (Primitive, mouse::Interaction) { let bounds = layout.bounds(); let translation = Vector::new(bounds.x, bounds.y); let cursor = Cursor::from_window_position(cursor_position); @@ -209,7 +209,7 @@ impl> Widget .collect(), }), }, - self.program.mouse_cursor(bounds, cursor), + self.program.mouse_interaction(bounds, cursor), ) } diff --git a/wgpu/src/widget/canvas/program.rs b/wgpu/src/widget/canvas/program.rs index 5b995bfa14..725d9d726e 100644 --- a/wgpu/src/widget/canvas/program.rs +++ b/wgpu/src/widget/canvas/program.rs @@ -1,5 +1,5 @@ use crate::canvas::{Cursor, Event, Geometry}; -use iced_native::{MouseCursor, Rectangle}; +use iced_native::{mouse, Rectangle}; /// The state and logic of a [`Canvas`]. /// @@ -42,15 +42,19 @@ pub trait Program { /// [`Cache`]: struct.Cache.html fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Vec; - /// Returns the mouse cursor state of the [`Program`]. + /// Returns the current mouse interaction of the [`Program`]. /// - /// The mouse cursor returned will be in effect even if the cursor position + /// The interaction returned will be in effect even if the cursor position /// is out of bounds of the program's [`Canvas`]. /// /// [`Program`]: trait.Program.html /// [`Canvas`]: struct.Canvas.html - fn mouse_cursor(&self, _bounds: Rectangle, _cursor: Cursor) -> MouseCursor { - MouseCursor::default() + fn mouse_interaction( + &self, + _bounds: Rectangle, + _cursor: Cursor, + ) -> mouse::Interaction { + mouse::Interaction::default() } } @@ -71,7 +75,11 @@ where T::draw(self, bounds, cursor) } - fn mouse_cursor(&self, bounds: Rectangle, cursor: Cursor) -> MouseCursor { - T::mouse_cursor(self, bounds, cursor) + fn mouse_interaction( + &self, + bounds: Rectangle, + cursor: Cursor, + ) -> mouse::Interaction { + T::mouse_interaction(self, bounds, cursor) } } diff --git a/wgpu/src/window/backend.rs b/wgpu/src/window/backend.rs index e1b77700c4..7e40ae0ce6 100644 --- a/wgpu/src/window/backend.rs +++ b/wgpu/src/window/backend.rs @@ -1,6 +1,6 @@ use crate::{window::SwapChain, Renderer, Settings, Target}; -use iced_native::{futures, MouseCursor}; +use iced_native::{futures, mouse}; use raw_window_handle::HasRawWindowHandle; /// A window graphics backend for iced powered by `wgpu`. @@ -78,7 +78,7 @@ impl iced_native::window::Backend for Backend { output: &::Output, scale_factor: f64, overlay: &[T], - ) -> MouseCursor { + ) -> mouse::Interaction { let (frame, viewport) = swap_chain.next_frame().expect("Next frame"); let mut encoder = self.device.create_command_encoder( @@ -101,7 +101,7 @@ impl iced_native::window::Backend for Backend { depth_stencil_attachment: None, }); - let mouse_cursor = renderer.draw( + let mouse_interaction = renderer.draw( &mut self.device, &mut encoder, Target { @@ -115,6 +115,6 @@ impl iced_native::window::Backend for Backend { self.queue.submit(&[encoder.finish()]); - mouse_cursor + mouse_interaction } } diff --git a/winit/src/application.rs b/winit/src/application.rs index ae9775f74d..f6bc8fcc42 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -1,6 +1,6 @@ use crate::{ - conversion, size::Size, window, Cache, Clipboard, Command, Debug, Element, - Executor, Mode, MouseCursor, Proxy, Runtime, Settings, Subscription, + conversion, mouse, size::Size, window, Cache, Clipboard, Command, Debug, + Element, Executor, Mode, Proxy, Runtime, Settings, Subscription, UserInterface, }; @@ -205,7 +205,7 @@ pub trait Application: Sized { let mut cache = Some(user_interface.into_cache()); let mut events = Vec::new(); - let mut mouse_cursor = MouseCursor::default(); + let mut mouse_interaction = mouse::Interaction::default(); let mut modifiers = winit::event::ModifiersState::default(); debug.startup_finished(); @@ -328,7 +328,7 @@ pub trait Application: Sized { resized = false; } - let new_mouse_cursor = backend.draw( + let new_mouse_interaction = backend.draw( &mut renderer, &mut swap_chain, &primitive, @@ -338,12 +338,12 @@ pub trait Application: Sized { debug.render_finished(); - if new_mouse_cursor != mouse_cursor { - window.set_cursor_icon(conversion::mouse_cursor( - new_mouse_cursor, + if new_mouse_interaction != mouse_interaction { + window.set_cursor_icon(conversion::mouse_interaction( + new_mouse_interaction, )); - mouse_cursor = new_mouse_cursor; + mouse_interaction = new_mouse_interaction; } // TODO: Handle animations! diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index 93827e372d..b887db6eff 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -4,7 +4,7 @@ //! [`iced_native`]: https://github.com/hecrj/iced/tree/master/native use crate::{ keyboard::{self, KeyCode, ModifiersState}, - mouse, window, Event, Mode, MouseCursor, + mouse, window, Event, Mode, }; /// Converts a winit window event into an iced event. @@ -125,19 +125,23 @@ pub fn fullscreen( /// /// [`winit`]: https://github.com/rust-windowing/winit /// [`iced_native`]: https://github.com/hecrj/iced/tree/master/native -pub fn mouse_cursor(mouse_cursor: MouseCursor) -> winit::window::CursorIcon { - match mouse_cursor { - MouseCursor::Idle => winit::window::CursorIcon::Default, - MouseCursor::Pointer => winit::window::CursorIcon::Hand, - MouseCursor::Working => winit::window::CursorIcon::Progress, - MouseCursor::Grab => winit::window::CursorIcon::Grab, - MouseCursor::Grabbing => winit::window::CursorIcon::Grabbing, - MouseCursor::Crosshair => winit::window::CursorIcon::Crosshair, - MouseCursor::Text => winit::window::CursorIcon::Text, - MouseCursor::ResizingHorizontally => { +pub fn mouse_interaction( + interaction: mouse::Interaction, +) -> winit::window::CursorIcon { + use mouse::Interaction; + + match interaction { + Interaction::Idle => winit::window::CursorIcon::Default, + Interaction::Pointer => winit::window::CursorIcon::Hand, + Interaction::Working => winit::window::CursorIcon::Progress, + Interaction::Grab => winit::window::CursorIcon::Grab, + Interaction::Grabbing => winit::window::CursorIcon::Grabbing, + Interaction::Crosshair => winit::window::CursorIcon::Crosshair, + Interaction::Text => winit::window::CursorIcon::Text, + Interaction::ResizingHorizontally => { winit::window::CursorIcon::EwResize } - MouseCursor::ResizingVertically => winit::window::CursorIcon::NsResize, + Interaction::ResizingVertically => winit::window::CursorIcon::NsResize, } } From 005ad6215aa9e2a8f159454c8006b5dc6aeb3635 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 30 Apr 2020 08:59:47 +0200 Subject: [PATCH 46/73] Update `README` of `game_of_life` example --- examples/game_of_life/README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/examples/game_of_life/README.md b/examples/game_of_life/README.md index ebbb12cce3..bb1b9736df 100644 --- a/examples/game_of_life/README.md +++ b/examples/game_of_life/README.md @@ -1,18 +1,19 @@ -## Bézier tool +## Game of Life -A Paint-like tool for drawing Bézier curves using the `Canvas` widget. +An interactive version of the Game of Life, invented by John Conway, implemented +on top of a `Canvas` widget and other controls. -The __[`main`]__ file contains all the code of the example. +The __[`main`]__ file contains the relevant code of the example. You can run it with `cargo run`: ``` -cargo run --package bezier_tool +cargo run --package game_of_life ``` [`main`]: src/main.rs From ee97887409849395ecfd63e499c5d5372b121aa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 1 May 2020 00:50:40 +0200 Subject: [PATCH 47/73] Introduce `Cell` type in `game_of_life` --- examples/game_of_life/Cargo.toml | 2 +- examples/game_of_life/src/main.rs | 182 ++++++++++++++++-------------- 2 files changed, 98 insertions(+), 86 deletions(-) diff --git a/examples/game_of_life/Cargo.toml b/examples/game_of_life/Cargo.toml index 413493ae77..b105453787 100644 --- a/examples/game_of_life/Cargo.toml +++ b/examples/game_of_life/Cargo.toml @@ -6,5 +6,5 @@ edition = "2018" publish = false [dependencies] -iced = { path = "../..", features = ["canvas", "tokio"] } +iced = { path = "../..", features = ["canvas", "tokio", "debug"] } itertools = "0.9" diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index f0891db1da..9f43b56a6e 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -161,11 +161,9 @@ mod grid { }; use std::collections::{HashMap, HashSet}; - const CELL_SIZE: usize = 20; - #[derive(Default)] pub struct Grid { - alive_cells: HashSet<(isize, isize)>, + life: HashSet, interaction: Option, cache: canvas::Cache, translation: Vector, @@ -173,7 +171,7 @@ mod grid { #[derive(Debug, Clone, Copy)] pub enum Message { - Populate { cell: (isize, isize) }, + Populate(Cell), } enum Interaction { @@ -182,41 +180,26 @@ mod grid { } impl Grid { - fn with_neighbors( - i: isize, - j: isize, - ) -> impl Iterator { - use itertools::Itertools; - - let rows = i.saturating_sub(1)..=i.saturating_add(1); - let columns = j.saturating_sub(1)..=j.saturating_add(1); - - rows.cartesian_product(columns) - } - pub fn tick(&mut self) { use itertools::Itertools; - let populated_neighbors: HashMap<(isize, isize), usize> = self - .alive_cells + let populated_neighbors: HashMap = self + .life .iter() - .flat_map(|&(i, j)| Self::with_neighbors(i, j)) + .flat_map(Cell::cluster) .unique() - .map(|(i, j)| ((i, j), self.populated_neighbors(i, j))) + .map(|cell| (cell, self.count_adjacent_life(cell))) .collect(); - for (&(i, j), amount) in populated_neighbors.iter() { - let is_populated = self.alive_cells.contains(&(i, j)); - + for (cell, amount) in populated_neighbors.iter() { match amount { - 2 | 3 if is_populated => {} + 2 => {} 3 => { - let _ = self.alive_cells.insert((i, j)); + let _ = self.life.insert(*cell); } - _ if is_populated => { - let _ = self.alive_cells.remove(&(i, j)); + _ => { + let _ = self.life.remove(cell); } - _ => {} } } @@ -225,8 +208,8 @@ mod grid { pub fn update(&mut self, message: Message) { match message { - Message::Populate { cell } => { - self.alive_cells.insert(cell); + Message::Populate(cell) => { + self.life.insert(cell); self.cache.clear() } } @@ -239,24 +222,16 @@ mod grid { .into() } - fn populated_neighbors(&self, row: isize, column: isize) -> usize { - let with_neighbors = Self::with_neighbors(row, column); + fn count_adjacent_life(&self, cell: Cell) -> usize { + let cluster = Cell::cluster(&cell); - let is_neighbor = |i: isize, j: isize| i != row || j != column; - let is_populated = - |i: isize, j: isize| self.alive_cells.contains(&(i, j)); + let is_neighbor = |candidate| candidate != cell; + let is_populated = |cell| self.life.contains(&cell); - with_neighbors - .filter(|&(i, j)| is_neighbor(i, j) && is_populated(i, j)) + cluster + .filter(|&cell| is_neighbor(cell) && is_populated(cell)) .count() } - - fn cell_at(&self, position: Point) -> Option<(isize, isize)> { - let i = (position.y / CELL_SIZE as f32).ceil() as isize; - let j = (position.x / CELL_SIZE as f32).ceil() as isize; - - Some((i.saturating_sub(1), j.saturating_sub(1))) - } } impl<'a> canvas::Program for Grid { @@ -271,12 +246,12 @@ mod grid { } let cursor_position = cursor.position_in(&bounds)?; - let cell = self.cell_at(cursor_position - self.translation)?; + let cell = Cell::at(cursor_position - self.translation); - let populate = if self.alive_cells.contains(&cell) { + let populate = if self.life.contains(&cell) { None } else { - Some(Message::Populate { cell }) + Some(Message::Populate(cell)) }; match event { @@ -333,31 +308,24 @@ mod grid { ), ); - let first_row = - (-self.translation.y / CELL_SIZE as f32).floor() as isize; - let first_column = - (-self.translation.x / CELL_SIZE as f32).floor() as isize; - - let visible_rows = - (frame.height() / CELL_SIZE as f32).ceil() as isize; - let visible_columns = - (frame.width() / CELL_SIZE as f32).ceil() as isize; - frame.with_save(|frame| { frame.translate(self.translation); - frame.scale(CELL_SIZE as f32); + frame.scale(Cell::SIZE as f32); let cells = Path::new(|p| { - for i in first_row..=(first_row + visible_rows) { - for j in - first_column..=(first_column + visible_columns) - { - if self.alive_cells.contains(&(i, j)) { - p.rectangle( - Point::new(j as f32, i as f32), - cell_size, - ); - } + let region = Rectangle { + x: -self.translation.x, + y: -self.translation.y, + width: frame.width(), + height: frame.height(), + }; + + for cell in Cell::all_visible_in(region) { + if self.life.contains(&cell) { + p.rectangle( + Point::new(cell.j as f32, cell.i as f32), + cell_size, + ); } } }); @@ -369,25 +337,23 @@ mod grid { let mut frame = Frame::new(bounds.size()); frame.translate(self.translation); - frame.scale(CELL_SIZE as f32); + frame.scale(Cell::SIZE as f32); if let Some(cursor_position) = cursor.position_in(&bounds) { - if let Some((i, j)) = - self.cell_at(cursor_position - self.translation) - { - let interaction = Path::rectangle( - Point::new(j as f32, i as f32), - cell_size, - ); - - frame.fill( - &interaction, - Color { - a: 0.5, - ..Color::BLACK - }, - ); - } + let cell = Cell::at(cursor_position - self.translation); + + let interaction = Path::rectangle( + Point::new(cell.j as f32, cell.i as f32), + cell_size, + ); + + frame.fill( + &interaction, + Color { + a: 0.5, + ..Color::BLACK + }, + ); } frame.into_geometry() @@ -413,4 +379,50 @@ mod grid { } } } + + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + pub struct Cell { + i: isize, + j: isize, + } + + impl Cell { + const SIZE: usize = 20; + + fn at(position: Point) -> Cell { + let i = (position.y / Cell::SIZE as f32).ceil() as isize; + let j = (position.x / Cell::SIZE as f32).ceil() as isize; + + Cell { + i: i.saturating_sub(1), + j: j.saturating_sub(1), + } + } + + fn cluster(cell: &Cell) -> impl Iterator { + use itertools::Itertools; + + let rows = cell.i.saturating_sub(1)..=cell.i.saturating_add(1); + let columns = cell.j.saturating_sub(1)..=cell.j.saturating_add(1); + + rows.cartesian_product(columns).map(|(i, j)| Cell { i, j }) + } + + fn all_visible_in(region: Rectangle) -> impl Iterator { + use itertools::Itertools; + + let first_row = (region.y / Cell::SIZE as f32).floor() as isize; + let first_column = (region.x / Cell::SIZE as f32).floor() as isize; + + let visible_rows = + (region.height / Cell::SIZE as f32).ceil() as isize; + let visible_columns = + (region.width / Cell::SIZE as f32).ceil() as isize; + + let rows = first_row..=first_row + visible_rows; + let columns = first_column..=first_column + visible_columns; + + rows.cartesian_product(columns).map(|(i, j)| Cell { i, j }) + } + } } From 71323c51bbdcb7dcccd6249fcd4ea3b1df589a9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 1 May 2020 00:54:43 +0200 Subject: [PATCH 48/73] Simplify `Interaction` handling in `game_of_life` --- examples/game_of_life/src/main.rs | 42 ++++++++++++++++--------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index 9f43b56a6e..8a841c91c2 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -164,7 +164,7 @@ mod grid { #[derive(Default)] pub struct Grid { life: HashSet, - interaction: Option, + interaction: Interaction, cache: canvas::Cache, translation: Vector, } @@ -174,11 +174,6 @@ mod grid { Populate(Cell), } - enum Interaction { - Drawing, - Panning { translation: Vector, start: Point }, - } - impl Grid { pub fn tick(&mut self) { use itertools::Itertools; @@ -242,7 +237,7 @@ mod grid { cursor: Cursor, ) -> Option { if let Event::Mouse(mouse::Event::ButtonReleased(_)) = event { - self.interaction = None; + self.interaction = Interaction::None; } let cursor_position = cursor.position_in(&bounds)?; @@ -258,15 +253,15 @@ mod grid { Event::Mouse(mouse_event) => match mouse_event { mouse::Event::ButtonPressed(button) => match button { mouse::Button::Left => { - self.interaction = Some(Interaction::Drawing); + self.interaction = Interaction::Drawing; populate } mouse::Button::Right => { - self.interaction = Some(Interaction::Panning { + self.interaction = Interaction::Panning { translation: self.translation, start: cursor_position, - }); + }; None } @@ -274,11 +269,8 @@ mod grid { }, mouse::Event::CursorMoved { .. } => { match self.interaction { - Some(Interaction::Drawing) => populate, - Some(Interaction::Panning { - translation, - start, - }) => { + Interaction::Drawing => populate, + Interaction::Panning { translation, start } => { self.translation = translation + (cursor_position - start); @@ -368,11 +360,9 @@ mod grid { cursor: Cursor, ) -> mouse::Interaction { match self.interaction { - Some(Interaction::Drawing) => mouse::Interaction::Crosshair, - Some(Interaction::Panning { .. }) => { - mouse::Interaction::Grabbing - } - None if cursor.is_over(&bounds) => { + Interaction::Drawing => mouse::Interaction::Crosshair, + Interaction::Panning { .. } => mouse::Interaction::Grabbing, + Interaction::None if cursor.is_over(&bounds) => { mouse::Interaction::Crosshair } _ => mouse::Interaction::default(), @@ -425,4 +415,16 @@ mod grid { rows.cartesian_product(columns).map(|(i, j)| Cell { i, j }) } } + + enum Interaction { + None, + Drawing, + Panning { translation: Vector, start: Point }, + } + + impl Default for Interaction { + fn default() -> Interaction { + Interaction::None + } + } } From a6db1e1fb3e512f86be076e70eff92abb11fd457 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 1 May 2020 01:08:39 +0200 Subject: [PATCH 49/73] Introduce `Life` type in `game_of_life` --- examples/game_of_life/src/main.rs | 88 +++++++++++++++++++------------ 1 file changed, 53 insertions(+), 35 deletions(-) diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index 8a841c91c2..b539247ba3 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -163,7 +163,7 @@ mod grid { #[derive(Default)] pub struct Grid { - life: HashSet, + life: Life, interaction: Interaction, cache: canvas::Cache, translation: Vector, @@ -176,35 +176,14 @@ mod grid { impl Grid { pub fn tick(&mut self) { - use itertools::Itertools; - - let populated_neighbors: HashMap = self - .life - .iter() - .flat_map(Cell::cluster) - .unique() - .map(|cell| (cell, self.count_adjacent_life(cell))) - .collect(); - - for (cell, amount) in populated_neighbors.iter() { - match amount { - 2 => {} - 3 => { - let _ = self.life.insert(*cell); - } - _ => { - let _ = self.life.remove(cell); - } - } - } - + self.life.tick(); self.cache.clear() } pub fn update(&mut self, message: Message) { match message { Message::Populate(cell) => { - self.life.insert(cell); + self.life.populate(cell); self.cache.clear() } } @@ -216,17 +195,6 @@ mod grid { .height(Length::Fill) .into() } - - fn count_adjacent_life(&self, cell: Cell) -> usize { - let cluster = Cell::cluster(&cell); - - let is_neighbor = |candidate| candidate != cell; - let is_populated = |cell| self.life.contains(&cell); - - cluster - .filter(|&cell| is_neighbor(cell) && is_populated(cell)) - .count() - } } impl<'a> canvas::Program for Grid { @@ -370,6 +338,56 @@ mod grid { } } + #[derive(Default)] + pub struct Life { + cells: HashSet, + } + + impl Life { + fn contains(&self, cell: &Cell) -> bool { + self.cells.contains(cell) + } + + fn populate(&mut self, cell: Cell) { + self.cells.insert(cell); + } + + fn tick(&mut self) { + use itertools::Itertools; + + let populated_neighbors: HashMap = self + .cells + .iter() + .flat_map(Cell::cluster) + .unique() + .map(|cell| (cell, self.count_adjacent(cell))) + .collect(); + + for (cell, amount) in populated_neighbors.iter() { + match amount { + 2 => {} + 3 => { + let _ = self.cells.insert(*cell); + } + _ => { + let _ = self.cells.remove(cell); + } + } + } + } + + fn count_adjacent(&self, cell: Cell) -> usize { + let cluster = Cell::cluster(&cell); + + let is_neighbor = |candidate| candidate != cell; + let is_populated = |cell| self.cells.contains(&cell); + + cluster + .filter(|&cell| is_neighbor(cell) && is_populated(cell)) + .count() + } + } + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Cell { i: isize, From 377ead93d6d506e7fe1e49d4b8b54c0f1d4c5e14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 1 May 2020 01:24:31 +0200 Subject: [PATCH 50/73] Improve tick performance in `game_of_life` --- examples/game_of_life/src/main.rs | 37 +++++++++++++------------------ 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index b539247ba3..3b37dc3495 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -353,17 +353,19 @@ mod grid { } fn tick(&mut self) { - use itertools::Itertools; + let mut adjacent_life = HashMap::new(); + + for cell in &self.cells { + let _ = adjacent_life.entry(*cell).or_insert(0); + + for neighbor in Cell::neighbors(*cell) { + let amount = adjacent_life.entry(neighbor).or_insert(0); - let populated_neighbors: HashMap = self - .cells - .iter() - .flat_map(Cell::cluster) - .unique() - .map(|cell| (cell, self.count_adjacent(cell))) - .collect(); + *amount += 1; + } + } - for (cell, amount) in populated_neighbors.iter() { + for (cell, amount) in adjacent_life.iter() { match amount { 2 => {} 3 => { @@ -375,17 +377,6 @@ mod grid { } } } - - fn count_adjacent(&self, cell: Cell) -> usize { - let cluster = Cell::cluster(&cell); - - let is_neighbor = |candidate| candidate != cell; - let is_populated = |cell| self.cells.contains(&cell); - - cluster - .filter(|&cell| is_neighbor(cell) && is_populated(cell)) - .count() - } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -407,7 +398,7 @@ mod grid { } } - fn cluster(cell: &Cell) -> impl Iterator { + fn cluster(cell: Cell) -> impl Iterator { use itertools::Itertools; let rows = cell.i.saturating_sub(1)..=cell.i.saturating_add(1); @@ -416,6 +407,10 @@ mod grid { rows.cartesian_product(columns).map(|(i, j)| Cell { i, j }) } + fn neighbors(cell: Cell) -> impl Iterator { + Cell::cluster(cell).filter(move |candidate| *candidate != cell) + } + fn all_visible_in(region: Rectangle) -> impl Iterator { use itertools::Itertools; From 980ac6c2a42541224d694f9cd0699f6236fb66ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 1 May 2020 04:30:54 +0200 Subject: [PATCH 51/73] Add `UNIT` constant to `Size` --- core/src/size.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/src/size.rs b/core/src/size.rs index 4276f05feb..a02299e8b3 100644 --- a/core/src/size.rs +++ b/core/src/size.rs @@ -15,6 +15,11 @@ impl Size { /// [`Size`]: struct.Size.html pub const ZERO: Size = Size::new(0., 0.); + /// A [`Size`] with a width and height of 1 unit. + /// + /// [`Size`]: struct.Size.html + pub const UNIT: Size = Size::new(1., 1.); + /// A [`Size`] with infinite width and height. /// /// [`Size`]: struct.Size.html From 345f0e13362aed3debe30226e28c4c4b870e9b8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 1 May 2020 04:32:56 +0200 Subject: [PATCH 52/73] Implement scalar multiplication for `Vector` --- core/src/vector.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/core/src/vector.rs b/core/src/vector.rs index a75053a071..def3f8c0c8 100644 --- a/core/src/vector.rs +++ b/core/src/vector.rs @@ -43,6 +43,17 @@ where } } +impl std::ops::Mul for Vector +where + T: std::ops::Mul + Copy, +{ + type Output = Self; + + fn mul(self, scale: T) -> Self { + Self::new(self.x * scale, self.y * scale) + } +} + impl Default for Vector where T: Default, From 08b376c6d7d2f3c1fab1328202325cb47ce369e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 1 May 2020 04:33:17 +0200 Subject: [PATCH 53/73] Implement `Frame::fill_rectangle` This method greatly improves performance when drawing axis-aligned rectangles. --- wgpu/src/widget/canvas/frame.rs | 51 +++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/wgpu/src/widget/canvas/frame.rs b/wgpu/src/widget/canvas/frame.rs index 1c4a038a2b..5262ab4e2a 100644 --- a/wgpu/src/widget/canvas/frame.rs +++ b/wgpu/src/widget/canvas/frame.rs @@ -120,6 +120,43 @@ impl Frame { let _ = result.expect("Tessellate path"); } + /// Draws an axis-aligned rectangle given its top-left corner coordinate and + /// its `Size` on the [`Frame`] by filling it with the provided style. + /// + /// [`Frame`]: struct.Frame.html + pub fn fill_rectangle( + &mut self, + top_left: Point, + size: Size, + fill: impl Into, + ) { + use lyon::tessellation::{BuffersBuilder, FillOptions}; + + let mut buffers = BuffersBuilder::new( + &mut self.buffers, + FillVertex(match fill.into() { + Fill::Color(color) => color.into_linear(), + }), + ); + + let top_left = + self.transforms.current.raw.transform_point( + lyon::math::Point::new(top_left.x, top_left.y), + ); + + let size = + self.transforms.current.raw.transform_vector( + lyon::math::Vector::new(size.width, size.height), + ); + + let _ = lyon::tessellation::basic_shapes::fill_rectangle( + &lyon::math::Rect::new(top_left, size.into()), + &FillOptions::default(), + &mut buffers, + ) + .expect("Fill rectangle"); + } + /// Draws the stroke of the given [`Path`] on the [`Frame`] with the /// provided style. /// @@ -283,6 +320,20 @@ impl Frame { struct FillVertex([f32; 4]); +impl lyon::tessellation::BasicVertexConstructor + for FillVertex +{ + fn new_vertex( + &mut self, + position: lyon::math::Point, + ) -> triangle::Vertex2D { + triangle::Vertex2D { + position: [position.x, position.y], + color: self.0, + } + } +} + impl lyon::tessellation::FillVertexConstructor for FillVertex { From 404122e0b17300aa46cdb5ec5f0366f24b8ea931 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 1 May 2020 04:35:59 +0200 Subject: [PATCH 54/73] Implement zooming for `game_of_life` example --- examples/game_of_life/src/main.rs | 157 ++++++++++++++++++------------ 1 file changed, 97 insertions(+), 60 deletions(-) diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index 3b37dc3495..d9ff5a149a 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -156,17 +156,17 @@ impl Application for GameOfLife { mod grid { use iced::{ - canvas::{self, Canvas, Cursor, Event, Frame, Geometry, Path}, + canvas::{self, Cache, Canvas, Cursor, Event, Frame, Geometry, Path}, mouse, Color, Element, Length, Point, Rectangle, Size, Vector, }; use std::collections::{HashMap, HashSet}; - #[derive(Default)] pub struct Grid { life: Life, interaction: Interaction, - cache: canvas::Cache, + cache: Cache, translation: Vector, + scaling: f32, } #[derive(Debug, Clone, Copy)] @@ -174,6 +174,18 @@ mod grid { Populate(Cell), } + impl Default for Grid { + fn default() -> Self { + Self { + life: Life::default(), + interaction: Interaction::default(), + cache: Cache::default(), + translation: Vector::default(), + scaling: 1.0, + } + } + } + impl Grid { pub fn tick(&mut self) { self.life.tick(); @@ -195,6 +207,27 @@ mod grid { .height(Length::Fill) .into() } + + pub fn visible_region(&self, size: Size) -> Rectangle { + let width = size.width / self.scaling; + let height = size.height / self.scaling; + + Rectangle { + x: -self.translation.x - width / 2.0, + y: -self.translation.y - height / 2.0, + width, + height, + } + } + + pub fn project(&self, position: Point, size: Size) -> Point { + let region = self.visible_region(size); + + Point::new( + position.x / self.scaling + region.x, + position.y / self.scaling + region.y, + ) + } } impl<'a> canvas::Program for Grid { @@ -209,7 +242,7 @@ mod grid { } let cursor_position = cursor.position_in(&bounds)?; - let cell = Cell::at(cursor_position - self.translation); + let cell = Cell::at(self.project(cursor_position, bounds.size())); let populate = if self.life.contains(&cell) { None @@ -239,8 +272,9 @@ mod grid { match self.interaction { Interaction::Drawing => populate, Interaction::Panning { translation, start } => { - self.translation = - translation + (cursor_position - start); + self.translation = translation + + (cursor_position - start) + * (1.0 / self.scaling); self.cache.clear(); @@ -249,62 +283,65 @@ mod grid { _ => None, } } + mouse::Event::WheelScrolled { delta } => match delta { + mouse::ScrollDelta::Lines { y, .. } + | mouse::ScrollDelta::Pixels { y, .. } => { + if y > 0.0 && self.scaling < 2.0 + || y < 0.0 && self.scaling > 0.25 + { + self.scaling += y / 30.0; + + self.cache.clear(); + } + + None + } + }, _ => None, }, } } fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Vec { - let cell_size = Size::new(1.0, 1.0); + let center = Vector::new(bounds.width / 2.0, bounds.height / 2.0); let life = self.cache.draw(bounds.size(), |frame| { let background = Path::rectangle(Point::ORIGIN, frame.size()); - frame.fill( - &background, - Color::from_rgb( - 0x40 as f32 / 255.0, - 0x44 as f32 / 255.0, - 0x4B as f32 / 255.0, - ), - ); + frame.fill(&background, Color::from_rgb8(0x40, 0x44, 0x4B)); frame.with_save(|frame| { + frame.translate(center); + frame.scale(self.scaling); frame.translate(self.translation); frame.scale(Cell::SIZE as f32); - let cells = Path::new(|p| { - let region = Rectangle { - x: -self.translation.x, - y: -self.translation.y, - width: frame.width(), - height: frame.height(), - }; - - for cell in Cell::all_visible_in(region) { - if self.life.contains(&cell) { - p.rectangle( - Point::new(cell.j as f32, cell.i as f32), - cell_size, - ); - } - } - }); - frame.fill(&cells, Color::WHITE); + let region = self.visible_region(frame.size()); + + for cell in self.life.visible_in(region) { + frame.fill_rectangle( + Point::new(cell.j as f32, cell.i as f32), + Size::UNIT, + Color::WHITE, + ); + } }); }); let hovered_cell = { let mut frame = Frame::new(bounds.size()); + frame.translate(center); + frame.scale(self.scaling); frame.translate(self.translation); frame.scale(Cell::SIZE as f32); if let Some(cursor_position) = cursor.position_in(&bounds) { - let cell = Cell::at(cursor_position - self.translation); + let cell = + Cell::at(self.project(cursor_position, frame.size())); let interaction = Path::rectangle( Point::new(cell.j as f32, cell.i as f32), - cell_size, + Size::UNIT, ); frame.fill( @@ -344,14 +381,6 @@ mod grid { } impl Life { - fn contains(&self, cell: &Cell) -> bool { - self.cells.contains(cell) - } - - fn populate(&mut self, cell: Cell) { - self.cells.insert(cell); - } - fn tick(&mut self) { let mut adjacent_life = HashMap::new(); @@ -377,6 +406,31 @@ mod grid { } } } + + fn contains(&self, cell: &Cell) -> bool { + self.cells.contains(cell) + } + + fn populate(&mut self, cell: Cell) { + self.cells.insert(cell); + } + + fn visible_in(&self, region: Rectangle) -> impl Iterator { + let first_row = (region.y / Cell::SIZE as f32).floor() as isize; + let first_column = (region.x / Cell::SIZE as f32).floor() as isize; + + let visible_rows = + (region.height / Cell::SIZE as f32).ceil() as isize; + let visible_columns = + (region.width / Cell::SIZE as f32).ceil() as isize; + + let rows = first_row..=first_row + visible_rows; + let columns = first_column..=first_column + visible_columns; + + self.cells.iter().filter(move |cell| { + rows.contains(&cell.i) && columns.contains(&cell.j) + }) + } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -410,23 +464,6 @@ mod grid { fn neighbors(cell: Cell) -> impl Iterator { Cell::cluster(cell).filter(move |candidate| *candidate != cell) } - - fn all_visible_in(region: Rectangle) -> impl Iterator { - use itertools::Itertools; - - let first_row = (region.y / Cell::SIZE as f32).floor() as isize; - let first_column = (region.x / Cell::SIZE as f32).floor() as isize; - - let visible_rows = - (region.height / Cell::SIZE as f32).ceil() as isize; - let visible_columns = - (region.width / Cell::SIZE as f32).ceil() as isize; - - let rows = first_row..=first_row + visible_rows; - let columns = first_column..=first_column + visible_columns; - - rows.cartesian_product(columns).map(|(i, j)| Cell { i, j }) - } } enum Interaction { From f9227546ca975bf3bf7f293d10e79004935b8645 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 1 May 2020 04:41:04 +0200 Subject: [PATCH 55/73] Use `fill_rectangle` for cursor in `game_of_life` --- examples/game_of_life/src/main.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index d9ff5a149a..ef040263af 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -339,13 +339,9 @@ mod grid { let cell = Cell::at(self.project(cursor_position, frame.size())); - let interaction = Path::rectangle( + frame.fill_rectangle( Point::new(cell.j as f32, cell.i as f32), Size::UNIT, - ); - - frame.fill( - &interaction, Color { a: 0.5, ..Color::BLACK From c23995ecb4ddc0c9cc33b0d50404a478b8b5e659 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 1 May 2020 04:48:26 +0200 Subject: [PATCH 56/73] Increase speed limit to `200` in `game_of_life` --- examples/game_of_life/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index ef040263af..8a140ed45e 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -124,7 +124,7 @@ impl Application for GameOfLife { .push( Slider::new( &mut self.speed_slider, - 1.0..=20.0, + 1.0..=200.0, selected_speed as f32, Message::SpeedChanged, ) From 0a5f1bb676f89a26711a8885935ffe94a370c261 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 1 May 2020 05:19:05 +0200 Subject: [PATCH 57/73] Improve zooming logic in `game_of_life` --- examples/game_of_life/src/main.rs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index 8a140ed45e..925003098b 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -178,7 +178,7 @@ mod grid { fn default() -> Self { Self { life: Life::default(), - interaction: Interaction::default(), + interaction: Interaction::None, cache: Cache::default(), translation: Vector::default(), scaling: 1.0, @@ -187,6 +187,9 @@ mod grid { } impl Grid { + const MIN_SCALING: f32 = 0.1; + const MAX_SCALING: f32 = 2.0; + pub fn tick(&mut self) { self.life.tick(); self.cache.clear() @@ -286,10 +289,12 @@ mod grid { mouse::Event::WheelScrolled { delta } => match delta { mouse::ScrollDelta::Lines { y, .. } | mouse::ScrollDelta::Pixels { y, .. } => { - if y > 0.0 && self.scaling < 2.0 - || y < 0.0 && self.scaling > 0.25 + if y < 0.0 && self.scaling > Self::MIN_SCALING + || y > 0.0 && self.scaling < Self::MAX_SCALING { - self.scaling += y / 30.0; + self.scaling = (self.scaling + y / 30.0) + .max(Self::MIN_SCALING) + .min(Self::MAX_SCALING); self.cache.clear(); } @@ -467,10 +472,4 @@ mod grid { Drawing, Panning { translation: Vector, start: Point }, } - - impl Default for Interaction { - fn default() -> Interaction { - Interaction::None - } - } } From ffbe59f8129c80afb3e86eae67efaa5370fbfa8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 1 May 2020 05:42:07 +0200 Subject: [PATCH 58/73] Zoom to cursor in `game_of_life` example --- examples/game_of_life/src/main.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index 925003098b..c818d99fba 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -292,7 +292,21 @@ mod grid { if y < 0.0 && self.scaling > Self::MIN_SCALING || y > 0.0 && self.scaling < Self::MAX_SCALING { - self.scaling = (self.scaling + y / 30.0) + let factor = y / 30.0; + + if let Some(cursor_to_center) = + cursor.position_from(bounds.center()) + { + self.translation = self.translation + - Vector::new( + cursor_to_center.x * factor + / (self.scaling * self.scaling), + cursor_to_center.y * factor + / (self.scaling * self.scaling), + ); + } + + self.scaling = (self.scaling + factor) .max(Self::MIN_SCALING) .min(Self::MAX_SCALING); From 1833c77312ede2e5d47b62df0eea771f6fa559e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 1 May 2020 06:23:05 +0200 Subject: [PATCH 59/73] Improve scrolling smoothness in `game_of_life` --- examples/game_of_life/src/main.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index c818d99fba..44ab4da6da 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -292,24 +292,27 @@ mod grid { if y < 0.0 && self.scaling > Self::MIN_SCALING || y > 0.0 && self.scaling < Self::MAX_SCALING { - let factor = y / 30.0; + let old_scaling = self.scaling; + + self.scaling = (self.scaling + * (1.0 + y / 30.0)) + .max(Self::MIN_SCALING) + .min(Self::MAX_SCALING); if let Some(cursor_to_center) = cursor.position_from(bounds.center()) { + let factor = self.scaling - old_scaling; + self.translation = self.translation - Vector::new( cursor_to_center.x * factor - / (self.scaling * self.scaling), + / (old_scaling * old_scaling), cursor_to_center.y * factor - / (self.scaling * self.scaling), + / (old_scaling * old_scaling), ); } - self.scaling = (self.scaling + factor) - .max(Self::MIN_SCALING) - .min(Self::MAX_SCALING); - self.cache.clear(); } From e7e8e76c28e5bc8eac0c98d6d72c7e49d65468fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 1 May 2020 06:23:30 +0200 Subject: [PATCH 60/73] Change speed limit to `100` in `game_of_life` --- examples/game_of_life/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index 44ab4da6da..17b4090db1 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -124,7 +124,7 @@ impl Application for GameOfLife { .push( Slider::new( &mut self.speed_slider, - 1.0..=200.0, + 1.0..=100.0, selected_speed as f32, Message::SpeedChanged, ) From 4fd8e47737e82817d652d86b306400da663f7a98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 2 May 2020 03:31:31 +0200 Subject: [PATCH 61/73] Use `rustc_hash` for hashing in `game_of_life` This seems to produce a 2x speedup. --- examples/game_of_life/Cargo.toml | 1 + examples/game_of_life/src/main.rs | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/game_of_life/Cargo.toml b/examples/game_of_life/Cargo.toml index b105453787..2b945c4c31 100644 --- a/examples/game_of_life/Cargo.toml +++ b/examples/game_of_life/Cargo.toml @@ -8,3 +8,4 @@ publish = false [dependencies] iced = { path = "../..", features = ["canvas", "tokio", "debug"] } itertools = "0.9" +rustc-hash = "1.1" diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index 17b4090db1..fb4b5b751d 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -159,7 +159,7 @@ mod grid { canvas::{self, Cache, Canvas, Cursor, Event, Frame, Geometry, Path}, mouse, Color, Element, Length, Point, Rectangle, Size, Vector, }; - use std::collections::{HashMap, HashSet}; + use rustc_hash::{FxHashMap, FxHashSet}; pub struct Grid { life: Life, @@ -395,12 +395,12 @@ mod grid { #[derive(Default)] pub struct Life { - cells: HashSet, + cells: FxHashSet, } impl Life { fn tick(&mut self) { - let mut adjacent_life = HashMap::new(); + let mut adjacent_life = FxHashMap::default(); for cell in &self.cells { let _ = adjacent_life.entry(*cell).or_insert(0); From 8fa9e4c94eb9d6b6e13b45fd6a99209536880a2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 2 May 2020 03:37:20 +0200 Subject: [PATCH 62/73] Rename `visible_in` to `within` in `game_of_life` --- examples/game_of_life/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index fb4b5b751d..88fd3a93e5 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -339,7 +339,7 @@ mod grid { let region = self.visible_region(frame.size()); - for cell in self.life.visible_in(region) { + for cell in self.life.within(region) { frame.fill_rectangle( Point::new(cell.j as f32, cell.i as f32), Size::UNIT, @@ -433,7 +433,7 @@ mod grid { self.cells.insert(cell); } - fn visible_in(&self, region: Rectangle) -> impl Iterator { + fn within(&self, region: Rectangle) -> impl Iterator { let first_row = (region.y / Cell::SIZE as f32).floor() as isize; let first_column = (region.x / Cell::SIZE as f32).floor() as isize; From 916a1bfc7049867669b81f446e711021d92a4132 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 2 May 2020 07:01:27 +0200 Subject: [PATCH 63/73] Run ticks in a background thread in `game_of_life` --- examples/game_of_life/Cargo.toml | 1 + examples/game_of_life/src/main.rs | 260 ++++++++++++++++++++++++------ 2 files changed, 210 insertions(+), 51 deletions(-) diff --git a/examples/game_of_life/Cargo.toml b/examples/game_of_life/Cargo.toml index 2b945c4c31..b9bb7f2aa4 100644 --- a/examples/game_of_life/Cargo.toml +++ b/examples/game_of_life/Cargo.toml @@ -7,5 +7,6 @@ publish = false [dependencies] iced = { path = "../..", features = ["canvas", "tokio", "debug"] } +tokio = { version = "0.2", features = ["blocking"] } itertools = "0.9" rustc-hash = "1.1" diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index 88fd3a93e5..b8cabf24ad 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -19,7 +19,7 @@ pub fn main() { #[derive(Default)] struct GameOfLife { grid: Grid, - is_playing: bool, + state: State, speed: u64, next_speed: Option, toggle_button: button::State, @@ -28,6 +28,17 @@ struct GameOfLife { speed_slider: slider::State, } +enum State { + Paused, + Playing { last_tick: Instant }, +} + +impl Default for State { + fn default() -> State { + State::Paused + } +} + #[derive(Debug, Clone)] enum Message { Grid(grid::Message), @@ -62,37 +73,61 @@ impl Application for GameOfLife { Message::Grid(message) => { self.grid.update(message); } - Message::Tick(_) | Message::Next => { - self.grid.tick(); + Message::Tick(_) | Message::Next => match &mut self.state { + State::Paused => { + if let Some(task) = self.grid.tick(1) { + return Command::perform(task, Message::Grid); + } + } + State::Playing { last_tick } => { + let seconds_elapsed = + last_tick.elapsed().as_millis() as f32 / 1000.0; + + let needed_ticks = + (self.speed as f32 * seconds_elapsed).ceil() as usize; + + if let Some(task) = self.grid.tick(needed_ticks) { + *last_tick = Instant::now(); - if let Some(speed) = self.next_speed.take() { - self.speed = speed; + if let Some(speed) = self.next_speed.take() { + self.speed = speed; + } + + return Command::perform(task, Message::Grid); + } } - } + }, Message::Toggle => { - self.is_playing = !self.is_playing; + self.state = match self.state { + State::Paused => State::Playing { + last_tick: Instant::now(), + }, + State::Playing { .. } => State::Paused, + }; } Message::Clear => { - self.grid = Grid::default(); + self.grid.clear(); } - Message::SpeedChanged(speed) => { - if self.is_playing { - self.next_speed = Some(speed.round() as u64); - } else { + Message::SpeedChanged(speed) => match self.state { + State::Paused => { self.speed = speed.round() as u64; } - } + State::Playing { .. } => { + self.next_speed = Some(speed.round() as u64); + } + }, } Command::none() } fn subscription(&self) -> Subscription { - if self.is_playing { - time::every(Duration::from_millis(1000 / self.speed)) - .map(Message::Tick) - } else { - Subscription::none() + match self.state { + State::Paused => Subscription::none(), + State::Playing { .. } => { + time::every(Duration::from_millis(1000 / self.speed)) + .map(Message::Tick) + } } } @@ -102,7 +137,11 @@ impl Application for GameOfLife { .push( Button::new( &mut self.toggle_button, - Text::new(if self.is_playing { "Pause" } else { "Play" }), + Text::new(if let State::Paused = self.state { + "Play" + } else { + "Pause" + }), ) .on_press(Message::Toggle) .style(style::Button), @@ -111,11 +150,6 @@ impl Application for GameOfLife { Button::new(&mut self.next_button, Text::new("Next")) .on_press(Message::Next) .style(style::Button), - ) - .push( - Button::new(&mut self.clear_button, Text::new("Clear")) - .on_press(Message::Clear) - .style(style::Button), ); let selected_speed = self.next_speed.unwrap_or(self.speed); @@ -138,10 +172,14 @@ impl Application for GameOfLife { .padding(10) .spacing(20) .push(playback_controls) - .push(speed_controls); + .push(speed_controls) + .push( + Button::new(&mut self.clear_button, Text::new("Clear")) + .on_press(Message::Clear) + .style(style::Button), + ); let content = Column::new() - .spacing(10) .align_items(Align::Center) .push(self.grid.view().map(Message::Grid)) .push(controls); @@ -160,28 +198,40 @@ mod grid { mouse, Color, Element, Length, Point, Rectangle, Size, Vector, }; use rustc_hash::{FxHashMap, FxHashSet}; + use std::future::Future; pub struct Grid { - life: Life, + state: State, interaction: Interaction, cache: Cache, translation: Vector, scaling: f32, + version: usize, } - #[derive(Debug, Clone, Copy)] + #[derive(Debug, Clone)] pub enum Message { Populate(Cell), + Ticked { + result: Result, + version: usize, + }, + } + + #[derive(Debug, Clone)] + pub enum TickError { + JoinFailed, } impl Default for Grid { fn default() -> Self { Self { - life: Life::default(), + state: State::default(), interaction: Interaction::None, cache: Cache::default(), translation: Vector::default(), scaling: 1.0, + version: 0, } } } @@ -190,17 +240,44 @@ mod grid { const MIN_SCALING: f32 = 0.1; const MAX_SCALING: f32 = 2.0; - pub fn tick(&mut self) { - self.life.tick(); - self.cache.clear() + pub fn tick( + &mut self, + amount: usize, + ) -> Option> { + use iced::futures::FutureExt; + + let version = self.version; + let tick = self.state.tick(amount)?; + + Some(tick.map(move |result| Message::Ticked { result, version })) + } + + pub fn clear(&mut self) { + self.state = State::default(); + self.version += 1; + + self.cache.clear(); } pub fn update(&mut self, message: Message) { match message { Message::Populate(cell) => { - self.life.populate(cell); + self.state.populate(cell); + self.cache.clear() + } + Message::Ticked { + result: Ok(life), + version, + } if version == self.version => { + self.state.update(life); self.cache.clear() } + Message::Ticked { + result: Err(error), .. + } => { + dbg!(error); + } + Message::Ticked { .. } => {} } } @@ -211,11 +288,11 @@ mod grid { .into() } - pub fn visible_region(&self, size: Size) -> Rectangle { + pub fn visible_region(&self, size: Size) -> Region { let width = size.width / self.scaling; let height = size.height / self.scaling; - Rectangle { + Region { x: -self.translation.x - width / 2.0, y: -self.translation.y - height / 2.0, width, @@ -247,7 +324,7 @@ mod grid { let cursor_position = cursor.position_in(&bounds)?; let cell = Cell::at(self.project(cursor_position, bounds.size())); - let populate = if self.life.contains(&cell) { + let populate = if self.state.contains(&cell) { None } else { Some(Message::Populate(cell)) @@ -339,7 +416,7 @@ mod grid { let region = self.visible_region(frame.size()); - for cell in self.life.within(region) { + for cell in region.view(self.state.cells()) { frame.fill_rectangle( Point::new(cell.j as f32, cell.i as f32), Size::UNIT, @@ -394,6 +471,63 @@ mod grid { } #[derive(Default)] + struct State { + life: Life, + births: FxHashSet, + is_ticking: bool, + } + + impl State { + fn contains(&self, cell: &Cell) -> bool { + self.life.contains(cell) || self.births.contains(cell) + } + + fn cells(&self) -> impl Iterator { + self.life.iter().chain(self.births.iter()) + } + + fn populate(&mut self, cell: Cell) { + if self.is_ticking { + self.births.insert(cell); + } else { + self.life.populate(cell); + } + } + + fn update(&mut self, mut life: Life) { + self.births.drain().for_each(|cell| life.populate(cell)); + + self.life = life; + self.is_ticking = false; + } + + fn tick( + &mut self, + amount: usize, + ) -> Option>> { + if self.is_ticking { + return None; + } + + self.is_ticking = true; + + let mut life = self.life.clone(); + + Some(async move { + tokio::task::spawn_blocking(move || { + for _ in 0..amount { + life.tick(); + } + + life + }) + .await + .map_err(|_| TickError::JoinFailed) + }) + } + } + + #[derive(Clone, Default)] pub struct Life { cells: FxHashSet, } @@ -433,21 +567,16 @@ mod grid { self.cells.insert(cell); } - fn within(&self, region: Rectangle) -> impl Iterator { - let first_row = (region.y / Cell::SIZE as f32).floor() as isize; - let first_column = (region.x / Cell::SIZE as f32).floor() as isize; - - let visible_rows = - (region.height / Cell::SIZE as f32).ceil() as isize; - let visible_columns = - (region.width / Cell::SIZE as f32).ceil() as isize; - - let rows = first_row..=first_row + visible_rows; - let columns = first_column..=first_column + visible_columns; + pub fn iter(&self) -> impl Iterator { + self.cells.iter() + } + } - self.cells.iter().filter(move |cell| { - rows.contains(&cell.i) && columns.contains(&cell.j) - }) + impl std::fmt::Debug for Life { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Life") + .field("cells", &self.cells.len()) + .finish() } } @@ -484,6 +613,35 @@ mod grid { } } + pub struct Region { + x: f32, + y: f32, + width: f32, + height: f32, + } + + impl Region { + fn view<'a>( + &self, + cells: impl Iterator, + ) -> impl Iterator { + let first_row = (self.y / Cell::SIZE as f32).floor() as isize; + let first_column = (self.x / Cell::SIZE as f32).floor() as isize; + + let visible_rows = + (self.height / Cell::SIZE as f32).ceil() as isize; + let visible_columns = + (self.width / Cell::SIZE as f32).ceil() as isize; + + let rows = first_row..=first_row + visible_rows; + let columns = first_column..=first_column + visible_columns; + + cells.filter(move |cell| { + rows.contains(&cell.i) && columns.contains(&cell.j) + }) + } + } + enum Interaction { None, Drawing, From 0025b8c3f8f029d6fb7b8b5a599cc6450248aad6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 2 May 2020 09:27:49 +0200 Subject: [PATCH 64/73] Display some statistics in `game_of_life` --- examples/game_of_life/src/main.rs | 104 ++++++++++++++++++++++------- examples/game_of_life/src/style.rs | 38 +++++++++++ 2 files changed, 119 insertions(+), 23 deletions(-) diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index b8cabf24ad..b77d06ea47 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -26,6 +26,8 @@ struct GameOfLife { next_button: button::State, clear_button: button::State, speed_slider: slider::State, + tick_duration: Duration, + tick_amount: usize, } enum State { @@ -71,7 +73,12 @@ impl Application for GameOfLife { fn update(&mut self, message: Message) -> Command { match message { Message::Grid(message) => { - self.grid.update(message); + if let Some((tick_duration, tick_amount)) = + self.grid.update(message) + { + self.tick_duration = tick_duration; + self.tick_amount = tick_amount; + } } Message::Tick(_) | Message::Next => match &mut self.state { State::Paused => { @@ -86,7 +93,7 @@ impl Application for GameOfLife { let needed_ticks = (self.speed as f32 * seconds_elapsed).ceil() as usize; - if let Some(task) = self.grid.tick(needed_ticks) { + if let Some(task) = self.grid.tick(needed_ticks.max(1)) { *last_tick = Instant::now(); if let Some(speed) = self.next_speed.take() { @@ -154,33 +161,49 @@ impl Application for GameOfLife { let selected_speed = self.next_speed.unwrap_or(self.speed); let speed_controls = Row::new() + .width(Length::Fill) + .align_items(Align::Center) .spacing(10) .push( Slider::new( &mut self.speed_slider, - 1.0..=100.0, + 1.0..=1000.0, selected_speed as f32, Message::SpeedChanged, ) - .width(Length::Units(200)) .style(style::Slider), ) - .push(Text::new(format!("x{}", selected_speed)).size(16)) - .align_items(Align::Center); + .push(Text::new(format!("x{}", selected_speed)).size(16)); + + let stats = Column::new() + .width(Length::Units(150)) + .align_items(Align::Center) + .spacing(2) + .push( + Text::new(format!("{} cells", self.grid.cell_count())).size(14), + ) + .push( + Text::new(format!( + "{:?} ({})", + self.tick_duration, self.tick_amount + )) + .size(14), + ); let controls = Row::new() .padding(10) .spacing(20) + .align_items(Align::Center) .push(playback_controls) .push(speed_controls) + .push(stats) .push( Button::new(&mut self.clear_button, Text::new("Clear")) .on_press(Message::Clear) - .style(style::Button), + .style(style::Clear), ); let content = Column::new() - .align_items(Align::Center) .push(self.grid.view().map(Message::Grid)) .push(controls); @@ -199,6 +222,7 @@ mod grid { }; use rustc_hash::{FxHashMap, FxHashSet}; use std::future::Future; + use std::time::{Duration, Instant}; pub struct Grid { state: State, @@ -214,6 +238,8 @@ mod grid { Populate(Cell), Ticked { result: Result, + tick_duration: Duration, + tick_amount: usize, version: usize, }, } @@ -240,16 +266,29 @@ mod grid { const MIN_SCALING: f32 = 0.1; const MAX_SCALING: f32 = 2.0; + pub fn cell_count(&self) -> usize { + self.state.cell_count() + } + pub fn tick( &mut self, amount: usize, ) -> Option> { - use iced::futures::FutureExt; - let version = self.version; let tick = self.state.tick(amount)?; - Some(tick.map(move |result| Message::Ticked { result, version })) + Some(async move { + let start = Instant::now(); + let result = tick.await; + let tick_duration = start.elapsed() / amount as u32; + + Message::Ticked { + result, + version, + tick_duration, + tick_amount: amount, + } + }) } pub fn clear(&mut self) { @@ -259,25 +298,36 @@ mod grid { self.cache.clear(); } - pub fn update(&mut self, message: Message) { + pub fn update( + &mut self, + message: Message, + ) -> Option<(Duration, usize)> { match message { Message::Populate(cell) => { self.state.populate(cell); - self.cache.clear() + self.cache.clear(); + + None } Message::Ticked { result: Ok(life), version, + tick_duration, + tick_amount, } if version == self.version => { self.state.update(life); - self.cache.clear() + self.cache.clear(); + + Some((tick_duration, tick_amount)) } Message::Ticked { result: Err(error), .. } => { dbg!(error); + + None } - Message::Ticked { .. } => {} + Message::Ticked { .. } => None, } } @@ -478,6 +528,10 @@ mod grid { } impl State { + fn cell_count(&self) -> usize { + self.life.len() + self.births.len() + } + fn contains(&self, cell: &Cell) -> bool { self.life.contains(cell) || self.births.contains(cell) } @@ -533,6 +587,18 @@ mod grid { } impl Life { + fn len(&self) -> usize { + self.cells.len() + } + + fn contains(&self, cell: &Cell) -> bool { + self.cells.contains(cell) + } + + fn populate(&mut self, cell: Cell) { + self.cells.insert(cell); + } + fn tick(&mut self) { let mut adjacent_life = FxHashMap::default(); @@ -559,14 +625,6 @@ mod grid { } } - fn contains(&self, cell: &Cell) -> bool { - self.cells.contains(cell) - } - - fn populate(&mut self, cell: Cell) { - self.cells.insert(cell); - } - pub fn iter(&self) -> impl Iterator { self.cells.iter() } diff --git a/examples/game_of_life/src/style.rs b/examples/game_of_life/src/style.rs index 0becb5bef9..d59569f20c 100644 --- a/examples/game_of_life/src/style.rs +++ b/examples/game_of_life/src/style.rs @@ -6,6 +6,12 @@ const ACTIVE: Color = Color::from_rgb( 0xDA as f32 / 255.0, ); +const DESTRUCTIVE: Color = Color::from_rgb( + 0xC0 as f32 / 255.0, + 0x47 as f32 / 255.0, + 0x47 as f32 / 255.0, +); + const HOVERED: Color = Color::from_rgb( 0x67 as f32 / 255.0, 0x7B as f32 / 255.0, @@ -55,6 +61,38 @@ impl button::StyleSheet for Button { } } +pub struct Clear; + +impl button::StyleSheet for Clear { + fn active(&self) -> button::Style { + button::Style { + background: Some(Background::Color(DESTRUCTIVE)), + border_radius: 3, + text_color: Color::WHITE, + ..button::Style::default() + } + } + + fn hovered(&self) -> button::Style { + button::Style { + background: Some(Background::Color(Color { + a: 0.5, + ..DESTRUCTIVE + })), + text_color: Color::WHITE, + ..self.active() + } + } + + fn pressed(&self) -> button::Style { + button::Style { + border_width: 1, + border_color: Color::WHITE, + ..self.hovered() + } + } +} + pub struct Slider; impl slider::StyleSheet for Slider { From cc8f5b6fc82e253466f7fab3a9285b0b7531f189 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 2 May 2020 10:48:42 +0200 Subject: [PATCH 65/73] Simplify logic and limit ticks in `game_of_life` --- examples/game_of_life/src/main.rs | 106 ++++++++++-------------------- 1 file changed, 33 insertions(+), 73 deletions(-) diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index b77d06ea47..1b9bad445c 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -19,26 +19,16 @@ pub fn main() { #[derive(Default)] struct GameOfLife { grid: Grid, - state: State, - speed: u64, - next_speed: Option, + is_playing: bool, + speed: usize, + next_speed: Option, toggle_button: button::State, next_button: button::State, clear_button: button::State, speed_slider: slider::State, tick_duration: Duration, - tick_amount: usize, -} - -enum State { - Paused, - Playing { last_tick: Instant }, -} - -impl Default for State { - fn default() -> State { - State::Paused - } + queued_ticks: usize, + last_ticks: usize, } #[derive(Debug, Clone)] @@ -73,68 +63,48 @@ impl Application for GameOfLife { fn update(&mut self, message: Message) -> Command { match message { Message::Grid(message) => { - if let Some((tick_duration, tick_amount)) = - self.grid.update(message) - { + if let Some(tick_duration) = self.grid.update(message) { self.tick_duration = tick_duration; - self.tick_amount = tick_amount; } } - Message::Tick(_) | Message::Next => match &mut self.state { - State::Paused => { - if let Some(task) = self.grid.tick(1) { - return Command::perform(task, Message::Grid); - } - } - State::Playing { last_tick } => { - let seconds_elapsed = - last_tick.elapsed().as_millis() as f32 / 1000.0; - - let needed_ticks = - (self.speed as f32 * seconds_elapsed).ceil() as usize; - - if let Some(task) = self.grid.tick(needed_ticks.max(1)) { - *last_tick = Instant::now(); - - if let Some(speed) = self.next_speed.take() { - self.speed = speed; - } + Message::Tick(_) | Message::Next => { + if let Some(task) = self.grid.tick(self.queued_ticks + 1) { + self.last_ticks = self.queued_ticks; + self.queued_ticks = 0; - return Command::perform(task, Message::Grid); + if let Some(speed) = self.next_speed.take() { + self.speed = speed; } + + return Command::perform(task, Message::Grid); + } else { + self.queued_ticks = (self.queued_ticks + 1).min(self.speed); } - }, + } Message::Toggle => { - self.state = match self.state { - State::Paused => State::Playing { - last_tick: Instant::now(), - }, - State::Playing { .. } => State::Paused, - }; + self.is_playing = !self.is_playing; } Message::Clear => { self.grid.clear(); } - Message::SpeedChanged(speed) => match self.state { - State::Paused => { - self.speed = speed.round() as u64; + Message::SpeedChanged(speed) => { + if self.is_playing { + self.next_speed = Some(speed.round() as usize); + } else { + self.speed = speed.round() as usize; } - State::Playing { .. } => { - self.next_speed = Some(speed.round() as u64); - } - }, + } } Command::none() } fn subscription(&self) -> Subscription { - match self.state { - State::Paused => Subscription::none(), - State::Playing { .. } => { - time::every(Duration::from_millis(1000 / self.speed)) - .map(Message::Tick) - } + if self.is_playing { + time::every(Duration::from_millis(1000 / self.speed as u64)) + .map(Message::Tick) + } else { + Subscription::none() } } @@ -144,11 +114,7 @@ impl Application for GameOfLife { .push( Button::new( &mut self.toggle_button, - Text::new(if let State::Paused = self.state { - "Play" - } else { - "Pause" - }), + Text::new(if self.is_playing { "Pause" } else { "Play" }), ) .on_press(Message::Toggle) .style(style::Button), @@ -185,7 +151,7 @@ impl Application for GameOfLife { .push( Text::new(format!( "{:?} ({})", - self.tick_duration, self.tick_amount + self.tick_duration, self.last_ticks )) .size(14), ); @@ -239,7 +205,6 @@ mod grid { Ticked { result: Result, tick_duration: Duration, - tick_amount: usize, version: usize, }, } @@ -286,7 +251,6 @@ mod grid { result, version, tick_duration, - tick_amount: amount, } }) } @@ -298,10 +262,7 @@ mod grid { self.cache.clear(); } - pub fn update( - &mut self, - message: Message, - ) -> Option<(Duration, usize)> { + pub fn update(&mut self, message: Message) -> Option { match message { Message::Populate(cell) => { self.state.populate(cell); @@ -313,12 +274,11 @@ mod grid { result: Ok(life), version, tick_duration, - tick_amount, } if version == self.version => { self.state.update(life); self.cache.clear(); - Some((tick_duration, tick_amount)) + Some(tick_duration) } Message::Ticked { result: Err(error), .. From a43fb42428cbcef3d80e0ec21ec92c6db506353d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 3 May 2020 00:08:41 +0200 Subject: [PATCH 66/73] Reorganize `view` code in `game_of_life` --- examples/game_of_life/src/main.rs | 173 ++++++++++++++++++------------ 1 file changed, 102 insertions(+), 71 deletions(-) diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index 1b9bad445c..0e66c23728 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -19,16 +19,12 @@ pub fn main() { #[derive(Default)] struct GameOfLife { grid: Grid, + controls: Controls, + statistics: Statistics, is_playing: bool, + queued_ticks: usize, speed: usize, next_speed: Option, - toggle_button: button::State, - next_button: button::State, - clear_button: button::State, - speed_slider: slider::State, - tick_duration: Duration, - queued_ticks: usize, - last_ticks: usize, } #[derive(Debug, Clone)] @@ -64,21 +60,21 @@ impl Application for GameOfLife { match message { Message::Grid(message) => { if let Some(tick_duration) = self.grid.update(message) { - self.tick_duration = tick_duration; + self.statistics.tick_duration = tick_duration; } } Message::Tick(_) | Message::Next => { - if let Some(task) = self.grid.tick(self.queued_ticks + 1) { - self.last_ticks = self.queued_ticks; - self.queued_ticks = 0; + self.queued_ticks = (self.queued_ticks + 1).min(self.speed); + if let Some(task) = self.grid.tick(self.queued_ticks) { if let Some(speed) = self.next_speed.take() { self.speed = speed; } + self.statistics.last_queued_ticks = self.queued_ticks; + self.queued_ticks = 0; + return Command::perform(task, Message::Grid); - } else { - self.queued_ticks = (self.queued_ticks + 1).min(self.speed); } } Message::Toggle => { @@ -109,65 +105,13 @@ impl Application for GameOfLife { } fn view(&mut self) -> Element { - let playback_controls = Row::new() - .spacing(10) - .push( - Button::new( - &mut self.toggle_button, - Text::new(if self.is_playing { "Pause" } else { "Play" }), - ) - .on_press(Message::Toggle) - .style(style::Button), - ) - .push( - Button::new(&mut self.next_button, Text::new("Next")) - .on_press(Message::Next) - .style(style::Button), - ); - let selected_speed = self.next_speed.unwrap_or(self.speed); - let speed_controls = Row::new() - .width(Length::Fill) - .align_items(Align::Center) - .spacing(10) - .push( - Slider::new( - &mut self.speed_slider, - 1.0..=1000.0, - selected_speed as f32, - Message::SpeedChanged, - ) - .style(style::Slider), - ) - .push(Text::new(format!("x{}", selected_speed)).size(16)); - - let stats = Column::new() - .width(Length::Units(150)) - .align_items(Align::Center) - .spacing(2) - .push( - Text::new(format!("{} cells", self.grid.cell_count())).size(14), - ) - .push( - Text::new(format!( - "{:?} ({})", - self.tick_duration, self.last_ticks - )) - .size(14), - ); - - let controls = Row::new() - .padding(10) - .spacing(20) - .align_items(Align::Center) - .push(playback_controls) - .push(speed_controls) - .push(stats) - .push( - Button::new(&mut self.clear_button, Text::new("Clear")) - .on_press(Message::Clear) - .style(style::Clear), - ); + let controls = self.controls.view( + &self.grid, + &self.statistics, + self.is_playing, + selected_speed, + ); let content = Column::new() .push(self.grid.view().map(Message::Grid)) @@ -666,3 +610,90 @@ mod grid { Panning { translation: Vector, start: Point }, } } + +#[derive(Default)] +struct Controls { + toggle_button: button::State, + next_button: button::State, + clear_button: button::State, + speed_slider: slider::State, +} + +impl Controls { + fn view<'a>( + &'a mut self, + grid: &Grid, + statistics: &'a Statistics, + is_playing: bool, + speed: usize, + ) -> Element<'a, Message> { + let playback_controls = Row::new() + .spacing(10) + .push( + Button::new( + &mut self.toggle_button, + Text::new(if is_playing { "Pause" } else { "Play" }), + ) + .on_press(Message::Toggle) + .style(style::Button), + ) + .push( + Button::new(&mut self.next_button, Text::new("Next")) + .on_press(Message::Next) + .style(style::Button), + ); + + let speed_controls = Row::new() + .width(Length::Fill) + .align_items(Align::Center) + .spacing(10) + .push( + Slider::new( + &mut self.speed_slider, + 1.0..=1000.0, + speed as f32, + Message::SpeedChanged, + ) + .style(style::Slider), + ) + .push(Text::new(format!("x{}", speed)).size(16)); + + Row::new() + .padding(10) + .spacing(20) + .align_items(Align::Center) + .push(playback_controls) + .push(speed_controls) + .push(statistics.view(grid)) + .push( + Button::new(&mut self.clear_button, Text::new("Clear")) + .on_press(Message::Clear) + .style(style::Clear), + ) + .into() + } +} + +#[derive(Default)] +struct Statistics { + tick_duration: Duration, + last_queued_ticks: usize, +} + +impl Statistics { + fn view(&self, grid: &Grid) -> Element { + Column::new() + .width(Length::Units(150)) + .align_items(Align::Center) + .spacing(2) + .push(Text::new(format!("{} cells", grid.cell_count())).size(14)) + .push( + Text::new(format!( + "{:?} ({})", + self.tick_duration, self.last_queued_ticks + )) + .size(14), + ) + .into() + } +} From c3c5161386cb527bf6d0fe34e5f4103392733599 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 3 May 2020 00:57:15 +0200 Subject: [PATCH 67/73] Draw grid in `game_of_life` --- examples/game_of_life/src/main.rs | 91 +++++++++++++++++++++++++------ 1 file changed, 73 insertions(+), 18 deletions(-) diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index 0e66c23728..52b046964d 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -13,7 +13,10 @@ use iced::{ use std::time::{Duration, Instant}; pub fn main() { - GameOfLife::run(Settings::default()) + GameOfLife::run(Settings { + antialiasing: true, + ..Settings::default() + }) } #[derive(Default)] @@ -132,12 +135,14 @@ mod grid { }; use rustc_hash::{FxHashMap, FxHashSet}; use std::future::Future; + use std::ops::RangeInclusive; use std::time::{Duration, Instant}; pub struct Grid { state: State, interaction: Interaction, - cache: Cache, + life_cache: Cache, + grid_cache: Cache, translation: Vector, scaling: f32, version: usize, @@ -163,7 +168,8 @@ mod grid { Self { state: State::default(), interaction: Interaction::None, - cache: Cache::default(), + life_cache: Cache::default(), + grid_cache: Cache::default(), translation: Vector::default(), scaling: 1.0, version: 0, @@ -203,14 +209,14 @@ mod grid { self.state = State::default(); self.version += 1; - self.cache.clear(); + self.life_cache.clear(); } pub fn update(&mut self, message: Message) -> Option { match message { Message::Populate(cell) => { self.state.populate(cell); - self.cache.clear(); + self.life_cache.clear(); None } @@ -220,7 +226,7 @@ mod grid { tick_duration, } if version == self.version => { self.state.update(life); - self.cache.clear(); + self.life_cache.clear(); Some(tick_duration) } @@ -310,7 +316,8 @@ mod grid { + (cursor_position - start) * (1.0 / self.scaling); - self.cache.clear(); + self.life_cache.clear(); + self.grid_cache.clear(); None } @@ -344,7 +351,8 @@ mod grid { ); } - self.cache.clear(); + self.life_cache.clear(); + self.grid_cache.clear(); } None @@ -358,7 +366,7 @@ mod grid { fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Vec { let center = Vector::new(bounds.width / 2.0, bounds.height / 2.0); - let life = self.cache.draw(bounds.size(), |frame| { + let life = self.life_cache.draw(bounds.size(), |frame| { let background = Path::rectangle(Point::ORIGIN, frame.size()); frame.fill(&background, Color::from_rgb8(0x40, 0x44, 0x4B)); @@ -370,7 +378,7 @@ mod grid { let region = self.visible_region(frame.size()); - for cell in region.view(self.state.cells()) { + for cell in region.cull(self.state.cells()) { frame.fill_rectangle( Point::new(cell.j as f32, cell.i as f32), Size::UNIT, @@ -405,7 +413,44 @@ mod grid { frame.into_geometry() }; - vec![life, hovered_cell] + if self.scaling < 0.2 { + vec![life, hovered_cell] + } else { + let grid = self.grid_cache.draw(bounds.size(), |frame| { + frame.translate(center); + frame.scale(self.scaling); + frame.translate(self.translation); + frame.scale(Cell::SIZE as f32); + + let region = self.visible_region(frame.size()); + let rows = region.rows(); + let columns = region.columns(); + let (total_rows, total_columns) = + (rows.clone().count(), columns.clone().count()); + let width = 2.0 / Cell::SIZE as f32; + let color = Color::from_rgb8(70, 74, 83); + + frame.translate(Vector::new(-width / 2.0, -width / 2.0)); + + for row in region.rows() { + frame.fill_rectangle( + Point::new(*columns.start() as f32, row as f32), + Size::new(total_columns as f32, width), + color, + ); + } + + for column in region.columns() { + frame.fill_rectangle( + Point::new(column as f32, *rows.start() as f32), + Size::new(width, total_rows as f32), + color, + ); + } + }); + + vec![life, grid, hovered_cell] + } } fn mouse_interaction( @@ -583,20 +628,30 @@ mod grid { } impl Region { - fn view<'a>( - &self, - cells: impl Iterator, - ) -> impl Iterator { + fn rows(&self) -> RangeInclusive { let first_row = (self.y / Cell::SIZE as f32).floor() as isize; - let first_column = (self.x / Cell::SIZE as f32).floor() as isize; let visible_rows = (self.height / Cell::SIZE as f32).ceil() as isize; + + first_row..=first_row + visible_rows + } + + fn columns(&self) -> RangeInclusive { + let first_column = (self.x / Cell::SIZE as f32).floor() as isize; + let visible_columns = (self.width / Cell::SIZE as f32).ceil() as isize; - let rows = first_row..=first_row + visible_rows; - let columns = first_column..=first_column + visible_columns; + first_column..=first_column + visible_columns + } + + fn cull<'a>( + &self, + cells: impl Iterator, + ) -> impl Iterator { + let rows = self.rows(); + let columns = self.columns(); cells.filter(move |cell| { rows.contains(&cell.i) && columns.contains(&cell.j) From 5aaaea7c8824fb65bac35307cdf760c57f2bf5df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 3 May 2020 01:53:45 +0200 Subject: [PATCH 68/73] Render stats as an overlay in `game_of_life` Also allow toggling the grid lines --- examples/game_of_life/src/main.rs | 185 +++++++++++++++++------------- 1 file changed, 103 insertions(+), 82 deletions(-) diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index 52b046964d..018ebc5092 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -7,8 +7,8 @@ use iced::{ button::{self, Button}, executor, slider::{self, Slider}, - time, Align, Application, Column, Command, Container, Element, Length, Row, - Settings, Subscription, Text, + time, Align, Application, Checkbox, Column, Command, Container, Element, + Length, Row, Settings, Subscription, Text, }; use std::time::{Duration, Instant}; @@ -23,7 +23,6 @@ pub fn main() { struct GameOfLife { grid: Grid, controls: Controls, - statistics: Statistics, is_playing: bool, queued_ticks: usize, speed: usize, @@ -34,7 +33,8 @@ struct GameOfLife { enum Message { Grid(grid::Message), Tick(Instant), - Toggle, + TogglePlayback, + ToggleGrid(bool), Next, Clear, SpeedChanged(f32), @@ -62,9 +62,7 @@ impl Application for GameOfLife { fn update(&mut self, message: Message) -> Command { match message { Message::Grid(message) => { - if let Some(tick_duration) = self.grid.update(message) { - self.statistics.tick_duration = tick_duration; - } + self.grid.update(message); } Message::Tick(_) | Message::Next => { self.queued_ticks = (self.queued_ticks + 1).min(self.speed); @@ -74,15 +72,17 @@ impl Application for GameOfLife { self.speed = speed; } - self.statistics.last_queued_ticks = self.queued_ticks; self.queued_ticks = 0; return Command::perform(task, Message::Grid); } } - Message::Toggle => { + Message::TogglePlayback => { self.is_playing = !self.is_playing; } + Message::ToggleGrid(show_grid_lines) => { + self.grid.toggle_lines(show_grid_lines); + } Message::Clear => { self.grid.clear(); } @@ -110,9 +110,8 @@ impl Application for GameOfLife { fn view(&mut self) -> Element { let selected_speed = self.next_speed.unwrap_or(self.speed); let controls = self.controls.view( - &self.grid, - &self.statistics, self.is_playing, + self.grid.are_lines_visible(), selected_speed, ); @@ -130,8 +129,11 @@ impl Application for GameOfLife { mod grid { use iced::{ - canvas::{self, Cache, Canvas, Cursor, Event, Frame, Geometry, Path}, - mouse, Color, Element, Length, Point, Rectangle, Size, Vector, + canvas::{ + self, Cache, Canvas, Cursor, Event, Frame, Geometry, Path, Text, + }, + mouse, Color, Element, HorizontalAlignment, Length, Point, Rectangle, + Size, Vector, VerticalAlignment, }; use rustc_hash::{FxHashMap, FxHashSet}; use std::future::Future; @@ -145,6 +147,9 @@ mod grid { grid_cache: Cache, translation: Vector, scaling: f32, + show_lines: bool, + last_tick_duration: Duration, + last_queued_ticks: usize, version: usize, } @@ -172,6 +177,9 @@ mod grid { grid_cache: Cache::default(), translation: Vector::default(), scaling: 1.0, + show_lines: true, + last_tick_duration: Duration::default(), + last_queued_ticks: 0, version: 0, } } @@ -181,10 +189,6 @@ mod grid { const MIN_SCALING: f32 = 0.1; const MAX_SCALING: f32 = 2.0; - pub fn cell_count(&self) -> usize { - self.state.cell_count() - } - pub fn tick( &mut self, amount: usize, @@ -192,6 +196,8 @@ mod grid { let version = self.version; let tick = self.state.tick(amount)?; + self.last_queued_ticks = amount; + Some(async move { let start = Instant::now(); let result = tick.await; @@ -205,20 +211,11 @@ mod grid { }) } - pub fn clear(&mut self) { - self.state = State::default(); - self.version += 1; - - self.life_cache.clear(); - } - - pub fn update(&mut self, message: Message) -> Option { + pub fn update(&mut self, message: Message) { match message { Message::Populate(cell) => { self.state.populate(cell); self.life_cache.clear(); - - None } Message::Ticked { result: Ok(life), @@ -228,16 +225,14 @@ mod grid { self.state.update(life); self.life_cache.clear(); - Some(tick_duration) + self.last_tick_duration = tick_duration; } Message::Ticked { result: Err(error), .. } => { dbg!(error); - - None } - Message::Ticked { .. } => None, + Message::Ticked { .. } => {} } } @@ -248,7 +243,22 @@ mod grid { .into() } - pub fn visible_region(&self, size: Size) -> Region { + pub fn clear(&mut self) { + self.state = State::default(); + self.version += 1; + + self.life_cache.clear(); + } + + pub fn toggle_lines(&mut self, enabled: bool) { + self.show_lines = enabled; + } + + pub fn are_lines_visible(&self) -> bool { + self.show_lines + } + + fn visible_region(&self, size: Size) -> Region { let width = size.width / self.scaling; let height = size.height / self.scaling; @@ -260,7 +270,7 @@ mod grid { } } - pub fn project(&self, position: Point, size: Size) -> Point { + fn project(&self, position: Point, size: Size) -> Point { let region = self.visible_region(size); Point::new( @@ -388,33 +398,64 @@ mod grid { }); }); - let hovered_cell = { + let overlay = { let mut frame = Frame::new(bounds.size()); - frame.translate(center); - frame.scale(self.scaling); - frame.translate(self.translation); - frame.scale(Cell::SIZE as f32); - - if let Some(cursor_position) = cursor.position_in(&bounds) { - let cell = - Cell::at(self.project(cursor_position, frame.size())); - - frame.fill_rectangle( - Point::new(cell.j as f32, cell.i as f32), - Size::UNIT, - Color { - a: 0.5, - ..Color::BLACK - }, - ); + let hovered_cell = + cursor.position_in(&bounds).map(|position| { + Cell::at(self.project(position, frame.size())) + }); + + if let Some(cell) = hovered_cell { + frame.with_save(|frame| { + frame.translate(center); + frame.scale(self.scaling); + frame.translate(self.translation); + frame.scale(Cell::SIZE as f32); + + frame.fill_rectangle( + Point::new(cell.j as f32, cell.i as f32), + Size::UNIT, + Color { + a: 0.5, + ..Color::BLACK + }, + ); + }); + } + + let text = Text { + color: Color::WHITE, + size: 14.0, + position: Point::new(frame.width(), frame.height()), + horizontal_alignment: HorizontalAlignment::Right, + vertical_alignment: VerticalAlignment::Bottom, + ..Text::default() + }; + + if let Some(cell) = hovered_cell { + frame.fill_text(Text { + content: format!("({}, {})", cell.i, cell.j), + position: text.position - Vector::new(0.0, 16.0), + ..text + }); } + frame.fill_text(Text { + content: format!( + "{} cells @ {:?} ({})", + self.state.cell_count(), + self.last_tick_duration, + self.last_queued_ticks + ), + ..text + }); + frame.into_geometry() }; - if self.scaling < 0.2 { - vec![life, hovered_cell] + if self.scaling < 0.2 || !self.show_lines { + vec![life, overlay] } else { let grid = self.grid_cache.draw(bounds.size(), |frame| { frame.translate(center); @@ -449,7 +490,7 @@ mod grid { } }); - vec![life, grid, hovered_cell] + vec![life, grid, overlay] } } @@ -677,9 +718,8 @@ struct Controls { impl Controls { fn view<'a>( &'a mut self, - grid: &Grid, - statistics: &'a Statistics, is_playing: bool, + is_grid_enabled: bool, speed: usize, ) -> Element<'a, Message> { let playback_controls = Row::new() @@ -689,7 +729,7 @@ impl Controls { &mut self.toggle_button, Text::new(if is_playing { "Pause" } else { "Play" }), ) - .on_press(Message::Toggle) + .on_press(Message::TogglePlayback) .style(style::Button), ) .push( @@ -719,7 +759,12 @@ impl Controls { .align_items(Align::Center) .push(playback_controls) .push(speed_controls) - .push(statistics.view(grid)) + .push( + Checkbox::new(is_grid_enabled, "Grid", Message::ToggleGrid) + .size(16) + .spacing(5) + .text_size(16), + ) .push( Button::new(&mut self.clear_button, Text::new("Clear")) .on_press(Message::Clear) @@ -728,27 +773,3 @@ impl Controls { .into() } } - -#[derive(Default)] -struct Statistics { - tick_duration: Duration, - last_queued_ticks: usize, -} - -impl Statistics { - fn view(&self, grid: &Grid) -> Element { - Column::new() - .width(Length::Units(150)) - .align_items(Align::Center) - .spacing(2) - .push(Text::new(format!("{} cells", grid.cell_count())).size(14)) - .push( - Text::new(format!( - "{:?} ({})", - self.tick_duration, self.last_queued_ticks - )) - .size(14), - ) - .into() - } -} From 4417a34edb7d002276f0419a5f62c6eee4a3af87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 3 May 2020 02:15:11 +0200 Subject: [PATCH 69/73] Fix "1 cells" overlay in `game_of_life` --- examples/game_of_life/src/main.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index 018ebc5092..c2f80dfcd4 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -435,16 +435,19 @@ mod grid { if let Some(cell) = hovered_cell { frame.fill_text(Text { - content: format!("({}, {})", cell.i, cell.j), + content: format!("({}, {})", cell.j, cell.i), position: text.position - Vector::new(0.0, 16.0), ..text }); } + let cell_count = self.state.cell_count(); + frame.fill_text(Text { content: format!( - "{} cells @ {:?} ({})", - self.state.cell_count(), + "{} cell{} @ {:?} ({})", + cell_count, + if cell_count == 1 { "" } else { "s" }, self.last_tick_duration, self.last_queued_ticks ), From 917199197f9719bbb3f6f98c63985cb64dfd147c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 3 May 2020 02:43:20 +0200 Subject: [PATCH 70/73] Allow erasing cells in `game_of_life` --- examples/game_of_life/src/main.rs | 35 ++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index c2f80dfcd4..080d55c088 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -156,6 +156,7 @@ mod grid { #[derive(Debug, Clone)] pub enum Message { Populate(Cell), + Unpopulate(Cell), Ticked { result: Result, tick_duration: Duration, @@ -217,6 +218,10 @@ mod grid { self.state.populate(cell); self.life_cache.clear(); } + Message::Unpopulate(cell) => { + self.state.unpopulate(&cell); + self.life_cache.clear(); + } Message::Ticked { result: Ok(life), version, @@ -293,20 +298,25 @@ mod grid { let cursor_position = cursor.position_in(&bounds)?; let cell = Cell::at(self.project(cursor_position, bounds.size())); + let is_populated = self.state.contains(&cell); - let populate = if self.state.contains(&cell) { - None + let (populate, unpopulate) = if is_populated { + (None, Some(Message::Unpopulate(cell))) } else { - Some(Message::Populate(cell)) + (Some(Message::Populate(cell)), None) }; match event { Event::Mouse(mouse_event) => match mouse_event { mouse::Event::ButtonPressed(button) => match button { mouse::Button::Left => { - self.interaction = Interaction::Drawing; + self.interaction = if is_populated { + Interaction::Erasing + } else { + Interaction::Drawing + }; - populate + populate.or(unpopulate) } mouse::Button::Right => { self.interaction = Interaction::Panning { @@ -321,6 +331,7 @@ mod grid { mouse::Event::CursorMoved { .. } => { match self.interaction { Interaction::Drawing => populate, + Interaction::Erasing => unpopulate, Interaction::Panning { translation, start } => { self.translation = translation + (cursor_position - start) @@ -504,6 +515,7 @@ mod grid { ) -> mouse::Interaction { match self.interaction { Interaction::Drawing => mouse::Interaction::Crosshair, + Interaction::Erasing => mouse::Interaction::Crosshair, Interaction::Panning { .. } => mouse::Interaction::Grabbing, Interaction::None if cursor.is_over(&bounds) => { mouse::Interaction::Crosshair @@ -541,6 +553,14 @@ mod grid { } } + fn unpopulate(&mut self, cell: &Cell) { + if self.is_ticking { + let _ = self.births.remove(cell); + } else { + self.life.unpopulate(cell); + } + } + fn update(&mut self, mut life: Life) { self.births.drain().for_each(|cell| life.populate(cell)); @@ -592,6 +612,10 @@ mod grid { self.cells.insert(cell); } + fn unpopulate(&mut self, cell: &Cell) { + let _ = self.cells.remove(cell); + } + fn tick(&mut self) { let mut adjacent_life = FxHashMap::default(); @@ -706,6 +730,7 @@ mod grid { enum Interaction { None, Drawing, + Erasing, Panning { translation: Vector, start: Point }, } } From 2f41ccee1c7b52f872d68d2e5ebd68ea49a1559b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 3 May 2020 02:49:04 +0200 Subject: [PATCH 71/73] Update GIF of `game_of_life` example --- examples/game_of_life/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/game_of_life/README.md b/examples/game_of_life/README.md index bb1b9736df..5741cbf665 100644 --- a/examples/game_of_life/README.md +++ b/examples/game_of_life/README.md @@ -6,8 +6,8 @@ on top of a `Canvas` widget and other controls. The __[`main`]__ file contains the relevant code of the example. From 80e2d1b08bd8e6d9a049367d49c1508ce8ffe2a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 4 May 2020 23:46:15 +0200 Subject: [PATCH 72/73] Adapt `color_palette` to new `canvas` API --- examples/color_palette/src/main.rs | 48 +++++++++++++++++------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs index 073a673496..cec6ac7937 100644 --- a/examples/color_palette/src/main.rs +++ b/examples/color_palette/src/main.rs @@ -1,9 +1,10 @@ +use iced::canvas::{self, Cursor, Frame, Geometry, Path}; use iced::{ - canvas, slider, Align, Canvas, Color, Column, Element, HorizontalAlignment, - Length, Point, Row, Sandbox, Settings, Size, Slider, Text, Vector, + slider, Align, Canvas, Color, Column, Element, HorizontalAlignment, Length, + Point, Rectangle, Row, Sandbox, Settings, Size, Slider, Text, Vector, VerticalAlignment, }; -use palette::{self, Limited}; +use palette::{self, Hsl, Limited, Srgb}; use std::marker::PhantomData; use std::ops::RangeInclusive; @@ -23,7 +24,6 @@ pub struct ColorPalette { hwb: ColorPicker, lab: ColorPicker, lch: ColorPicker, - canvas_layer: canvas::layer::Cache, } #[derive(Debug, Clone, Copy)] @@ -58,7 +58,6 @@ impl Sandbox for ColorPalette { }; self.theme = Theme::new(srgb.clamp()); - self.canvas_layer.clear(); } fn view(&mut self) -> Element { @@ -80,12 +79,7 @@ impl Sandbox for ColorPalette { .push(self.hwb.view(hwb).map(Message::HwbColorChanged)) .push(self.lab.view(lab).map(Message::LabColorChanged)) .push(self.lch.view(lch).map(Message::LchColorChanged)) - .push( - Canvas::new() - .width(Length::Fill) - .height(Length::Fill) - .push(self.canvas_layer.with(&self.theme)), - ) + .push(self.theme.view()) .into() } } @@ -95,11 +89,12 @@ pub struct Theme { lower: Vec, base: Color, higher: Vec, + canvas_cache: canvas::Cache, } impl Theme { pub fn new(base: impl Into) -> Theme { - use palette::{Hsl, Hue, Shade, Srgb}; + use palette::{Hue, Shade}; let base = base.into(); @@ -130,6 +125,7 @@ impl Theme { .iter() .map(|&color| Srgb::from(color).clamp().into()) .collect(), + canvas_cache: canvas::Cache::default(), } } @@ -143,13 +139,15 @@ impl Theme { .chain(std::iter::once(&self.base)) .chain(self.higher.iter()) } -} -impl canvas::Drawable for Theme { - fn draw(&self, frame: &mut canvas::Frame) { - use canvas::Path; - use palette::{Hsl, Srgb}; + pub fn view(&mut self) -> Element { + Canvas::new(self) + .width(Length::Fill) + .height(Length::Fill) + .into() + } + fn draw(&self, frame: &mut Frame) { let pad = 20.0; let box_size = Size { @@ -176,8 +174,7 @@ impl canvas::Drawable for Theme { x: (i as f32) * box_size.width, y: 0.0, }; - let rect = Path::rectangle(anchor, box_size); - frame.fill(&rect, color); + frame.fill_rectangle(anchor, box_size, color); // We show a little indicator for the base color if color == self.base { @@ -225,8 +222,7 @@ impl canvas::Drawable for Theme { y: box_size.height + 2.0 * pad, }; - let rect = Path::rectangle(anchor, box_size); - frame.fill(&rect, color); + frame.fill_rectangle(anchor, box_size, color); frame.fill_text(canvas::Text { content: color_hex_string(&color), @@ -240,6 +236,16 @@ impl canvas::Drawable for Theme { } } +impl canvas::Program for Theme { + fn draw(&self, bounds: Rectangle, _cursor: Cursor) -> Vec { + let theme = self.canvas_cache.draw(bounds.size(), |frame| { + self.draw(frame); + }); + + vec![theme] + } +} + impl Default for Theme { fn default() -> Self { Theme::new(Color::from_rgb8(75, 128, 190)) From 93c6be5eef577f0778b5787dac37351c035ed471 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 4 May 2020 23:54:28 +0200 Subject: [PATCH 73/73] Update `README` of `game_of_life` example --- examples/README.md | 21 +++++++++++++++++++++ examples/game_of_life/README.md | 7 +++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/examples/README.md b/examples/README.md index 7e7bda9dcf..8e1b781f36 100644 --- a/examples/README.md +++ b/examples/README.md @@ -50,6 +50,27 @@ We have not yet implemented a `LocalStorage` version of the auto-save feature. T [TodoMVC]: http://todomvc.com/ +## [Game of Life](game_of_life) +An interactive version of the [Game of Life], invented by [John Horton Conway]. + +It runs a simulation in a background thread while allowing interaction with a `Canvas` that displays an infinite grid with zooming, panning, and drawing support. + +The relevant code is located in the __[`main`](game_of_life/src/main.rs)__ file. + + + +You can run it with `cargo run`: +``` +cargo run --package game_of_life +``` + +[Game of Life]: https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life +[John Horton Conway]: https://en.wikipedia.org/wiki/John_Horton_Conway + ## [Styling](styling) An example showcasing custom styling with a light and dark theme. diff --git a/examples/game_of_life/README.md b/examples/game_of_life/README.md index 5741cbf665..1aeb1455ba 100644 --- a/examples/game_of_life/README.md +++ b/examples/game_of_life/README.md @@ -1,7 +1,8 @@ ## Game of Life -An interactive version of the Game of Life, invented by John Conway, implemented -on top of a `Canvas` widget and other controls. +An interactive version of the [Game of Life], invented by [John Horton Conway]. + +It runs a simulation in a background thread while allowing interaction with a `Canvas` that displays an infinite grid with zooming, panning, and drawing support. The __[`main`]__ file contains the relevant code of the example. @@ -17,3 +18,5 @@ cargo run --package game_of_life ``` [`main`]: src/main.rs +[Game of Life]: https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life +[John Horton Conway]: https://en.wikipedia.org/wiki/John_Horton_Conway