fui-rs 0.2.3

Retained-mode Rust UI SDK for EffinDOM
Documentation
use crate::controls::TextInputColors;
use crate::ffi::{CursorStyle, Unit};
use crate::node::{Border, Corners, EdgeInsets, FlexBox, PresenterHostStyle, TextNode};
use crate::theme::Theme;
use std::cell::RefCell;
use std::rc::Rc;

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct TextInputVisualState {
    pub multiline: bool,
    pub enabled: bool,
    pub wrapping: bool,
}

pub trait TextInputPresenter {
    fn bind(&self, editor_host: TextNode, placeholder_host: FlexBox);
    fn present(
        &self,
        theme: Theme,
        state: &TextInputVisualState,
        colors: Option<TextInputColors>,
    ) -> PresenterHostStyle;
}

pub trait TextInputTemplate {
    fn create(&self) -> Rc<dyn TextInputPresenter>;
}

#[derive(Clone, Default)]
pub struct DefaultTextInputPresenter {
    editor_host: RefCell<Option<TextNode>>,
    placeholder_host: RefCell<Option<FlexBox>>,
}

impl DefaultTextInputPresenter {
    pub fn new() -> Self {
        Self::default()
    }
}

impl TextInputPresenter for DefaultTextInputPresenter {
    fn bind(&self, editor_host: TextNode, placeholder_host: FlexBox) {
        *self.editor_host.borrow_mut() = Some(editor_host);
        *self.placeholder_host.borrow_mut() = Some(placeholder_host);
    }

    fn present(
        &self,
        theme: Theme,
        state: &TextInputVisualState,
        colors: Option<TextInputColors>,
    ) -> PresenterHostStyle {
        let Some(editor_host) = self.editor_host.borrow().clone() else {
            return PresenterHostStyle::new();
        };
        let Some(placeholder_host) = self.placeholder_host.borrow().clone() else {
            return PresenterHostStyle::new();
        };

        let horizontal_padding = theme.spacing.md;
        let vertical_padding = theme.spacing.sm;
        let editable_cursor = if state.enabled {
            CursorStyle::Text
        } else {
            CursorStyle::Default
        };
        let shell_cursor = if !state.multiline && state.enabled {
            CursorStyle::Text
        } else {
            CursorStyle::Default
        };
        let bg = colors
            .filter(|value| value.has_background())
            .map(|value| value.background_color())
            .unwrap_or(theme.colors.surface);
        let border_color = colors
            .filter(|value| value.has_border())
            .map(|value| value.border_color())
            .unwrap_or(theme.colors.border);

        editor_host.cursor(editable_cursor);
        placeholder_host
            .position(horizontal_padding, vertical_padding)
            .width(100.0, Unit::Percent)
            .cursor(editable_cursor);
        PresenterHostStyle::new()
            .background(bg)
            .corners(Corners::all(theme.spacing.sm))
            .border(Border::solid(1.0, border_color))
            .padding(EdgeInsets::new(
                horizontal_padding,
                vertical_padding,
                horizontal_padding,
                vertical_padding,
            ))
            .cursor(shell_cursor)
            .opacity(if state.enabled { 1.0 } else { 0.6 })
    }
}

#[derive(Clone)]
pub struct DefaultTextInputTemplate;

impl TextInputTemplate for DefaultTextInputTemplate {
    fn create(&self) -> Rc<dyn TextInputPresenter> {
        Rc::new(DefaultTextInputPresenter::new())
    }
}

pub const DEFAULT_TEXT_INPUT_TEMPLATE: DefaultTextInputTemplate = DefaultTextInputTemplate;

pub fn create_default_text_input_presenter() -> Rc<dyn TextInputPresenter> {
    DEFAULT_TEXT_INPUT_TEMPLATE.create()
}