Refactored root resize behavior

Closes #84, Closes #77, Closes #78
This commit is contained in:
Jonathan Johnson 2023-11-25 12:00:59 -08:00
parent 0fd8a9487f
commit e15ae59c5c
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
22 changed files with 269 additions and 82 deletions

View file

@ -54,6 +54,5 @@ fn main() -> gooey::Result {
)
.into_rows()
.centered()
.expand()
.run()
}

View file

@ -21,6 +21,5 @@ fn main() -> gooey::Result {
)
.into_columns()
.centered()
.expand()
.run()
}

View file

@ -7,7 +7,6 @@ fn main() -> gooey::Result {
let theme_mode = Dynamic::default();
set_of_containers(3, theme_mode.clone())
.centered()
.expand()
.into_window()
.themed_mode(theme_mode)
.run()

View file

@ -23,6 +23,5 @@ fn main() -> gooey::Result {
})))
.into_columns()
.centered()
.expand()
.run()
}

View file

@ -20,6 +20,5 @@ fn main() -> gooey::Result {
.on_hit_test(|_location, _context| true)
.contain()
.centered()
.expand()
.run()
}

View file

@ -21,7 +21,6 @@ fn main() -> gooey::Result {
.and("impl Widget".and(impl_widget()).into_rows())
.into_columns()
.centered()
.expand()
.run()
}

View file

@ -74,7 +74,6 @@ fn main() -> gooey::Result {
.width(Lp::points(300)..Lp::points(600))
.scroll()
.centered()
.expand()
.run()
}

View file

@ -16,6 +16,5 @@ fn main() -> gooey::Result {
.width(Px::new(100)..Px::new(800))
.scroll()
.centered()
.expand()
.run()
}

View file

@ -78,6 +78,5 @@ fn main() -> gooey::Result {
.pad()
.scroll()
.centered()
.expand()
.run()
}

View file

@ -26,6 +26,5 @@ fn main() -> gooey::Result {
.pad()
.size(Size::squared(Lp::inches(3)))
.centered()
.expand()
.run()
}

View file

@ -19,6 +19,5 @@ fn main() -> gooey::Result {
.and(option.new_radio(Choice::C, "C"))
.into_rows()
.centered()
.expand()
.run()
}

View file

@ -20,8 +20,8 @@ fn main() -> gooey::Result {
.expand_horizontally()
.contain()
.width(..Lp::points(800))
.pad()
.centered()
.expand()
.run()
}

View file

@ -18,7 +18,6 @@ fn main() -> gooey::Result {
})
.contain()
.centered()
.expand()
.run()
}

View file

@ -22,7 +22,6 @@ fn main() -> gooey::Result {
.width(Lp::inches(2)..Lp::inches(6))
.height(Lp::inches(2)..Lp::inches(6))
.centered()
.expand()
.run()
}

View file

@ -35,9 +35,9 @@ fn main() -> gooey::Result {
validations.reset();
}))
.into_rows()
.pad()
.width(Lp::inches(6))
.centered()
.expand()
.run()
}

View file

@ -5,7 +5,7 @@ use std::borrow::Cow;
use std::collections::hash_map;
use std::fmt::Debug;
use std::ops::{
Add, Bound, Deref, Div, Mul, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo,
Add, AddAssign, Bound, Deref, Div, Mul, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo,
RangeToInclusive,
};
use std::panic::{RefUnwindSafe, UnwindSafe};
@ -608,6 +608,15 @@ pub struct DimensionRange {
pub end: Bound<Dimension>,
}
impl Default for DimensionRange {
fn default() -> Self {
Self {
start: Bound::Unbounded,
end: Bound::Unbounded,
}
}
}
impl DimensionRange {
/// Returns this range's dimension if the range represents a single
/// dimension.
@ -1117,6 +1126,97 @@ impl IntoValue<Dimension> for Lp {
}
}
impl<U> ScreenScale for Edges<U>
where
U: ScreenScale<Px = Px, UPx = UPx, Lp = Lp>,
{
type Lp = Edges<Lp>;
type Px = Edges<Px>;
type UPx = Edges<UPx>;
fn into_px(self, scale: Fraction) -> Self::Px {
Edges {
left: self.left.into_px(scale),
top: self.top.into_px(scale),
right: self.right.into_px(scale),
bottom: self.bottom.into_px(scale),
}
}
fn from_px(px: Self::Px, scale: Fraction) -> Self {
Self {
left: U::from_px(px.left, scale),
top: U::from_px(px.top, scale),
right: U::from_px(px.right, scale),
bottom: U::from_px(px.bottom, scale),
}
}
fn into_upx(self, scale: Fraction) -> Self::UPx {
Edges {
left: self.left.into_upx(scale),
top: self.top.into_upx(scale),
right: self.right.into_upx(scale),
bottom: self.bottom.into_upx(scale),
}
}
fn from_upx(px: Self::UPx, scale: Fraction) -> Self {
Self {
left: U::from_upx(px.left, scale),
top: U::from_upx(px.top, scale),
right: U::from_upx(px.right, scale),
bottom: U::from_upx(px.bottom, scale),
}
}
fn into_lp(self, scale: Fraction) -> Self::Lp {
Edges {
left: self.left.into_lp(scale),
top: self.top.into_lp(scale),
right: self.right.into_lp(scale),
bottom: self.bottom.into_lp(scale),
}
}
fn from_lp(lp: Self::Lp, scale: Fraction) -> Self {
Self {
left: U::from_lp(lp.left, scale),
top: U::from_lp(lp.top, scale),
right: U::from_lp(lp.right, scale),
bottom: U::from_lp(lp.bottom, scale),
}
}
}
impl<U, R> Add for Edges<U>
where
U: Add<Output = R>,
{
type Output = Edges<R>;
fn add(self, rhs: Self) -> Self::Output {
Edges {
left: self.left + rhs.left,
top: self.top + rhs.top,
right: self.right + rhs.right,
bottom: self.bottom + rhs.bottom,
}
}
}
impl<U, R> AddAssign<Edges<R>> for Edges<U>
where
U: AddAssign<R>,
{
fn add_assign(&mut self, rhs: Edges<R>) {
self.left += rhs.left;
self.top += rhs.top;
self.right += rhs.right;
self.bottom += rhs.bottom;
}
}
/// A set of light and dark [`Theme`]s.
#[derive(Clone, Debug, PartialEq)]
pub struct ThemePair {

View file

@ -199,7 +199,11 @@ pub trait Widget: Send + UnwindSafe + Debug + 'static {
/// Returns a reference to a single child widget if this widget is a widget
/// that primarily wraps a single other widget to customize its behavior.
#[must_use]
fn wraps(&mut self) -> Option<&WidgetInstance> {
#[allow(unused_variables)]
fn root_behavior(
&mut self,
context: &mut EventContext<'_, '_>,
) -> Option<(RootBehavior, &WidgetInstance)> {
None
}
}
@ -213,6 +217,23 @@ where
}
}
/// A behavior that should be applied to a root widget.
#[derive(Debug, Clone, Copy)]
pub enum RootBehavior {
/// This widget does not care about root behaviors, and its child should be
/// allowed to specify a behavior.
PassThrough,
/// This widget will try to expand to fill the window.
Expand,
/// This widget will measure its contents to fit its child, but Gooey should
/// still stretch this widget to fill the window.
Align,
/// This widget adjusts its child layout with padding.
Pad(Edges<Dimension>),
/// This widget changes the size of its child.
Resize(Size<DimensionRange>),
}
/// The layout of a [wrapped](WrapperWidget) child widget.
#[derive(Clone, Copy, Debug)]
pub struct WrappedLayout {
@ -254,6 +275,13 @@ pub trait WrapperWidget: Debug + Send + UnwindSafe + 'static {
/// Returns the child widget.
fn child_mut(&mut self) -> &mut WidgetRef;
/// Returns the behavior this widget should apply when positioned at the
/// root of the window.
#[allow(unused_variables)]
fn root_behavior(&mut self, context: &mut EventContext<'_, '_>) -> Option<RootBehavior> {
None
}
/// Draws the background of the widget.
///
/// This is invoked before the wrapped widget is drawn.
@ -472,8 +500,11 @@ impl<T> Widget for T
where
T: WrapperWidget,
{
fn wraps(&mut self) -> Option<&WidgetInstance> {
Some(self.child_mut().widget())
fn root_behavior(
&mut self,
context: &mut EventContext<'_, '_>,
) -> Option<(RootBehavior, &WidgetInstance)> {
T::root_behavior(self, context).map(|behavior| (behavior, T::child_mut(self).widget()))
}
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {

View file

@ -3,10 +3,10 @@ use std::fmt::Debug;
use kludgine::figures::units::UPx;
use kludgine::figures::{Fraction, IntoSigned, Point, Rect, ScreenScale, Size};
use crate::context::{AsEventContext, LayoutContext};
use crate::context::{AsEventContext, EventContext, LayoutContext};
use crate::styles::{Edges, FlexibleDimension};
use crate::value::{IntoValue, Value};
use crate::widget::{MakeWidget, WidgetRef, WrappedLayout, WrapperWidget};
use crate::widget::{MakeWidget, RootBehavior, WidgetRef, WrappedLayout, WrapperWidget};
use crate::ConstraintLimit;
/// A widget aligns its contents to its container's boundaries.
@ -178,6 +178,10 @@ impl WrapperWidget for Align {
&mut self.child
}
fn root_behavior(&mut self, _context: &mut EventContext<'_, '_>) -> Option<RootBehavior> {
Some(RootBehavior::Align)
}
fn layout_child(
&mut self,
available_space: Size<ConstraintLimit>,

View file

@ -4,11 +4,11 @@ use kludgine::figures::units::Px;
use kludgine::figures::{IntoUnsigned, Point, Rect, ScreenScale, Size};
use kludgine::Color;
use crate::context::{GraphicsContext, LayoutContext, WidgetContext};
use crate::context::{EventContext, GraphicsContext, LayoutContext, WidgetContext};
use crate::styles::components::{IntrinsicPadding, SurfaceColor};
use crate::styles::{Component, ContainerLevel, Dimension, Edges, RequireInvalidation, Styles};
use crate::value::{IntoValue, Value};
use crate::widget::{MakeWidget, WidgetRef, WrappedLayout, WrapperWidget};
use crate::widget::{MakeWidget, RootBehavior, WidgetRef, WrappedLayout, WrapperWidget};
use crate::ConstraintLimit;
/// A visual container widget, optionally applying padding and a background
@ -159,6 +159,16 @@ impl WrapperWidget for Container {
&mut self.child
}
fn root_behavior(&mut self, _context: &mut EventContext<'_, '_>) -> Option<RootBehavior> {
Some(
self.padding
.as_ref()
.map_or(RootBehavior::PassThrough, |padding| {
RootBehavior::Pad(padding.get())
}),
)
}
fn background_color(&mut self, context: &WidgetContext<'_, '_>) -> Option<kludgine::Color> {
let background = match self.background.get() {
ContainerBackground::Color(color) => EffectiveBackground::Color(color),

View file

@ -1,7 +1,7 @@
use kludgine::figures::{IntoSigned, Size};
use crate::context::{AsEventContext, LayoutContext};
use crate::widget::{MakeWidget, WidgetRef, WrappedLayout, WrapperWidget};
use crate::context::{AsEventContext, EventContext, LayoutContext};
use crate::widget::{MakeWidget, RootBehavior, WidgetRef, WrappedLayout, WrapperWidget};
use crate::widgets::Space;
use crate::ConstraintLimit;
@ -97,6 +97,10 @@ impl WrapperWidget for Expand {
&mut self.child
}
fn root_behavior(&mut self, _context: &mut EventContext<'_, '_>) -> Option<RootBehavior> {
Some(RootBehavior::Expand)
}
fn layout_child(
&mut self,
available_space: Size<ConstraintLimit>,

View file

@ -1,8 +1,8 @@
use kludgine::figures::{Fraction, IntoSigned, ScreenScale, Size};
use crate::context::{AsEventContext, LayoutContext};
use crate::context::{AsEventContext, EventContext, LayoutContext};
use crate::styles::DimensionRange;
use crate::widget::{MakeWidget, WidgetRef, WrappedLayout, WrapperWidget};
use crate::widget::{MakeWidget, RootBehavior, WidgetRef, WrappedLayout, WrapperWidget};
use crate::ConstraintLimit;
/// A widget that resizes its contained widget to an explicit size.
@ -89,6 +89,10 @@ impl WrapperWidget for Resize {
&mut self.child
}
fn root_behavior(&mut self, _context: &mut EventContext<'_, '_>) -> Option<RootBehavior> {
Some(RootBehavior::Resize(Size::new(self.width, self.height)))
}
fn layout_child(
&mut self,
available_space: Size<ConstraintLimit>,

View file

@ -32,14 +32,13 @@ use crate::context::{
WidgetContext,
};
use crate::graphics::Graphics;
use crate::styles::{FontFamilyList, ThemePair};
use crate::styles::{Edges, FontFamilyList, ThemePair};
use crate::tree::Tree;
use crate::utils::{IgnorePoison, ModifiersExt};
use crate::value::{Dynamic, DynamicReader, IntoDynamic, IntoValue, Value};
use crate::widget::{
EventHandling, ManagedWidget, Widget, WidgetId, WidgetInstance, HANDLED, IGNORED,
EventHandling, ManagedWidget, RootBehavior, Widget, WidgetId, WidgetInstance, HANDLED, IGNORED,
};
use crate::widgets::{Expand, Resize};
use crate::window::sealed::WindowCommand;
use crate::{initialize_tracing, ConstraintLimit, Run};
@ -417,62 +416,104 @@ where
fn constrain_window_resizing(
&mut self,
resizable: bool,
window: &kludgine::app::Window<'_, WindowCommand>,
window: &mut RunningWindow<'_>,
graphics: &mut kludgine::Graphics<'_>,
) -> bool {
) -> RootMode {
let mut root_or_child = self.root.widget.clone();
let mut is_expanded = false;
let mut root_mode = None;
let mut padding = Edges::<Px>::default();
loop {
let mut widget = root_or_child.lock();
if let Some(resize) = widget.downcast_ref::<Resize>() {
let min_width = resize
.width
.minimum()
.map_or(Px::ZERO, |width| width.into_px(graphics.scale()));
let max_width = resize
.width
.maximum()
.map_or(Px::MAX, |width| width.into_px(graphics.scale()));
let min_height = resize
.height
.minimum()
.map_or(Px::ZERO, |height| height.into_px(graphics.scale()));
let max_height = resize
.height
.maximum()
.map_or(Px::MAX, |height| height.into_px(graphics.scale()));
let new_min_size = (min_width > 0 || min_height > 0)
.then_some(Size::new(min_width, min_height).into_unsigned());
if new_min_size != self.min_inner_size && resizable {
window.set_min_inner_size(new_min_size);
self.min_inner_size = new_min_size;
}
let new_max_size = (max_width > 0 || max_height > 0)
.then_some(Size::new(max_width, max_height).into_unsigned());
if new_max_size != self.max_inner_size && resizable {
window.set_max_inner_size(new_max_size);
}
self.max_inner_size = new_max_size;
} else if widget.downcast_ref::<Expand>().is_some() {
is_expanded = true;
}
if let Some(wraps) = widget.as_widget().wraps().cloned() {
drop(widget);
root_or_child = wraps;
} else {
let Some(managed) = self.root.tree.widget(root_or_child.id()) else {
break;
};
let mut context = EventContext::new(
WidgetContext::new(
managed,
&self.redraw_status,
&self.current_theme,
window,
self.theme_mode.get(),
&mut self.cursor,
),
graphics,
);
let mut widget = root_or_child.lock();
match widget.as_widget().root_behavior(&mut context) {
Some((behavior, child)) => {
let child = child.clone();
match behavior {
RootBehavior::PassThrough => {}
RootBehavior::Expand => {
root_mode = root_mode.or(Some(RootMode::Expand));
}
RootBehavior::Align => {
root_mode = root_mode.or(Some(RootMode::Align));
}
RootBehavior::Pad(edges) => {
padding += edges.into_px(context.kludgine.scale());
}
RootBehavior::Resize(range) => {
let padding = padding.size();
let min_width = range
.width
.minimum()
.map_or(Px::ZERO, |width| width.into_px(context.kludgine.scale()))
.saturating_add(padding.width);
let max_width = range
.width
.maximum()
.map_or(Px::MAX, |width| width.into_px(context.kludgine.scale()))
.saturating_add(padding.width);
let min_height = range
.height
.minimum()
.map_or(Px::ZERO, |height| height.into_px(context.kludgine.scale()))
.saturating_add(padding.height);
let max_height = range
.height
.maximum()
.map_or(Px::MAX, |height| height.into_px(context.kludgine.scale()))
.saturating_add(padding.height);
let new_min_size = (min_width > 0 || min_height > 0)
.then_some(Size::new(min_width, min_height).into_unsigned());
if new_min_size != self.min_inner_size && resizable {
context.set_min_inner_size(new_min_size);
self.min_inner_size = new_min_size;
}
let new_max_size = (max_width > 0 || max_height > 0)
.then_some(Size::new(max_width, max_height).into_unsigned());
if new_max_size != self.max_inner_size && resizable {
context.set_max_inner_size(new_max_size);
}
self.max_inner_size = new_max_size;
break;
}
}
drop(widget);
root_or_child = child.clone();
}
None => break,
}
}
is_expanded
root_mode.unwrap_or(RootMode::Fit)
}
}
#[derive(Clone, Copy, Eq, PartialEq)]
enum RootMode {
Fit,
Expand,
Align,
}
impl<T> kludgine::app::WindowBehavior<WindowCommand> for GooeyWindow<T>
where
T: WindowBehavior,
@ -592,10 +633,10 @@ where
self.root.tree.new_frame(invalidations.iter().copied());
let resizable = window.winit().is_resizable();
let is_expanded = self.constrain_window_resizing(resizable, &window, graphics);
let mut window = RunningWindow::new(window, &self.clipboard, &self.focused, &self.occluded);
let root_mode = self.constrain_window_resizing(resizable, &mut window, graphics);
let graphics = self.contents.new_frame(graphics);
let mut window = RunningWindow::new(window, &self.clipboard, &self.focused, &self.occluded);
let mut context = GraphicsContext {
widget: WidgetContext::new(
self.root.clone(),
@ -616,11 +657,17 @@ where
layout_context.graphics.gfx.fill(background_color);
}
let actual_size = layout_context.layout(if is_expanded {
window_size.map(ConstraintLimit::Fill)
let layout_size =
layout_context.layout(if matches!(root_mode, RootMode::Expand | RootMode::Align) {
window_size.map(ConstraintLimit::Fill)
} else {
window_size.map(ConstraintLimit::SizeToFit)
});
let actual_size = if root_mode == RootMode::Align {
window_size
} else {
window_size.map(ConstraintLimit::SizeToFit)
});
layout_size
};
let render_size = actual_size.min(window_size);
if actual_size != window_size && !resizable {
let mut new_size = actual_size;