famiq 0.3.1

Experimental GUI library, powered by Bevy engine.
Documentation
pub mod components;
pub mod styling;
pub mod systems;
pub mod tests;

use famiq_macros::set_widget_attributes;
use crate::widgets::text::base_text::*;
use crate::widgets::container::base_container::*;
use crate::utils::*;
use crate::widgets::*;
use crate::event_writer::*;
use crate::plugin::{CursorType, CursorIcons};
use bevy::ui::FocusPolicy;
use bevy::prelude::*;

pub(crate) use components::*;
pub(crate) use styling::*;
pub(crate) use systems::*;

#[set_widget_attributes]
#[derive(Clone, Debug)]
pub struct SelectionBuilder {
    pub all_reactive_keys: Vec<String>,
    pub placeholder: String,
    pub choices: Vec<String>,
    pub panel_entity: Option<Entity>
}

impl SelectionBuilder {
    pub fn new(placeholder: String, font_handle: &Handle<Font>) -> Self {
        let mut attributes = WidgetAttributes::default();
        attributes.font_handle = Some(font_handle.clone());
        Self {
            attributes,
            cloned_attrs: WidgetAttributes::default(),
            all_reactive_keys: Vec::new(),
            placeholder,
            choices: Vec::new(),
            panel_entity: None
        }
    }

    fn handle_placeholder_val(&mut self, r_data: &HashMap<String, RVal>) -> String {
        let reactive_keys = get_reactive_key(&self.placeholder);
        let parsed_text = replace_reactive_keys(&self.placeholder, &reactive_keys, r_data);
        self.all_reactive_keys.extend_from_slice(&reactive_keys);
        parsed_text
    }

    pub fn build_placeholder(&mut self, commands: &mut Commands, r_data: &HashMap<String, RVal>) -> (Entity, String) {
        let parsed_text = self.handle_placeholder_val(r_data);
        let mut ph = FaBaseText::new_with_attributes(&parsed_text, &self.cloned_attrs);
        ph.layout = TextLayout::new(JustifyText::Left, LineBreak::NoWrap);

        let ph_entity = ph.build(r_data, commands);
        commands.entity(ph_entity).insert(SelectorPlaceHolder);
        insert_class_id(commands, ph_entity, &self.cloned_attrs.id, &self.cloned_attrs.class);

        (ph_entity, parsed_text)
    }

    pub fn build_arrow(&self, commands: &mut Commands, r_data: &HashMap<String, RVal>) -> Entity {
        let mut arrow = FaBaseText::new_with_attributes("", &self.cloned_attrs);
        arrow.layout = TextLayout::new_with_justify(JustifyText::Right);

        let arrow_entity = arrow.build(r_data, commands);
        commands.entity(arrow_entity).insert(ArrowIcon);
        insert_class_id(commands, arrow_entity, &self.cloned_attrs.id, &self.cloned_attrs.class);
        arrow_entity
    }

    pub fn build_choice_text(&self, text: &str, commands: &mut Commands, r_data: &HashMap<String, RVal>) -> Entity {
        let mut choice_text = FaBaseText::new_with_attributes(text, &self.cloned_attrs);
        choice_text.build(r_data, commands)
    }

    fn choice_wrapper_components(&self, text: Entity, selector: Entity) -> impl Bundle {
        (IsFamiqSelectionChoice, SelectionChoiceTextEntity(text), SelectorEntity(selector))
    }

    pub fn build_choice_wrapper(
        &self,
        text_entity: Entity,
        selector_entity: Entity,
        commands: &mut Commands,
        r_data: &HashMap<String, RVal>
    ) -> Entity {
        let mut wrapper = FaBaseContainer::new();
        wrapper.cloned_attrs.node = default_choice_container_node();

        let wrapper_entity = wrapper.build(r_data, commands);
        commands
            .entity(wrapper_entity)
            .add_child(text_entity)
            .insert(self.choice_wrapper_components(text_entity, selector_entity));
        wrapper_entity
    }

    fn choice_panel_components(&self) -> impl Bundle {
        (
            IsFamiqSelectionChoicesPanel,
            FocusPolicy::Block,
            GlobalZIndex(2),
            Transform::default()
        )
    }

    pub fn build_choices_panel(
        &mut self,
        selector_entity: Entity,
        commands: &mut Commands,
        r_data: &HashMap<String, RVal>,
    ) -> Entity {
        let mut choice_entities: Vec<Entity> = Vec::new();
        self.choices.insert(0, "-/-".into());

        self.choices.iter().for_each(|choice| {
            let choice_text = self.build_choice_text(choice, commands, r_data);
            let choice_wrapper = self.build_choice_wrapper(choice_text, selector_entity, commands, r_data);
            choice_entities.push(choice_wrapper);
        });

        let mut panel = FaBaseContainer::new();
        panel.cloned_attrs.node = default_selection_choices_panel_node();
        panel.cloned_attrs.color = self.cloned_attrs.color.clone();
        panel.cloned_attrs.id = self.cloned_attrs.id.clone();
        panel.cloned_attrs.class = self.cloned_attrs.class.clone();

        let panel_entity = panel.build(r_data, commands);
        commands
            .entity(panel_entity)
            .add_children(&choice_entities)
            .remove::<DefaultWidgetConfig>()
            .insert(self.choice_panel_components());

        self.panel_entity = Some(panel_entity);
        panel_entity
    }

    pub fn rebuild_choices_panel(
        &mut self,
        r_data: &HashMap<String, RVal>,
        world: &mut World
    ) {
        let mut panel = FaBaseContainer::new();
        panel.cloned_attrs.node = default_selection_choices_panel_node();
        panel.cloned_attrs.color = self.cloned_attrs.color.clone();
        panel.cloned_attrs.id = self.cloned_attrs.id.clone();
        panel.cloned_attrs.class = self.cloned_attrs.class.clone();
        panel.rebuild(r_data, self.panel_entity.unwrap(), world);
    }

    pub(crate) fn prepare_attrs(&mut self, r_data: &HashMap<String, RVal>) {
        self.cloned_attrs = self.attributes.clone();
        self.cloned_attrs.node = default_selector_node();
        self.cloned_attrs.default_visibility = Visibility::Visible;
        replace_reactive_keys_common_attrs(&mut self.cloned_attrs, r_data, &mut self.all_reactive_keys);
    }

    pub(crate) fn arrow_up(text_q: &mut Query<&mut Text, With<ArrowIcon>>, arrow_entity: Entity) {
        if let Ok(mut text) = text_q.get_mut(arrow_entity) {
            text.0 = "".to_string()
        }
    }

    pub(crate) fn arrow_down(text_q: &mut Query<&mut Text, With<ArrowIcon>>, arrow_entity: Entity) {
        if let Ok(mut text) = text_q.get_mut(arrow_entity) {
            text.0 = "".to_string()
        }
    }
}

impl SetupWidget for SelectionBuilder {
    fn components(&mut self) -> impl Bundle {
        (
            MainWidget,
            IsFamiqSelectionSelector,
            ReactiveWidget,
            SelectionValue::default(),
        )
    }

    fn build(&mut self, r_data: &HashMap<String, RVal>, commands: &mut Commands) -> Entity {
        self.prepare_attrs(r_data);
        let mut selector = FaBaseContainer::new_with_attributes(&self.cloned_attrs);
        let selector_entity = selector.build(r_data, commands);
        let (ph_entity, ph_text) = self.build_placeholder(commands, r_data);
        let arrow_entity = self.build_arrow(commands, r_data);
        let panel_entity = self.build_choices_panel(selector_entity, commands, r_data);

        commands.entity(ph_entity).insert(SelectorEntity(selector_entity));

        commands
            .entity(selector_entity)
            .add_children(&[ph_entity, arrow_entity, panel_entity])
            .insert((
                self.components(),
                SelectionChoicesPanelEntity(panel_entity),
                SelectorPlaceHolderEntity(ph_entity),
                SelectorArrowIconEntity(arrow_entity),
                SelectorPlaceholderText(ph_text)
            ))
            .observe(on_mouse_up)
            .observe(on_mouse_down)
            .observe(on_mouse_over)
            .observe(on_mouse_out);

        if self.attributes.has_tooltip {
            build_tooltip_node(&self.cloned_attrs, commands, selector_entity);
        }
        insert_class_id(commands, selector_entity, &self.cloned_attrs.id, &self.cloned_attrs.class);
        insert_model(commands, selector_entity, &self.cloned_attrs.model_key);

        let cloned_builder = self.clone();
        let ar_keys = self.all_reactive_keys.clone();
        commands.queue(move |w: &mut World| {
            w.send_event(UpdateReactiveSubscriberEvent::new(
                ar_keys,
                selector_entity,
                WidgetBuilder {
                    builder: BuilderType::Selection(cloned_builder)
                }
            ));
        });
        self.all_reactive_keys.clear();
        selector_entity
    }

    fn rebuild(&mut self, r_data: &HashMap<String, RVal>, old_entity: Entity, world: &mut World) {
        self.prepare_attrs(r_data);
        let mut selector = FaBaseContainer::new_with_attributes(&self.cloned_attrs);
        selector.rebuild(r_data, old_entity, world);
        self.rebuild_choices_panel(r_data, world);

        insert_class_id_world(world, old_entity, &self.cloned_attrs.id, &self.cloned_attrs.class);

        let cloned_builder = self.clone();
        let ar_keys = self.all_reactive_keys.clone();
        world.send_event(UpdateReactiveSubscriberEvent::new(
            ar_keys,
            old_entity,
            WidgetBuilder {
                builder: BuilderType::Selection(cloned_builder)
            }
        ));
        self.all_reactive_keys.clear();
    }
}

/// Macro for creating a single choice selector.
#[macro_export]
macro_rules! selection {
    ( placeholder: $placeholder:expr $(, $key:ident : $value:tt )* $(,)? ) => {{
        let famiq_builder = builder_mut();
        let s_builder = &mut SelectionBuilder::new($placeholder.to_string(), &famiq_builder.get_font_handle());
        $(
            $crate::selection_attributes!(s_builder, $key : $value);
        )*
        s_builder.build(
            &famiq_builder.reactive_data.data,
            &mut famiq_builder.ui_root_node.commands()
        )
    }};
}

#[macro_export]
macro_rules! selection_attributes {
    ($s_builder:ident, choices: $choices:expr) => {{
        $s_builder.choices = $choices.iter().map(|s| s.to_string()).collect();
    }};
    ($s_builder:ident, model: $model:expr) => {{
        $s_builder.set_model($model);
    }};
    ($s_builder:ident, $key:ident : $value:expr) => {{
        $crate::common_attributes!($s_builder, $key : $value);
    }};
}

pub fn can_run_selection_systems(selection_q: Query<&IsFamiqSelectionSelector>) -> bool {
    !selection_q.is_empty()
}