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 {
173 TextFieldValue {
174 annotated_string,
175 selection: self.selection,
176 composition: self.composition,
177 }
178 }
179
180 pub fn copy_text(&self, text: String) -> Self {
182 TextFieldValue {
183 annotated_string: AnnotatedString::from(text),
184 selection: self.selection,
185 composition: self.composition,
186 }
187 }
188}
189
190#[derive(Clone, Debug)]
193pub struct TextLayoutResult {
194 pub line_count: usize,
196 pub width_px: f32,
198 pub height_px: f32,
200 pub first_baseline: f32,
202 pub last_baseline: f32,
204 pub did_overflow_width: bool,
206 pub did_overflow_height: bool,
208 pub lines: Vec<TextLineInfo>,
210}
211
212#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
215pub enum TextObfuscationMode {
216 Visible,
218 RevealLastTyped,
220 Hidden,
222 #[default]
224 System,
225}
226
227#[derive(Clone, Debug)]
229pub struct TextLineInfo {
230 pub start: usize,
232 pub end: usize,
234 pub top: f32,
236 pub baseline: f32,
238 pub bottom: f32,
240 pub left: f32,
242 pub right: f32,
244 pub width: f32,
246}
247
248impl TextLayoutResult {
249 pub fn get_line_start(&self, line_index: usize) -> Option<usize> {
250 self.lines.get(line_index).map(|l| l.start)
251 }
252
253 pub fn get_line_end(&self, line_index: usize) -> Option<usize> {
254 self.lines.get(line_index).map(|l| l.end)
255 }
256
257 pub fn get_line_end_visible(&self, line_index: usize) -> Option<usize> {
258 self.lines.get(line_index).map(|l| l.end)
259 }
260
261 pub fn is_line_ellipsized(&self, _line_index: usize) -> bool {
262 false
263 }
264
265 pub fn get_line_top(&self, line_index: usize) -> Option<f32> {
266 self.lines.get(line_index).map(|l| l.top)
267 }
268
269 pub fn get_line_baseline(&self, line_index: usize) -> Option<f32> {
270 self.lines.get(line_index).map(|l| l.baseline)
271 }
272
273 pub fn get_line_bottom(&self, line_index: usize) -> Option<f32> {
274 self.lines.get(line_index).map(|l| l.bottom)
275 }
276
277 pub fn get_line_left(&self, line_index: usize) -> Option<f32> {
278 self.lines.get(line_index).map(|l| l.left)
279 }
280
281 pub fn get_line_right(&self, line_index: usize) -> Option<f32> {
282 self.lines.get(line_index).map(|l| l.right)
283 }
284
285 pub fn get_line_for_offset(&self, offset: usize) -> usize {
286 for (i, line) in self.lines.iter().enumerate() {
287 if offset >= line.start && offset < line.end {
288 return i;
289 }
290 }
291 self.line_count.saturating_sub(1)
292 }
293
294 pub fn get_line_for_vertical_position(&self, vertical: f32) -> usize {
295 for (i, line) in self.lines.iter().enumerate() {
296 if vertical >= line.top && vertical < line.bottom {
297 return i;
298 }
299 }
300 self.line_count.saturating_sub(1)
301 }
302
303 pub fn get_horizontal_position(&self, offset: usize, _use_primary_direction: bool) -> f32 {
304 self.lines
305 .iter()
306 .find(|l| offset >= l.start && offset <= l.end)
307 .map(|l| l.left)
308 .unwrap_or(0.0)
309 }
310
311 pub fn has_visual_overflow(&self) -> bool {
313 self.did_overflow_width || self.did_overflow_height
314 }
315
316 pub fn get_bounding_box(&self, offset: usize) -> crate::Rect {
319 if self.lines.is_empty() || offset > self.lines.last().unwrap().end {
320 return crate::Rect::default();
321 }
322 let line = self
323 .lines
324 .iter()
325 .find(|l| offset >= l.start && offset < l.end)
326 .unwrap_or_else(|| {
327 if offset >= self.lines.last().unwrap().end {
328 self.lines.last().unwrap()
329 } else {
330 &self.lines[0]
331 }
332 });
333 let _line_idx = self.get_line_for_offset(offset);
334 let char_in_line = offset - line.start;
335 let pos_in_line = if char_in_line == 0 {
336 line.left
337 } else {
338 let chars = line.end - line.start;
340 if chars == 0 {
341 line.left
342 } else {
343 line.left + (line.width * (char_in_line as f32) / (chars as f32))
344 }
345 };
346 crate::Rect {
347 x: pos_in_line,
348 y: line.top,
349 w: if char_in_line < (line.end - line.start) {
350 line.width / (line.end - line.start).max(1) as f32
351 } else {
352 1.0
353 },
354 h: line.bottom - line.top,
355 }
356 }
357
358 pub fn get_cursor_rect(&self, offset: usize) -> crate::Rect {
360 let line = self
361 .lines
362 .iter()
363 .find(|l| offset >= l.start && offset <= l.end)
364 .unwrap_or_else(|| {
365 if offset >= self.lines.last().map(|l| l.end).unwrap_or(0) {
366 self.lines.last().unwrap()
367 } else {
368 &self.lines[0]
369 }
370 });
371 let char_in_line = offset.saturating_sub(line.start);
372 let chars = (line.end - line.start).max(1);
373 let x = line.left + (line.width * (char_in_line as f32) / (chars as f32));
374 crate::Rect {
375 x,
376 y: line.top,
377 w: 1.0,
378 h: line.bottom - line.top,
379 }
380 }
381
382 pub fn get_offset_for_position(&self, position: (f32, f32)) -> Option<usize> {
384 let line_idx = self.get_line_for_vertical_position(position.1);
385 let line = self.lines.get(line_idx)?;
386 if position.0 <= line.left {
387 return Some(line.start);
388 }
389 if position.0 >= line.right {
390 return Some(line.end);
391 }
392 let fraction = (position.0 - line.left) / line.width.max(1.0);
393 let offset_in_line = ((line.end - line.start) as f32 * fraction).round() as usize;
394 Some((line.start + offset_in_line).min(line.end))
395 }
396
397 pub fn get_word_boundary(&self, _offset: usize) -> super::TextRange {
399 let text = ""; let start = if text.is_empty() { 0 } else { 0 };
402 let end = if text.is_empty() { 0 } else { 0 };
403 super::TextRange::new(start, end)
404 }
405
406 pub fn get_paragraph_direction(&self, _offset: usize) -> u8 {
408 0 }
410
411 pub fn get_bidi_run_direction(&self, _offset: usize) -> u8 {
413 0 }
415}
416
417pub trait OffsetMapping: Debug + Send + Sync + 'static {
419 fn original_to_transformed(&self, offset: usize) -> usize;
420 fn transformed_to_original(&self, offset: usize) -> usize;
421 fn clone_box(&self) -> Box<dyn OffsetMapping>;
422}
423
424#[derive(Clone, Copy, Debug)]
426pub struct IdentityOffsetMapping;
427
428impl OffsetMapping for IdentityOffsetMapping {
429 fn original_to_transformed(&self, offset: usize) -> usize {
430 offset
431 }
432 fn transformed_to_original(&self, offset: usize) -> usize {
433 offset
434 }
435 fn clone_box(&self) -> Box<dyn OffsetMapping> {
436 Box::new(*self)
437 }
438}
439
440pub trait VisualTransformation: Debug + Send + Sync + 'static {
443 fn filter(&self, text: &AnnotatedString) -> TransformedText;
446}
447
448pub struct TransformedText {
450 pub text: AnnotatedString,
452 pub offset_mapping: Box<dyn OffsetMapping>,
454}
455
456impl TransformedText {
457 pub fn new(text: AnnotatedString, offset_mapping: Box<dyn OffsetMapping>) -> Self {
458 TransformedText {
459 text,
460 offset_mapping,
461 }
462 }
463}
464
465impl Clone for TransformedText {
466 fn clone(&self) -> Self {
467 Self {
468 text: self.text.clone(),
469 offset_mapping: self.offset_mapping.clone_box(),
470 }
471 }
472}
473
474impl Debug for TransformedText {
475 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
476 f.debug_struct("TransformedText")
477 .field("text", &self.text.text)
478 .finish()
479 }
480}
481
482#[derive(Clone, Copy, Debug)]
485pub struct IdentityVisualTransformation;
486
487impl VisualTransformation for IdentityVisualTransformation {
488 fn filter(&self, text: &AnnotatedString) -> TransformedText {
489 TransformedText {
490 text: text.clone(),
491 offset_mapping: Box::new(IdentityOffsetMapping),
492 }
493 }
494}
495
496#[derive(Clone, Copy, Debug)]
499pub struct PasswordVisualTransformation {
500 pub mask: char,
502}
503
504impl Default for PasswordVisualTransformation {
505 fn default() -> Self {
506 Self { mask: '\u{2022}' }
507 }
508}
509
510impl VisualTransformation for PasswordVisualTransformation {
511 fn filter(&self, text: &AnnotatedString) -> TransformedText {
512 let masked_text: String = text.text.chars().map(|_| self.mask).collect();
513 TransformedText {
514 text: AnnotatedString::new(masked_text, vec![]),
515 offset_mapping: Box::new(IdentityOffsetMapping),
516 }
517 }
518}
519
520pub fn original_offset_to_display(original: &str, display: &str, original_byte: usize) -> usize {
523 original_offset_to_display_with_mapping(original, display, original_byte, None)
524}
525
526pub fn original_offset_to_display_with_mapping(
529 original: &str,
530 display: &str,
531 original_byte: usize,
532 offset_mapping: Option<&dyn OffsetMapping>,
533) -> usize {
534 if let Some(om) = offset_mapping {
535 om.original_to_transformed(original_byte)
536 } else {
537 let char_idx = original[..original_byte.min(original.len())]
538 .chars()
539 .count();
540 display
541 .char_indices()
542 .nth(char_idx)
543 .map(|(i, _)| i)
544 .unwrap_or(display.len())
545 }
546}
547
548#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
551pub enum KeyboardCapitalization {
552 #[default]
553 Unspecified,
554 None,
555 Characters,
556 Words,
557 Sentences,
558}
559
560#[derive(Clone, Copy, Debug, PartialEq)]
562pub struct Shadow {
563 pub color: Color,
564 pub offset_x: f32,
566 pub offset_y: f32,
568 pub blur_radius: f32,
570}
571
572#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
575pub enum FontSynthesis {
576 #[default]
577 Unspecified,
578 None,
579 Weight,
580 Style,
581 SmallCaps,
582 All,
583}
584
585#[derive(Clone, Copy, Debug, PartialEq)]
588pub struct BaselineShift(pub f32);
589
590#[allow(non_upper_case_globals)]
591impl BaselineShift {
592 pub const Unspecified: BaselineShift = BaselineShift(0.0);
594 pub const Superscript: BaselineShift = BaselineShift(-0.333);
596 pub const Subscript: BaselineShift = BaselineShift(0.2);
598}
599
600impl Default for BaselineShift {
601 fn default() -> Self {
602 BaselineShift::Unspecified
603 }
604}
605
606#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
608pub enum Hyphens {
609 #[default]
610 Unspecified,
611 None,
612 Auto,
613}
614
615#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
617pub enum LineBreak {
618 #[default]
619 Unspecified,
620 Simple,
621 Heading,
622 Paragraph,
623}
624
625#[derive(Clone, Copy, Debug, PartialEq)]
627pub struct TextIndent {
628 pub first_line: f32,
629 pub rest_lines: f32,
630}
631
632impl Default for TextIndent {
633 fn default() -> Self {
634 Self {
635 first_line: 0.0,
636 rest_lines: 0.0,
637 }
638 }
639}
640
641#[derive(Clone, Debug, PartialEq)]
644pub enum PathEffect {
645 Corner {
647 radius: f32,
649 },
650 Dash {
652 intervals: Vec<f32>,
654 phase: f32,
656 },
657}
658
659#[derive(Clone, Debug, PartialEq, Default)]
661pub enum DrawStyle {
662 #[default]
663 Fill,
664 Stroke {
665 width: f32,
667 cap: crate::StrokeCap,
669 join: crate::StrokeJoin,
671 miter: f32,
673 path_effect: Option<PathEffect>,
675 },
676}
677
678impl DrawStyle {
679 pub const fn stroke(width: f32) -> Self {
681 Self::Stroke {
682 width,
683 cap: crate::StrokeCap::Butt,
684 join: crate::StrokeJoin::Miter,
685 miter: 4.0,
686 path_effect: None,
687 }
688 }
689}
690
691#[derive(Clone, Debug)]
694pub struct TextStyle {
695 pub font_size: f32,
697 pub color: Option<Color>,
699 pub font_weight: Option<u16>,
701 pub font_family: Option<&'static str>,
703 pub font_style: Option<u8>,
705 pub text_align: crate::TextAlign,
707 pub letter_spacing: f32,
709 pub line_height: f32,
711 pub background: Option<Color>,
713 pub text_decoration: Option<crate::TextDecoration>,
715 pub shadow: Option<Shadow>,
717 pub text_direction: Option<crate::TextDirection>,
719 pub font_synthesis: FontSynthesis,
721 pub baseline_shift: BaselineShift,
723 pub hyphens: Hyphens,
725 pub line_break: LineBreak,
727 pub text_indent: Option<TextIndent>,
729 pub draw_style: DrawStyle,
731 pub alpha: f32,
733 pub locale_list: Option<String>,
735 pub font_feature_settings: Option<String>,
737 pub font_variation_settings: Option<String>,
739}
740
741impl Default for TextStyle {
742 fn default() -> Self {
743 Self {
744 font_size: 0.0,
745 color: None,
746 font_weight: None,
747 font_family: Some("sans-serif"),
748 font_style: None,
749 text_align: crate::TextAlign::Unspecified,
750 letter_spacing: 0.0,
751 line_height: 0.0,
752 background: None,
753 text_decoration: None,
754 shadow: None,
755 text_direction: None,
756 font_synthesis: FontSynthesis::Unspecified,
757 baseline_shift: BaselineShift::Unspecified,
758 hyphens: Hyphens::Unspecified,
759 line_break: LineBreak::Unspecified,
760 text_indent: None,
761 draw_style: DrawStyle::Fill,
762 alpha: 0.0,
763 locale_list: None,
764 font_feature_settings: None,
765 font_variation_settings: None,
766 }
767 }
768}
769
770#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
772pub enum KeyboardType {
773 #[default]
774 Unspecified,
775 Text,
776 Ascii,
777 Number,
778 Phone,
779 Uri,
780 Email,
781 Password,
782 NumberPassword,
783 Decimal,
784 PasswordVisible,
785 PostalAddress,
786 PersonName,
787 EmailSubject,
788 ShortMessage,
789 LongMessage,
790 Filter,
791 Phonetic,
792 DateTime,
793 Date,
794 Time,
795 NumberSigned,
796 DecimalSigned,
797 DecimalPassword,
798 NumberPasswordSigned,
799 DecimalPasswordSigned,
800}
801
802#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
804pub enum ImeAction {
805 #[default]
806 Unspecified,
807 None,
808 Default,
809 Go,
810 Search,
811 Send,
812 Previous,
813 Next,
814 Done,
815}
816
817#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
824pub enum ImePurposeHint {
825 #[default]
826 Normal,
827 Password,
828 Email,
829 Url,
830 Phone,
831 Number,
832}
833
834impl KeyboardType {
835 pub fn ime_purpose_hint(self) -> ImePurposeHint {
837 use KeyboardType::*;
838 match self {
839 Password
840 | NumberPassword
841 | DecimalPassword
842 | NumberPasswordSigned
843 | DecimalPasswordSigned => ImePurposeHint::Password,
844 Email | EmailSubject => ImePurposeHint::Email,
845 Uri => ImePurposeHint::Url,
846 Phone => ImePurposeHint::Phone,
847 Number | NumberSigned | Decimal | DecimalSigned => ImePurposeHint::Number,
848 _ => ImePurposeHint::Normal,
849 }
850 }
851}
852
853pub trait KeyboardActionScope {
856 fn default_keyboard_action(&self, action: ImeAction);
857}
858
859#[derive(Clone, Default)]
862pub struct KeyboardActions {
863 pub on_done: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
864 pub on_go: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
865 pub on_next: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
866 pub on_previous: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
867 pub on_search: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
868 pub on_send: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
869}
870
871impl KeyboardActions {
872 pub fn on_any(f: impl Fn(ImeAction, &dyn KeyboardActionScope) + 'static) -> Self {
873 let f = Rc::new(f);
874 KeyboardActions {
875 on_done: Some({
876 let f = f.clone();
877 Rc::new(move |scope| f(ImeAction::Done, scope))
878 }),
879 on_go: Some({
880 let f = f.clone();
881 Rc::new(move |scope| f(ImeAction::Go, scope))
882 }),
883 on_next: Some({
884 let f = f.clone();
885 Rc::new(move |scope| f(ImeAction::Next, scope))
886 }),
887 on_previous: Some({
888 let f = f.clone();
889 Rc::new(move |scope| f(ImeAction::Previous, scope))
890 }),
891 on_search: Some({
892 let f = f.clone();
893 Rc::new(move |scope| f(ImeAction::Search, scope))
894 }),
895 on_send: Some(Rc::new(move |scope| f(ImeAction::Send, scope))),
896 }
897 }
898}
899
900pub trait KeyboardActionHandler: Debug + 'static {
903 fn on_keyboard_action(&self, perform_default: &dyn Fn());
904}
905
906#[derive(Clone, Copy, Debug)]
908pub struct DefaultKeyboardActionHandler;
909
910impl KeyboardActionHandler for DefaultKeyboardActionHandler {
911 fn on_keyboard_action(&self, perform_default: &dyn Fn()) {
912 perform_default();
913 }
914}
915
916pub trait TextFieldBuffer {
919 fn text(&self) -> &str;
920 fn set_text(&mut self, text: &str);
921 fn selection(&self) -> TextRange;
922 fn set_selection(&mut self, sel: TextRange);
923 fn length(&self) -> usize;
924 fn replace(&mut self, start: usize, end: usize, text: &str);
925 fn insert(&mut self, index: usize, text: &str);
926 fn delete(&mut self, start: usize, end: usize);
927 fn place_cursor_before_char_at(&mut self, index: usize);
928 fn place_cursor_at_end(&mut self);
929 fn select_all(&mut self);
930 fn revert_all_changes(&mut self);
931 fn original_text(&self) -> &str;
932 fn original_selection(&self) -> TextRange;
933 fn has_selection(&self) -> bool;
934}
935
936#[derive(Clone, Copy, Debug, PartialEq, Eq)]
939pub enum TextFieldLineLimits {
940 SingleLine,
941 MultiLine {
942 min_height_in_lines: usize,
943 max_height_in_lines: usize,
944 },
945}
946
947impl TextFieldLineLimits {
948 pub fn default() -> Self {
949 TextFieldLineLimits::MultiLine {
950 min_height_in_lines: 1,
951 max_height_in_lines: usize::MAX,
952 }
953 }
954}
955
956pub trait TextFieldDecorator: Debug + 'static {
959 fn decorate(&self, inner: crate::View) -> crate::View;
960}
961
962#[derive(Clone, Copy, Debug)]
964pub struct DefaultTextFieldDecorator;
965
966impl TextFieldDecorator for DefaultTextFieldDecorator {
967 fn decorate(&self, inner: crate::View) -> crate::View {
968 inner
969 }
970}
971
972pub trait InputTransformation: Debug + 'static {
975 fn keyboard_options(&self) -> Option<KeyboardOptions> {
976 None
977 }
978 fn transform_input(&self, buffer: &mut dyn TextFieldBuffer);
979}
980
981pub trait OutputTransformation: Debug + 'static {
984 fn transform_output(&self, buffer: &mut dyn TextFieldBuffer);
985}
986
987pub struct CodepointTransformation {
990 pub transform: Box<dyn Fn(usize, char) -> char>,
991}
992
993impl Clone for CodepointTransformation {
994 fn clone(&self) -> Self {
995 panic!("CodepointTransformation::clone() is not supported -> use Rc instead");
999 }
1000}
1001
1002impl CodepointTransformation {
1003 pub fn new(transform: impl Fn(usize, char) -> char + 'static) -> Self {
1004 CodepointTransformation {
1005 transform: Box::new(transform),
1006 }
1007 }
1008
1009 pub fn transform(&self, codepoint_index: usize, codepoint: char) -> char {
1010 (self.transform)(codepoint_index, codepoint)
1011 }
1012}
1013
1014#[derive(Clone, Copy, Debug, PartialEq)]
1017pub struct KeyboardOptions {
1018 pub keyboard_type: KeyboardType,
1019 pub capitalization: KeyboardCapitalization,
1020 pub ime_action: ImeAction,
1021 pub auto_correct_enabled: Option<bool>,
1022 pub show_keyboard_on_focus: Option<bool>,
1023 pub platform_ime_options: Option<&'static str>,
1024 pub hint_locales: Option<&'static str>,
1025}
1026
1027impl KeyboardOptions {
1028 pub const DEFAULT: KeyboardOptions = KeyboardOptions {
1029 keyboard_type: KeyboardType::Text,
1030 capitalization: KeyboardCapitalization::Unspecified,
1031 ime_action: ImeAction::Unspecified,
1032 auto_correct_enabled: None,
1033 show_keyboard_on_focus: None,
1034 platform_ime_options: None,
1035 hint_locales: None,
1036 };
1037
1038 pub const SECURE_TEXT_FIELD: KeyboardOptions = KeyboardOptions {
1039 keyboard_type: KeyboardType::Password,
1040 capitalization: KeyboardCapitalization::Unspecified,
1041 ime_action: ImeAction::Unspecified,
1042 auto_correct_enabled: Some(false),
1043 show_keyboard_on_focus: None,
1044 platform_ime_options: None,
1045 hint_locales: None,
1046 };
1047
1048 pub fn fill_unspecified_values_with(&self, other: Option<&KeyboardOptions>) -> KeyboardOptions {
1049 let other = match other {
1050 Some(o) => o,
1051 None => return *self,
1052 };
1053 KeyboardOptions {
1054 keyboard_type: if self.keyboard_type == KeyboardType::Unspecified {
1055 other.keyboard_type
1056 } else {
1057 self.keyboard_type
1058 },
1059 capitalization: if self.capitalization == KeyboardCapitalization::Unspecified {
1060 other.capitalization
1061 } else {
1062 self.capitalization
1063 },
1064 ime_action: if self.ime_action == ImeAction::Unspecified {
1065 other.ime_action
1066 } else {
1067 self.ime_action
1068 },
1069 auto_correct_enabled: self.auto_correct_enabled.or(other.auto_correct_enabled),
1070 show_keyboard_on_focus: self.show_keyboard_on_focus.or(other.show_keyboard_on_focus),
1071 platform_ime_options: self.platform_ime_options.or(other.platform_ime_options),
1072 hint_locales: self.hint_locales.or(other.hint_locales),
1073 }
1074 }
1075
1076 pub fn merge(&self, other: Option<&KeyboardOptions>) -> KeyboardOptions {
1080 other.map_or(*self, |o| o.fill_unspecified_values_with(Some(self)))
1081 }
1082}
1083
1084impl Default for KeyboardOptions {
1085 fn default() -> Self {
1086 Self::DEFAULT
1087 }
1088}
1089
1090#[derive(Debug, Clone, PartialEq)]
1091pub struct SpanStyle {
1092 pub color: Option<Color>,
1093 pub font_size: Option<f32>,
1094 pub font_weight: Option<u16>,
1095 pub font_family: Option<&'static str>,
1096 pub font_style: Option<u8>,
1097 pub text_align: Option<crate::TextAlign>,
1098 pub letter_spacing: Option<f32>,
1099 pub line_height: Option<f32>,
1100 pub background: Option<Color>,
1101 pub text_decoration: Option<crate::TextDecoration>,
1102 pub text_direction: Option<crate::TextDirection>,
1103 pub font_synthesis: Option<FontSynthesis>,
1104 pub baseline_shift: Option<BaselineShift>,
1105 pub hyphens: Option<Hyphens>,
1106 pub line_break: Option<LineBreak>,
1107 pub text_indent: Option<TextIndent>,
1108 pub draw_style: Option<DrawStyle>,
1109 pub alpha: f32,
1110 pub font_variation_settings: Option<String>,
1112}
1113
1114impl SpanStyle {
1115 pub const fn default() -> Self {
1116 Self {
1117 color: None,
1118 font_size: None,
1119 font_weight: None,
1120 font_family: None,
1121 font_style: None,
1122 text_align: None,
1123 letter_spacing: None,
1124 line_height: None,
1125 background: None,
1126 text_decoration: None,
1127 text_direction: None,
1128 font_synthesis: None,
1129 baseline_shift: None,
1130 hyphens: None,
1131 line_break: None,
1132 text_indent: None,
1133 draw_style: None,
1134 alpha: 0.0,
1135 font_variation_settings: None,
1136 }
1137 }
1138
1139 pub fn color(mut self, c: Color) -> Self {
1140 self.color = Some(c);
1141 self
1142 }
1143
1144 pub fn font_size(mut self, px: f32) -> Self {
1145 self.font_size = Some(px);
1146 self
1147 }
1148
1149 pub fn text_decoration(mut self, d: TextDecoration) -> Self {
1150 self.text_decoration = Some(d);
1151 self
1152 }
1153
1154 pub fn font_weight(mut self, w: u16) -> Self {
1155 self.font_weight = Some(w);
1156 self
1157 }
1158
1159 pub fn font_style(mut self, s: u8) -> Self {
1160 self.font_style = Some(s);
1161 self
1162 }
1163
1164 pub fn draw_style(mut self, s: DrawStyle) -> Self {
1165 self.draw_style = Some(s);
1166 self
1167 }
1168
1169 pub fn background(mut self, c: Color) -> Self {
1170 self.background = Some(c);
1171 self
1172 }
1173
1174 pub fn baseline_shift(mut self, s: BaselineShift) -> Self {
1175 self.baseline_shift = Some(s);
1176 self
1177 }
1178}
1179
1180impl Default for SpanStyle {
1181 fn default() -> Self {
1182 Self::default()
1183 }
1184}
1185
1186#[derive(Debug, Clone, PartialEq)]
1188pub struct TextSpan {
1189 pub start: usize,
1191 pub end: usize,
1193 pub style: SpanStyle,
1194 pub url: Option<Arc<str>>,
1196}
1197
1198#[derive(Debug, Clone, PartialEq)]
1202pub struct AnnotatedString {
1203 pub text: String,
1204 pub spans: Arc<[TextSpan]>,
1205}
1206
1207impl AnnotatedString {
1208 pub fn new(text: impl Into<String>, spans: Vec<TextSpan>) -> Self {
1209 let text = text.into();
1210 Self {
1211 text,
1212 spans: spans.into(),
1213 }
1214 }
1215
1216 pub fn as_str(&self) -> &str {
1217 &self.text
1218 }
1219}
1220
1221impl From<String> for AnnotatedString {
1222 fn from(text: String) -> Self {
1223 Self {
1224 text,
1225 spans: Arc::from([]),
1226 }
1227 }
1228}
1229
1230impl From<&str> for AnnotatedString {
1231 fn from(text: &str) -> Self {
1232 Self {
1233 text: text.to_string(),
1234 spans: Arc::from([]),
1235 }
1236 }
1237}
1238
1239#[derive(Default)]
1241pub struct AnnotatedStringBuilder {
1242 text: String,
1243 spans: Vec<TextSpan>,
1244}
1245
1246impl AnnotatedStringBuilder {
1247 pub fn new() -> Self {
1248 Self::default()
1249 }
1250
1251 pub fn push(&mut self, text: &str) -> &mut Self {
1253 self.text.push_str(text);
1254 self
1255 }
1256
1257 pub fn push_with_style(&mut self, text: &str, style: SpanStyle) -> &mut Self {
1259 let start = self.text.len();
1260 self.text.push_str(text);
1261 let end = self.text.len();
1262 if start < end {
1263 self.spans.push(TextSpan {
1264 start,
1265 end,
1266 style,
1267 url: None,
1268 });
1269 }
1270 self
1271 }
1272
1273 pub fn push_color(&mut self, text: &str, color: Color) -> &mut Self {
1275 self.push_with_style(text, SpanStyle::default().color(color))
1276 }
1277
1278 pub fn push_link(&mut self, text: &str, url: impl Into<Arc<str>>) -> &mut Self {
1280 let start = self.text.len();
1281 self.text.push_str(text);
1282 let end = self.text.len();
1283 if start < end {
1284 self.spans.push(TextSpan {
1285 start,
1286 end,
1287 style: SpanStyle::default()
1288 .color(Color::from_rgba(0x15, 0x76, 0xFF, 255))
1289 .text_decoration(TextDecoration::UNDERLINE),
1290 url: Some(url.into()),
1291 });
1292 }
1293 self
1294 }
1295
1296 pub fn add_style(&mut self, start: usize, end: usize, style: SpanStyle) -> &mut Self {
1298 if start < end && end <= self.text.len() {
1299 self.spans.push(TextSpan {
1300 start,
1301 end,
1302 style,
1303 url: None,
1304 });
1305 }
1306 self
1307 }
1308
1309 pub fn build(&mut self) -> AnnotatedString {
1310 let text = std::mem::take(&mut self.text);
1311 self.spans.sort_by_key(|s| s.start);
1312 let mut merged: Vec<TextSpan> = Vec::new();
1314 for span in std::mem::take(&mut self.spans) {
1315 if let Some(last) = merged.last_mut()
1316 && last.end == span.start
1317 && last.style == span.style
1318 {
1319 last.end = span.end;
1320 continue;
1321 }
1322 merged.push(span);
1323 }
1324 AnnotatedString {
1325 text,
1326 spans: merged.into(),
1327 }
1328 }
1329}
1330
1331#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Default)]
1333pub enum TextAlign {
1334 Left,
1335 Right,
1336 Center,
1337 Justify,
1338 Start,
1339 End,
1340 #[default]
1341 Unspecified,
1342}
1343
1344#[derive(Clone, Copy, Debug, PartialEq)]
1346pub struct FontWeight(pub u16);
1347
1348impl FontWeight {
1349 pub const THIN: FontWeight = FontWeight(100);
1350 pub const EXTRA_LIGHT: FontWeight = FontWeight(200);
1351 pub const LIGHT: FontWeight = FontWeight(300);
1352 pub const NORMAL: FontWeight = FontWeight(400);
1353 pub const MEDIUM: FontWeight = FontWeight(500);
1354 pub const SEMI_BOLD: FontWeight = FontWeight(600);
1355 pub const BOLD: FontWeight = FontWeight(700);
1356 pub const EXTRA_BOLD: FontWeight = FontWeight(800);
1357 pub const BLACK: FontWeight = FontWeight(900);
1358}
1359
1360impl Default for FontWeight {
1361 fn default() -> Self {
1362 FontWeight::NORMAL
1363 }
1364}
1365
1366#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Default)]
1368pub enum FontStyle {
1369 #[default]
1370 Normal,
1371 Italic,
1372}
1373
1374#[derive(Clone, Copy, Debug, PartialEq, Default)]
1376pub struct TextDecoration {
1377 pub underline: bool,
1378 pub strikethrough: bool,
1379 pub color: Option<Color>,
1380}
1381
1382impl TextDecoration {
1383 pub const UNDERLINE: TextDecoration = TextDecoration {
1384 underline: true,
1385 strikethrough: false,
1386 color: None,
1387 };
1388 pub const STRIKETHROUGH: TextDecoration = TextDecoration {
1389 underline: false,
1390 strikethrough: true,
1391 color: None,
1392 };
1393}
1394
1395pub fn build_annotated_string(b: impl FnOnce(&mut AnnotatedStringBuilder)) -> AnnotatedString {
1397 let mut builder = AnnotatedStringBuilder::new();
1398 b(&mut builder);
1399 builder.build()
1400}