1use crate::Color;
2use std::rc::Rc;
3use std::sync::Arc;
4
5pub trait VisualTransformation: std::fmt::Debug + Send + Sync + 'static {
8 fn filter(&self, text: &str) -> TransformedText;
12}
13
14pub struct TransformedText {
16 pub text: String,
18 pub offset_map: Rc<dyn Fn(usize) -> usize>,
20}
21
22impl Clone for TransformedText {
23 fn clone(&self) -> Self {
24 Self {
25 text: self.text.clone(),
26 offset_map: self.offset_map.clone(),
27 }
28 }
29}
30
31impl std::fmt::Debug for TransformedText {
32 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33 f.debug_struct("TransformedText")
34 .field("text", &self.text)
35 .finish()
36 }
37}
38
39#[derive(Clone, Copy, Debug)]
41pub struct NoVisualTransformation;
42
43impl VisualTransformation for NoVisualTransformation {
44 fn filter(&self, text: &str) -> TransformedText {
45 let len = text.len();
46 TransformedText {
47 text: text.to_string(),
48 offset_map: Rc::new(move |offset| offset.min(len)),
49 }
50 }
51}
52
53#[derive(Clone, Copy, Debug)]
55pub struct PasswordVisualTransformation {
56 pub mask_char: char,
58}
59
60impl Default for PasswordVisualTransformation {
61 fn default() -> Self {
62 Self { mask_char: '*' }
63 }
64}
65
66impl VisualTransformation for PasswordVisualTransformation {
67 fn filter(&self, text: &str) -> TransformedText {
68 let masked: String = text.chars().map(|_| self.mask_char).collect();
69 let len = text.len();
70 TransformedText {
71 text: masked,
72 offset_map: Rc::new(move |offset| offset.min(len)),
73 }
74 }
75}
76
77pub fn original_offset_to_display(original: &str, display: &str, original_byte: usize) -> usize {
81 let char_idx = original[..original_byte.min(original.len())]
82 .chars()
83 .count();
84 display
85 .char_indices()
86 .nth(char_idx)
87 .map(|(i, _)| i)
88 .unwrap_or(display.len())
89}
90
91#[derive(Clone, Copy, Debug, PartialEq, Eq)]
93#[derive(Default)]
94pub enum KeyboardType {
95 #[default]
96 Text,
97 Ascii,
98 Number,
99 Phone,
100 Email,
101 Uri,
102 Decimal,
103}
104
105
106#[derive(Clone, Copy, Debug, PartialEq, Eq)]
108#[derive(Default)]
109pub enum ImeAction {
110 #[default]
111 Unspecified,
112 None,
113 Go,
114 Search,
115 Send,
116 Next,
117 Done,
118 Previous,
119}
120
121
122#[derive(Debug, Clone, Copy, PartialEq)]
123pub struct SpanStyle {
124 pub color: Option<Color>,
125 pub font_size: Option<f32>,
126}
127
128impl SpanStyle {
129 pub const fn default() -> Self {
130 Self {
131 color: None,
132 font_size: None,
133 }
134 }
135
136 pub fn color(mut self, c: Color) -> Self {
137 self.color = Some(c);
138 self
139 }
140
141 pub fn font_size(mut self, px: f32) -> Self {
142 self.font_size = Some(px);
143 self
144 }
145}
146
147impl Default for SpanStyle {
148 fn default() -> Self {
149 Self::default()
150 }
151}
152
153#[derive(Debug, Clone, PartialEq)]
155pub struct TextSpan {
156 pub start: usize,
158 pub end: usize,
160 pub style: SpanStyle,
161}
162
163#[derive(Debug, Clone)]
167pub struct AnnotatedString {
168 pub text: String,
169 pub spans: Arc<[TextSpan]>,
170}
171
172impl AnnotatedString {
173 pub fn new(text: impl Into<String>, spans: Vec<TextSpan>) -> Self {
174 let text = text.into();
175 Self {
176 text,
177 spans: spans.into(),
178 }
179 }
180
181 pub fn as_str(&self) -> &str {
182 &self.text
183 }
184}
185
186impl From<String> for AnnotatedString {
187 fn from(text: String) -> Self {
188 Self {
189 text,
190 spans: Arc::from([]),
191 }
192 }
193}
194
195impl From<&str> for AnnotatedString {
196 fn from(text: &str) -> Self {
197 Self {
198 text: text.to_string(),
199 spans: Arc::from([]),
200 }
201 }
202}
203
204#[derive(Default)]
206pub struct AnnotatedStringBuilder {
207 text: String,
208 spans: Vec<TextSpan>,
209}
210
211impl AnnotatedStringBuilder {
212 pub fn new() -> Self {
213 Self::default()
214 }
215
216 pub fn push(&mut self, text: &str) -> &mut Self {
218 self.text.push_str(text);
219 self
220 }
221
222 pub fn push_with_style(&mut self, text: &str, style: SpanStyle) -> &mut Self {
224 let start = self.text.len();
225 self.text.push_str(text);
226 let end = self.text.len();
227 if start < end {
228 self.spans.push(TextSpan { start, end, style });
229 }
230 self
231 }
232
233 pub fn push_color(&mut self, text: &str, color: Color) -> &mut Self {
235 self.push_with_style(text, SpanStyle::default().color(color))
236 }
237
238 pub fn add_style(&mut self, start: usize, end: usize, style: SpanStyle) -> &mut Self {
240 if start < end && end <= self.text.len() {
241 self.spans.push(TextSpan { start, end, style });
242 }
243 self
244 }
245
246 pub fn build(&mut self) -> AnnotatedString {
247 let text = std::mem::take(&mut self.text);
248 self.spans.sort_by_key(|s| s.start);
249 let mut merged: Vec<TextSpan> = Vec::new();
251 for span in std::mem::take(&mut self.spans) {
252 if let Some(last) = merged.last_mut()
253 && last.end == span.start && last.style == span.style {
254 last.end = span.end;
255 continue;
256 }
257 merged.push(span);
258 }
259 AnnotatedString {
260 text,
261 spans: merged.into(),
262 }
263 }
264}
265
266pub fn build_annotated_string(b: impl FnOnce(&mut AnnotatedStringBuilder)) -> AnnotatedString {
268 let mut builder = AnnotatedStringBuilder::new();
269 b(&mut builder);
270 builder.build()
271}