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
590impl BaselineShift {
591 pub const Unspecified: BaselineShift = BaselineShift(0.0);
593 pub const Superscript: BaselineShift = BaselineShift(-0.333);
595 pub const Subscript: BaselineShift = BaselineShift(0.2);
597}
598
599impl Default for BaselineShift {
600 fn default() -> Self {
601 BaselineShift::Unspecified
602 }
603}
604
605#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
607pub enum Hyphens {
608 #[default]
609 Unspecified,
610 None,
611 Auto,
612}
613
614#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
616pub enum LineBreak {
617 #[default]
618 Unspecified,
619 Simple,
620 Heading,
621 Paragraph,
622}
623
624#[derive(Clone, Copy, Debug, PartialEq)]
626pub struct TextIndent {
627 pub first_line: f32,
628 pub rest_lines: f32,
629}
630
631impl Default for TextIndent {
632 fn default() -> Self {
633 Self {
634 first_line: 0.0,
635 rest_lines: 0.0,
636 }
637 }
638}
639
640#[derive(Clone, Debug, PartialEq)]
643pub enum PathEffect {
644 Corner {
646 radius: f32,
648 },
649 Dash {
651 intervals: Vec<f32>,
653 phase: f32,
655 },
656}
657
658#[derive(Clone, Debug, PartialEq)]
660pub enum DrawStyle {
661 Fill,
662 Stroke {
663 width: f32,
665 cap: crate::StrokeCap,
667 join: crate::StrokeJoin,
669 miter: f32,
671 path_effect: Option<PathEffect>,
673 },
674}
675
676impl DrawStyle {
677 pub const fn stroke(width: f32) -> Self {
679 Self::Stroke {
680 width,
681 cap: crate::StrokeCap::Butt,
682 join: crate::StrokeJoin::Miter,
683 miter: 4.0,
684 path_effect: None,
685 }
686 }
687}
688
689impl Default for DrawStyle {
690 fn default() -> Self {
691 Self::Fill
692 }
693}
694
695#[derive(Clone, Debug)]
698pub struct TextStyle {
699 pub font_size: f32,
701 pub color: Option<Color>,
703 pub font_weight: Option<u16>,
705 pub font_family: Option<&'static str>,
707 pub font_style: Option<u8>,
709 pub text_align: crate::TextAlign,
711 pub letter_spacing: f32,
713 pub line_height: f32,
715 pub background: Option<Color>,
717 pub text_decoration: Option<crate::TextDecoration>,
719 pub shadow: Option<Shadow>,
721 pub text_direction: Option<crate::TextDirection>,
723 pub font_synthesis: FontSynthesis,
725 pub baseline_shift: BaselineShift,
727 pub hyphens: Hyphens,
729 pub line_break: LineBreak,
731 pub text_indent: Option<TextIndent>,
733 pub draw_style: DrawStyle,
735 pub alpha: f32,
737 pub locale_list: Option<String>,
739 pub font_feature_settings: Option<String>,
741 pub font_variation_settings: Option<String>,
743}
744
745impl Default for TextStyle {
746 fn default() -> Self {
747 Self {
748 font_size: 0.0,
749 color: None,
750 font_weight: None,
751 font_family: Some("sans-serif"),
752 font_style: None,
753 text_align: crate::TextAlign::Unspecified,
754 letter_spacing: 0.0,
755 line_height: 0.0,
756 background: None,
757 text_decoration: None,
758 shadow: None,
759 text_direction: None,
760 font_synthesis: FontSynthesis::Unspecified,
761 baseline_shift: BaselineShift::Unspecified,
762 hyphens: Hyphens::Unspecified,
763 line_break: LineBreak::Unspecified,
764 text_indent: None,
765 draw_style: DrawStyle::Fill,
766 alpha: 0.0,
767 locale_list: None,
768 font_feature_settings: None,
769 font_variation_settings: None,
770 }
771 }
772}
773
774#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
776pub enum KeyboardType {
777 #[default]
778 Unspecified,
779 Text,
780 Ascii,
781 Number,
782 Phone,
783 Uri,
784 Email,
785 Password,
786 NumberPassword,
787 Decimal,
788 PasswordVisible,
789 PostalAddress,
790 PersonName,
791 EmailSubject,
792 ShortMessage,
793 LongMessage,
794 Filter,
795 Phonetic,
796 DateTime,
797 Date,
798 Time,
799 NumberSigned,
800 DecimalSigned,
801 DecimalPassword,
802 NumberPasswordSigned,
803 DecimalPasswordSigned,
804}
805
806#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
808pub enum ImeAction {
809 #[default]
810 Unspecified,
811 None,
812 Default,
813 Go,
814 Search,
815 Send,
816 Previous,
817 Next,
818 Done,
819}
820
821pub trait KeyboardActionScope {
824 fn default_keyboard_action(&self, action: ImeAction);
825}
826
827#[derive(Clone)]
830pub struct KeyboardActions {
831 pub on_done: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
832 pub on_go: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
833 pub on_next: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
834 pub on_previous: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
835 pub on_search: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
836 pub on_send: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
837}
838
839impl KeyboardActions {
840 pub fn on_any(f: impl Fn(ImeAction, &dyn KeyboardActionScope) + 'static) -> Self {
841 let f = Rc::new(f);
842 KeyboardActions {
843 on_done: Some({
844 let f = f.clone();
845 Rc::new(move |scope| f(ImeAction::Done, scope))
846 }),
847 on_go: Some({
848 let f = f.clone();
849 Rc::new(move |scope| f(ImeAction::Go, scope))
850 }),
851 on_next: Some({
852 let f = f.clone();
853 Rc::new(move |scope| f(ImeAction::Next, scope))
854 }),
855 on_previous: Some({
856 let f = f.clone();
857 Rc::new(move |scope| f(ImeAction::Previous, scope))
858 }),
859 on_search: Some({
860 let f = f.clone();
861 Rc::new(move |scope| f(ImeAction::Search, scope))
862 }),
863 on_send: Some({ Rc::new(move |scope| f(ImeAction::Send, scope)) }),
864 }
865 }
866}
867
868impl Default for KeyboardActions {
869 fn default() -> Self {
870 KeyboardActions {
871 on_done: None,
872 on_go: None,
873 on_next: None,
874 on_previous: None,
875 on_search: None,
876 on_send: None,
877 }
878 }
879}
880
881struct NoopKeyboardActionScope;
882impl KeyboardActionScope for NoopKeyboardActionScope {
883 fn default_keyboard_action(&self, _action: ImeAction) {}
884}
885
886pub trait KeyboardActionHandler: Debug + 'static {
889 fn on_keyboard_action(&self, perform_default: &dyn Fn());
890}
891
892#[derive(Clone, Copy, Debug)]
894pub struct DefaultKeyboardActionHandler;
895
896impl KeyboardActionHandler for DefaultKeyboardActionHandler {
897 fn on_keyboard_action(&self, perform_default: &dyn Fn()) {
898 perform_default();
899 }
900}
901
902pub trait TextFieldBuffer {
905 fn text(&self) -> &str;
906 fn set_text(&mut self, text: &str);
907 fn selection(&self) -> TextRange;
908 fn set_selection(&mut self, sel: TextRange);
909 fn length(&self) -> usize;
910 fn replace(&mut self, start: usize, end: usize, text: &str);
911 fn insert(&mut self, index: usize, text: &str);
912 fn delete(&mut self, start: usize, end: usize);
913 fn place_cursor_before_char_at(&mut self, index: usize);
914 fn place_cursor_at_end(&mut self);
915 fn select_all(&mut self);
916 fn revert_all_changes(&mut self);
917 fn original_text(&self) -> &str;
918 fn original_selection(&self) -> TextRange;
919 fn has_selection(&self) -> bool;
920}
921
922#[derive(Clone, Copy, Debug, PartialEq, Eq)]
925pub enum TextFieldLineLimits {
926 SingleLine,
927 MultiLine {
928 min_height_in_lines: usize,
929 max_height_in_lines: usize,
930 },
931}
932
933impl TextFieldLineLimits {
934 pub fn default() -> Self {
935 TextFieldLineLimits::MultiLine {
936 min_height_in_lines: 1,
937 max_height_in_lines: usize::MAX,
938 }
939 }
940}
941
942pub trait TextFieldDecorator: Debug + 'static {
945 fn decorate(&self, inner: crate::View) -> crate::View;
946}
947
948#[derive(Clone, Copy, Debug)]
950pub struct DefaultTextFieldDecorator;
951
952impl TextFieldDecorator for DefaultTextFieldDecorator {
953 fn decorate(&self, inner: crate::View) -> crate::View {
954 inner
955 }
956}
957
958pub trait InputTransformation: Debug + 'static {
961 fn keyboard_options(&self) -> Option<KeyboardOptions> {
962 None
963 }
964 fn transform_input(&self, buffer: &mut dyn TextFieldBuffer);
965}
966
967pub trait OutputTransformation: Debug + 'static {
970 fn transform_output(&self, buffer: &mut dyn TextFieldBuffer);
971}
972
973pub struct CodepointTransformation {
976 pub transform: Box<dyn Fn(usize, char) -> char>,
977}
978
979impl Clone for CodepointTransformation {
980 fn clone(&self) -> Self {
981 panic!("CodepointTransformation::clone() is not supported -> use Rc instead");
985 }
986}
987
988impl CodepointTransformation {
989 pub fn new(transform: impl Fn(usize, char) -> char + 'static) -> Self {
990 CodepointTransformation {
991 transform: Box::new(transform),
992 }
993 }
994
995 pub fn transform(&self, codepoint_index: usize, codepoint: char) -> char {
996 (self.transform)(codepoint_index, codepoint)
997 }
998}
999
1000#[derive(Clone, Copy, Debug, PartialEq)]
1003pub struct KeyboardOptions {
1004 pub keyboard_type: KeyboardType,
1005 pub capitalization: KeyboardCapitalization,
1006 pub ime_action: ImeAction,
1007 pub auto_correct_enabled: Option<bool>,
1008 pub show_keyboard_on_focus: Option<bool>,
1009 pub platform_ime_options: Option<&'static str>,
1010 pub hint_locales: Option<&'static str>,
1011}
1012
1013impl KeyboardOptions {
1014 pub const DEFAULT: KeyboardOptions = KeyboardOptions {
1015 keyboard_type: KeyboardType::Text,
1016 capitalization: KeyboardCapitalization::Unspecified,
1017 ime_action: ImeAction::Unspecified,
1018 auto_correct_enabled: None,
1019 show_keyboard_on_focus: None,
1020 platform_ime_options: None,
1021 hint_locales: None,
1022 };
1023
1024 pub const SECURE_TEXT_FIELD: KeyboardOptions = KeyboardOptions {
1025 keyboard_type: KeyboardType::Password,
1026 capitalization: KeyboardCapitalization::Unspecified,
1027 ime_action: ImeAction::Unspecified,
1028 auto_correct_enabled: Some(false),
1029 show_keyboard_on_focus: None,
1030 platform_ime_options: None,
1031 hint_locales: None,
1032 };
1033
1034 pub fn fill_unspecified_values_with(&self, other: Option<&KeyboardOptions>) -> KeyboardOptions {
1035 let other = match other {
1036 Some(o) => o,
1037 None => return *self,
1038 };
1039 KeyboardOptions {
1040 keyboard_type: if self.keyboard_type == KeyboardType::Unspecified {
1041 other.keyboard_type
1042 } else {
1043 self.keyboard_type
1044 },
1045 capitalization: if self.capitalization == KeyboardCapitalization::Unspecified {
1046 other.capitalization
1047 } else {
1048 self.capitalization
1049 },
1050 ime_action: if self.ime_action == ImeAction::Unspecified {
1051 other.ime_action
1052 } else {
1053 self.ime_action
1054 },
1055 auto_correct_enabled: self.auto_correct_enabled.or(other.auto_correct_enabled),
1056 show_keyboard_on_focus: self.show_keyboard_on_focus.or(other.show_keyboard_on_focus),
1057 platform_ime_options: self.platform_ime_options.or(other.platform_ime_options),
1058 hint_locales: self.hint_locales.or(other.hint_locales),
1059 }
1060 }
1061
1062 pub fn merge(&self, other: Option<&KeyboardOptions>) -> KeyboardOptions {
1066 other.map_or(*self, |o| o.fill_unspecified_values_with(Some(self)))
1067 }
1068}
1069
1070impl Default for KeyboardOptions {
1071 fn default() -> Self {
1072 Self::DEFAULT
1073 }
1074}
1075
1076#[derive(Debug, Clone, PartialEq)]
1077pub struct SpanStyle {
1078 pub color: Option<Color>,
1079 pub font_size: Option<f32>,
1080 pub font_weight: Option<u16>,
1081 pub font_family: Option<&'static str>,
1082 pub font_style: Option<u8>,
1083 pub text_align: Option<crate::TextAlign>,
1084 pub letter_spacing: Option<f32>,
1085 pub line_height: Option<f32>,
1086 pub background: Option<Color>,
1087 pub text_decoration: Option<crate::TextDecoration>,
1088 pub text_direction: Option<crate::TextDirection>,
1089 pub font_synthesis: Option<FontSynthesis>,
1090 pub baseline_shift: Option<BaselineShift>,
1091 pub hyphens: Option<Hyphens>,
1092 pub line_break: Option<LineBreak>,
1093 pub text_indent: Option<TextIndent>,
1094 pub draw_style: Option<DrawStyle>,
1095 pub alpha: f32,
1096 pub font_variation_settings: Option<String>,
1098}
1099
1100impl SpanStyle {
1101 pub const fn default() -> Self {
1102 Self {
1103 color: None,
1104 font_size: None,
1105 font_weight: None,
1106 font_family: None,
1107 font_style: None,
1108 text_align: None,
1109 letter_spacing: None,
1110 line_height: None,
1111 background: None,
1112 text_decoration: None,
1113 text_direction: None,
1114 font_synthesis: None,
1115 baseline_shift: None,
1116 hyphens: None,
1117 line_break: None,
1118 text_indent: None,
1119 draw_style: None,
1120 alpha: 0.0,
1121 font_variation_settings: None,
1122 }
1123 }
1124
1125 pub fn color(mut self, c: Color) -> Self {
1126 self.color = Some(c);
1127 self
1128 }
1129
1130 pub fn font_size(mut self, px: f32) -> Self {
1131 self.font_size = Some(px);
1132 self
1133 }
1134
1135 pub fn text_decoration(mut self, d: TextDecoration) -> Self {
1136 self.text_decoration = Some(d);
1137 self
1138 }
1139
1140 pub fn font_weight(mut self, w: u16) -> Self {
1141 self.font_weight = Some(w);
1142 self
1143 }
1144
1145 pub fn font_style(mut self, s: u8) -> Self {
1146 self.font_style = Some(s);
1147 self
1148 }
1149
1150 pub fn draw_style(mut self, s: DrawStyle) -> Self {
1151 self.draw_style = Some(s);
1152 self
1153 }
1154
1155 pub fn background(mut self, c: Color) -> Self {
1156 self.background = Some(c);
1157 self
1158 }
1159
1160 pub fn baseline_shift(mut self, s: BaselineShift) -> Self {
1161 self.baseline_shift = Some(s);
1162 self
1163 }
1164}
1165
1166impl Default for SpanStyle {
1167 fn default() -> Self {
1168 Self::default()
1169 }
1170}
1171
1172#[derive(Debug, Clone, PartialEq)]
1174pub struct TextSpan {
1175 pub start: usize,
1177 pub end: usize,
1179 pub style: SpanStyle,
1180 pub url: Option<Arc<str>>,
1182}
1183
1184#[derive(Debug, Clone, PartialEq)]
1188pub struct AnnotatedString {
1189 pub text: String,
1190 pub spans: Arc<[TextSpan]>,
1191}
1192
1193impl AnnotatedString {
1194 pub fn new(text: impl Into<String>, spans: Vec<TextSpan>) -> Self {
1195 let text = text.into();
1196 Self {
1197 text,
1198 spans: spans.into(),
1199 }
1200 }
1201
1202 pub fn as_str(&self) -> &str {
1203 &self.text
1204 }
1205}
1206
1207impl From<String> for AnnotatedString {
1208 fn from(text: String) -> Self {
1209 Self {
1210 text,
1211 spans: Arc::from([]),
1212 }
1213 }
1214}
1215
1216impl From<&str> for AnnotatedString {
1217 fn from(text: &str) -> Self {
1218 Self {
1219 text: text.to_string(),
1220 spans: Arc::from([]),
1221 }
1222 }
1223}
1224
1225#[derive(Default)]
1227pub struct AnnotatedStringBuilder {
1228 text: String,
1229 spans: Vec<TextSpan>,
1230}
1231
1232impl AnnotatedStringBuilder {
1233 pub fn new() -> Self {
1234 Self::default()
1235 }
1236
1237 pub fn push(&mut self, text: &str) -> &mut Self {
1239 self.text.push_str(text);
1240 self
1241 }
1242
1243 pub fn push_with_style(&mut self, text: &str, style: SpanStyle) -> &mut Self {
1245 let start = self.text.len();
1246 self.text.push_str(text);
1247 let end = self.text.len();
1248 if start < end {
1249 self.spans.push(TextSpan {
1250 start,
1251 end,
1252 style,
1253 url: None,
1254 });
1255 }
1256 self
1257 }
1258
1259 pub fn push_color(&mut self, text: &str, color: Color) -> &mut Self {
1261 self.push_with_style(text, SpanStyle::default().color(color))
1262 }
1263
1264 pub fn push_link(&mut self, text: &str, url: impl Into<Arc<str>>) -> &mut Self {
1266 let start = self.text.len();
1267 self.text.push_str(text);
1268 let end = self.text.len();
1269 if start < end {
1270 self.spans.push(TextSpan {
1271 start,
1272 end,
1273 style: SpanStyle::default()
1274 .color(Color::from_rgba(0x15, 0x76, 0xFF, 255))
1275 .text_decoration(TextDecoration::UNDERLINE),
1276 url: Some(url.into()),
1277 });
1278 }
1279 self
1280 }
1281
1282 pub fn add_style(&mut self, start: usize, end: usize, style: SpanStyle) -> &mut Self {
1284 if start < end && end <= self.text.len() {
1285 self.spans.push(TextSpan {
1286 start,
1287 end,
1288 style,
1289 url: None,
1290 });
1291 }
1292 self
1293 }
1294
1295 pub fn build(&mut self) -> AnnotatedString {
1296 let text = std::mem::take(&mut self.text);
1297 self.spans.sort_by_key(|s| s.start);
1298 let mut merged: Vec<TextSpan> = Vec::new();
1300 for span in std::mem::take(&mut self.spans) {
1301 if let Some(last) = merged.last_mut()
1302 && last.end == span.start
1303 && last.style == span.style
1304 {
1305 last.end = span.end;
1306 continue;
1307 }
1308 merged.push(span);
1309 }
1310 AnnotatedString {
1311 text,
1312 spans: merged.into(),
1313 }
1314 }
1315}
1316
1317#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
1319pub enum TextAlign {
1320 Left,
1321 Right,
1322 Center,
1323 Justify,
1324 Start,
1325 End,
1326 Unspecified,
1327}
1328
1329impl Default for TextAlign {
1330 fn default() -> Self {
1331 TextAlign::Unspecified
1332 }
1333}
1334
1335#[derive(Clone, Copy, Debug, PartialEq)]
1337pub struct FontWeight(pub u16);
1338
1339impl FontWeight {
1340 pub const THIN: FontWeight = FontWeight(100);
1341 pub const EXTRA_LIGHT: FontWeight = FontWeight(200);
1342 pub const LIGHT: FontWeight = FontWeight(300);
1343 pub const NORMAL: FontWeight = FontWeight(400);
1344 pub const MEDIUM: FontWeight = FontWeight(500);
1345 pub const SEMI_BOLD: FontWeight = FontWeight(600);
1346 pub const BOLD: FontWeight = FontWeight(700);
1347 pub const EXTRA_BOLD: FontWeight = FontWeight(800);
1348 pub const BLACK: FontWeight = FontWeight(900);
1349}
1350
1351impl Default for FontWeight {
1352 fn default() -> Self {
1353 FontWeight::NORMAL
1354 }
1355}
1356
1357#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
1359pub enum FontStyle {
1360 Normal,
1361 Italic,
1362}
1363
1364impl Default for FontStyle {
1365 fn default() -> Self {
1366 FontStyle::Normal
1367 }
1368}
1369
1370#[derive(Clone, Copy, Debug, PartialEq)]
1372pub struct TextDecoration {
1373 pub underline: bool,
1374 pub strikethrough: bool,
1375 pub color: Option<Color>,
1376}
1377
1378impl TextDecoration {
1379 pub const UNDERLINE: TextDecoration = TextDecoration {
1380 underline: true,
1381 strikethrough: false,
1382 color: None,
1383 };
1384 pub const STRIKETHROUGH: TextDecoration = TextDecoration {
1385 underline: false,
1386 strikethrough: true,
1387 color: None,
1388 };
1389}
1390
1391impl Default for TextDecoration {
1392 fn default() -> Self {
1393 Self {
1394 underline: false,
1395 strikethrough: false,
1396 color: None,
1397 }
1398 }
1399}
1400
1401pub fn build_annotated_string(b: impl FnOnce(&mut AnnotatedStringBuilder)) -> AnnotatedString {
1403 let mut builder = AnnotatedStringBuilder::new();
1404 b(&mut builder);
1405 builder.build()
1406}