raui_material/component/interactive/
text_field_paper.rs

1use crate::{
2    component::{
3        containers::paper::{PaperProps, paper},
4        text_paper::{TextPaperProps, text_paper},
5    },
6    theme::ThemedWidgetProps,
7};
8use raui_core::{
9    PropsData, Scalar, make_widget,
10    widget::{
11        component::{
12            WidgetAlpha, WidgetComponent,
13            interactive::input_field::{
14                TextInputProps, TextInputState, input_field, input_text_with_cursor,
15            },
16        },
17        context::WidgetContext,
18        node::WidgetNode,
19        unit::{
20            content::ContentBoxItemLayout,
21            text::{TextBoxHorizontalAlign, TextBoxSizeValue, TextBoxVerticalAlign},
22        },
23        utils::{Color, Rect, Transform},
24    },
25};
26use serde::{Deserialize, Serialize};
27
28#[derive(PropsData, Debug, Clone, Serialize, Deserialize)]
29#[props_data(raui_core::props::PropsData)]
30#[prefab(raui_core::Prefab)]
31pub struct TextFieldPaperProps {
32    #[serde(default)]
33    pub hint: String,
34    #[serde(default)]
35    pub width: TextBoxSizeValue,
36    #[serde(default)]
37    pub height: TextBoxSizeValue,
38    #[serde(default)]
39    pub variant: String,
40    #[serde(default)]
41    pub use_main_color: bool,
42    #[serde(default = "TextFieldPaperProps::default_inactive_alpha")]
43    pub inactive_alpha: Scalar,
44    #[serde(default)]
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub horizontal_align_override: Option<TextBoxHorizontalAlign>,
47    #[serde(default)]
48    #[serde(skip_serializing_if = "Option::is_none")]
49    pub vertical_align_override: Option<TextBoxVerticalAlign>,
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub color_override: Option<Color>,
52    #[serde(default)]
53    pub transform: Transform,
54    #[serde(default)]
55    pub paper_theme: ThemedWidgetProps,
56    #[serde(default = "TextFieldPaperProps::default_padding")]
57    pub padding: Rect,
58    #[serde(default)]
59    pub password: Option<char>,
60    #[serde(default = "TextFieldPaperProps::default_cursor")]
61    pub cursor: Option<char>,
62}
63
64impl TextFieldPaperProps {
65    fn default_inactive_alpha() -> Scalar {
66        0.75
67    }
68
69    fn default_cursor() -> Option<char> {
70        Some('|')
71    }
72
73    fn default_padding() -> Rect {
74        4.0.into()
75    }
76}
77
78impl Default for TextFieldPaperProps {
79    fn default() -> Self {
80        Self {
81            hint: Default::default(),
82            width: Default::default(),
83            height: Default::default(),
84            variant: Default::default(),
85            use_main_color: Default::default(),
86            inactive_alpha: Self::default_inactive_alpha(),
87            horizontal_align_override: Default::default(),
88            vertical_align_override: Default::default(),
89            color_override: Default::default(),
90            transform: Default::default(),
91            paper_theme: Default::default(),
92            padding: Self::default_padding(),
93            password: Default::default(),
94            cursor: Self::default_cursor(),
95        }
96    }
97}
98
99fn text_field_paper_content(context: WidgetContext) -> WidgetNode {
100    let WidgetContext { key, props, .. } = context;
101
102    let TextFieldPaperProps {
103        hint,
104        width,
105        height,
106        variant,
107        use_main_color,
108        inactive_alpha,
109        horizontal_align_override,
110        vertical_align_override,
111        color_override,
112        transform,
113        paper_theme,
114        padding,
115        password,
116        cursor,
117    } = props.read_cloned_or_default();
118    let TextInputState {
119        cursor_position,
120        focused,
121    } = props.read_cloned_or_default();
122    let text = props
123        .read::<TextInputProps>()
124        .ok()
125        .and_then(|props| props.text.as_ref())
126        .map(|text| text.get())
127        .unwrap_or_default();
128    let text = if let Some(c) = password {
129        std::iter::repeat_n(c, text.chars().count()).collect()
130    } else {
131        text
132    };
133    let text = if focused {
134        if let Some(cursor) = cursor {
135            input_text_with_cursor(&text, cursor_position, cursor)
136        } else {
137            text
138        }
139    } else if text.is_empty() {
140        hint
141    } else {
142        text
143    };
144    let paper_variant = props.map_or_default::<PaperProps, _, _>(|p| p.variant.clone());
145    let paper_props = props
146        .clone()
147        .with(PaperProps {
148            variant: paper_variant,
149            ..Default::default()
150        })
151        .with(paper_theme);
152    let text_props = props
153        .clone()
154        .with(TextPaperProps {
155            text,
156            width,
157            height,
158            variant,
159            use_main_color,
160            horizontal_align_override,
161            vertical_align_override,
162            color_override,
163            transform,
164        })
165        .with(ContentBoxItemLayout {
166            margin: padding,
167            ..Default::default()
168        });
169    let alpha = if focused { 1.0 } else { inactive_alpha };
170
171    make_widget!(paper)
172        .key(key)
173        .merge_props(paper_props)
174        .listed_slot(
175            make_widget!(text_paper)
176                .key("text")
177                .merge_props(text_props)
178                .with_shared_props(WidgetAlpha(alpha)),
179        )
180        .into()
181}
182
183pub fn text_field_paper(context: WidgetContext) -> WidgetNode {
184    text_field_paper_impl(make_widget!(input_field), context)
185}
186
187pub fn text_field_paper_impl(component: WidgetComponent, context: WidgetContext) -> WidgetNode {
188    let WidgetContext {
189        idref, key, props, ..
190    } = context;
191
192    component
193        .key(key)
194        .maybe_idref(idref.cloned())
195        .merge_props(props.clone())
196        .named_slot(
197            "content",
198            make_widget!(text_field_paper_content)
199                .key("text")
200                .merge_props(props.clone()),
201        )
202        .into()
203}