Create iced_widget subcrate and re-organize the whole codebase
This commit is contained in:
parent
c54409d171
commit
3a0d34c024
209 changed files with 1959 additions and 2183 deletions
13
widget/src/lazy/cache.rs
Normal file
13
widget/src/lazy/cache.rs
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
use crate::core::overlay;
|
||||
use crate::core::Element;
|
||||
|
||||
use ouroboros::self_referencing;
|
||||
|
||||
#[self_referencing(pub_extras)]
|
||||
pub struct Cache<'a, Message: 'a, Renderer: 'a> {
|
||||
pub element: Element<'a, Message, Renderer>,
|
||||
|
||||
#[borrows(mut element)]
|
||||
#[covariant]
|
||||
overlay: Option<overlay::Element<'this, Message, Renderer>>,
|
||||
}
|
||||
575
widget/src/lazy/component.rs
Normal file
575
widget/src/lazy/component.rs
Normal file
|
|
@ -0,0 +1,575 @@
|
|||
//! Build and reuse custom widgets using The Elm Architecture.
|
||||
use crate::core::event;
|
||||
use crate::core::layout::{self, Layout};
|
||||
use crate::core::mouse;
|
||||
use crate::core::overlay;
|
||||
use crate::core::renderer;
|
||||
use crate::core::widget;
|
||||
use crate::core::widget::tree::{self, Tree};
|
||||
use crate::core::{
|
||||
self, Clipboard, Element, Length, Point, Rectangle, Shell, Size, Widget,
|
||||
};
|
||||
|
||||
use ouroboros::self_referencing;
|
||||
use std::cell::RefCell;
|
||||
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>;
|
||||
|
||||
/// Update the [`Component`] state based on the provided [`Operation`](widget::Operation)
|
||||
///
|
||||
/// By default, it does nothing.
|
||||
fn operate(
|
||||
&self,
|
||||
_state: &mut Self::State,
|
||||
_operation: &mut dyn widget::Operation<Message>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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: core::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 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 rebuild_element_with_operation(
|
||||
&self,
|
||||
state: &mut S,
|
||||
operation: &mut dyn widget::Operation<Message>,
|
||||
) {
|
||||
let heads = self.state.borrow_mut().take().unwrap().into_heads();
|
||||
|
||||
heads.component.operate(state, operation);
|
||||
|
||||
*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,
|
||||
) -> T {
|
||||
self.with_element_mut(|element| f(element))
|
||||
}
|
||||
|
||||
fn with_element_mut<T>(
|
||||
&self,
|
||||
f: impl FnOnce(&mut Element<'_, Event, Renderer>) -> T,
|
||||
) -> T {
|
||||
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: core::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.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: core::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 let Some(redraw_request) = local_shell.redraw_request() {
|
||||
shell.request_redraw(redraw_request);
|
||||
}
|
||||
|
||||
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(),
|
||||
));
|
||||
|
||||
self.with_element(|element| {
|
||||
tree.diff_children(std::slice::from_ref(&element))
|
||||
});
|
||||
|
||||
shell.invalidate_layout();
|
||||
}
|
||||
|
||||
event_status
|
||||
}
|
||||
|
||||
fn operate(
|
||||
&self,
|
||||
tree: &mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
operation: &mut dyn widget::Operation<Message>,
|
||||
) {
|
||||
self.rebuild_element_with_operation(
|
||||
tree.state.downcast_mut(),
|
||||
operation,
|
||||
);
|
||||
|
||||
struct MapOperation<'a, B> {
|
||||
operation: &'a mut dyn widget::Operation<B>,
|
||||
}
|
||||
|
||||
impl<'a, T, B> widget::Operation<T> for MapOperation<'a, B> {
|
||||
fn container(
|
||||
&mut self,
|
||||
id: Option<&widget::Id>,
|
||||
operate_on_children: &mut dyn FnMut(
|
||||
&mut dyn widget::Operation<T>,
|
||||
),
|
||||
) {
|
||||
self.operation.container(id, &mut |operation| {
|
||||
operate_on_children(&mut MapOperation { operation });
|
||||
});
|
||||
}
|
||||
|
||||
fn focusable(
|
||||
&mut self,
|
||||
state: &mut dyn widget::operation::Focusable,
|
||||
id: Option<&widget::Id>,
|
||||
) {
|
||||
self.operation.focusable(state, id);
|
||||
}
|
||||
|
||||
fn text_input(
|
||||
&mut self,
|
||||
state: &mut dyn widget::operation::TextInput,
|
||||
id: Option<&widget::Id>,
|
||||
) {
|
||||
self.operation.text_input(state, id);
|
||||
}
|
||||
}
|
||||
|
||||
self.with_element(|element| {
|
||||
tree.diff_children(std::slice::from_ref(&element));
|
||||
|
||||
element.as_widget().operate(
|
||||
&mut tree.children[0],
|
||||
layout,
|
||||
renderer,
|
||||
&mut MapOperation { operation },
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
self.with_element(|element| {
|
||||
element.as_widget().draw(
|
||||
&tree.children[0],
|
||||
renderer,
|
||||
theme,
|
||||
style,
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
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 mut self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
||||
let overlay = OverlayBuilder {
|
||||
instance: self,
|
||||
tree,
|
||||
types: PhantomData,
|
||||
overlay_builder: |instance, tree| {
|
||||
instance.state.get_mut().as_mut().unwrap().with_element_mut(
|
||||
move |element| {
|
||||
element.as_mut().unwrap().as_widget_mut().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 mut Instance<'b, Message, Renderer, Event, S>,
|
||||
tree: &'a mut Tree,
|
||||
types: PhantomData<(Message, Event, S)>,
|
||||
|
||||
#[borrows(mut instance, 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: core::Renderer,
|
||||
S: 'static + Default,
|
||||
{
|
||||
fn layout(
|
||||
&self,
|
||||
renderer: &Renderer,
|
||||
bounds: Size,
|
||||
position: Point,
|
||||
) -> layout::Node {
|
||||
self.with_overlay_maybe(|overlay| {
|
||||
let translation = position - overlay.position();
|
||||
|
||||
overlay.layout(renderer, bounds, translation)
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
) {
|
||||
let _ = self.with_overlay_maybe(|overlay| {
|
||||
overlay.draw(renderer, theme, 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 on_event(
|
||||
&mut self,
|
||||
event: core::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_overlay_mut_maybe(|overlay| {
|
||||
overlay.on_event(
|
||||
event,
|
||||
layout,
|
||||
cursor_position,
|
||||
renderer,
|
||||
clipboard,
|
||||
&mut local_shell,
|
||||
)
|
||||
})
|
||||
.unwrap_or(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(),
|
||||
);
|
||||
|
||||
overlay.instance.with_element(|element| {
|
||||
overlay.tree.diff_children(std::slice::from_ref(&element))
|
||||
});
|
||||
|
||||
self.overlay = Some(
|
||||
OverlayBuilder {
|
||||
instance: overlay.instance,
|
||||
tree: overlay.tree,
|
||||
types: PhantomData,
|
||||
overlay_builder: |_, _| None,
|
||||
}
|
||||
.build(),
|
||||
);
|
||||
|
||||
shell.invalidate_layout();
|
||||
}
|
||||
|
||||
event_status
|
||||
}
|
||||
|
||||
fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
|
||||
self.with_overlay_maybe(|overlay| {
|
||||
overlay.is_over(layout, cursor_position)
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
39
widget/src/lazy/helpers.rs
Normal file
39
widget/src/lazy/helpers.rs
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
use crate::core::{self, Element, Size};
|
||||
use crate::lazy::component::{self, Component};
|
||||
use crate::lazy::{Lazy, Responsive};
|
||||
|
||||
use std::hash::Hash;
|
||||
|
||||
pub fn lazy<'a, Message, Renderer, Dependency, View>(
|
||||
dependency: Dependency,
|
||||
view: impl Fn(&Dependency) -> View + 'a,
|
||||
) -> Lazy<'a, Message, Renderer, Dependency, View>
|
||||
where
|
||||
Dependency: Hash + 'a,
|
||||
View: Into<Element<'static, Message, Renderer>>,
|
||||
{
|
||||
Lazy::new(dependency, view)
|
||||
}
|
||||
|
||||
/// Turns an implementor of [`Component`] into an [`Element`] that can be
|
||||
/// embedded in any application.
|
||||
pub fn component<'a, C, Message, Renderer>(
|
||||
component: C,
|
||||
) -> Element<'a, Message, Renderer>
|
||||
where
|
||||
C: Component<Message, Renderer> + 'a,
|
||||
C::State: 'static,
|
||||
Message: 'a,
|
||||
Renderer: core::Renderer + 'a,
|
||||
{
|
||||
component::view(component)
|
||||
}
|
||||
|
||||
pub fn responsive<'a, Message, Renderer>(
|
||||
f: impl Fn(Size) -> Element<'a, Message, Renderer> + 'a,
|
||||
) -> Responsive<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: core::Renderer,
|
||||
{
|
||||
Responsive::new(f)
|
||||
}
|
||||
427
widget/src/lazy/responsive.rs
Normal file
427
widget/src/lazy/responsive.rs
Normal file
|
|
@ -0,0 +1,427 @@
|
|||
use crate::core::event::{self, Event};
|
||||
use crate::core::layout::{self, Layout};
|
||||
use crate::core::mouse;
|
||||
use crate::core::overlay;
|
||||
use crate::core::renderer;
|
||||
use crate::core::widget;
|
||||
use crate::core::widget::tree::{self, Tree};
|
||||
use crate::core::{
|
||||
self, Clipboard, Element, Length, Point, Rectangle, Shell, Size, Widget,
|
||||
};
|
||||
use crate::horizontal_space;
|
||||
|
||||
use ouroboros::self_referencing;
|
||||
use std::cell::{RefCell, RefMut};
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::Deref;
|
||||
|
||||
/// A widget that is aware of its dimensions.
|
||||
///
|
||||
/// A [`Responsive`] widget will always try to fill all the available space of
|
||||
/// its parent.
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Responsive<'a, Message, Renderer = crate::Renderer> {
|
||||
view: Box<dyn Fn(Size) -> Element<'a, Message, Renderer> + 'a>,
|
||||
content: RefCell<Content<'a, Message, Renderer>>,
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Responsive<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: core::Renderer,
|
||||
{
|
||||
/// Creates a new [`Responsive`] widget with a closure that produces its
|
||||
/// contents.
|
||||
///
|
||||
/// The `view` closure will be provided with the current [`Size`] of
|
||||
/// the [`Responsive`] widget and, therefore, can be used to build the
|
||||
/// contents of the widget in a responsive way.
|
||||
pub fn new(
|
||||
view: impl Fn(Size) -> Element<'a, Message, Renderer> + 'a,
|
||||
) -> Self {
|
||||
Self {
|
||||
view: Box::new(view),
|
||||
content: RefCell::new(Content {
|
||||
size: Size::ZERO,
|
||||
layout: layout::Node::new(Size::ZERO),
|
||||
element: Element::new(horizontal_space(0)),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Content<'a, Message, Renderer> {
|
||||
size: Size,
|
||||
layout: layout::Node,
|
||||
element: Element<'a, Message, Renderer>,
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Content<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: core::Renderer,
|
||||
{
|
||||
fn update(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
renderer: &Renderer,
|
||||
new_size: Size,
|
||||
view: &dyn Fn(Size) -> Element<'a, Message, Renderer>,
|
||||
) {
|
||||
if self.size == new_size {
|
||||
return;
|
||||
}
|
||||
|
||||
self.element = view(new_size);
|
||||
self.size = new_size;
|
||||
|
||||
tree.diff(&self.element);
|
||||
|
||||
self.layout = self
|
||||
.element
|
||||
.as_widget()
|
||||
.layout(renderer, &layout::Limits::new(Size::ZERO, self.size));
|
||||
}
|
||||
|
||||
fn resolve<R, T>(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
renderer: R,
|
||||
layout: Layout<'_>,
|
||||
view: &dyn Fn(Size) -> Element<'a, Message, Renderer>,
|
||||
f: impl FnOnce(
|
||||
&mut Tree,
|
||||
R,
|
||||
Layout<'_>,
|
||||
&mut Element<'a, Message, Renderer>,
|
||||
) -> T,
|
||||
) -> T
|
||||
where
|
||||
R: Deref<Target = Renderer>,
|
||||
{
|
||||
self.update(tree, renderer.deref(), layout.bounds().size(), view);
|
||||
|
||||
let content_layout = Layout::with_offset(
|
||||
layout.position() - Point::ORIGIN,
|
||||
&self.layout,
|
||||
);
|
||||
|
||||
f(tree, renderer, content_layout, &mut self.element)
|
||||
}
|
||||
}
|
||||
|
||||
struct State {
|
||||
tree: RefCell<Tree>,
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||
for Responsive<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: core::Renderer,
|
||||
{
|
||||
fn tag(&self) -> tree::Tag {
|
||||
tree::Tag::of::<State>()
|
||||
}
|
||||
|
||||
fn state(&self) -> tree::State {
|
||||
tree::State::new(State {
|
||||
tree: RefCell::new(Tree::empty()),
|
||||
})
|
||||
}
|
||||
|
||||
fn width(&self) -> Length {
|
||||
Length::Fill
|
||||
}
|
||||
|
||||
fn height(&self) -> Length {
|
||||
Length::Fill
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
_renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
layout::Node::new(limits.max())
|
||||
}
|
||||
|
||||
fn operate(
|
||||
&self,
|
||||
tree: &mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
operation: &mut dyn widget::Operation<Message>,
|
||||
) {
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
let mut content = self.content.borrow_mut();
|
||||
|
||||
content.resolve(
|
||||
&mut state.tree.borrow_mut(),
|
||||
renderer,
|
||||
layout,
|
||||
&self.view,
|
||||
|tree, renderer, layout, element| {
|
||||
element
|
||||
.as_widget()
|
||||
.operate(tree, layout, renderer, operation);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
) -> event::Status {
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
let mut content = self.content.borrow_mut();
|
||||
|
||||
content.resolve(
|
||||
&mut state.tree.borrow_mut(),
|
||||
renderer,
|
||||
layout,
|
||||
&self.view,
|
||||
|tree, renderer, layout, element| {
|
||||
element.as_widget_mut().on_event(
|
||||
tree,
|
||||
event,
|
||||
layout,
|
||||
cursor_position,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
let state = tree.state.downcast_ref::<State>();
|
||||
let mut content = self.content.borrow_mut();
|
||||
|
||||
content.resolve(
|
||||
&mut state.tree.borrow_mut(),
|
||||
renderer,
|
||||
layout,
|
||||
&self.view,
|
||||
|tree, renderer, layout, element| {
|
||||
element.as_widget().draw(
|
||||
tree,
|
||||
renderer,
|
||||
theme,
|
||||
style,
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
let state = tree.state.downcast_ref::<State>();
|
||||
let mut content = self.content.borrow_mut();
|
||||
|
||||
content.resolve(
|
||||
&mut state.tree.borrow_mut(),
|
||||
renderer,
|
||||
layout,
|
||||
&self.view,
|
||||
|tree, renderer, layout, element| {
|
||||
element.as_widget().mouse_interaction(
|
||||
tree,
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
renderer,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn overlay<'b>(
|
||||
&'b mut self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
||||
use std::ops::DerefMut;
|
||||
|
||||
let state = tree.state.downcast_ref::<State>();
|
||||
|
||||
let overlay = OverlayBuilder {
|
||||
content: self.content.borrow_mut(),
|
||||
tree: state.tree.borrow_mut(),
|
||||
types: PhantomData,
|
||||
overlay_builder: |content: &mut RefMut<'_, Content<'_, _, _>>,
|
||||
tree| {
|
||||
content.update(
|
||||
tree,
|
||||
renderer,
|
||||
layout.bounds().size(),
|
||||
&self.view,
|
||||
);
|
||||
|
||||
let Content {
|
||||
element,
|
||||
layout: content_layout,
|
||||
..
|
||||
} = content.deref_mut();
|
||||
|
||||
let content_layout = Layout::with_offset(
|
||||
layout.bounds().position() - Point::ORIGIN,
|
||||
content_layout,
|
||||
);
|
||||
|
||||
element
|
||||
.as_widget_mut()
|
||||
.overlay(tree, content_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)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<Responsive<'a, Message, Renderer>>
|
||||
for Element<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: core::Renderer + 'a,
|
||||
Message: 'a,
|
||||
{
|
||||
fn from(responsive: Responsive<'a, Message, Renderer>) -> Self {
|
||||
Self::new(responsive)
|
||||
}
|
||||
}
|
||||
|
||||
#[self_referencing]
|
||||
struct Overlay<'a, 'b, Message, Renderer> {
|
||||
content: RefMut<'a, Content<'b, Message, Renderer>>,
|
||||
tree: RefMut<'a, Tree>,
|
||||
types: PhantomData<Message>,
|
||||
|
||||
#[borrows(mut content, mut tree)]
|
||||
#[covariant]
|
||||
overlay: Option<overlay::Element<'this, Message, Renderer>>,
|
||||
}
|
||||
|
||||
impl<'a, 'b, Message, Renderer> Overlay<'a, 'b, Message, Renderer> {
|
||||
fn with_overlay_maybe<T>(
|
||||
&self,
|
||||
f: impl FnOnce(&overlay::Element<'_, Message, Renderer>) -> T,
|
||||
) -> Option<T> {
|
||||
self.borrow_overlay().as_ref().map(f)
|
||||
}
|
||||
|
||||
fn with_overlay_mut_maybe<T>(
|
||||
&mut self,
|
||||
f: impl FnOnce(&mut overlay::Element<'_, Message, Renderer>) -> T,
|
||||
) -> Option<T> {
|
||||
self.with_overlay_mut(|overlay| overlay.as_mut().map(f))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b, Message, Renderer> overlay::Overlay<Message, Renderer>
|
||||
for Overlay<'a, 'b, Message, Renderer>
|
||||
where
|
||||
Renderer: core::Renderer,
|
||||
{
|
||||
fn layout(
|
||||
&self,
|
||||
renderer: &Renderer,
|
||||
bounds: Size,
|
||||
position: Point,
|
||||
) -> layout::Node {
|
||||
self.with_overlay_maybe(|overlay| {
|
||||
let translation = position - overlay.position();
|
||||
|
||||
overlay.layout(renderer, bounds, translation)
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
) {
|
||||
let _ = self.with_overlay_maybe(|overlay| {
|
||||
overlay.draw(renderer, theme, 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 on_event(
|
||||
&mut self,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
) -> event::Status {
|
||||
self.with_overlay_mut_maybe(|overlay| {
|
||||
overlay.on_event(
|
||||
event,
|
||||
layout,
|
||||
cursor_position,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
)
|
||||
})
|
||||
.unwrap_or(event::Status::Ignored)
|
||||
}
|
||||
|
||||
fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
|
||||
self.with_overlay_maybe(|overlay| {
|
||||
overlay.is_over(layout, cursor_position)
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue