Custom widget, layout size rounding

Closes #56
This commit is contained in:
Jonathan Johnson 2023-11-20 10:11:10 -08:00
parent 9c1c77f9b1
commit bb66803653
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
14 changed files with 952 additions and 20 deletions

120
examples/custom-widgets.rs Normal file
View file

@ -0,0 +1,120 @@
//! This example shows two approaches to writing custom widgets: implementing
//! traits or using the [`Custom`] widget with callbacks.
use gooey::value::Dynamic;
use gooey::widget::{MakeWidget, Widget, HANDLED};
use gooey::widgets::Custom;
use gooey::Run;
use kludgine::figures::units::{Lp, UPx};
use kludgine::figures::{ScreenScale, Size};
use kludgine::Color;
fn main() -> gooey::Result {
"Inline Widgets"
.and(callback_widget())
.into_rows()
.and(
"impl MakeWidget"
.and(ToggleMakeWidget::default())
.into_rows(),
)
.and("impl Widget".and(impl_widget()).into_rows())
.into_columns()
.centered()
.expand()
.run()
}
/// This function returns a [`Custom`] widget that implements its functionality
/// using callbacks.
///
/// This approach was added to make it easy to create one-off widgets in a
/// hierarchy to handle events or other purpose-built functions.
fn callback_widget() -> impl MakeWidget {
// This implementation and the impl `Widget` implementation both use the
// same Dynamic value setup.
let toggle = Toggle::default();
Custom::empty()
.background_color(toggle.color)
.on_hit_test(|_, _| true)
.on_mouse_down(move |_, _, _, _| {
toggle.value.toggle();
HANDLED
})
.height(Lp::inches(1))
}
/// A second approach is to implement [`MakeWidget`] for a type. This allows any
/// type to be used when composing your UI that know how to create a widget.
///
/// This enables using callback-based widgets (or any other combination of
/// widgets) in a reusable fashion.
#[derive(Default)]
struct ToggleMakeWidget(Toggle);
impl MakeWidget for ToggleMakeWidget {
fn make_widget(self) -> gooey::widget::WidgetInstance {
// In a real code base, the contents of callback_widget() would go here
callback_widget().make_widget()
}
}
/// This function returns [`Toggle`] using its [`Widget`] implementation.
///
/// This is the lowest-level way to implement a Widget, but it also provides the
/// most power and flexibility.
fn impl_widget() -> impl MakeWidget {
Toggle::default()
}
#[derive(Debug)]
struct Toggle {
value: Dynamic<bool>,
color: Dynamic<Color>,
}
impl Default for Toggle {
fn default() -> Self {
let value = Dynamic::default();
let color = value.map_each(|on| if *on { Color::RED } else { Color::BLUE });
Self { value, color }
}
}
impl Widget for Toggle {
fn redraw(&mut self, context: &mut gooey::context::GraphicsContext<'_, '_, '_, '_, '_>) {
context.fill(self.color.get_tracking_refresh(context));
}
fn layout(
&mut self,
available_space: Size<gooey::ConstraintLimit>,
context: &mut gooey::context::LayoutContext<'_, '_, '_, '_, '_>,
) -> Size<UPx> {
Size::new(
available_space.width.min(),
Lp::inches(1).into_upx(context.gfx.scale()),
)
}
fn hit_test(
&mut self,
_location: kludgine::figures::Point<kludgine::figures::units::Px>,
_context: &mut gooey::context::EventContext<'_, '_>,
) -> bool {
true
}
fn mouse_down(
&mut self,
_location: kludgine::figures::Point<kludgine::figures::units::Px>,
_device_id: kludgine::app::winit::event::DeviceId,
_button: kludgine::app::winit::event::MouseButton,
_context: &mut gooey::context::EventContext<'_, '_>,
) -> gooey::widget::EventHandling {
self.value.toggle();
HANDLED
}
}

View file

@ -10,7 +10,7 @@ use kludgine::app::winit::event::{
DeviceId, Ime, KeyEvent, MouseButton, MouseScrollDelta, TouchPhase,
};
use kludgine::figures::units::{Lp, Px, UPx};
use kludgine::figures::{IntoSigned, Point, Px2D, Rect, ScreenScale, Size, Zero};
use kludgine::figures::{IntoSigned, Point, Px2D, Rect, Round, ScreenScale, Size, Zero};
use kludgine::shapes::{Shape, StrokeOptions};
use kludgine::{Color, Kludgine};
@ -610,7 +610,7 @@ impl<'context, 'window, 'clip, 'gfx, 'pass> GraphicsContext<'context, 'window, '
);
let background = self.get(&WidgetBackground);
self.gfx.fill(background);
self.fill(background);
self.apply_current_font_settings();
@ -707,7 +707,8 @@ impl<'context, 'window, 'clip, 'gfx, 'pass> LayoutContext<'context, 'window, 'cl
.clone()
.lock()
.as_widget()
.layout(available_space, self);
.layout(available_space, self)
.map(Round::ceil);
if self.persist_layout {
self.graphics
.current_node

View file

@ -42,6 +42,16 @@ pub enum ConstraintLimit {
}
impl ConstraintLimit {
/// Returns `UPx::ZERO` when sizing to fit, otherwise it returns the size
/// being filled.
#[must_use]
pub fn min(self) -> UPx {
match self {
ConstraintLimit::Fill(v) => v,
ConstraintLimit::SizeToFit(_) => UPx::ZERO,
}
}
/// Returns the maximum measurement that will fit the constraint.
#[must_use]
pub fn max(self) -> UPx {

View file

@ -20,6 +20,7 @@ use crate::styles::{Dimension, FocusableWidgets, FontFamilyList, VisualOrder};
/// use gooey::styles::Dimension;
/// use gooey::styles::components::{SurfaceColor, TextColor};
/// use gooey::kludgine::Color;
/// use gooey::kludgine::figures::Zero;
///
/// define_components! {
/// GroupName {

View file

@ -132,8 +132,8 @@ impl Tree {
let node = &mut data.nodes[parent];
if let Some(cached_layout) = &node.last_layout_query {
if constraints.width.max() < cached_layout.constraints.width.max()
&& constraints.height.max() < cached_layout.constraints.height.max()
if constraints.width.max() <= cached_layout.constraints.width.max()
&& constraints.height.max() <= cached_layout.constraints.height.max()
{
return Some(cached_layout.size);
}

View file

@ -3,7 +3,7 @@
use std::cell::Cell;
use std::fmt::{Debug, Display};
use std::future::Future;
use std::ops::{Deref, DerefMut};
use std::ops::{Deref, DerefMut, Not};
use std::str::FromStr;
use std::sync::{Arc, Mutex, MutexGuard, TryLockError};
use std::task::{Poll, Waker};
@ -134,6 +134,19 @@ impl<T> Dynamic<T> {
self.0.map_mut(|value, _| map(value)).expect("deadlocked")
}
/// Updates the value to the result of invoking [`Not`] on the current
/// value. This function returns the new value.
#[allow(clippy::must_use_candidate)]
pub fn toggle(&self) -> T
where
T: Not<Output = T> + Clone,
{
self.map_mut(|value| {
*value = !value.clone();
value.clone()
})
}
/// Returns a new dynamic that is updated using `U::from(T.clone())` each
/// time `self` is updated.
#[must_use]

View file

@ -42,11 +42,14 @@ pub trait Widget: Send + UnwindSafe + Debug + 'static {
/// Layout this widget and returns the ideal size based on its contents and
/// the `available_space`.
#[allow(unused_variables)]
fn layout(
&mut self,
available_space: Size<ConstraintLimit>,
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> Size<UPx>;
) -> Size<UPx> {
available_space.map(ConstraintLimit::min)
}
/// Return true if this widget should expand to fill the window when it is
/// the root widget.
@ -250,7 +253,7 @@ pub trait WrapperWidget: Debug + Send + UnwindSafe + 'static {
available_space: Size<ConstraintLimit>,
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> WrappedLayout {
let adjusted_space = self.adjust_child_constraint(available_space, context);
let adjusted_space = self.adjust_child_constraints(available_space, context);
let child = self.child_mut().mounted(&mut context.as_event_context());
let size = context
.for_other(&child)
@ -263,7 +266,7 @@ pub trait WrapperWidget: Debug + Send + UnwindSafe + 'static {
/// Returns the adjusted contraints to use when laying out the child.
#[allow(unused_variables)]
#[must_use]
fn adjust_child_constraint(
fn adjust_child_constraints(
&mut self,
available_space: Size<ConstraintLimit>,
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
@ -430,7 +433,7 @@ where
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
let background_color = self.background_color(context);
if let Some(color) = background_color {
context.gfx.fill(color);
context.fill(color);
}
self.redraw_background(context);

View file

@ -5,6 +5,7 @@ pub mod button;
mod canvas;
pub mod checkbox;
pub mod container;
mod custom;
mod expand;
pub mod input;
pub mod label;
@ -24,6 +25,7 @@ pub use button::Button;
pub use canvas::Canvas;
pub use checkbox::Checkbox;
pub use container::Container;
pub use custom::Custom;
pub use expand::Expand;
pub use input::Input;
pub use label::Label;

View file

@ -188,7 +188,7 @@ impl WrapperWidget for Container {
})
}
fn adjust_child_constraint(
fn adjust_child_constraints(
&mut self,
available_space: Size<ConstraintLimit>,
context: &mut LayoutContext<'_, '_, '_, '_, '_>,

779
src/widgets/custom.rs Normal file
View file

@ -0,0 +1,779 @@
use std::fmt::Debug;
use std::panic::UnwindSafe;
use kludgine::app::winit::event::{
DeviceId, Ime, KeyEvent, MouseButton, MouseScrollDelta, TouchPhase,
};
use kludgine::figures::units::Px;
use kludgine::figures::{Point, Size};
use kludgine::Color;
use crate::context::{EventContext, GraphicsContext, LayoutContext, WidgetContext};
use crate::value::{IntoValue, Value};
use crate::widget::{EventHandling, MakeWidget, WidgetRef, WrappedLayout, WrapperWidget, IGNORED};
use crate::widgets::Space;
use crate::ConstraintLimit;
/// A callback-based custom widget.
///
/// This type can be used to create inline widgets without defining a new type
/// and implementing [`Widget`]/[`WrapperWidget`] for it.
#[must_use]
pub struct Custom {
child: WidgetRef,
redraw_foreground: Option<Box<dyn RedrawFunc>>,
redraw_background: Option<Box<dyn RedrawFunc>>,
mounted: Option<Box<dyn EventFunc>>,
unmounted: Option<Box<dyn EventFunc>>,
background: Option<Value<Color>>,
unhover: Option<Box<dyn EventFunc>>,
focus: Option<Box<dyn EventFunc>>,
blur: Option<Box<dyn EventFunc>>,
activate: Option<Box<dyn EventFunc>>,
deactivate: Option<Box<dyn EventFunc>>,
accept_focus: Option<Box<dyn EventFunc<bool>>>,
adjust_child: Option<Box<dyn AdjustChildConstraintsFunc>>,
position_child: Option<Box<dyn PositionChildFunc>>,
hit_test: Option<Box<dyn OneParamEventFunc<Point<Px>, bool>>>,
hover: Option<Box<dyn OneParamEventFunc<Point<Px>>>>,
mouse_down:
Option<Box<dyn ThreeParamEventFunc<Point<Px>, DeviceId, MouseButton, EventHandling>>>,
mouse_drag: Option<Box<dyn ThreeParamEventFunc<Point<Px>, DeviceId, MouseButton>>>,
mouse_up: Option<Box<MouseUpFunc>>,
ime: Option<Box<dyn OneParamEventFunc<Ime, EventHandling>>>,
keyboard_input: Option<Box<dyn ThreeParamEventFunc<DeviceId, KeyEvent, bool, EventHandling>>>,
mouse_wheel:
Option<Box<dyn ThreeParamEventFunc<DeviceId, MouseScrollDelta, TouchPhase, EventHandling>>>,
}
impl Debug for Custom {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Custom")
.field("child", &self.child)
.finish_non_exhaustive()
}
}
impl Default for Custom {
fn default() -> Self {
Self::empty()
}
}
impl Custom {
/// Returns a custom widget that has no child.
pub fn empty() -> Self {
Self::new(Space::clear())
}
/// Returns a custom widget that contains `child`.
pub fn new(child: impl MakeWidget) -> Self {
Self {
child: WidgetRef::new(child),
redraw_background: None,
redraw_foreground: None,
background: None,
mounted: None,
unmounted: None,
unhover: None,
focus: None,
blur: None,
activate: None,
deactivate: None,
accept_focus: None,
adjust_child: None,
position_child: None,
hit_test: None,
hover: None,
mouse_down: None,
mouse_drag: None,
mouse_up: None,
ime: None,
keyboard_input: None,
mouse_wheel: None,
}
}
/// Sets the background color of this widget to `color` and returns self.
///
/// If the color is set to a non-transparent value, it will be filled before
/// any of the redraw callbacks are invoked.
///
/// This value coresponds to [`WrapperWidget::background_color`].
pub fn background_color(mut self, color: impl IntoValue<Color>) -> Self {
self.background = Some(color.into_value());
self
}
/// Sets `redraw` as the callback to invoke when redrawing this control.
///
/// If this control contains a child, its redraw function will be invoked
/// after `redraw` is invoked. Use [`Self::on_redraw_after_child()`] to draw
/// after the child widget.
///
/// This callback corresponds to [`WrapperWidget::redraw_background`].
pub fn on_redraw<Redraw>(mut self, redraw: Redraw) -> Self
where
Redraw: Send
+ UnwindSafe
+ 'static
+ for<'context, 'window, 'clip, 'gfx, 'pass> FnMut(
&mut GraphicsContext<'context, 'window, 'clip, 'gfx, 'pass>,
),
{
self.redraw_background = Some(Box::new(redraw));
self
}
/// Sets `redraw` as the callback to invoke when redrawing this control,
/// after the child control has been redrawn.
///
/// If this control contains a child, its redraw function will be invoked
/// before `redraw` is invoked. Use [`Self::on_redraw()`] to draw before the
/// child widget.
///
/// `redraw` will be invoked regardless of whether a child is present.
///
/// This callback corresponds to [`WrapperWidget::redraw_foreground`].
pub fn on_redraw_after_child<Redraw>(mut self, redraw: Redraw) -> Self
where
Redraw: Send
+ UnwindSafe
+ 'static
+ for<'context, 'window, 'clip, 'gfx, 'pass> FnMut(
&mut GraphicsContext<'context, 'window, 'clip, 'gfx, 'pass>,
),
{
self.redraw_foreground = Some(Box::new(redraw));
self
}
/// Sets `mounted` to be invoked when this widget is mounted into a parent.
///
/// This callback corresponds to [`WrapperWidget::mounted`].
pub fn on_mounted<Mounted>(mut self, mounted: Mounted) -> Self
where
Mounted: Send
+ UnwindSafe
+ 'static
+ for<'context, 'window> FnMut(&mut EventContext<'context, 'window>),
{
self.mounted = Some(Box::new(mounted));
self
}
/// Sets `unmounted` to be invoked when this widget is unmounted from its
/// parent.
///
/// This callback corresponds to [`WrapperWidget::unmounted`].
pub fn on_unmounted<Mounted>(mut self, mounted: Mounted) -> Self
where
Mounted: Send
+ UnwindSafe
+ 'static
+ for<'context, 'window> FnMut(&mut EventContext<'context, 'window>),
{
self.unmounted = Some(Box::new(mounted));
self
}
/// Invokes `unhovered` when the mouse cursor leaves the widget's boundary.
///
/// This callback corresponds to [`WrapperWidget::unhover`].
pub fn on_unhover<Unhover>(mut self, unhovered: Unhover) -> Self
where
Unhover: Send
+ UnwindSafe
+ 'static
+ for<'context, 'window> FnMut(&mut EventContext<'context, 'window>),
{
self.unhover = Some(Box::new(unhovered));
self
}
/// Invokes `focus` when the widget receives input focus.
///
/// This callback corresponds to [`WrapperWidget::focus`].
pub fn on_focus<Focused>(mut self, focus: Focused) -> Self
where
Focused: Send
+ UnwindSafe
+ 'static
+ for<'context, 'window> FnMut(&mut EventContext<'context, 'window>),
{
self.focus = Some(Box::new(focus));
self
}
/// Invokes `blur` when the widget loses input focus.
///
/// This callback corresponds to [`WrapperWidget::blur`].
pub fn on_blur<Blur>(mut self, blur: Blur) -> Self
where
Blur: Send
+ UnwindSafe
+ 'static
+ for<'context, 'window> FnMut(&mut EventContext<'context, 'window>),
{
self.blur = Some(Box::new(blur));
self
}
/// Invokes `activated` when this widget becomes the active widget.
///
/// This callback corresponds to [`WrapperWidget::activate`].
pub fn on_activate<Activated>(mut self, activated: Activated) -> Self
where
Activated: Send
+ UnwindSafe
+ 'static
+ for<'context, 'window> FnMut(&mut EventContext<'context, 'window>),
{
self.activate = Some(Box::new(activated));
self
}
/// Invokes `deactivated` when this widget no longer is the active widget.
///
/// This callback corresponds to [`WrapperWidget::deactivate`].
pub fn on_deactivate<Deactivated>(mut self, deactivated: Deactivated) -> Self
where
Deactivated: Send
+ UnwindSafe
+ 'static
+ for<'context, 'window> FnMut(&mut EventContext<'context, 'window>),
{
self.deactivate = Some(Box::new(deactivated));
self
}
/// Invokes `accept` when this widget is set to receive input focus. If this
/// function returns true, this widget will become the focused widget.
///
/// This callback corresponds to [`WrapperWidget::accept_focus`].
pub fn on_accept_focus<AcceptFocus>(mut self, accept: AcceptFocus) -> Self
where
AcceptFocus: Send
+ UnwindSafe
+ 'static
+ for<'context, 'window> FnMut(&mut EventContext<'context, 'window>) -> bool,
{
self.accept_focus = Some(Box::new(accept));
self
}
/// Invokes `adjust_child_constraints` before measuring the child widget.
/// The returned constraints will be passed along to the child in its layout
/// function.
///
/// This callback corresponds to [`WrapperWidget::adjust_child_constraints`].
pub fn on_adjust_child_constraints<AdjustChildConstraints>(
mut self,
adjust_child_constraints: AdjustChildConstraints,
) -> Self
where
AdjustChildConstraints: Send
+ UnwindSafe
+ 'static
+ for<'context, 'window, 'clip, 'gfx, 'pass> FnMut(
Size<ConstraintLimit>,
&mut LayoutContext<'context, 'window, 'clip, 'gfx, 'pass>,
) -> Size<ConstraintLimit>,
{
self.adjust_child = Some(Box::new(adjust_child_constraints));
self
}
/// Invokes `position_child` to determine the position of a measured child.
///
/// This callback corresponds to [`WrapperWidget::position_child`].
pub fn on_position_child<PositionChild>(mut self, position_child: PositionChild) -> Self
where
PositionChild: Send
+ UnwindSafe
+ 'static
+ for<'context, 'window, 'clip, 'gfx, 'pass> FnMut(
Size<Px>,
Size<ConstraintLimit>,
&mut LayoutContext<'context, 'window, 'clip, 'gfx, 'pass>,
) -> WrappedLayout,
{
self.position_child = Some(Box::new(position_child));
self
}
/// Invokes `hit_test` when determining if a location should be considered
/// interacting with this widget.
///
/// This callback corresponds to [`WrapperWidget::hit_test`].
pub fn on_hit_test<HitTest>(mut self, hit_test: HitTest) -> Self
where
HitTest: Send
+ UnwindSafe
+ 'static
+ for<'context, 'window> FnMut(Point<Px>, &mut EventContext<'context, 'window>) -> bool,
{
self.hit_test = Some(Box::new(hit_test));
self
}
/// Invokes `hover` when a mouse cursor is above this widget.
///
/// This callback corresponds to [`WrapperWidget::hover`].
pub fn on_hover<Hover>(mut self, hover: Hover) -> Self
where
Hover: Send
+ UnwindSafe
+ 'static
+ for<'context, 'window> FnMut(Point<Px>, &mut EventContext<'context, 'window>),
{
self.hover = Some(Box::new(hover));
self
}
/// Invokes `mouse_down` when a mouse button is pushed on a location where
/// [`Self::on_hit_test`] returned true.
///
/// Returning [`HANDLED`](crate::widget::HANDLED) will set this widget as
/// the handler for the [`DeviceId`] and [`MouseButton`]. Future mouse
/// events for the same device and button will be sent to this widget's
/// [`Self::on_mouse_drag`] and [`Self::on_mouse_up`] callbacks.
///
/// This callback corresponds to [`WrapperWidget::mouse_down`].
pub fn on_mouse_down<MouseDown>(mut self, mouse_down: MouseDown) -> Self
where
MouseDown: Send
+ UnwindSafe
+ 'static
+ for<'context, 'window> FnMut(
Point<Px>,
DeviceId,
MouseButton,
&mut EventContext<'context, 'window>,
) -> EventHandling,
{
self.mouse_down = Some(Box::new(mouse_down));
self
}
/// Invokes `mouse_drag` when the mouse cursor moves while a tracked button
/// is presed.
///
/// This callback corresponds to [`WrapperWidget::mouse_drag`].
pub fn on_mouse_drag<MouseDrag>(mut self, mouse_drag: MouseDrag) -> Self
where
MouseDrag: Send
+ UnwindSafe
+ 'static
+ for<'context, 'window> FnMut(
Point<Px>,
DeviceId,
MouseButton,
&mut EventContext<'context, 'window>,
),
{
self.mouse_drag = Some(Box::new(mouse_drag));
self
}
/// Invokes `mouse_up` when a tracked mouse button is released.
///
/// This callback corresponds to [`WrapperWidget::mouse_up`].
pub fn on_mouse_up<MouseUp>(mut self, mouse_up: MouseUp) -> Self
where
MouseUp: Send
+ UnwindSafe
+ 'static
+ for<'context, 'window> FnMut(
Option<Point<Px>>,
DeviceId,
MouseButton,
&mut EventContext<'context, 'window>,
),
{
self.mouse_up = Some(Box::new(mouse_up));
self
}
/// Invokes `ime` when an input manager event occurs.
///
/// This callback corresponds to [`WrapperWidget::ime`].
pub fn on_ime<OnIme>(mut self, ime: OnIme) -> Self
where
OnIme: Send
+ UnwindSafe
+ 'static
+ for<'context, 'window> FnMut(Ime, &mut EventContext<'context, 'window>) -> EventHandling,
{
self.ime = Some(Box::new(ime));
self
}
/// Invokes `keyboard_input` when a keyboard event occurs.
///
/// This callback corresponds to [`WrapperWidget::keyboard_input`].
pub fn on_keyboard_input<KeyboardInput>(mut self, keyboard_input: KeyboardInput) -> Self
where
KeyboardInput: Send
+ UnwindSafe
+ 'static
+ for<'context, 'window> FnMut(
DeviceId,
KeyEvent,
bool,
&mut EventContext<'context, 'window>,
) -> EventHandling,
{
self.keyboard_input = Some(Box::new(keyboard_input));
self
}
/// Invokes `mouse_wheel` when a mouse wheel event occurs.
///
/// This callback corresponds to [`WrapperWidget::mouse_wheel`].
pub fn mouse_wheel<MouseWheel>(mut self, mouse_wheel: MouseWheel) -> Self
where
MouseWheel: Send
+ UnwindSafe
+ 'static
+ for<'context, 'window> FnMut(
DeviceId,
MouseScrollDelta,
TouchPhase,
&mut EventContext<'context, 'window>,
) -> EventHandling,
{
self.mouse_wheel = Some(Box::new(mouse_wheel));
self
}
}
impl WrapperWidget for Custom {
fn child_mut(&mut self) -> &mut WidgetRef {
&mut self.child
}
fn redraw_background(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
if let Some(redraw) = &mut self.redraw_background {
redraw.invoke(context);
}
}
fn redraw_foreground(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
if let Some(redraw) = &mut self.redraw_foreground {
redraw.invoke(context);
}
}
fn adjust_child_constraints(
&mut self,
available_space: Size<ConstraintLimit>,
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> Size<ConstraintLimit> {
if let Some(adjust_child) = &mut self.adjust_child {
adjust_child.invoke(available_space, context)
} else {
available_space
}
}
fn position_child(
&mut self,
size: Size<Px>,
available_space: Size<ConstraintLimit>,
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> WrappedLayout {
if let Some(position_child) = &mut self.position_child {
position_child.invoke(size, available_space, context)
} else {
Size::new(
available_space
.width
.fit_measured(size.width, context.gfx.scale()),
available_space
.height
.fit_measured(size.height, context.gfx.scale()),
)
.into()
}
}
fn background_color(&mut self, context: &WidgetContext<'_, '_>) -> Option<Color> {
self.background.as_ref().map(|bg| bg.get_tracked(context))
}
fn mounted(&mut self, context: &mut EventContext<'_, '_>) {
if let Some(mounted) = &mut self.mounted {
mounted.invoke(context);
}
}
fn unmounted(&mut self, context: &mut EventContext<'_, '_>) {
if let Some(unmounted) = &mut self.unmounted {
unmounted.invoke(context);
}
}
fn hit_test(&mut self, location: Point<Px>, context: &mut EventContext<'_, '_>) -> bool {
if let Some(hit_test) = &mut self.hit_test {
hit_test.invoke(location, context)
} else {
false
}
}
fn hover(&mut self, location: Point<Px>, context: &mut EventContext<'_, '_>) {
if let Some(hover) = &mut self.hover {
hover.invoke(location, context);
}
}
fn unhover(&mut self, context: &mut EventContext<'_, '_>) {
if let Some(unhover) = &mut self.unhover {
unhover.invoke(context);
}
}
fn accept_focus(&mut self, context: &mut EventContext<'_, '_>) -> bool {
if let Some(accept_focus) = &mut self.accept_focus {
accept_focus.invoke(context)
} else {
false
}
}
fn focus(&mut self, context: &mut EventContext<'_, '_>) {
if let Some(focus) = &mut self.focus {
focus.invoke(context);
}
}
fn blur(&mut self, context: &mut EventContext<'_, '_>) {
if let Some(blur) = &mut self.blur {
blur.invoke(context);
}
}
fn activate(&mut self, context: &mut EventContext<'_, '_>) {
if let Some(activate) = &mut self.activate {
activate.invoke(context);
}
}
fn deactivate(&mut self, context: &mut EventContext<'_, '_>) {
if let Some(deactivate) = &mut self.deactivate {
deactivate.invoke(context);
}
}
fn mouse_down(
&mut self,
location: Point<Px>,
device_id: DeviceId,
button: MouseButton,
context: &mut EventContext<'_, '_>,
) -> EventHandling {
if let Some(mouse_down) = &mut self.mouse_down {
mouse_down.invoke(location, device_id, button, context)
} else {
IGNORED
}
}
fn mouse_drag(
&mut self,
location: Point<Px>,
device_id: DeviceId,
button: MouseButton,
context: &mut EventContext<'_, '_>,
) {
if let Some(mouse_drag) = &mut self.mouse_drag {
mouse_drag.invoke(location, device_id, button, context);
}
}
fn mouse_up(
&mut self,
location: Option<Point<Px>>,
device_id: DeviceId,
button: MouseButton,
context: &mut EventContext<'_, '_>,
) {
if let Some(mouse_up) = &mut self.mouse_up {
mouse_up.invoke(location, device_id, button, context);
}
}
fn keyboard_input(
&mut self,
device_id: DeviceId,
input: KeyEvent,
is_synthetic: bool,
context: &mut EventContext<'_, '_>,
) -> EventHandling {
if let Some(keyboard_input) = &mut self.keyboard_input {
keyboard_input.invoke(device_id, input, is_synthetic, context)
} else {
IGNORED
}
}
fn ime(&mut self, ime: Ime, context: &mut EventContext<'_, '_>) -> EventHandling {
if let Some(f) = &mut self.ime {
f.invoke(ime, context)
} else {
IGNORED
}
}
fn mouse_wheel(
&mut self,
device_id: DeviceId,
delta: MouseScrollDelta,
phase: TouchPhase,
context: &mut EventContext<'_, '_>,
) -> EventHandling {
if let Some(mouse_wheel) = &mut self.mouse_wheel {
mouse_wheel.invoke(device_id, delta, phase, context)
} else {
IGNORED
}
}
}
trait RedrawFunc: Send + UnwindSafe {
fn invoke(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>);
}
impl<Func> RedrawFunc for Func
where
Func: Send
+ UnwindSafe
+ 'static
+ for<'context, 'window, 'clip, 'gfx, 'pass> FnMut(
&mut GraphicsContext<'context, 'window, 'clip, 'gfx, 'pass>,
),
{
fn invoke(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
self(context);
}
}
trait AdjustChildConstraintsFunc: Send + UnwindSafe {
fn invoke(
&mut self,
available_space: Size<ConstraintLimit>,
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> Size<ConstraintLimit>;
}
impl<Func> AdjustChildConstraintsFunc for Func
where
Func: Send
+ UnwindSafe
+ 'static
+ for<'context, 'window, 'clip, 'gfx, 'pass> FnMut(
Size<ConstraintLimit>,
&mut LayoutContext<'context, 'window, 'clip, 'gfx, 'pass>,
) -> Size<ConstraintLimit>,
{
fn invoke(
&mut self,
available_space: Size<ConstraintLimit>,
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> Size<ConstraintLimit> {
self(available_space, context)
}
}
trait PositionChildFunc: Send + UnwindSafe {
fn invoke(
&mut self,
size: Size<Px>,
available_space: Size<ConstraintLimit>,
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> WrappedLayout;
}
impl<Func> PositionChildFunc for Func
where
Func: Send
+ UnwindSafe
+ 'static
+ for<'context, 'window, 'clip, 'gfx, 'pass> FnMut(
Size<Px>,
Size<ConstraintLimit>,
&mut LayoutContext<'context, 'window, 'clip, 'gfx, 'pass>,
) -> WrappedLayout,
{
fn invoke(
&mut self,
size: Size<Px>,
available_space: Size<ConstraintLimit>,
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> WrappedLayout {
self(size, available_space, context)
}
}
trait EventFunc<R = ()>: Send + UnwindSafe {
fn invoke(&mut self, context: &mut EventContext<'_, '_>) -> R;
}
impl<R, Func> EventFunc<R> for Func
where
Func: Send
+ UnwindSafe
+ 'static
+ for<'context, 'window> FnMut(&mut EventContext<'context, 'window>) -> R,
{
fn invoke(&mut self, context: &mut EventContext<'_, '_>) -> R {
self(context)
}
}
trait OneParamEventFunc<P, R = ()>: Send + UnwindSafe {
fn invoke(&mut self, param: P, context: &mut EventContext<'_, '_>) -> R;
}
impl<P, R, Func> OneParamEventFunc<P, R> for Func
where
Func: Send
+ UnwindSafe
+ 'static
+ for<'context, 'window> FnMut(P, &mut EventContext<'context, 'window>) -> R,
{
fn invoke(&mut self, location: P, context: &mut EventContext<'_, '_>) -> R {
self(location, context)
}
}
trait ThreeParamEventFunc<P1, P2, P3, R = ()>: Send + UnwindSafe {
fn invoke(
&mut self,
location: P1,
device_id: P2,
button: P3,
context: &mut EventContext<'_, '_>,
) -> R;
}
type MouseUpFunc = dyn ThreeParamEventFunc<Option<Point<Px>>, DeviceId, MouseButton>;
impl<P1, P2, P3, R, Func> ThreeParamEventFunc<P1, P2, P3, R> for Func
where
Func: Send
+ UnwindSafe
+ 'static
+ for<'context, 'window> FnMut(P1, P2, P3, &mut EventContext<'context, 'window>) -> R,
{
fn invoke(
&mut self,
location: P1,
device_id: P2,
button: P3,
context: &mut EventContext<'_, '_>,
) -> R {
self(location, device_id, button, context)
}
}

View file

@ -40,7 +40,7 @@ impl Label {
if *prepared_generation == check_generation
&& *prepared_color == color
&& (*prepared_width == width
|| (*prepared_width < width
|| ((*prepared_width < width || prepared.size.width <= width)
&& prepared.line_height == prepared.size.height)) => {}
_ => {
let measured = self.text.map(|text| {

View file

@ -41,7 +41,7 @@ impl Widget for Space {
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
self.color.redraw_when_changed(context);
let color = self.color.get();
context.gfx.fill(color);
context.fill(color);
}
fn layout(

View file

@ -5,7 +5,9 @@ use std::ops::{Bound, Deref};
use alot::{LotId, OrderedLots};
use kludgine::figures::units::{Lp, UPx};
use kludgine::figures::{Fraction, IntoSigned, IntoUnsigned, Point, Rect, ScreenScale, Size};
use kludgine::figures::{
Fraction, IntoSigned, IntoUnsigned, Point, Rect, Round, ScreenScale, Size,
};
use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext};
use crate::styles::Dimension;
@ -343,7 +345,7 @@ impl Layout {
self.premeasured.retain(|&measured| measured != id);
match min {
Dimension::Px(pixels) => {
self.allocated_space.0 -= pixels.into_unsigned();
self.allocated_space.0 -= pixels.into_unsigned().ceil();
}
Dimension::Lp(lp) => {
self.allocated_space.1 -= lp;
@ -403,7 +405,8 @@ impl Layout {
) -> Size<UPx> {
let (space_constraint, other_constraint) = self.orientation.split_size(available);
let available_space = space_constraint.max();
let allocated_space = self.allocated_space.0 + self.allocated_space.1.into_upx(scale);
let allocated_space =
self.allocated_space.0 + self.allocated_space.1.into_upx(scale).ceil();
let mut remaining = available_space.saturating_sub(allocated_space);
// If our `other_constraint` is not known, we will need to give child
// widgets an opportunity to lay themselves out in the full area. This
@ -442,8 +445,8 @@ impl Layout {
// Measure the weighted children within the remaining space
if self.total_weights > 0 {
let space_per_weight = remaining / self.total_weights;
remaining %= self.total_weights;
let space_per_weight = (remaining / self.total_weights).floor();
remaining -= space_per_weight * self.total_weights;
for (fractional_index, &(id, weight)) in self.fractional.iter().enumerate() {
let index = self.children.index_of_id(id).expect("child not found");
let mut size = space_per_weight * u32::from(weight);
@ -453,7 +456,7 @@ impl Layout {
let from_end = u32::try_from(self.fractional.len() - fractional_index)
.expect("too many items");
if remaining >= from_end {
let amount = (remaining + from_end - 1) / from_end;
let amount = (remaining / from_end).ceil().min(remaining);
remaining -= amount;
size += amount;
}

View file

@ -49,7 +49,7 @@ impl WrapperWidget for Switcher {
}
// TODO this should be moved to an invalidated() event once we have it.
fn adjust_child_constraint(
fn adjust_child_constraints(
&mut self,
available_space: Size<ConstraintLimit>,
context: &mut LayoutContext<'_, '_, '_, '_, '_>,