diff options
author | 2023-09-14 13:58:36 -0700 | |
---|---|---|
committer | 2023-11-14 11:31:44 +0100 | |
commit | 781ef1f94c4859aeeb852f801b72be095b8ff82b (patch) | |
tree | 63e2678eca11dd41c26a40633c04341fd795d733 /renderer | |
parent | 817f72868746461891ca4e74473c555f3b5c5703 (diff) | |
download | iced-781ef1f94c4859aeeb852f801b72be095b8ff82b.tar.gz iced-781ef1f94c4859aeeb852f801b72be095b8ff82b.tar.bz2 iced-781ef1f94c4859aeeb852f801b72be095b8ff82b.zip |
Added support for custom shader widget for iced_wgpu backend.
Diffstat (limited to 'renderer')
-rw-r--r-- | renderer/src/lib.rs | 21 | ||||
-rw-r--r-- | renderer/src/widget.rs | 3 | ||||
-rw-r--r-- | renderer/src/widget/shader.rs | 215 | ||||
-rw-r--r-- | renderer/src/widget/shader/event.rs | 21 | ||||
-rw-r--r-- | renderer/src/widget/shader/program.rs | 60 |
5 files changed, 320 insertions, 0 deletions
diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs index 78dec847..8c5ee2f0 100644 --- a/renderer/src/lib.rs +++ b/renderer/src/lib.rs @@ -7,6 +7,7 @@ pub mod compositor; pub mod geometry; mod settings; +pub mod widget; pub use iced_graphics as graphics; pub use iced_graphics::core; @@ -59,6 +60,26 @@ impl<T> Renderer<T> { } } } + + pub fn draw_custom<P: widget::shader::Primitive>( + &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<T> core::Renderer for Renderer<T> { diff --git a/renderer/src/widget.rs b/renderer/src/widget.rs index 6c0c2a83..0422c99c 100644 --- a/renderer/src/widget.rs +++ b/renderer/src/widget.rs @@ -9,3 +9,6 @@ pub mod qr_code; #[cfg(feature = "qr_code")] pub use qr_code::QRCode; + +#[cfg(feature = "wgpu")] +pub mod shader; diff --git a/renderer/src/widget/shader.rs b/renderer/src/widget/shader.rs new file mode 100644 index 00000000..da42a7dd --- /dev/null +++ b/renderer/src/widget/shader.rs @@ -0,0 +1,215 @@ +//! 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, Clipboard, Element, Layout, Length, Rectangle, + Shell, Size, Widget, +}; +use std::marker::PhantomData; + +mod event; +mod program; + +pub use event::Event; +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<Message, P: Program<Message>> { + width: Length, + height: Length, + program: P, + _message: PhantomData<Message>, +} + +impl<Message, P: Program<Message>> Shader<Message, P> { + /// 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<Length>) -> Self { + self.width = width.into(); + self + } + + /// Set the `height` of the custom [`Shader`]. + pub fn height(mut self, height: impl Into<Length>) -> Self { + self.height = height.into(); + self + } +} + +impl<P, Message, Theme> Widget<Message, crate::Renderer<Theme>> + for Shader<Message, P> +where + P: Program<Message>, +{ + fn tag(&self) -> Tag { + struct Tag<T>(T); + tree::Tag::of::<Tag<P::State>>() + } + + 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<Theme>, + 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<Theme>, + _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)), + _ => None, + }; + + if let Some(custom_shader_event) = custom_shader_event { + let state = tree.state.downcast_mut::<P::State>(); + + 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<Theme>, + ) -> mouse::Interaction { + let bounds = layout.bounds(); + let state = tree.state.downcast_ref::<P::State>(); + + self.program.mouse_interaction(state, bounds, cursor) + } + + fn draw( + &self, + tree: &widget::Tree, + renderer: &mut crate::Renderer<Theme>, + _theme: &Theme, + _style: &Style, + layout: Layout<'_>, + cursor_position: mouse::Cursor, + _viewport: &Rectangle, + ) { + let bounds = layout.bounds(); + let state = tree.state.downcast_ref::<P::State>(); + + renderer.draw_custom( + bounds, + self.program.draw(state, cursor_position, bounds), + ); + } +} + +impl<'a, M, P, Theme> From<Shader<M, P>> + for Element<'a, M, crate::Renderer<Theme>> +where + M: 'a, + P: Program<M> + 'a, +{ + fn from(custom: Shader<M, P>) -> Element<'a, M, crate::Renderer<Theme>> { + Element::new(custom) + } +} + +impl<Message, T> Program<Message> for &T +where + T: Program<Message>, +{ + 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<Message>) { + 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 new file mode 100644 index 00000000..981b30d7 --- /dev/null +++ b/renderer/src/widget/shader/event.rs @@ -0,0 +1,21 @@ +//! Handle events of a custom shader widget. +use crate::core::keyboard; +use crate::core::mouse; +use crate::core::touch; + +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), +} diff --git a/renderer/src/widget/shader/program.rs b/renderer/src/widget/shader/program.rs new file mode 100644 index 00000000..b8871688 --- /dev/null +++ b/renderer/src/widget/shader/program.rs @@ -0,0 +1,60 @@ +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<Message> { + /// 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<Message>) { + (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() + } +} |