mirror of
https://github.com/danbulant/cushy
synced 2026-06-19 14:31:04 +00:00
Added MountedChildren
This commit is contained in:
parent
aa996a090b
commit
c4200e6009
3 changed files with 190 additions and 81 deletions
162
src/widget.rs
162
src/widget.rs
|
|
@ -5,9 +5,9 @@ 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};
|
||||
use std::{slice, vec};
|
||||
|
||||
use alot::LotId;
|
||||
use intentional::Assert;
|
||||
|
|
@ -37,7 +37,7 @@ use crate::styles::{
|
|||
};
|
||||
use crate::tree::Tree;
|
||||
use crate::utils::IgnorePoison;
|
||||
use crate::value::{Dynamic, IntoDynamic, IntoValue, Validation, Value};
|
||||
use crate::value::{Dynamic, Generation, IntoDynamic, IntoValue, Validation, Value};
|
||||
use crate::widgets::checkbox::{Checkable, CheckboxState};
|
||||
use crate::widgets::layers::{OverlayLayer, Tooltipped};
|
||||
use crate::widgets::{
|
||||
|
|
@ -1506,6 +1506,12 @@ impl ManagedWidget {
|
|||
self.widget.id()
|
||||
}
|
||||
|
||||
/// Returns the underlying widget instance
|
||||
#[must_use]
|
||||
pub const fn instance(&self) -> &WidgetInstance {
|
||||
&self.widget
|
||||
}
|
||||
|
||||
/// Returns the next widget to focus after this widget.
|
||||
///
|
||||
/// This function returns the value set in
|
||||
|
|
@ -1756,6 +1762,46 @@ impl Children {
|
|||
pub fn into_layers(self) -> Layers {
|
||||
Layers::new(self)
|
||||
}
|
||||
|
||||
/// Synchronizes this list of children with another collection.
|
||||
///
|
||||
/// This function updates `collection` by calling `change_fn` for each
|
||||
/// operation that needs to be performed to synchronize. The algorithm first
|
||||
/// mounts/inserts all new children before sending a final change to
|
||||
/// `change_fn`: [`ChildrenSyncChange::Truncate`].
|
||||
pub fn synchronize_with<Collection>(
|
||||
&self,
|
||||
collection: &mut Collection,
|
||||
get_index: impl Fn(&Collection, usize) -> Option<&WidgetInstance>,
|
||||
mut change_fn: impl FnMut(&mut Collection, ChildrenSyncChange),
|
||||
) {
|
||||
for (index, widget) in self.iter().enumerate() {
|
||||
if get_index(collection, 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(Some(swap_index)) = (index + 1..usize::MAX).find_map(|index| {
|
||||
if let Some(child) = get_index(collection, index) {
|
||||
if widget == child {
|
||||
Some(Some(index))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
Some(None)
|
||||
}
|
||||
}) {
|
||||
change_fn(collection, ChildrenSyncChange::Swap(index, swap_index));
|
||||
} else {
|
||||
change_fn(
|
||||
collection,
|
||||
ChildrenSyncChange::Insert(index, widget.clone()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
change_fn(collection, ChildrenSyncChange::Truncate(self.len()));
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Children {
|
||||
|
|
@ -1819,6 +1865,118 @@ impl<'a> IntoIterator for &'a Children {
|
|||
}
|
||||
}
|
||||
|
||||
/// A change to perform during [`Children::synchronize_with`].
|
||||
pub enum ChildrenSyncChange {
|
||||
/// Insert a new widget at the given index.
|
||||
Insert(usize, WidgetInstance),
|
||||
/// Swap the widgets at the given indices.
|
||||
Swap(usize, usize),
|
||||
/// Truncate the collection to the length given.
|
||||
Truncate(usize),
|
||||
}
|
||||
|
||||
/// A collection of mounted children.
|
||||
///
|
||||
/// This collection is a helper aimed at making it easier to build widgets that
|
||||
/// contain multiple children widgets. It is used in conjunction with a
|
||||
/// `Value<Children>`.
|
||||
#[derive(Debug)]
|
||||
pub struct MountedChildren<T = ManagedWidget> {
|
||||
generation: Option<Generation>,
|
||||
children: Vec<T>,
|
||||
}
|
||||
|
||||
impl<T> MountedChildren<T>
|
||||
where
|
||||
T: MountableChild,
|
||||
{
|
||||
/// Mounts and unmounts all children needed to be in sync with `children`.
|
||||
pub fn synchronize_with(
|
||||
&mut self,
|
||||
children: &Value<Children>,
|
||||
context: &mut EventContext<'_, '_>,
|
||||
) {
|
||||
let current_generation = children.generation();
|
||||
if current_generation.map_or_else(
|
||||
|| children.map(Children::len) != self.children.len(),
|
||||
|gen| Some(gen) != self.generation,
|
||||
) {
|
||||
self.generation = current_generation;
|
||||
children.map(|children| {
|
||||
children.synchronize_with(
|
||||
self,
|
||||
|this, index| {
|
||||
this.children
|
||||
.get(index)
|
||||
.map(|mounted| mounted.widget().instance())
|
||||
},
|
||||
|this, change| match change {
|
||||
ChildrenSyncChange::Insert(index, widget) => {
|
||||
this.children
|
||||
.insert(index, T::mount(context.push_child(widget), this, index));
|
||||
}
|
||||
ChildrenSyncChange::Swap(a, b) => {
|
||||
this.children.swap(a, b);
|
||||
}
|
||||
ChildrenSyncChange::Truncate(length) => {
|
||||
for removed in this.children.drain(length..) {
|
||||
context.remove_child(&removed.unmount());
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an iterator that contains every widget in this collection.
|
||||
///
|
||||
/// When the iterator is dropped, this collection will be empty.
|
||||
pub fn drain(&mut self) -> vec::Drain<'_, T> {
|
||||
self.generation = None;
|
||||
self.children.drain(..)
|
||||
}
|
||||
|
||||
/// Returns a reference to the children.
|
||||
#[must_use]
|
||||
pub fn children(&self) -> &[T] {
|
||||
&self.children
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for MountedChildren<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
generation: None,
|
||||
children: Vec::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A child in a [`MountedChildren`] collection.
|
||||
pub trait MountableChild: Sized {
|
||||
/// Returns the mounted representation of `widget`.
|
||||
fn mount(widget: ManagedWidget, into: &MountedChildren<Self>, index: usize) -> Self;
|
||||
/// Returns the widget and performs any other cleanup for this widget being unmounted.q
|
||||
fn unmount(self) -> ManagedWidget;
|
||||
/// Returns a reference to the widget.
|
||||
fn widget(&self) -> &ManagedWidget;
|
||||
}
|
||||
|
||||
impl MountableChild for ManagedWidget {
|
||||
fn mount(widget: ManagedWidget, _into: &MountedChildren<Self>, _index: usize) -> Self {
|
||||
widget
|
||||
}
|
||||
|
||||
fn widget(&self) -> &ManagedWidget {
|
||||
self
|
||||
}
|
||||
|
||||
fn unmount(self) -> ManagedWidget {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A child widget
|
||||
#[derive(Clone)]
|
||||
pub enum WidgetRef {
|
||||
|
|
|
|||
|
|
@ -14,9 +14,10 @@ use crate::animation::easings::EaseOutQuadradic;
|
|||
use crate::animation::{AnimationHandle, AnimationTarget, IntoAnimate, Spawn, ZeroToOne};
|
||||
use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext};
|
||||
use crate::utils::IgnorePoison;
|
||||
use crate::value::{Dynamic, DynamicGuard, Generation, IntoValue, Value};
|
||||
use crate::value::{Dynamic, DynamicGuard, IntoValue, Value};
|
||||
use crate::widget::{
|
||||
Callback, Children, MakeWidget, ManagedWidget, Widget, WidgetId, WidgetRef, WrapperWidget,
|
||||
Callback, Children, MakeWidget, ManagedWidget, MountedChildren, Widget, WidgetId, WidgetRef,
|
||||
WrapperWidget,
|
||||
};
|
||||
use crate::widgets::container::ContainerShadow;
|
||||
use crate::ConstraintLimit;
|
||||
|
|
@ -26,8 +27,7 @@ use crate::ConstraintLimit;
|
|||
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>,
|
||||
mounted: MountedChildren,
|
||||
}
|
||||
|
||||
impl Layers {
|
||||
|
|
@ -35,51 +35,13 @@ impl Layers {
|
|||
pub fn new(children: impl IntoValue<Children>) -> Self {
|
||||
Self {
|
||||
children: children.into_value(),
|
||||
mounted: Vec::new(),
|
||||
mounted_generation: None,
|
||||
mounted: MountedChildren::default(),
|
||||
}
|
||||
}
|
||||
|
||||
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 != 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 == 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
self.mounted.synchronize_with(&self.children, context);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -87,7 +49,7 @@ impl Widget for Layers {
|
|||
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
|
||||
self.synchronize_children(&mut context.as_event_context());
|
||||
|
||||
for mounted in &self.mounted {
|
||||
for mounted in self.mounted.children() {
|
||||
context.for_other(mounted).redraw();
|
||||
}
|
||||
}
|
||||
|
|
@ -112,7 +74,7 @@ impl Widget for Layers {
|
|||
|
||||
let mut size = Size::ZERO;
|
||||
|
||||
for child in &self.mounted {
|
||||
for child in self.mounted.children() {
|
||||
size = size.max(
|
||||
context
|
||||
.for_other(child)
|
||||
|
|
@ -132,7 +94,7 @@ impl Widget for Layers {
|
|||
.fit_measured(size.height, context.gfx.scale()),
|
||||
);
|
||||
let layout = Rect::from(size.into_signed());
|
||||
for child in &self.mounted {
|
||||
for child in self.mounted.children() {
|
||||
context
|
||||
.for_other(child)
|
||||
.layout(size.map(ConstraintLimit::Fill));
|
||||
|
|
@ -147,10 +109,9 @@ impl Widget for Layers {
|
|||
}
|
||||
|
||||
fn unmounted(&mut self, context: &mut EventContext<'_, '_>) {
|
||||
for child in self.mounted.drain(..) {
|
||||
for child in self.mounted.drain() {
|
||||
context.remove_child(&child);
|
||||
}
|
||||
self.mounted_generation = None;
|
||||
}
|
||||
|
||||
fn root_behavior(
|
||||
|
|
@ -159,7 +120,7 @@ impl Widget for Layers {
|
|||
) -> Option<(RootBehavior, WidgetInstance)> {
|
||||
self.synchronize_children(context);
|
||||
|
||||
for child in &self.mounted {
|
||||
for child in self.mounted.children() {
|
||||
let Some((child_behavior, next_in_chain)) = context.for_other(child).root_behavior()
|
||||
else {
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContex
|
|||
use crate::styles::components::IntrinsicPadding;
|
||||
use crate::styles::FlexibleDimension;
|
||||
use crate::value::{Generation, IntoValue, Value};
|
||||
use crate::widget::{Children, ManagedWidget, Widget, WidgetRef};
|
||||
use crate::widget::{Children, ChildrenSyncChange, ManagedWidget, Widget, WidgetRef};
|
||||
use crate::widgets::grid::{GridDimension, GridLayout, Orientation};
|
||||
use crate::widgets::{Expand, Resize};
|
||||
use crate::ConstraintLimit;
|
||||
|
|
@ -66,24 +66,11 @@ impl Stack {
|
|||
) {
|
||||
self.layout_generation = self.children.generation();
|
||||
self.children.map(|children| {
|
||||
for (index, widget) in children.iter().enumerate() {
|
||||
if self
|
||||
.synced_children
|
||||
.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
|
||||
.synced_children
|
||||
.iter()
|
||||
.enumerate()
|
||||
.skip(index + 1)
|
||||
.find(|(_, child)| *child == widget)
|
||||
{
|
||||
self.synced_children.swap(index, swap_index);
|
||||
self.layout.swap(index, swap_index);
|
||||
} else {
|
||||
children.synchronize_with(
|
||||
&mut self.synced_children,
|
||||
|this, index| this.get(index).map(ManagedWidget::instance),
|
||||
|this, change| match change {
|
||||
ChildrenSyncChange::Insert(index, widget) => {
|
||||
// This is a brand new child.
|
||||
let guard = widget.lock();
|
||||
let (mut widget, dimension) = if let Some((weight, expand)) =
|
||||
|
|
@ -112,20 +99,23 @@ impl Stack {
|
|||
)
|
||||
};
|
||||
drop(guard);
|
||||
self.synced_children.insert(index, widget.mounted(context));
|
||||
this.insert(index, widget.mounted(context));
|
||||
|
||||
self.layout
|
||||
.insert(index, dimension, context.kludgine.scale());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Any children remaining at the end of this process are ones
|
||||
// that have been removed.
|
||||
for removed in self.synced_children.drain(children.len()..) {
|
||||
context.remove_child(&removed);
|
||||
}
|
||||
self.layout.truncate(children.len());
|
||||
ChildrenSyncChange::Swap(a, b) => {
|
||||
this.swap(a, b);
|
||||
self.layout.swap(a, b);
|
||||
}
|
||||
ChildrenSyncChange::Truncate(length) => {
|
||||
for removed in this.drain(length..) {
|
||||
context.remove_child(&removed);
|
||||
}
|
||||
self.layout.truncate(length);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue