mirror of
https://github.com/danbulant/cushy
synced 2026-06-19 22:41:10 +00:00
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:
parent
6f5ffd80b4
commit
0f6d3838b1
26 changed files with 963 additions and 800 deletions
|
|
@ -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)),
|
||||
|
|
|
|||
|
|
@ -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 |_| {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"))),
|
||||
)
|
||||
|
|
|
|||
345
src/animation.rs
345
src/animation.rs
|
|
@ -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
342
src/animation/easings.rs
Normal 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
|
||||
}
|
||||
}
|
||||
);
|
||||
211
src/context.rs
211
src/context.rs
|
|
@ -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 {
|
||||
|
|
|
|||
14
src/lib.rs
14
src/lib.rs
|
|
@ -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),+)
|
||||
}};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
29
src/tree.rs
29
src/tree.rs
|
|
@ -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>,
|
||||
}
|
||||
|
||||
|
|
|
|||
65
src/utils.rs
65
src/utils.rs
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
19
src/value.rs
19
src/value.rs
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
134
src/widget.rs
134
src/widget.rs
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
Loading…
Reference in a new issue