diff options
Diffstat (limited to 'widget')
-rw-r--r-- | widget/src/lib.rs | 6 | ||||
-rw-r--r-- | widget/src/shader.rs | 219 | ||||
-rw-r--r-- | widget/src/shader/event.rs | 26 | ||||
-rw-r--r-- | widget/src/shader/program.rs | 62 |
4 files changed, 312 insertions, 1 deletions
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<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, Renderer> Widget<Message, Renderer> for Shader<Message, P> +where + P: Program<Message>, + Renderer: pipeline::Renderer, +{ + fn tag(&self) -> tree::Tag { + struct Tag<T>(T); + tree::Tag::of::<Tag<P::State>>() + } + + 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::<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: mouse::Cursor, + _viewport: &Rectangle, + _renderer: &Renderer, + ) -> 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 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::<P::State>(); + + renderer.draw_pipeline_primitive( + bounds, + self.program.draw(state, cursor_position, bounds), + ); + } +} + +impl<'a, Message, Renderer, P> From<Shader<Message, P>> + for Element<'a, Message, Renderer> +where + Message: 'a, + Renderer: pipeline::Renderer, + P: Program<Message> + 'a, +{ + fn from(custom: Shader<Message, P>) -> Element<'a, Message, Renderer> { + 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: mouse::Cursor, + shell: &mut Shell<'_, Message>, + ) -> (event::Status, Option<Message>) { + 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<Message> { + /// 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<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() + } +} |