diff --git a/examples/animation.rs b/examples/animation.rs index f8c138a..33a1444 100644 --- a/examples/animation.rs +++ b/examples/animation.rs @@ -2,8 +2,9 @@ use std::time::Duration; use gooey::animation::{AnimationHandle, AnimationTarget, IntoAnimate, Spawn}; use gooey::value::Dynamic; +use gooey::widget::MakeWidget; use gooey::widgets::{Button, Label, Stack}; -use gooey::{children, Run, WithClone}; +use gooey::{Run, WithClone}; fn main() -> gooey::Result { let animation = Dynamic::new(AnimationHandle::new()); @@ -17,11 +18,12 @@ fn main() -> gooey::Result { .on_complete(|| println!("Gooey animations are neat!")) .launch(); - Stack::columns(children![ - Button::new("To 0").on_click(animate_to(&animation, &value, 0)), - Label::new(label), - Button::new("To 100").on_click(animate_to(&animation, &value, 100)), - ]) + Stack::columns( + Button::new("To 0") + .on_click(animate_to(&animation, &value, 0)) + .and(Label::new(label)) + .and(Button::new("To 100").on_click(animate_to(&animation, &value, 100))), + ) .run() } diff --git a/examples/counter.rs b/examples/counter.rs index 43e5f26..8ef3d2a 100644 --- a/examples/counter.rs +++ b/examples/counter.rs @@ -1,25 +1,28 @@ use std::string::ToString; use gooey::value::Dynamic; -use gooey::widgets::{Align, Button, Expand, Label, Resize, Stack}; -use gooey::{children, Run}; +use gooey::widget::MakeWidget; +use gooey::widgets::{Button, Label, Resize, Stack}; +use gooey::Run; use kludgine::figures::units::Lp; fn main() -> gooey::Result { let counter = Dynamic::new(0i32); let label = counter.map_each(ToString::to_string); - Expand::new(Align::centered(Stack::columns(children![ - Resize::width(Lp::points(100), Label::new(label)), - Button::new("+").on_click(counter.with_clone(|counter| { - move |_| { - counter.set(counter.get() + 1); - } - })), - Button::new("-").on_click(counter.with_clone(|counter| { - move |_| { - counter.set(counter.get() - 1); - } - })), - ]))) + Stack::columns( + Resize::width(Lp::points(100), Label::new(label)) + .and(Button::new("+").on_click(counter.with_clone(|counter| { + move |_| { + counter.set(counter.get() + 1); + } + }))) + .and(Button::new("-").on_click(counter.with_clone(|counter| { + move |_| { + counter.set(counter.get() - 1); + } + }))), + ) + .centered() + .expand() .run() } diff --git a/examples/gameui.rs b/examples/gameui.rs index 765b33f..3c0e8fc 100644 --- a/examples/gameui.rs +++ b/examples/gameui.rs @@ -1,7 +1,7 @@ use gooey::value::Dynamic; use gooey::widget::{MakeWidget, HANDLED, IGNORED}; -use gooey::widgets::{Canvas, Expand, Input, Label, Scroll, Stack}; -use gooey::{children, Run}; +use gooey::widgets::{Canvas, Input, Label, Stack}; +use gooey::Run; use kludgine::app::winit::event::ElementState; use kludgine::app::winit::keyboard::Key; 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_message = Dynamic::new(String::new()); - Expand::new(Stack::rows(children![ - Expand::new(Stack::columns(children![ - Expand::new(Scroll::vertical(Label::new(chat_log.clone()))), - Expand::weighted( - 2, + Stack::rows( + Stack::columns( + Label::new(chat_log.clone()).vertical_scroll().expand().and( Canvas::new(|context| { let entire_canvas = Rect::from(context.graphics.size()); context.graphics.draw_shape( @@ -26,10 +24,12 @@ fn main() -> gooey::Result { None, ); }) - ) - ])), - Input::new(chat_message.clone()) - .on_key(move |input| match (input.state, input.logical_key) { + .expand_weighted(2), + ), + ) + .expand() + .and(Input::new(chat_message.clone()).on_key(move |input| { + match (input.state, input.logical_key) { (ElementState::Pressed, Key::Enter) => { let new_message = chat_message.map_mut(|text| std::mem::take(text)); chat_log.map_mut(|chat_log| { @@ -39,8 +39,9 @@ fn main() -> gooey::Result { HANDLED } _ => IGNORED, - }) - .make_widget(), - ])) + } + })), + ) + .expand() .run() } diff --git a/examples/input.rs b/examples/input.rs index 94ae2a1..48c1cef 100644 --- a/examples/input.rs +++ b/examples/input.rs @@ -1,6 +1,7 @@ -use gooey::widgets::{Expand, Input}; +use gooey::widget::MakeWidget; +use gooey::widgets::Input; use gooey::Run; fn main() -> gooey::Result { - Expand::new(Input::new("Hello")).run() + Input::new("Hello").expand().run() } diff --git a/examples/login.rs b/examples/login.rs index afc6e98..51af383 100644 --- a/examples/login.rs +++ b/examples/login.rs @@ -2,8 +2,8 @@ use std::process::exit; use gooey::value::{Dynamic, MapEach}; use gooey::widget::MakeWidget; -use gooey::widgets::{Align, Button, Expand, Input, Label, Resize, Stack}; -use gooey::{children, Run}; +use gooey::widgets::{Button, Expand, Input, Label, Resize, Stack}; +use gooey::Run; use kludgine::figures::units::Lp; fn main() -> gooey::Result { @@ -13,42 +13,49 @@ fn main() -> gooey::Result { let valid = (&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 Lp::points(400), - Stack::rows(children![ - Stack::columns(children![ - Label::new("Username"), - Expand::new(Align::centered(Input::new(username.clone())).fit_horizontally()), - ]), - Stack::columns(children![ - Label::new("Password"), - Expand::new( - Align::centered( - // TODO secure input - Input::new(password.clone()) - ) - .fit_horizontally() + Stack::rows( + Stack::columns( + Label::new("Username").and( + Input::new(username.clone()) + .centered() + .fit_horizontally() + .expand(), ), - ]), - 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") .on_click(|_| { eprintln!("Login cancelled"); exit(0) }) - .into_escape(), - Expand::empty(), - Button::new("Log In") - .enabled(valid) - .on_click(move |_| { - println!("Welcome, {}", username.get()); - exit(0); - }) - .into_default(), - ]), - ]), - ))) + .into_escape() + .and(Expand::empty()) + .and( + Button::new("Log In") + .enabled(valid) + .on_click(move |_| { + println!("Welcome, {}", username.get()); + exit(0); + }) + .into_default(), + ), + )), + ), + ) + .centered() + .expand() .run() } diff --git a/examples/scroll.rs b/examples/scroll.rs index ee2bd5a..4875ca7 100644 --- a/examples/scroll.rs +++ b/examples/scroll.rs @@ -1,6 +1,9 @@ -use gooey::widgets::{Label, Scroll}; +use gooey::widget::MakeWidget; +use gooey::widgets::Label; use gooey::Run; 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() } diff --git a/examples/style.rs b/examples/style.rs index 69f203f..809a1e6 100644 --- a/examples/style.rs +++ b/examples/style.rs @@ -1,22 +1,15 @@ use gooey::styles::components::TextColor; use gooey::styles::Styles; -use gooey::widget::{Children, MakeWidget, Widget}; +use gooey::widget::{MakeWidget, Widget}; use gooey::widgets::stack::Stack; use gooey::widgets::{Button, Style}; -use gooey::window::Window; use gooey::{styles, Run}; use kludgine::Color; fn main() -> gooey::Result { - Window::for_widget( - Stack::rows( - Children::new() - .with_widget(Button::new("Default")) - .with_widget(red_text(Button::new("Styled"))), - ) - .with_styles(Styles::new().with(&TextColor, Color::GREEN)), - ) - .run() + Stack::rows(Button::new("Green").and(red_text(Button::new("Red")))) + .with_styles(Styles::new().with(&TextColor, Color::GREEN)) + .run() } /// Creating reusable style helpers that work with any Widget is straightfoward diff --git a/src/lib.rs b/src/lib.rs index 52ac9f7..664bf3a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -92,6 +92,7 @@ pub trait Run: Sized { /// Creates a [`Children`](crate::widget::Children) instance with the given list /// of widgets. #[macro_export] +#[deprecated = "use MakeWidget.and()/Children.and() to chain widgets without a macro"] macro_rules! children { () => { $crate::widget::Children::new() diff --git a/src/tree.rs b/src/tree.rs index fe2247b..6803fde 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -396,8 +396,8 @@ impl TreeData { let node = &self.nodes[&perspective]; if let Some(styles) = &node.styles { query.retain(|name| { - if let Some(component) = styles.get(name) { - resolved.insert(name, component.clone()); + if let Some(component) = styles.get(dbg!(name)) { + resolved.insert(name, dbg!(component.clone())); false } else { true diff --git a/src/value.rs b/src/value.rs index 3bb70b7..2366d3a 100644 --- a/src/value.rs +++ b/src/value.rs @@ -752,14 +752,36 @@ macro_rules! impl_tuple_for_each { // [$type:ident $field:tt $var:ident, $($rtype:ident $rfield:tt $rvar:ident),+] ) => { - impl_tuple_for_each!( invoke $self $for_each $type $field $var [$($ltype $lfield $lvar,)* $type $field $var, $($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 diff --git a/src/widget.rs b/src/widget.rs index cb1c95f..b91fbeb 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -19,7 +19,7 @@ use crate::styles::components::VisualOrder; use crate::styles::Styles; use crate::tree::Tree; use crate::value::{IntoValue, Value}; -use crate::widgets::Style; +use crate::widgets::{Align, Expand, Scroll, Style}; use crate::window::{RunningWindow, Window, WindowBehavior}; use crate::{ConstraintLimit, Run}; @@ -479,6 +479,51 @@ pub trait MakeWidget: Sized { fn into_escape(self) -> WidgetInstance { 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 @@ -945,7 +990,7 @@ impl Children { } /// Adds `widget` to self and returns the updated list. - pub fn with_widget(mut self, widget: W) -> Self + pub fn and(mut self, widget: W) -> Self where W: MakeWidget, { diff --git a/src/window.rs b/src/window.rs index cb91851..d36eba0 100644 --- a/src/window.rs +++ b/src/window.rs @@ -365,6 +365,10 @@ where if self.initial_frame { self.initial_frame = false; + self.root + .lock() + .as_widget() + .mounted(&mut layout_context.as_event_context()); layout_context.focus(); layout_context.as_event_context().apply_pending_state(); }