use std::fmt::Debug;
use alot::OrderedLots;
use crate::value::{Dynamic, DynamicReader, ForEach, Source, WeakDynamic};
use crate::widget::{MakeWidget, WidgetInstance, WidgetList};
use crate::widgets::grid::{Grid, GridWidgets};
use crate::window::{MakeWindow, Window};
#[derive(Clone, Default)]
pub struct DebugContext {
section: Dynamic<DebugSection>,
}
impl DebugContext {
pub fn dbg<T>(&self, label: impl Into<String>, value: Dynamic<T>) -> Dynamic<T>
where
T: Clone + Debug + Send + Sync + 'static,
{
self.observe(label, &value, |value| {
value.map_each(|value| format!("{value:?}")).make_widget()
});
value
}
pub fn observe<T, Widget, MakeObserver>(
&self,
label: impl Into<String>,
value: &Dynamic<T>,
make_observer: MakeObserver,
) where
T: Clone + Send + Sync + 'static,
MakeObserver: FnOnce(Dynamic<T>) -> Widget,
Widget: MakeWidget,
{
let reader = value.create_reader();
let id = self.section.map_ref(|section| {
section.values.lock().push(Box::new(RegisteredValue {
label: label.into(),
_value: reader.clone(),
widget: make_observer(value.weak_clone()).make_widget(),
}))
});
let this = self.clone();
reader.on_disconnect(move || {
let values = this.section.map_ref(|section| section.values.clone());
values.lock().remove(id);
});
}
#[must_use]
pub fn section(&self, label: impl Into<String>) -> Self {
let label = label.into();
let this = self.section.lock();
let mut children = this.children.lock();
let section = if let Some(existing) = children.iter().find_map(|child| {
child
.map_ref(|child| child.label == label)
.then(|| child.clone())
}) {
existing
} else {
let new_section = Dynamic::new(DebugSection::new(Some(&self.section), label.clone()));
let mut insert_at = children.len();
for index in 0..children.len() {
if children[index].map_ref(|child| label < child.label) {
insert_at = index;
break;
}
}
children.insert(insert_at, new_section.clone());
new_section
};
Self { section }
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.section.map_ref(|section| {
section.children.map_ref(OrderedLots::len) + section.values.map_ref(OrderedLots::len)
}) == 0
}
}
impl MakeWindow for DebugContext {
type Behavior = WidgetInstance;
fn make_window(self) -> Window<Self::Behavior> {
self.section
.map_ref(|section| section.widget.clone())
.make_window()
.titled("Cushy Debugger")
}
}
impl Drop for DebugContext {
fn drop(&mut self) {
if self.section.instances() == 2 {
let section = self.section.lock();
if let Some(parent) = section.parent.clone() {
let label = section.label.clone();
drop(section);
DebugSection::remove_child_section(&parent, &label);
}
}
}
}
trait Observable: Send {
fn label(&self) -> &str;
fn widget(&self) -> &WidgetInstance;
}
struct RegisteredValue<T> {
label: String,
_value: DynamicReader<T>,
widget: WidgetInstance,
}
impl<T> Observable for RegisteredValue<T>
where
T: Send,
{
fn label(&self) -> &str {
&self.label
}
fn widget(&self) -> &WidgetInstance {
&self.widget
}
}
struct DebugSection {
label: String,
children: Dynamic<OrderedLots<Dynamic<DebugSection>>>,
values: Dynamic<OrderedLots<Box<dyn Observable>>>,
widget: WidgetInstance,
parent: Option<WeakDynamic<DebugSection>>,
}
impl Default for DebugSection {
fn default() -> Self {
Self::new(None, String::default())
}
}
impl DebugSection {
fn new(parent: Option<&Dynamic<Self>>, label: String) -> Self {
let values = Dynamic::<OrderedLots<Box<dyn Observable>>>::default();
let value_grid = Grid::from_rows(values.map_each(|values| {
values
.iter()
.map(|o| (o.label(), o.widget().clone().align_left()))
.collect::<GridWidgets<2>>()
}));
let children = Dynamic::<OrderedLots<Dynamic<DebugSection>>>::default();
let child_widgets = children.map_each(|children| {
children
.iter()
.map(|section| section.map_ref(|section| section.widget.clone()))
.collect::<WidgetList>()
});
let parent = parent.map(Dynamic::downgrade);
if let Some(parent) = parent.clone() {
let label = label.clone();
(&children, &values)
.for_each_subsequent({
move |(children, values)| {
if children.is_empty() && values.is_empty() {
Self::remove_child_section(&parent, &label);
}
}
})
.persist();
}
let contents = value_grid
.and(child_widgets.into_rows())
.into_rows()
.make_widget();
let widget = if label.is_empty() {
contents
} else {
contents
.disclose()
.labelled_by(label.as_str())
.collapsed(false)
.make_widget()
};
Self {
label,
children,
values,
widget,
parent,
}
}
fn remove_child_section(parent: &WeakDynamic<DebugSection>, label: &str) {
if let Some(parent) = parent.upgrade() {
let parent = parent.lock();
let mut children = parent.children.lock();
if let Some(index) = children.iter().enumerate().find_map(|(index, child)| {
child.map_ref(|child| child.label == label).then_some(index)
}) {
children.remove_by_index(index);
}
}
}
}
#[test]
fn empty_child_clears_on_drop() {
let root = DebugContext::default();
drop(root.section("child"));
assert!(root.is_empty());
}