use std::collections::VecDeque;
use std::sync::Arc;
use ahash::AHashMap;
use alot::{LotId, Lots};
use figures::units::UPx;
use figures::{IntoSigned, Rect, Size};
use intentional::Assert;
use crate::context::{EventContext, GraphicsContext, LayoutContext};
use crate::value::{Dynamic, DynamicRead, DynamicReader};
use crate::widget::{MakeWidget, MakeWidgetWithTag, Widget, WidgetInstance, WidgetRef, WidgetTag};
use crate::ConstraintLimit;
#[derive(Debug, Clone, Default)]
pub struct Pile {
data: Dynamic<PileData>,
}
#[derive(Default, Debug)]
struct PileData {
widgets: Lots<Option<WidgetInstance>>,
visible: VecDeque<LotId>,
focus_visible: bool,
}
impl PileData {
fn hide_id(&mut self, to_remove: LotId) {
let Some((index, _)) = self
.visible
.iter()
.enumerate()
.find(|(_index, id)| **id == to_remove)
else {
return;
};
self.visible.remove(index);
}
}
impl Pile {
#[must_use]
pub fn new_pending(&self) -> PendingPiledWidget {
let mut pile = self.data.lock();
let id = pile.widgets.push(None);
PendingPiledWidget(Some(PiledWidget(Arc::new(PiledWidgetData {
pile: self.clone(),
id,
}))))
}
pub fn push(&self, widget: impl MakeWidget) -> PiledWidget {
self.new_pending().finish(widget)
}
}
impl MakeWidgetWithTag for Pile {
fn make_with_tag(self, tag: WidgetTag) -> WidgetInstance {
WidgetPile {
pile: self.data.into_reader(),
widgets: AHashMap::new(),
last_visible: None,
}
.make_with_tag(tag)
}
}
#[derive(Debug)]
struct WidgetPile {
pile: DynamicReader<PileData>,
widgets: AHashMap<LotId, WidgetRef>,
last_visible: Option<LotId>,
}
impl WidgetPile {
fn synchronize_widgets(&mut self) {
let pile = self.pile.read();
for (id, widget) in pile.widgets.entries() {
if let Some(widget) = widget.as_ref() {
self.widgets
.entry(id)
.or_insert_with(|| WidgetRef::new(widget.clone()));
}
}
self.widgets.retain(|id, _| pile.widgets.get(*id).is_some());
}
}
impl Widget for WidgetPile {
fn layout(
&mut self,
available_space: Size<ConstraintLimit>,
context: &mut LayoutContext<'_, '_, '_, '_>,
) -> Size<UPx> {
context.invalidate_when_changed(&self.pile);
self.synchronize_widgets();
let pile = self.pile.read();
let visible = pile.visible.front().copied();
let size = if let Some(id) = visible {
let visible = self
.widgets
.get_mut(&id)
.expect("visible widget")
.mounted(context);
let mut child_context = context.for_other(&visible);
if pile.focus_visible && self.last_visible != Some(id) {
child_context.focus();
}
let size = child_context.layout(available_space);
drop(child_context);
context.set_child_layout(&visible, Rect::from(size).into_signed());
size
} else {
available_space.map(ConstraintLimit::min)
};
self.last_visible = visible;
size
}
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_>) {
context.invalidate_when_changed(&self.pile);
self.synchronize_widgets();
let pile = self.pile.read();
if let Some(visible) = pile.visible.front() {
let visible = self
.widgets
.get_mut(visible)
.expect("visible widget")
.mounted(context);
context.for_other(&visible).redraw();
}
}
fn unmounted(&mut self, context: &mut EventContext<'_>) {
for widget in self.widgets.values_mut() {
widget.unmount_in(context);
}
}
}
pub struct PendingPiledWidget(Option<PiledWidget>);
impl PendingPiledWidget {
#[allow(clippy::must_use_candidate)]
pub fn finish(mut self, widget: impl MakeWidget) -> PiledWidget {
let piled = self.0.take().assert("finished called once");
let mut pile = piled.0.pile.data.lock();
pile.widgets[piled.0.id] = Some(widget.make_widget());
pile.visible.push_back(piled.0.id);
drop(pile);
piled
}
}
impl std::ops::Deref for PendingPiledWidget {
type Target = PiledWidget;
fn deref(&self) -> &Self::Target {
self.0.as_ref().expect("accessed after finished")
}
}
#[derive(Clone, Debug)]
pub struct PiledWidget(Arc<PiledWidgetData>);
impl PiledWidget {
pub fn show(&self, focus: bool) {
let mut pile = self.0.pile.data.lock();
pile.hide_id(self.0.id);
pile.visible.push_front(self.0.id);
pile.focus_visible = focus;
}
pub fn remove(&self) {
let mut pile = self.0.pile.data.lock();
if pile.visible.front() == Some(&self.0.id) {
pile.focus_visible = false;
}
pile.hide_id(self.0.id);
pile.widgets.remove(self.0.id);
}
}
#[derive(Clone, Debug)]
struct PiledWidgetData {
pile: Pile,
id: LotId,
}
impl Drop for PiledWidgetData {
fn drop(&mut self) {
let mut pile = self.pile.data.lock();
pile.hide_id(self.id);
pile.widgets.remove(self.id);
}
}