summaryrefslogtreecommitdiffstats
path: root/widget
diff options
context:
space:
mode:
Diffstat (limited to 'widget')
-rw-r--r--widget/src/lib.rs6
-rw-r--r--widget/src/shader.rs219
-rw-r--r--widget/src/shader/event.rs26
-rw-r--r--widget/src/shader/program.rs62
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()
+ }
+}