Scroll fixes, resize helpers

This commit is contained in:
Jonathan Johnson 2023-11-13 11:30:45 -08:00
parent ee3813f44d
commit 40343e163f
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
8 changed files with 150 additions and 87 deletions

View file

@ -2,8 +2,8 @@ use gooey::value::Dynamic;
use gooey::widgets::Button; use gooey::widgets::Button;
use gooey::Run; use gooey::Run;
// begin rustme snippet: readme
fn main() -> gooey::Result { fn main() -> gooey::Result {
// begin rustme snippet: readme
// Create a dynamic usize. // Create a dynamic usize.
let count = Dynamic::new(0_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))) .on_click(count.with_clone(|count| move |_| count.set(count.get() + 1)))
// Run the button as an an application. // Run the button as an an application.
.run() .run()
// end rustme snippet
} }
// end rustme snippet

View file

@ -7,6 +7,7 @@ use gooey::Run;
fn main() -> gooey::Result { fn main() -> gooey::Result {
let theme_mode = Dynamic::default(); let theme_mode = Dynamic::default();
set_of_containers(1, theme_mode.clone()) set_of_containers(1, theme_mode.clone())
.centered()
.into_window() .into_window()
.with_theme_mode(theme_mode) .with_theme_mode(theme_mode)
.run() .run()

View file

@ -2,27 +2,28 @@ use std::string::ToString;
use gooey::value::Dynamic; use gooey::value::Dynamic;
use gooey::widget::MakeWidget; use gooey::widget::MakeWidget;
use gooey::widgets::{Button, Label, Resize, Stack}; use gooey::widgets::{Button, Label};
use gooey::Run; 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);
Stack::columns(
Resize::width(Lp::points(100), Label::new(label)) Label::new(label)
.and(Button::new("+").on_click(counter.with_clone(|counter| { .width(Lp::points(100))
move |_| { .and(Button::new("+").on_click(counter.with_clone(|counter| {
*counter.lock() += 1; move |_| {
} *counter.lock() += 1;
}))) }
.and(Button::new("-").on_click(counter.with_clone(|counter| { })))
move |_| { .and(Button::new("-").on_click(counter.with_clone(|counter| {
*counter.lock() -= 1; move |_| {
} *counter.lock() -= 1;
}))), }
) })))
.centered() .into_columns()
.expand() .centered()
.run() .expand()
.run()
} }

View file

@ -2,7 +2,7 @@ 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::{Button, Expand, Input, Label, Resize, Stack}; use gooey::widgets::{Button, Expand, Input, Label};
use gooey::Run; use gooey::Run;
use kludgine::figures::units::Lp; use kludgine::figures::units::Lp;
@ -14,42 +14,46 @@ fn main() -> gooey::Result {
(&username, &password).map_each(|(username, password)| validate(username, password)); (&username, &password).map_each(|(username, password)| validate(username, password));
// TODO this should be a grid layout to ensure proper visual alignment. // TODO this should be a grid layout to ensure proper visual alignment.
let username_row = Stack::columns( let username_row = Label::new("Username")
Label::new("Username").and(Input::new(username.clone()).fit_horizontally().expand()), .and(Input::new(username.clone()).expand())
); .into_columns();
let password_row = Stack::columns(Label::new("Password").and( let password_row = Label::new("Password")
// TODO secure input .and(
Input::new(password.clone()).fit_horizontally().expand(), // TODO secure input
)); Input::new(password.clone()).expand(),
)
.into_columns();
let buttons = Stack::columns( let buttons = Button::new("Cancel")
Button::new("Cancel") .on_click(|_| {
.on_click(|_| { eprintln!("Login cancelled");
eprintln!("Login cancelled"); exit(0)
exit(0) })
}) .into_escape()
.into_escape() .and(Expand::empty())
.and(Expand::empty()) .and(
.and( Button::new("Log In")
Button::new("Log In") .enabled(valid)
.enabled(valid) .on_click(move |_| {
.on_click(move |_| { println!("Welcome, {}", username.get());
println!("Welcome, {}", username.get()); exit(0);
exit(0); })
}) .into_default(),
.into_default(), )
), .into_columns();
);
Resize::width( username_row
Lp::points(300)..Lp::points(600), .pad()
Stack::rows(username_row.and(password_row).and(buttons)), .and(password_row.pad())
) .and(buttons.pad())
.scroll() .into_rows()
.centered() .contain()
.expand() .width(Lp::points(300)..Lp::points(600))
.run() .scroll()
.centered()
.expand()
.run()
} }
fn validate(username: &String, password: &String) -> bool { fn validate(username: &String, password: &String) -> bool {

View file

@ -18,12 +18,12 @@ use kludgine::Color;
use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext, WidgetContext}; use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext, WidgetContext};
use crate::styles::{ use crate::styles::{
ContainerLevel, Dimension, Edges, IntoComponentValue, NamedComponent, Styles, ThemePair, ContainerLevel, Dimension, DimensionRange, Edges, IntoComponentValue, NamedComponent, Styles,
VisualOrder, ThemePair, VisualOrder,
}; };
use crate::tree::Tree; use crate::tree::Tree;
use crate::value::{IntoValue, Value}; 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::window::{RunningWindow, ThemeMode, Window, WindowBehavior};
use crate::{ConstraintLimit, Run}; use crate::{ConstraintLimit, Run};
@ -622,6 +622,26 @@ pub trait MakeWidget: Sized {
Expand::horizontal(self) 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<DimensionRange>) -> 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<DimensionRange>) -> Resize {
Resize::from_height(height, 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 {

View file

@ -17,7 +17,9 @@ use kludgine::text::TextOrigin;
use kludgine::{Color, Kludgine}; use kludgine::{Color, Kludgine};
use crate::context::{EventContext, LayoutContext, WidgetContext}; 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::utils::ModifiersExt;
use crate::value::{Generation, IntoValue, Value}; use crate::value::{Generation, IntoValue, Value};
use crate::widget::{Callback, EventHandling, Widget, HANDLED, IGNORED}; use crate::widget::{Callback, EventHandling, Widget, HANDLED, IGNORED};
@ -195,6 +197,8 @@ impl Widget for Input {
self.cursor_state.update(context.elapsed()); self.cursor_state.update(context.elapsed());
let cursor_state = self.cursor_state; let cursor_state = self.cursor_state;
let size = context.gfx.size(); let size = context.gfx.size();
let padding = context.get(&IntrinsicPadding).into_px(context.gfx.scale());
let padding = Point::<Px>::new(padding, padding);
let highlight = context.get(&HighlightColor); let highlight = context.get(&HighlightColor);
let editor = self.editor_mut(&mut context.gfx, &context.widget); let editor = self.editor_mut(&mut context.gfx, &context.widget);
let cursor = editor.cursor(); let cursor = editor.cursor();
@ -228,7 +232,7 @@ impl Widget for Input {
Rect::new(start_position, Size::new(width, line_height)), Rect::new(start_position, Size::new(width, line_height)),
highlight, highlight,
), ),
Point::default(), padding,
None, None,
None, None,
); );
@ -240,7 +244,7 @@ impl Widget for Input {
Rect::new(start_position, Size::new(width, line_height)), Rect::new(start_position, Size::new(width, line_height)),
highlight, highlight,
), ),
Point::default(), padding,
None, None,
None, None,
); );
@ -256,7 +260,7 @@ impl Widget for Input {
), ),
highlight, highlight,
), ),
Point::default(), padding,
None, None,
None, None,
); );
@ -270,7 +274,7 @@ impl Widget for Input {
), ),
highlight, highlight,
), ),
Point::default(), padding,
None, None,
None, None,
); );
@ -283,7 +287,7 @@ impl Widget for Input {
Rect::new(start_position, Size::new(width, line_height)), Rect::new(start_position, Size::new(width, line_height)),
highlight, highlight,
), ),
Point::default(), padding,
None, None,
None, None,
); );
@ -300,7 +304,7 @@ impl Widget for Input {
), ),
highlight, highlight,
), ),
Point::default(), padding,
None, None,
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. highlight, // TODO cursor should be a bold color, highlight probably not. This should have its own color.
), ),
Point::default(), padding,
None, None,
None, None,
); );
@ -340,14 +344,9 @@ impl Widget for Input {
} }
let text_color = context.get(&TextColor); let text_color = context.get(&TextColor);
context.gfx.draw_text_buffer( context
buffer, .gfx
text_color, .draw_text_buffer(buffer, text_color, TextOrigin::TopLeft, padding, None, None);
TextOrigin::TopLeft,
Point::<Px>::default(),
None,
None,
);
} }
fn layout( fn layout(
@ -355,22 +354,34 @@ impl Widget for Input {
available_space: Size<ConstraintLimit>, available_space: Size<ConstraintLimit>,
context: &mut LayoutContext<'_, '_, '_, '_, '_>, context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> Size<UPx> { ) -> Size<UPx> {
let padding = context
.get(&IntrinsicPadding)
.into_px(context.gfx.scale())
.into_unsigned();
if self.needs_to_select_all { if self.needs_to_select_all {
self.needs_to_select_all = false; self.needs_to_select_all = false;
self.select_all(); self.select_all();
} }
let editor = self.editor_mut(&mut context.graphics.gfx, &context.graphics.widget); let editor = self.editor_mut(&mut context.graphics.gfx, &context.graphics.widget);
let buffer = editor.buffer_mut(); let buffer = editor.buffer_mut();
buffer.set_size( let width = available_space
context.gfx.font_system(), .width
available_space.width.max().into_float(), .max()
available_space.height.max().into_float(), .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 context
.gfx .gfx
.measure_text_buffer::<Px>(buffer, Color::WHITE) .measure_text_buffer::<Px>(buffer, Color::WHITE)
.size .size
.into_unsigned() .into_unsigned()
+ Size::new(padding * 2, padding * 2)
} }
fn keyboard_input( fn keyboard_input(

View file

@ -38,7 +38,7 @@ impl Resize {
/// Resizes `child`'s width to `width`. /// Resizes `child`'s width to `width`.
#[must_use] #[must_use]
pub fn width(width: impl Into<DimensionRange>, child: impl MakeWidget) -> Self { pub fn from_width(width: impl Into<DimensionRange>, child: impl MakeWidget) -> Self {
Self { Self {
child: WidgetRef::new(child), child: WidgetRef::new(child),
width: width.into(), 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<DimensionRange>) -> 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<DimensionRange>) -> Self {
self.height = height.into();
self
}
/// Resizes `child`'s height to `height`. /// Resizes `child`'s height to `height`.
#[must_use] #[must_use]
pub fn height(height: impl Into<DimensionRange>, child: impl MakeWidget) -> Self { pub fn from_height(height: impl Into<DimensionRange>, child: impl MakeWidget) -> Self {
Self { Self {
child: WidgetRef::new(child), child: WidgetRef::new(child),
width: DimensionRange::from(..), width: DimensionRange::from(..),

View file

@ -176,12 +176,12 @@ impl Widget for Scroll {
.into_signed(); .into_signed();
let max_extents = Size::new( let max_extents = Size::new(
if self.enabled.x { if self.enabled.x {
ConstraintLimit::ClippedAfter(UPx::MAX - scroll.x.into_unsigned()) ConstraintLimit::ClippedAfter((control_size.width).into_unsigned())
} else { } else {
available_space.width available_space.width
}, },
if self.enabled.y { if self.enabled.y {
ConstraintLimit::ClippedAfter(UPx::MAX - scroll.y.into_unsigned()) ConstraintLimit::ClippedAfter((control_size.height).into_unsigned())
} else { } else {
available_space.height available_space.height
}, },
@ -242,16 +242,12 @@ impl Widget for Scroll {
Size::new( Size::new(
if self.enabled.x { if self.enabled.x {
available_space constrain_child(available_space.width, self.content_size.width)
.width
.fit_measured(self.content_size.width, context.gfx.scale())
} else { } else {
self.content_size.width.into_unsigned() self.content_size.width.into_unsigned()
}, },
if self.enabled.y { if self.enabled.y {
available_space constrain_child(available_space.height, self.content_size.height)
.height
.fit_measured(self.content_size.height, context.gfx.scale())
} else { } else {
self.content_size.height.into_unsigned() 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)] #[derive(Debug, Default)]
struct ScrollbarInfo { struct ScrollbarInfo {
offset: Px, offset: Px,