More fluent APIs

This commit is contained in:
Jonathan Johnson 2023-11-22 05:54:35 -08:00
parent 46a0758d09
commit dd38fa7bf4
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
11 changed files with 189 additions and 131 deletions

View file

@ -1,7 +1,6 @@
use gooey::value::Dynamic; use gooey::value::Dynamic;
use gooey::widget::MakeWidget; use gooey::widget::MakeWidget;
use gooey::widgets::button::ButtonKind; use gooey::widgets::button::ButtonKind;
use gooey::widgets::{Button, Checkbox};
use gooey::Run; use gooey::Run;
fn main() -> gooey::Result { fn main() -> gooey::Result {
@ -18,35 +17,39 @@ fn main() -> gooey::Result {
clicked_label clicked_label
.clone() .clone()
.and( .and(
Button::new("Normal Button") "Normal Button"
.into_button()
.on_click( .on_click(
clicked_label.with_clone(|label| { clicked_label.with_clone(|label| {
move |_| label.set(String::from("Clicked Normal Button")) move |_| label.set(String::from("Clicked Normal Button"))
}), }),
) )
.and( .and(
Button::new("Outline Button") "Outline Button"
.into_button()
.on_click(clicked_label.with_clone(|label| { .on_click(clicked_label.with_clone(|label| {
move |_| label.set(String::from("Clicked Outline Button")) move |_| label.set(String::from("Clicked Outline Button"))
})) }))
.kind(ButtonKind::Outline), .kind(ButtonKind::Outline),
) )
.and( .and(
Button::new("Transparent Button") "Transparent Button"
.into_button()
.on_click(clicked_label.with_clone(|label| { .on_click(clicked_label.with_clone(|label| {
move |_| label.set(String::from("Clicked Transparent Button")) move |_| label.set(String::from("Clicked Transparent Button"))
})) }))
.kind(ButtonKind::Transparent), .kind(ButtonKind::Transparent),
) )
.and( .and(
Button::new("Default Button") "Default Button"
.into_button()
.on_click(clicked_label.with_clone(|label| { .on_click(clicked_label.with_clone(|label| {
move |_| label.set(String::from("Clicked Default Button")) move |_| label.set(String::from("Clicked Default Button"))
})) }))
.kind(default_button_style) .kind(default_button_style)
.into_default(), .into_default(),
) )
.and(Checkbox::new(default_is_outline, "Set Default to Outline")) .and("Set Default to Outline".into_checkbox(default_is_outline))
.into_columns(), .into_columns(),
) )
.into_rows() .into_rows()

View file

@ -1,14 +1,15 @@
use gooey::value::Dynamic; use gooey::value::Dynamic;
use gooey::widget::MakeWidget; use gooey::widget::MakeWidget;
use gooey::widgets::checkbox::CheckboxState; use gooey::widgets::checkbox::{Checkable, CheckboxState};
use gooey::widgets::Checkbox;
use gooey::Run; use gooey::Run;
fn main() -> gooey::Result { fn main() -> gooey::Result {
let checkbox_state = Dynamic::new(CheckboxState::Checked); let checkbox_state = Dynamic::new(CheckboxState::Checked);
let label = checkbox_state.map_each(|state| format!("Check Me! Current: {state:?}")); let label = checkbox_state.map_each(|state| format!("Check Me! Current: {state:?}"));
Checkbox::new(checkbox_state.clone(), label) checkbox_state
.clone()
.into_checkbox(label)
.and("Maybe".into_button().on_click(move |()| { .and("Maybe".into_button().on_click(move |()| {
checkbox_state.update(CheckboxState::Indeterminant); checkbox_state.update(CheckboxState::Indeterminant);
})) }))

View file

@ -2,7 +2,7 @@ use gooey::value::Dynamic;
use gooey::widget::MakeWidget; use gooey::widget::MakeWidget;
use gooey::widgets::input::InputValue; use gooey::widgets::input::InputValue;
use gooey::widgets::slider::Slidable; use gooey::widgets::slider::Slidable;
use gooey::widgets::{Checkbox, Custom}; use gooey::widgets::Custom;
use gooey::Run; use gooey::Run;
use kludgine::figures::units::Lp; use kludgine::figures::units::Lp;
@ -14,10 +14,7 @@ fn main() -> gooey::Result {
.and(Dynamic::<u8>::default().slider_between(0_u8, 100_u8)) .and(Dynamic::<u8>::default().slider_between(0_u8, 100_u8))
.and("Range Slider") .and("Range Slider")
.and(Dynamic::new(10..=30).slider_between(0_u8, 100_u8)) .and(Dynamic::new(10..=30).slider_between(0_u8, 100_u8))
.and(Checkbox::new( .and("Allow Custom Widget to Lose Focus".into_checkbox(allow_blur.clone()))
allow_blur.clone(),
"Allow Custom Widget to Lose Focus",
))
.and( .and(
Custom::empty() Custom::empty()
.on_accept_focus(|_| true) .on_accept_focus(|_| true)

View file

@ -1,7 +1,6 @@
use gooey::value::Dynamic; use gooey::value::Dynamic;
use gooey::widget::{MakeWidget, HANDLED, IGNORED}; use gooey::widget::{MakeWidget, HANDLED, IGNORED};
use gooey::widgets::input::InputValue; use gooey::widgets::input::InputValue;
use gooey::widgets::Space;
use gooey::Run; use gooey::Run;
use kludgine::app::winit::event::ElementState; use kludgine::app::winit::event::ElementState;
use kludgine::app::winit::keyboard::{Key, NamedKey}; use kludgine::app::winit::keyboard::{Key, NamedKey};
@ -15,7 +14,7 @@ fn main() -> gooey::Result {
.clone() .clone()
.vertical_scroll() .vertical_scroll()
.expand() .expand()
.and(Space::colored(Color::RED).expand_weighted(2)) .and(Color::RED.expand_weighted(2))
.into_columns() .into_columns()
.expand() .expand()
.and(chat_message.clone().into_input().on_key(move |input| { .and(chat_message.clone().into_input().on_key(move |input| {

View file

@ -2,7 +2,6 @@ use gooey::value::{Dynamic, MapEach};
use gooey::widget::MakeWidget; use gooey::widget::MakeWidget;
use gooey::widgets::progress::Progressable; use gooey::widgets::progress::Progressable;
use gooey::widgets::slider::Slidable; use gooey::widgets::slider::Slidable;
use gooey::widgets::Checkbox;
use gooey::Run; use gooey::Run;
use kludgine::figures::units::Lp; use kludgine::figures::units::Lp;
use kludgine::figures::Size; use kludgine::figures::Size;
@ -17,7 +16,7 @@ fn main() -> gooey::Result {
.clone() .clone()
.slider() .slider()
.and(progress.clone().progress_bar()) .and(progress.clone().progress_bar())
.and(Checkbox::new(indeterminant.clone(), "Indeterminant")) .and("Indeterminant".into_checkbox(indeterminant))
.into_rows() .into_rows()
.fit_horizontally() .fit_horizontally()
.expand() .expand()

View file

@ -1,4 +1,3 @@
use gooey::animation::ZeroToOne;
use gooey::styles::components::{TextColor, WidgetBackground}; use gooey::styles::components::{TextColor, WidgetBackground};
use gooey::styles::{ use gooey::styles::{
ColorScheme, ColorSource, ColorTheme, FixedTheme, SurfaceTheme, Theme, ThemePair, ColorScheme, ColorSource, ColorTheme, FixedTheme, SurfaceTheme, Theme, ThemePair,
@ -7,7 +6,6 @@ use gooey::value::{Dynamic, MapEach};
use gooey::widget::MakeWidget; use gooey::widget::MakeWidget;
use gooey::widgets::input::InputValue; use gooey::widgets::input::InputValue;
use gooey::widgets::slider::Slidable; use gooey::widgets::slider::Slidable;
use gooey::widgets::{Slider, Stack};
use gooey::window::ThemeMode; use gooey::window::ThemeMode;
use gooey::Run; use gooey::Run;
use kludgine::Color; use kludgine::Color;
@ -100,13 +98,12 @@ fn color_editor(
( (
color, color,
Stack::rows( label
label .and(hue.slider_between(0., 360.))
.and(hue.slider_between(0., 360.)) .and(hue_text.into_input())
.and(hue_text.into_input()) .and(saturation.slider())
.and(Slider::<ZeroToOne>::from_value(saturation)) .and(saturation_text.into_input())
.and(saturation_text.into_input()), .into_rows(),
),
) )
} }
@ -179,83 +176,80 @@ fn theme(theme: Dynamic<Theme>, mode: ThemeMode) -> impl MakeWidget {
fn surface_theme(theme: Dynamic<SurfaceTheme>) -> impl MakeWidget { fn surface_theme(theme: Dynamic<SurfaceTheme>) -> impl MakeWidget {
let color = theme.map_each(|theme| theme.color); let color = theme.map_each(|theme| theme.color);
let on_color = theme.map_each(|theme| theme.on_color); let on_color = theme.map_each(|theme| theme.on_color);
Stack::rows(
Stack::columns( swatch(color.clone(), "Surface", on_color.clone())
swatch(color.clone(), "Surface", on_color.clone()) .and(swatch(
.and(swatch( theme.map_each(|theme| theme.bright_color),
theme.map_each(|theme| theme.bright_color), "Bright Surface",
"Bright Surface", on_color.clone(),
on_color.clone(), ))
)) .and(swatch(
.and(swatch( theme.map_each(|theme| theme.dim_color),
theme.map_each(|theme| theme.dim_color), "Dim Surface",
"Dim Surface", on_color.clone(),
on_color.clone(), ))
)), .into_columns()
)
.contain() .contain()
.expand() .expand()
.and( .and(
Stack::columns( swatch(
swatch( theme.map_each(|theme| theme.lowest_container),
theme.map_each(|theme| theme.lowest_container), "Lowest Container",
"Lowest Container", on_color.clone(),
on_color.clone(),
)
.and(swatch(
theme.map_each(|theme| theme.low_container),
"Low Container",
on_color.clone(),
))
.and(swatch(
theme.map_each(|theme| theme.container),
"Container",
on_color.clone(),
))
.and(swatch(
theme.map_each(|theme| theme.high_container),
"High Container",
on_color.clone(),
))
.and(swatch(
theme.map_each(|theme| theme.highest_container),
"Highest Container",
on_color.clone(),
)),
) )
.and(swatch(
theme.map_each(|theme| theme.low_container),
"Low Container",
on_color.clone(),
))
.and(swatch(
theme.map_each(|theme| theme.container),
"Container",
on_color.clone(),
))
.and(swatch(
theme.map_each(|theme| theme.high_container),
"High Container",
on_color.clone(),
))
.and(swatch(
theme.map_each(|theme| theme.highest_container),
"Highest Container",
on_color.clone(),
))
.into_columns()
.contain() .contain()
.expand(), .expand(),
) )
.and( .and(
Stack::columns( swatch(on_color.clone(), "On Surface", color.clone())
swatch(on_color.clone(), "On Surface", color.clone()) .and(swatch(
.and(swatch( theme.map_each(|theme| theme.on_color_variant),
theme.map_each(|theme| theme.on_color_variant), "On Color Variant",
"On Color Variant", color.clone(),
color.clone(), ))
)) .and(swatch(
.and(swatch( theme.map_each(|theme| theme.outline),
theme.map_each(|theme| theme.outline), "Outline",
"Outline", color.clone(),
color.clone(), ))
)) .and(swatch(
.and(swatch( theme.map_each(|theme| theme.outline_variant),
theme.map_each(|theme| theme.outline_variant), "Outline Variant",
"Outline Variant", color,
color, ))
)) .and(swatch(
.and(swatch( theme.map_each(|theme| theme.opaque_widget),
theme.map_each(|theme| theme.opaque_widget), "Opaque Widget",
"Opaque Widget", on_color,
on_color, ))
)), .into_columns()
) .contain()
.contain() .expand(),
.expand(), )
), .into_rows()
) .contain()
.contain() .expand()
.expand()
} }
fn color_theme(theme: Dynamic<ColorTheme>, label: &str) -> impl MakeWidget { fn color_theme(theme: Dynamic<ColorTheme>, label: &str) -> impl MakeWidget {
@ -265,36 +259,36 @@ fn color_theme(theme: Dynamic<ColorTheme>, label: &str) -> impl MakeWidget {
let on_color = theme.map_each(|theme| theme.on_color); let on_color = theme.map_each(|theme| theme.on_color);
let container = theme.map_each(|theme| theme.container); let container = theme.map_each(|theme| theme.container);
let on_container = theme.map_each(|theme| theme.on_container); let on_container = theme.map_each(|theme| theme.on_container);
Stack::rows(
swatch(color.clone(), label, on_color.clone()) swatch(color.clone(), label, on_color.clone())
.and(swatch( .and(swatch(
dim_color.clone(), dim_color.clone(),
&format!("{label} Dim"), &format!("{label} Dim"),
on_color.clone(), on_color.clone(),
)) ))
.and(swatch( .and(swatch(
bright_color.clone(), bright_color.clone(),
&format!("{label} bright"), &format!("{label} bright"),
on_color.clone(), on_color.clone(),
)) ))
.and(swatch( .and(swatch(
on_color.clone(), on_color.clone(),
&format!("On {label}"), &format!("On {label}"),
color.clone(), color.clone(),
)) ))
.and(swatch( .and(swatch(
container.clone(), container.clone(),
&format!("{label} Container"), &format!("{label} Container"),
on_container.clone(), on_container.clone(),
)) ))
.and(swatch( .and(swatch(
on_container, on_container,
&format!("On {label} Container"), &format!("On {label} Container"),
container, container,
)), ))
) .into_rows()
.contain() .contain()
.expand() .expand()
} }
fn swatch(background: Dynamic<Color>, label: &str, text: Dynamic<Color>) -> impl MakeWidget { fn swatch(background: Dynamic<Color>, label: &str, text: Dynamic<Color>) -> impl MakeWidget {

View file

@ -293,6 +293,12 @@ pub trait AnimationTarget: Sized + Send + Sync {
fn over(self, duration: Duration) -> Animation<Self, Linear> { fn over(self, duration: Duration) -> Animation<Self, Linear> {
Animation::new(self, duration) Animation::new(self, duration)
} }
/// Returns a pending animation that transitions to the target values after
/// no delay.
fn immediately(self) -> Animation<Self, Linear> {
self.over(Duration::ZERO)
}
} }
/// The target of an [`Animate`] implementor. /// The target of an [`Animate`] implementor.

View file

@ -37,7 +37,8 @@ pub use self::tick::{InputState, Tick};
pub enum ConstraintLimit { pub enum ConstraintLimit {
/// The widget is expected to occupy a known size. /// The widget is expected to occupy a known size.
Fill(UPx), Fill(UPx),
/// The widget is expected to resize itself to fit within the size provided. /// The widget is expected to resize itself to fit its contents, trying to
/// stay within the size given.
SizeToFit(UPx), SizeToFit(UPx),
} }

View file

@ -25,9 +25,11 @@ use crate::styles::{
}; };
use crate::tree::Tree; use crate::tree::Tree;
use crate::utils::IgnorePoison; use crate::utils::IgnorePoison;
use crate::value::{IntoValue, Value}; use crate::value::{IntoDynamic, IntoValue, Value};
use crate::widgets::checkbox::{Checkable, CheckboxState};
use crate::widgets::{ use crate::widgets::{
Align, Button, Container, Expand, Resize, Scroll, Stack, Style, Themed, ThemedMode, Align, Button, Checkbox, Container, Expand, Resize, Scroll, Space, Stack, Style, Themed,
ThemedMode,
}; };
use crate::window::{RunningWindow, ThemeMode, Window, WindowBehavior}; use crate::window::{RunningWindow, ThemeMode, Window, WindowBehavior};
use crate::{ConstraintLimit, Run}; use crate::{ConstraintLimit, Run};
@ -743,11 +745,16 @@ pub trait MakeWidget: Sized {
Resize::from_height(height, self) Resize::from_height(height, self)
} }
/// Returns this string as a clickable button. /// Returns this widget as the contents of a clickable button.
fn into_button(self) -> Button { fn into_button(self) -> Button {
Button::new(self) Button::new(self)
} }
/// Returns this widget as the label of a Checkbox.
fn into_checkbox(self, value: impl IntoDynamic<CheckboxState>) -> Checkbox {
value.into_checkbox(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 {
@ -875,6 +882,18 @@ impl MakeWidget for WidgetInstance {
} }
} }
impl MakeWidget for Color {
fn make_widget(self) -> WidgetInstance {
Space::colored(self).make_widget()
}
}
impl MakeWidget for () {
fn make_widget(self) -> WidgetInstance {
Space::clear().make_widget()
}
}
/// A type that represents whether an event has been handled or ignored. /// A type that represents whether an event has been handled or ignored.
pub type EventHandling = ControlFlow<EventHandled, EventIgnored>; pub type EventHandling = ControlFlow<EventHandled, EventIgnored>;

View file

@ -95,6 +95,26 @@ impl From<bool> for CheckboxState {
} }
} }
impl From<CheckboxState> for Option<bool> {
fn from(value: CheckboxState) -> Self {
match value {
CheckboxState::Indeterminant => None,
CheckboxState::Unchecked => Some(false),
CheckboxState::Checked => Some(true),
}
}
}
impl From<Option<bool>> for CheckboxState {
fn from(value: Option<bool>) -> Self {
match value {
Some(true) => CheckboxState::Checked,
Some(false) => CheckboxState::Unchecked,
None => CheckboxState::Indeterminant,
}
}
}
impl TryFrom<CheckboxState> for bool { impl TryFrom<CheckboxState> for bool {
type Error = CheckboxToBoolError; type Error = CheckboxToBoolError;
@ -127,6 +147,15 @@ impl IntoDynamic<CheckboxState> for Dynamic<bool> {
} }
} }
impl IntoDynamic<CheckboxState> for Dynamic<Option<bool>> {
fn into_dynamic(self) -> Dynamic<CheckboxState> {
self.linked(
|bool| CheckboxState::from(*bool),
|tri_state: &CheckboxState| bool::try_from(*tri_state).ok(),
)
}
}
/// An [`CheckboxState::Indeterminant`] was encountered when converting to a /// An [`CheckboxState::Indeterminant`] was encountered when converting to a
/// `bool`. /// `bool`.
#[derive(Debug, Clone, Copy, Eq, PartialEq)] #[derive(Debug, Clone, Copy, Eq, PartialEq)]
@ -222,3 +251,13 @@ impl WrapperWidget for CheckboxLabel {
} }
} }
} }
/// A value that can be used as a checkbox.
pub trait Checkable: IntoDynamic<CheckboxState> + Sized {
/// Returns a new checkbox using `self` as the value and `label`.
fn into_checkbox(self, label: impl MakeWidget) -> Checkbox {
Checkbox::new(self.into_dynamic(), label)
}
}
impl<T> Checkable for T where T: IntoDynamic<CheckboxState> {}

View file

@ -88,7 +88,7 @@ fn update_progress_bar(
start.transition_to(ZeroToOne::ZERO), start.transition_to(ZeroToOne::ZERO),
end.transition_to(ZeroToOne::ZERO), end.transition_to(ZeroToOne::ZERO),
) )
.over(Duration::ZERO) .immediately()
.and_then( .and_then(
end.transition_to(ZeroToOne::new(0.66)) end.transition_to(ZeroToOne::new(0.66))
.over(Duration::from_millis(500)) .over(Duration::from_millis(500))