Replace stateful widgets with new iced_pure API
This commit is contained in:
parent
c44267b85f
commit
ff2519b1d4
142 changed files with 3631 additions and 14494 deletions
|
|
@ -1,17 +1,16 @@
|
|||
//! Build and reuse custom widgets using The Elm Architecture.
|
||||
use crate::{Cache, CacheBuilder};
|
||||
|
||||
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::widget::tree::{self, Tree};
|
||||
use iced_native::{
|
||||
Clipboard, Element, Length, Point, Rectangle, Shell, Size, Widget,
|
||||
};
|
||||
|
||||
use ouroboros::self_referencing;
|
||||
use std::cell::RefCell;
|
||||
use std::cell::{Ref, RefCell};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
/// A reusable, custom widget that uses The Elm Architecture.
|
||||
|
|
@ -28,17 +27,24 @@ use std::marker::PhantomData;
|
|||
/// 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, event: Self::Event) -> Option<Message>;
|
||||
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(&mut self) -> Element<'_, Self::Event, Renderer>;
|
||||
fn view(&self, state: &Self::State) -> Element<'_, Self::Event, Renderer>;
|
||||
}
|
||||
|
||||
/// Turns an implementor of [`Component`] into an [`Element`] that can be
|
||||
|
|
@ -48,6 +54,7 @@ pub fn view<'a, C, Message, Renderer>(
|
|||
) -> Element<'a, Message, Renderer>
|
||||
where
|
||||
C: Component<Message, Renderer> + 'a,
|
||||
C::State: 'static,
|
||||
Message: 'a,
|
||||
Renderer: iced_native::Renderer + 'a,
|
||||
{
|
||||
|
|
@ -56,36 +63,48 @@ where
|
|||
StateBuilder {
|
||||
component: Box::new(component),
|
||||
message: PhantomData,
|
||||
cache_builder: |state| {
|
||||
Some(
|
||||
CacheBuilder {
|
||||
element: state.view(),
|
||||
overlay_builder: |_| None,
|
||||
}
|
||||
.build(),
|
||||
)
|
||||
},
|
||||
state: PhantomData,
|
||||
element_builder: |_| None,
|
||||
}
|
||||
.build(),
|
||||
)),
|
||||
})
|
||||
}
|
||||
|
||||
struct Instance<'a, Message, Renderer, Event> {
|
||||
state: RefCell<Option<State<'a, Message, Renderer, Event>>>,
|
||||
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> {
|
||||
component: Box<dyn Component<Message, Renderer, Event = Event> + 'a>,
|
||||
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(mut component)]
|
||||
#[borrows(component)]
|
||||
#[covariant]
|
||||
cache: Option<Cache<'this, Event, Renderer>>,
|
||||
element: Option<Element<'this, Event, Renderer>>,
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer, Event> Instance<'a, Message, Renderer, Event> {
|
||||
impl<'a, Message, Renderer, Event, S> Instance<'a, Message, Renderer, Event, S>
|
||||
where
|
||||
S: Default,
|
||||
{
|
||||
fn rebuild_element(&self, state: &S) {
|
||||
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: |component| Some(component.view(state)),
|
||||
}
|
||||
.build(),
|
||||
);
|
||||
}
|
||||
|
||||
fn with_element<T>(
|
||||
&self,
|
||||
f: impl FnOnce(&Element<'_, Event, Renderer>) -> T,
|
||||
|
|
@ -101,34 +120,43 @@ impl<'a, Message, Renderer, Event> Instance<'a, Message, Renderer, Event> {
|
|||
.borrow_mut()
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.with_cache_mut(|cache| {
|
||||
let mut element = cache.take().unwrap().into_heads().element;
|
||||
let result = f(&mut element);
|
||||
|
||||
*cache = Some(
|
||||
CacheBuilder {
|
||||
element,
|
||||
overlay_builder: |_| None,
|
||||
}
|
||||
.build(),
|
||||
);
|
||||
|
||||
result
|
||||
})
|
||||
.with_element_mut(|element| f(element.as_mut().unwrap()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer, Event> Widget<Message, Renderer>
|
||||
for Instance<'a, Message, Renderer, Event>
|
||||
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 {
|
||||
struct Tag<T>(T);
|
||||
tree::Tag::of::<Tag<S>>()
|
||||
}
|
||||
|
||||
fn state(&self) -> tree::State {
|
||||
tree::State::new(S::default())
|
||||
}
|
||||
|
||||
fn children(&self) -> Vec<Tree> {
|
||||
self.rebuild_element(&S::default());
|
||||
self.with_element(|element| vec![Tree::new(element)])
|
||||
}
|
||||
|
||||
fn diff(&self, tree: &mut Tree) {
|
||||
self.rebuild_element(tree.state.downcast_ref());
|
||||
self.with_element(|element| {
|
||||
tree.diff_children(std::slice::from_ref(&element))
|
||||
})
|
||||
}
|
||||
|
||||
fn width(&self) -> Length {
|
||||
self.with_element(|element| element.width())
|
||||
self.with_element(|element| element.as_widget().width())
|
||||
}
|
||||
|
||||
fn height(&self) -> Length {
|
||||
self.with_element(|element| element.height())
|
||||
self.with_element(|element| element.as_widget().height())
|
||||
}
|
||||
|
||||
fn layout(
|
||||
|
|
@ -136,11 +164,14 @@ where
|
|||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
self.with_element(|element| element.layout(renderer, limits))
|
||||
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,
|
||||
|
|
@ -152,7 +183,8 @@ where
|
|||
let mut local_shell = Shell::new(&mut local_messages);
|
||||
|
||||
let event_status = self.with_element_mut(|element| {
|
||||
element.on_event(
|
||||
element.as_widget_mut().on_event(
|
||||
&mut tree.children[0],
|
||||
event,
|
||||
layout,
|
||||
cursor_position,
|
||||
|
|
@ -165,37 +197,31 @@ where
|
|||
local_shell.revalidate_layout(|| shell.invalidate_layout());
|
||||
|
||||
if !local_messages.is_empty() {
|
||||
let mut component = self
|
||||
.state
|
||||
.borrow_mut()
|
||||
.take()
|
||||
.unwrap()
|
||||
.into_heads()
|
||||
.component;
|
||||
let mut heads = self.state.take().unwrap().into_heads();
|
||||
|
||||
for message in local_messages
|
||||
.into_iter()
|
||||
.filter_map(|message| component.update(message))
|
||||
{
|
||||
for message in local_messages.into_iter().filter_map(|message| {
|
||||
heads
|
||||
.component
|
||||
.update(tree.state.downcast_mut::<S>(), message)
|
||||
}) {
|
||||
shell.publish(message);
|
||||
}
|
||||
|
||||
*self.state.borrow_mut() = Some(
|
||||
self.state = RefCell::new(Some(
|
||||
StateBuilder {
|
||||
component,
|
||||
component: heads.component,
|
||||
message: PhantomData,
|
||||
cache_builder: |state| {
|
||||
Some(
|
||||
CacheBuilder {
|
||||
element: state.view(),
|
||||
overlay_builder: |_| None,
|
||||
}
|
||||
.build(),
|
||||
)
|
||||
state: PhantomData,
|
||||
element_builder: |state| {
|
||||
Some(state.view(tree.state.downcast_ref::<S>()))
|
||||
},
|
||||
}
|
||||
.build(),
|
||||
);
|
||||
));
|
||||
|
||||
self.with_element(|element| {
|
||||
tree.diff_children(std::slice::from_ref(&element))
|
||||
});
|
||||
|
||||
shell.invalidate_layout();
|
||||
}
|
||||
|
|
@ -205,6 +231,7 @@ where
|
|||
|
||||
fn draw(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
style: &renderer::Style,
|
||||
|
|
@ -213,7 +240,8 @@ where
|
|||
viewport: &Rectangle,
|
||||
) {
|
||||
self.with_element(|element| {
|
||||
element.draw(
|
||||
element.as_widget().draw(
|
||||
&tree.children[0],
|
||||
renderer,
|
||||
theme,
|
||||
style,
|
||||
|
|
@ -226,13 +254,15 @@ where
|
|||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
self.with_element(|element| {
|
||||
element.mouse_interaction(
|
||||
element.as_widget().mouse_interaction(
|
||||
&tree.children[0],
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
|
|
@ -241,63 +271,72 @@ where
|
|||
})
|
||||
}
|
||||
|
||||
fn overlay(
|
||||
&mut self,
|
||||
fn overlay<'b>(
|
||||
&'b self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'_, Message, Renderer>> {
|
||||
let has_overlay = self
|
||||
.state
|
||||
.borrow_mut()
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.with_cache_mut(|cache| {
|
||||
let element = cache.take().unwrap().into_heads().element;
|
||||
|
||||
*cache = Some(
|
||||
CacheBuilder {
|
||||
element,
|
||||
overlay_builder: |element| {
|
||||
element.overlay(layout, renderer)
|
||||
},
|
||||
}
|
||||
.build(),
|
||||
);
|
||||
|
||||
cache
|
||||
) -> 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_overlay()
|
||||
.borrow_element()
|
||||
.as_ref()
|
||||
.map(|overlay| overlay.position())
|
||||
});
|
||||
.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(Overlay { instance: self }),
|
||||
Box::new(OverlayInstance {
|
||||
overlay: Some(overlay),
|
||||
}),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct Overlay<'a, 'b, Message, Renderer, Event> {
|
||||
instance: &'b mut Instance<'a, Message, Renderer, Event>,
|
||||
#[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>>,
|
||||
}
|
||||
|
||||
impl<'a, 'b, Message, Renderer, Event>
|
||||
Overlay<'a, 'b, Message, Renderer, Event>
|
||||
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.instance
|
||||
.state
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.borrow_cache()
|
||||
self.overlay
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.borrow_overlay()
|
||||
|
|
@ -306,27 +345,21 @@ impl<'a, 'b, Message, Renderer, Event>
|
|||
}
|
||||
|
||||
fn with_overlay_mut_maybe<T>(
|
||||
&self,
|
||||
&mut self,
|
||||
f: impl FnOnce(&mut overlay::Element<'_, Event, Renderer>) -> T,
|
||||
) -> Option<T> {
|
||||
self.instance
|
||||
.state
|
||||
.borrow_mut()
|
||||
self.overlay
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.with_cache_mut(|cache| {
|
||||
cache
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.with_overlay_mut(|overlay| overlay.as_mut().map(f))
|
||||
})
|
||||
.with_overlay_mut(|overlay| overlay.as_mut().map(f))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b, Message, Renderer, Event> overlay::Overlay<Message, Renderer>
|
||||
for Overlay<'a, 'b, Message, Renderer, Event>
|
||||
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,
|
||||
|
|
@ -401,32 +434,43 @@ where
|
|||
local_shell.revalidate_layout(|| shell.invalidate_layout());
|
||||
|
||||
if !local_messages.is_empty() {
|
||||
let mut component =
|
||||
self.instance.state.take().unwrap().into_heads().component;
|
||||
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| component.update(message))
|
||||
{
|
||||
for message in local_messages.into_iter().filter_map(|message| {
|
||||
heads
|
||||
.component
|
||||
.update(overlay.tree.state.downcast_mut::<S>(), message)
|
||||
}) {
|
||||
shell.publish(message);
|
||||
}
|
||||
|
||||
self.instance.state = RefCell::new(Some(
|
||||
*overlay.instance.state.borrow_mut() = Some(
|
||||
StateBuilder {
|
||||
component,
|
||||
component: heads.component,
|
||||
message: PhantomData,
|
||||
cache_builder: |state| {
|
||||
Some(
|
||||
CacheBuilder {
|
||||
element: state.view(),
|
||||
overlay_builder: |_| None,
|
||||
}
|
||||
.build(),
|
||||
)
|
||||
state: PhantomData,
|
||||
element_builder: |state| {
|
||||
Some(state.view(overlay.tree.state.downcast_ref::<S>()))
|
||||
},
|
||||
}
|
||||
.build(),
|
||||
));
|
||||
);
|
||||
|
||||
overlay.instance.with_element(|element| {
|
||||
overlay.tree.diff_children(std::slice::from_ref(&element))
|
||||
});
|
||||
|
||||
self.overlay = Some(
|
||||
OverlayBuilder {
|
||||
instance: overlay.instance,
|
||||
instance_ref_builder: |instance| instance.state.borrow(),
|
||||
tree: overlay.tree,
|
||||
types: PhantomData,
|
||||
overlay_builder: |_, _| None,
|
||||
}
|
||||
.build(),
|
||||
);
|
||||
|
||||
shell.invalidate_layout();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue