More combinators

Maybe I went overboard.
This commit is contained in:
Jonathan Johnson 2023-11-08 20:10:01 -08:00
parent 8e268615a1
commit 22fb955dca
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
12 changed files with 168 additions and 86 deletions

View file

@ -2,8 +2,9 @@ use std::time::Duration;
use gooey::animation::{AnimationHandle, AnimationTarget, IntoAnimate, Spawn}; use gooey::animation::{AnimationHandle, AnimationTarget, IntoAnimate, Spawn};
use gooey::value::Dynamic; use gooey::value::Dynamic;
use gooey::widget::MakeWidget;
use gooey::widgets::{Button, Label, Stack}; use gooey::widgets::{Button, Label, Stack};
use gooey::{children, Run, WithClone}; use gooey::{Run, WithClone};
fn main() -> gooey::Result { fn main() -> gooey::Result {
let animation = Dynamic::new(AnimationHandle::new()); let animation = Dynamic::new(AnimationHandle::new());
@ -17,11 +18,12 @@ fn main() -> gooey::Result {
.on_complete(|| println!("Gooey animations are neat!")) .on_complete(|| println!("Gooey animations are neat!"))
.launch(); .launch();
Stack::columns(children![ Stack::columns(
Button::new("To 0").on_click(animate_to(&animation, &value, 0)), Button::new("To 0")
Label::new(label), .on_click(animate_to(&animation, &value, 0))
Button::new("To 100").on_click(animate_to(&animation, &value, 100)), .and(Label::new(label))
]) .and(Button::new("To 100").on_click(animate_to(&animation, &value, 100))),
)
.run() .run()
} }

View file

@ -1,25 +1,28 @@
use std::string::ToString; use std::string::ToString;
use gooey::value::Dynamic; use gooey::value::Dynamic;
use gooey::widgets::{Align, Button, Expand, Label, Resize, Stack}; use gooey::widget::MakeWidget;
use gooey::{children, Run}; use gooey::widgets::{Button, Label, Resize, Stack};
use gooey::Run;
use kludgine::figures::units::Lp; use kludgine::figures::units::Lp;
fn main() -> gooey::Result { fn main() -> gooey::Result {
let counter = Dynamic::new(0i32); let counter = Dynamic::new(0i32);
let label = counter.map_each(ToString::to_string); let label = counter.map_each(ToString::to_string);
Expand::new(Align::centered(Stack::columns(children![ Stack::columns(
Resize::width(Lp::points(100), Label::new(label)), Resize::width(Lp::points(100), Label::new(label))
Button::new("+").on_click(counter.with_clone(|counter| { .and(Button::new("+").on_click(counter.with_clone(|counter| {
move |_| { move |_| {
counter.set(counter.get() + 1); counter.set(counter.get() + 1);
} }
})), })))
Button::new("-").on_click(counter.with_clone(|counter| { .and(Button::new("-").on_click(counter.with_clone(|counter| {
move |_| { move |_| {
counter.set(counter.get() - 1); counter.set(counter.get() - 1);
} }
})), }))),
]))) )
.centered()
.expand()
.run() .run()
} }

View file

@ -1,7 +1,7 @@
use gooey::value::Dynamic; use gooey::value::Dynamic;
use gooey::widget::{MakeWidget, HANDLED, IGNORED}; use gooey::widget::{MakeWidget, HANDLED, IGNORED};
use gooey::widgets::{Canvas, Expand, Input, Label, Scroll, Stack}; use gooey::widgets::{Canvas, Input, Label, Stack};
use gooey::{children, Run}; use gooey::Run;
use kludgine::app::winit::event::ElementState; use kludgine::app::winit::event::ElementState;
use kludgine::app::winit::keyboard::Key; use kludgine::app::winit::keyboard::Key;
use kludgine::figures::{Point, Rect}; use kludgine::figures::{Point, Rect};
@ -12,11 +12,9 @@ fn main() -> gooey::Result {
let chat_log = Dynamic::new("Chat log goes here.\n".repeat(100)); let chat_log = Dynamic::new("Chat log goes here.\n".repeat(100));
let chat_message = Dynamic::new(String::new()); let chat_message = Dynamic::new(String::new());
Expand::new(Stack::rows(children![ Stack::rows(
Expand::new(Stack::columns(children![ Stack::columns(
Expand::new(Scroll::vertical(Label::new(chat_log.clone()))), Label::new(chat_log.clone()).vertical_scroll().expand().and(
Expand::weighted(
2,
Canvas::new(|context| { Canvas::new(|context| {
let entire_canvas = Rect::from(context.graphics.size()); let entire_canvas = Rect::from(context.graphics.size());
context.graphics.draw_shape( context.graphics.draw_shape(
@ -26,10 +24,12 @@ fn main() -> gooey::Result {
None, None,
); );
}) })
) .expand_weighted(2),
])), ),
Input::new(chat_message.clone()) )
.on_key(move |input| match (input.state, input.logical_key) { .expand()
.and(Input::new(chat_message.clone()).on_key(move |input| {
match (input.state, input.logical_key) {
(ElementState::Pressed, Key::Enter) => { (ElementState::Pressed, Key::Enter) => {
let new_message = chat_message.map_mut(|text| std::mem::take(text)); let new_message = chat_message.map_mut(|text| std::mem::take(text));
chat_log.map_mut(|chat_log| { chat_log.map_mut(|chat_log| {
@ -39,8 +39,9 @@ fn main() -> gooey::Result {
HANDLED HANDLED
} }
_ => IGNORED, _ => IGNORED,
}) }
.make_widget(), })),
])) )
.expand()
.run() .run()
} }

View file

@ -1,6 +1,7 @@
use gooey::widgets::{Expand, Input}; use gooey::widget::MakeWidget;
use gooey::widgets::Input;
use gooey::Run; use gooey::Run;
fn main() -> gooey::Result { fn main() -> gooey::Result {
Expand::new(Input::new("Hello")).run() Input::new("Hello").expand().run()
} }

View file

@ -2,8 +2,8 @@ use std::process::exit;
use gooey::value::{Dynamic, MapEach}; use gooey::value::{Dynamic, MapEach};
use gooey::widget::MakeWidget; use gooey::widget::MakeWidget;
use gooey::widgets::{Align, Button, Expand, Input, Label, Resize, Stack}; use gooey::widgets::{Button, Expand, Input, Label, Resize, Stack};
use gooey::{children, Run}; use gooey::Run;
use kludgine::figures::units::Lp; use kludgine::figures::units::Lp;
fn main() -> gooey::Result { fn main() -> gooey::Result {
@ -13,42 +13,49 @@ fn main() -> gooey::Result {
let valid = let valid =
(&username, &password).map_each(|(username, password)| validate(username, password)); (&username, &password).map_each(|(username, password)| validate(username, password));
Expand::new(Align::centered(Resize::width( Resize::width(
// TODO We need a min/max range for the Resize widget // TODO We need a min/max range for the Resize widget
Lp::points(400), Lp::points(400),
Stack::rows(children![ Stack::rows(
Stack::columns(children![ Stack::columns(
Label::new("Username"), Label::new("Username").and(
Expand::new(Align::centered(Input::new(username.clone())).fit_horizontally()), Input::new(username.clone())
]), .centered()
Stack::columns(children![ .fit_horizontally()
Label::new("Password"), .expand(),
Expand::new(
Align::centered(
// TODO secure input
Input::new(password.clone())
)
.fit_horizontally()
), ),
]), )
Stack::columns(children![ .and(Stack::columns(
Label::new("Password").and(
// TODO secure input
Input::new(password.clone())
.centered()
.fit_horizontally()
.expand(),
),
))
.and(Stack::columns(
Button::new("Cancel") Button::new("Cancel")
.on_click(|_| { .on_click(|_| {
eprintln!("Login cancelled"); eprintln!("Login cancelled");
exit(0) exit(0)
}) })
.into_escape(), .into_escape()
Expand::empty(), .and(Expand::empty())
Button::new("Log In") .and(
.enabled(valid) Button::new("Log In")
.on_click(move |_| { .enabled(valid)
println!("Welcome, {}", username.get()); .on_click(move |_| {
exit(0); println!("Welcome, {}", username.get());
}) exit(0);
.into_default(), })
]), .into_default(),
]), ),
))) )),
),
)
.centered()
.expand()
.run() .run()
} }

View file

@ -1,6 +1,9 @@
use gooey::widgets::{Label, Scroll}; use gooey::widget::MakeWidget;
use gooey::widgets::Label;
use gooey::Run; use gooey::Run;
fn main() -> gooey::Result { fn main() -> gooey::Result {
Scroll::new(Label::new(include_str!("../src/widgets/scroll.rs"))).run() Label::new(include_str!("../src/widgets/scroll.rs"))
.scroll()
.run()
} }

View file

@ -1,22 +1,15 @@
use gooey::styles::components::TextColor; use gooey::styles::components::TextColor;
use gooey::styles::Styles; use gooey::styles::Styles;
use gooey::widget::{Children, MakeWidget, Widget}; use gooey::widget::{MakeWidget, Widget};
use gooey::widgets::stack::Stack; use gooey::widgets::stack::Stack;
use gooey::widgets::{Button, Style}; use gooey::widgets::{Button, Style};
use gooey::window::Window;
use gooey::{styles, Run}; use gooey::{styles, Run};
use kludgine::Color; use kludgine::Color;
fn main() -> gooey::Result { fn main() -> gooey::Result {
Window::for_widget( Stack::rows(Button::new("Green").and(red_text(Button::new("Red"))))
Stack::rows( .with_styles(Styles::new().with(&TextColor, Color::GREEN))
Children::new() .run()
.with_widget(Button::new("Default"))
.with_widget(red_text(Button::new("Styled"))),
)
.with_styles(Styles::new().with(&TextColor, Color::GREEN)),
)
.run()
} }
/// Creating reusable style helpers that work with any Widget is straightfoward /// Creating reusable style helpers that work with any Widget is straightfoward

View file

@ -92,6 +92,7 @@ pub trait Run: Sized {
/// Creates a [`Children`](crate::widget::Children) instance with the given list /// Creates a [`Children`](crate::widget::Children) instance with the given list
/// of widgets. /// of widgets.
#[macro_export] #[macro_export]
#[deprecated = "use MakeWidget.and()/Children.and() to chain widgets without a macro"]
macro_rules! children { macro_rules! children {
() => { () => {
$crate::widget::Children::new() $crate::widget::Children::new()

View file

@ -396,8 +396,8 @@ impl TreeData {
let node = &self.nodes[&perspective]; let node = &self.nodes[&perspective];
if let Some(styles) = &node.styles { if let Some(styles) = &node.styles {
query.retain(|name| { query.retain(|name| {
if let Some(component) = styles.get(name) { if let Some(component) = styles.get(dbg!(name)) {
resolved.insert(name, component.clone()); resolved.insert(name, dbg!(component.clone()));
false false
} else { } else {
true true

View file

@ -752,14 +752,36 @@ macro_rules! impl_tuple_for_each {
// //
[$type:ident $field:tt $var:ident, $($rtype:ident $rfield:tt $rvar:ident),+] [$type:ident $field:tt $var:ident, $($rtype:ident $rfield:tt $rvar:ident),+]
) => { ) => {
impl_tuple_for_each!( impl_tuple_for_each!(
invoke invoke
$self $for_each $self $for_each
$type $field $var $type $field $var
[$($ltype $lfield $lvar,)* $type $field $var, $($rtype $rfield $rvar),+] [$($ltype $lfield $lvar,)* $type $field $var, $($rtype $rfield $rvar),+]
[$($ltype $lfield $lvar,)* $($rtype $rfield $rvar),+] [$($ltype $lfield $lvar,)* $($rtype $rfield $rvar),+]
) );
impl_tuple_for_each!(
invoke
$self $for_each
[$($ltype $lfield $lvar,)* $type $field $var]
[$($rtype $rfield $rvar),+]
);
};
(
invoke
// Identifiers used from the outer method
$self:ident $for_each:ident
// List of all tuple fields that have already been positioned as the focused call
[$($ltype:ident $lfield:tt $lvar:ident),+]
//
[$type:ident $field:tt $var:ident]
) => {
impl_tuple_for_each!(
invoke
$self $for_each
$type $field $var
[$($ltype $lfield $lvar,)+ $type $field $var]
[$($ltype $lfield $lvar),+]
);
}; };
( (
invoke invoke

View file

@ -19,7 +19,7 @@ use crate::styles::components::VisualOrder;
use crate::styles::Styles; use crate::styles::Styles;
use crate::tree::Tree; use crate::tree::Tree;
use crate::value::{IntoValue, Value}; use crate::value::{IntoValue, Value};
use crate::widgets::Style; use crate::widgets::{Align, Expand, Scroll, Style};
use crate::window::{RunningWindow, Window, WindowBehavior}; use crate::window::{RunningWindow, Window, WindowBehavior};
use crate::{ConstraintLimit, Run}; use crate::{ConstraintLimit, Run};
@ -479,6 +479,51 @@ pub trait MakeWidget: Sized {
fn into_escape(self) -> WidgetInstance { fn into_escape(self) -> WidgetInstance {
self.make_widget().into_escape() self.make_widget().into_escape()
} }
/// Returns a collection of widgets using `self` and `other`.
fn and(self, other: impl MakeWidget) -> Children {
let mut children = Children::new();
children.push(self);
children.push(other);
children
}
/// Expands `self` to grow to fill its parent.
#[must_use]
fn expand(self) -> Expand {
Expand::new(self)
}
/// Expands `self` to grow to fill its parent proportionally with other
/// weighted siblings.
#[must_use]
fn expand_weighted(self, weight: u8) -> Expand {
Expand::weighted(weight, self)
}
/// Aligns `self` to the center vertically and horizontally.
#[must_use]
fn centered(self) -> Align {
Align::centered(self)
}
/// Allows scrolling `self` both vertically and horizontally.
#[must_use]
fn scroll(self) -> Scroll {
Scroll::new(self)
}
/// Allows scrolling `self` vertically.
#[must_use]
fn vertical_scroll(self) -> Scroll {
Scroll::vertical(self)
}
/// Allows scrolling `self` horizontally.
#[must_use]
fn horizontal_scroll(self) -> Scroll {
Scroll::horizontal(self)
}
} }
/// A type that can create a [`WidgetInstance`] with a preallocated /// A type that can create a [`WidgetInstance`] with a preallocated
@ -945,7 +990,7 @@ impl Children {
} }
/// Adds `widget` to self and returns the updated list. /// Adds `widget` to self and returns the updated list.
pub fn with_widget<W>(mut self, widget: W) -> Self pub fn and<W>(mut self, widget: W) -> Self
where where
W: MakeWidget, W: MakeWidget,
{ {

View file

@ -365,6 +365,10 @@ where
if self.initial_frame { if self.initial_frame {
self.initial_frame = false; self.initial_frame = false;
self.root
.lock()
.as_widget()
.mounted(&mut layout_context.as_event_context());
layout_context.focus(); layout_context.focus();
layout_context.as_event_context().apply_pending_state(); layout_context.as_event_context().apply_pending_state();
} }