From 592cc685067c36cbba87e4db14f4ebc71d65b951 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 19 Apr 2020 21:55:23 +0200 Subject: 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/cache.rs | 110 +++++++++++++++++++++++++++ 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/layer/cache.rs | 120 ----------------------------- wgpu/src/widget/canvas/program.rs | 16 ---- wgpu/src/widget/canvas/state.rs | 20 +++++ 10 files changed, 255 insertions(+), 248 deletions(-) create mode 100644 wgpu/src/widget/canvas/cache.rs 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/layer/cache.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 2407db65..8b3a92c7 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 8870fe52..618f4206 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 326bc7d5..2b485e18 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/cache.rs b/wgpu/src/widget/canvas/cache.rs new file mode 100644 index 00000000..c88239af --- /dev/null +++ b/wgpu/src/widget/canvas/cache.rs @@ -0,0 +1,110 @@ +use crate::{ + canvas::{Drawable, Frame, Geometry}, + Primitive, +}; + +use iced_native::Size; +use std::{cell::RefCell, sync::Arc}; + +enum State { + Empty, + Filled { + bounds: Size, + primitive: Arc, + }, +} + +impl Default for State { + fn default() -> Self { + State::Empty + } +} +/// 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 +#[derive(Debug, Default)] +pub struct Cache { + state: RefCell, +} + +impl Cache { + /// Creates a new empty [`Cache`]. + /// + /// [`Cache`]: struct.Cache.html + pub fn new() -> Self { + Cache { + state: Default::default(), + } + } + + /// Clears the cache, forcing a redraw the next time it is used. + /// + /// [`Cached`]: struct.Cached.html + pub fn clear(&mut self) { + *self.state.borrow_mut() = State::Empty; + } + + pub fn draw(&self, new_bounds: Size, input: T) -> Geometry + where + T: Drawable + std::fmt::Debug, + { + use std::ops::Deref; + + if let State::Filled { bounds, primitive } = self.state.borrow().deref() + { + if *bounds == new_bounds { + return Geometry::from_primitive(primitive.clone()); + } + } + + let mut frame = Frame::new(new_bounds.width, new_bounds.height); + input.draw(&mut frame); + + let primitive = Arc::new(frame.into_primitive()); + + *self.state.borrow_mut() = State::Filled { + bounds: new_bounds, + primitive: primitive.clone(), + }; + + 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)] + } +} + +impl std::fmt::Debug for State { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + State::Empty => write!(f, "Empty"), + State::Filled { primitive, bounds } => f + .debug_struct("Filled") + .field("primitive", primitive) + .field("bounds", bounds) + .finish(), + } + } +} diff --git a/wgpu/src/widget/canvas/drawable.rs b/wgpu/src/widget/canvas/drawable.rs index 48ba6b4c..32258b71 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 00000000..db7b4054 --- /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 a46b7fb1..00000000 --- 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/layer/cache.rs b/wgpu/src/widget/canvas/layer/cache.rs deleted file mode 100644 index 4ecebb48..00000000 --- a/wgpu/src/widget/canvas/layer/cache.rs +++ /dev/null @@ -1,120 +0,0 @@ -use crate::{ - canvas::{Drawable, Frame, Layer}, - Primitive, -}; - -use iced_native::Size; -use std::{borrow::Borrow, cell::RefCell, marker::PhantomData, sync::Arc}; - -enum State { - Empty, - Filled { - bounds: Size, - primitive: Arc, - }, -} - -impl Default for State { - fn default() -> Self { - State::Empty - } -} -/// 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 -#[derive(Debug, Default)] -pub struct Cache { - state: RefCell, -} - -impl Cache { - /// Creates a new empty [`Cache`]. - /// - /// [`Cache`]: struct.Cache.html - pub fn new() -> Self { - Cache { - state: Default::default(), - } - } - - /// Clears the cache, forcing a redraw the next time it is used. - /// - /// [`Cached`]: struct.Cached.html - pub fn clear(&mut self) { - *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 - where - T: Drawable + std::fmt::Debug + 'a, - { - 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 *bounds == current_bounds { - return primitive.clone(); - } - } - - let mut frame = Frame::new(current_bounds.width, current_bounds.height); - self.input.borrow().draw(&mut frame); - - let primitive = Arc::new(frame.into_primitive()); - - *self.cache.state.borrow_mut() = State::Filled { - bounds: current_bounds, - primitive: primitive.clone(), - }; - - primitive - } -} - -impl std::fmt::Debug for State { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - State::Empty => write!(f, "Empty"), - State::Filled { primitive, bounds } => f - .debug_struct("Filled") - .field("primitive", primitive) - .field("bounds", bounds) - .finish(), - } - } -} diff --git a/wgpu/src/widget/canvas/program.rs b/wgpu/src/widget/canvas/program.rs deleted file mode 100644 index c65a078b..00000000 --- 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 00000000..8388f94d --- /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) + } +} -- cgit