mirror of
https://github.com/danbulant/cushy
synced 2026-06-21 23:52:52 +00:00
Added Layers
This commit is contained in:
parent
17847d6947
commit
288119a831
5 changed files with 211 additions and 6 deletions
12
examples/layers.rs
Normal file
12
examples/layers.rs
Normal 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()
|
||||
}
|
||||
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
153
src/widgets/layers.rs
Normal 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
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue