Refactored switcher to use Dynamic<WidgetInstance>

This commit is contained in:
Jonathan Johnson 2023-11-14 13:03:09 -08:00
parent 4a4bc5de1a
commit 89c8805924
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
3 changed files with 67 additions and 69 deletions

View file

@ -1,6 +1,5 @@
use gooey::value::Dynamic;
use gooey::value::{Dynamic, Switchable};
use gooey::widget::{MakeWidget, WidgetInstance};
use gooey::widgets::Switcher;
use gooey::Run;
#[derive(Debug)]
@ -12,14 +11,15 @@ enum ActiveContent {
fn main() -> gooey::Result {
let active = Dynamic::new(ActiveContent::Intro);
Switcher::new(active.clone(), move |content| match content {
ActiveContent::Intro => intro(active.clone()),
ActiveContent::Success => success(active.clone()),
})
.contain()
.centered()
.expand()
.run()
active
.switcher(|current, active| match current {
ActiveContent::Intro => intro(active.clone()),
ActiveContent::Success => success(active.clone()),
})
.contain()
.centered()
.expand()
.run()
}
fn intro(active: Dynamic<ActiveContent>) -> WidgetInstance {

View file

@ -15,8 +15,8 @@ use intentional::Assert;
use crate::animation::{DynamicTransition, LinearInterpolate};
use crate::context::{WidgetContext, WindowHandle};
use crate::utils::{IgnorePoison, WithClone};
use crate::widget::WidgetId;
use crate::widgets::Input;
use crate::widget::{WidgetId, WidgetInstance};
use crate::widgets::{Input, Switcher};
/// An instance of a value that provides APIs to observe and react to its
/// contents.
@ -395,6 +395,15 @@ impl<T> Dynamic<T> {
}
}
impl Dynamic<WidgetInstance> {
/// Returns a new [`Switcher`] widget whose contents is the value of this
/// dynamic.
#[must_use]
pub fn switcher(self) -> Switcher {
Switcher::new(self)
}
}
impl<T> Default for Dynamic<T>
where
T: Default,
@ -921,6 +930,21 @@ where
}
}
/// A type that can be the source of a [`Switcher`] widget.
pub trait Switchable<T>: IntoDynamic<T> + Sized {
/// Returns a new [`Switcher`] whose contents is the result of invoking
/// `map` each time `self` is updated.
fn switcher<F>(self, map: F) -> Switcher
where
F: FnMut(&T, &Dynamic<T>) -> WidgetInstance + Send + 'static,
T: Send + 'static,
{
Switcher::mapping(self, map)
}
}
impl<T, W> Switchable<T> for W where W: IntoDynamic<T> {}
/// A value that may be either constant or dynamic.
#[derive(Debug)]
pub enum Value<T> {

View file

@ -1,58 +1,49 @@
use std::fmt::Debug;
use std::panic::UnwindSafe;
use kludgine::figures::Size;
use crate::context::{AsEventContext, LayoutContext};
use crate::value::{Generation, IntoValue, Value};
use crate::widget::{MakeWidget, WidgetInstance, WidgetRef, WrapperWidget};
use crate::value::{Dynamic, DynamicReader, IntoDynamic};
use crate::widget::{WidgetInstance, WidgetRef, WrapperWidget};
use crate::ConstraintLimit;
/// A widget that switches its contents based on a value of `T`.
pub struct Switcher<T> {
value: Value<T>,
value_generation: Option<Generation>,
factory: Box<dyn SwitchMap<T>>,
#[derive(Debug)]
pub struct Switcher {
source: DynamicReader<WidgetInstance>,
child: WidgetRef,
}
impl<T> Switcher<T> {
impl Switcher {
/// Returns a new widget that replaces its contents with the results of
/// calling `map` each time `source` is updated.
///
/// This function is equivalent to calling
/// `Self::new(source.into_dynamic().map_each(map))`, but this function's
/// signature helps the compiler's type inference work correctly. When using
/// new directly, the compiler often requires annotating the closure's
/// argument type.
pub fn mapping<T, F>(source: impl IntoDynamic<T>, mut map: F) -> Self
where
F: FnMut(&T, &Dynamic<T>) -> WidgetInstance + Send + 'static,
T: Send + 'static,
{
let source = source.into_dynamic();
Self::new(source.clone().map_each(move |value| map(value, &source)))
}
/// Returns a new widget that replaces its contents with the result of
/// `widget_factory` each time `value` changes.
#[must_use]
pub fn new<W, F>(value: impl IntoValue<T>, mut widget_factory: F) -> Self
where
F: for<'a> FnMut(&'a T) -> W + Send + UnwindSafe + 'static,
W: MakeWidget,
{
let value = value.into_value();
let value_generation = value.generation();
let child = WidgetRef::new(value.map(|value| widget_factory(value)));
Self {
value,
value_generation,
factory: Box::new(widget_factory),
child,
}
pub fn new(source: impl IntoDynamic<WidgetInstance>) -> Self {
let mut source = source.into_dynamic().into_reader();
let child = WidgetRef::new(source.get());
Self { source, child }
}
}
impl<T> Debug for Switcher<T>
where
T: Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Switcher")
.field("value", &self.value)
.field("child", &self.child)
.finish_non_exhaustive()
}
}
impl<T> WrapperWidget for Switcher<T>
where
T: Debug + Send + UnwindSafe + 'static,
{
impl WrapperWidget for Switcher {
fn child_mut(&mut self) -> &mut WidgetRef {
&mut self.child
}
@ -63,11 +54,8 @@ where
available_space: Size<ConstraintLimit>,
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> Size<ConstraintLimit> {
let current_generation = self.value.generation();
if self.value_generation != current_generation {
self.value_generation = current_generation;
let new_child = WidgetRef::new(self.value.map(|value| self.factory.invoke(value)));
let removed = std::mem::replace(&mut self.child, new_child);
if self.source.has_updated() {
let removed = std::mem::replace(&mut self.child, WidgetRef::new(self.source.get()));
if let WidgetRef::Mounted(removed) = removed {
context.remove_child(&removed);
}
@ -75,17 +63,3 @@ where
available_space
}
}
trait SwitchMap<T>: UnwindSafe + Send {
fn invoke(&mut self, value: &T) -> WidgetInstance;
}
impl<W, T, F> SwitchMap<T> for F
where
F: for<'a> FnMut(&'a T) -> W + Send + UnwindSafe,
W: MakeWidget,
{
fn invoke(&mut self, value: &T) -> WidgetInstance {
self(value).make_widget()
}
}