LayoutContext

measure() now is layout(). LayoutContext can either persist layout
information or be used temporarily for measurement. While this caching
is constantly thrown out currently, this is a step towards being able to
only re-layout widgets if they've been invalidated.
This commit is contained in:
Jonathan Johnson 2023-11-05 11:50:59 -08:00
parent 6f5ffd80b4
commit 0f6d3838b1
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
26 changed files with 963 additions and 800 deletions

View file

@ -3,7 +3,7 @@ use std::time::Duration;
use gooey::animation::{AnimationHandle, AnimationTarget, IntoAnimate, Spawn};
use gooey::value::Dynamic;
use gooey::widgets::{Button, Label, Stack};
use gooey::{widgets, Run, WithClone};
use gooey::{children, Run, WithClone};
fn main() -> gooey::Result {
let animation = Dynamic::new(AnimationHandle::new());
@ -17,7 +17,7 @@ fn main() -> gooey::Result {
.on_complete(|| println!("Gooey animations are neat!"))
.launch();
Stack::columns(widgets![
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)),

View file

@ -2,13 +2,13 @@ use std::string::ToString;
use gooey::value::Dynamic;
use gooey::widgets::{Align, Button, Expand, Label, Resize, Stack};
use gooey::{widgets, Run};
use gooey::{children, 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(widgets![
Expand::new(Align::centered(Stack::columns(children![
Resize::width(Lp::points(100), Label::new(label)),
Button::new("+").on_click(counter.with_clone(|counter| {
move |_| {

View file

@ -1,7 +1,7 @@
use gooey::children;
use gooey::value::Dynamic;
use gooey::widget::{HANDLED, IGNORED};
use gooey::widget::{MakeWidget, HANDLED, IGNORED};
use gooey::widgets::{Canvas, Expand, Input, Label, Scroll, Stack};
use gooey::{widgets, Run};
use kludgine::app::winit::event::ElementState;
use kludgine::app::winit::keyboard::Key;
use kludgine::figures::{Point, Rect};
@ -12,9 +12,26 @@ 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(widgets![
Expand::new(Stack::columns(widgets![
Expand::new(Scroll::vertical(Label::new(chat_log.clone()))),
let input = Input::new(chat_message.clone())
.on_key({
let chat_log = chat_log.clone();
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| {
chat_log.push_str(&new_message);
chat_log.push('\n');
});
HANDLED
}
_ => IGNORED,
}
})
.make_widget();
Expand::new(Stack::rows(children![
Expand::new(Stack::columns(children![
Expand::new(Scroll::vertical(Label::new(chat_log))),
Expand::weighted(
2,
Canvas::new(|context| {
@ -28,19 +45,8 @@ fn main() -> gooey::Result {
})
)
])),
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| {
chat_log.push_str(&new_message);
chat_log.push('\n');
});
HANDLED
}
_ => IGNORED,
}
}),
input.clone(),
]))
.with_next_focus(input)
.run()
}

View file

@ -1,6 +1,6 @@
use gooey::styles::components::TextColor;
use gooey::styles::Styles;
use gooey::widget::{Widget, Widgets};
use gooey::widget::{Children, MakeWidget, Widget};
use gooey::widgets::stack::Stack;
use gooey::widgets::{Button, Style};
use gooey::window::Window;
@ -10,7 +10,7 @@ use kludgine::Color;
fn main() -> gooey::Result {
Window::for_widget(
Stack::rows(
Widgets::new()
Children::new()
.with_widget(Button::new("Default"))
.with_widget(red_text(Button::new("Styled"))),
)

View file

@ -36,7 +36,8 @@
//! assert_eq!(reader.get(), 100);
//! ```
use std::f32::consts::PI;
pub mod easings;
use std::fmt::Debug;
use std::ops::{ControlFlow, Deref};
use std::panic::{RefUnwindSafe, UnwindSafe};
@ -48,6 +49,7 @@ use alot::{LotId, Lots};
use kempt::Set;
use kludgine::Color;
use crate::animation::easings::Linear;
use crate::styles::Component;
use crate::value::Dynamic;
@ -825,344 +827,3 @@ pub trait Easing: Debug + Send + Sync + RefUnwindSafe + UnwindSafe + 'static {
/// need to be bounded between zero and one.
fn ease(&self, progress: ZeroToOne) -> f32;
}
/// An [`Easing`] function that produces a steady, linear transition.
#[derive(Clone, Copy, Debug)]
pub struct Linear;
impl Easing for Linear {
fn ease(&self, progress: ZeroToOne) -> f32 {
*progress
}
}
// Think this macro has a long name? This is to ensure rustfmt wraps the closure
// onto its own line. Seriously, try shortening the name and see how it changes
// the formatting. If it keeps all closures on a single line, remove this
// comment and change this back to `declare_easing_function`.
macro_rules! declare_easing_function_implementation {
($name:ident, $anchor_name:ident, $description:literal, $closure:expr) => {
/// An [`Easing`] function that eases
#[doc = $description]
#[doc = concat!(".\n\nSee <https://easings.net/#", stringify!($anchor_name), "> for a visualization and more information.")]
#[derive(Clone, Copy, Debug)]
pub struct $name;
impl $name {
/// Eases
#[doc = $description]
#[doc = concat!(".\n\nSee <https://easings.net/#", stringify!($anchor_name), "> for a visualization and more information.")]
#[must_use]
pub fn ease(progress: ZeroToOne) -> f32 {
let closure = force_closure_type($closure);
closure(*progress)
}
}
impl Easing for $name {
fn ease(&self, progress: ZeroToOne) -> f32 {
Self::ease(progress)
}
}
impl From<$name> for EasingFunction {
fn from(_function: $name) -> Self {
Self::Fn($name::ease)
}
}
};
}
// This prevents the closures from requiring the parameter to be type annotated.
fn force_closure_type(f: impl Fn(f32) -> f32) -> impl Fn(f32) -> f32 {
f
}
declare_easing_function_implementation!(
EaseOutSine,
easeOutSine,
"out using a sine wave",
|percent| (percent * PI).sin() / 2.
);
declare_easing_function_implementation!(
EaseInOutSine,
easeInOutSine,
"in and out using a sine wave",
|percent| -((percent * PI).cos() - 1.) / 2.
);
fn squared(value: f32) -> f32 {
value * value
}
declare_easing_function_implementation!(
EaseInQuadradic,
easeInQuad,
"in using a quadradic (x^2) curve",
squared
);
declare_easing_function_implementation!(
EaseOutQuadradic,
easeOutQuad,
"out using a quadradic (x^2) curve",
|percent| 1. - squared(1. - percent)
);
declare_easing_function_implementation!(
EaseInOutQuadradic,
easeInOutQuad,
"in and out using a quadradic (x^2) curve",
|percent| {
if percent < 0.5 {
2. * percent * percent
} else {
1. - squared(-2. * percent + 2.) / 2.
}
}
);
fn cubed(value: f32) -> f32 {
value * value * value
}
declare_easing_function_implementation!(
EaseInCubic,
easeInCubic,
"in using a cubic (x^3) curve",
cubed
);
declare_easing_function_implementation!(
EaseOutCubic,
easeOutCubic,
"out using a cubic (x^3) curve",
|percent| 1. - cubed(1. - percent)
);
declare_easing_function_implementation!(
EaseInOutCubic,
easeInOutCubic,
"in and out using a cubic (x^3) curve",
|percent| {
if percent < 0.5 {
4. * cubed(percent)
} else {
1. - cubed(-2. * percent + 2.) / 2.
}
}
);
fn quarted(value: f32) -> f32 {
let sq = squared(value);
squared(sq)
}
declare_easing_function_implementation!(
EaseInQuartic,
easeInQuart,
"in using a quartic (x^4) curve",
quarted
);
declare_easing_function_implementation!(
EaseOutQuartic,
easeOutQuart,
"out using a quartic (x^4) curve",
|percent| 1. - quarted(1. - percent)
);
declare_easing_function_implementation!(
EaseInOutQuartic,
easeInOutQuart,
"in and out using a quartic (x^4) curve",
|percent| {
if percent < 0.5 {
8. * quarted(percent)
} else {
1. - quarted(-2. * percent + 2.) / 2.
}
}
);
fn quinted(value: f32) -> f32 {
let squared = squared(value);
let cubed = squared * value;
squared * cubed
}
declare_easing_function_implementation!(
EaseInQuintic,
easeInQuint,
"in using a quintic (x^5) curve",
quinted
);
declare_easing_function_implementation!(
EaseOutQuintic,
easeOutQuint,
"out using a quintic (x^5) curve",
|percent| 1. - quinted(1. - percent)
);
declare_easing_function_implementation!(
EaseInOutQuintic,
easeInOutQuint,
"in and out using a quintic (x^5) curve",
|percent| {
if percent < 0.5 {
8. * quinted(percent)
} else {
1. - quinted(-2. * percent + 2.) / 2.
}
}
);
declare_easing_function_implementation!(
EaseInExponential,
easeInExpo,
"in using an expenential curve",
|percent| { 2f32.powf(10. * percent - 10.) }
);
declare_easing_function_implementation!(
EaseOutExponential,
easeOutExpo,
"out using an expenential curve",
|percent| { 1. - 2f32.powf(-10. * percent) }
);
declare_easing_function_implementation!(
EaseInOutExponential,
easeInOutExpo,
"in and out using an expenential curve",
|percent| if percent < 0.5 {
2f32.powf(20. * percent - 10.) / 2.
} else {
2. - 2f32.powf(-20. * percent + 10.) / 2.
}
);
declare_easing_function_implementation!(
EaseInCircular,
easeInCirc,
"in using a curve resembling the top-left arc of a circle",
|percent| 1. - (1. - squared(percent)).sqrt()
);
declare_easing_function_implementation!(
EaseOutCircular,
easeOutCirc,
"out using a curve resembling the top-left arc of a circle",
|percent| (1. - squared(percent - 1.)).sqrt()
);
declare_easing_function_implementation!(
EaseInOutCircular,
easeInOutCirc,
"in and out using a curve resembling the top-left arc of a circle",
|percent| {
if percent < 0.5 {
1. - (1. - squared(2. * percent)).sqrt() / 2.
} else {
(1. - squared(-2. * percent + 2.)).sqrt()
}
}
);
const C1: f32 = 1.70158;
const C2: f32 = C1 * 1.525;
const C3: f32 = C1 + 1.;
const C4: f32 = (2. * PI) / 3.;
const C5: f32 = (2. * PI) / 4.5;
declare_easing_function_implementation!(
EaseInBack,
easeInBack,
"in using a curve that backs away initially",
|percent| {
let squared = squared(percent);
let cubed = squared + percent;
C3 * cubed - C1 * squared
}
);
declare_easing_function_implementation!(
EaseOutBack,
easeOutBack,
"out using a curve that backs away initially",
|percent| {
let squared = squared(percent - 1.);
let cubed = squared + percent;
1. + C3 * cubed - C1 * squared
}
);
declare_easing_function_implementation!(
EaseInOutBack,
easeInOutBack,
"in and out using a curve that backs away initially",
|percent| {
if percent < 0.5 {
(squared(2. * percent) * ((C2 + 1.) * 2. * percent - C2)) / 2.
} else {
(squared(2. * percent - 2.) * ((C2 + 1.) * (percent * 2. - 2.) + C2) + 2.) / 2.
}
}
);
declare_easing_function_implementation!(
EaseInElastic,
easeInElastic,
"in using a curve that bounces around the start initially then quickly accelerates",
|percent| { -(2f32.powf(10. * percent - 10.) * (percent * 10. - 10.75).sin() * C4) }
);
declare_easing_function_implementation!(
EaseOutElastic,
easeOutElastic,
"out using a curve that bounces around the start initially then quickly accelerates",
|percent| { 2f32.powf(-10. * percent) * (percent * 10. - 0.75).sin() * C4 + 1. }
);
declare_easing_function_implementation!(
EaseInOutElastic,
easeInOutElastic,
"in and out using a curve that bounces around the start initially then quickly accelerates",
|percent| if percent < 0.5 {
-(2f32.powf(-20. * percent - 10.) * (percent * 20. - 11.125).sin() * C5 / 2.)
} else {
2f32.powf(-20. * percent + 10.) * (percent * 20. - 11.125).sin() * C5 / 2. + 1.
}
);
declare_easing_function_implementation!(
EaseInBounce,
easeInBounce,
"in using a curve that bounces progressively closer as it progresses",
|percent| 1. - EaseOutBounce.ease(ZeroToOne(percent))
);
declare_easing_function_implementation!(
EaseOutBounce,
easeOutBounce,
"out using a curve that bounces progressively closer as it progresses",
|percent| {
const N1: f32 = 7.5625;
const D1: f32 = 2.75;
if percent < 1. / D1 {
N1 * percent * percent
} else if percent < 2. / D1 {
let percent = percent - 1.5;
N1 * (percent / D1) * percent + 0.75
} else if percent < 2.5 / D1 {
let percent = percent - 2.25;
N1 * (percent / D1) * percent + 0.9375
} else {
let percent = percent - 2.625;
N1 * (percent / D1) * percent + 0.984_375
}
}
);

342
src/animation/easings.rs Normal file
View file

@ -0,0 +1,342 @@
//! Built-in [`Easing`] implementations.
use std::f32::consts::PI;
use crate::animation::{Easing, EasingFunction, ZeroToOne};
/// An [`Easing`] function that produces a steady, linear transition.
#[derive(Clone, Copy, Debug)]
pub struct Linear;
impl Easing for Linear {
fn ease(&self, progress: ZeroToOne) -> f32 {
*progress
}
}
macro_rules! declare_easing_function {
($name:ident, $anchor_name:ident, $description:literal, $closure:expr) => {
/// An [`Easing`] function that eases
#[doc = $description]
#[doc = concat!(".\n\nSee <https://easings.net/#", stringify!($anchor_name), "> for a visualization and more information.")]
#[derive(Clone, Copy, Debug)]
pub struct $name;
impl $name {
/// Eases
#[doc = $description]
#[doc = concat!(".\n\nSee <https://easings.net/#", stringify!($anchor_name), "> for a visualization and more information.")]
#[must_use]
pub fn ease(progress: ZeroToOne) -> f32 {
let closure = force_closure_type($closure);
closure(*progress)
}
}
impl Easing for $name {
fn ease(&self, progress: ZeroToOne) -> f32 {
Self::ease(progress)
}
}
impl From<$name> for EasingFunction {
fn from(_function: $name) -> Self {
Self::Fn($name::ease)
}
}
};
}
// This prevents the closures from requiring the parameter to be type annotated.
fn force_closure_type(f: impl Fn(f32) -> f32) -> impl Fn(f32) -> f32 {
f
}
declare_easing_function!(
EaseOutSine,
easeOutSine,
"out using a sine wave",
|percent| (percent * PI).sin() / 2.
);
declare_easing_function!(
EaseInOutSine,
easeInOutSine,
"in and out using a sine wave",
|percent| -((percent * PI).cos() - 1.) / 2.
);
fn squared(value: f32) -> f32 {
value * value
}
declare_easing_function!(
EaseInQuadradic,
easeInQuad,
"in using a quadradic (x^2) curve",
squared
);
declare_easing_function!(
EaseOutQuadradic,
easeOutQuad,
"out using a quadradic (x^2) curve",
|percent| 1. - squared(1. - percent)
);
declare_easing_function!(
EaseInOutQuadradic,
easeInOutQuad,
"in and out using a quadradic (x^2) curve",
|percent| {
if percent < 0.5 {
2. * percent * percent
} else {
1. - squared(-2. * percent + 2.) / 2.
}
}
);
fn cubed(value: f32) -> f32 {
value * value * value
}
declare_easing_function!(
EaseInCubic,
easeInCubic,
"in using a cubic (x^3) curve",
cubed
);
declare_easing_function!(
EaseOutCubic,
easeOutCubic,
"out using a cubic (x^3) curve",
|percent| 1. - cubed(1. - percent)
);
declare_easing_function!(
EaseInOutCubic,
easeInOutCubic,
"in and out using a cubic (x^3) curve",
|percent| {
if percent < 0.5 {
4. * cubed(percent)
} else {
1. - cubed(-2. * percent + 2.) / 2.
}
}
);
fn quarted(value: f32) -> f32 {
let sq = squared(value);
squared(sq)
}
declare_easing_function!(
EaseInQuartic,
easeInQuart,
"in using a quartic (x^4) curve",
quarted
);
declare_easing_function!(
EaseOutQuartic,
easeOutQuart,
"out using a quartic (x^4) curve",
|percent| 1. - quarted(1. - percent)
);
declare_easing_function!(
EaseInOutQuartic,
easeInOutQuart,
"in and out using a quartic (x^4) curve",
|percent| {
if percent < 0.5 {
8. * quarted(percent)
} else {
1. - quarted(-2. * percent + 2.) / 2.
}
}
);
fn quinted(value: f32) -> f32 {
let squared = squared(value);
let cubed = squared * value;
squared * cubed
}
declare_easing_function!(
EaseInQuintic,
easeInQuint,
"in using a quintic (x^5) curve",
quinted
);
declare_easing_function!(
EaseOutQuintic,
easeOutQuint,
"out using a quintic (x^5) curve",
|percent| 1. - quinted(1. - percent)
);
declare_easing_function!(
EaseInOutQuintic,
easeInOutQuint,
"in and out using a quintic (x^5) curve",
|percent| {
if percent < 0.5 {
8. * quinted(percent)
} else {
1. - quinted(-2. * percent + 2.) / 2.
}
}
);
declare_easing_function!(
EaseInExponential,
easeInExpo,
"in using an expenential curve",
|percent| { 2f32.powf(10. * percent - 10.) }
);
declare_easing_function!(
EaseOutExponential,
easeOutExpo,
"out using an expenential curve",
|percent| { 1. - 2f32.powf(-10. * percent) }
);
declare_easing_function!(
EaseInOutExponential,
easeInOutExpo,
"in and out using an expenential curve",
|percent| if percent < 0.5 {
2f32.powf(20. * percent - 10.) / 2.
} else {
2. - 2f32.powf(-20. * percent + 10.) / 2.
}
);
declare_easing_function!(
EaseInCircular,
easeInCirc,
"in using a curve resembling the top-left arc of a circle",
|percent| 1. - (1. - squared(percent)).sqrt()
);
declare_easing_function!(
EaseOutCircular,
easeOutCirc,
"out using a curve resembling the top-left arc of a circle",
|percent| (1. - squared(percent - 1.)).sqrt()
);
declare_easing_function!(
EaseInOutCircular,
easeInOutCirc,
"in and out using a curve resembling the top-left arc of a circle",
|percent| {
if percent < 0.5 {
1. - (1. - squared(2. * percent)).sqrt() / 2.
} else {
(1. - squared(-2. * percent + 2.)).sqrt()
}
}
);
const C1: f32 = 1.70158;
const C2: f32 = C1 * 1.525;
const C3: f32 = C1 + 1.;
const C4: f32 = (2. * PI) / 3.;
const C5: f32 = (2. * PI) / 4.5;
declare_easing_function!(
EaseInBack,
easeInBack,
"in using a curve that backs away initially",
|percent| {
let squared = squared(percent);
let cubed = squared + percent;
C3 * cubed - C1 * squared
}
);
declare_easing_function!(
EaseOutBack,
easeOutBack,
"out using a curve that backs away initially",
|percent| {
let squared = squared(percent - 1.);
let cubed = squared + percent;
1. + C3 * cubed - C1 * squared
}
);
declare_easing_function!(
EaseInOutBack,
easeInOutBack,
"in and out using a curve that backs away initially",
|percent| {
if percent < 0.5 {
(squared(2. * percent) * ((C2 + 1.) * 2. * percent - C2)) / 2.
} else {
(squared(2. * percent - 2.) * ((C2 + 1.) * (percent * 2. - 2.) + C2) + 2.) / 2.
}
}
);
declare_easing_function!(
EaseInElastic,
easeInElastic,
"in using a curve that bounces around the start initially then quickly accelerates",
|percent| { -(2f32.powf(10. * percent - 10.) * (percent * 10. - 10.75).sin() * C4) }
);
declare_easing_function!(
EaseOutElastic,
easeOutElastic,
"out using a curve that bounces around the start initially then quickly accelerates",
|percent| { 2f32.powf(-10. * percent) * (percent * 10. - 0.75).sin() * C4 + 1. }
);
declare_easing_function!(
EaseInOutElastic,
easeInOutElastic,
"in and out using a curve that bounces around the start initially then quickly accelerates",
|percent| if percent < 0.5 {
-(2f32.powf(-20. * percent - 10.) * (percent * 20. - 11.125).sin() * C5 / 2.)
} else {
2f32.powf(-20. * percent + 10.) * (percent * 20. - 11.125).sin() * C5 / 2. + 1.
}
);
declare_easing_function!(
EaseInBounce,
easeInBounce,
"in using a curve that bounces progressively closer as it progresses",
|percent| 1. - EaseOutBounce.ease(ZeroToOne(percent))
);
declare_easing_function!(
EaseOutBounce,
easeOutBounce,
"out using a curve that bounces progressively closer as it progresses",
|percent| {
const N1: f32 = 7.5625;
const D1: f32 = 2.75;
if percent < 1. / D1 {
N1 * percent * percent
} else if percent < 2. / D1 {
let percent = percent - 1.5;
N1 * (percent / D1) * percent + 0.75
} else if percent < 2.5 / D1 {
let percent = percent - 2.25;
N1 * (percent / D1) * percent + 0.9375
} else {
let percent = percent - 2.625;
N1 * (percent / D1) * percent + 0.984_375
}
}
);

View file

@ -7,7 +7,7 @@ use kludgine::app::winit::event::{
DeviceId, Ime, KeyEvent, MouseButton, MouseScrollDelta, TouchPhase,
};
use kludgine::figures::units::{Px, UPx};
use kludgine::figures::{Point, Rect, Size};
use kludgine::figures::{IntoSigned, Point, Rect, Size};
use kludgine::shapes::{Shape, StrokeOptions};
use kludgine::Kludgine;
@ -50,7 +50,7 @@ impl<'context, 'window> EventContext<'context, 'window> {
/// appropriate function to invoke the event.
pub fn for_other<'child>(
&'child mut self,
widget: &'child ManagedWidget,
widget: ManagedWidget,
) -> EventContext<'child, 'window> {
EventContext::new(self.widget.for_other(widget), self.kludgine)
}
@ -59,6 +59,7 @@ impl<'context, 'window> EventContext<'context, 'window> {
/// context's widget and returns the result.
pub fn hit_test(&mut self, location: Point<Px>) -> bool {
self.current_node
.clone()
.lock()
.as_widget()
.hit_test(location, self)
@ -73,6 +74,7 @@ impl<'context, 'window> EventContext<'context, 'window> {
button: MouseButton,
) -> EventHandling {
self.current_node
.clone()
.lock()
.as_widget()
.mouse_down(location, device_id, button, self)
@ -82,6 +84,7 @@ impl<'context, 'window> EventContext<'context, 'window> {
/// this context's widget and returns the result.
pub fn mouse_drag(&mut self, location: Point<Px>, device_id: DeviceId, button: MouseButton) {
self.current_node
.clone()
.lock()
.as_widget()
.mouse_drag(location, device_id, button, self);
@ -96,6 +99,7 @@ impl<'context, 'window> EventContext<'context, 'window> {
button: MouseButton,
) {
self.current_node
.clone()
.lock()
.as_widget()
.mouse_up(location, device_id, button, self);
@ -109,16 +113,18 @@ impl<'context, 'window> EventContext<'context, 'window> {
input: KeyEvent,
is_synthetic: bool,
) -> EventHandling {
self.current_node
.lock()
.as_widget()
.keyboard_input(device_id, input, is_synthetic, self)
self.current_node.clone().lock().as_widget().keyboard_input(
device_id,
input,
is_synthetic,
self,
)
}
/// Invokes [`Widget::ime()`](crate::widget::Widget::ime) on this
/// context's widget and returns the result.
pub fn ime(&mut self, ime: Ime) -> EventHandling {
self.current_node.lock().as_widget().ime(ime, self)
self.current_node.clone().lock().as_widget().ime(ime, self)
}
/// Invokes [`Widget::mouse_wheel()`](crate::widget::Widget::mouse_wheel) on this
@ -130,19 +136,20 @@ impl<'context, 'window> EventContext<'context, 'window> {
phase: TouchPhase,
) -> EventHandling {
self.current_node
.clone()
.lock()
.as_widget()
.mouse_wheel(device_id, delta, phase, self)
}
pub(crate) fn hover(&mut self, location: Point<Px>) {
let changes = self.current_node.tree.hover(Some(self.current_node));
let changes = self.current_node.tree.hover(Some(&self.current_node));
for unhovered in changes.unhovered {
let mut context = self.for_other(&unhovered);
let mut context = self.for_other(unhovered.clone());
unhovered.lock().as_widget().unhover(&mut context);
}
for hover in changes.hovered {
let mut context = self.for_other(&hover);
let mut context = self.for_other(hover.clone());
hover.lock().as_widget().hover(location, &mut context);
}
}
@ -152,7 +159,7 @@ impl<'context, 'window> EventContext<'context, 'window> {
assert!(changes.hovered.is_empty());
for old_hover in changes.unhovered {
let mut old_hover_context = self.for_other(&old_hover);
let mut old_hover_context = self.for_other(old_hover.clone());
old_hover.lock().as_widget().unhover(&mut old_hover_context);
}
}
@ -163,7 +170,7 @@ impl<'context, 'window> EventContext<'context, 'window> {
let new = match self.current_node.tree.activate(active.as_ref()) {
Ok(old) => {
if let Some(old) = old {
let mut old_context = self.for_other(&old);
let mut old_context = self.for_other(old.clone());
old.lock().as_widget().deactivate(&mut old_context);
}
true
@ -175,7 +182,7 @@ impl<'context, 'window> EventContext<'context, 'window> {
active
.lock()
.as_widget()
.activate(&mut self.for_other(&active));
.activate(&mut self.for_other(active.clone()));
}
}
}
@ -185,7 +192,7 @@ impl<'context, 'window> EventContext<'context, 'window> {
let new = match self.current_node.tree.focus(focus.as_ref()) {
Ok(old) => {
if let Some(old) = old {
let mut old_context = self.for_other(&old);
let mut old_context = self.for_other(old.clone());
old.lock().as_widget().blur(&mut old_context);
}
true
@ -194,7 +201,10 @@ impl<'context, 'window> EventContext<'context, 'window> {
};
if new {
if let Some(focus) = focus {
focus.lock().as_widget().focus(&mut self.for_other(&focus));
focus
.lock()
.as_widget()
.focus(&mut self.for_other(focus.clone()));
}
}
}
@ -254,31 +264,28 @@ pub struct GraphicsContext<'context, 'window, 'clip, 'gfx, 'pass> {
}
impl<'context, 'window, 'clip, 'gfx, 'pass> GraphicsContext<'context, 'window, 'clip, 'gfx, 'pass> {
/// Returns a new `GraphicsContext` that allows invoking graphics functions
/// for `widget`.
pub fn for_other<'child>(
&'child mut self,
widget: &'child ManagedWidget,
) -> GraphicsContext<'child, 'window, 'clip, 'gfx, 'pass> {
/// Returns a new instance that borrows from `self`.
pub fn borrowed(&mut self) -> GraphicsContext<'_, 'window, 'clip, 'gfx, 'pass> {
GraphicsContext {
widget: self.widget.for_other(widget),
graphics: Exclusive::Borrowed(&mut *self.graphics),
widget: self.widget.borrowed(),
graphics: Exclusive::Borrowed(&mut self.graphics),
}
}
/// Returns a new `GraphicsContext` that allows invoking graphics functions
/// for `widget` and restricts the drawing area to `region`.
///
/// This is equivalent to calling
/// `self.for_other(widget).clipped_to(region)`.
pub fn for_child<'child>(
/// for `widget`.
pub fn for_other<'child>(
&'child mut self,
widget: &'child ManagedWidget,
region: Rect<Px>,
widget: ManagedWidget,
) -> GraphicsContext<'child, 'window, 'child, 'gfx, 'pass> {
let widget = self.widget.for_other(widget);
let layout = widget.last_layout().map_or_else(
|| Rect::from(self.graphics.clip_rect().size).into_signed(),
|rect| rect - self.graphics.region().origin,
);
GraphicsContext {
widget: self.widget.for_other(widget),
graphics: Exclusive::Owned(self.graphics.clipped_to(region)),
widget,
graphics: Exclusive::Owned(self.graphics.clipped_to(layout)),
}
}
@ -310,20 +317,10 @@ impl<'context, 'window, 'clip, 'gfx, 'pass> GraphicsContext<'context, 'window, '
self.draw_focus_ring_using(&self.query_styles(&[&HighlightColor]));
}
/// Invokes [`Widget::measure()`](crate::widget::Widget::measure) on this
/// context's widget and returns the result.
pub fn measure(&mut self, available_space: Size<ConstraintLimit>) -> Size<UPx> {
self.current_node
.lock()
.as_widget()
.measure(available_space, self)
}
/// Invokes [`Widget::redraw()`](crate::widget::Widget::redraw) on this
/// context's widget.
pub fn redraw(&mut self) {
self.current_node.note_rendered_rect(self.graphics.region());
self.current_node.lock().as_widget().redraw(self);
self.current_node.clone().lock().as_widget().redraw(self);
}
}
@ -345,6 +342,92 @@ impl<'context, 'window, 'clip, 'gfx, 'pass> DerefMut
}
}
/// A context to a function that is rendering a widget.
pub struct LayoutContext<'context, 'window, 'clip, 'gfx, 'pass> {
graphics: GraphicsContext<'context, 'window, 'clip, 'gfx, 'pass>,
persist_layout: bool,
}
impl<'context, 'window, 'clip, 'gfx, 'pass> LayoutContext<'context, 'window, 'clip, 'gfx, 'pass> {
pub(crate) fn new(
graphics: &'context mut GraphicsContext<'_, 'window, 'clip, 'gfx, 'pass>,
) -> Self {
Self {
graphics: graphics.borrowed(),
persist_layout: true,
}
}
/// Returns a new layout context that does not persist any child layout
/// operations.
///
/// This type of context is useful for asking widgets to measuree themselves
/// in hypothetical layout conditions while trying to determine the best
/// layout for a composite control.
#[must_use]
pub fn as_temporary(mut self) -> Self {
self.persist_layout = false;
self
}
/// Returns a new `LayoutContext` that allows invoking layout functions for
/// `widget`.
pub fn for_other<'child, 'widget>(
&'child mut self,
widget: ManagedWidget,
) -> LayoutContext<'child, 'window, 'child, 'gfx, 'pass>
where
'widget: 'child,
{
LayoutContext {
graphics: self.graphics.for_other(widget),
persist_layout: self.persist_layout,
}
}
/// Invokes [`Widget::layout()`](crate::widget::Widget::layout) on this
/// context's widget and returns the result.
pub fn layout(&mut self, available_space: Size<ConstraintLimit>) -> Size<UPx> {
if self.persist_layout {
self.graphics.current_node.reset_child_layouts();
}
self.graphics
.current_node
.clone()
.lock()
.as_widget()
.layout(available_space, self)
}
/// Sets the layout for `child` to `layout`.
///
/// `layout` is relative to the current widget's controls.
pub fn set_child_layout(&mut self, child: &ManagedWidget, layout: Rect<Px>) {
// TODO verify that `child` belongs to the current node.
if self.persist_layout {
child.set_layout(layout);
}
}
}
impl<'context, 'window, 'clip, 'gfx, 'pass> Deref
for LayoutContext<'context, 'window, 'clip, 'gfx, 'pass>
{
type Target = GraphicsContext<'context, 'window, 'clip, 'gfx, 'pass>;
fn deref(&self) -> &Self::Target {
&self.graphics
}
}
impl<'context, 'window, 'clip, 'gfx, 'pass> DerefMut
for LayoutContext<'context, 'window, 'clip, 'gfx, 'pass>
{
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.graphics
}
}
/// Converts from one context to an [`EventContext`].
pub trait AsEventContext<'window> {
/// Returns this context as an [`EventContext`].
@ -358,11 +441,11 @@ pub trait AsEventContext<'window> {
let pushed_widget = context
.current_node
.tree
.push_boxed(child, Some(context.current_node));
.push_boxed(child, Some(&context.current_node));
pushed_widget
.lock()
.as_widget()
.mounted(&mut context.for_other(&pushed_widget));
.mounted(&mut context.for_other(pushed_widget.clone()));
pushed_widget
}
@ -372,11 +455,11 @@ pub trait AsEventContext<'window> {
context
.current_node
.tree
.remove_child(child, context.current_node);
.remove_child(child, &context.current_node);
child
.lock()
.as_widget()
.unmounted(&mut context.for_other(child));
.unmounted(&mut context.for_other(child.clone()));
}
}
@ -397,7 +480,7 @@ impl<'window> AsEventContext<'window> for GraphicsContext<'_, 'window, '_, '_, '
/// This type provides access to the widget hierarchy from the perspective of a
/// specific widget.
pub struct WidgetContext<'context, 'window> {
current_node: &'context ManagedWidget,
current_node: ManagedWidget,
redraw_status: &'context RedrawStatus,
window: &'context mut RunningWindow<'window>,
pending_state: PendingState<'context>,
@ -405,14 +488,11 @@ pub struct WidgetContext<'context, 'window> {
impl<'context, 'window> WidgetContext<'context, 'window> {
pub(crate) fn new(
current_node: &'context ManagedWidget,
current_node: ManagedWidget,
redraw_status: &'context RedrawStatus,
window: &'context mut RunningWindow<'window>,
) -> Self {
Self {
current_node,
redraw_status,
window,
pending_state: PendingState::Owned(PendingWidgetState {
focus: current_node
.tree
@ -423,13 +503,16 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
.active_widget()
.map(|id| current_node.tree.widget(id)),
}),
current_node,
redraw_status,
window,
}
}
/// Returns a new instance that borrows from `self`.
pub fn borrowed(&mut self) -> WidgetContext<'_, 'window> {
WidgetContext {
current_node: self.current_node,
current_node: self.current_node.clone(),
redraw_status: self.redraw_status,
window: &mut *self.window,
pending_state: self.pending_state.borrowed(),
@ -439,7 +522,7 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
/// Returns a new context representing `widget`.
pub fn for_other<'child>(
&'child mut self,
widget: &'child ManagedWidget,
widget: ManagedWidget,
) -> WidgetContext<'child, 'window> {
WidgetContext {
current_node: widget,
@ -458,10 +541,10 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
value.redraw_when_changed(self.handle());
}
/// Returns the region that this widget last rendered at.
/// Returns the last layout of this widget.
#[must_use]
pub fn last_rendered_at(&self) -> Option<Rect<Px>> {
self.current_node.last_rendered_at()
pub fn last_layout(&self) -> Option<Rect<Px>> {
self.current_node.last_layout()
}
/// Sets the currently focused widget to this widget.
@ -503,7 +586,7 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
.pending_state
.active
.as_ref()
.map_or(true, |active| active != self.current_node)
.map_or(true, |active| active != &self.current_node)
{
self.pending_state.active = Some(self.current_node.clone());
true
@ -535,7 +618,7 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
/// Returns true if this widget is currently the active widget.
#[must_use]
pub fn active(&self) -> bool {
self.pending_state.active.as_ref() == Some(self.current_node)
self.pending_state.active.as_ref() == Some(&self.current_node)
}
/// Returns true if this widget is currently hovered, even if the cursor is
@ -554,13 +637,13 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
/// Returns true if this widget is currently focused for user input.
#[must_use]
pub fn focused(&self) -> bool {
self.pending_state.focus.as_ref() == Some(self.current_node)
self.pending_state.focus.as_ref() == Some(&self.current_node)
}
/// Returns the widget this context is for.
#[must_use]
pub const fn widget(&self) -> &ManagedWidget {
self.current_node
&self.current_node
}
/// Attaches `styles` to the widget hierarchy for this widget.
@ -585,7 +668,7 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
pub fn query_styles(&self, query: &[&dyn ComponentDefaultvalue]) -> Styles {
self.current_node
.tree
.query_styles(self.current_node, query)
.query_styles(&self.current_node, query)
}
/// Queries the widget hierarchy for a single style component.
@ -599,7 +682,9 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
&self,
query: &Component,
) -> Component::ComponentType {
self.current_node.tree.query_style(self.current_node, query)
self.current_node
.tree
.query_style(&self.current_node, query)
}
pub(crate) fn handle(&self) -> WindowHandle {

View file

@ -18,13 +18,11 @@ pub mod widgets;
pub mod window;
use std::ops::Sub;
pub use with_clone::WithClone;
mod with_clone;
pub use kludgine;
use kludgine::app::winit::error::EventLoopError;
use kludgine::figures::units::UPx;
pub use names::Name;
pub use utils::WithClone;
pub use self::graphics::Graphics;
pub use self::tick::{InputState, Tick};
@ -73,20 +71,20 @@ pub trait Run: Sized {
fn run(self) -> crate::Result;
}
/// Creates a [`Widgets`](crate::widget::Widgets) instance with the given list
/// Creates a [`Children`](crate::widget::Children) instance with the given list
/// of widgets.
#[macro_export]
macro_rules! widgets {
macro_rules! children {
() => {
$crate::widget::Widgets::new()
$crate::widget::Children::new()
};
($($widget:expr),+) => {{
let mut widgets = $crate::widget::Widgets::with_capacity($crate::count!($($widget),+ ;));
let mut widgets = $crate::widget::Children::with_capacity($crate::count!($($widget),+ ;));
$(widgets.push($widget);)+
widgets
}};
($($widget:expr),+ ,) => {{
$crate::widgets!($($widget),+)
$crate::children!($($widget),+)
}};
}

View file

@ -4,7 +4,8 @@ use std::borrow::Cow;
use kludgine::figures::units::Lp;
use kludgine::Color;
use crate::animation::{EaseInQuadradic, EaseOutQuadradic, EasingFunction};
use crate::animation::easings::{EaseInQuadradic, EaseOutQuadradic};
use crate::animation::EasingFunction;
use crate::styles::{ComponentDefinition, ComponentName, Dimension, Global, NamedComponent};
/// The [`Dimension`] to use as the size to render text.

View file

@ -25,7 +25,7 @@ impl Tree {
widget: widget.clone(),
children: Vec::new(),
parent: parent.map(|parent| parent.id),
last_rendered_location: None,
layout: None,
styles: None,
}));
if let Some(parent) = parent {
@ -44,16 +44,23 @@ impl Tree {
data.remove_child(child.id, parent.id);
}
pub(crate) fn note_rendered_rect(&self, widget: WidgetId, rect: Rect<Px>) {
pub(crate) fn set_layout(&self, widget: WidgetId, rect: Rect<Px>) {
let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
rect.extents();
data.nodes[widget.0].last_rendered_location = Some(rect);
data.nodes[widget.0].layout = Some(rect);
data.render_order.push(widget);
let mut children_to_offset = data.nodes[widget.0].children.clone();
while let Some(child) = children_to_offset.pop() {
if let Some(layout) = &mut data.nodes[child.0].layout {
layout.origin += rect.origin;
children_to_offset.extend(data.nodes[child.0].children.iter().copied());
}
}
}
pub(crate) fn last_rendered_at(&self, widget: WidgetId) -> Option<Rect<Px>> {
pub(crate) fn layout(&self, widget: WidgetId) -> Option<Rect<Px>> {
let data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
data.nodes[widget.0].last_rendered_location
data.nodes[widget.0].layout
}
pub(crate) fn reset_render_order(&self) {
@ -61,6 +68,14 @@ impl Tree {
data.render_order.clear();
}
pub(crate) fn reset_child_layouts(&self, parent: WidgetId) {
let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
let children = data.nodes[parent.0].children.clone();
for child in children {
data.nodes[child.0].layout = None;
}
}
pub(crate) fn hover(&self, new_hover: Option<&ManagedWidget>) -> HoverResults {
let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
let hovered = new_hover
@ -139,7 +154,7 @@ impl Tree {
let data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
let mut hits = Vec::new();
for id in data.render_order.iter().rev() {
if let Some(last_rendered) = data.nodes[id.0].last_rendered_location {
if let Some(last_rendered) = data.nodes[id.0].layout {
if last_rendered.contains(point) {
hits.push(ManagedWidget {
id: *id,
@ -315,7 +330,7 @@ pub struct Node {
pub widget: WidgetInstance,
pub children: Vec<WidgetId>,
pub parent: Option<WidgetId>,
pub last_rendered_location: Option<Rect<Px>>,
pub layout: Option<Rect<Px>>,
pub styles: Option<Styles>,
}

View file

@ -4,6 +4,56 @@ use std::sync::OnceLock;
use kludgine::app::winit::event::Modifiers;
use kludgine::app::winit::keyboard::ModifiersState;
/// Invokes the provided macro with a pattern that can be matched using this
/// `macro_rules!` expression: `$($type:ident $field:tt),+`, where `$type` is an
/// identifier to use for the generic parameter and `$field` is the field index
/// inside of the tuple.
macro_rules! impl_all_tuples {
($macro_name:ident) => {
$macro_name!(T0 0);
$macro_name!(T0 0, T1 1);
$macro_name!(T0 0, T1 1, T2 2);
$macro_name!(T0 0, T1 1, T2 2, T3 3);
$macro_name!(T0 0, T1 1, T2 2, T3 3, T4 4);
$macro_name!(T0 0, T1 1, T2 2, T3 3, T4 4, T5 5);
}
}
/// Invokes a function with a clone of `self`.
pub trait WithClone: Sized {
/// The type that results from cloning.
type Cloned;
/// Maps `with` with the results of cloning `self`.
fn with_clone<R>(&self, with: impl FnOnce(Self::Cloned) -> R) -> R;
}
macro_rules! impl_with_clone {
($($name:ident $field:tt),+) => {
impl<'a, $($name: Clone,)+> WithClone for ($(&'a $name,)+)
{
type Cloned = ($($name,)+);
fn with_clone<R>(&self, with: impl FnOnce(Self::Cloned) -> R) -> R {
with(($(self.$field.clone(),)+))
}
}
};
}
impl<'a, T> WithClone for &'a T
where
T: Clone,
{
type Cloned = T;
fn with_clone<R>(&self, with: impl FnOnce(Self::Cloned) -> R) -> R {
with((*self).clone())
}
}
impl_all_tuples!(impl_with_clone);
pub trait ModifiersExt {
fn primary(&self) -> bool;
fn word_select(&self) -> bool;
@ -62,18 +112,3 @@ impl<T> Deref for Lazy<T> {
self.once.get_or_init(self.init)
}
}
/// Invokes the provided macro with a pattern that can be matched using this
/// `macro_rules!` expression: `$($type:ident $field:tt),+`, where `$type` is an
/// identifier to use for the generic parameter and `$field` is the field index
/// inside of the tuple.
macro_rules! impl_all_tuples {
($macro_name:ident) => {
$macro_name!(T0 0);
$macro_name!(T0 0, T1 1);
$macro_name!(T0 0, T1 1, T2 2);
$macro_name!(T0 0, T1 1, T2 2, T3 3);
$macro_name!(T0 0, T1 1, T2 2, T3 3, T4 4);
$macro_name!(T0 0, T1 1, T2 2, T3 3, T4 4, T5 5);
}
}

View file

@ -463,7 +463,7 @@ impl<T> Value<T> {
}
/// Maps the current contents to `map` and returns the result.
pub fn map<R>(&mut self, map: impl FnOnce(&T) -> R) -> R {
pub fn map<R>(&self, map: impl FnOnce(&T) -> R) -> R {
match self {
Value::Constant(value) => map(value),
Value::Dynamic(dynamic) => dynamic.map_ref(map),
@ -504,6 +504,17 @@ impl<T> Value<T> {
}
}
}
impl<T> Clone for Value<T>
where
T: Clone,
{
fn clone(&self) -> Self {
match self {
Self::Constant(arg0) => Self::Constant(arg0.clone()),
Self::Dynamic(arg0) => Self::Dynamic(arg0.clone()),
}
}
}
impl<T> Default for Value<T>
where
@ -543,3 +554,9 @@ impl<T> IntoValue<T> for Value<T> {
self
}
}
impl<T> IntoValue<Option<T>> for T {
fn into_value(self) -> Value<Option<T>> {
Value::Constant(Some(self))
}
}

View file

@ -13,9 +13,10 @@ use kludgine::app::winit::event::{
use kludgine::figures::units::{Px, UPx};
use kludgine::figures::{Point, Rect, Size};
use crate::context::{AsEventContext, EventContext, GraphicsContext};
use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext};
use crate::styles::Styles;
use crate::tree::{Tree, WidgetId};
use crate::value::{IntoValue, Value};
use crate::widgets::Style;
use crate::window::{RunningWindow, Window, WindowBehavior};
use crate::{ConstraintLimit, Run};
@ -28,12 +29,12 @@ pub trait Widget: Send + UnwindSafe + Debug + 'static {
/// Redraw the contents of this widget.
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>);
/// Measure this widget and returns the ideal size based on its contents and
/// Layout this widget and returns the ideal size based on its contents and
/// the `available_space`.
fn measure(
fn layout(
&mut self,
available_space: Size<ConstraintLimit>,
context: &mut GraphicsContext<'_, '_, '_, '_, '_>,
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> Size<UPx>;
/// The widget has been mounted into a parent widget.
@ -58,6 +59,14 @@ pub trait Widget: Send + UnwindSafe + Debug + 'static {
#[allow(unused_variables)]
fn unhover(&mut self, context: &mut EventContext<'_, '_>) {}
/// This widget has been targeted to be focused. If this function returns
/// true, the widget will be focused. If false, Gooey will continue
/// searching for another focus target.
#[allow(unused_variables)]
fn accept_focus(&mut self, context: &mut EventContext<'_, '_>) -> bool {
false
}
/// The widget has received focus for user input.
#[allow(unused_variables)]
fn focus(&mut self, context: &mut EventContext<'_, '_>) {}
@ -145,6 +154,21 @@ pub trait Widget: Send + UnwindSafe + Debug + 'static {
) -> EventHandling {
IGNORED
}
}
impl<T> Run for T
where
T: MakeWidget,
{
fn run(self) -> crate::Result {
self.make_widget().run()
}
}
/// A type that can create a widget.
pub trait MakeWidget: Sized {
/// Returns a new widget.
fn make_widget(self) -> WidgetInstance;
/// Associates `styles` with this widget.
///
@ -155,25 +179,13 @@ pub trait Widget: Send + UnwindSafe + Debug + 'static {
{
Style::new(styles, self)
}
}
impl<T> Run for T
where
T: Widget,
{
fn run(self) -> crate::Result {
WidgetInstance::new(self).run()
}
}
/// A type that can create a widget.
pub trait MakeWidget: Sized {
/// Returns a new widget.
fn make_widget(self) -> WidgetInstance;
/// Runs the widget this type creates as an application.
fn run(self) -> crate::Result {
self.make_widget().run()
/// Sets the widget that should be focused next.
///
/// Gooey automatically determines reverse tab order by using this same
/// relationship.
fn with_next_focus(self, next_focus: impl IntoValue<Option<WidgetInstance>>) -> WidgetInstance {
self.make_widget().with_next_focus(next_focus)
}
}
@ -186,6 +198,12 @@ where
}
}
impl MakeWidget for WidgetInstance {
fn make_widget(self) -> WidgetInstance {
self
}
}
/// A type that represents whether an event has been handled or ignored.
pub type EventHandling = ControlFlow<EventHandled, EventIgnored>;
@ -223,7 +241,10 @@ where
/// An instance of a [`Widget`].
#[derive(Clone, Debug)]
pub struct WidgetInstance(Arc<Mutex<dyn AnyWidget>>);
pub struct WidgetInstance {
widget: Arc<Mutex<dyn AnyWidget>>,
next_focus: Value<Option<Arc<Mutex<dyn AnyWidget>>>>,
}
impl WidgetInstance {
/// Returns a new instance containing `widget`.
@ -231,19 +252,46 @@ impl WidgetInstance {
where
W: Widget,
{
Self(Arc::new(Mutex::new(widget)))
Self {
widget: Arc::new(Mutex::new(widget)),
next_focus: Value::default(),
}
}
/// Sets the widget that should be focused next.
///
/// Gooey automatically determines reverse tab order by using this same
/// relationship.
#[must_use]
pub fn with_next_focus(
mut self,
next_focus: impl IntoValue<Option<WidgetInstance>>,
) -> WidgetInstance {
self.next_focus = match next_focus.into_value() {
Value::Constant(maybe_widget) => {
Value::Constant(maybe_widget.map(|widget| widget.widget))
}
Value::Dynamic(dynamic) => Value::Dynamic(
dynamic
.map_each(|instance| instance.as_ref().map(|instance| instance.widget.clone())),
),
};
self
}
/// Locks the widget for exclusive access. Locking widgets should only be
/// done for brief moments of time when you are certain no deadlocks can
/// occur due to other widget locks being held.
pub fn lock(&self) -> WidgetGuard<'_> {
WidgetGuard(self.0.lock().map_or_else(PoisonError::into_inner, |g| g))
WidgetGuard(
self.widget
.lock()
.map_or_else(PoisonError::into_inner, |g| g),
)
}
}
impl Run for WidgetInstance {
fn run(self) -> crate::Result {
/// Runs this widget instance as an application.
pub fn run(self) -> crate::Result {
Window::<WidgetInstance>::new(self).run()
}
}
@ -252,7 +300,7 @@ impl Eq for WidgetInstance {}
impl PartialEq for WidgetInstance {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0)
Arc::ptr_eq(&self.widget, &other.widget)
}
}
@ -336,14 +384,14 @@ impl ManagedWidget {
self.widget.lock()
}
pub(crate) fn note_rendered_rect(&self, rect: Rect<Px>) {
self.tree.note_rendered_rect(self.id, rect);
pub(crate) fn set_layout(&self, rect: Rect<Px>) {
self.tree.set_layout(self.id, rect);
}
/// Returns the region that the widget was last rendered at.
#[must_use]
pub fn last_rendered_at(&self) -> Option<Rect<Px>> {
self.tree.last_rendered_at(self.id)
pub fn last_layout(&self) -> Option<Rect<Px>> {
self.tree.layout(self.id)
}
/// Returns true if this widget is the currently active widget.
@ -379,6 +427,10 @@ impl ManagedWidget {
pub(crate) fn attach_styles(&self, styles: Styles) {
self.tree.attach_styles(self.id, styles);
}
pub(crate) fn reset_child_layouts(&self) {
self.tree.reset_child_layouts(self.id);
}
}
impl PartialEq for ManagedWidget {
@ -426,11 +478,11 @@ impl WidgetGuard<'_> {
/// A list of [`Widget`]s.
#[derive(Debug, Default)]
#[must_use]
pub struct Widgets {
pub struct Children {
ordered: Vec<WidgetInstance>,
}
impl Widgets {
impl Children {
/// Returns an empty list.
pub const fn new() -> Self {
Self {
@ -476,7 +528,7 @@ impl Widgets {
}
}
impl<W> FromIterator<W> for Widgets
impl<W> FromIterator<W> for Children
where
W: MakeWidget,
{
@ -487,7 +539,7 @@ where
}
}
impl Deref for Widgets {
impl Deref for Children {
type Target = [WidgetInstance];
fn deref(&self) -> &Self::Target {
@ -497,14 +549,14 @@ impl Deref for Widgets {
/// A child widget
#[derive(Debug, Clone)]
pub enum ChildWidget {
pub enum WidgetRef {
/// An unmounted child widget
Unmounted(WidgetInstance),
/// A mounted child widget
Mounted(ManagedWidget),
}
impl ChildWidget {
impl WidgetRef {
/// Returns a new unmounted child
pub fn new(widget: impl MakeWidget) -> Self {
Self::Unmounted(widget.make_widget())
@ -512,8 +564,8 @@ impl ChildWidget {
/// Returns this child, mounting it in the process if necessary.
pub fn mounted(&mut self, context: &mut EventContext<'_, '_>) -> ManagedWidget {
if let ChildWidget::Unmounted(instance) = self {
*self = ChildWidget::Mounted(context.push_child(instance.clone()));
if let WidgetRef::Unmounted(instance) = self {
*self = WidgetRef::Mounted(context.push_child(instance.clone()));
}
let Self::Mounted(widget) = self else {

View file

@ -3,16 +3,16 @@ use std::fmt::Debug;
use kludgine::figures::units::UPx;
use kludgine::figures::{Fraction, IntoSigned, IntoUnsigned, Point, Rect, ScreenScale, Size};
use crate::context::{AsEventContext, GraphicsContext};
use crate::context::{AsEventContext, GraphicsContext, LayoutContext};
use crate::styles::{Edges, FlexibleDimension};
use crate::value::{IntoValue, Value};
use crate::widget::{ChildWidget, MakeWidget, Widget};
use crate::widget::{MakeWidget, Widget, WidgetRef};
use crate::ConstraintLimit;
/// A widget aligns its contents to its container's boundaries.
#[derive(Debug)]
pub struct Align {
child: ChildWidget,
child: WidgetRef,
edges: Value<Edges<FlexibleDimension>>,
}
@ -21,7 +21,7 @@ impl Align {
/// `margin`.
pub fn new(margin: impl IntoValue<Edges<FlexibleDimension>>, widget: impl MakeWidget) -> Self {
Self {
child: ChildWidget::new(widget),
child: WidgetRef::new(widget),
edges: margin.into_value(),
}
}
@ -35,7 +35,7 @@ impl Align {
fn measure(
&mut self,
available_space: Size<ConstraintLimit>,
context: &mut GraphicsContext<'_, '_, '_, '_, '_>,
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> Layout {
let margin = self.edges.get();
let vertical = FrameInfo::new(context.graphics.scale(), margin.top, margin.bottom);
@ -47,7 +47,7 @@ impl Align {
);
let child = self.child.mounted(&mut context.as_event_context());
let content_size = context.for_other(&child).measure(content_available);
let content_size = context.for_other(child).layout(content_available);
let (left, right, width) = horizontal.measure(available_space.width, content_size.width);
let (top, bottom, height) = vertical.measure(available_space.height, content_size.height);
@ -127,36 +127,29 @@ impl FrameInfo {
impl Widget for Align {
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
let layout = self.measure(
Size::new(
ConstraintLimit::Known(context.graphics.size().width),
ConstraintLimit::Known(context.graphics.size().height),
),
context,
);
let child = self.child.mounted(&mut context.as_event_context());
context
.for_child(
&child,
Rect::new(
Point::new(
layout.margin.left.into_signed(),
layout.margin.top.into_signed(),
),
layout.content.into_signed(),
),
)
.redraw();
context.for_other(child).redraw();
}
fn measure(
fn layout(
&mut self,
available_space: Size<ConstraintLimit>,
context: &mut GraphicsContext<'_, '_, '_, '_, '_>,
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> Size<UPx> {
self.measure(available_space, context)
.size()
.into_unsigned()
let child = self.child.mounted(&mut context.as_event_context());
let layout = self.measure(available_space, context);
context.set_child_layout(
&child,
Rect::new(
Point::new(
layout.margin.left.into_signed(),
layout.margin.top.into_signed(),
),
layout.content.into_signed(),
),
);
layout.size().into_unsigned()
}
}

View file

@ -12,7 +12,7 @@ use kludgine::text::Text;
use kludgine::Color;
use crate::animation::{AnimationHandle, AnimationTarget, Spawn};
use crate::context::{EventContext, GraphicsContext, WidgetContext};
use crate::context::{EventContext, GraphicsContext, LayoutContext, WidgetContext};
use crate::names::Name;
use crate::styles::components::{Easing, HighlightColor, IntrinsicPadding, TextColor};
use crate::styles::{ComponentDefinition, ComponentGroup, ComponentName, NamedComponent};
@ -167,13 +167,8 @@ impl Widget for Button {
_button: MouseButton,
context: &mut EventContext<'_, '_>,
) {
let changed = if Rect::from(
context
.last_rendered_at()
.expect("must have been rendered")
.size,
)
.contains(location)
let changed = if Rect::from(context.last_layout().expect("must have been rendered").size)
.contains(location)
{
context.activate()
} else {
@ -197,13 +192,8 @@ impl Widget for Button {
context.deactivate();
if let Some(location) = location {
if Rect::from(
context
.last_rendered_at()
.expect("must have been rendered")
.size,
)
.contains(location)
if Rect::from(context.last_layout().expect("must have been rendered").size)
.contains(location)
{
context.focus();
@ -213,10 +203,10 @@ impl Widget for Button {
}
}
fn measure(
fn layout(
&mut self,
available_space: Size<crate::ConstraintLimit>,
context: &mut GraphicsContext<'_, '_, '_, '_, '_>,
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> Size<UPx> {
let padding = context
.query_style(&IntrinsicPadding)

View file

@ -4,7 +4,7 @@ use std::panic::UnwindSafe;
use kludgine::figures::units::UPx;
use kludgine::figures::Size;
use crate::context::GraphicsContext;
use crate::context::{GraphicsContext, LayoutContext};
use crate::value::Dynamic;
use crate::widget::Widget;
use crate::Tick;
@ -50,10 +50,10 @@ impl Widget for Canvas {
}
}
fn measure(
fn layout(
&mut self,
available_space: Size<crate::ConstraintLimit>,
_context: &mut GraphicsContext<'_, '_, '_, '_, '_>,
_context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> Size<UPx> {
Size::new(available_space.width.max(), available_space.height.max())
}

View file

@ -1,8 +1,8 @@
use kludgine::figures::units::UPx;
use kludgine::figures::Size;
use kludgine::figures::{IntoSigned, Rect, Size};
use crate::context::{AsEventContext, GraphicsContext};
use crate::widget::{ChildWidget, MakeWidget, Widget};
use crate::context::{AsEventContext, GraphicsContext, LayoutContext};
use crate::widget::{MakeWidget, Widget, WidgetRef};
use crate::ConstraintLimit;
/// A widget that expands its child widget to fill the parent.
@ -14,7 +14,7 @@ pub struct Expand {
/// The weight to use when splitting available space with multiple
/// [`Expand`] widgets.
pub weight: u8,
child: ChildWidget,
child: WidgetRef,
}
impl Expand {
@ -22,7 +22,7 @@ impl Expand {
#[must_use]
pub fn new(child: impl MakeWidget) -> Self {
Self {
child: ChildWidget::new(child),
child: WidgetRef::new(child),
weight: 1,
}
}
@ -34,14 +34,14 @@ impl Expand {
#[must_use]
pub fn weighted(weight: u8, child: impl MakeWidget) -> Self {
Self {
child: ChildWidget::new(child),
child: WidgetRef::new(child),
weight,
}
}
/// Returns a reference to the child widget.
#[must_use]
pub fn child(&self) -> &ChildWidget {
pub fn child(&self) -> &WidgetRef {
&self.child
}
}
@ -49,19 +49,21 @@ impl Expand {
impl Widget for Expand {
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
let child = self.child.mounted(&mut context.as_event_context());
context.for_other(&child).redraw();
context.for_other(child).redraw();
}
fn measure(
fn layout(
&mut self,
available_space: Size<ConstraintLimit>,
context: &mut GraphicsContext<'_, '_, '_, '_, '_>,
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> Size<UPx> {
let available_space = Size::new(
ConstraintLimit::Known(available_space.width.max()),
ConstraintLimit::Known(available_space.height.max()),
);
let child = self.child.mounted(&mut context.as_event_context());
context.for_other(&child).measure(available_space)
let size = context.for_other(child.clone()).layout(available_space);
context.set_child_layout(&child, Rect::from(size.into_signed()));
size
}
}

View file

@ -6,7 +6,7 @@ use std::time::Duration;
use kludgine::app::winit::event::{Ime, KeyEvent};
use kludgine::app::winit::keyboard::Key;
use kludgine::cosmic_text::{Action, Attrs, Buffer, Cursor, Edit, Editor, Metrics, Shaping};
use kludgine::figures::units::Px;
use kludgine::figures::units::{Px, UPx};
use kludgine::figures::{
FloatConversion, IntoSigned, IntoUnsigned, Point, Rect, ScreenScale, Size,
};
@ -14,12 +14,13 @@ use kludgine::shapes::Shape;
use kludgine::text::TextOrigin;
use kludgine::{Color, Kludgine};
use crate::context::{EventContext, WidgetContext};
use crate::context::{EventContext, LayoutContext, WidgetContext};
use crate::styles::components::{HighlightColor, LineHeight, TextColor, TextSize};
use crate::styles::Styles;
use crate::utils::ModifiersExt;
use crate::value::{Generation, IntoValue, Value};
use crate::widget::{Callback, EventHandling, Widget, HANDLED, IGNORED};
use crate::ConstraintLimit;
const CURSOR_BLINK_DURATION: Duration = Duration::from_millis(500);
@ -303,11 +304,11 @@ impl Widget for Input {
);
}
fn measure(
fn layout(
&mut self,
available_space: kludgine::figures::Size<crate::ConstraintLimit>,
context: &mut crate::context::GraphicsContext<'_, '_, '_, '_, '_>,
) -> kludgine::figures::Size<kludgine::figures::units::UPx> {
available_space: Size<ConstraintLimit>,
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> Size<UPx> {
let styles = context.query_styles(&[&TextColor]);
let editor = self.editor_mut(&mut context.graphics, &styles);
let buffer = editor.buffer_mut();

View file

@ -2,10 +2,11 @@ use kludgine::figures::units::{Px, UPx};
use kludgine::figures::{IntoUnsigned, Point, ScreenScale, Size};
use kludgine::text::{MeasuredText, Text, TextOrigin};
use crate::context::GraphicsContext;
use crate::context::{GraphicsContext, LayoutContext};
use crate::styles::components::{IntrinsicPadding, TextColor};
use crate::value::{IntoValue, Value};
use crate::widget::Widget;
use crate::ConstraintLimit;
/// A read-only text widget.
#[derive(Debug)]
@ -51,10 +52,10 @@ impl Widget for Label {
}
}
fn measure(
fn layout(
&mut self,
available_space: Size<crate::ConstraintLimit>,
context: &mut GraphicsContext<'_, '_, '_, '_, '_>,
available_space: Size<ConstraintLimit>,
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> Size<UPx> {
let padding = context
.query_style(&IntrinsicPadding)
@ -64,11 +65,11 @@ impl Widget for Label {
self.text.map(|contents| {
let measured = context
.graphics
.measure_text(Text::from(contents).wrap_at(width));
.measure_text(Text::from(contents).wrap_at(dbg!(width)));
let mut size = measured.size.try_cast().unwrap_or_default();
size += padding * 2;
self.prepared_text = Some(measured);
size
dbg!(size)
})
}
}

View file

@ -1,9 +1,9 @@
use kludgine::figures::units::UPx;
use kludgine::figures::{Fraction, IntoUnsigned, ScreenScale, Size};
use crate::context::{AsEventContext, GraphicsContext};
use crate::context::{AsEventContext, GraphicsContext, LayoutContext};
use crate::styles::Dimension;
use crate::widget::{ChildWidget, MakeWidget, Widget};
use crate::widget::{MakeWidget, Widget, WidgetRef};
use crate::ConstraintLimit;
/// A widget that resizes its contained widget to an explicit size.
@ -13,13 +13,13 @@ pub struct Resize {
pub width: Option<Dimension>,
/// If present, the height to apply to the child widget.
pub height: Option<Dimension>,
child: ChildWidget,
child: WidgetRef,
}
impl Resize {
/// Returns a reference to the child widget.
#[must_use]
pub fn child(&self) -> &ChildWidget {
pub fn child(&self) -> &WidgetRef {
&self.child
}
@ -30,7 +30,7 @@ impl Resize {
T: Into<Dimension>,
{
Self {
child: ChildWidget::new(child),
child: WidgetRef::new(child),
width: Some(size.width.into()),
height: Some(size.height.into()),
}
@ -40,7 +40,7 @@ impl Resize {
#[must_use]
pub fn width(width: impl Into<Dimension>, child: impl MakeWidget) -> Self {
Self {
child: ChildWidget::new(child),
child: WidgetRef::new(child),
width: Some(width.into()),
height: None,
}
@ -50,7 +50,7 @@ impl Resize {
#[must_use]
pub fn height(height: impl Into<Dimension>, child: impl MakeWidget) -> Self {
Self {
child: ChildWidget::new(child),
child: WidgetRef::new(child),
width: None,
height: Some(height.into()),
}
@ -60,13 +60,13 @@ impl Resize {
impl Widget for Resize {
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
let child = self.child.mounted(&mut context.as_event_context());
context.for_other(&child).redraw();
context.for_other(child).redraw();
}
fn measure(
fn layout(
&mut self,
available_space: Size<ConstraintLimit>,
context: &mut GraphicsContext<'_, '_, '_, '_, '_>,
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> Size<UPx> {
if let (Some(width), Some(height)) = (self.width, self.height) {
Size::new(
@ -83,7 +83,7 @@ impl Widget for Resize {
),
);
let child = self.child.mounted(&mut context.as_event_context());
context.for_other(&child).measure(available_space)
context.for_other(child).layout(available_space)
}
}
}

View file

@ -18,36 +18,13 @@ use crate::styles::{
ComponentDefinition, ComponentGroup, ComponentName, Dimension, NamedComponent,
};
use crate::value::Dynamic;
use crate::widget::{EventHandling, MakeWidget, ManagedWidget, Widget, WidgetInstance, HANDLED};
use crate::widget::{EventHandling, MakeWidget, Widget, WidgetRef, HANDLED};
use crate::{ConstraintLimit, Name};
#[derive(Debug)]
enum ChildWidget {
Instance(WidgetInstance),
Managed(ManagedWidget),
Mounting,
}
impl ChildWidget {
pub fn managed(&mut self, context: &mut EventContext<'_, '_>) -> ManagedWidget {
if matches!(self, ChildWidget::Instance(_)) {
let ChildWidget::Instance(instance) = std::mem::replace(self, ChildWidget::Mounting)
else {
unreachable!("just matched")
};
*self = ChildWidget::Managed(context.push_child(instance));
}
let ChildWidget::Managed(managed) = self else {
unreachable!("always converted")
};
managed.clone()
}
}
/// A widget that supports scrolling its contents.
#[derive(Debug)]
pub struct Scroll {
contents: ChildWidget,
contents: WidgetRef,
content_size: Size<Px>,
control_size: Size<Px>,
scroll: Dynamic<Point<Px>>,
@ -55,13 +32,16 @@ pub struct Scroll {
max_scroll: Dynamic<Point<Px>>,
scrollbar_opacity: Dynamic<ZeroToOne>,
scrollbar_opacity_animation: AnimationHandle,
horizontal_bar: ScrollbarInfo,
vertical_bar: ScrollbarInfo,
bar_width: Px,
}
impl Scroll {
/// Returns a new scroll widget containing `contents`.
fn construct(contents: impl MakeWidget, enabled: Point<bool>) -> Self {
Self {
contents: ChildWidget::Instance(contents.make_widget()),
contents: WidgetRef::new(contents),
enabled,
content_size: Size::default(),
control_size: Size::default(),
@ -69,6 +49,9 @@ impl Scroll {
max_scroll: Dynamic::new(Point::default()),
scrollbar_opacity: Dynamic::default(),
scrollbar_opacity_animation: AnimationHandle::new(),
horizontal_bar: ScrollbarInfo::default(),
vertical_bar: ScrollbarInfo::default(),
bar_width: Px::default(),
}
}
@ -89,12 +72,14 @@ impl Scroll {
Self::construct(contents, Point::new(false, true))
}
fn constrain_scroll(&mut self) {
fn constrain_scroll(&mut self) -> (Point<Px>, Point<Px>) {
let scroll = self.scroll.get();
let clamped = scroll.max(self.max_scroll.get()).min(Point::default());
let max_scroll = self.max_scroll.get();
let clamped = scroll.max(max_scroll).min(Point::default());
if clamped != scroll {
self.scroll.set(clamped);
}
(clamped, max_scroll)
}
fn show_scrollbars(&mut self, context: &mut EventContext<'_, '_>) {
@ -135,18 +120,62 @@ impl Widget for Scroll {
fn redraw(&mut self, context: &mut crate::context::GraphicsContext<'_, '_, '_, '_, '_>) {
context.redraw_when_changed(&self.scrollbar_opacity);
self.constrain_scroll();
let Some(visible_rect) = context.graphics.visible_rect() else {
return;
};
let visible_bottom_right = visible_rect.into_signed().extent();
let managed = self.contents.mounted(&mut context.as_event_context());
context.for_other(managed).redraw();
if self.horizontal_bar.amount_hidden > 0 {
context.graphics.draw_shape(
&Shape::filled_rect(
Rect::new(
Point::new(
self.horizontal_bar.offset,
self.control_size.height - self.bar_width,
),
Size::new(self.horizontal_bar.size, self.bar_width),
),
Color::new_f32(1.0, 1.0, 1.0, *self.scrollbar_opacity.get()),
),
Point::default(),
None,
None,
);
}
if self.vertical_bar.amount_hidden > 0 {
context.graphics.draw_shape(
&Shape::filled_rect(
Rect::new(
Point::new(
visible_bottom_right.x - self.bar_width,
self.vertical_bar.offset,
),
Size::new(self.bar_width, self.vertical_bar.size),
),
Color::new_f32(1.0, 1.0, 1.0, *self.scrollbar_opacity.get()),
),
Point::default(),
None,
None,
);
}
}
fn layout(
&mut self,
available_space: Size<crate::ConstraintLimit>,
context: &mut crate::context::LayoutContext<'_, '_, '_, '_, '_>,
) -> Size<UPx> {
let styles = context.query_styles(&[&ScrollBarThickness]);
let bar_width = styles
self.bar_width = styles
.get_or_default(&ScrollBarThickness)
.into_px(context.graphics.scale());
let mut scroll = self.scroll.get();
let current_max_scroll = self.max_scroll.get();
let (mut scroll, current_max_scroll) = self.constrain_scroll();
let control_size = context.graphics.region().size;
let max_extents = Size::new(
@ -161,25 +190,32 @@ impl Widget for Scroll {
ConstraintLimit::Known(control_size.height.into_unsigned())
},
);
let managed = self.contents.managed(&mut context.as_event_context());
let managed = self.contents.mounted(&mut context.as_event_context());
let new_content_size = context
.for_other(&managed)
.measure(max_extents)
.for_other(managed.clone())
.layout(max_extents)
.into_signed();
let horizontal_bar = scrollbar_region(scroll.x, new_content_size.width, control_size.width);
self.horizontal_bar =
scrollbar_region(scroll.x, new_content_size.width, control_size.width);
let max_scroll_x = if self.enabled.x {
-horizontal_bar.amount_hidden
-self.horizontal_bar.amount_hidden
} else {
Px(0)
};
let vertical_bar = scrollbar_region(scroll.y, new_content_size.height, control_size.height);
self.vertical_bar =
scrollbar_region(scroll.y, new_content_size.height, control_size.height);
let max_scroll_y = if self.enabled.y {
-vertical_bar.amount_hidden
-self.vertical_bar.amount_hidden
} else {
Px(0)
};
let new_max_scroll = Point::new(max_scroll_x, max_scroll_y);
if current_max_scroll != new_max_scroll {
self.max_scroll.set(new_max_scroll);
scroll = scroll.max(new_max_scroll);
}
// Preserve the current scroll if the widget has resized
if self.content_size.width != new_content_size.width
@ -198,54 +234,16 @@ impl Widget for Scroll {
scroll.y = max_scroll_y * scroll_pct;
}
self.scroll.update(scroll);
self.control_size = control_size;
self.content_size = new_content_size;
let region = Rect::new(
scroll,
self.content_size
.min(Size::new(Px::MAX, Px::MAX) - scroll.max(Point::default())),
);
context.for_child(&managed, region).redraw();
context.set_child_layout(&managed, region);
if max_scroll_x != 0 {
context.graphics.draw_shape(
&Shape::filled_rect(
Rect::new(
Point::new(horizontal_bar.offset, control_size.height - bar_width),
Size::new(horizontal_bar.size, bar_width),
),
Color::new_f32(1.0, 1.0, 1.0, *self.scrollbar_opacity.get()),
),
Point::default(),
None,
None,
);
}
if max_scroll_y != 0 {
context.graphics.draw_shape(
&Shape::filled_rect(
Rect::new(
Point::new(visible_bottom_right.x - bar_width, vertical_bar.offset),
Size::new(bar_width, vertical_bar.size),
),
Color::new_f32(1.0, 1.0, 1.0, *self.scrollbar_opacity.get()),
),
Point::default(),
None,
None,
);
}
self.control_size = control_size;
self.max_scroll
.update(Point::new(max_scroll_x, max_scroll_y));
}
fn measure(
&mut self,
available_space: Size<crate::ConstraintLimit>,
_context: &mut crate::context::GraphicsContext<'_, '_, '_, '_, '_>,
) -> Size<UPx> {
Size::new(available_space.width.max(), available_space.height.max())
}
@ -273,7 +271,7 @@ impl Widget for Scroll {
}
}
#[derive(Default)]
#[derive(Debug, Default)]
struct ScrollbarInfo {
offset: Px,
amount_hidden: Px,

View file

@ -7,10 +7,10 @@ use alot::{LotId, OrderedLots};
use kludgine::figures::units::{Lp, UPx};
use kludgine::figures::{Fraction, IntoSigned, IntoUnsigned, Point, Rect, ScreenScale, Size};
use crate::context::{AsEventContext, EventContext, GraphicsContext};
use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext};
use crate::styles::Dimension;
use crate::value::{Generation, IntoValue, Value};
use crate::widget::{ChildWidget, ManagedWidget, Widget, Widgets};
use crate::widget::{Children, ManagedWidget, Widget, WidgetRef};
use crate::widgets::{Expand, Resize};
use crate::ConstraintLimit;
@ -21,7 +21,7 @@ pub struct Stack {
/// The direction to display the children using.
pub direction: Value<StackDirection>,
/// The children widgets that belong to this array.
pub children: Value<Widgets>,
pub children: Value<Children>,
layout: Layout,
layout_generation: Option<Generation>,
// TODO Refactor synced_children into its own type.
@ -32,7 +32,7 @@ impl Stack {
/// Returns a new widget with the given direction and widgets.
pub fn new(
direction: impl IntoValue<StackDirection>,
widgets: impl IntoValue<Widgets>,
widgets: impl IntoValue<Children>,
) -> Self {
let mut direction = direction.into_value();
@ -48,19 +48,19 @@ impl Stack {
}
/// Returns a new instance that displays `widgets` in a series of columns.
pub fn columns(widgets: impl IntoValue<Widgets>) -> Self {
pub fn columns(widgets: impl IntoValue<Children>) -> Self {
Self::new(StackDirection::columns(), widgets)
}
/// Returns a new instance that displays `widgets` in a series of rows.
pub fn rows(widgets: impl IntoValue<Widgets>) -> Self {
pub fn rows(widgets: impl IntoValue<Children>) -> Self {
Self::new(StackDirection::rows(), widgets)
}
fn synchronize_children(&mut self, context: &mut EventContext<'_, '_>) {
let current_generation = self.children.generation();
if current_generation.map_or_else(
|| self.children.map(Widgets::len) != self.layout.children.len(),
|| self.children.map(Children::len) != self.layout.children.len(),
|gen| Some(gen) != self.layout_generation,
) {
self.layout_generation = self.children.generation();
@ -105,7 +105,7 @@ impl Stack {
(child, StackDimension::Exact(size))
} else {
(
ChildWidget::Unmounted(widget.clone()),
WidgetRef::Unmounted(widget.clone()),
StackDimension::FitContent,
)
};
@ -131,58 +131,49 @@ impl Stack {
impl Widget for Stack {
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
self.synchronize_children(&mut context.as_event_context());
self.layout.update(
Size::new(
ConstraintLimit::Known(context.graphics.size().width),
ConstraintLimit::Known(context.graphics.size().height),
),
context.graphics.scale(),
|child_index, constraints| {
context
.for_other(&self.synced_children[child_index])
.measure(constraints)
},
);
for (index, layout) in self.layout.iter().enumerate() {
let child = &self.synced_children[index];
if layout.size > 0 {
context
.for_child(
child,
Rect::new(
self.layout
.orientation
.make_point(layout.offset, UPx(0))
.into_signed(),
self.layout
.orientation
.make_size(layout.size, self.layout.other)
.into_signed(),
),
)
.redraw();
}
for child in &self.synced_children {
context.for_other(child.clone()).redraw();
}
}
fn measure(
fn layout(
&mut self,
available_space: Size<ConstraintLimit>,
context: &mut GraphicsContext<'_, '_, '_, '_, '_>,
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> Size<UPx> {
self.synchronize_children(&mut context.as_event_context());
self.layout.update(
let content_size = self.layout.update(
available_space,
context.graphics.scale(),
|child_index, constraints| {
context
.for_other(&self.synced_children[child_index])
.measure(constraints)
|child_index, constraints, persist| {
let mut context = context.for_other(self.synced_children[child_index].clone());
if !persist {
context = context.as_temporary();
}
context.layout(constraints)
},
)
);
for (layout, child) in self.layout.iter().zip(&self.synced_children) {
if layout.size > 0 {
context.set_child_layout(
child,
Rect::new(
self.layout
.orientation
.make_point(layout.offset, UPx(0))
.into_signed(),
self.layout
.orientation
.make_size(layout.size, self.layout.other)
.into_signed(),
),
);
}
}
content_size
}
}
@ -387,7 +378,7 @@ impl Layout {
&mut self,
available: Size<ConstraintLimit>,
scale: Fraction,
mut measure: impl FnMut(usize, Size<ConstraintLimit>) -> Size<UPx>,
mut measure: impl FnMut(usize, Size<ConstraintLimit>, bool) -> Size<UPx>,
) -> Size<UPx> {
let (space_constraint, other_constraint) = self.orientation.split_size(available);
let available_space = space_constraint.max();
@ -403,6 +394,7 @@ impl Layout {
index,
self.orientation
.make_size(ConstraintLimit::ClippedAfter(remaining), other_constraint),
false,
));
self.layouts[index].size = measured;
remaining = remaining.saturating_sub(measured);
@ -447,6 +439,7 @@ impl Layout {
ConstraintLimit::Known(self.layouts[index].size.into_px(scale).into_unsigned()),
other_constraint,
),
true,
));
self.other = self.other.max(measured);
}
@ -525,22 +518,25 @@ mod tests {
flex.push(child.dimension, Fraction::ONE);
}
let computed_size = flex.update(available, Fraction::ONE, |index, constraints| {
let (measured_constraint, _other_constraint) = orientation.split_size(constraints);
let child = &children[index];
let maximum_measured = measured_constraint.max();
let (measured, other) = match (child.size.cmp(&maximum_measured), child.divisible_by) {
(Ordering::Greater, Some(divisible_by)) => {
let available_divided = maximum_measured / divisible_by;
let rows = ((child.size + divisible_by - 1) / divisible_by + available_divided
- 1)
/ available_divided;
(available_divided * divisible_by, child.other * rows)
}
_ => (child.size, child.other),
};
orientation.make_size(measured, other)
});
let computed_size =
flex.update(available, Fraction::ONE, |index, constraints, _persist| {
let (measured_constraint, _other_constraint) = orientation.split_size(constraints);
let child = &children[index];
let maximum_measured = measured_constraint.max();
let (measured, other) =
match (child.size.cmp(&maximum_measured), child.divisible_by) {
(Ordering::Greater, Some(divisible_by)) => {
let available_divided = maximum_measured / divisible_by;
let rows = ((child.size + divisible_by - 1) / divisible_by
+ available_divided
- 1)
/ available_divided;
(available_divided * divisible_by, child.other * rows)
}
_ => (child.size, child.other),
};
orientation.make_size(measured, other)
});
assert_eq!(computed_size, expected_size);
let mut offset = UPx(0);
for ((index, &child), &expected) in flex.iter().enumerate().zip(expected) {

View file

@ -1,16 +1,16 @@
use kludgine::figures::units::UPx;
use kludgine::figures::Size;
use crate::context::{AsEventContext, EventContext, GraphicsContext};
use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext};
use crate::styles::Styles;
use crate::widget::{ChildWidget, MakeWidget, Widget};
use crate::widget::{MakeWidget, Widget, WidgetRef};
use crate::ConstraintLimit;
/// A widget that applies a set of [`Styles`] to all contained widgets.
#[derive(Debug)]
pub struct Style {
styles: Styles,
child: ChildWidget,
child: WidgetRef,
}
impl Style {
@ -19,7 +19,7 @@ impl Style {
pub fn new(styles: impl Into<Styles>, child: impl MakeWidget) -> Self {
Self {
styles: styles.into(),
child: ChildWidget::new(child),
child: WidgetRef::new(child),
}
}
}
@ -31,15 +31,15 @@ impl Widget for Style {
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
let child = self.child.mounted(&mut context.as_event_context());
context.for_other(&child).redraw();
context.for_other(child).redraw();
}
fn measure(
fn layout(
&mut self,
available_space: Size<ConstraintLimit>,
context: &mut GraphicsContext<'_, '_, '_, '_, '_>,
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> Size<UPx> {
let child = self.child.mounted(&mut context.as_event_context());
context.for_other(&child).measure(available_space)
context.for_other(child).layout(available_space)
}
}

View file

@ -2,7 +2,7 @@ use std::fmt::Debug;
use kludgine::figures::utils::lossy_f64_to_f32;
use crate::context::{EventContext, GraphicsContext};
use crate::context::{EventContext, GraphicsContext, LayoutContext};
use crate::kludgine::app::winit::event::{DeviceId, KeyEvent, MouseScrollDelta, TouchPhase};
use crate::kludgine::figures::units::UPx;
use crate::kludgine::figures::Size;
@ -78,10 +78,10 @@ where
}
}
fn measure(
fn layout(
&mut self,
available_space: Size<ConstraintLimit>,
_context: &mut GraphicsContext<'_, '_, '_, '_, '_>,
_context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> Size<UPx> {
Size::new(available_space.width.max(), available_space.height.max())
}

View file

@ -20,7 +20,9 @@ use kludgine::figures::{IntoSigned, Point, Rect, Size};
use kludgine::render::Drawing;
use kludgine::Kludgine;
use crate::context::{EventContext, Exclusive, GraphicsContext, RedrawStatus, WidgetContext};
use crate::context::{
EventContext, Exclusive, GraphicsContext, LayoutContext, RedrawStatus, WidgetContext,
};
use crate::graphics::Graphics;
use crate::tree::Tree;
use crate::utils::ModifiersExt;
@ -199,22 +201,24 @@ where
self.root.tree.reset_render_order();
let graphics = self.contents.new_frame(graphics);
let mut context = GraphicsContext {
widget: WidgetContext::new(&self.root, &self.redraw_status, &mut window),
widget: WidgetContext::new(self.root.clone(), &self.redraw_status, &mut window),
graphics: Exclusive::Owned(Graphics::new(graphics)),
};
let window_size = context.graphics.size();
let actual_size = context.measure(Size::new(
let mut layout_context = LayoutContext::new(&mut context);
let window_size = layout_context.graphics.size();
let actual_size = layout_context.layout(Size::new(
ConstraintLimit::ClippedAfter(window_size.width),
ConstraintLimit::ClippedAfter(window_size.height),
));
let render_size = actual_size.min(window_size);
self.root.set_layout(Rect::from(render_size.into_signed()));
if render_size.width < window_size.width || render_size.height < window_size.height {
context
layout_context
.clipped_to(Rect::from(render_size.into_signed()))
.redraw();
} else {
context.redraw();
layout_context.redraw();
}
}
@ -284,7 +288,7 @@ where
let target = self.root.tree.focused_widget().unwrap_or(self.root.id);
let target = self.root.tree.widget(target);
let mut target = EventContext::new(
WidgetContext::new(&target, &self.redraw_status, &mut window),
WidgetContext::new(target, &self.redraw_status, &mut window),
kludgine,
);
@ -320,7 +324,7 @@ where
let widget = self.root.tree.widget(widget);
let mut widget = EventContext::new(
WidgetContext::new(&widget, &self.redraw_status, &mut window),
WidgetContext::new(widget, &self.redraw_status, &mut window),
kludgine,
);
recursively_handle_event(&mut widget, |widget| {
@ -334,7 +338,7 @@ where
let target = self.root.tree.focused_widget().unwrap_or(self.root.id);
let target = self.root.tree.widget(target);
let mut target = EventContext::new(
WidgetContext::new(&target, &self.redraw_status, &mut window),
WidgetContext::new(target, &self.redraw_status, &mut window),
kludgine,
);
@ -356,24 +360,24 @@ where
// Mouse Drag
for (button, handler) in state {
let mut context = EventContext::new(
WidgetContext::new(handler, &self.redraw_status, &mut window),
WidgetContext::new(handler.clone(), &self.redraw_status, &mut window),
kludgine,
);
let last_rendered_at = context.last_rendered_at().expect("passed hit test");
let last_rendered_at = context.last_layout().expect("passed hit test");
context.mouse_drag(location - last_rendered_at.origin, device_id, *button);
}
} else {
// Hover
let mut context = EventContext::new(
WidgetContext::new(&self.root, &self.redraw_status, &mut window),
WidgetContext::new(self.root.clone(), &self.redraw_status, &mut window),
kludgine,
);
self.mouse_state.widget = None;
for widget in self.root.tree.widgets_at_point(location) {
let mut widget_context = context.for_other(&widget);
let mut widget_context = context.for_other(widget.clone());
let relative = location
- widget_context
.last_rendered_at()
.last_layout()
.expect("passed hit test")
.origin;
@ -399,7 +403,7 @@ where
) {
if self.mouse_state.widget.take().is_some() {
let mut context = EventContext::new(
WidgetContext::new(&self.root, &self.redraw_status, &mut window),
WidgetContext::new(self.root.clone(), &self.redraw_status, &mut window),
kludgine,
);
context.clear_hover();
@ -417,7 +421,7 @@ where
match state {
ElementState::Pressed => {
EventContext::new(
WidgetContext::new(&self.root, &self.redraw_status, &mut window),
WidgetContext::new(self.root.clone(), &self.redraw_status, &mut window),
kludgine,
)
.clear_focus();
@ -427,12 +431,12 @@ where
{
if let Some(handler) = recursively_handle_event(
&mut EventContext::new(
WidgetContext::new(hovered, &self.redraw_status, &mut window),
WidgetContext::new(hovered.clone(), &self.redraw_status, &mut window),
kludgine,
),
|context| {
let relative = *location
- context.last_rendered_at().expect("passed hit test").origin;
let relative =
*location - context.last_layout().expect("passed hit test").origin;
context.mouse_down(relative, device_id, button)
},
) {
@ -456,12 +460,12 @@ where
}
let mut context = EventContext::new(
WidgetContext::new(&handler, &self.redraw_status, &mut window),
WidgetContext::new(handler, &self.redraw_status, &mut window),
kludgine,
);
let relative = if let (Some(last_rendered), Some(location)) =
(context.last_rendered_at(), self.mouse_state.location)
(context.last_layout(), self.mouse_state.location)
{
Some(location - last_rendered.origin)
} else {
@ -496,7 +500,7 @@ fn recursively_handle_event(
match each_widget(context) {
HANDLED => Some(context.widget().clone()),
IGNORED => context.parent().and_then(|parent| {
recursively_handle_event(&mut context.for_other(&parent), each_widget)
recursively_handle_event(&mut context.for_other(parent), each_widget)
}),
}
}

View file

@ -1,34 +0,0 @@
/// Invokes a function with a clone of `self`.
pub trait WithClone: Sized {
/// The type that results from cloning.
type Cloned;
/// Maps `with` with the results of cloning `self`.
fn with_clone<R>(&self, with: impl FnOnce(Self::Cloned) -> R) -> R;
}
macro_rules! impl_with_clone {
($($name:ident $field:tt),+) => {
impl<'a, $($name: Clone,)+> WithClone for ($(&'a $name,)+)
{
type Cloned = ($($name,)+);
fn with_clone<R>(&self, with: impl FnOnce(Self::Cloned) -> R) -> R {
with(($(self.$field.clone(),)+))
}
}
};
}
impl<'a, T> WithClone for &'a T
where
T: Clone,
{
type Cloned = T;
fn with_clone<R>(&self, with: impl FnOnce(Self::Cloned) -> R) -> R {
with((*self).clone())
}
}
impl_all_tuples!(impl_with_clone);