raui_material/component/interactive/
text_field_paper.rs1use 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}