WrapperWidget, Space

This commit is contained in:
Jonathan Johnson 2023-11-08 19:09:59 -08:00
parent 9596eaac17
commit 8e268615a1
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
10 changed files with 379 additions and 123 deletions

View file

@ -468,7 +468,17 @@ impl<'context, 'window, 'clip, 'gfx, 'pass> GraphicsContext<'context, 'window, '
/// Invokes [`Widget::redraw()`](crate::widget::Widget::redraw) on this
/// context's widget.
///
/// # Panics
///
/// This function panics if the widget being drawn has no layout set (via
/// [`LayoutContext::set_child_layout()`]).
pub fn redraw(&mut self) {
assert!(
self.last_layout().is_some(),
"redraw called without set_widget_layout"
);
self.current_node
.tree
.note_widget_rendered(self.current_node.id());

View file

@ -264,17 +264,7 @@ impl<T> DynamicData<T> {
let mut changed = true;
let result = map(&mut state.wrapped.value, &mut changed);
if changed {
state.wrapped.generation = state.wrapped.generation.next();
for callback in &mut state.callbacks {
callback.update(&state.wrapped);
}
for window in state.windows.drain(..) {
window.redraw();
}
for waker in state.wakers.drain(..) {
waker.wake();
}
state.note_changed();
}
result
@ -321,6 +311,22 @@ struct State<T> {
readers: usize,
}
impl<T> State<T> {
fn note_changed(&mut self) {
self.wrapped.generation = self.wrapped.generation.next();
for callback in &mut self.callbacks {
callback.update(&self.wrapped);
}
for window in self.windows.drain(..) {
window.redraw();
}
for waker in self.wakers.drain(..) {
waker.wake();
}
}
}
impl<T> Debug for State<T>
where
T: Debug,
@ -353,6 +359,9 @@ struct GenerationalValue<T> {
}
/// An exclusive reference to the contents of a [`Dynamic`].
///
/// If the contents are accessed through [`DerefMut`], all obververs will be
/// notified of a change when this guard is dropped.
#[derive(Debug)]
pub struct DynamicGuard<'a, T> {
guard: MutexGuard<'a, State<T>>,
@ -377,7 +386,7 @@ impl<'a, T> DerefMut for DynamicGuard<'a, T> {
impl<T> Drop for DynamicGuard<'_, T> {
fn drop(&mut self) {
if self.accessed_mut {
todo!("trigger callbacks")
self.guard.note_changed();
}
}
}

View file

@ -12,7 +12,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, IntoUnsigned, Point, Rect, Size};
use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext};
use crate::styles::components::VisualOrder;
@ -167,6 +167,263 @@ where
}
}
/// A [`Widget`] that contains a single child.
pub trait WrapperWidget: Debug + Send + UnwindSafe + 'static {
/// Returns the child widget.
fn child(&mut self) -> &mut WidgetRef;
/// Returns the rectangle that the child widget should occupy given
/// `available_space`.
#[allow(unused_variables)]
fn layout_child(
&mut self,
available_space: Size<ConstraintLimit>,
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> Rect<Px> {
let child = self.child().mounted(&mut context.as_event_context());
context
.for_other(&child)
.layout(available_space)
.into_signed()
.into()
}
/// The widget has been mounted into a parent widget.
#[allow(unused_variables)]
fn mounted(&mut self, context: &mut EventContext<'_, '_>) {}
/// The widget has been removed from its parent widget.
#[allow(unused_variables)]
fn unmounted(&mut self, context: &mut EventContext<'_, '_>) {}
/// Returns true if this widget should respond to mouse input at `location`.
#[allow(unused_variables)]
fn hit_test(&mut self, location: Point<Px>, context: &mut EventContext<'_, '_>) -> bool {
false
}
/// The widget is currently has a cursor hovering it at `location`.
#[allow(unused_variables)]
fn hover(&mut self, location: Point<Px>, context: &mut EventContext<'_, '_>) {}
/// The widget is no longer being hovered.
#[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<'_, '_>) {}
/// The widget is no longer focused for user input.
#[allow(unused_variables)]
fn blur(&mut self, context: &mut EventContext<'_, '_>) {}
/// The widget has become the active widget.
#[allow(unused_variables)]
fn activate(&mut self, context: &mut EventContext<'_, '_>) {}
/// The widget is no longer active.
#[allow(unused_variables)]
fn deactivate(&mut self, context: &mut EventContext<'_, '_>) {}
/// A mouse button event has occurred at `location`. Returns whether the
/// event has been handled or not.
///
/// If an event is handled, the widget will receive callbacks for
/// [`mouse_drag`](Self::mouse_drag) and [`mouse_up`](Self::mouse_up).
#[allow(unused_variables)]
fn mouse_down(
&mut self,
location: Point<Px>,
device_id: DeviceId,
button: MouseButton,
context: &mut EventContext<'_, '_>,
) -> EventHandling {
IGNORED
}
/// A mouse button is being held down as the cursor is moved across the
/// widget.
#[allow(unused_variables)]
fn mouse_drag(
&mut self,
location: Point<Px>,
device_id: DeviceId,
button: MouseButton,
context: &mut EventContext<'_, '_>,
) {
}
/// A mouse button is no longer being pressed.
#[allow(unused_variables)]
fn mouse_up(
&mut self,
location: Option<Point<Px>>,
device_id: DeviceId,
button: MouseButton,
context: &mut EventContext<'_, '_>,
) {
}
/// A keyboard event has been sent to this widget. Returns whether the event
/// has been handled or not.
#[allow(unused_variables)]
fn keyboard_input(
&mut self,
device_id: DeviceId,
input: KeyEvent,
is_synthetic: bool,
context: &mut EventContext<'_, '_>,
) -> EventHandling {
IGNORED
}
/// An input manager event has been sent to this widget. Returns whether the
/// event has been handled or not.
#[allow(unused_variables)]
fn ime(&mut self, ime: Ime, context: &mut EventContext<'_, '_>) -> EventHandling {
IGNORED
}
/// A mouse wheel event has been sent to this widget. Returns whether the
/// event has been handled or not.
#[allow(unused_variables)]
fn mouse_wheel(
&mut self,
device_id: DeviceId,
delta: MouseScrollDelta,
phase: TouchPhase,
context: &mut EventContext<'_, '_>,
) -> EventHandling {
IGNORED
}
}
impl<T> Widget for T
where
T: WrapperWidget,
{
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
let child = self.child().mounted(&mut context.as_event_context());
context.for_other(&child).redraw();
}
fn layout(
&mut self,
available_space: Size<ConstraintLimit>,
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> Size<UPx> {
let child = self.child().mounted(&mut context.as_event_context());
let layout = self.layout_child(available_space, context);
context.set_child_layout(&child, layout);
layout.size.into_unsigned()
}
fn mounted(&mut self, context: &mut EventContext<'_, '_>) {
T::mounted(self, context);
}
fn unmounted(&mut self, context: &mut EventContext<'_, '_>) {
T::unmounted(self, context);
}
fn hit_test(&mut self, location: Point<Px>, context: &mut EventContext<'_, '_>) -> bool {
T::hit_test(self, location, context)
}
fn hover(&mut self, location: Point<Px>, context: &mut EventContext<'_, '_>) {
T::hover(self, location, context);
}
fn unhover(&mut self, context: &mut EventContext<'_, '_>) {
T::unhover(self, context);
}
fn accept_focus(&mut self, context: &mut EventContext<'_, '_>) -> bool {
T::accept_focus(self, context)
}
fn focus(&mut self, context: &mut EventContext<'_, '_>) {
T::focus(self, context);
}
fn blur(&mut self, context: &mut EventContext<'_, '_>) {
T::blur(self, context);
}
fn activate(&mut self, context: &mut EventContext<'_, '_>) {
T::activate(self, context);
}
fn deactivate(&mut self, context: &mut EventContext<'_, '_>) {
T::deactivate(self, context);
}
fn mouse_down(
&mut self,
location: Point<Px>,
device_id: DeviceId,
button: MouseButton,
context: &mut EventContext<'_, '_>,
) -> EventHandling {
T::mouse_down(self, location, device_id, button, context)
}
fn mouse_drag(
&mut self,
location: Point<Px>,
device_id: DeviceId,
button: MouseButton,
context: &mut EventContext<'_, '_>,
) {
T::mouse_drag(self, location, device_id, button, context);
}
fn mouse_up(
&mut self,
location: Option<Point<Px>>,
device_id: DeviceId,
button: MouseButton,
context: &mut EventContext<'_, '_>,
) {
T::mouse_up(self, location, device_id, button, context);
}
fn keyboard_input(
&mut self,
device_id: DeviceId,
input: KeyEvent,
is_synthetic: bool,
context: &mut EventContext<'_, '_>,
) -> EventHandling {
T::keyboard_input(self, device_id, input, is_synthetic, context)
}
fn ime(&mut self, ime: Ime, context: &mut EventContext<'_, '_>) -> EventHandling {
T::ime(self, ime, context)
}
fn mouse_wheel(
&mut self,
device_id: DeviceId,
delta: MouseScrollDelta,
phase: TouchPhase,
context: &mut EventContext<'_, '_>,
) -> EventHandling {
T::mouse_wheel(self, device_id, delta, phase, context)
}
}
/// A type that can create a [`WidgetInstance`].
pub trait MakeWidget: Sized {
/// Returns a new widget.

View file

@ -8,6 +8,7 @@ mod input;
mod label;
mod resize;
pub mod scroll;
mod space;
pub mod stack;
mod style;
mod tilemap;
@ -20,6 +21,7 @@ pub use input::Input;
pub use label::Label;
pub use resize::Resize;
pub use scroll::Scroll;
pub use space::Space;
pub use stack::Stack;
pub use style::Style;
pub use tilemap::TileMap;

View file

@ -3,10 +3,10 @@ use std::fmt::Debug;
use kludgine::figures::units::UPx;
use kludgine::figures::{Fraction, IntoSigned, IntoUnsigned, Point, Rect, ScreenScale, Size};
use crate::context::{AsEventContext, GraphicsContext, LayoutContext};
use crate::context::{AsEventContext, LayoutContext};
use crate::styles::{Dimension, Edges, FlexibleDimension};
use crate::value::{IntoValue, Value};
use crate::widget::{MakeWidget, Widget, WidgetRef};
use crate::widget::{MakeWidget, WidgetRef, WrapperWidget};
use crate::ConstraintLimit;
/// A widget aligns its contents to its container's boundaries.
@ -145,31 +145,31 @@ impl FrameInfo {
}
}
impl Widget for Align {
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
let child = self.child.mounted(&mut context.as_event_context());
context.for_other(&child).redraw();
impl WrapperWidget for Align {
fn child(&mut self) -> &mut WidgetRef {
&mut self.child
}
fn layout(
fn layout_child(
&mut self,
available_space: Size<ConstraintLimit>,
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> Size<UPx> {
let child = self.child.mounted(&mut context.as_event_context());
) -> Rect<kludgine::figures::units::Px> {
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()
Rect::new(
Point::new(
layout.margin.left.into_signed(),
layout.margin.top.into_signed(),
),
layout.content.into_signed(),
)
}
}
impl AsMut<WidgetRef> for Align {
fn as_mut(&mut self) -> &mut WidgetRef {
&mut self.child
}
}
@ -178,9 +178,3 @@ struct Layout {
margin: Edges<UPx>,
content: Size<UPx>,
}
impl Layout {
pub fn size(&self) -> Size<UPx> {
self.margin.size() + self.content
}
}

View file

@ -1,8 +1,9 @@
use kludgine::figures::units::UPx;
use kludgine::figures::units::{Px, UPx};
use kludgine::figures::{IntoSigned, Rect, Size};
use crate::context::{AsEventContext, GraphicsContext, LayoutContext};
use crate::widget::{MakeWidget, Widget, WidgetRef};
use crate::context::{AsEventContext, LayoutContext};
use crate::widget::{MakeWidget, WidgetRef, WrapperWidget};
use crate::widgets::Space;
use crate::ConstraintLimit;
/// A widget that expands its child widget to fill the parent.
@ -14,7 +15,7 @@ pub struct Expand {
/// The weight to use when splitting available space with multiple
/// [`Expand`] widgets.
pub weight: u8,
child: Option<WidgetRef>,
child: WidgetRef,
}
impl Default for Expand {
@ -28,7 +29,7 @@ impl Expand {
#[must_use]
pub fn new(child: impl MakeWidget) -> Self {
Self {
child: Some(WidgetRef::new(child)),
child: WidgetRef::new(child),
weight: 1,
}
}
@ -37,7 +38,7 @@ impl Expand {
#[must_use]
pub fn empty() -> Self {
Self {
child: None,
child: WidgetRef::new(Space),
weight: 1,
}
}
@ -49,57 +50,44 @@ impl Expand {
#[must_use]
pub fn weighted(weight: u8, child: impl MakeWidget) -> Self {
Self {
child: Some(WidgetRef::new(child)),
child: WidgetRef::new(child),
weight,
}
}
/// Returns a reference to the child widget.
#[must_use]
pub fn child(&self) -> Option<&WidgetRef> {
self.child.as_ref()
pub const fn child(&self) -> &WidgetRef {
&self.child
}
}
impl Widget for Expand {
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
let Some(child) = &mut self.child else { return };
let child = child.mounted(&mut context.as_event_context());
context.for_other(&child).redraw();
impl WrapperWidget for Expand {
fn child(&mut self) -> &mut WidgetRef {
&mut self.child
}
fn layout(
fn layout_child(
&mut self,
available_space: Size<ConstraintLimit>,
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> Size<UPx> {
) -> Rect<Px> {
let available_space = Size::new(
ConstraintLimit::Known(available_space.width.max()),
ConstraintLimit::Known(available_space.height.max()),
);
let child = self
.child
.as_mut()
.map(|child| child.mounted(&mut context.as_event_context()));
let size = if let Some(child) = &child {
context.for_other(child).layout(available_space)
} else {
Size::default()
};
let child = self.child.mounted(&mut context.as_event_context());
let size = context.for_other(&child).layout(available_space);
let expanded_size = Size::new(
Size::<UPx>::new(
available_space
.width
.fit_measured(size.width, context.graphics.scale()),
available_space
.height
.fit_measured(size.height, context.graphics.scale()),
);
if let Some(child) = child {
context.set_child_layout(&child, Rect::from(expanded_size.into_signed()));
}
expanded_size
)
.into_signed()
.into()
}
}

View file

@ -1,9 +1,8 @@
use kludgine::figures::units::UPx;
use kludgine::figures::{Fraction, IntoSigned, IntoUnsigned, Rect, ScreenScale, Size};
use crate::context::{AsEventContext, GraphicsContext, LayoutContext};
use crate::context::{AsEventContext, LayoutContext};
use crate::styles::Dimension;
use crate::widget::{MakeWidget, Widget, WidgetRef};
use crate::widget::{MakeWidget, WidgetRef, WrapperWidget};
use crate::ConstraintLimit;
/// A widget that resizes its contained widget to an explicit size.
@ -57,17 +56,16 @@ 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();
impl WrapperWidget for Resize {
fn child(&mut self) -> &mut WidgetRef {
&mut self.child
}
fn layout(
fn layout_child(
&mut self,
available_space: Size<ConstraintLimit>,
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> Size<UPx> {
) -> Rect<kludgine::figures::units::Px> {
let child = self.child.mounted(&mut context.as_event_context());
let size = if let (Some(width), Some(height)) = (self.width, self.height) {
Size::new(
@ -85,8 +83,7 @@ impl Widget for Resize {
);
context.for_other(&child).layout(available_space)
};
context.set_child_layout(&child, Rect::from(size.into_signed()));
size
Rect::from(size.into_signed())
}
}

22
src/widgets/space.rs Normal file
View file

@ -0,0 +1,22 @@
use kludgine::figures::units::UPx;
use kludgine::figures::Size;
use crate::context::{GraphicsContext, LayoutContext};
use crate::widget::Widget;
use crate::ConstraintLimit;
/// A widget that does nothing and draws nothing.
#[derive(Debug, Clone)]
pub struct Space;
impl Widget for Space {
fn redraw(&mut self, _context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {}
fn layout(
&mut self,
_available_space: Size<ConstraintLimit>,
_context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> Size<UPx> {
Size::default()
}
}

View file

@ -1,5 +1,5 @@
//! A widget that combines an array of [`Widgets`] into one.
// TODO on scale change, all Lp children need to resize
//! A widget that combines a collection of [`Children`] widgets into one.
// TODO on scale change, all `Lp` children need to resize
use std::ops::Deref;
@ -11,10 +11,10 @@ use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContex
use crate::styles::Dimension;
use crate::value::{Generation, IntoValue, Value};
use crate::widget::{Children, ManagedWidget, Widget, WidgetRef};
use crate::widgets::{Expand, Label, Resize};
use crate::widgets::{Expand, Resize};
use crate::ConstraintLimit;
/// A widget that displays a collection of [`Widgets`] in a
/// A widget that displays a collection of [`Children`] widgets in a
/// [direction](StackDirection).
#[derive(Debug)]
pub struct Stack {
@ -87,21 +87,12 @@ impl Stack {
let guard = widget.lock();
let (mut widget, dimension) =
if let Some(expand) = guard.downcast_ref::<Expand>() {
if let Some(child) = expand.child() {
(
child.clone(),
StackDimension::Fractional {
weight: expand.weight,
},
)
} else {
(
WidgetRef::new(Label::new("")), // TODO this should be an empty widget.
StackDimension::Fractional {
weight: expand.weight,
},
)
}
(
expand.child().clone(),
StackDimension::Fractional {
weight: expand.weight,
},
)
} else if let Some((child, size)) =
guard.downcast_ref::<Resize>().and_then(|r| {
match self.layout.orientation.orientation {

View file

@ -1,10 +1,6 @@
use kludgine::figures::units::UPx;
use kludgine::figures::Size;
use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext};
use crate::context::EventContext;
use crate::styles::Styles;
use crate::widget::{MakeWidget, Widget, WidgetRef};
use crate::ConstraintLimit;
use crate::widget::{MakeWidget, WidgetRef, WrapperWidget};
/// A widget that applies a set of [`Styles`] to all contained widgets.
#[derive(Debug)]
@ -24,22 +20,12 @@ impl Style {
}
}
impl Widget for Style {
impl WrapperWidget for Style {
fn child(&mut self) -> &mut WidgetRef {
&mut self.child
}
fn mounted(&mut self, context: &mut EventContext<'_, '_>) {
context.attach_styles(self.styles.clone());
}
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
let child = self.child.mounted(&mut context.as_event_context());
context.for_other(&child).redraw();
}
fn layout(
&mut self,
available_space: Size<ConstraintLimit>,
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> Size<UPx> {
let child = self.child.mounted(&mut context.as_event_context());
context.for_other(&child).layout(available_space)
}
}