use super::{EmbeddedControlRun, IInlineHost, Inline, InlineImpl, TextElementImpl};
use crate::Control;
use ferroui_base::media::text_formatting::TextRun;
use ferroui_base::{
ferro_class, ferro_property, instantiate, FerroObjectImpl, FerroObjectImplExt, FerroProperty,
FerroPropertyChangedEventArgs, IntoRef, Nullable, Ref, Size, StyledElement, StyledElementImpl,
StyledProperty, Visual,
};
use std::cell::{Cell, RefCell};
use std::rc::Rc;
#[repr(C)]
pub struct InlineUIContainer {
base: Inline,
measured_width: Cell<f64>,
measured_metrics: Cell<Option<(Size, f64)>>,
hosted_metrics: RefCell<Rc<Cell<Option<(Size, f64)>>>>,
}
ferro_class!(InlineUIContainer: Inline);
ferroui_base::ferro_class_info!(InlineUIContainer {
new: InlineUIContainer::new
});
ferroui_base::ferro_impl_classes!(InlineUIContainer: StyledElementImpl);
impl FerroObjectImpl for InlineUIContainer {
fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
Self::parent_on_property_changed(this, change);
if change.property() == Self::child_property().as_property() {
let (old_child, new_child) = change.get_old_and_new_value::<Option<Ref<Control>>>();
if this.child() != new_child {
return;
}
this.measured_width.set(f64::NAN);
this.measured_metrics.set(None);
this.hosted_metrics.replace(Rc::new(Cell::new(None)));
let host = this.inline_host();
if let Some(old_child) = old_child {
let old_visual_parent = old_child.visual_parent();
this.logical_children()
.remove(&old_child.clone().upcast::<StyledElement>());
if this.child().as_ref() != Some(&old_child) {
if let Some(parent) = old_visual_parent {
if old_child.visual_parent().as_ref() == Some(&parent) {
parent
.visual_children()
.remove(&old_child.clone().upcast::<Visual>());
}
}
}
}
if this.child() == new_child {
if let Some(new_child) = new_child {
let logical = new_child.clone().upcast::<StyledElement>();
if !this.logical_children().contains(&logical) {
this.logical_children().add(logical);
}
if this.child().as_ref() == Some(&new_child) {
if let Some(current_host) = this.inline_host() {
this.attach_child_to_host(&new_child, ¤t_host);
}
}
}
}
if let Some(host) = &host {
host.invalidate();
}
}
}
}
impl TextElementImpl for InlineUIContainer {
fn on_inline_host_changed(
this: &Self,
old_value: Option<&Rc<dyn IInlineHost>>,
new_value: Option<&Rc<dyn IInlineHost>>,
) {
let Some(child) = this.child() else { return };
if !this.has_inline_host(new_value) {
return;
}
if old_value.is_some() {
if let Some(parent) = child.visual_parent() {
parent
.visual_children()
.remove(&child.clone().upcast::<Visual>());
}
}
if this.child().as_ref() == Some(&child) && this.has_inline_host(new_value) {
if let Some(new_value) = new_value {
this.attach_child_to_host(&child, new_value);
}
}
}
}
impl InlineImpl for InlineUIContainer {
fn build_text_run(this: &Self, text_runs: &mut Vec<Rc<dyn TextRun>>) {
let Some(child) = this.child() else { return };
text_runs.push(Rc::new(
EmbeddedControlRun::new(child, this.create_text_run_properties())
.with_hosted_cache(this.hosted_metrics.borrow().clone()),
));
}
fn measure_embedded_controls(this: &Self, block_size: Size) -> bool {
let Some(child) = this.child() else {
return false;
};
if this.measured_width.get() != block_size.width || !child.is_measure_valid() {
child.measure(Size::new(block_size.width, f64::INFINITY));
this.measured_width.set(block_size.width);
}
let metrics = (
child.desired_size(),
EmbeddedControlRun::measured_baseline(&child),
);
this.measured_metrics.replace(Some(metrics)) != Some(metrics)
}
fn append_text(this: &Self, string_builder: &mut String) {
if this.child().is_some() {
string_builder.push('\u{FFFC}');
}
}
}
ferroui_base::ferro_properties! { impl InlineUIContainer {
ferro_property!(
pub fn child_property() -> StyledProperty<Option<Ref<Control>>> {
FerroProperty::register::<InlineUIContainer, _>("Child", None)
}
);
} }
impl InlineUIContainer {
pub fn construct() -> Self {
Self {
base: Inline::construct(),
measured_width: Cell::new(f64::NAN),
measured_metrics: Cell::new(None),
hosted_metrics: RefCell::new(Rc::new(Cell::new(None))),
}
}
fn has_inline_host(&self, expected: Option<&Rc<dyn IInlineHost>>) -> bool {
match (self.inline_host().as_ref(), expected) {
(Some(current), Some(expected)) => Rc::ptr_eq(current, expected),
(None, None) => true,
_ => false,
}
}
fn attach_child_to_host(&self, child: &Ref<Control>, host: &Rc<dyn IInlineHost>) {
host.with_visual_children(&mut |children| {
if self.child().as_ref() == Some(child)
&& self.has_inline_host(Some(host))
&& child.visual_parent().is_none()
{
children.add(child.clone().upcast::<Visual>());
}
});
}
pub fn new() -> Ref<Self> {
instantiate(Self::construct())
}
pub fn with_child(child: impl IntoRef<Control>) -> Ref<Self> {
let container = Self::new();
container.set_child(child.into_ref());
container
}
pub fn child(&self) -> Option<Ref<Control>> {
self.get_value(Self::child_property())
}
pub fn set_child(&self, value: impl Into<Nullable<Control>>) {
self.set_value(Self::child_property(), value.into().0)
}
}