use super::{IInlineHost, Inline, InlineUIContainer, Run};
use crate::Control;
use ferroui_base::collections::{FerroList, NotifyCollectionChangedAction, NotifyCollectionChangedEventArgs, ResetBehavior};
use ferroui_base::reactive::{Disposable, IDisposable};
use ferroui_base::utilities::HandlerList;
use ferroui_base::{IntoRef, Ref, StyledElement, WeakRef};
use std::cell::RefCell;
use std::ops::Deref;
use std::rc::{Rc, Weak};
#[derive(Clone)]
pub struct InlineCollection(Rc<InlineCollectionData>);
struct InlineCollectionData {
list: FerroList<Ref<Inline>>,
logical_children: RefCell<Option<WeakRef<StyledElement>>>,
inline_host: RefCell<Option<Rc<dyn IInlineHost>>>,
invalidated: Rc<HandlerList<dyn Fn()>>,
}
impl PartialEq for InlineCollection {
fn eq(&self, other: &Self) -> bool {
Rc::ptr_eq(&self.0, &other.0)
}
}
impl std::fmt::Debug for InlineCollection {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "InlineCollection({})", self.0.list.count())
}
}
impl Deref for InlineCollection {
type Target = FerroList<Ref<Inline>>;
fn deref(&self) -> &Self::Target {
&self.0.list
}
}
impl InlineCollection {
pub fn new() -> Self {
let list = FerroList::new();
list.set_reset_behavior(ResetBehavior::Remove);
let collection = Self(Rc::new(InlineCollectionData {
list,
logical_children: RefCell::new(None),
inline_host: RefCell::new(None),
invalidated: Rc::new(HandlerList::new()),
}));
let weak: Weak<InlineCollectionData> = Rc::downgrade(&collection.0);
collection.0.list.add_collection_changed(Rc::new(
move |e: &NotifyCollectionChangedEventArgs<'_, Ref<Inline>>| {
let Some(this) = weak.upgrade().map(InlineCollection) else { return };
match e.action {
NotifyCollectionChangedAction::Add => {
for inline in e.new_items {
this.on_add(inline);
}
}
NotifyCollectionChangedAction::Remove => {
for inline in e.old_items {
this.on_remove(inline);
}
}
NotifyCollectionChangedAction::Replace => {
for inline in e.old_items {
this.on_remove(inline);
}
for inline in e.new_items {
this.on_add(inline);
}
}
NotifyCollectionChangedAction::Move => {}
NotifyCollectionChangedAction::Reset => panic!("Resetting the collection is not supported."),
}
},
));
collection
}
pub(crate) fn logical_children(&self) -> Option<Ref<StyledElement>> {
self.0.logical_children.borrow().as_ref().and_then(WeakRef::upgrade)
}
pub(crate) fn set_logical_children(&self, value: Option<&StyledElement>) {
let old_value = self.logical_children();
let new_value = value.map(StyledElement::to_ref);
*self.0.logical_children.borrow_mut() = new_value.as_ref().map(Ref::downgrade);
self.on_parent_changed(old_value.as_ref(), new_value.as_ref());
}
pub(crate) fn inline_host(&self) -> Option<Rc<dyn IInlineHost>> {
self.0.inline_host.borrow().clone()
}
pub(crate) fn set_inline_host(&self, value: Option<Rc<dyn IInlineHost>>) {
*self.0.inline_host.borrow_mut() = value.clone();
self.on_inline_host_changed(value);
}
pub fn text(&self) -> Option<String> {
if self.0.list.count() == 0 {
return None;
}
let mut builder = String::new();
for inline in self.0.list.snapshot().iter() {
inline.append_text(&mut builder);
}
Some(builder)
}
pub fn add(&self, inline: impl IntoRef<Inline>) {
if let Some(text_block) = self.inline_host().and_then(|host| host.as_text_block()) {
if let Some(text) = text_block.text().filter(|text| !text.is_empty()) {
self.0.list.add(Run::with_text(Some(&text)).upcast());
text_block.clear_text_internal();
}
}
self.0.list.add(inline.into_ref());
}
pub fn add_text(&self, text: &str) {
match self.inline_host().and_then(|host| host.as_text_block()) {
Some(text_block) if !text_block.has_complex_content() => {
let mut current = text_block.text().unwrap_or_default();
current.push_str(text);
text_block.set_text(Some(¤t));
}
_ => self.add(Run::with_text(Some(text))),
}
}
pub fn add_control(&self, control: impl IntoRef<Control>) {
self.add(InlineUIContainer::with_child(control));
}
pub fn invalidated(&self, handler: impl Fn() + 'static) -> Rc<dyn IDisposable> {
let token = self.0.invalidated.add(Rc::new(handler));
let handlers = Rc::downgrade(&self.0.invalidated);
Disposable::create(move || {
if let Some(handlers) = handlers.upgrade() {
handlers.remove(token);
}
})
}
fn invalidate(&self) {
if let Some(host) = self.inline_host() {
host.invalidate();
}
for (_, handler) in self.0.invalidated.snapshot().iter() {
handler();
}
}
fn on_parent_changed(&self, old_value: Option<&Ref<StyledElement>>, new_value: Option<&Ref<StyledElement>>) {
if old_value != new_value {
for child in self.0.list.snapshot().iter() {
let child: Ref<StyledElement> = child.clone().upcast();
if let Some(old_value) = old_value {
old_value.logical_children().remove(&child);
}
if let Some(new_value) = new_value {
new_value.logical_children().add(child);
}
}
}
self.invalidate();
}
fn on_inline_host_changed(&self, new_value: Option<Rc<dyn IInlineHost>>) {
for child in self.0.list.snapshot().iter() {
child.set_inline_host(new_value.clone());
}
self.invalidate();
}
fn on_add(&self, inline: &Ref<Inline>) {
inline.set_inline_host(self.inline_host());
if let Some(logical_children) = self.logical_children() {
logical_children.logical_children().add(inline.clone().upcast());
}
self.invalidate();
}
fn on_remove(&self, inline: &Ref<Inline>) {
if let Some(logical_children) = self.logical_children() {
logical_children.logical_children().remove(&inline.clone().upcast());
}
inline.set_inline_host(None);
self.invalidate();
}
}