Switcher, h/v expand

This commit is contained in:
Jonathan Johnson 2023-11-13 09:14:38 -08:00
parent 07b93397c5
commit ee3813f44d
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
12 changed files with 364 additions and 87 deletions

22
Cargo.lock generated
View file

@ -281,11 +281,10 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.83" version = "1.0.84"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" checksum = "0f8e7c90afad890484a21653d08b6e209ae34770fb5ee298f9c699fcc1e5c856"
dependencies = [ dependencies = [
"jobserver",
"libc", "libc",
] ]
@ -838,15 +837,6 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
[[package]]
name = "jobserver"
version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.65" version = "0.3.65"
@ -1970,9 +1960,9 @@ dependencies = [
[[package]] [[package]]
name = "tracing-log" name = "tracing-log"
version = "0.1.4" version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2" checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
dependencies = [ dependencies = [
"log", "log",
"once_cell", "once_cell",
@ -1981,9 +1971,9 @@ dependencies = [
[[package]] [[package]]
name = "tracing-subscriber" name = "tracing-subscriber"
version = "0.3.17" version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
dependencies = [ dependencies = [
"matchers", "matchers",
"nu-ansi-term", "nu-ansi-term",

46
examples/switcher.rs Normal file
View file

@ -0,0 +1,46 @@
use gooey::value::Dynamic;
use gooey::widget::{MakeWidget, WidgetInstance};
use gooey::widgets::{Button, Label, Switcher};
use gooey::Run;
#[derive(Debug)]
enum ActiveContent {
Intro,
Success,
}
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()
}
fn intro(active: Dynamic<ActiveContent>) -> WidgetInstance {
const INTRO: &str = "This example demonstrates the Switcher<T> widget, which uses a mapping function to convert from a generic type to the widget it uses for its contents.";
Label::new(INTRO)
.and(
Button::new("Switch!")
.on_click(move |_| active.set(ActiveContent::Success))
.centered(),
)
.into_rows()
.make_widget()
}
fn success(active: Dynamic<ActiveContent>) -> WidgetInstance {
Label::new("The value changed to `ActiveContent::Success`!")
.and(
Button::new("Start Over")
.on_click(move |_| active.set(ActiveContent::Intro))
// .centered(),
)
.into_rows()
.make_widget()
}

View file

@ -5,10 +5,9 @@ use gooey::styles::components::{TextColor, WidgetBackground};
use gooey::styles::{ColorSource, ColorTheme, FixedTheme, SurfaceTheme, Theme, ThemePair}; use gooey::styles::{ColorSource, ColorTheme, FixedTheme, SurfaceTheme, Theme, ThemePair};
use gooey::value::{Dynamic, MapEach}; use gooey::value::{Dynamic, MapEach};
use gooey::widget::MakeWidget; use gooey::widget::MakeWidget;
use gooey::widgets::{Input, Label, Scroll, Slider, Stack, Themed}; use gooey::widgets::{Input, Label, ModeSwitch, Scroll, Slider, Stack, Themed};
use gooey::window::ThemeMode; use gooey::window::ThemeMode;
use gooey::Run; use gooey::Run;
use kludgine::figures::units::Lp;
use kludgine::Color; use kludgine::Color;
const PRIMARY_HUE: f32 = 240.; const PRIMARY_HUE: f32 = 240.;
@ -59,16 +58,22 @@ fn main() -> gooey::Result {
.and(neutral_editor) .and(neutral_editor)
.and(neutral_variant_editor), .and(neutral_variant_editor),
)) ))
.and(theme(default_theme.map_each(|theme| theme.dark), "Dark"))
.and(theme(default_theme.map_each(|theme| theme.light), "Light"))
.and(fixed_themes( .and(fixed_themes(
default_theme.map_each(|theme| theme.primary_fixed), default_theme.map_each(|theme| theme.primary_fixed),
default_theme.map_each(|theme| theme.secondary_fixed), default_theme.map_each(|theme| theme.secondary_fixed),
default_theme.map_each(|theme| theme.tertiary_fixed), default_theme.map_each(|theme| theme.tertiary_fixed),
))
.and(theme(
default_theme.map_each(|theme| theme.dark),
ThemeMode::Dark,
))
.and(theme(
default_theme.map_each(|theme| theme.light),
ThemeMode::Light,
)), )),
), ),
) )
.pad_by(Lp::points(16)) .pad()
.expand() .expand()
.into_window() .into_window()
.with_theme_mode(theme_mode) .with_theme_mode(theme_mode)
@ -132,6 +137,7 @@ fn fixed_themes(
.and(fixed_theme(secondary, "Secondary")) .and(fixed_theme(secondary, "Secondary"))
.and(fixed_theme(tertiary, "Tertiary")), .and(fixed_theme(tertiary, "Tertiary")),
) )
.contain()
.expand() .expand()
} }
@ -156,12 +162,18 @@ fn fixed_theme(theme: Dynamic<FixedTheme>, label: &str) -> impl MakeWidget {
color, color,
)), )),
) )
.contain()
.expand() .expand()
} }
fn theme(theme: Dynamic<Theme>, label: &str) -> impl MakeWidget { fn theme(theme: Dynamic<Theme>, mode: ThemeMode) -> impl MakeWidget {
Stack::rows( ModeSwitch::new(
Label::new(label) mode,
Stack::rows(
Label::new(match mode {
ThemeMode::Light => "Light",
ThemeMode::Dark => "Dark",
})
.and( .and(
Stack::columns( Stack::columns(
color_theme(theme.map_each(|theme| theme.primary), "Primary") color_theme(theme.map_each(|theme| theme.primary), "Primary")
@ -175,9 +187,12 @@ fn theme(theme: Dynamic<Theme>, label: &str) -> impl MakeWidget {
)) ))
.and(color_theme(theme.map_each(|theme| theme.error), "Error")), .and(color_theme(theme.map_each(|theme| theme.error), "Error")),
) )
.contain()
.expand(), .expand(),
) )
.and(surface_theme(theme.map_each(|theme| theme.surface))), .and(surface_theme(theme.map_each(|theme| theme.surface))),
)
.contain(),
) )
.expand() .expand()
} }
@ -199,6 +214,7 @@ fn surface_theme(theme: Dynamic<SurfaceTheme>) -> impl MakeWidget {
on_color.clone(), on_color.clone(),
)), )),
) )
.contain()
.expand() .expand()
.and( .and(
Stack::columns( Stack::columns(
@ -228,6 +244,7 @@ fn surface_theme(theme: Dynamic<SurfaceTheme>) -> impl MakeWidget {
on_color.clone(), on_color.clone(),
)), )),
) )
.contain()
.expand(), .expand(),
) )
.and( .and(
@ -254,9 +271,11 @@ fn surface_theme(theme: Dynamic<SurfaceTheme>) -> impl MakeWidget {
on_color, on_color,
)), )),
) )
.contain()
.expand(), .expand(),
), ),
) )
.contain()
.expand() .expand()
} }
@ -289,6 +308,7 @@ fn color_theme(theme: Dynamic<ColorTheme>, label: &str) -> impl MakeWidget {
container, container,
)), )),
) )
.contain()
.expand() .expand()
} }

View file

@ -22,14 +22,14 @@
//! use gooey::value::Dynamic; //! use gooey::value::Dynamic;
//! //!
//! let value = Dynamic::new(0); //! let value = Dynamic::new(0);
//! //! let mut reader = value.create_reader();
//! value //! value
//! .transition_to(100) //! .transition_to(100)
//! .over(Duration::from_millis(100)) //! .over(Duration::from_millis(100))
//! .with_easing(EaseInOutElastic) //! .with_easing(EaseInOutElastic)
//! .launch(); //! .launch();
//! drop(value);
//! //!
//! let mut reader = value.into_reader();
//! while reader.block_until_updated() { //! while reader.block_until_updated() {
//! println!("{}", reader.get()); //! println!("{}", reader.get());
//! } //! }
@ -726,6 +726,48 @@ impl LinearInterpolate for Color {
} }
} }
/// A wrapper that implements [`LinearInterpolate`] such that the value switches
/// after 50%.
///
/// This wrapper can be used to add [`LinearInterpolate`] to types that normally
/// don't support interpolation.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct BinaryLerp<T>(T);
impl<T> LinearInterpolate for BinaryLerp<T>
where
T: Clone + PartialEq,
{
fn lerp(&self, target: &Self, percent: f32) -> Self {
if false.lerp(&true, percent) {
target.clone()
} else {
self.clone()
}
}
}
/// A wrapper that implements [`LinearInterpolate`] such that the target value
/// is immediately returned as long as percent is > 0.
///
/// This wrapper can be used to add [`LinearInterpolate`] to types that normally
/// don't support interpolation.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct ImmediateLerp<T>(T);
impl<T> LinearInterpolate for ImmediateLerp<T>
where
T: Clone + PartialEq,
{
fn lerp(&self, target: &Self, percent: f32) -> Self {
if percent > 0. {
target.clone()
} else {
self.clone()
}
}
}
/// Calculates the ratio of one value against a minimum and maximum. /// Calculates the ratio of one value against a minimum and maximum.
pub trait PercentBetween { pub trait PercentBetween {
/// Return the percentage that `self` is between `min` and `max`. /// Return the percentage that `self` is between `min` and `max`.

View file

@ -30,7 +30,7 @@ use crate::styles::{Dimension, FocusableWidgets, VisualOrder};
/// /// This component defaults to picking a contrasting color between `TextColor` and `SurfaceColor` /// /// This component defaults to picking a contrasting color between `TextColor` and `SurfaceColor`
/// ContrastingColor(Color, "contrasting_color", contrasting!(ThemedComponent, TextColor, SurfaceColor)) /// ContrastingColor(Color, "contrasting_color", contrasting!(ThemedComponent, TextColor, SurfaceColor))
/// /// This component shows how to use a closure for nearly infinite flexibility in computing the default value. /// /// This component shows how to use a closure for nearly infinite flexibility in computing the default value.
/// ClosureDefaultComponent(Color, "closure_component", |context| context.query_style(&TextColor)) /// ClosureDefaultComponent(Color, "closure_component", |context| context.get(&TextColor))
/// } /// }
/// } /// }
/// ``` /// ```

View file

@ -418,6 +418,7 @@ impl TreeData {
while let Some(node) = detached_nodes.pop() { while let Some(node) = detached_nodes.pop() {
let mut node = self.nodes.remove(node).expect("detached node missing"); let mut node = self.nodes.remove(node).expect("detached node missing");
self.nodes_by_id.remove(&node.widget.id());
detached_nodes.append(&mut node.children); detached_nodes.append(&mut node.children);
} }
} }

View file

@ -43,6 +43,12 @@ pub trait Widget: Send + UnwindSafe + Debug + 'static {
context: &mut LayoutContext<'_, '_, '_, '_, '_>, context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> Size<UPx>; ) -> Size<UPx>;
/// Return true if this widget should expand to fill the window when it is
/// the root widget.
fn expand_if_at_root(&self) -> Option<bool> {
Some(false)
}
/// The widget has been mounted into a parent widget. /// The widget has been mounted into a parent widget.
#[allow(unused_variables)] #[allow(unused_variables)]
fn mounted(&mut self, context: &mut EventContext<'_, '_>) {} fn mounted(&mut self, context: &mut EventContext<'_, '_>) {}
@ -227,9 +233,8 @@ pub trait WrapperWidget: Debug + Send + UnwindSafe + 'static {
available_space: Size<ConstraintLimit>, available_space: Size<ConstraintLimit>,
context: &mut LayoutContext<'_, '_, '_, '_, '_>, context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> WrappedLayout { ) -> WrappedLayout {
let child = self.child_mut().mounted(&mut context.as_event_context());
let adjusted_space = self.adjust_child_constraint(available_space, context); let adjusted_space = self.adjust_child_constraint(available_space, context);
let child = self.child_mut().mounted(&mut context.as_event_context());
let size = context let size = context
.for_other(&child) .for_other(&child)
.layout(adjusted_space) .layout(adjusted_space)
@ -420,9 +425,8 @@ where
available_space: Size<ConstraintLimit>, available_space: Size<ConstraintLimit>,
context: &mut LayoutContext<'_, '_, '_, '_, '_>, context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> Size<UPx> { ) -> Size<UPx> {
let child = self.child_mut().mounted(&mut context.as_event_context());
let layout = self.layout_child(available_space, context); let layout = self.layout_child(available_space, context);
let child = self.child_mut().mounted(&mut context.as_event_context());
context.set_child_layout(&child, layout.child); context.set_child_layout(&child, layout.child);
layout.size layout.size
} }
@ -606,6 +610,18 @@ pub trait MakeWidget: Sized {
Expand::weighted(weight, self) Expand::weighted(weight, self)
} }
/// Expands `self` to grow to fill its parent horizontally.
#[must_use]
fn expand_horizontally(self) -> Expand {
Expand::horizontal(self)
}
/// Expands `self` to grow to fill its parent vertically.
#[must_use]
fn expand_vertically(self) -> Expand {
Expand::horizontal(self)
}
/// Aligns `self` to the center vertically and horizontally. /// Aligns `self` to the center vertically and horizontally.
#[must_use] #[must_use]
fn centered(self) -> Align { fn centered(self) -> Align {

View file

@ -14,6 +14,7 @@ mod slider;
mod space; mod space;
pub mod stack; pub mod stack;
mod style; mod style;
mod switcher;
mod themed; mod themed;
mod tilemap; mod tilemap;
@ -31,5 +32,6 @@ pub use slider::Slider;
pub use space::Space; pub use space::Space;
pub use stack::Stack; pub use stack::Stack;
pub use style::Style; pub use style::Style;
pub use switcher::Switcher;
pub use themed::Themed; pub use themed::Themed;
pub use tilemap::TileMap; pub use tilemap::TileMap;

View file

@ -12,12 +12,17 @@ use crate::ConstraintLimit;
/// [`Expand`]ed widget. /// [`Expand`]ed widget.
#[derive(Debug)] #[derive(Debug)]
pub struct Expand { pub struct Expand {
/// The weight to use when splitting available space with multiple kind: ExpandKind,
/// [`Expand`] widgets.
pub weight: u8,
child: WidgetRef, child: WidgetRef,
} }
#[derive(Debug)]
enum ExpandKind {
Weighted(u8),
Horizontal,
Vertical,
}
impl Default for Expand { impl Default for Expand {
fn default() -> Self { fn default() -> Self {
Self::empty() Self::empty()
@ -30,7 +35,25 @@ impl Expand {
pub fn new(child: impl MakeWidget) -> Self { pub fn new(child: impl MakeWidget) -> Self {
Self { Self {
child: WidgetRef::new(child), child: WidgetRef::new(child),
weight: 1, kind: ExpandKind::Weighted(1),
}
}
/// Returns a widget that expands `child` to fill the parent widget horizontally.
#[must_use]
pub fn horizontal(child: impl MakeWidget) -> Self {
Self {
child: WidgetRef::new(child),
kind: ExpandKind::Horizontal,
}
}
/// Returns a widget that expands `child` to fill the parent widget vertically.
#[must_use]
pub fn vertical(child: impl MakeWidget) -> Self {
Self {
child: WidgetRef::new(child),
kind: ExpandKind::Vertical,
} }
} }
@ -39,7 +62,7 @@ impl Expand {
pub fn empty() -> Self { pub fn empty() -> Self {
Self { Self {
child: WidgetRef::new(Space::clear()), child: WidgetRef::new(Space::clear()),
weight: 1, kind: ExpandKind::Weighted(1),
} }
} }
@ -51,7 +74,7 @@ impl Expand {
pub fn weighted(weight: u8, child: impl MakeWidget) -> Self { pub fn weighted(weight: u8, child: impl MakeWidget) -> Self {
Self { Self {
child: WidgetRef::new(child), child: WidgetRef::new(child),
weight, kind: ExpandKind::Weighted(weight),
} }
} }
@ -60,6 +83,14 @@ impl Expand {
pub const fn child(&self) -> &WidgetRef { pub const fn child(&self) -> &WidgetRef {
&self.child &self.child
} }
#[must_use]
pub(crate) fn weight(&self) -> Option<u8> {
match self.kind {
ExpandKind::Weighted(weight) => Some(weight),
ExpandKind::Horizontal | ExpandKind::Vertical => None,
}
}
} }
impl WrapperWidget for Expand { impl WrapperWidget for Expand {
@ -79,15 +110,29 @@ impl WrapperWidget for Expand {
let child = self.child.mounted(&mut context.as_event_context()); let child = self.child.mounted(&mut context.as_event_context());
let size = context.for_other(&child).layout(available_space); let size = context.for_other(&child).layout(available_space);
Size::<UPx>::new( let (width, height) = match &self.kind {
available_space ExpandKind::Weighted(_) => (
.width available_space
.fit_measured(size.width, context.gfx.scale()), .width
available_space .fit_measured(size.width, context.gfx.scale()),
.height available_space
.fit_measured(size.height, context.gfx.scale()), .height
) .fit_measured(size.height, context.gfx.scale()),
.into_signed() ),
.into() ExpandKind::Horizontal => (
available_space
.width
.fit_measured(size.width, context.gfx.scale()),
size.height,
),
ExpandKind::Vertical => (
size.width,
available_space
.height
.fit_measured(size.height, context.gfx.scale()),
),
};
Size::<UPx>::new(width, height).into_signed().into()
} }
} }

View file

@ -85,37 +85,38 @@ impl Stack {
} else { } else {
// This is a brand new child. // This is a brand new child.
let guard = widget.lock(); let guard = widget.lock();
let (mut widget, dimension) = let (mut widget, dimension) = if let Some((weight, expand)) = guard
if let Some(expand) = guard.downcast_ref::<Expand>() { .downcast_ref::<Expand>()
let weight = expand.weight; .and_then(|expand| expand.weight().map(|weight| (weight, expand)))
( {
expand.child().clone(), (
StackDimension::Fractional { weight }, expand.child().clone(),
) StackDimension::Fractional { weight },
} else if let Some((child, size)) = )
guard.downcast_ref::<Resize>().and_then(|r| { } else if let Some((child, size)) =
let range = match self.layout.orientation.orientation { guard.downcast_ref::<Resize>().and_then(|r| {
StackOrientation::Row => r.height, let range = match self.layout.orientation.orientation {
StackOrientation::Column => r.width, StackOrientation::Row => r.height,
}; StackOrientation::Column => r.width,
range.minimum().map(|min| { };
( range.minimum().map(|min| {
r.child().clone(), (
StackDimension::Measured { r.child().clone(),
min, StackDimension::Measured {
_max: range.end, min,
}, _max: range.end,
) },
}) )
}) })
{ })
(child, size) {
} else { (child, size)
( } else {
WidgetRef::Unmounted(widget.clone()), (
StackDimension::FitContent, WidgetRef::Unmounted(widget.clone()),
) StackDimension::FitContent,
}; )
};
drop(guard); drop(guard);
self.synced_children.insert(index, widget.mounted(context)); self.synced_children.insert(index, widget.mounted(context));
@ -449,7 +450,7 @@ impl Layout {
ConstraintLimit::Known(self.layouts[index].size.into_px(scale).into_unsigned()), ConstraintLimit::Known(self.layouts[index].size.into_px(scale).into_unsigned()),
other_constraint, other_constraint,
), ),
true, false,
)); ));
self.other = self.other.max(measured); self.other = self.other.max(measured);
} }
@ -459,6 +460,18 @@ impl Layout {
ConstraintLimit::ClippedAfter(clip_limit) => self.other.min(clip_limit), ConstraintLimit::ClippedAfter(clip_limit) => self.other.min(clip_limit),
}; };
// Finally layout the widgets with the final constraints
for index in 0..self.children.len() {
self.orientation.split_size(measure(
index,
self.orientation.make_size(
ConstraintLimit::Known(self.layouts[index].size.into_px(scale).into_unsigned()),
ConstraintLimit::Known(self.other),
),
true,
));
}
self.orientation.make_size(offset, self.other) self.orientation.make_size(offset, self.other)
} }
} }

91
src/widgets/switcher.rs Normal file
View file

@ -0,0 +1,91 @@
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::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>>,
child: WidgetRef,
}
impl<T> Switcher<T> {
/// 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,
}
}
}
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,
{
fn child_mut(&mut self) -> &mut WidgetRef {
&mut self.child
}
// TODO this should be moved to an invalidated() event once we have it.
fn adjust_child_constraint(
&mut self,
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 let WidgetRef::Mounted(removed) = removed {
context.remove_child(&removed);
}
}
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()
}
}

View file

@ -36,7 +36,9 @@ use crate::styles::ThemePair;
use crate::tree::Tree; use crate::tree::Tree;
use crate::utils::ModifiersExt; use crate::utils::ModifiersExt;
use crate::value::{Dynamic, DynamicReader, IntoDynamic, IntoValue, Value}; use crate::value::{Dynamic, DynamicReader, IntoDynamic, IntoValue, Value};
use crate::widget::{EventHandling, ManagedWidget, Widget, WidgetInstance, HANDLED, IGNORED}; use crate::widget::{
EventHandling, ManagedWidget, Widget, WidgetId, WidgetInstance, HANDLED, IGNORED,
};
use crate::widgets::{Expand, Resize}; use crate::widgets::{Expand, Resize};
use crate::window::sealed::WindowCommand; use crate::window::sealed::WindowCommand;
use crate::{initialize_tracing, ConstraintLimit, Run}; use crate::{initialize_tracing, ConstraintLimit, Run};
@ -841,6 +843,9 @@ where
if let Some(state) = self.mouse_state.devices.get(&device_id) { if let Some(state) = self.mouse_state.devices.get(&device_id) {
// Mouse Drag // Mouse Drag
for (button, handler) in state { for (button, handler) in state {
let Some(handler) = self.root.tree.widget(*handler) else {
continue;
};
let mut context = EventContext::new( let mut context = EventContext::new(
WidgetContext::new( WidgetContext::new(
handler.clone(), handler.clone(),
@ -878,7 +883,7 @@ where
if widget_context.hit_test(relative) { if widget_context.hit_test(relative) {
widget_context.hover(relative); widget_context.hover(relative);
drop(widget_context); drop(widget_context);
self.mouse_state.widget = Some(widget); self.mouse_state.widget = Some(widget.id());
break; break;
} }
} }
@ -934,9 +939,13 @@ where
) )
.clear_focus(); .clear_focus();
if let (ElementState::Pressed, Some(location), Some(hovered)) = if let (ElementState::Pressed, Some(location), Some(hovered)) = (
(state, &self.mouse_state.location, &self.mouse_state.widget) state,
{ &self.mouse_state.location,
self.mouse_state
.widget
.and_then(|id| self.root.tree.widget(id)),
) {
if let Some(handler) = recursively_handle_event( if let Some(handler) = recursively_handle_event(
&mut EventContext::new( &mut EventContext::new(
WidgetContext::new( WidgetContext::new(
@ -958,7 +967,7 @@ where
.devices .devices
.entry(device_id) .entry(device_id)
.or_default() .or_default()
.insert(button, handler); .insert(button, handler.id());
} }
} }
} }
@ -972,7 +981,9 @@ where
if device_buttons.is_empty() { if device_buttons.is_empty() {
self.mouse_state.devices.remove(&device_id); self.mouse_state.devices.remove(&device_id);
} }
let Some(handler) = self.root.tree.widget(handler) else {
return;
};
let mut context = EventContext::new( let mut context = EventContext::new(
WidgetContext::new( WidgetContext::new(
handler, handler,
@ -1036,8 +1047,8 @@ fn recursively_handle_event(
#[derive(Default)] #[derive(Default)]
struct MouseState { struct MouseState {
location: Option<Point<Px>>, location: Option<Point<Px>>,
widget: Option<ManagedWidget>, widget: Option<WidgetId>,
devices: AHashMap<DeviceId, AHashMap<MouseButton, ManagedWidget>>, devices: AHashMap<DeviceId, AHashMap<MouseButton, WidgetId>>,
} }
pub(crate) mod sealed { pub(crate) mod sealed {