use super::{EventOwnerTreeNode, EventTreeNode, ViewModelBase};
use ferroui_base::collections::FerroList;
use ferroui_base::data::model::{Event, INotifyPropertyChanged, ModelTypes};
use ferroui_base::ferro_markup_type;
use std::cell::{Cell, OnceCell, RefCell};
use std::rc::{Rc, Weak};
pub enum EventTreeNodeKind {
Owner(EventOwnerTreeNode),
Event(EventTreeNode),
}
pub struct EventTreeNodeBase {
base: ViewModelBase,
this: Weak<EventTreeNodeBase>,
pub(crate) update_children: Cell<bool>,
pub(crate) update_parent: Cell<bool>,
is_expanded: Cell<bool>,
is_enabled: Cell<Option<bool>>,
is_visible: Cell<bool>,
parent: Option<Weak<EventTreeNodeBase>>,
text: String,
children: RefCell<Option<FerroList<Rc<EventTreeNodeBase>>>>,
visible_children: OnceCell<Rc<FerroList<Rc<EventTreeNodeBase>>>>,
kind: EventTreeNodeKind,
}
impl PartialEq for EventTreeNodeBase {
fn eq(&self, other: &Self) -> bool {
std::ptr::eq(self, other)
}
}
impl INotifyPropertyChanged for EventTreeNodeBase {
fn property_changed(&self) -> &Event<str> {
self.base.property_changed()
}
}
impl EventTreeNodeBase {
pub(crate) fn new(
parent: Option<&Rc<EventTreeNodeBase>>,
text: String,
kind: impl FnOnce(&Weak<EventTreeNodeBase>) -> EventTreeNodeKind,
) -> Rc<Self> {
let this = Rc::new_cyclic(|this| Self {
base: ViewModelBase::new(),
this: this.clone(),
update_children: Cell::new(true),
update_parent: Cell::new(true),
is_expanded: Cell::new(false),
is_enabled: Cell::new(Some(false)),
is_visible: Cell::new(false),
parent: parent.map(Rc::downgrade),
text,
children: RefCell::new(None),
visible_children: OnceCell::new(),
kind: kind(this),
});
this.set_is_visible(true);
this
}
pub(crate) fn to_rc(&self) -> Rc<EventTreeNodeBase> {
self.this.upgrade().expect("a node is reached through its shared handle")
}
pub fn kind(&self) -> &EventTreeNodeKind {
&self.kind
}
pub fn as_event_tree_node(&self) -> Option<&EventTreeNode> {
match &self.kind {
EventTreeNodeKind::Event(node) => Some(node),
EventTreeNodeKind::Owner(_) => None,
}
}
pub fn as_event_owner_tree_node(&self) -> Option<&EventOwnerTreeNode> {
match &self.kind {
EventTreeNodeKind::Owner(node) => Some(node),
EventTreeNodeKind::Event(_) => None,
}
}
pub fn children(&self) -> Option<FerroList<Rc<EventTreeNodeBase>>> {
self.children.borrow().clone()
}
pub fn visible_children(&self) -> Option<Rc<FerroList<Rc<EventTreeNodeBase>>>> {
let children = self.children()?;
let visible = self.visible_children.get_or_init(|| {
Rc::new(FerroList::from_items(children.to_vec().into_iter().filter(|child| child.is_visible())))
});
Some(visible.clone())
}
pub(crate) fn update_visible_children(&self) {
if let (Some(children), Some(visible)) = (self.children(), self.visible_children.get()) {
sync_visible_nodes(&children.to_vec(), visible);
}
}
pub(crate) fn register_model() {
if !ModelTypes::is_registered(std::any::TypeId::of::<EventTreeNodeBase>()) {
ModelTypes::register::<EventTreeNodeBase>(|builder| builder.notify_property_changed());
}
}
pub(crate) fn set_children(&self, value: Option<FerroList<Rc<EventTreeNodeBase>>>) {
*self.children.borrow_mut() = value;
}
pub fn is_expanded(&self) -> bool {
self.is_expanded.get()
}
pub fn set_is_expanded(&self, value: bool) {
self.base.raise_and_set_if_changed_cell(&self.is_expanded, value, "IsExpanded");
}
pub fn is_enabled(&self) -> Option<bool> {
self.is_enabled.get()
}
pub fn set_is_enabled(&self, value: Option<bool>) {
match &self.kind {
EventTreeNodeKind::Owner(_) => EventOwnerTreeNode::set_is_enabled(self, value),
EventTreeNodeKind::Event(_) => EventTreeNode::set_is_enabled(self, value),
}
}
pub(crate) fn base_set_is_enabled(&self, value: Option<bool>) {
self.base.raise_and_set_if_changed_cell(&self.is_enabled, value, "IsEnabled");
}
pub fn is_visible(&self) -> bool {
self.is_visible.get()
}
pub fn set_is_visible(&self, value: bool) {
self.base.raise_and_set_if_changed_cell(&self.is_visible, value, "IsVisible");
}
pub fn parent(&self) -> Option<Rc<EventTreeNodeBase>> {
self.parent.as_ref().and_then(Weak::upgrade)
}
pub fn text(&self) -> &str {
&self.text
}
pub(crate) fn update_checked(&self) {
let get_value = || -> Option<bool> {
let Some(children) = self.children() else {
return Some(false);
};
let mut value = Some(false);
for i in 0..children.count() {
if i == 0 {
value = children.get(i).is_enabled();
continue;
}
if value != children.get(i).is_enabled() {
value = None;
break;
}
}
value
};
self.set_is_enabled(get_value());
}
}
pub(crate) fn sync_visible_nodes(all: &[Rc<EventTreeNodeBase>], visible: &FerroList<Rc<EventTreeNodeBase>>) {
let mut index = 0;
for node in all {
let shown = index < visible.count() && Rc::ptr_eq(&visible.get(index), node);
if node.is_visible() {
if !shown {
visible.insert(index, node.clone());
}
index += 1;
} else if shown {
visible.remove_at(index);
}
}
}
ferroui_controls::ferro_markup_list!(pub EventTreeNodeList: Rc<EventTreeNodeBase>);
ferro_markup_type!(class EventTreeNodeBase {
this: Rc<EventTreeNodeBase>,
handles: [EventTreeNodeBase, Rc<EventTreeNodeBase>, Option<Rc<EventTreeNodeBase>>],
base: ViewModelBase,
properties: [
Children: Option<FerroList<Rc<EventTreeNodeBase>>> { get: EventTreeNodeBase::children },
IsExpanded: bool { get: EventTreeNodeBase::is_expanded, set: EventTreeNodeBase::set_is_expanded },
IsEnabled: Option<bool> { get: EventTreeNodeBase::is_enabled, set: EventTreeNodeBase::set_is_enabled },
IsVisible: bool { get: EventTreeNodeBase::is_visible, set: EventTreeNodeBase::set_is_visible },
Parent: Option<Rc<EventTreeNodeBase>> { get: EventTreeNodeBase::parent },
Text: String { get: |this: &Rc<EventTreeNodeBase>| this.text().to_string() },
],
notify_property_changed: EventTreeNodeBase,
});