diff --git a/examples/button.rs b/examples/button.rs index a6ca0a267..587c46cf5 100644 --- a/examples/button.rs +++ b/examples/button.rs @@ -2,8 +2,8 @@ use gooey::value::Dynamic; use gooey::widgets::Button; use gooey::Run; +// begin rustme snippet: readme fn main() -> gooey::Result { - // begin rustme snippet: readme // Create a dynamic usize. let count = Dynamic::new(0_usize); @@ -14,5 +14,5 @@ fn main() -> gooey::Result { .on_click(count.with_clone(|count| move |_| count.set(count.get() + 1))) // Run the button as an an application. .run() - // end rustme snippet } +// end rustme snippet diff --git a/examples/containers.rs b/examples/containers.rs index 8fafd1db0..2f2c36392 100644 --- a/examples/containers.rs +++ b/examples/containers.rs @@ -7,6 +7,7 @@ use gooey::Run; fn main() -> gooey::Result { let theme_mode = Dynamic::default(); set_of_containers(1, theme_mode.clone()) + .centered() .into_window() .with_theme_mode(theme_mode) .run() diff --git a/examples/counter.rs b/examples/counter.rs index 6948de513..cabdb9338 100644 --- a/examples/counter.rs +++ b/examples/counter.rs @@ -2,27 +2,28 @@ use std::string::ToString; use gooey::value::Dynamic; use gooey::widget::MakeWidget; -use gooey::widgets::{Button, Label, Resize, Stack}; +use gooey::widgets::{Button, Label}; 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); - Stack::columns( - Resize::width(Lp::points(100), Label::new(label)) - .and(Button::new("+").on_click(counter.with_clone(|counter| { - move |_| { - *counter.lock() += 1; - } - }))) - .and(Button::new("-").on_click(counter.with_clone(|counter| { - move |_| { - *counter.lock() -= 1; - } - }))), - ) - .centered() - .expand() - .run() + + Label::new(label) + .width(Lp::points(100)) + .and(Button::new("+").on_click(counter.with_clone(|counter| { + move |_| { + *counter.lock() += 1; + } + }))) + .and(Button::new("-").on_click(counter.with_clone(|counter| { + move |_| { + *counter.lock() -= 1; + } + }))) + .into_columns() + .centered() + .expand() + .run() } diff --git a/examples/login.rs b/examples/login.rs index 89d128133..60a7be9d6 100644 --- a/examples/login.rs +++ b/examples/login.rs @@ -2,7 +2,7 @@ use std::process::exit; use gooey::value::{Dynamic, MapEach}; use gooey::widget::MakeWidget; -use gooey::widgets::{Button, Expand, Input, Label, Resize, Stack}; +use gooey::widgets::{Button, Expand, Input, Label}; use gooey::Run; use kludgine::figures::units::Lp; @@ -14,42 +14,46 @@ fn main() -> gooey::Result { (&username, &password).map_each(|(username, password)| validate(username, password)); // TODO this should be a grid layout to ensure proper visual alignment. - let username_row = Stack::columns( - Label::new("Username").and(Input::new(username.clone()).fit_horizontally().expand()), - ); - - let password_row = Stack::columns(Label::new("Password").and( - // TODO secure input - Input::new(password.clone()).fit_horizontally().expand(), - )); - - let buttons = Stack::columns( - Button::new("Cancel") - .on_click(|_| { - eprintln!("Login cancelled"); - exit(0) - }) - .into_escape() - .and(Expand::empty()) - .and( - Button::new("Log In") - .enabled(valid) - .on_click(move |_| { - println!("Welcome, {}", username.get()); - exit(0); - }) - .into_default(), - ), - ); - - Resize::width( - Lp::points(300)..Lp::points(600), - Stack::rows(username_row.and(password_row).and(buttons)), - ) - .scroll() - .centered() - .expand() - .run() + let username_row = Label::new("Username") + .and(Input::new(username.clone()).expand()) + .into_columns(); + + let password_row = Label::new("Password") + .and( + // TODO secure input + Input::new(password.clone()).expand(), + ) + .into_columns(); + + let buttons = Button::new("Cancel") + .on_click(|_| { + eprintln!("Login cancelled"); + exit(0) + }) + .into_escape() + .and(Expand::empty()) + .and( + Button::new("Log In") + .enabled(valid) + .on_click(move |_| { + println!("Welcome, {}", username.get()); + exit(0); + }) + .into_default(), + ) + .into_columns(); + + username_row + .pad() + .and(password_row.pad()) + .and(buttons.pad()) + .into_rows() + .contain() + .width(Lp::points(300)..Lp::points(600)) + .scroll() + .centered() + .expand() + .run() } fn validate(username: &String, password: &String) -> bool { diff --git a/src/widget.rs b/src/widget.rs index a72586f3a..506e338b7 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -18,12 +18,12 @@ use kludgine::Color; use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext, WidgetContext}; use crate::styles::{ - ContainerLevel, Dimension, Edges, IntoComponentValue, NamedComponent, Styles, ThemePair, - VisualOrder, + ContainerLevel, Dimension, DimensionRange, Edges, IntoComponentValue, NamedComponent, Styles, + ThemePair, VisualOrder, }; use crate::tree::Tree; use crate::value::{IntoValue, Value}; -use crate::widgets::{Align, Container, Expand, Scroll, Stack, Style}; +use crate::widgets::{Align, Container, Expand, Resize, Scroll, Stack, Style}; use crate::window::{RunningWindow, ThemeMode, Window, WindowBehavior}; use crate::{ConstraintLimit, Run}; @@ -622,6 +622,26 @@ pub trait MakeWidget: Sized { Expand::horizontal(self) } + /// Resizes `self` to `width`. + /// + /// `width` can be an individual + /// [`Dimension`]/[`Px`]/[`Lp`](crate::kludgine::figures::units::Lp) or a + /// range. + #[must_use] + fn width(self, width: impl Into) -> Resize { + Resize::from_width(width, self) + } + + /// Resizes `self` to `height`. + /// + /// `height` can be an individual + /// [`Dimension`]/[`Px`]/[`Lp`](crate::kludgine::figures::units::Lp) or a + /// range. + #[must_use] + fn height(self, height: impl Into) -> Resize { + Resize::from_height(height, self) + } + /// Aligns `self` to the center vertically and horizontally. #[must_use] fn centered(self) -> Align { diff --git a/src/widgets/input.rs b/src/widgets/input.rs index 367c7ed31..286c35e52 100644 --- a/src/widgets/input.rs +++ b/src/widgets/input.rs @@ -17,7 +17,9 @@ use kludgine::text::TextOrigin; use kludgine::{Color, Kludgine}; use crate::context::{EventContext, LayoutContext, WidgetContext}; -use crate::styles::components::{HighlightColor, LineHeight, OutlineColor, TextColor, TextSize}; +use crate::styles::components::{ + HighlightColor, IntrinsicPadding, LineHeight, OutlineColor, TextColor, TextSize, +}; use crate::utils::ModifiersExt; use crate::value::{Generation, IntoValue, Value}; use crate::widget::{Callback, EventHandling, Widget, HANDLED, IGNORED}; @@ -195,6 +197,8 @@ impl Widget for Input { self.cursor_state.update(context.elapsed()); let cursor_state = self.cursor_state; let size = context.gfx.size(); + let padding = context.get(&IntrinsicPadding).into_px(context.gfx.scale()); + let padding = Point::::new(padding, padding); let highlight = context.get(&HighlightColor); let editor = self.editor_mut(&mut context.gfx, &context.widget); let cursor = editor.cursor(); @@ -228,7 +232,7 @@ impl Widget for Input { Rect::new(start_position, Size::new(width, line_height)), highlight, ), - Point::default(), + padding, None, None, ); @@ -240,7 +244,7 @@ impl Widget for Input { Rect::new(start_position, Size::new(width, line_height)), highlight, ), - Point::default(), + padding, None, None, ); @@ -256,7 +260,7 @@ impl Widget for Input { ), highlight, ), - Point::default(), + padding, None, None, ); @@ -270,7 +274,7 @@ impl Widget for Input { ), highlight, ), - Point::default(), + padding, None, None, ); @@ -283,7 +287,7 @@ impl Widget for Input { Rect::new(start_position, Size::new(width, line_height)), highlight, ), - Point::default(), + padding, None, None, ); @@ -300,7 +304,7 @@ impl Widget for Input { ), highlight, ), - Point::default(), + padding, None, None, ); @@ -323,7 +327,7 @@ impl Widget for Input { ), highlight, // TODO cursor should be a bold color, highlight probably not. This should have its own color. ), - Point::default(), + padding, None, None, ); @@ -340,14 +344,9 @@ impl Widget for Input { } let text_color = context.get(&TextColor); - context.gfx.draw_text_buffer( - buffer, - text_color, - TextOrigin::TopLeft, - Point::::default(), - None, - None, - ); + context + .gfx + .draw_text_buffer(buffer, text_color, TextOrigin::TopLeft, padding, None, None); } fn layout( @@ -355,22 +354,34 @@ impl Widget for Input { available_space: Size, context: &mut LayoutContext<'_, '_, '_, '_, '_>, ) -> Size { + let padding = context + .get(&IntrinsicPadding) + .into_px(context.gfx.scale()) + .into_unsigned(); if self.needs_to_select_all { self.needs_to_select_all = false; self.select_all(); } let editor = self.editor_mut(&mut context.graphics.gfx, &context.graphics.widget); let buffer = editor.buffer_mut(); - buffer.set_size( - context.gfx.font_system(), - available_space.width.max().into_float(), - available_space.height.max().into_float(), - ); + let width = available_space + .width + .max() + .saturating_sub(padding * 2) + .into_float(); + let height = available_space + .height + .max() + .saturating_sub(padding * 2) + .into_float(); + + buffer.set_size(context.gfx.font_system(), width, height); context .gfx .measure_text_buffer::(buffer, Color::WHITE) .size .into_unsigned() + + Size::new(padding * 2, padding * 2) } fn keyboard_input( diff --git a/src/widgets/resize.rs b/src/widgets/resize.rs index af7b73a31..241655af5 100644 --- a/src/widgets/resize.rs +++ b/src/widgets/resize.rs @@ -38,7 +38,7 @@ impl Resize { /// Resizes `child`'s width to `width`. #[must_use] - pub fn width(width: impl Into, child: impl MakeWidget) -> Self { + pub fn from_width(width: impl Into, child: impl MakeWidget) -> Self { Self { child: WidgetRef::new(child), width: width.into(), @@ -46,9 +46,31 @@ impl Resize { } } + /// Resizes `self` to `width`. + /// + /// `width` can be an individual + /// [`Dimension`]/[`Px`]/[`Lp`](crate::kludgine::figures::units::Lp) or a + /// range. + #[must_use] + pub fn width(mut self, width: impl Into) -> Self { + self.width = width.into(); + self + } + + /// Resizes `self` to `height`. + /// + /// `width` can be an individual + /// [`Dimension`]/[`Px`]/[`Lp`](crate::kludgine::figures::units::Lp) or a + /// range. + #[must_use] + pub fn height(mut self, height: impl Into) -> Self { + self.height = height.into(); + self + } + /// Resizes `child`'s height to `height`. #[must_use] - pub fn height(height: impl Into, child: impl MakeWidget) -> Self { + pub fn from_height(height: impl Into, child: impl MakeWidget) -> Self { Self { child: WidgetRef::new(child), width: DimensionRange::from(..), diff --git a/src/widgets/scroll.rs b/src/widgets/scroll.rs index 9728d303a..cd733ff83 100644 --- a/src/widgets/scroll.rs +++ b/src/widgets/scroll.rs @@ -176,12 +176,12 @@ impl Widget for Scroll { .into_signed(); let max_extents = Size::new( if self.enabled.x { - ConstraintLimit::ClippedAfter(UPx::MAX - scroll.x.into_unsigned()) + ConstraintLimit::ClippedAfter((control_size.width).into_unsigned()) } else { available_space.width }, if self.enabled.y { - ConstraintLimit::ClippedAfter(UPx::MAX - scroll.y.into_unsigned()) + ConstraintLimit::ClippedAfter((control_size.height).into_unsigned()) } else { available_space.height }, @@ -242,16 +242,12 @@ impl Widget for Scroll { Size::new( if self.enabled.x { - available_space - .width - .fit_measured(self.content_size.width, context.gfx.scale()) + constrain_child(available_space.width, self.content_size.width) } else { self.content_size.width.into_unsigned() }, if self.enabled.y { - available_space - .height - .fit_measured(self.content_size.height, context.gfx.scale()) + constrain_child(available_space.height, self.content_size.height) } else { self.content_size.height.into_unsigned() }, @@ -287,6 +283,14 @@ impl Widget for Scroll { } } +fn constrain_child(constraint: ConstraintLimit, measured: Px) -> UPx { + let measured = measured.into_unsigned(); + match constraint { + ConstraintLimit::Known(size) => size.min(measured), + ConstraintLimit::ClippedAfter(_) => measured, + } +} + #[derive(Debug, Default)] struct ScrollbarInfo { offset: Px,