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, Default)]
93pub enum KeyboardType {
94 #[default]
95 Text,
96 Ascii,
97 Number,
98 Phone,
99 Email,
100 Uri,
101 Decimal,
102}
103
104#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
106pub enum ImeAction {
107 #[default]
108 Unspecified,
109 None,
110 Go,
111 Search,
112 Send,
113 Next,
114 Done,
115 Previous,
116}
117
118#[derive(Debug, Clone, Copy, PartialEq)]
119pub struct SpanStyle {
120 pub color: Option<Color>,
121 pub font_size: Option<f32>,
122}
123
124impl SpanStyle {
125 pub const fn default() -> Self {
126 Self {
127 color: None,
128 font_size: None,
129 }
130 }
131
132 pub fn color(mut self, c: Color) -> Self {
133 self.color = Some(c);
134 self
135 }
136
137 pub fn font_size(mut self, px: f32) -> Self {
138 self.font_size = Some(px);
139 self
140 }
141}
142
143impl Default for SpanStyle {
144 fn default() -> Self {
145 Self::default()
146 }
147}
148
149#[derive(Debug, Clone, PartialEq)]
151pub struct TextSpan {
152 pub start: usize,
154 pub end: usize,
156 pub style: SpanStyle,
157}
158
159#[derive(Debug, Clone)]
163pub struct AnnotatedString {
164 pub text: String,
165 pub spans: Arc<[TextSpan]>,
166}
167
168impl AnnotatedString {
169 pub fn new(text: impl Into<String>, spans: Vec<TextSpan>) -> Self {
170 let text = text.into();
171 Self {
172 text,
173 spans: spans.into(),
174 }
175 }
176
177 pub fn as_str(&self) -> &str {
178 &self.text
179 }
180}
181
182impl From<String> for AnnotatedString {
183 fn from(text: String) -> Self {
184 Self {
185 text,
186 spans: Arc::from([]),
187 }
188 }
189}
190
191impl From<&str> for AnnotatedString {
192 fn from(text: &str) -> Self {
193 Self {
194 text: text.to_string(),
195 spans: Arc::from([]),
196 }
197 }
198}
199
200#[derive(Default)]
202pub struct AnnotatedStringBuilder {
203 text: String,
204 spans: Vec<TextSpan>,
205}
206
207impl AnnotatedStringBuilder {
208 pub fn new() -> Self {
209 Self::default()
210 }
211
212 pub fn push(&mut self, text: &str) -> &mut Self {
214 self.text.push_str(text);
215 self
216 }
217
218 pub fn push_with_style(&mut self, text: &str, style: SpanStyle) -> &mut Self {
220 let start = self.text.len();
221 self.text.push_str(text);
222 let end = self.text.len();
223 if start < end {
224 self.spans.push(TextSpan { start, end, style });
225 }
226 self
227 }
228
229 pub fn push_color(&mut self, text: &str, color: Color) -> &mut Self {
231 self.push_with_style(text, SpanStyle::default().color(color))
232 }
233
234 pub fn add_style(&mut self, start: usize, end: usize, style: SpanStyle) -> &mut Self {
236 if start < end && end <= self.text.len() {
237 self.spans.push(TextSpan { start, end, style });
238 }
239 self
240 }
241
242 pub fn build(&mut self) -> AnnotatedString {
243 let text = std::mem::take(&mut self.text);
244 self.spans.sort_by_key(|s| s.start);
245 let mut merged: Vec<TextSpan> = Vec::new();
247 for span in std::mem::take(&mut self.spans) {
248 if let Some(last) = merged.last_mut()
249 && last.end == span.start
250 && last.style == span.style
251 {
252 last.end = span.end;
253 continue;
254 }
255 merged.push(span);
256 }
257 AnnotatedString {
258 text,
259 spans: merged.into(),
260 }
261 }
262}
263
264pub fn build_annotated_string(b: impl FnOnce(&mut AnnotatedStringBuilder)) -> AnnotatedString {
266 let mut builder = AnnotatedStringBuilder::new();
267 b(&mut builder);
268 builder.build()
269}