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::widget::MakeWidget;
use gooey::widgets::button::ButtonKind;
use gooey::widgets::{Button, Checkbox};
use gooey::Run;
fn main() -> gooey::Result {
@ -18,35 +17,39 @@ fn main() -> gooey::Result {
clicked_label
.clone()
.and(
Button::new("Normal Button")
"Normal Button"
.into_button()
.on_click(
clicked_label.with_clone(|label| {
move |_| label.set(String::from("Clicked Normal Button"))
}),
)
.and(
Button::new("Outline Button")
"Outline Button"
.into_button()
.on_click(clicked_label.with_clone(|label| {
move |_| label.set(String::from("Clicked Outline Button"))
}))
.kind(ButtonKind::Outline),
)
.and(
Button::new("Transparent Button")
"Transparent Button"
.into_button()
.on_click(clicked_label.with_clone(|label| {
move |_| label.set(String::from("Clicked Transparent Button"))
}))
.kind(ButtonKind::Transparent),
)
.and(
Button::new("Default Button")
"Default Button"
.into_button()
.on_click(clicked_label.with_clone(|label| {
move |_| label.set(String::from("Clicked Default Button"))
}))
.kind(default_button_style)
.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_rows()

View file

@ -1,14 +1,15 @@
use gooey::value::Dynamic;
use gooey::widget::MakeWidget;
use gooey::widgets::checkbox::CheckboxState;
use gooey::widgets::Checkbox;
use gooey::widgets::checkbox::{Checkable, CheckboxState};
use gooey::Run;
fn main() -> gooey::Result {
let checkbox_state = Dynamic::new(CheckboxState::Checked);
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 |()| {
checkbox_state.update(CheckboxState::Indeterminant);
}))

View file

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

View file

@ -1,7 +1,6 @@
use gooey::value::Dynamic;
use gooey::widget::{MakeWidget, HANDLED, IGNORED};
use gooey::widgets::input::InputValue;
use gooey::widgets::Space;
use gooey::Run;
use kludgine::app::winit::event::ElementState;
use kludgine::app::winit::keyboard::{Key, NamedKey};
@ -15,7 +14,7 @@ fn main() -> gooey::Result {
.clone()
.vertical_scroll()
.expand()
.and(Space::colored(Color::RED).expand_weighted(2))
.and(Color::RED.expand_weighted(2))
.into_columns()
.expand()
.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::widgets::progress::Progressable;
use gooey::widgets::slider::Slidable;
use gooey::widgets::Checkbox;
use gooey::Run;
use kludgine::figures::units::Lp;
use kludgine::figures::Size;
@ -17,7 +16,7 @@ fn main() -> gooey::Result {
.clone()
.slider()
.and(progress.clone().progress_bar())
.and(Checkbox::new(indeterminant.clone(), "Indeterminant"))
.and("Indeterminant".into_checkbox(indeterminant))
.into_rows()
.fit_horizontally()
.expand()

View file

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

View file

@ -37,7 +37,8 @@ pub use self::tick::{InputState, Tick};
pub enum ConstraintLimit {
/// The widget is expected to occupy a known size.
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),
}

View file

@ -25,9 +25,11 @@ use crate::styles::{
};
use crate::tree::Tree;
use crate::utils::IgnorePoison;
use crate::value::{IntoValue, Value};
use crate::value::{IntoDynamic, IntoValue, Value};
use crate::widgets::checkbox::{Checkable, CheckboxState};
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::{ConstraintLimit, Run};
@ -743,11 +745,16 @@ pub trait MakeWidget: Sized {
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 {
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.
#[must_use]
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.
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 {
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
/// `bool`.
#[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),
end.transition_to(ZeroToOne::ZERO),
)
.over(Duration::ZERO)
.immediately()
.and_then(
end.transition_to(ZeroToOne::new(0.66))
.over(Duration::from_millis(500))