summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lazy/Cargo.toml8
-rw-r--r--lazy/src/lib.rs3
-rw-r--r--lazy/src/pure.rs1
-rw-r--r--lazy/src/pure/component.rs500
-rw-r--r--pure/src/overlay.rs6
-rw-r--r--pure/src/widget.rs4
-rw-r--r--pure/src/widget/button.rs4
-rw-r--r--pure/src/widget/column.rs4
-rw-r--r--pure/src/widget/container.rs4
-rw-r--r--pure/src/widget/pick_list.rs2
-rw-r--r--pure/src/widget/row.rs4
-rw-r--r--pure/src/widget/scrollable.rs4
-rw-r--r--pure/src/widget/space.rs7
13 files changed, 530 insertions, 21 deletions
diff --git a/lazy/Cargo.toml b/lazy/Cargo.toml
index b840de50..2d7451f3 100644
--- a/lazy/Cargo.toml
+++ b/lazy/Cargo.toml
@@ -3,9 +3,17 @@ name = "iced_lazy"
version = "0.1.0"
edition = "2021"
+[features]
+pure = ["iced_pure"]
+
[dependencies]
ouroboros = "0.13"
[dependencies.iced_native]
version = "0.4"
path = "../native"
+
+[dependencies.iced_pure]
+version = "0.1"
+path = "../pure"
+optional = true
diff --git a/lazy/src/lib.rs b/lazy/src/lib.rs
index 05fce765..5d7d10e4 100644
--- a/lazy/src/lib.rs
+++ b/lazy/src/lib.rs
@@ -1,6 +1,9 @@
pub mod component;
pub mod responsive;
+#[cfg(feature = "pure")]
+pub mod pure;
+
pub use component::Component;
pub use responsive::Responsive;
diff --git a/lazy/src/pure.rs b/lazy/src/pure.rs
new file mode 100644
index 00000000..9cea807e
--- /dev/null
+++ b/lazy/src/pure.rs
@@ -0,0 +1 @@
+pub mod component;
diff --git a/lazy/src/pure/component.rs b/lazy/src/pure/component.rs
new file mode 100644
index 00000000..3061e45d
--- /dev/null
+++ b/lazy/src/pure/component.rs
@@ -0,0 +1,500 @@
+//! Build and reuse custom widgets using The Elm Architecture.
+use iced_native::event;
+use iced_native::layout::{self, Layout};
+use iced_native::mouse;
+use iced_native::overlay;
+use iced_native::renderer;
+use iced_native::{Clipboard, Hasher, Length, Point, Rectangle, Shell, Size};
+use iced_pure::widget::tree::{self, Tree};
+use iced_pure::{Element, Widget};
+
+use ouroboros::self_referencing;
+use std::cell::{Ref, RefCell};
+use std::hash::Hash;
+use std::marker::PhantomData;
+
+/// A reusable, custom widget that uses The Elm Architecture.
+///
+/// A [`Component`] allows you to implement custom widgets as if they were
+/// `iced` applications with encapsulated state.
+///
+/// In other words, a [`Component`] allows you to turn `iced` applications into
+/// custom widgets and embed them without cumbersome wiring.
+///
+/// A [`Component`] produces widgets that may fire an [`Event`](Component::Event)
+/// and update the internal state of the [`Component`].
+///
+/// Additionally, a [`Component`] is capable of producing a `Message` to notify
+/// the parent application of any relevant interactions.
+pub trait Component<Message, Renderer> {
+ /// The internal state of this [`Component`].
+ type State: Default;
+
+ /// The type of event this [`Component`] handles internally.
+ type Event;
+
+ /// Processes an [`Event`](Component::Event) and updates the [`Component`] state accordingly.
+ ///
+ /// It can produce a `Message` for the parent application.
+ fn update(
+ &mut self,
+ state: &mut Self::State,
+ event: Self::Event,
+ ) -> Option<Message>;
+
+ /// Produces the widgets of the [`Component`], which may trigger an [`Event`](Component::Event)
+ /// on user interaction.
+ fn view(&self, state: &Self::State) -> Element<Self::Event, Renderer>;
+}
+
+/// Turns an implementor of [`Component`] into an [`Element`] that can be
+/// embedded in any application.
+pub fn view<'a, C, Message, Renderer>(
+ component: C,
+) -> Element<'a, Message, Renderer>
+where
+ C: Component<Message, Renderer> + 'a,
+ C::State: 'static,
+ Message: 'a,
+ Renderer: iced_native::Renderer + 'a,
+{
+ Element::new(Instance {
+ state: RefCell::new(Some(
+ StateBuilder {
+ component: Box::new(component),
+ message: PhantomData,
+ state: PhantomData,
+ element_builder: |_| None,
+ }
+ .build(),
+ )),
+ })
+}
+
+struct Instance<'a, Message, Renderer, Event, S> {
+ state: RefCell<Option<State<'a, Message, Renderer, Event, S>>>,
+}
+
+#[self_referencing]
+struct State<'a, Message: 'a, Renderer: 'a, Event: 'a, S: 'a> {
+ component:
+ Box<dyn Component<Message, Renderer, Event = Event, State = S> + 'a>,
+ message: PhantomData<Message>,
+ state: PhantomData<S>,
+
+ #[borrows(component)]
+ #[covariant]
+ element: Option<Element<'this, Event, Renderer>>,
+}
+
+impl<'a, Message, Renderer, Event, S> Instance<'a, Message, Renderer, Event, S>
+where
+ S: Default,
+{
+ fn with_element<T>(
+ &self,
+ f: impl FnOnce(&Element<'_, Event, Renderer>) -> T,
+ ) -> T {
+ self.with_element_mut(|element| f(element))
+ }
+
+ fn with_element_mut<T>(
+ &self,
+ f: impl FnOnce(&mut Element<'_, Event, Renderer>) -> T,
+ ) -> T {
+ if self
+ .state
+ .borrow()
+ .as_ref()
+ .unwrap()
+ .borrow_element()
+ .is_none()
+ {
+ let heads = self.state.borrow_mut().take().unwrap().into_heads();
+
+ *self.state.borrow_mut() = Some(
+ StateBuilder {
+ component: heads.component,
+ message: PhantomData,
+ state: PhantomData,
+ element_builder: |state| Some(state.view(&S::default())),
+ }
+ .build(),
+ );
+ }
+
+ self.state
+ .borrow_mut()
+ .as_mut()
+ .unwrap()
+ .with_element_mut(|element| f(element.as_mut().unwrap()))
+ }
+}
+
+impl<'a, Message, Renderer, Event, S> Widget<Message, Renderer>
+ for Instance<'a, Message, Renderer, Event, S>
+where
+ S: 'static + Default,
+ Renderer: iced_native::Renderer,
+{
+ fn tag(&self) -> tree::Tag {
+ tree::Tag::of::<S>()
+ }
+
+ fn state(&self) -> tree::State {
+ tree::State::new(S::default())
+ }
+
+ fn children(&self) -> Vec<Tree> {
+ self.with_element(|element| vec![Tree::new(element)])
+ }
+
+ fn diff(&self, tree: &mut Tree) {
+ self.with_element(|element| {
+ tree.diff_children(std::slice::from_ref(&element))
+ })
+ }
+
+ fn width(&self) -> Length {
+ self.with_element(|element| element.as_widget().width())
+ }
+
+ fn height(&self) -> Length {
+ self.with_element(|element| element.as_widget().height())
+ }
+
+ fn layout(
+ &self,
+ renderer: &Renderer,
+ limits: &layout::Limits,
+ ) -> layout::Node {
+ self.with_element(|element| {
+ element.as_widget().layout(renderer, limits)
+ })
+ }
+
+ fn on_event(
+ &mut self,
+ tree: &mut Tree,
+ event: iced_native::Event,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ renderer: &Renderer,
+ clipboard: &mut dyn Clipboard,
+ shell: &mut Shell<'_, Message>,
+ ) -> event::Status {
+ let mut local_messages = Vec::new();
+ let mut local_shell = Shell::new(&mut local_messages);
+
+ let event_status = self.with_element_mut(|element| {
+ element.as_widget_mut().on_event(
+ &mut tree.children[0],
+ event,
+ layout,
+ cursor_position,
+ renderer,
+ clipboard,
+ &mut local_shell,
+ )
+ });
+
+ local_shell.revalidate_layout(|| shell.invalidate_layout());
+
+ if !local_messages.is_empty() {
+ let mut heads = self.state.take().unwrap().into_heads();
+
+ for message in local_messages.into_iter().filter_map(|message| {
+ heads
+ .component
+ .update(tree.state.downcast_mut::<S>(), message)
+ }) {
+ shell.publish(message);
+ }
+
+ self.state = RefCell::new(Some(
+ StateBuilder {
+ component: heads.component,
+ message: PhantomData,
+ state: PhantomData,
+ element_builder: |state| {
+ Some(state.view(tree.state.downcast_ref::<S>()))
+ },
+ }
+ .build(),
+ ));
+
+ shell.invalidate_layout();
+ }
+
+ event_status
+ }
+
+ fn draw(
+ &self,
+ tree: &Tree,
+ renderer: &mut Renderer,
+ style: &renderer::Style,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ ) {
+ self.with_element(|element| {
+ element.as_widget().draw(
+ &tree.children[0],
+ renderer,
+ style,
+ layout,
+ cursor_position,
+ viewport,
+ );
+ });
+ }
+
+ fn hash_layout(&self, state: &mut Hasher) {
+ self.with_element(|element| {
+ element.as_widget().hash_layout(state);
+ });
+ }
+
+ fn mouse_interaction(
+ &self,
+ tree: &Tree,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ renderer: &Renderer,
+ ) -> mouse::Interaction {
+ self.with_element(|element| {
+ element.as_widget().mouse_interaction(
+ &tree.children[0],
+ layout,
+ cursor_position,
+ viewport,
+ renderer,
+ )
+ })
+ }
+
+ fn overlay<'b>(
+ &'b self,
+ tree: &'b mut Tree,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ ) -> Option<overlay::Element<'b, Message, Renderer>> {
+ let overlay = OverlayBuilder {
+ instance: self,
+ instance_ref_builder: |instance| instance.state.borrow(),
+ tree,
+ types: PhantomData,
+ overlay_builder: |instance, tree| {
+ instance
+ .as_ref()
+ .unwrap()
+ .borrow_element()
+ .as_ref()
+ .unwrap()
+ .as_widget()
+ .overlay(&mut tree.children[0], layout, renderer)
+ },
+ }
+ .build();
+
+ let has_overlay = overlay.with_overlay(|overlay| {
+ overlay.as_ref().map(overlay::Element::position)
+ });
+
+ has_overlay.map(|position| {
+ overlay::Element::new(
+ position,
+ Box::new(OverlayInstance {
+ overlay: Some(overlay),
+ }),
+ )
+ })
+ }
+}
+
+#[self_referencing]
+struct Overlay<'a, 'b, Message, Renderer, Event, S> {
+ instance: &'a Instance<'b, Message, Renderer, Event, S>,
+ tree: &'a mut Tree,
+ types: PhantomData<(Message, Event, S)>,
+
+ #[borrows(instance)]
+ #[covariant]
+ instance_ref: Ref<'this, Option<State<'a, Message, Renderer, Event, S>>>,
+
+ #[borrows(instance_ref, mut tree)]
+ #[covariant]
+ overlay: Option<overlay::Element<'this, Event, Renderer>>,
+}
+
+struct OverlayInstance<'a, 'b, Message, Renderer, Event, S> {
+ overlay: Option<Overlay<'a, 'b, Message, Renderer, Event, S>>,
+}
+
+impl<'a, 'b, Message, Renderer, Event, S>
+ OverlayInstance<'a, 'b, Message, Renderer, Event, S>
+{
+ fn with_overlay_maybe<T>(
+ &self,
+ f: impl FnOnce(&overlay::Element<'_, Event, Renderer>) -> T,
+ ) -> Option<T> {
+ self.overlay
+ .as_ref()
+ .unwrap()
+ .borrow_overlay()
+ .as_ref()
+ .map(f)
+ }
+
+ fn with_overlay_mut_maybe<T>(
+ &mut self,
+ f: impl FnOnce(&mut overlay::Element<'_, Event, Renderer>) -> T,
+ ) -> Option<T> {
+ self.overlay
+ .as_mut()
+ .unwrap()
+ .with_overlay_mut(|overlay| overlay.as_mut().map(f))
+ }
+}
+
+impl<'a, 'b, Message, Renderer, Event, S> overlay::Overlay<Message, Renderer>
+ for OverlayInstance<'a, 'b, Message, Renderer, Event, S>
+where
+ Renderer: iced_native::Renderer,
+ S: 'static + Default,
+{
+ fn layout(
+ &self,
+ renderer: &Renderer,
+ bounds: Size,
+ position: Point,
+ ) -> layout::Node {
+ self.with_overlay_maybe(|overlay| {
+ let vector = position - overlay.position();
+
+ overlay.layout(renderer, bounds).translate(vector)
+ })
+ .unwrap_or_default()
+ }
+
+ fn draw(
+ &self,
+ renderer: &mut Renderer,
+ style: &renderer::Style,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ ) {
+ self.with_overlay_maybe(|overlay| {
+ overlay.draw(renderer, style, layout, cursor_position);
+ });
+ }
+
+ fn mouse_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ renderer: &Renderer,
+ ) -> mouse::Interaction {
+ self.with_overlay_maybe(|overlay| {
+ overlay.mouse_interaction(
+ layout,
+ cursor_position,
+ viewport,
+ renderer,
+ )
+ })
+ .unwrap_or_default()
+ }
+
+ fn hash_layout(&self, state: &mut Hasher, position: Point) {
+ struct Marker;
+ std::any::TypeId::of::<Marker>().hash(state);
+
+ (position.x as u32).hash(state);
+ (position.y as u32).hash(state);
+
+ self.with_overlay_maybe(|overlay| {
+ overlay.hash_layout(state);
+ });
+ }
+
+ fn on_event(
+ &mut self,
+ event: iced_native::Event,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ renderer: &Renderer,
+ clipboard: &mut dyn Clipboard,
+ shell: &mut Shell<'_, Message>,
+ ) -> iced_native::event::Status {
+ let mut local_messages = Vec::new();
+ let mut local_shell = Shell::new(&mut local_messages);
+
+ let event_status = self
+ .with_overlay_mut_maybe(|overlay| {
+ overlay.on_event(
+ event,
+ layout,
+ cursor_position,
+ renderer,
+ clipboard,
+ &mut local_shell,
+ )
+ })
+ .unwrap_or_else(|| iced_native::event::Status::Ignored);
+
+ local_shell.revalidate_layout(|| shell.invalidate_layout());
+
+ if !local_messages.is_empty() {
+ let overlay = self.overlay.take().unwrap().into_heads();
+ let mut heads = overlay.instance.state.take().unwrap().into_heads();
+
+ for message in local_messages.into_iter().filter_map(|message| {
+ heads
+ .component
+ .update(overlay.tree.state.downcast_mut::<S>(), message)
+ }) {
+ shell.publish(message);
+ }
+
+ *overlay.instance.state.borrow_mut() = Some(
+ StateBuilder {
+ component: heads.component,
+ message: PhantomData,
+ state: PhantomData,
+ element_builder: |state| {
+ Some(state.view(overlay.tree.state.downcast_ref::<S>()))
+ },
+ }
+ .build(),
+ );
+
+ self.overlay = Some(
+ OverlayBuilder {
+ instance: overlay.instance,
+ instance_ref_builder: |instance| instance.state.borrow(),
+ tree: overlay.tree,
+ types: PhantomData,
+ overlay_builder: |instance, tree| {
+ instance
+ .as_ref()
+ .unwrap()
+ .borrow_element()
+ .as_ref()
+ .unwrap()
+ .as_widget()
+ .overlay(tree, layout, renderer)
+ },
+ }
+ .build(),
+ );
+
+ shell.invalidate_layout();
+ }
+
+ event_status
+ }
+}
diff --git a/pure/src/overlay.rs b/pure/src/overlay.rs
index b009fde8..72415634 100644
--- a/pure/src/overlay.rs
+++ b/pure/src/overlay.rs
@@ -5,17 +5,17 @@ use iced_native::Layout;
pub use iced_native::overlay::*;
pub fn from_children<'a, Message, Renderer>(
- children: &'a mut [crate::Element<'_, Message, Renderer>],
+ children: &'a [crate::Element<'_, Message, Renderer>],
tree: &'a mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<Element<'a, Message, Renderer>> {
children
- .iter_mut()
+ .iter()
.zip(&mut tree.children)
.zip(layout.children())
.filter_map(|((child, state), layout)| {
- child.as_widget_mut().overlay(state, layout, renderer)
+ child.as_widget().overlay(state, layout, renderer)
})
.next()
}
diff --git a/pure/src/widget.rs b/pure/src/widget.rs
index 62f9d95b..a12d6fad 100644
--- a/pure/src/widget.rs
+++ b/pure/src/widget.rs
@@ -1,4 +1,5 @@
pub mod image;
+pub mod tree;
mod button;
mod checkbox;
@@ -14,7 +15,6 @@ mod space;
mod text;
mod text_input;
mod toggler;
-mod tree;
pub use button::Button;
pub use checkbox::Checkbox;
@@ -104,7 +104,7 @@ pub trait Widget<Message, Renderer> {
}
fn overlay<'a>(
- &'a mut self,
+ &'a self,
_state: &'a mut Tree,
_layout: Layout<'_>,
_renderer: &Renderer,
diff --git a/pure/src/widget/button.rs b/pure/src/widget/button.rs
index f5e78933..2ed67a9c 100644
--- a/pure/src/widget/button.rs
+++ b/pure/src/widget/button.rs
@@ -209,12 +209,12 @@ where
}
fn overlay<'b>(
- &'b mut self,
+ &'b self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
- self.content.as_widget_mut().overlay(
+ self.content.as_widget().overlay(
&mut tree.children[0],
layout.children().next().unwrap(),
renderer,
diff --git a/pure/src/widget/column.rs b/pure/src/widget/column.rs
index 1f025335..698d7e9c 100644
--- a/pure/src/widget/column.rs
+++ b/pure/src/widget/column.rs
@@ -219,12 +219,12 @@ where
}
fn overlay<'b>(
- &'b mut self,
+ &'b self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
- overlay::from_children(&mut self.children, tree, layout, renderer)
+ overlay::from_children(&self.children, tree, layout, renderer)
}
}
diff --git a/pure/src/widget/container.rs b/pure/src/widget/container.rs
index 8ad6a064..c8f0b3a2 100644
--- a/pure/src/widget/container.rs
+++ b/pure/src/widget/container.rs
@@ -240,12 +240,12 @@ where
}
fn overlay<'b>(
- &'b mut self,
+ &'b self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
- self.content.as_widget_mut().overlay(
+ self.content.as_widget().overlay(
&mut tree.children[0],
layout.children().next().unwrap(),
renderer,
diff --git a/pure/src/widget/pick_list.rs b/pure/src/widget/pick_list.rs
index 324950e1..9dc847ee 100644
--- a/pure/src/widget/pick_list.rs
+++ b/pure/src/widget/pick_list.rs
@@ -212,7 +212,7 @@ where
}
fn overlay<'b>(
- &'b mut self,
+ &'b self,
tree: &'b mut Tree,
layout: Layout<'_>,
_renderer: &Renderer,
diff --git a/pure/src/widget/row.rs b/pure/src/widget/row.rs
index 29128589..1c574d51 100644
--- a/pure/src/widget/row.rs
+++ b/pure/src/widget/row.rs
@@ -205,12 +205,12 @@ where
}
fn overlay<'b>(
- &'b mut self,
+ &'b self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
- overlay::from_children(&mut self.children, tree, layout, renderer)
+ overlay::from_children(&self.children, tree, layout, renderer)
}
}
diff --git a/pure/src/widget/scrollable.rs b/pure/src/widget/scrollable.rs
index 8a206a6c..bbda50e5 100644
--- a/pure/src/widget/scrollable.rs
+++ b/pure/src/widget/scrollable.rs
@@ -233,13 +233,13 @@ where
}
fn overlay<'b>(
- &'b mut self,
+ &'b self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
self.content
- .as_widget_mut()
+ .as_widget()
.overlay(
&mut tree.children[0],
layout.children().next().unwrap(),
diff --git a/pure/src/widget/space.rs b/pure/src/widget/space.rs
index d7394398..e0c9c285 100644
--- a/pure/src/widget/space.rs
+++ b/pure/src/widget/space.rs
@@ -4,15 +4,13 @@ use iced_native::event::{self, Event};
use iced_native::layout::{self, Layout};
use iced_native::mouse;
use iced_native::renderer;
-use iced_native::text;
use iced_native::{Clipboard, Hasher, Length, Point, Rectangle, Shell};
pub use iced_native::widget::Space;
impl<'a, Message, Renderer> Widget<Message, Renderer> for Space
where
- Message: Clone,
- Renderer: text::Renderer,
+ Renderer: iced_native::Renderer,
{
fn width(&self) -> Length {
<Self as iced_native::Widget<Message, Renderer>>::width(self)
@@ -98,8 +96,7 @@ where
impl<'a, Message, Renderer> Into<Element<'a, Message, Renderer>> for Space
where
- Message: 'a + Clone,
- Renderer: text::Renderer + 'a,
+ Renderer: iced_native::Renderer + 'a,
{
fn into(self) -> Element<'a, Message, Renderer> {
Element::new(self)