fui-rs 0.2.3-alpha1

Retained-mode Rust UI SDK for EffinDOM
Documentation
use super::Button;
use crate::event;
use crate::ffi::{FlexDirection, KeyEventType, SemanticRole};
use crate::node::{flex_box, FlexBox, Node, NodeRef, ThemeBindable};
use std::any::Any;
use std::cell::{Cell, RefCell};
use std::rc::Rc;

#[derive(Clone)]
pub struct Form {
    shared: Rc<FormShared>,
}

struct FormShared {
    root: FlexBox,
    default_button: RefCell<Option<Button>>,
    cancel_button: RefCell<Option<Button>>,
    key_filter_token: Cell<u32>,
    armed_key: RefCell<Option<String>>,
    armed_button: RefCell<Option<Button>>,
}

impl Default for Form {
    fn default() -> Self {
        Self::new()
    }
}

impl Form {
    pub fn new() -> Self {
        let root = flex_box();
        root.flex_direction(FlexDirection::Column)
            .semantic_role(SemanticRole::Form);
        let form = Self {
            shared: Rc::new(FormShared {
                root,
                default_button: RefCell::new(None),
                cancel_button: RefCell::new(None),
                key_filter_token: Cell::new(0),
                armed_key: RefCell::new(None),
                armed_button: RefCell::new(None),
            }),
        };
        form.bind_events();
        form
    }

    pub fn default_btn(&self, button: &Button) -> &Self {
        self.shared
            .default_button
            .borrow_mut()
            .replace(button.clone());
        self
    }

    pub fn cancel_btn(&self, button: &Button) -> &Self {
        self.shared
            .cancel_button
            .borrow_mut()
            .replace(button.clone());
        self
    }

    pub fn activate(&self) {
        if self.shared.key_filter_token.get() != 0
            || (self.shared.default_button.borrow().is_none()
                && self.shared.cancel_button.borrow().is_none())
        {
            return;
        }
        let weak = Rc::downgrade(&self.shared);
        let token = event::push_key_filter(move |event_type, key, modifiers| {
            let Some(shared) = weak.upgrade() else {
                return false;
            };
            shared.handle_global_key_event(event_type, key, modifiers)
        });
        self.shared.key_filter_token.set(token);
    }

    pub fn deactivate(&self) {
        self.shared.deactivate();
    }

    fn bind_events(&self) {
        let weak = Rc::downgrade(&self.shared);
        self.shared.root.on_key_down(move |event| {
            let Some(shared) = weak.upgrade() else {
                return;
            };
            if shared.handle_global_key_event(
                KeyEventType::Down,
                event.key.as_str(),
                event.modifiers,
            ) {
                event.handled = true;
            }
        });
        let weak = Rc::downgrade(&self.shared);
        self.shared.root.on_key_up(move |event| {
            let Some(shared) = weak.upgrade() else {
                return;
            };
            if shared.handle_global_key_event(KeyEventType::Up, event.key.as_str(), event.modifiers)
            {
                event.handled = true;
            }
        });
    }
}

impl Drop for Form {
    fn drop(&mut self) {
        if Rc::strong_count(&self.shared) == 1 {
            self.shared.deactivate();
        }
    }
}

impl FormShared {
    fn deactivate(&self) {
        self.cancel_armed_button();
        let token = self.key_filter_token.replace(0);
        if token != 0 {
            event::remove_key_filter(token);
        }
    }

    fn handle_global_key_event(&self, event_type: KeyEventType, key: &str, modifiers: u32) -> bool {
        if key == "Enter" && self.should_defer_enter_to_focused_button(modifiers) {
            return false;
        }
        match event_type {
            KeyEventType::Down => self.handle_key_down(key),
            KeyEventType::Up => self.handle_key_up(key),
        }
    }

    fn handle_key_down(&self, key: &str) -> bool {
        let Some(button) = self.resolve_button_for_key(key) else {
            return false;
        };
        if let Some(armed_key) = self.armed_key.borrow().as_ref() {
            return armed_key == key;
        }
        self.armed_key.borrow_mut().replace(key.to_string());
        self.armed_button.borrow_mut().replace(button.clone());
        button.begin_press();
        true
    }

    fn handle_key_up(&self, key: &str) -> bool {
        let armed_matches = self
            .armed_key
            .borrow()
            .as_ref()
            .is_some_and(|armed_key| armed_key == key);
        if !armed_matches {
            return false;
        }
        let button = self.armed_button.borrow_mut().take();
        self.armed_key.borrow_mut().take();
        if let Some(button) = button {
            button.end_press(true);
        }
        true
    }

    fn cancel_armed_button(&self) {
        let button = self.armed_button.borrow_mut().take();
        self.armed_key.borrow_mut().take();
        if let Some(button) = button {
            button.cancel_press();
        }
    }

    fn resolve_button_for_key(&self, key: &str) -> Option<Button> {
        match key {
            "Enter" => self.default_button.borrow().clone(),
            "Escape" => self.cancel_button.borrow().clone(),
            _ => None,
        }
    }

    fn should_defer_enter_to_focused_button(&self, modifiers: u32) -> bool {
        if modifiers != 0 {
            return false;
        }
        event::focused_node_is_enabled_button()
    }
}

impl Node for Form {
    fn retained_node_ref(&self) -> NodeRef {
        self.shared.root.retained_node_ref()
    }

    fn retained_owner_attachment(&self) -> Option<Rc<dyn Any>> {
        Some(self.shared.clone())
    }

    fn build_self(&self) {
        self.shared.root.build_self();
    }

    fn dispose(&self) {
        self.deactivate();
        self.shared.root.dispose();
    }
}

impl crate::node::HasFlexBoxRoot for Form {
    fn flex_box_root(&self) -> &FlexBox {
        &self.shared.root
    }
}

impl ThemeBindable for Form {
    fn theme_binding_node(&self) -> NodeRef {
        self.shared.root.retained_node_ref()
    }

    fn weak_theme_target(&self) -> Box<dyn Fn() -> Option<Self>> {
        let shared = Rc::downgrade(&self.shared);
        Box::new(move || {
            Some(Self {
                shared: shared.upgrade()?,
            })
        })
    }
}