mirror of
https://github.com/danbulant/cushy
synced 2026-06-24 17:12:11 +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]]
|
[[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
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::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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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`.
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
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::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 {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue