polyhorn-ios 0.4.0

Polyhorn for iOS.
Documentation
use polyhorn_core::CommandBuffer;
use polyhorn_ios_sys::coregraphics::{CGRect, CGSize};
use polyhorn_ios_sys::foundation::{NSAttributedString, NSMutableAttributedString};
use polyhorn_ios_sys::polykit::{PLYLabel, PLYView};
use polyhorn_ui::geometry::{Dimension, Size};
use polyhorn_ui::layout::MeasureFunc;
use polyhorn_ui::styles::TextStyle;
use std::sync::Arc;

use crate::prelude::*;
use crate::raw::{attributed_string, Builtin, Container, OpaqueContainer};
use crate::Key;

impl Container for PLYLabel {
    fn mount(&mut self, _child: &mut OpaqueContainer) {
        unimplemented!("PLYLabel cannot mount children.")
    }

    fn unmount(&mut self) {
        PLYLabel::to_view(self).remove_from_superview()
    }

    fn to_view(&self) -> Option<PLYView> {
        Some(PLYLabel::to_view(self))
    }
}

struct TextSegment {
    value: String,
    style: TextStyle,
}

fn collect_texts(style: &TextStyle, children: &Element) -> Vec<TextSegment> {
    fn collect_texts(style: &TextStyle, element: &Element, results: &mut Vec<TextSegment>) {
        match element {
            Element::Component(component) => {
                let opaque = &component.component;

                if let Some(text) = opaque.as_ref().as_any().downcast_ref::<Text>() {
                    collect_texts(&text.style, &component.children, results);
                }
            }
            Element::String(string) => results.push(TextSegment {
                value: string.clone(),
                style: style.clone(),
            }),
            Element::Fragment(fragment) => {
                for element in &fragment.elements {
                    collect_texts(style, element, results);
                }
            }
            _ => unimplemented!(),
        }
    }

    let mut segments = vec![];

    collect_texts(style, children, &mut segments);

    segments
}

fn transform_texts(texts: &[TextSegment]) -> NSAttributedString {
    let mut string = NSMutableAttributedString::new();

    for text in texts {
        string.append_attributed_string(&attributed_string(&text.value, &text.style));
    }

    string.into()
}

impl Component for Text {
    fn render(&self, manager: &mut Manager) -> Element {
        let label_ref = use_reference!(manager, None);

        let texts = collect_texts(&self.style, &manager.children());
        let attributed_string = transform_texts(&texts);

        use_layout_effect!(manager, move |link, buffer| {
            let id = match label_ref.apply(link, |label| label.to_owned()) {
                Some(id) => id,
                None => return,
            };

            buffer.mutate(&[id], move |containers, _| {
                let container = &mut containers[0];

                let layout = match container.layout() {
                    Some(layout) => layout.clone(),
                    None => return,
                };

                if let Some(view) = container.downcast_mut::<PLYLabel>() {
                    let attributed_string = transform_texts(&texts);

                    view.set_attributed_text(&attributed_string);

                    view.to_view().set_layout(move || {
                        let current = layout.current();

                        CGRect::new(
                            current.origin.x as _,
                            current.origin.y as _,
                            current.size.width as _,
                            current.size.height as _,
                        )
                    });
                }
            });
        });

        Element::builtin(
            Key::new(()),
            Builtin::Label(MeasureFunc::Boxed(Arc::new(move |size| {
                let min_size = CGSize {
                    width: match size.width {
                        Dimension::Points(width) => width as _,
                        _ => 0.0,
                    },
                    height: match size.height {
                        Dimension::Points(height) => height as _,
                        _ => 0.0,
                    },
                };

                let target = attributed_string.bounding_rect_with_size(min_size).size;

                let result = Size {
                    width: target.width.ceil() as _,
                    height: target.height.ceil() as _,
                };

                result
            }))),
            Element::fragment(Key::new(()), vec![]),
            Some(label_ref.weak(manager)),
        )
    }
}