mirror of
https://github.com/danbulant/cushy
synced 2026-06-13 19:42:39 +00:00
Switcher, h/v expand
This commit is contained in:
parent
07b93397c5
commit
ee3813f44d
12 changed files with 364 additions and 87 deletions
22
Cargo.lock
generated
22
Cargo.lock
generated
|
|
@ -281,11 +281,10 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.83"
|
||||
version = "1.0.84"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
|
||||
checksum = "0f8e7c90afad890484a21653d08b6e209ae34770fb5ee298f9c699fcc1e5c856"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
]
|
||||
|
||||
|
|
@ -838,15 +837,6 @@ version = "0.3.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.65"
|
||||
|
|
@ -1970,9 +1960,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tracing-log"
|
||||
version = "0.1.4"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2"
|
||||
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
|
|
@ -1981,9 +1971,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.17"
|
||||
version = "0.3.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77"
|
||||
checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
|
||||
dependencies = [
|
||||
"matchers",
|
||||
"nu-ansi-term",
|
||||
|
|
|
|||
46
examples/switcher.rs
Normal file
46
examples/switcher.rs
Normal 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()
|
||||
}
|
||||
|
|
@ -5,10 +5,9 @@ use gooey::styles::components::{TextColor, WidgetBackground};
|
|||
use gooey::styles::{ColorSource, ColorTheme, FixedTheme, SurfaceTheme, Theme, ThemePair};
|
||||
use gooey::value::{Dynamic, MapEach};
|
||||
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::Run;
|
||||
use kludgine::figures::units::Lp;
|
||||
use kludgine::Color;
|
||||
|
||||
const PRIMARY_HUE: f32 = 240.;
|
||||
|
|
@ -59,16 +58,22 @@ fn main() -> gooey::Result {
|
|||
.and(neutral_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(
|
||||
default_theme.map_each(|theme| theme.primary_fixed),
|
||||
default_theme.map_each(|theme| theme.secondary_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()
|
||||
.into_window()
|
||||
.with_theme_mode(theme_mode)
|
||||
|
|
@ -132,6 +137,7 @@ fn fixed_themes(
|
|||
.and(fixed_theme(secondary, "Secondary"))
|
||||
.and(fixed_theme(tertiary, "Tertiary")),
|
||||
)
|
||||
.contain()
|
||||
.expand()
|
||||
}
|
||||
|
||||
|
|
@ -156,12 +162,18 @@ fn fixed_theme(theme: Dynamic<FixedTheme>, label: &str) -> impl MakeWidget {
|
|||
color,
|
||||
)),
|
||||
)
|
||||
.contain()
|
||||
.expand()
|
||||
}
|
||||
|
||||
fn theme(theme: Dynamic<Theme>, label: &str) -> impl MakeWidget {
|
||||
Stack::rows(
|
||||
Label::new(label)
|
||||
fn theme(theme: Dynamic<Theme>, mode: ThemeMode) -> impl MakeWidget {
|
||||
ModeSwitch::new(
|
||||
mode,
|
||||
Stack::rows(
|
||||
Label::new(match mode {
|
||||
ThemeMode::Light => "Light",
|
||||
ThemeMode::Dark => "Dark",
|
||||
})
|
||||
.and(
|
||||
Stack::columns(
|
||||
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")),
|
||||
)
|
||||
.contain()
|
||||
.expand(),
|
||||
)
|
||||
.and(surface_theme(theme.map_each(|theme| theme.surface))),
|
||||
)
|
||||
.contain(),
|
||||
)
|
||||
.expand()
|
||||
}
|
||||
|
|
@ -199,6 +214,7 @@ fn surface_theme(theme: Dynamic<SurfaceTheme>) -> impl MakeWidget {
|
|||
on_color.clone(),
|
||||
)),
|
||||
)
|
||||
.contain()
|
||||
.expand()
|
||||
.and(
|
||||
Stack::columns(
|
||||
|
|
@ -228,6 +244,7 @@ fn surface_theme(theme: Dynamic<SurfaceTheme>) -> impl MakeWidget {
|
|||
on_color.clone(),
|
||||
)),
|
||||
)
|
||||
.contain()
|
||||
.expand(),
|
||||
)
|
||||
.and(
|
||||
|
|
@ -254,9 +271,11 @@ fn surface_theme(theme: Dynamic<SurfaceTheme>) -> impl MakeWidget {
|
|||
on_color,
|
||||
)),
|
||||
)
|
||||
.contain()
|
||||
.expand(),
|
||||
),
|
||||
)
|
||||
.contain()
|
||||
.expand()
|
||||
}
|
||||
|
||||
|
|
@ -289,6 +308,7 @@ fn color_theme(theme: Dynamic<ColorTheme>, label: &str) -> impl MakeWidget {
|
|||
container,
|
||||
)),
|
||||
)
|
||||
.contain()
|
||||
.expand()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,14 +22,14 @@
|
|||
//! use gooey::value::Dynamic;
|
||||
//!
|
||||
//! let value = Dynamic::new(0);
|
||||
//!
|
||||
//! let mut reader = value.create_reader();
|
||||
//! value
|
||||
//! .transition_to(100)
|
||||
//! .over(Duration::from_millis(100))
|
||||
//! .with_easing(EaseInOutElastic)
|
||||
//! .launch();
|
||||
//! drop(value);
|
||||
//!
|
||||
//! let mut reader = value.into_reader();
|
||||
//! while reader.block_until_updated() {
|
||||
//! 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.
|
||||
pub trait PercentBetween {
|
||||
/// Return the percentage that `self` is between `min` and `max`.
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ use crate::styles::{Dimension, FocusableWidgets, VisualOrder};
|
|||
/// /// This component defaults to picking a contrasting color between `TextColor` and `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.
|
||||
/// ClosureDefaultComponent(Color, "closure_component", |context| context.query_style(&TextColor))
|
||||
/// ClosureDefaultComponent(Color, "closure_component", |context| context.get(&TextColor))
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
|
|
|
|||
|
|
@ -418,6 +418,7 @@ impl TreeData {
|
|||
|
||||
while let Some(node) = detached_nodes.pop() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,12 @@ pub trait Widget: Send + UnwindSafe + Debug + 'static {
|
|||
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
|
||||
) -> 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.
|
||||
#[allow(unused_variables)]
|
||||
fn mounted(&mut self, context: &mut EventContext<'_, '_>) {}
|
||||
|
|
@ -227,9 +233,8 @@ pub trait WrapperWidget: Debug + Send + UnwindSafe + 'static {
|
|||
available_space: Size<ConstraintLimit>,
|
||||
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
|
||||
) -> WrappedLayout {
|
||||
let child = self.child_mut().mounted(&mut context.as_event_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
|
||||
.for_other(&child)
|
||||
.layout(adjusted_space)
|
||||
|
|
@ -420,9 +425,8 @@ where
|
|||
available_space: Size<ConstraintLimit>,
|
||||
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
|
||||
) -> Size<UPx> {
|
||||
let child = self.child_mut().mounted(&mut context.as_event_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);
|
||||
layout.size
|
||||
}
|
||||
|
|
@ -606,6 +610,18 @@ pub trait MakeWidget: Sized {
|
|||
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.
|
||||
#[must_use]
|
||||
fn centered(self) -> Align {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ mod slider;
|
|||
mod space;
|
||||
pub mod stack;
|
||||
mod style;
|
||||
mod switcher;
|
||||
mod themed;
|
||||
mod tilemap;
|
||||
|
||||
|
|
@ -31,5 +32,6 @@ pub use slider::Slider;
|
|||
pub use space::Space;
|
||||
pub use stack::Stack;
|
||||
pub use style::Style;
|
||||
pub use switcher::Switcher;
|
||||
pub use themed::Themed;
|
||||
pub use tilemap::TileMap;
|
||||
|
|
|
|||
|
|
@ -12,12 +12,17 @@ use crate::ConstraintLimit;
|
|||
/// [`Expand`]ed widget.
|
||||
#[derive(Debug)]
|
||||
pub struct Expand {
|
||||
/// The weight to use when splitting available space with multiple
|
||||
/// [`Expand`] widgets.
|
||||
pub weight: u8,
|
||||
kind: ExpandKind,
|
||||
child: WidgetRef,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ExpandKind {
|
||||
Weighted(u8),
|
||||
Horizontal,
|
||||
Vertical,
|
||||
}
|
||||
|
||||
impl Default for Expand {
|
||||
fn default() -> Self {
|
||||
Self::empty()
|
||||
|
|
@ -30,7 +35,25 @@ impl Expand {
|
|||
pub fn new(child: impl MakeWidget) -> Self {
|
||||
Self {
|
||||
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 {
|
||||
Self {
|
||||
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 {
|
||||
Self {
|
||||
child: WidgetRef::new(child),
|
||||
weight,
|
||||
kind: ExpandKind::Weighted(weight),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -60,6 +83,14 @@ impl Expand {
|
|||
pub const fn child(&self) -> &WidgetRef {
|
||||
&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 {
|
||||
|
|
@ -79,15 +110,29 @@ impl WrapperWidget for Expand {
|
|||
let child = self.child.mounted(&mut context.as_event_context());
|
||||
let size = context.for_other(&child).layout(available_space);
|
||||
|
||||
Size::<UPx>::new(
|
||||
available_space
|
||||
.width
|
||||
.fit_measured(size.width, context.gfx.scale()),
|
||||
available_space
|
||||
.height
|
||||
.fit_measured(size.height, context.gfx.scale()),
|
||||
)
|
||||
.into_signed()
|
||||
.into()
|
||||
let (width, height) = match &self.kind {
|
||||
ExpandKind::Weighted(_) => (
|
||||
available_space
|
||||
.width
|
||||
.fit_measured(size.width, context.gfx.scale()),
|
||||
available_space
|
||||
.height
|
||||
.fit_measured(size.height, context.gfx.scale()),
|
||||
),
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,37 +85,38 @@ impl Stack {
|
|||
} else {
|
||||
// This is a brand new child.
|
||||
let guard = widget.lock();
|
||||
let (mut widget, dimension) =
|
||||
if let Some(expand) = guard.downcast_ref::<Expand>() {
|
||||
let weight = expand.weight;
|
||||
(
|
||||
expand.child().clone(),
|
||||
StackDimension::Fractional { weight },
|
||||
)
|
||||
} else if let Some((child, size)) =
|
||||
guard.downcast_ref::<Resize>().and_then(|r| {
|
||||
let range = match self.layout.orientation.orientation {
|
||||
StackOrientation::Row => r.height,
|
||||
StackOrientation::Column => r.width,
|
||||
};
|
||||
range.minimum().map(|min| {
|
||||
(
|
||||
r.child().clone(),
|
||||
StackDimension::Measured {
|
||||
min,
|
||||
_max: range.end,
|
||||
},
|
||||
)
|
||||
})
|
||||
let (mut widget, dimension) = if let Some((weight, expand)) = guard
|
||||
.downcast_ref::<Expand>()
|
||||
.and_then(|expand| expand.weight().map(|weight| (weight, expand)))
|
||||
{
|
||||
(
|
||||
expand.child().clone(),
|
||||
StackDimension::Fractional { weight },
|
||||
)
|
||||
} else if let Some((child, size)) =
|
||||
guard.downcast_ref::<Resize>().and_then(|r| {
|
||||
let range = match self.layout.orientation.orientation {
|
||||
StackOrientation::Row => r.height,
|
||||
StackOrientation::Column => r.width,
|
||||
};
|
||||
range.minimum().map(|min| {
|
||||
(
|
||||
r.child().clone(),
|
||||
StackDimension::Measured {
|
||||
min,
|
||||
_max: range.end,
|
||||
},
|
||||
)
|
||||
})
|
||||
{
|
||||
(child, size)
|
||||
} else {
|
||||
(
|
||||
WidgetRef::Unmounted(widget.clone()),
|
||||
StackDimension::FitContent,
|
||||
)
|
||||
};
|
||||
})
|
||||
{
|
||||
(child, size)
|
||||
} else {
|
||||
(
|
||||
WidgetRef::Unmounted(widget.clone()),
|
||||
StackDimension::FitContent,
|
||||
)
|
||||
};
|
||||
drop(guard);
|
||||
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()),
|
||||
other_constraint,
|
||||
),
|
||||
true,
|
||||
false,
|
||||
));
|
||||
self.other = self.other.max(measured);
|
||||
}
|
||||
|
|
@ -459,6 +460,18 @@ impl Layout {
|
|||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
91
src/widgets/switcher.rs
Normal file
91
src/widgets/switcher.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
|
|
@ -36,7 +36,9 @@ use crate::styles::ThemePair;
|
|||
use crate::tree::Tree;
|
||||
use crate::utils::ModifiersExt;
|
||||
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::window::sealed::WindowCommand;
|
||||
use crate::{initialize_tracing, ConstraintLimit, Run};
|
||||
|
|
@ -841,6 +843,9 @@ where
|
|||
if let Some(state) = self.mouse_state.devices.get(&device_id) {
|
||||
// Mouse Drag
|
||||
for (button, handler) in state {
|
||||
let Some(handler) = self.root.tree.widget(*handler) else {
|
||||
continue;
|
||||
};
|
||||
let mut context = EventContext::new(
|
||||
WidgetContext::new(
|
||||
handler.clone(),
|
||||
|
|
@ -878,7 +883,7 @@ where
|
|||
if widget_context.hit_test(relative) {
|
||||
widget_context.hover(relative);
|
||||
drop(widget_context);
|
||||
self.mouse_state.widget = Some(widget);
|
||||
self.mouse_state.widget = Some(widget.id());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -934,9 +939,13 @@ where
|
|||
)
|
||||
.clear_focus();
|
||||
|
||||
if let (ElementState::Pressed, Some(location), Some(hovered)) =
|
||||
(state, &self.mouse_state.location, &self.mouse_state.widget)
|
||||
{
|
||||
if let (ElementState::Pressed, Some(location), Some(hovered)) = (
|
||||
state,
|
||||
&self.mouse_state.location,
|
||||
self.mouse_state
|
||||
.widget
|
||||
.and_then(|id| self.root.tree.widget(id)),
|
||||
) {
|
||||
if let Some(handler) = recursively_handle_event(
|
||||
&mut EventContext::new(
|
||||
WidgetContext::new(
|
||||
|
|
@ -958,7 +967,7 @@ where
|
|||
.devices
|
||||
.entry(device_id)
|
||||
.or_default()
|
||||
.insert(button, handler);
|
||||
.insert(button, handler.id());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -972,7 +981,9 @@ where
|
|||
if device_buttons.is_empty() {
|
||||
self.mouse_state.devices.remove(&device_id);
|
||||
}
|
||||
|
||||
let Some(handler) = self.root.tree.widget(handler) else {
|
||||
return;
|
||||
};
|
||||
let mut context = EventContext::new(
|
||||
WidgetContext::new(
|
||||
handler,
|
||||
|
|
@ -1036,8 +1047,8 @@ fn recursively_handle_event(
|
|||
#[derive(Default)]
|
||||
struct MouseState {
|
||||
location: Option<Point<Px>>,
|
||||
widget: Option<ManagedWidget>,
|
||||
devices: AHashMap<DeviceId, AHashMap<MouseButton, ManagedWidget>>,
|
||||
widget: Option<WidgetId>,
|
||||
devices: AHashMap<DeviceId, AHashMap<MouseButton, WidgetId>>,
|
||||
}
|
||||
|
||||
pub(crate) mod sealed {
|
||||
|
|
|
|||
Loading…
Reference in a new issue