Added Layers

This commit is contained in:
Jonathan Johnson 2023-12-05 08:51:55 -08:00
parent 17847d6947
commit 288119a831
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
5 changed files with 211 additions and 6 deletions

12
examples/layers.rs Normal file
View file

@ -0,0 +1,12 @@
use gooey::widget::MakeWidget;
use gooey::widgets::Space;
use gooey::Run;
use kludgine::Color;
fn main() -> gooey::Result {
Space::colored(Color::RED)
.and("Layers stack widgets on top of each other")
.into_layers()
.centered()
.run()
}

View file

@ -23,7 +23,9 @@ use crate::styles::components::{
use crate::styles::{ComponentDefinition, Styles, Theme, ThemePair};
use crate::utils::IgnorePoison;
use crate::value::{IntoValue, Value};
use crate::widget::{EventHandling, ManagedWidget, WidgetId, WidgetInstance, WidgetRef};
use crate::widget::{
EventHandling, ManagedWidget, RootBehavior, WidgetId, WidgetInstance, WidgetRef,
};
use crate::window::{CursorState, RunningWindow, ThemeMode};
use crate::ConstraintLimit;
@ -461,6 +463,17 @@ impl<'context, 'window> EventContext<'context, 'window> {
// sets it to `true` explicitly.
self.pending_state.focus_is_advancing = advance;
}
/// Invokes
/// [`Widget::root_behavior()`](crate::widget::Widget::root_behavior) on
/// this context's widget and returns the result.
pub fn root_behavior(&mut self) -> Option<(RootBehavior, WidgetInstance)> {
self.current_node
.clone()
.lock()
.as_widget()
.root_behavior(self)
}
}
impl<'context, 'window> Deref for EventContext<'context, 'window> {

View file

@ -5,6 +5,7 @@ use std::clone::Clone;
use std::fmt::{self, Debug};
use std::ops::{ControlFlow, Deref, DerefMut};
use std::panic::UnwindSafe;
use std::slice;
use std::sync::atomic::{self, AtomicU64};
use std::sync::{Arc, Mutex, MutexGuard};
@ -38,8 +39,8 @@ use crate::utils::IgnorePoison;
use crate::value::{Dynamic, IntoDynamic, IntoValue, Validation, Value};
use crate::widgets::checkbox::{Checkable, CheckboxState};
use crate::widgets::{
Align, Button, Checkbox, Collapse, Container, Expand, Resize, Scroll, Space, Stack, Style,
Themed, ThemedMode, Validated,
Align, Button, Checkbox, Collapse, Container, Expand, Layers, Resize, Scroll, Space, Stack,
Style, Themed, ThemedMode, Validated,
};
use crate::window::{RunningWindow, ThemeMode, Window, WindowBehavior};
use crate::{ConstraintLimit, Run};
@ -222,7 +223,7 @@ pub trait Widget: Send + UnwindSafe + Debug + 'static {
fn root_behavior(
&mut self,
context: &mut EventContext<'_, '_>,
) -> Option<(RootBehavior, &WidgetInstance)> {
) -> Option<(RootBehavior, WidgetInstance)> {
None
}
}
@ -531,8 +532,9 @@ where
fn root_behavior(
&mut self,
context: &mut EventContext<'_, '_>,
) -> Option<(RootBehavior, &WidgetInstance)> {
T::root_behavior(self, context).map(|behavior| (behavior, T::child_mut(self).widget()))
) -> Option<(RootBehavior, WidgetInstance)> {
T::root_behavior(self, context)
.map(|behavior| (behavior, T::child_mut(self).widget().clone()))
}
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
@ -1675,6 +1677,13 @@ impl Children {
pub fn into_columns(self) -> Stack {
Stack::columns(self)
}
/// Returns `self` as [`Layers`], with the widgets being stacked in the Z
/// direction.
#[must_use]
pub fn into_layers(self) -> Layers {
Layers::new(self)
}
}
impl Debug for Children {
@ -1695,6 +1704,13 @@ impl Dynamic<Children> {
pub fn into_columns(self) -> Stack {
Stack::columns(self)
}
/// Returns `self` as [`Layers`], with the widgets being stacked in the Z
/// direction.
#[must_use]
pub fn into_layers(self) -> Layers {
Layers::new(self)
}
}
impl<W> FromIterator<W> for Children
@ -1722,6 +1738,15 @@ impl DerefMut for Children {
}
}
impl<'a> IntoIterator for &'a Children {
type IntoIter = slice::Iter<'a, WidgetInstance>;
type Item = &'a WidgetInstance;
fn into_iter(self) -> Self::IntoIter {
self.ordered.iter()
}
}
/// A child widget
#[derive(Clone)]
pub enum WidgetRef {

View file

@ -12,6 +12,7 @@ mod expand;
pub mod grid;
pub mod input;
pub mod label;
mod layers;
mod mode_switch;
pub mod progress;
mod radio;
@ -38,6 +39,7 @@ pub use data::Data;
pub use expand::Expand;
pub use input::Input;
pub use label::Label;
pub use layers::Layers;
pub use mode_switch::ThemedMode;
pub use progress::ProgressBar;
pub use radio::Radio;

153
src/widgets/layers.rs Normal file
View file

@ -0,0 +1,153 @@
use std::fmt;
use gooey::widget::{RootBehavior, WidgetInstance};
use kludgine::figures::units::UPx;
use kludgine::figures::{IntoSigned, Rect, Size, Zero};
use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext};
use crate::value::{Generation, IntoValue, Value};
use crate::widget::{Children, ManagedWidget, Widget};
use crate::ConstraintLimit;
/// A Z-direction stack of widgets.
#[derive(Debug)]
pub struct Layers {
/// The children that are laid out as layers with index 0 being the lowest (bottom).
pub children: Value<Children>,
mounted: Vec<ManagedWidget>,
mounted_generation: Option<Generation>,
}
impl Layers {
/// Returns a new instance that lays out `children` as layers.
pub fn new(children: impl IntoValue<Children>) -> Self {
Self {
children: children.into_value(),
mounted: Vec::new(),
mounted_generation: None,
}
}
fn synchronize_children(&mut self, context: &mut EventContext<'_, '_>) {
let current_generation = self.children.generation();
self.children.invalidate_when_changed(context);
if current_generation.map_or_else(
|| self.children.map(Children::len) != self.mounted.len(),
|gen| Some(gen) != self.mounted_generation,
) {
self.mounted_generation = self.children.generation();
self.children.map(|children| {
for (index, widget) in children.iter().enumerate() {
if self
.mounted
.get(index)
.map_or(true, |child| child != widget)
{
// These entries do not match. See if we can find the
// new id somewhere else, if so we can swap the entries.
if let Some((swap_index, _)) = self
.mounted
.iter()
.enumerate()
.skip(index + 1)
.find(|(_, child)| *child == widget)
{
self.mounted.swap(index, swap_index);
} else {
// This is a brand new child.
self.mounted
.insert(index, context.push_child(widget.clone()));
}
}
}
// Any children remaining at the end of this process are ones
// that have been removed.
for removed in self.mounted.drain(children.len()..) {
context.remove_child(&removed);
}
});
}
}
}
impl Widget for Layers {
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
self.synchronize_children(&mut context.as_event_context());
for child in &self.mounted {
context.for_other(child).redraw();
}
}
fn summarize(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.children.map(|children| {
let mut f = f.debug_tuple("Layered");
for child in children {
f.field(child);
}
f.finish()
})
}
fn layout(
&mut self,
available_space: Size<ConstraintLimit>,
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> Size<UPx> {
self.synchronize_children(&mut context.as_event_context());
let mut size = Size::ZERO;
for child in &self.mounted {
size = size.max(
context
.for_other(child)
.as_temporary()
.layout(available_space),
);
}
// Now we know the size of the widget, we can request the widgets fill
// the allocated space.
let layout = Rect::from(size).into_signed();
for child in &self.mounted {
context
.for_other(child)
.layout(size.map(ConstraintLimit::Fill));
context.set_child_layout(child, layout);
}
size
}
fn mounted(&mut self, context: &mut EventContext<'_, '_>) {
self.synchronize_children(context);
}
fn unmounted(&mut self, context: &mut EventContext<'_, '_>) {
for child in self.mounted.drain(..) {
context.remove_child(&child);
}
self.mounted_generation = None;
}
fn root_behavior(
&mut self,
context: &mut EventContext<'_, '_>,
) -> Option<(RootBehavior, WidgetInstance)> {
self.synchronize_children(context);
for child in &self.mounted {
let Some((child_behavior, next_in_chain)) = context.for_other(child).root_behavior()
else {
continue;
};
return Some((child_behavior, next_in_chain));
}
None
}
}