Skip to main content

fui/controls/internal/
text_input_presenter.rs

1use crate::controls::TextInputColors;
2use crate::ffi::{CursorStyle, Unit};
3use crate::node::{Border, Corners, EdgeInsets, FlexBox, PresenterHostStyle, TextNode};
4use crate::theme::Theme;
5use std::cell::RefCell;
6use std::rc::Rc;
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9pub struct TextInputVisualState {
10    pub multiline: bool,
11    pub enabled: bool,
12    pub wrapping: bool,
13}
14
15pub trait TextInputPresenter {
16    fn bind(&self, editor_host: TextNode, placeholder_host: FlexBox);
17    fn present(
18        &self,
19        theme: Theme,
20        state: &TextInputVisualState,
21        colors: Option<TextInputColors>,
22    ) -> PresenterHostStyle;
23}
24
25pub trait TextInputTemplate {
26    fn create(&self) -> Rc<dyn TextInputPresenter>;
27}
28
29#[derive(Clone, Default)]
30pub struct DefaultTextInputPresenter {
31    editor_host: RefCell<Option<TextNode>>,
32    placeholder_host: RefCell<Option<FlexBox>>,
33}
34
35impl DefaultTextInputPresenter {
36    pub fn new() -> Self {
37        Self::default()
38    }
39}
40
41impl TextInputPresenter for DefaultTextInputPresenter {
42    fn bind(&self, editor_host: TextNode, placeholder_host: FlexBox) {
43        *self.editor_host.borrow_mut() = Some(editor_host);
44        *self.placeholder_host.borrow_mut() = Some(placeholder_host);
45    }
46
47    fn present(
48        &self,
49        theme: Theme,
50        state: &TextInputVisualState,
51        colors: Option<TextInputColors>,
52    ) -> PresenterHostStyle {
53        let Some(editor_host) = self.editor_host.borrow().clone() else {
54            return PresenterHostStyle::new();
55        };
56        let Some(placeholder_host) = self.placeholder_host.borrow().clone() else {
57            return PresenterHostStyle::new();
58        };
59
60        let horizontal_padding = theme.spacing.md;
61        let vertical_padding = theme.spacing.sm;
62        let editable_cursor = if state.enabled {
63            CursorStyle::Text
64        } else {
65            CursorStyle::Default
66        };
67        let shell_cursor = if !state.multiline && state.enabled {
68            CursorStyle::Text
69        } else {
70            CursorStyle::Default
71        };
72        let bg = colors
73            .filter(|value| value.has_background())
74            .map(|value| value.background_color())
75            .unwrap_or(theme.colors.surface);
76        let border_color = colors
77            .filter(|value| value.has_border())
78            .map(|value| value.border_color())
79            .unwrap_or(theme.colors.border);
80
81        editor_host.cursor(editable_cursor);
82        placeholder_host
83            .position(horizontal_padding, vertical_padding)
84            .width(100.0, Unit::Percent)
85            .cursor(editable_cursor);
86        PresenterHostStyle::new()
87            .background(bg)
88            .corners(Corners::all(theme.spacing.sm))
89            .border(Border::solid(1.0, border_color))
90            .padding(EdgeInsets::new(
91                horizontal_padding,
92                vertical_padding,
93                horizontal_padding,
94                vertical_padding,
95            ))
96            .cursor(shell_cursor)
97            .opacity(if state.enabled { 1.0 } else { 0.6 })
98    }
99}
100
101#[derive(Clone)]
102pub struct DefaultTextInputTemplate;
103
104impl TextInputTemplate for DefaultTextInputTemplate {
105    fn create(&self) -> Rc<dyn TextInputPresenter> {
106        Rc::new(DefaultTextInputPresenter::new())
107    }
108}
109
110pub const DEFAULT_TEXT_INPUT_TEMPLATE: DefaultTextInputTemplate = DefaultTextInputTemplate;
111
112pub fn create_default_text_input_presenter() -> Rc<dyn TextInputPresenter> {
113    DEFAULT_TEXT_INPUT_TEMPLATE.create()
114}