From 9489e29e6619b14ed9f41a8887c4b34158266f71 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Nov 2023 12:49:49 +0100 Subject: Re-organize `custom` module as `pipeline` module ... and move `Shader` widget to `iced_widget` crate --- examples/custom_shader/src/main.rs | 11 +- examples/custom_shader/src/primitive.rs | 15 ++- renderer/src/lib.rs | 44 ++++--- renderer/src/widget.rs | 2 - renderer/src/widget/shader.rs | 219 -------------------------------- renderer/src/widget/shader/event.rs | 25 ---- renderer/src/widget/shader/program.rs | 60 --------- wgpu/src/backend.rs | 29 +++-- wgpu/src/custom.rs | 63 +-------- wgpu/src/layer.rs | 19 ++- wgpu/src/lib.rs | 1 - wgpu/src/primitive.rs | 43 ++----- wgpu/src/primitive/pipeline.rs | 117 +++++++++++++++++ widget/src/lib.rs | 6 +- widget/src/shader.rs | 219 ++++++++++++++++++++++++++++++++ widget/src/shader/event.rs | 26 ++++ widget/src/shader/program.rs | 62 +++++++++ 17 files changed, 505 insertions(+), 456 deletions(-) delete mode 100644 renderer/src/widget.rs delete mode 100644 renderer/src/widget/shader.rs delete mode 100644 renderer/src/widget/shader/event.rs delete mode 100644 renderer/src/widget/shader/program.rs create mode 100644 wgpu/src/primitive/pipeline.rs create mode 100644 widget/src/shader.rs create mode 100644 widget/src/shader/event.rs create mode 100644 widget/src/shader/program.rs diff --git a/examples/custom_shader/src/main.rs b/examples/custom_shader/src/main.rs index 76fa1625..f6370f46 100644 --- a/examples/custom_shader/src/main.rs +++ b/examples/custom_shader/src/main.rs @@ -3,15 +3,20 @@ mod cubes; mod pipeline; mod primitive; +use crate::camera::Camera; use crate::cubes::Cubes; +use crate::pipeline::Pipeline; + +use iced::executor; +use iced::time::Instant; use iced::widget::{ checkbox, column, container, row, slider, text, vertical_space, Shader, }; +use iced::window; use iced::{ - executor, window, Alignment, Application, Color, Command, Element, Length, - Renderer, Subscription, Theme, + Alignment, Application, Color, Command, Element, Length, Renderer, + Subscription, Theme, }; -use std::time::Instant; fn main() -> iced::Result { IcedCubes::run(iced::Settings::default()) diff --git a/examples/custom_shader/src/primitive.rs b/examples/custom_shader/src/primitive.rs index f81ce95d..520ceb8e 100644 --- a/examples/custom_shader/src/primitive.rs +++ b/examples/custom_shader/src/primitive.rs @@ -4,17 +4,18 @@ pub mod vertex; mod buffer; mod uniforms; -use crate::camera::Camera; -use crate::pipeline::Pipeline; -use crate::primitive::cube::Cube; +pub use buffer::Buffer; +pub use cube::Cube; +pub use uniforms::Uniforms; +pub use vertex::Vertex; + +use crate::Camera; +use crate::Pipeline; + use iced::advanced::graphics::Transformation; use iced::widget::shader; use iced::{Color, Rectangle, Size}; -pub use crate::primitive::vertex::Vertex; -pub use buffer::Buffer; -pub use uniforms::Uniforms; - /// A collection of `Cube`s that can be rendered. #[derive(Debug)] pub struct Primitive { diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs index e4b1eda9..1fc4c86b 100644 --- a/renderer/src/lib.rs +++ b/renderer/src/lib.rs @@ -1,13 +1,15 @@ #![forbid(rust_2018_idioms)] #![deny(unsafe_code, unused_results, rustdoc::broken_intra_doc_links)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] +#[cfg(feature = "wgpu")] +pub use iced_wgpu as wgpu; + pub mod compositor; #[cfg(feature = "geometry")] pub mod geometry; mod settings; -pub mod widget; pub use iced_graphics as graphics; pub use iced_graphics::core; @@ -60,26 +62,6 @@ impl Renderer { } } } - - pub fn draw_custom( - &mut self, - bounds: Rectangle, - primitive: P, - ) { - match self { - Renderer::TinySkia(_) => { - log::warn!( - "Custom shader primitive is unavailable with tiny-skia." - ); - } - #[cfg(feature = "wgpu")] - Renderer::Wgpu(renderer) => { - renderer.draw_primitive(iced_wgpu::Primitive::Custom( - iced_wgpu::primitive::Custom::shader(bounds, primitive), - )); - } - } - } } impl core::Renderer for Renderer { @@ -292,3 +274,23 @@ impl crate::graphics::geometry::Renderer for Renderer { } } } + +#[cfg(feature = "wgpu")] +impl iced_wgpu::primitive::pipeline::Renderer for Renderer { + fn draw_pipeline_primitive( + &mut self, + bounds: Rectangle, + primitive: impl wgpu::primitive::pipeline::Primitive, + ) { + match self { + Self::TinySkia(_renderer) => { + log::warn!( + "Custom shader primitive is unavailable with tiny-skia." + ); + } + Self::Wgpu(renderer) => { + renderer.draw_pipeline_primitive(bounds, primitive); + } + } + } +} diff --git a/renderer/src/widget.rs b/renderer/src/widget.rs deleted file mode 100644 index 4b7dad5d..00000000 --- a/renderer/src/widget.rs +++ /dev/null @@ -1,2 +0,0 @@ -#[cfg(feature = "wgpu")] -pub mod shader; diff --git a/renderer/src/widget/shader.rs b/renderer/src/widget/shader.rs deleted file mode 100644 index e218f35a..00000000 --- a/renderer/src/widget/shader.rs +++ /dev/null @@ -1,219 +0,0 @@ -//! A custom shader widget for wgpu applications. -use crate::core::event::Status; -use crate::core::layout::{Limits, Node}; -use crate::core::mouse::{Cursor, Interaction}; -use crate::core::renderer::Style; -use crate::core::widget::tree::{State, Tag}; -use crate::core::widget::{tree, Tree}; -use crate::core::{ - self, layout, mouse, widget, window, Clipboard, Element, Layout, Length, - Rectangle, Shell, Size, Widget, -}; -use std::marker::PhantomData; - -mod event; -mod program; - -pub use event::Event; -pub use iced_graphics::Transformation; -pub use iced_wgpu::custom::Primitive; -pub use iced_wgpu::custom::Storage; -pub use program::Program; - -/// A widget which can render custom shaders with Iced's `wgpu` backend. -/// -/// Must be initialized with a [`Program`], which describes the internal widget state & how -/// its [`Program::Primitive`]s are drawn. -#[allow(missing_debug_implementations)] -pub struct Shader> { - width: Length, - height: Length, - program: P, - _message: PhantomData, -} - -impl> Shader { - /// Create a new custom [`Shader`]. - pub fn new(program: P) -> Self { - Self { - width: Length::Fixed(100.0), - height: Length::Fixed(100.0), - program, - _message: PhantomData, - } - } - - /// Set the `width` of the custom [`Shader`]. - pub fn width(mut self, width: impl Into) -> Self { - self.width = width.into(); - self - } - - /// Set the `height` of the custom [`Shader`]. - pub fn height(mut self, height: impl Into) -> Self { - self.height = height.into(); - self - } -} - -impl Widget> - for Shader -where - P: Program, -{ - fn tag(&self) -> Tag { - struct Tag(T); - tree::Tag::of::>() - } - - fn state(&self) -> State { - tree::State::new(P::State::default()) - } - - fn width(&self) -> Length { - self.width - } - - fn height(&self) -> Length { - self.height - } - - fn layout( - &self, - _tree: &mut Tree, - _renderer: &crate::Renderer, - limits: &Limits, - ) -> Node { - let limits = limits.width(self.width).height(self.height); - let size = limits.resolve(Size::ZERO); - - layout::Node::new(size) - } - - fn on_event( - &mut self, - tree: &mut Tree, - event: crate::core::Event, - layout: Layout<'_>, - cursor: Cursor, - _renderer: &crate::Renderer, - _clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - _viewport: &Rectangle, - ) -> Status { - let bounds = layout.bounds(); - - let custom_shader_event = match event { - core::Event::Mouse(mouse_event) => Some(Event::Mouse(mouse_event)), - core::Event::Keyboard(keyboard_event) => { - Some(Event::Keyboard(keyboard_event)) - } - core::Event::Touch(touch_event) => Some(Event::Touch(touch_event)), - core::Event::Window(window::Event::RedrawRequested(instant)) => { - Some(Event::RedrawRequested(instant)) - } - _ => None, - }; - - if let Some(custom_shader_event) = custom_shader_event { - let state = tree.state.downcast_mut::(); - - let (event_status, message) = self.program.update( - state, - custom_shader_event, - bounds, - cursor, - shell, - ); - - if let Some(message) = message { - shell.publish(message); - } - - return event_status; - } - - event::Status::Ignored - } - - fn mouse_interaction( - &self, - tree: &Tree, - layout: Layout<'_>, - cursor: Cursor, - _viewport: &Rectangle, - _renderer: &crate::Renderer, - ) -> mouse::Interaction { - let bounds = layout.bounds(); - let state = tree.state.downcast_ref::(); - - self.program.mouse_interaction(state, bounds, cursor) - } - - fn draw( - &self, - tree: &widget::Tree, - renderer: &mut crate::Renderer, - _theme: &Theme, - _style: &Style, - layout: Layout<'_>, - cursor_position: mouse::Cursor, - _viewport: &Rectangle, - ) { - let bounds = layout.bounds(); - let state = tree.state.downcast_ref::(); - - renderer.draw_custom( - bounds, - self.program.draw(state, cursor_position, bounds), - ); - } -} - -impl<'a, M, P, Theme> From> - for Element<'a, M, crate::Renderer> -where - M: 'a, - P: Program + 'a, -{ - fn from(custom: Shader) -> Element<'a, M, crate::Renderer> { - Element::new(custom) - } -} - -impl Program for &T -where - T: Program, -{ - type State = T::State; - type Primitive = T::Primitive; - - fn update( - &self, - state: &mut Self::State, - event: Event, - bounds: Rectangle, - cursor: Cursor, - shell: &mut Shell<'_, Message>, - ) -> (Status, Option) { - T::update(self, state, event, bounds, cursor, shell) - } - - fn draw( - &self, - state: &Self::State, - cursor: Cursor, - bounds: Rectangle, - ) -> Self::Primitive { - T::draw(self, state, cursor, bounds) - } - - fn mouse_interaction( - &self, - state: &Self::State, - bounds: Rectangle, - cursor: Cursor, - ) -> Interaction { - T::mouse_interaction(self, state, bounds, cursor) - } -} diff --git a/renderer/src/widget/shader/event.rs b/renderer/src/widget/shader/event.rs deleted file mode 100644 index 8901fb31..00000000 --- a/renderer/src/widget/shader/event.rs +++ /dev/null @@ -1,25 +0,0 @@ -//! Handle events of a custom shader widget. -use crate::core::keyboard; -use crate::core::mouse; -use crate::core::touch; -use std::time::Instant; - -pub use crate::core::event::Status; - -/// A [`Shader`] event. -/// -/// [`Shader`]: crate::widget::shader::Shader; -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum Event { - /// A mouse event. - Mouse(mouse::Event), - - /// A touch event. - Touch(touch::Event), - - /// A keyboard event. - Keyboard(keyboard::Event), - - /// A window requested a redraw. - RedrawRequested(Instant), -} diff --git a/renderer/src/widget/shader/program.rs b/renderer/src/widget/shader/program.rs deleted file mode 100644 index b8871688..00000000 --- a/renderer/src/widget/shader/program.rs +++ /dev/null @@ -1,60 +0,0 @@ -use crate::core::{event, mouse, Rectangle, Shell}; -use crate::widget; -use widget::shader; - -/// The state and logic of a [`Shader`] widget. -/// -/// A [`Program`] can mutate the internal state of a [`Shader`] widget -/// and produce messages for an application. -/// -/// [`Shader`]: crate::widget::shader::Shader -pub trait Program { - /// The internal state of the [`Program`]. - type State: Default + 'static; - - /// The type of primitive this [`Program`] can draw. - type Primitive: shader::Primitive + 'static; - - /// Update the internal [`State`] of the [`Program`]. This can be used to reflect state changes - /// based on mouse & other events. You can use the [`Shell`] to publish messages, request a - /// redraw for the window, etc. - /// - /// By default, this method does and returns nothing. - /// - /// [`State`]: Self::State - fn update( - &self, - _state: &mut Self::State, - _event: shader::Event, - _bounds: Rectangle, - _cursor: mouse::Cursor, - _shell: &mut Shell<'_, Message>, - ) -> (event::Status, Option) { - (event::Status::Ignored, None) - } - - /// Draws the [`Primitive`]. - /// - /// [`Primitive`]: Self::Primitive - fn draw( - &self, - state: &Self::State, - cursor: mouse::Cursor, - bounds: Rectangle, - ) -> Self::Primitive; - - /// Returns the current mouse interaction of the [`Program`]. - /// - /// The interaction returned will be in effect even if the cursor position is out of - /// bounds of the [`Shader`]'s program. - /// - /// [`Shader`]: crate::widget::shader::Shader - fn mouse_interaction( - &self, - _state: &Self::State, - _bounds: Rectangle, - _cursor: mouse::Cursor, - ) -> mouse::Interaction { - mouse::Interaction::default() - } -} diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 27ef0b3c..f89bcee1 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -2,8 +2,11 @@ use crate::core::{Color, Size}; use crate::graphics::backend; use crate::graphics::color; use crate::graphics::{Transformation, Viewport}; +use crate::primitive::pipeline; use crate::primitive::{self, Primitive}; -use crate::{custom, quad, text, triangle}; +use crate::quad; +use crate::text; +use crate::triangle; use crate::{Layer, Settings}; #[cfg(feature = "tracing")] @@ -23,7 +26,7 @@ pub struct Backend { quad_pipeline: quad::Pipeline, text_pipeline: text::Pipeline, triangle_pipeline: triangle::Pipeline, - pipeline_storage: custom::Storage, + pipeline_storage: pipeline::Storage, #[cfg(any(feature = "image", feature = "svg"))] image_pipeline: image::Pipeline, @@ -49,7 +52,7 @@ impl Backend { quad_pipeline, text_pipeline, triangle_pipeline, - pipeline_storage: custom::Storage::default(), + pipeline_storage: pipeline::Storage::default(), #[cfg(any(feature = "image", feature = "svg"))] image_pipeline, @@ -183,9 +186,9 @@ impl Backend { ); } - if !layer.shaders.is_empty() { - for shader in &layer.shaders { - shader.primitive.prepare( + if !layer.pipelines.is_empty() { + for pipeline in &layer.pipelines { + pipeline.primitive.prepare( format, device, queue, @@ -323,19 +326,17 @@ impl Backend { // kill render pass to let custom shaders get mut access to encoder let _ = ManuallyDrop::into_inner(render_pass); - if !layer.shaders.is_empty() { - for shader in &layer.shaders { - //This extra check is needed since each custom pipeline must set it's own - //scissor rect, which will panic if bounds.w/h < 1 - let bounds = shader.bounds * scale_factor; + if !layer.pipelines.is_empty() { + for pipeline in &layer.pipelines { + let bounds = (pipeline.bounds * scale_factor).snap(); - if bounds.width < 1.0 || bounds.height < 1.0 { + if bounds.width < 1 || bounds.height < 1 { continue; } - shader.primitive.render( + pipeline.primitive.render( &self.pipeline_storage, - bounds.snap(), + bounds, target, target_size, encoder, diff --git a/wgpu/src/custom.rs b/wgpu/src/custom.rs index 65a6f133..98e2b396 100644 --- a/wgpu/src/custom.rs +++ b/wgpu/src/custom.rs @@ -1,67 +1,8 @@ //! Draw custom primitives. use crate::core::{Rectangle, Size}; use crate::graphics::Transformation; +use crate::primitive; + use std::any::{Any, TypeId}; use std::collections::HashMap; use std::fmt::Debug; - -/// Stores custom, user-provided pipelines. -#[derive(Default, Debug)] -pub struct Storage { - pipelines: HashMap>, -} - -impl Storage { - /// Returns `true` if `Storage` contains a pipeline with type `T`. - pub fn has(&self) -> bool { - self.pipelines.get(&TypeId::of::()).is_some() - } - - /// Inserts the pipeline `T` in to [`Storage`]. - pub fn store(&mut self, pipeline: T) { - let _ = self.pipelines.insert(TypeId::of::(), Box::new(pipeline)); - } - - /// Returns a reference to pipeline with type `T` if it exists in [`Storage`]. - pub fn get(&self) -> Option<&T> { - self.pipelines.get(&TypeId::of::()).map(|pipeline| { - pipeline - .downcast_ref::() - .expect("Pipeline with this type does not exist in Storage.") - }) - } - - /// Returns a mutable reference to pipeline `T` if it exists in [`Storage`]. - pub fn get_mut(&mut self) -> Option<&mut T> { - self.pipelines.get_mut(&TypeId::of::()).map(|pipeline| { - pipeline - .downcast_mut::() - .expect("Pipeline with this type does not exist in Storage.") - }) - } -} - -/// A set of methods which allows a [`Primitive`] to be rendered. -pub trait Primitive: Debug + Send + Sync + 'static { - /// Processes the [`Primitive`], allowing for GPU buffer allocation. - fn prepare( - &self, - format: wgpu::TextureFormat, - device: &wgpu::Device, - queue: &wgpu::Queue, - target_size: Size, - scale_factor: f32, - transform: Transformation, - storage: &mut Storage, - ); - - /// Renders the [`Primitive`]. - fn render( - &self, - storage: &Storage, - bounds: Rectangle, - target: &wgpu::TextureView, - target_size: Size, - encoder: &mut wgpu::CommandEncoder, - ); -} diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index d451cbfd..33aaf670 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -35,8 +35,8 @@ pub struct Layer<'a> { /// The images of the [`Layer`]. pub images: Vec, - /// The custom shader primitives of this [`Layer`]. - pub shaders: Vec, + /// The custom pipelines of this [`Layer`]. + pub pipelines: Vec, } impl<'a> Layer<'a> { @@ -48,7 +48,7 @@ impl<'a> Layer<'a> { meshes: Vec::new(), text: Vec::new(), images: Vec::new(), - shaders: Vec::new(), + pipelines: Vec::new(), } } @@ -312,16 +312,21 @@ impl<'a> Layer<'a> { } } }, - primitive::Custom::Shader(shader) => { + primitive::Custom::Pipeline(pipeline) => { let layer = &mut layers[current_layer]; let bounds = Rectangle::new( Point::new(translation.x, translation.y), - shader.bounds.size(), + pipeline.bounds.size(), ); - if layer.bounds.intersection(&bounds).is_some() { - layer.shaders.push(shader.clone()); + if let Some(clip_bounds) = + layer.bounds.intersection(&bounds) + { + layer.pipelines.push(primitive::Pipeline { + bounds: clip_bounds, + primitive: pipeline.primitive.clone(), + }); } } }, diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 13d8e886..424dfeb3 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -29,7 +29,6 @@ rustdoc::broken_intra_doc_links )] #![cfg_attr(docsrs, feature(doc_auto_cfg))] -pub mod custom; pub mod layer; pub mod primitive; pub mod settings; diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs index 4347dcda..fff927ea 100644 --- a/wgpu/src/primitive.rs +++ b/wgpu/src/primitive.rs @@ -1,10 +1,12 @@ //! Draw using different graphical primitives. +pub mod pipeline; + +pub use pipeline::Pipeline; + use crate::core::Rectangle; -use crate::custom; use crate::graphics::{Damage, Mesh}; -use std::any::Any; + use std::fmt::Debug; -use std::sync::Arc; /// The graphical primitives supported by `iced_wgpu`. pub type Primitive = crate::graphics::Primitive; @@ -14,44 +16,15 @@ pub type Primitive = crate::graphics::Primitive; pub enum Custom { /// A mesh primitive. Mesh(Mesh), - /// A custom shader primitive - Shader(Shader), -} - -impl Custom { - /// Create a custom [`Shader`] primitive. - pub fn shader( - bounds: Rectangle, - primitive: P, - ) -> Self { - Self::Shader(Shader { - bounds, - primitive: Arc::new(primitive), - }) - } + /// A custom pipeline primitive. + Pipeline(Pipeline), } impl Damage for Custom { fn bounds(&self) -> Rectangle { match self { Self::Mesh(mesh) => mesh.bounds(), - Self::Shader(shader) => shader.bounds, + Self::Pipeline(pipeline) => pipeline.bounds, } } } - -#[derive(Clone, Debug)] -/// A custom primitive which can be used to render primitives associated with a custom pipeline. -pub struct Shader { - /// The bounds of the [`Shader`]. - pub bounds: Rectangle, - - /// The [`custom::Primitive`] to render. - pub primitive: Arc, -} - -impl PartialEq for Shader { - fn eq(&self, other: &Self) -> bool { - self.primitive.type_id() == other.primitive.type_id() - } -} diff --git a/wgpu/src/primitive/pipeline.rs b/wgpu/src/primitive/pipeline.rs new file mode 100644 index 00000000..b59aeff1 --- /dev/null +++ b/wgpu/src/primitive/pipeline.rs @@ -0,0 +1,117 @@ +//! Draw primitives using custom pipelines. +use crate::core::{Rectangle, Size}; +use crate::graphics::Transformation; + +use std::any::{Any, TypeId}; +use std::collections::HashMap; +use std::fmt::Debug; +use std::sync::Arc; + +#[derive(Clone, Debug)] +/// A custom primitive which can be used to render primitives associated with a custom pipeline. +pub struct Pipeline { + /// The bounds of the [`Shader`]. + pub bounds: Rectangle, + + /// The [`custom::Primitive`] to render. + pub primitive: Arc, +} + +impl Pipeline { + /// Creates a new [`Pipeline`] with the given [`Primitive`]. + pub fn new(bounds: Rectangle, primitive: impl Primitive) -> Self { + Pipeline { + bounds, + primitive: Arc::new(primitive), + } + } +} + +impl PartialEq for Pipeline { + fn eq(&self, other: &Self) -> bool { + self.primitive.type_id() == other.primitive.type_id() + } +} + +/// A set of methods which allows a [`Primitive`] to be rendered. +pub trait Primitive: Debug + Send + Sync + 'static { + /// Processes the [`Primitive`], allowing for GPU buffer allocation. + fn prepare( + &self, + format: wgpu::TextureFormat, + device: &wgpu::Device, + queue: &wgpu::Queue, + target_size: Size, + scale_factor: f32, + transform: Transformation, + storage: &mut Storage, + ); + + /// Renders the [`Primitive`]. + fn render( + &self, + storage: &Storage, + bounds: Rectangle, + target: &wgpu::TextureView, + target_size: Size, + encoder: &mut wgpu::CommandEncoder, + ); +} + +/// A renderer than can draw custom pipeline primitives. +pub trait Renderer: crate::core::Renderer { + /// Draws a custom pipeline primitive. + fn draw_pipeline_primitive( + &mut self, + bounds: Rectangle, + primitive: impl Primitive, + ); +} + +impl Renderer for crate::Renderer { + fn draw_pipeline_primitive( + &mut self, + bounds: Rectangle, + primitive: impl Primitive, + ) { + self.draw_primitive(super::Primitive::Custom(super::Custom::Pipeline( + Pipeline::new(bounds, primitive), + ))); + } +} + +/// Stores custom, user-provided pipelines. +#[derive(Default, Debug)] +pub struct Storage { + pipelines: HashMap>, +} + +impl Storage { + /// Returns `true` if `Storage` contains a pipeline with type `T`. + pub fn has(&self) -> bool { + self.pipelines.get(&TypeId::of::()).is_some() + } + + /// Inserts the pipeline `T` in to [`Storage`]. + pub fn store(&mut self, pipeline: T) { + let _ = self.pipelines.insert(TypeId::of::(), Box::new(pipeline)); + } + + /// Returns a reference to pipeline with type `T` if it exists in [`Storage`]. + pub fn get(&self) -> Option<&T> { + self.pipelines.get(&TypeId::of::()).map(|pipeline| { + pipeline + .downcast_ref::() + .expect("Pipeline with this type does not exist in Storage.") + }) + } + + /// Returns a mutable reference to pipeline `T` if it exists in [`Storage`]. + pub fn get_mut(&mut self) -> Option<&mut T> { + self.pipelines.get_mut(&TypeId::of::()).map(|pipeline| { + pipeline + .downcast_mut::() + .expect("Pipeline with this type does not exist in Storage.") + }) + } +} diff --git a/widget/src/lib.rs b/widget/src/lib.rs index 5220e83a..07378d83 100644 --- a/widget/src/lib.rs +++ b/widget/src/lib.rs @@ -98,7 +98,11 @@ pub use tooltip::Tooltip; pub use vertical_slider::VerticalSlider; #[cfg(feature = "wgpu")] -pub use renderer::widget::shader::{self, Shader, Transformation}; +pub mod shader; + +#[cfg(feature = "wgpu")] +#[doc(no_inline)] +pub use shader::Shader; #[cfg(feature = "svg")] pub mod svg; diff --git a/widget/src/shader.rs b/widget/src/shader.rs new file mode 100644 index 00000000..9d482537 --- /dev/null +++ b/widget/src/shader.rs @@ -0,0 +1,219 @@ +//! A custom shader widget for wgpu applications. +mod event; +mod program; + +pub use event::Event; +pub use program::Program; + +use crate::core; +use crate::core::layout::{self, Layout}; +use crate::core::mouse; +use crate::core::renderer; +use crate::core::widget::tree::{self, Tree}; +use crate::core::widget::{self, Widget}; +use crate::core::window; +use crate::core::{Clipboard, Element, Length, Rectangle, Shell, Size}; +use crate::renderer::wgpu::primitive::pipeline; + +use std::marker::PhantomData; + +pub use pipeline::{Primitive, Storage}; + +/// A widget which can render custom shaders with Iced's `wgpu` backend. +/// +/// Must be initialized with a [`Program`], which describes the internal widget state & how +/// its [`Program::Primitive`]s are drawn. +#[allow(missing_debug_implementations)] +pub struct Shader> { + width: Length, + height: Length, + program: P, + _message: PhantomData, +} + +impl> Shader { + /// Create a new custom [`Shader`]. + pub fn new(program: P) -> Self { + Self { + width: Length::Fixed(100.0), + height: Length::Fixed(100.0), + program, + _message: PhantomData, + } + } + + /// Set the `width` of the custom [`Shader`]. + pub fn width(mut self, width: impl Into) -> Self { + self.width = width.into(); + self + } + + /// Set the `height` of the custom [`Shader`]. + pub fn height(mut self, height: impl Into) -> Self { + self.height = height.into(); + self + } +} + +impl Widget for Shader +where + P: Program, + Renderer: pipeline::Renderer, +{ + fn tag(&self) -> tree::Tag { + struct Tag(T); + tree::Tag::of::>() + } + + fn state(&self) -> tree::State { + tree::State::new(P::State::default()) + } + + fn width(&self) -> Length { + self.width + } + + fn height(&self) -> Length { + self.height + } + + fn layout( + &self, + _tree: &mut Tree, + _renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + let limits = limits.width(self.width).height(self.height); + let size = limits.resolve(Size::ZERO); + + layout::Node::new(size) + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: crate::core::Event, + layout: Layout<'_>, + cursor: mouse::Cursor, + _renderer: &Renderer, + _clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + _viewport: &Rectangle, + ) -> event::Status { + let bounds = layout.bounds(); + + let custom_shader_event = match event { + core::Event::Mouse(mouse_event) => Some(Event::Mouse(mouse_event)), + core::Event::Keyboard(keyboard_event) => { + Some(Event::Keyboard(keyboard_event)) + } + core::Event::Touch(touch_event) => Some(Event::Touch(touch_event)), + core::Event::Window(window::Event::RedrawRequested(instant)) => { + Some(Event::RedrawRequested(instant)) + } + _ => None, + }; + + if let Some(custom_shader_event) = custom_shader_event { + let state = tree.state.downcast_mut::(); + + let (event_status, message) = self.program.update( + state, + custom_shader_event, + bounds, + cursor, + shell, + ); + + if let Some(message) = message { + shell.publish(message); + } + + return event_status; + } + + event::Status::Ignored + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor: mouse::Cursor, + _viewport: &Rectangle, + _renderer: &Renderer, + ) -> mouse::Interaction { + let bounds = layout.bounds(); + let state = tree.state.downcast_ref::(); + + self.program.mouse_interaction(state, bounds, cursor) + } + + fn draw( + &self, + tree: &widget::Tree, + renderer: &mut Renderer, + _theme: &Renderer::Theme, + _style: &renderer::Style, + layout: Layout<'_>, + cursor_position: mouse::Cursor, + _viewport: &Rectangle, + ) { + let bounds = layout.bounds(); + let state = tree.state.downcast_ref::(); + + renderer.draw_pipeline_primitive( + bounds, + self.program.draw(state, cursor_position, bounds), + ); + } +} + +impl<'a, Message, Renderer, P> From> + for Element<'a, Message, Renderer> +where + Message: 'a, + Renderer: pipeline::Renderer, + P: Program + 'a, +{ + fn from(custom: Shader) -> Element<'a, Message, Renderer> { + Element::new(custom) + } +} + +impl Program for &T +where + T: Program, +{ + type State = T::State; + type Primitive = T::Primitive; + + fn update( + &self, + state: &mut Self::State, + event: Event, + bounds: Rectangle, + cursor: mouse::Cursor, + shell: &mut Shell<'_, Message>, + ) -> (event::Status, Option) { + T::update(self, state, event, bounds, cursor, shell) + } + + fn draw( + &self, + state: &Self::State, + cursor: mouse::Cursor, + bounds: Rectangle, + ) -> Self::Primitive { + T::draw(self, state, cursor, bounds) + } + + fn mouse_interaction( + &self, + state: &Self::State, + bounds: Rectangle, + cursor: mouse::Cursor, + ) -> mouse::Interaction { + T::mouse_interaction(self, state, bounds, cursor) + } +} diff --git a/widget/src/shader/event.rs b/widget/src/shader/event.rs new file mode 100644 index 00000000..e4d2b03d --- /dev/null +++ b/widget/src/shader/event.rs @@ -0,0 +1,26 @@ +//! Handle events of a custom shader widget. +use crate::core::keyboard; +use crate::core::mouse; +use crate::core::touch; + +use std::time::Instant; + +pub use crate::core::event::Status; + +/// A [`Shader`] event. +/// +/// [`Shader`]: crate::widget::shader::Shader; +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Event { + /// A mouse event. + Mouse(mouse::Event), + + /// A touch event. + Touch(touch::Event), + + /// A keyboard event. + Keyboard(keyboard::Event), + + /// A window requested a redraw. + RedrawRequested(Instant), +} diff --git a/widget/src/shader/program.rs b/widget/src/shader/program.rs new file mode 100644 index 00000000..0319844d --- /dev/null +++ b/widget/src/shader/program.rs @@ -0,0 +1,62 @@ +use crate::core::event; +use crate::core::mouse; +use crate::core::{Rectangle, Shell}; +use crate::renderer::wgpu::primitive::pipeline; +use crate::shader; + +/// The state and logic of a [`Shader`] widget. +/// +/// A [`Program`] can mutate the internal state of a [`Shader`] widget +/// and produce messages for an application. +/// +/// [`Shader`]: crate::widget::shader::Shader +pub trait Program { + /// The internal state of the [`Program`]. + type State: Default + 'static; + + /// The type of primitive this [`Program`] can draw. + type Primitive: pipeline::Primitive + 'static; + + /// Update the internal [`State`] of the [`Program`]. This can be used to reflect state changes + /// based on mouse & other events. You can use the [`Shell`] to publish messages, request a + /// redraw for the window, etc. + /// + /// By default, this method does and returns nothing. + /// + /// [`State`]: Self::State + fn update( + &self, + _state: &mut Self::State, + _event: shader::Event, + _bounds: Rectangle, + _cursor: mouse::Cursor, + _shell: &mut Shell<'_, Message>, + ) -> (event::Status, Option) { + (event::Status::Ignored, None) + } + + /// Draws the [`Primitive`]. + /// + /// [`Primitive`]: Self::Primitive + fn draw( + &self, + state: &Self::State, + cursor: mouse::Cursor, + bounds: Rectangle, + ) -> Self::Primitive; + + /// Returns the current mouse interaction of the [`Program`]. + /// + /// The interaction returned will be in effect even if the cursor position is out of + /// bounds of the [`Shader`]'s program. + /// + /// [`Shader`]: crate::widget::shader::Shader + fn mouse_interaction( + &self, + _state: &Self::State, + _bounds: Rectangle, + _cursor: mouse::Cursor, + ) -> mouse::Interaction { + mouse::Interaction::default() + } +} -- cgit