Added MountedChildren

This commit is contained in:
Jonathan Johnson 2023-12-14 07:48:56 -08:00
parent aa996a090b
commit c4200e6009
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
3 changed files with 190 additions and 81 deletions

View file

@ -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 {

View file

@ -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;

View file

@ -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);
}
},
);
});
}
}