acrylic 0.3.5

ultra-portable, web-inspired UI toolkit with SIMD graphics
Documentation
use crate::core::xml::{XmlNodeKey, XmlTagParameters, AttributeValueType};
use crate::core::event::{Handlers, DEFAULT_HANDLERS, UserInputEvent};
use crate::core::visual::{aspect_ratio, LayoutMode, Axis, Pixels};
use crate::core::glyph::{space_width, get_font, load_font_bytes};
use crate::core::app::{Application, UNBREAKABLE_MUTATOR_INDEX};
use crate::core::node::{NodeKey, Mutator, MutatorIndex};
use crate::core::text_edit::{text_edit, break_ws};
use crate::{
    DEFAULT_FONT_NAME, DEFAULT_FONT_SIZE, FALSE_STR,
    Error, error, ArcStr, ro_string, Box,
};

const TEXT: usize = 0;
const FONT: usize = 1;
const SIZE: usize = 2;
const EDITABLE: usize = 3;

pub const PARAGRAPH_MUTATOR: Mutator = Mutator {
    name: ro_string!("ParagraphMutator"),
    xml_params: Some(XmlTagParameters {
        tag_name: ro_string!("p"),
        attr_set: &[
            ("text", AttributeValueType::Other, None),
            ("font", AttributeValueType::Other, Some(DEFAULT_FONT_NAME)),
            ("size", AttributeValueType::Pixels, Some(DEFAULT_FONT_SIZE)),
            ("editable", AttributeValueType::Other, Some(FALSE_STR)),
        ],
        accepts_children: false,
    }),
    handlers: Handlers {
        populator,
        parser,
        finalizer,
        resizer,
        user_input_handler,
        ..DEFAULT_HANDLERS
    },
    storage: None,
};

pub const UNBREAKABLE_MUTATOR: Mutator = Mutator {
    name: ro_string!("UnbreakableMutator"),
    xml_params: None,
    handlers: Handlers {
        ..DEFAULT_HANDLERS
    },
    storage: None,
};

fn populator(app: &mut Application, _m: MutatorIndex, node_key: NodeKey, xml_node_key: XmlNodeKey) -> Result<(), Error> {
    let text:      ArcStr = app.attr(node_key, TEXT)?;
    let font_file: ArcStr = app.attr(node_key, FONT)?;

    let parent = app.view.parent(node_key).ok_or_else(|| error!())?;
    if app.view[parent].config.get_content_axis() != Axis::Vertical {
        let xml_node = &app.xml_tree[xml_node_key];
        let line = xml_node.line.get().unwrap_or(0.into());
        return Err(error!("Paragraph is in an horizontal container; this is invalid! (line {})", line));
    }

    match text.len() > 0 {
        true => app.request(&font_file, node_key, true),
        false => Ok(()),
    }
}

fn parser(app: &mut Application, _m: MutatorIndex, _node_key: NodeKey, asset: &ArcStr, bytes: Box<[u8]>) -> Result<(), Error> {
    load_font_bytes(app, asset, bytes)
}

fn finalizer(app: &mut Application, _m: MutatorIndex, node_key: NodeKey) -> Result<(), Error> {
    let text:      ArcStr = app.attr(node_key, TEXT)?;
    let font_file: ArcStr = app.attr(node_key, FONT)?;
    let font_size: Pixels = app.attr(node_key, SIZE)?;
    let font_size = font_size.to_num();

    if text.len() > 0 {

        let font = get_font(&mut app.mutators, &font_file).unwrap();

        for unbreakable in break_ws(&text) {
            let new_node = app.view.create();

            let width = font.quick_width(&unbreakable, font_size);
            let ratio = aspect_ratio(width, font_size);
            app.view[new_node].config.set_layout_mode(LayoutMode::AspectRatio(ratio));

            let factory = Some(UNBREAKABLE_MUTATOR_INDEX.into()).into();
            app.view[new_node].factory = factory;

            app.view.append_children(new_node, node_key);
        }

        let row = Pixels::from_num(font_size);
        let gap = Pixels::from_num(space_width(font_size));
        app.view[node_key].config.set_layout_mode(LayoutMode::Chunks(row));
        app.view[node_key].config.set_content_axis(Axis::Horizontal);
        app.view[node_key].config.set_content_gap(gap);
        app.invalidate_layout();
    }

    Ok(())
}

fn resizer(app: &mut Application, _m: MutatorIndex, node_key: NodeKey) -> Result<(), Error> {
    let font_file:       ArcStr = app.attr(node_key, FONT)?;
    let font_size:       Pixels = app.attr(node_key, SIZE)?;
    let text:            ArcStr = app.attr(node_key, TEXT)?;

    let font_size = font_size.to_num();
    let is_focused = Some(node_key) == app.get_explicit_focus();
    let inherited_style = app.get_inherited_style(node_key)?;

    if text.len() > 0 && !app.debug.skip_glyph_rendering {
        let font = match get_font(&mut app.mutators, &font_file) {
            Some(font) => font,
            None => return Ok(()),
        };

        let mut child = app.view.first_child(node_key).unwrap();
        let mut unbrk_index = 0;
        for unbreakable in break_ws(&text) {
            let cursors = match is_focused {
                true => Some((unbrk_index, app.text_cursors.as_slice())),
                false => None,
            };

            let color = Some(inherited_style.foreground);
            app.view[child].config.set_dirty(true);
            app.view[child].foreground = {
                let mut renderer = font.renderer(color, cursors, font_size);
                renderer.write(&unbreakable);
                renderer.texture()
            };

            child = app.view.next_sibling(child);
            unbrk_index += 1;
        }
    }

    Ok(())
}

fn user_input_handler(
    app: &mut Application,
    _m: MutatorIndex,
    node_key: NodeKey,
    _target: NodeKey,
    event: &UserInputEvent,
) -> Result<bool, Error> {
    let font_size: Pixels = app.attr(node_key, SIZE)?;
    text_edit(true, app, node_key, event, EDITABLE, FONT, font_size, TEXT)
}