1use crate::Color;
2use std::fmt::Debug;
3use std::rc::Rc;
4use std::sync::Arc;
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
11pub struct TextRange {
12 pub start: usize,
13 pub end: usize,
14}
15
16impl TextRange {
17 pub const ZERO: TextRange = TextRange { start: 0, end: 0 };
18
19 pub fn new(start: usize, end: usize) -> Self {
20 Self { start, end }
21 }
22
23 pub fn collapsed(at: usize) -> Self {
24 Self { start: at, end: at }
25 }
26
27 pub fn min(self) -> usize {
28 self.start.min(self.end)
29 }
30
31 pub fn max(self) -> usize {
32 self.start.max(self.end)
33 }
34
35 pub fn is_collapsed(self) -> bool {
36 self.start == self.end
37 }
38
39 pub fn reversed(self) -> bool {
40 self.start > self.end
41 }
42
43 pub fn length(self) -> usize {
44 self.max() - self.min()
45 }
46
47 pub fn intersects(self, other: TextRange) -> bool {
48 self.min() < other.max() && other.min() < self.max()
49 }
50
51 pub fn contains(self, offset: usize) -> bool {
52 self.min() <= offset && offset < self.max()
53 }
54
55 pub fn coerce_in(self, min: usize, max: usize) -> Self {
56 Self {
57 start: self.start.clamp(min, max),
58 end: self.end.clamp(min, max),
59 }
60 }
61}
62
63impl From<(usize, usize)> for TextRange {
64 fn from((start, end): (usize, usize)) -> Self {
65 Self { start, end }
66 }
67}
68
69#[derive(Clone, Debug, PartialEq)]
72pub struct TextFieldValue {
73 pub annotated_string: AnnotatedString,
75 pub selection: TextRange,
77 pub composition: Option<TextRange>,
79}
80
81impl TextFieldValue {
82 pub fn new(text: impl Into<String>) -> Self {
84 let annotated = AnnotatedString::from(text.into());
85 let len = annotated.text.len();
86 Self {
87 selection: TextRange::collapsed(len),
88 annotated_string: annotated,
89 composition: None,
90 }
91 }
92
93 pub fn from_annotated(annotated: AnnotatedString) -> Self {
95 let len = annotated.text.len();
96 Self {
97 selection: TextRange::collapsed(len),
98 annotated_string: annotated,
99 composition: None,
100 }
101 }
102
103 pub fn text(&self) -> &str {
105 &self.annotated_string.text
106 }
107
108 pub fn with_selection(mut self, start: usize, end: usize) -> Self {
109 let len = self.annotated_string.text.len();
110 self.selection = TextRange::new(start.min(len), end.min(len));
111 self
112 }
113
114 pub fn get_text_before_selection(&self, max_chars: usize) -> AnnotatedString {
115 let sel_min = self.selection.min();
116 let start = sel_min.saturating_sub(max_chars);
117 let text = self.annotated_string.text[start..sel_min].to_string();
118 let spans: Vec<TextSpan> = self
120 .annotated_string
121 .spans
122 .iter()
123 .filter(|s| s.start >= start && s.end <= sel_min)
124 .map(|s| TextSpan {
125 start: s.start - start,
126 end: s.end - start,
127 style: s.style.clone(),
128 url: s.url.clone(),
129 })
130 .collect();
131 AnnotatedString::new(text, spans)
132 }
133
134 pub fn get_text_after_selection(&self, max_chars: usize) -> AnnotatedString {
135 let sel_max = self.selection.max();
136 let end = (sel_max + max_chars).min(self.annotated_string.text.len());
137 let text = self.annotated_string.text[sel_max..end].to_string();
138 let spans: Vec<TextSpan> = self
139 .annotated_string
140 .spans
141 .iter()
142 .filter(|s| s.start >= sel_max && s.end <= end)
143 .map(|s| TextSpan {
144 start: s.start - sel_max,
145 end: s.end - sel_max,
146 style: s.style.clone(),
147 url: s.url.clone(),
148 })
149 .collect();
150 AnnotatedString::new(text, spans)
151 }
152
153 pub fn get_selected_text(&self) -> AnnotatedString {
154 let r = self.selection.min()..self.selection.max();
155 let text = self.annotated_string.text[r.clone()].to_string();
156 let spans: Vec<TextSpan> = self
157 .annotated_string
158 .spans
159 .iter()
160 .filter(|s| s.start >= r.start && s.end <= r.end)
161 .map(|s| TextSpan {
162 start: s.start - r.start,
163 end: s.end - r.start,
164 style: s.style.clone(),
165 url: s.url.clone(),
166 })
167 .collect();
168 AnnotatedString::new(text, spans)
169 }
170
171 pub fn copy(&self, annotated_string: AnnotatedString) -> Self {
172 TextFieldValue {
173 annotated_string,
174 selection: self.selection,
175 composition: self.composition,
176 }
177 }
178
179 pub fn copy_text(&self, text: String) -> Self {
180 TextFieldValue {
181 annotated_string: AnnotatedString::from(text),
182 selection: self.selection,
183 composition: self.composition,
184 }
185 }
186}
187
188#[derive(Clone, Debug)]
190pub struct TextLayoutResult {
191 pub line_count: usize,
192 pub width_px: f32,
193 pub height_px: f32,
194 pub first_baseline: f32,
195 pub last_baseline: f32,
196 pub did_overflow_width: bool,
197 pub did_overflow_height: bool,
198 pub lines: Vec<TextLineInfo>,
199}
200
201#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
203pub enum TextObfuscationMode {
204 Visible,
206 RevealLastTyped,
208 Hidden,
210 #[default]
212 System,
213}
214
215#[derive(Clone, Debug)]
217pub struct TextLineInfo {
218 pub start: usize,
220 pub end: usize,
222 pub top: f32,
224 pub baseline: f32,
226 pub bottom: f32,
228 pub left: f32,
230 pub right: f32,
232 pub width: f32,
234}
235
236impl TextLayoutResult {
237 pub fn get_line_start(&self, line_index: usize) -> Option<usize> {
238 self.lines.get(line_index).map(|l| l.start)
239 }
240
241 pub fn get_line_end(&self, line_index: usize) -> Option<usize> {
242 self.lines.get(line_index).map(|l| l.end)
243 }
244
245 pub fn get_line_end_visible(&self, line_index: usize) -> Option<usize> {
246 self.lines.get(line_index).map(|l| l.end)
247 }
248
249 pub fn is_line_ellipsized(&self, _line_index: usize) -> bool {
250 false
251 }
252
253 pub fn get_line_top(&self, line_index: usize) -> Option<f32> {
254 self.lines.get(line_index).map(|l| l.top)
255 }
256
257 pub fn get_line_baseline(&self, line_index: usize) -> Option<f32> {
258 self.lines.get(line_index).map(|l| l.baseline)
259 }
260
261 pub fn get_line_bottom(&self, line_index: usize) -> Option<f32> {
262 self.lines.get(line_index).map(|l| l.bottom)
263 }
264
265 pub fn get_line_left(&self, line_index: usize) -> Option<f32> {
266 self.lines.get(line_index).map(|l| l.left)
267 }
268
269 pub fn get_line_right(&self, line_index: usize) -> Option<f32> {
270 self.lines.get(line_index).map(|l| l.right)
271 }
272
273 pub fn get_line_for_offset(&self, offset: usize) -> usize {
274 for (i, line) in self.lines.iter().enumerate() {
275 if offset >= line.start && offset < line.end {
276 return i;
277 }
278 }
279 self.line_count.saturating_sub(1)
280 }
281
282 pub fn get_line_for_vertical_position(&self, vertical: f32) -> usize {
283 for (i, line) in self.lines.iter().enumerate() {
284 if vertical >= line.top && vertical < line.bottom {
285 return i;
286 }
287 }
288 self.line_count.saturating_sub(1)
289 }
290
291 pub fn get_horizontal_position(&self, offset: usize, _use_primary_direction: bool) -> f32 {
292 self.lines
293 .iter()
294 .find(|l| offset >= l.start && offset <= l.end)
295 .map(|l| l.left)
296 .unwrap_or(0.0)
297 }
298
299 pub fn has_visual_overflow(&self) -> bool {
300 self.did_overflow_width || self.did_overflow_height
301 }
302
303 pub fn get_bounding_box(&self, offset: usize) -> crate::Rect {
304 if self.lines.is_empty() || offset > self.lines.last().unwrap().end {
305 return crate::Rect::default();
306 }
307 let line = self
308 .lines
309 .iter()
310 .find(|l| offset >= l.start && offset < l.end)
311 .unwrap_or_else(|| {
312 if offset >= self.lines.last().unwrap().end {
313 self.lines.last().unwrap()
314 } else {
315 &self.lines[0]
316 }
317 });
318 let _line_idx = self.get_line_for_offset(offset);
319 let char_in_line = offset - line.start;
320 let pos_in_line = if char_in_line == 0 {
321 line.left
322 } else {
323 let chars = line.end - line.start;
324 if chars == 0 {
325 line.left
326 } else {
327 line.left + (line.width * (char_in_line as f32) / (chars as f32))
328 }
329 };
330 crate::Rect {
331 x: pos_in_line,
332 y: line.top,
333 w: if char_in_line < (line.end - line.start) {
334 line.width / (line.end - line.start).max(1) as f32
335 } else {
336 1.0
337 },
338 h: line.bottom - line.top,
339 }
340 }
341
342 pub fn get_cursor_rect(&self, offset: usize) -> crate::Rect {
343 let line = self
344 .lines
345 .iter()
346 .find(|l| offset >= l.start && offset <= l.end)
347 .unwrap_or_else(|| {
348 if offset >= self.lines.last().map(|l| l.end).unwrap_or(0) {
349 self.lines.last().unwrap()
350 } else {
351 &self.lines[0]
352 }
353 });
354 let char_in_line = offset.saturating_sub(line.start);
355 let chars = (line.end - line.start).max(1);
356 let x = line.left + (line.width * (char_in_line as f32) / (chars as f32));
357 crate::Rect {
358 x,
359 y: line.top,
360 w: 1.0,
361 h: line.bottom - line.top,
362 }
363 }
364
365 pub fn get_offset_for_position(&self, position: (f32, f32)) -> Option<usize> {
366 let line_idx = self.get_line_for_vertical_position(position.1);
367 let line = self.lines.get(line_idx)?;
368 if position.0 <= line.left {
369 return Some(line.start);
370 }
371 if position.0 >= line.right {
372 return Some(line.end);
373 }
374 let fraction = (position.0 - line.left) / line.width.max(1.0);
375 let offset_in_line = ((line.end - line.start) as f32 * fraction).round() as usize;
376 Some((line.start + offset_in_line).min(line.end))
377 }
378
379 pub fn get_word_boundary(&self, _offset: usize) -> super::TextRange {
380 super::TextRange::new(0, 0)
383 }
384
385 pub fn get_paragraph_direction(&self, _offset: usize) -> u8 {
386 0 }
388
389 pub fn get_bidi_run_direction(&self, _offset: usize) -> u8 {
390 0 }
392}
393
394pub trait OffsetMapping: Debug + Send + Sync + 'static {
395 fn original_to_transformed(&self, offset: usize) -> usize;
396 fn transformed_to_original(&self, offset: usize) -> usize;
397 fn clone_box(&self) -> Box<dyn OffsetMapping>;
398}
399
400#[derive(Clone, Copy, Debug)]
402pub struct IdentityOffsetMapping;
403
404impl OffsetMapping for IdentityOffsetMapping {
405 fn original_to_transformed(&self, offset: usize) -> usize {
406 offset
407 }
408 fn transformed_to_original(&self, offset: usize) -> usize {
409 offset
410 }
411 fn clone_box(&self) -> Box<dyn OffsetMapping> {
412 Box::new(*self)
413 }
414}
415
416pub trait VisualTransformation: Debug + Send + Sync + 'static {
418 fn filter(&self, text: &AnnotatedString) -> TransformedText;
421}
422
423pub struct TransformedText {
425 pub text: AnnotatedString,
426 pub offset_mapping: Box<dyn OffsetMapping>,
427}
428
429impl TransformedText {
430 pub fn new(text: AnnotatedString, offset_mapping: Box<dyn OffsetMapping>) -> Self {
431 TransformedText {
432 text,
433 offset_mapping,
434 }
435 }
436}
437
438impl Clone for TransformedText {
439 fn clone(&self) -> Self {
440 Self {
441 text: self.text.clone(),
442 offset_mapping: self.offset_mapping.clone_box(),
443 }
444 }
445}
446
447impl Debug for TransformedText {
448 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
449 f.debug_struct("TransformedText")
450 .field("text", &self.text.text)
451 .finish()
452 }
453}
454
455#[derive(Clone, Copy, Debug)]
456pub struct IdentityVisualTransformation;
457
458impl VisualTransformation for IdentityVisualTransformation {
459 fn filter(&self, text: &AnnotatedString) -> TransformedText {
460 TransformedText {
461 text: text.clone(),
462 offset_mapping: Box::new(IdentityOffsetMapping),
463 }
464 }
465}
466
467#[derive(Clone, Copy, Debug)]
470pub struct PasswordVisualTransformation {
471 pub mask: char,
472}
473
474impl Default for PasswordVisualTransformation {
475 fn default() -> Self {
476 Self { mask: '\u{2022}' }
477 }
478}
479
480impl VisualTransformation for PasswordVisualTransformation {
481 fn filter(&self, text: &AnnotatedString) -> TransformedText {
482 let masked_text: String = text.text.chars().map(|_| self.mask).collect();
483 TransformedText {
484 text: AnnotatedString::new(masked_text, vec![]),
485 offset_mapping: Box::new(IdentityOffsetMapping),
486 }
487 }
488}
489
490pub fn original_offset_to_display(original: &str, display: &str, original_byte: usize) -> usize {
493 original_offset_to_display_with_mapping(original, display, original_byte, None)
494}
495
496pub fn original_offset_to_display_with_mapping(
499 original: &str,
500 display: &str,
501 original_byte: usize,
502 offset_mapping: Option<&dyn OffsetMapping>,
503) -> usize {
504 if let Some(om) = offset_mapping {
505 om.original_to_transformed(original_byte)
506 } else {
507 let char_idx = original[..original_byte.min(original.len())]
508 .chars()
509 .count();
510 display
511 .char_indices()
512 .nth(char_idx)
513 .map(|(i, _)| i)
514 .unwrap_or(display.len())
515 }
516}
517
518#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
521pub enum KeyboardCapitalization {
522 #[default]
523 Unspecified,
524 None,
525 Characters,
526 Words,
527 Sentences,
528}
529
530#[derive(Clone, Copy, Debug, PartialEq)]
532pub struct Shadow {
533 pub color: Color,
534 pub offset_x: f32,
536 pub offset_y: f32,
538 pub blur_radius: f32,
540}
541
542#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
545pub enum FontSynthesis {
546 #[default]
547 Unspecified,
548 None,
549 Weight,
550 Style,
551 SmallCaps,
552 All,
553}
554
555#[derive(Clone, Copy, Debug, PartialEq)]
558pub struct BaselineShift(pub f32);
559
560#[allow(non_upper_case_globals)]
561impl BaselineShift {
562 pub const Unspecified: BaselineShift = BaselineShift(0.0);
564 pub const Superscript: BaselineShift = BaselineShift(-0.333);
566 pub const Subscript: BaselineShift = BaselineShift(0.2);
568}
569
570impl Default for BaselineShift {
571 fn default() -> Self {
572 BaselineShift::Unspecified
573 }
574}
575
576#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
578pub enum Hyphens {
579 #[default]
580 Unspecified,
581 None,
582 Auto,
583}
584
585#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
587pub enum LineBreak {
588 #[default]
589 Unspecified,
590 Simple,
591 Heading,
592 Paragraph,
593}
594
595#[derive(Clone, Copy, Debug, PartialEq)]
597pub struct TextIndent {
598 pub first_line: f32,
599 pub rest_lines: f32,
600}
601
602impl Default for TextIndent {
603 fn default() -> Self {
604 Self {
605 first_line: 0.0,
606 rest_lines: 0.0,
607 }
608 }
609}
610
611#[derive(Clone, Debug, PartialEq)]
614pub enum PathEffect {
615 Corner {
617 radius: f32,
619 },
620 Dash {
622 intervals: Vec<f32>,
624 phase: f32,
626 },
627}
628
629#[derive(Clone, Debug, PartialEq, Default)]
631pub enum DrawStyle {
632 #[default]
633 Fill,
634 Stroke {
635 width: f32,
637 cap: crate::StrokeCap,
639 join: crate::StrokeJoin,
641 miter: f32,
643 path_effect: Option<PathEffect>,
645 },
646}
647
648impl DrawStyle {
649 pub const fn stroke(width: f32) -> Self {
651 Self::Stroke {
652 width,
653 cap: crate::StrokeCap::Butt,
654 join: crate::StrokeJoin::Miter,
655 miter: 4.0,
656 path_effect: None,
657 }
658 }
659}
660
661#[derive(Clone, Debug)]
664pub struct TextStyle {
665 pub font_size: f32,
667 pub color: Option<Color>,
669 pub font_weight: Option<u16>,
671 pub font_family: Option<&'static str>,
673 pub font_style: Option<u8>,
675 pub text_align: crate::TextAlign,
677 pub letter_spacing: f32,
679 pub line_height: f32,
681 pub background: Option<Color>,
683 pub text_decoration: Option<crate::TextDecoration>,
685 pub shadow: Option<Shadow>,
687 pub text_direction: Option<crate::TextDirection>,
689 pub font_synthesis: FontSynthesis,
691 pub baseline_shift: BaselineShift,
693 pub hyphens: Hyphens,
695 pub line_break: LineBreak,
697 pub text_indent: Option<TextIndent>,
699 pub draw_style: DrawStyle,
701 pub alpha: f32,
703 pub locale_list: Option<String>,
705 pub font_feature_settings: Option<String>,
707 pub font_variation_settings: Option<String>,
709}
710
711impl Default for TextStyle {
712 fn default() -> Self {
713 Self {
714 font_size: 0.0,
715 color: None,
716 font_weight: None,
717 font_family: Some("sans-serif"),
718 font_style: None,
719 text_align: crate::TextAlign::Unspecified,
720 letter_spacing: 0.0,
721 line_height: 0.0,
722 background: None,
723 text_decoration: None,
724 shadow: None,
725 text_direction: None,
726 font_synthesis: FontSynthesis::Unspecified,
727 baseline_shift: BaselineShift::Unspecified,
728 hyphens: Hyphens::Unspecified,
729 line_break: LineBreak::Unspecified,
730 text_indent: None,
731 draw_style: DrawStyle::Fill,
732 alpha: 0.0,
733 locale_list: None,
734 font_feature_settings: None,
735 font_variation_settings: None,
736 }
737 }
738}
739
740#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
742pub enum KeyboardType {
743 #[default]
744 Unspecified,
745 Text,
746 Ascii,
747 Number,
748 Phone,
749 Uri,
750 Email,
751 Password,
752 NumberPassword,
753 Decimal,
754 PasswordVisible,
755 PostalAddress,
756 PersonName,
757 EmailSubject,
758 ShortMessage,
759 LongMessage,
760 Filter,
761 Phonetic,
762 DateTime,
763 Date,
764 Time,
765 NumberSigned,
766 DecimalSigned,
767 DecimalPassword,
768 NumberPasswordSigned,
769 DecimalPasswordSigned,
770}
771
772#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
774pub enum ImeAction {
775 #[default]
776 Unspecified,
777 None,
778 Default,
779 Go,
780 Search,
781 Send,
782 Previous,
783 Next,
784 Done,
785}
786
787#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
794pub enum ImePurposeHint {
795 #[default]
796 Normal,
797 Password,
798 Email,
799 Url,
800 Phone,
801 Number,
802}
803
804impl KeyboardType {
805 pub fn ime_purpose_hint(self) -> ImePurposeHint {
807 use KeyboardType::*;
808 match self {
809 Password
810 | NumberPassword
811 | DecimalPassword
812 | NumberPasswordSigned
813 | DecimalPasswordSigned => ImePurposeHint::Password,
814 Email | EmailSubject => ImePurposeHint::Email,
815 Uri => ImePurposeHint::Url,
816 Phone => ImePurposeHint::Phone,
817 Number | NumberSigned | Decimal | DecimalSigned => ImePurposeHint::Number,
818 _ => ImePurposeHint::Normal,
819 }
820 }
821}
822
823pub trait KeyboardActionScope {
826 fn default_keyboard_action(&self, action: ImeAction);
827}
828
829#[derive(Clone, Default)]
832pub struct KeyboardActions {
833 pub on_done: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
834 pub on_go: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
835 pub on_next: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
836 pub on_previous: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
837 pub on_search: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
838 pub on_send: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
839}
840
841impl KeyboardActions {
842 pub fn on_any(f: impl Fn(ImeAction, &dyn KeyboardActionScope) + 'static) -> Self {
843 let f = Rc::new(f);
844 KeyboardActions {
845 on_done: Some({
846 let f = f.clone();
847 Rc::new(move |scope| f(ImeAction::Done, scope))
848 }),
849 on_go: Some({
850 let f = f.clone();
851 Rc::new(move |scope| f(ImeAction::Go, scope))
852 }),
853 on_next: Some({
854 let f = f.clone();
855 Rc::new(move |scope| f(ImeAction::Next, scope))
856 }),
857 on_previous: Some({
858 let f = f.clone();
859 Rc::new(move |scope| f(ImeAction::Previous, scope))
860 }),
861 on_search: Some({
862 let f = f.clone();
863 Rc::new(move |scope| f(ImeAction::Search, scope))
864 }),
865 on_send: Some(Rc::new(move |scope| f(ImeAction::Send, scope))),
866 }
867 }
868}
869
870pub trait KeyboardActionHandler: Debug + 'static {
873 fn on_keyboard_action(&self, perform_default: &dyn Fn());
874}
875
876#[derive(Clone, Copy, Debug)]
878pub struct DefaultKeyboardActionHandler;
879
880impl KeyboardActionHandler for DefaultKeyboardActionHandler {
881 fn on_keyboard_action(&self, perform_default: &dyn Fn()) {
882 perform_default();
883 }
884}
885
886pub trait TextFieldBuffer {
889 fn text(&self) -> &str;
890 fn set_text(&mut self, text: &str);
891 fn selection(&self) -> TextRange;
892 fn set_selection(&mut self, sel: TextRange);
893 fn length(&self) -> usize;
894 fn replace(&mut self, start: usize, end: usize, text: &str);
895 fn insert(&mut self, index: usize, text: &str);
896 fn delete(&mut self, start: usize, end: usize);
897 fn place_cursor_before_char_at(&mut self, index: usize);
898 fn place_cursor_at_end(&mut self);
899 fn select_all(&mut self);
900 fn revert_all_changes(&mut self);
901 fn original_text(&self) -> &str;
902 fn original_selection(&self) -> TextRange;
903 fn has_selection(&self) -> bool;
904}
905
906#[derive(Clone, Copy, Debug, PartialEq, Eq)]
909pub enum TextFieldLineLimits {
910 SingleLine,
911 MultiLine {
912 min_height_in_lines: usize,
913 max_height_in_lines: usize,
914 },
915}
916
917impl TextFieldLineLimits {
918 pub fn default() -> Self {
919 TextFieldLineLimits::MultiLine {
920 min_height_in_lines: 1,
921 max_height_in_lines: usize::MAX,
922 }
923 }
924}
925
926pub trait TextFieldDecorator: Debug + 'static {
929 fn decorate(&self, inner: crate::View) -> crate::View;
930}
931
932#[derive(Clone, Copy, Debug)]
934pub struct DefaultTextFieldDecorator;
935
936impl TextFieldDecorator for DefaultTextFieldDecorator {
937 fn decorate(&self, inner: crate::View) -> crate::View {
938 inner
939 }
940}
941
942pub trait InputTransformation: Debug + 'static {
945 fn keyboard_options(&self) -> Option<KeyboardOptions> {
946 None
947 }
948 fn transform_input(&self, buffer: &mut dyn TextFieldBuffer);
949}
950
951pub trait OutputTransformation: Debug + 'static {
954 fn transform_output(&self, buffer: &mut dyn TextFieldBuffer);
955}
956
957pub struct CodepointTransformation {
960 pub transform: Box<dyn Fn(usize, char) -> char>,
961}
962
963impl Clone for CodepointTransformation {
964 fn clone(&self) -> Self {
965 panic!("CodepointTransformation::clone() is not supported -> use Rc instead");
969 }
970}
971
972impl CodepointTransformation {
973 pub fn new(transform: impl Fn(usize, char) -> char + 'static) -> Self {
974 CodepointTransformation {
975 transform: Box::new(transform),
976 }
977 }
978
979 pub fn transform(&self, codepoint_index: usize, codepoint: char) -> char {
980 (self.transform)(codepoint_index, codepoint)
981 }
982}
983
984#[derive(Clone, Copy, Debug, PartialEq)]
987pub struct KeyboardOptions {
988 pub keyboard_type: KeyboardType,
989 pub capitalization: KeyboardCapitalization,
990 pub ime_action: ImeAction,
991 pub auto_correct_enabled: Option<bool>,
992 pub show_keyboard_on_focus: Option<bool>,
993 pub platform_ime_options: Option<&'static str>,
994 pub hint_locales: Option<&'static str>,
995}
996
997impl KeyboardOptions {
998 pub const DEFAULT: KeyboardOptions = KeyboardOptions {
999 keyboard_type: KeyboardType::Text,
1000 capitalization: KeyboardCapitalization::Unspecified,
1001 ime_action: ImeAction::Unspecified,
1002 auto_correct_enabled: None,
1003 show_keyboard_on_focus: None,
1004 platform_ime_options: None,
1005 hint_locales: None,
1006 };
1007
1008 pub const SECURE_TEXT_FIELD: KeyboardOptions = KeyboardOptions {
1009 keyboard_type: KeyboardType::Password,
1010 capitalization: KeyboardCapitalization::Unspecified,
1011 ime_action: ImeAction::Unspecified,
1012 auto_correct_enabled: Some(false),
1013 show_keyboard_on_focus: None,
1014 platform_ime_options: None,
1015 hint_locales: None,
1016 };
1017
1018 pub fn fill_unspecified_values_with(&self, other: Option<&KeyboardOptions>) -> KeyboardOptions {
1019 let other = match other {
1020 Some(o) => o,
1021 None => return *self,
1022 };
1023 KeyboardOptions {
1024 keyboard_type: if self.keyboard_type == KeyboardType::Unspecified {
1025 other.keyboard_type
1026 } else {
1027 self.keyboard_type
1028 },
1029 capitalization: if self.capitalization == KeyboardCapitalization::Unspecified {
1030 other.capitalization
1031 } else {
1032 self.capitalization
1033 },
1034 ime_action: if self.ime_action == ImeAction::Unspecified {
1035 other.ime_action
1036 } else {
1037 self.ime_action
1038 },
1039 auto_correct_enabled: self.auto_correct_enabled.or(other.auto_correct_enabled),
1040 show_keyboard_on_focus: self.show_keyboard_on_focus.or(other.show_keyboard_on_focus),
1041 platform_ime_options: self.platform_ime_options.or(other.platform_ime_options),
1042 hint_locales: self.hint_locales.or(other.hint_locales),
1043 }
1044 }
1045
1046 pub fn merge(&self, other: Option<&KeyboardOptions>) -> KeyboardOptions {
1050 other.map_or(*self, |o| o.fill_unspecified_values_with(Some(self)))
1051 }
1052}
1053
1054impl Default for KeyboardOptions {
1055 fn default() -> Self {
1056 Self::DEFAULT
1057 }
1058}
1059
1060#[derive(Debug, Clone, PartialEq)]
1061pub struct SpanStyle {
1062 pub color: Option<Color>,
1063 pub font_size: Option<f32>,
1064 pub font_weight: Option<u16>,
1065 pub font_family: Option<&'static str>,
1066 pub font_style: Option<u8>,
1067 pub text_align: Option<crate::TextAlign>,
1068 pub letter_spacing: Option<f32>,
1069 pub line_height: Option<f32>,
1070 pub background: Option<Color>,
1071 pub text_decoration: Option<crate::TextDecoration>,
1072 pub text_direction: Option<crate::TextDirection>,
1073 pub font_synthesis: Option<FontSynthesis>,
1074 pub baseline_shift: Option<BaselineShift>,
1075 pub hyphens: Option<Hyphens>,
1076 pub line_break: Option<LineBreak>,
1077 pub text_indent: Option<TextIndent>,
1078 pub draw_style: Option<DrawStyle>,
1079 pub alpha: f32,
1080 pub font_variation_settings: Option<String>,
1082}
1083
1084impl SpanStyle {
1085 pub const fn default() -> Self {
1086 Self {
1087 color: None,
1088 font_size: None,
1089 font_weight: None,
1090 font_family: None,
1091 font_style: None,
1092 text_align: None,
1093 letter_spacing: None,
1094 line_height: None,
1095 background: None,
1096 text_decoration: None,
1097 text_direction: None,
1098 font_synthesis: None,
1099 baseline_shift: None,
1100 hyphens: None,
1101 line_break: None,
1102 text_indent: None,
1103 draw_style: None,
1104 alpha: 0.0,
1105 font_variation_settings: None,
1106 }
1107 }
1108
1109 pub fn color(mut self, c: Color) -> Self {
1110 self.color = Some(c);
1111 self
1112 }
1113
1114 pub fn font_size(mut self, px: f32) -> Self {
1115 self.font_size = Some(px);
1116 self
1117 }
1118
1119 pub fn text_decoration(mut self, d: TextDecoration) -> Self {
1120 self.text_decoration = Some(d);
1121 self
1122 }
1123
1124 pub fn font_weight(mut self, w: u16) -> Self {
1125 self.font_weight = Some(w);
1126 self
1127 }
1128
1129 pub fn font_style(mut self, s: u8) -> Self {
1130 self.font_style = Some(s);
1131 self
1132 }
1133
1134 pub fn draw_style(mut self, s: DrawStyle) -> Self {
1135 self.draw_style = Some(s);
1136 self
1137 }
1138
1139 pub fn background(mut self, c: Color) -> Self {
1140 self.background = Some(c);
1141 self
1142 }
1143
1144 pub fn baseline_shift(mut self, s: BaselineShift) -> Self {
1145 self.baseline_shift = Some(s);
1146 self
1147 }
1148}
1149
1150impl Default for SpanStyle {
1151 fn default() -> Self {
1152 Self::default()
1153 }
1154}
1155
1156#[derive(Debug, Clone, PartialEq)]
1158pub struct TextSpan {
1159 pub start: usize,
1161 pub end: usize,
1163 pub style: SpanStyle,
1164 pub url: Option<Arc<str>>,
1166}
1167
1168#[derive(Debug, Clone, PartialEq)]
1172pub struct AnnotatedString {
1173 pub text: String,
1174 pub spans: Arc<[TextSpan]>,
1175}
1176
1177impl AnnotatedString {
1178 pub fn new(text: impl Into<String>, spans: Vec<TextSpan>) -> Self {
1179 let text = text.into();
1180 Self {
1181 text,
1182 spans: spans.into(),
1183 }
1184 }
1185
1186 pub fn as_str(&self) -> &str {
1187 &self.text
1188 }
1189}
1190
1191impl From<String> for AnnotatedString {
1192 fn from(text: String) -> Self {
1193 Self {
1194 text,
1195 spans: Arc::from([]),
1196 }
1197 }
1198}
1199
1200impl From<&str> for AnnotatedString {
1201 fn from(text: &str) -> Self {
1202 Self {
1203 text: text.to_string(),
1204 spans: Arc::from([]),
1205 }
1206 }
1207}
1208
1209#[derive(Default)]
1211pub struct AnnotatedStringBuilder {
1212 text: String,
1213 spans: Vec<TextSpan>,
1214}
1215
1216impl AnnotatedStringBuilder {
1217 pub fn new() -> Self {
1218 Self::default()
1219 }
1220
1221 pub fn push(&mut self, text: &str) -> &mut Self {
1223 self.text.push_str(text);
1224 self
1225 }
1226
1227 pub fn push_with_style(&mut self, text: &str, style: SpanStyle) -> &mut Self {
1229 let start = self.text.len();
1230 self.text.push_str(text);
1231 let end = self.text.len();
1232 if start < end {
1233 self.spans.push(TextSpan {
1234 start,
1235 end,
1236 style,
1237 url: None,
1238 });
1239 }
1240 self
1241 }
1242
1243 pub fn push_color(&mut self, text: &str, color: Color) -> &mut Self {
1245 self.push_with_style(text, SpanStyle::default().color(color))
1246 }
1247
1248 pub fn push_link(&mut self, text: &str, url: impl Into<Arc<str>>) -> &mut Self {
1250 let start = self.text.len();
1251 self.text.push_str(text);
1252 let end = self.text.len();
1253 if start < end {
1254 self.spans.push(TextSpan {
1255 start,
1256 end,
1257 style: SpanStyle::default()
1258 .color(Color::from_rgba(0x15, 0x76, 0xFF, 255))
1259 .text_decoration(TextDecoration::UNDERLINE),
1260 url: Some(url.into()),
1261 });
1262 }
1263 self
1264 }
1265
1266 pub fn add_style(&mut self, start: usize, end: usize, style: SpanStyle) -> &mut Self {
1268 if start < end && end <= self.text.len() {
1269 self.spans.push(TextSpan {
1270 start,
1271 end,
1272 style,
1273 url: None,
1274 });
1275 }
1276 self
1277 }
1278
1279 pub fn build(&mut self) -> AnnotatedString {
1280 let text = std::mem::take(&mut self.text);
1281 self.spans.sort_by_key(|s| s.start);
1282 let mut merged: Vec<TextSpan> = Vec::new();
1284 for span in std::mem::take(&mut self.spans) {
1285 if let Some(last) = merged.last_mut()
1286 && last.end == span.start
1287 && last.style == span.style
1288 {
1289 last.end = span.end;
1290 continue;
1291 }
1292 merged.push(span);
1293 }
1294 AnnotatedString {
1295 text,
1296 spans: merged.into(),
1297 }
1298 }
1299}
1300
1301#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Default)]
1303pub enum TextAlign {
1304 Left,
1305 Right,
1306 Center,
1307 Justify,
1308 Start,
1309 End,
1310 #[default]
1311 Unspecified,
1312}
1313
1314#[derive(Clone, Copy, Debug, PartialEq)]
1316pub struct FontWeight(pub u16);
1317
1318impl FontWeight {
1319 pub const THIN: FontWeight = FontWeight(100);
1320 pub const EXTRA_LIGHT: FontWeight = FontWeight(200);
1321 pub const LIGHT: FontWeight = FontWeight(300);
1322 pub const NORMAL: FontWeight = FontWeight(400);
1323 pub const MEDIUM: FontWeight = FontWeight(500);
1324 pub const SEMI_BOLD: FontWeight = FontWeight(600);
1325 pub const BOLD: FontWeight = FontWeight(700);
1326 pub const EXTRA_BOLD: FontWeight = FontWeight(800);
1327 pub const BLACK: FontWeight = FontWeight(900);
1328}
1329
1330impl Default for FontWeight {
1331 fn default() -> Self {
1332 FontWeight::NORMAL
1333 }
1334}
1335
1336#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Default)]
1338pub enum FontStyle {
1339 #[default]
1340 Normal,
1341 Italic,
1342}
1343
1344#[derive(Clone, Copy, Debug, PartialEq, Default)]
1346pub struct TextDecoration {
1347 pub underline: bool,
1348 pub strikethrough: bool,
1349 pub color: Option<Color>,
1350}
1351
1352impl TextDecoration {
1353 pub const UNDERLINE: TextDecoration = TextDecoration {
1354 underline: true,
1355 strikethrough: false,
1356 color: None,
1357 };
1358 pub const STRIKETHROUGH: TextDecoration = TextDecoration {
1359 underline: false,
1360 strikethrough: true,
1361 color: None,
1362 };
1363}
1364
1365pub fn build_annotated_string(b: impl FnOnce(&mut AnnotatedStringBuilder)) -> AnnotatedString {
1367 let mut builder = AnnotatedStringBuilder::new();
1368 b(&mut builder);
1369 builder.build()
1370}