fui/controls/internal/
text_input_presenter.rs1use 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}