From 422568dee49fa6b814ae0131a3f88d4ae2be243b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 8 Aug 2024 01:25:00 +0200 Subject: Introduce `black_box` and `chain` in `widget::operation` --- core/src/element.rs | 4 +- core/src/overlay.rs | 2 +- core/src/overlay/element.rs | 4 +- core/src/overlay/group.rs | 2 +- core/src/widget.rs | 2 +- core/src/widget/operation.rs | 191 +++++++++++++++++++++++++++++++-- core/src/widget/operation/focusable.rs | 30 ++---- 7 files changed, 200 insertions(+), 35 deletions(-) (limited to 'core/src') diff --git a/core/src/element.rs b/core/src/element.rs index 385d8295..6ebb8a15 100644 --- a/core/src/element.rs +++ b/core/src/element.rs @@ -304,7 +304,7 @@ where tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn widget::Operation<()>, + operation: &mut dyn widget::Operation, ) { self.widget.operate(tree, layout, renderer, operation); } @@ -440,7 +440,7 @@ where state: &mut Tree, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn widget::Operation<()>, + operation: &mut dyn widget::Operation, ) { self.element .widget diff --git a/core/src/overlay.rs b/core/src/overlay.rs index 3b79970e..f09de831 100644 --- a/core/src/overlay.rs +++ b/core/src/overlay.rs @@ -41,7 +41,7 @@ where &mut self, _layout: Layout<'_>, _renderer: &Renderer, - _operation: &mut dyn widget::Operation<()>, + _operation: &mut dyn widget::Operation, ) { } diff --git a/core/src/overlay/element.rs b/core/src/overlay/element.rs index 61e75e8a..32e987a3 100644 --- a/core/src/overlay/element.rs +++ b/core/src/overlay/element.rs @@ -92,7 +92,7 @@ where &mut self, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn widget::Operation<()>, + operation: &mut dyn widget::Operation, ) { self.overlay.operate(layout, renderer, operation); } @@ -144,7 +144,7 @@ where &mut self, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn widget::Operation<()>, + operation: &mut dyn widget::Operation, ) { self.content.operate(layout, renderer, operation); } diff --git a/core/src/overlay/group.rs b/core/src/overlay/group.rs index cd12eac9..6541d311 100644 --- a/core/src/overlay/group.rs +++ b/core/src/overlay/group.rs @@ -132,7 +132,7 @@ where &mut self, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn widget::Operation<()>, + operation: &mut dyn widget::Operation, ) { operation.container(None, layout.bounds(), &mut |operation| { self.children.iter_mut().zip(layout.children()).for_each( diff --git a/core/src/widget.rs b/core/src/widget.rs index 08cfa55b..c5beea54 100644 --- a/core/src/widget.rs +++ b/core/src/widget.rs @@ -105,7 +105,7 @@ where _state: &mut Tree, _layout: Layout<'_>, _renderer: &Renderer, - _operation: &mut dyn Operation<()>, + _operation: &mut dyn Operation, ) { } diff --git a/core/src/widget/operation.rs b/core/src/widget/operation.rs index 3e4ed618..741e1a5f 100644 --- a/core/src/widget/operation.rs +++ b/core/src/widget/operation.rs @@ -12,11 +12,12 @@ use crate::{Rectangle, Vector}; use std::any::Any; use std::fmt; +use std::marker::PhantomData; use std::sync::Arc; /// A piece of logic that can traverse the widget tree of an application in /// order to query or update some widget state. -pub trait Operation: Send { +pub trait Operation: Send { /// Operates on a widget that contains other widgets. /// /// The `operate_on_children` function can be called to return control to @@ -53,6 +54,46 @@ pub trait Operation: Send { } } +impl Operation for Box +where + T: Operation + ?Sized, +{ + fn container( + &mut self, + id: Option<&Id>, + bounds: Rectangle, + operate_on_children: &mut dyn FnMut(&mut dyn Operation), + ) { + self.as_mut().container(id, bounds, operate_on_children); + } + + fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) { + self.as_mut().focusable(state, id); + } + + fn scrollable( + &mut self, + state: &mut dyn Scrollable, + id: Option<&Id>, + bounds: Rectangle, + translation: Vector, + ) { + self.as_mut().scrollable(state, id, bounds, translation); + } + + fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) { + self.as_mut().text_input(state, id); + } + + fn custom(&mut self, state: &mut dyn Any, id: Option<&Id>) { + self.as_mut().custom(state, id); + } + + fn finish(&self) -> Outcome { + self.as_ref().finish() + } +} + /// The result of an [`Operation`]. pub enum Outcome { /// The [`Operation`] produced no result. @@ -78,9 +119,62 @@ where } } +/// Wraps the [`Operation`] in a black box, erasing its returning type. +pub fn black_box<'a, T, O>( + operation: &'a mut dyn Operation, +) -> impl Operation + 'a +where + T: 'a, +{ + struct BlackBox<'a, T> { + operation: &'a mut dyn Operation, + } + + impl<'a, T, O> Operation for BlackBox<'a, T> { + fn container( + &mut self, + id: Option<&Id>, + bounds: Rectangle, + operate_on_children: &mut dyn FnMut(&mut dyn Operation), + ) { + self.operation.container(id, bounds, &mut |operation| { + operate_on_children(&mut BlackBox { operation }); + }); + } + + fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) { + self.operation.focusable(state, id); + } + + fn scrollable( + &mut self, + state: &mut dyn Scrollable, + id: Option<&Id>, + bounds: Rectangle, + translation: Vector, + ) { + self.operation.scrollable(state, id, bounds, translation); + } + + fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) { + self.operation.text_input(state, id); + } + + fn custom(&mut self, state: &mut dyn Any, id: Option<&Id>) { + self.operation.custom(state, id); + } + + fn finish(&self) -> Outcome { + Outcome::None + } + } + + BlackBox { operation } +} + /// Maps the output of an [`Operation`] using the given function. pub fn map( - operation: Box>, + operation: impl Operation, f: impl Fn(A) -> B + Send + Sync + 'static, ) -> impl Operation where @@ -88,13 +182,14 @@ where B: 'static, { #[allow(missing_debug_implementations)] - struct Map { - operation: Box>, + struct Map { + operation: O, f: Arc B + Send + Sync>, } - impl Operation for Map + impl Operation for Map where + O: Operation, A: 'static, B: 'static, { @@ -155,10 +250,7 @@ where let Self { operation, .. } = self; - MapRef { - operation: operation.as_mut(), - } - .container(id, bounds, operate_on_children); + MapRef { operation }.container(id, bounds, operate_on_children); } fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) { @@ -201,6 +293,87 @@ where } } +/// Chains the output of an [`Operation`] with the provided function to +/// build a new [`Operation`]. +pub fn chain( + operation: impl Operation + 'static, + f: fn(A) -> O, +) -> impl Operation +where + A: 'static, + B: Send + 'static, + O: Operation + 'static, +{ + struct Chain + where + T: Operation, + O: Operation, + { + operation: T, + next: fn(A) -> O, + _result: PhantomData, + } + + impl Operation for Chain + where + T: Operation + 'static, + O: Operation + 'static, + A: 'static, + B: Send + 'static, + { + fn container( + &mut self, + id: Option<&Id>, + bounds: Rectangle, + operate_on_children: &mut dyn FnMut(&mut dyn Operation), + ) { + self.operation.container(id, bounds, &mut |operation| { + operate_on_children(&mut black_box(operation)); + }); + } + + fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) { + self.operation.focusable(state, id); + } + + fn scrollable( + &mut self, + state: &mut dyn Scrollable, + id: Option<&Id>, + bounds: Rectangle, + translation: crate::Vector, + ) { + self.operation.scrollable(state, id, bounds, translation); + } + + fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) { + self.operation.text_input(state, id); + } + + fn custom(&mut self, state: &mut dyn std::any::Any, id: Option<&Id>) { + self.operation.custom(state, id); + } + + fn finish(&self) -> Outcome { + match self.operation.finish() { + Outcome::None => Outcome::None, + Outcome::Some(value) => { + Outcome::Chain(Box::new((self.next)(value))) + } + Outcome::Chain(operation) => { + Outcome::Chain(Box::new(chain(operation, self.next))) + } + } + } + } + + Chain { + operation, + next: f, + _result: PhantomData, + } +} + /// Produces an [`Operation`] that applies the given [`Operation`] to the /// children of a container with the given [`Id`]. pub fn scope( diff --git a/core/src/widget/operation/focusable.rs b/core/src/widget/operation/focusable.rs index 68c22faa..0a6f2e96 100644 --- a/core/src/widget/operation/focusable.rs +++ b/core/src/widget/operation/focusable.rs @@ -1,5 +1,5 @@ //! Operate on widgets that can be focused. -use crate::widget::operation::{Operation, Outcome}; +use crate::widget::operation::{self, Operation, Outcome}; use crate::widget::Id; use crate::Rectangle; @@ -58,19 +58,12 @@ pub fn focus(target: Id) -> impl Operation { /// Produces an [`Operation`] that generates a [`Count`] and chains it with the /// provided function to build a new [`Operation`]. -pub fn count(f: fn(Count) -> O) -> impl Operation -where - O: Operation + 'static, -{ - struct CountFocusable { +pub fn count() -> impl Operation { + struct CountFocusable { count: Count, - next: fn(Count) -> O, } - impl Operation for CountFocusable - where - O: Operation + 'static, - { + impl Operation for CountFocusable { fn focusable(&mut self, state: &mut dyn Focusable, _id: Option<&Id>) { if state.is_focused() { self.count.focused = Some(self.count.total); @@ -83,26 +76,25 @@ where &mut self, _id: Option<&Id>, _bounds: Rectangle, - operate_on_children: &mut dyn FnMut(&mut dyn Operation), + operate_on_children: &mut dyn FnMut(&mut dyn Operation), ) { operate_on_children(self); } - fn finish(&self) -> Outcome { - Outcome::Chain(Box::new((self.next)(self.count))) + fn finish(&self) -> Outcome { + Outcome::Some(self.count) } } CountFocusable { count: Count::default(), - next: f, } } /// Produces an [`Operation`] that searches for the current focused widget, and /// - if found, focuses the previous focusable widget. /// - if not found, focuses the last focusable widget. -pub fn focus_previous() -> impl Operation { +pub fn focus_previous() -> impl Operation { struct FocusPrevious { count: Count, current: usize, @@ -136,13 +128,13 @@ pub fn focus_previous() -> impl Operation { } } - count(|count| FocusPrevious { count, current: 0 }) + operation::chain(count(), |count| FocusPrevious { count, current: 0 }) } /// Produces an [`Operation`] that searches for the current focused widget, and /// - if found, focuses the next focusable widget. /// - if not found, focuses the first focusable widget. -pub fn focus_next() -> impl Operation { +pub fn focus_next() -> impl Operation { struct FocusNext { count: Count, current: usize, @@ -170,7 +162,7 @@ pub fn focus_next() -> impl Operation { } } - count(|count| FocusNext { count, current: 0 }) + operation::chain(count(), |count| FocusNext { count, current: 0 }) } /// Produces an [`Operation`] that searches for the current focused widget -- cgit