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}
742
743impl Default for TextStyle {
744 fn default() -> Self {
745 Self {
746 font_size: 0.0,
747 color: None,
748 font_weight: None,
749 font_family: None,
750 font_style: None,
751 text_align: crate::TextAlign::Unspecified,
752 letter_spacing: 0.0,
753 line_height: 0.0,
754 background: None,
755 text_decoration: None,
756 shadow: None,
757 text_direction: None,
758 font_synthesis: FontSynthesis::Unspecified,
759 baseline_shift: BaselineShift::Unspecified,
760 hyphens: Hyphens::Unspecified,
761 line_break: LineBreak::Unspecified,
762 text_indent: None,
763 draw_style: DrawStyle::Fill,
764 alpha: 0.0,
765 locale_list: None,
766 font_feature_settings: None,
767 }
768 }
769}
770
771#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
773pub enum KeyboardType {
774 #[default]
775 Unspecified,
776 Text,
777 Ascii,
778 Number,
779 Phone,
780 Uri,
781 Email,
782 Password,
783 NumberPassword,
784 Decimal,
785 PasswordVisible,
786 PostalAddress,
787 PersonName,
788 EmailSubject,
789 ShortMessage,
790 LongMessage,
791 Filter,
792 Phonetic,
793 DateTime,
794 Date,
795 Time,
796 NumberSigned,
797 DecimalSigned,
798 DecimalPassword,
799 NumberPasswordSigned,
800 DecimalPasswordSigned,
801}
802
803#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
805pub enum ImeAction {
806 #[default]
807 Unspecified,
808 None,
809 Default,
810 Go,
811 Search,
812 Send,
813 Previous,
814 Next,
815 Done,
816}
817
818pub trait KeyboardActionScope {
821 fn default_keyboard_action(&self, action: ImeAction);
822}
823
824#[derive(Clone)]
827pub struct KeyboardActions {
828 pub on_done: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
829 pub on_go: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
830 pub on_next: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
831 pub on_previous: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
832 pub on_search: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
833 pub on_send: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
834}
835
836impl KeyboardActions {
837 pub fn on_any(f: impl Fn(ImeAction, &dyn KeyboardActionScope) + 'static) -> Self {
838 let f = Rc::new(f);
839 KeyboardActions {
840 on_done: Some({
841 let f = f.clone();
842 Rc::new(move |scope| f(ImeAction::Done, scope))
843 }),
844 on_go: Some({
845 let f = f.clone();
846 Rc::new(move |scope| f(ImeAction::Go, scope))
847 }),
848 on_next: Some({
849 let f = f.clone();
850 Rc::new(move |scope| f(ImeAction::Next, scope))
851 }),
852 on_previous: Some({
853 let f = f.clone();
854 Rc::new(move |scope| f(ImeAction::Previous, scope))
855 }),
856 on_search: Some({
857 let f = f.clone();
858 Rc::new(move |scope| f(ImeAction::Search, scope))
859 }),
860 on_send: Some({ Rc::new(move |scope| f(ImeAction::Send, scope)) }),
861 }
862 }
863}
864
865impl Default for KeyboardActions {
866 fn default() -> Self {
867 KeyboardActions {
868 on_done: None,
869 on_go: None,
870 on_next: None,
871 on_previous: None,
872 on_search: None,
873 on_send: None,
874 }
875 }
876}
877
878struct NoopKeyboardActionScope;
879impl KeyboardActionScope for NoopKeyboardActionScope {
880 fn default_keyboard_action(&self, _action: ImeAction) {}
881}
882
883pub trait KeyboardActionHandler: Debug + 'static {
886 fn on_keyboard_action(&self, perform_default: &dyn Fn());
887}
888
889#[derive(Clone, Copy, Debug)]
891pub struct DefaultKeyboardActionHandler;
892
893impl KeyboardActionHandler for DefaultKeyboardActionHandler {
894 fn on_keyboard_action(&self, perform_default: &dyn Fn()) {
895 perform_default();
896 }
897}
898
899pub trait TextFieldBuffer {
902 fn text(&self) -> &str;
903 fn set_text(&mut self, text: &str);
904 fn selection(&self) -> TextRange;
905 fn set_selection(&mut self, sel: TextRange);
906 fn length(&self) -> usize;
907 fn replace(&mut self, start: usize, end: usize, text: &str);
908 fn insert(&mut self, index: usize, text: &str);
909 fn delete(&mut self, start: usize, end: usize);
910 fn place_cursor_before_char_at(&mut self, index: usize);
911 fn place_cursor_at_end(&mut self);
912 fn select_all(&mut self);
913 fn revert_all_changes(&mut self);
914 fn original_text(&self) -> &str;
915 fn original_selection(&self) -> TextRange;
916 fn has_selection(&self) -> bool;
917}
918
919#[derive(Clone, Copy, Debug, PartialEq, Eq)]
922pub enum TextFieldLineLimits {
923 SingleLine,
924 MultiLine {
925 min_height_in_lines: usize,
926 max_height_in_lines: usize,
927 },
928}
929
930impl TextFieldLineLimits {
931 pub fn default() -> Self {
932 TextFieldLineLimits::MultiLine {
933 min_height_in_lines: 1,
934 max_height_in_lines: usize::MAX,
935 }
936 }
937}
938
939pub trait TextFieldDecorator: Debug + 'static {
942 fn decorate(&self, inner: crate::View) -> crate::View;
943}
944
945#[derive(Clone, Copy, Debug)]
947pub struct DefaultTextFieldDecorator;
948
949impl TextFieldDecorator for DefaultTextFieldDecorator {
950 fn decorate(&self, inner: crate::View) -> crate::View {
951 inner
952 }
953}
954
955pub trait InputTransformation: Debug + 'static {
958 fn keyboard_options(&self) -> Option<KeyboardOptions> {
959 None
960 }
961 fn transform_input(&self, buffer: &mut dyn TextFieldBuffer);
962}
963
964pub trait OutputTransformation: Debug + 'static {
967 fn transform_output(&self, buffer: &mut dyn TextFieldBuffer);
968}
969
970pub struct CodepointTransformation {
973 pub transform: Box<dyn Fn(usize, char) -> char>,
974}
975
976impl Clone for CodepointTransformation {
977 fn clone(&self) -> Self {
978 panic!("CodepointTransformation::clone() is not supported -> use Rc instead");
982 }
983}
984
985impl CodepointTransformation {
986 pub fn new(transform: impl Fn(usize, char) -> char + 'static) -> Self {
987 CodepointTransformation {
988 transform: Box::new(transform),
989 }
990 }
991
992 pub fn transform(&self, codepoint_index: usize, codepoint: char) -> char {
993 (self.transform)(codepoint_index, codepoint)
994 }
995}
996
997#[derive(Clone, Copy, Debug, PartialEq)]
1000pub struct KeyboardOptions {
1001 pub keyboard_type: KeyboardType,
1002 pub capitalization: KeyboardCapitalization,
1003 pub ime_action: ImeAction,
1004 pub auto_correct_enabled: Option<bool>,
1005 pub show_keyboard_on_focus: Option<bool>,
1006 pub platform_ime_options: Option<&'static str>,
1007 pub hint_locales: Option<&'static str>,
1008}
1009
1010impl KeyboardOptions {
1011 pub const DEFAULT: KeyboardOptions = KeyboardOptions {
1012 keyboard_type: KeyboardType::Text,
1013 capitalization: KeyboardCapitalization::Unspecified,
1014 ime_action: ImeAction::Unspecified,
1015 auto_correct_enabled: None,
1016 show_keyboard_on_focus: None,
1017 platform_ime_options: None,
1018 hint_locales: None,
1019 };
1020
1021 pub const SECURE_TEXT_FIELD: KeyboardOptions = KeyboardOptions {
1022 keyboard_type: KeyboardType::Password,
1023 capitalization: KeyboardCapitalization::Unspecified,
1024 ime_action: ImeAction::Unspecified,
1025 auto_correct_enabled: Some(false),
1026 show_keyboard_on_focus: None,
1027 platform_ime_options: None,
1028 hint_locales: None,
1029 };
1030
1031 pub fn fill_unspecified_values_with(&self, other: Option<&KeyboardOptions>) -> KeyboardOptions {
1032 let other = match other {
1033 Some(o) => o,
1034 None => return *self,
1035 };
1036 KeyboardOptions {
1037 keyboard_type: if self.keyboard_type == KeyboardType::Unspecified {
1038 other.keyboard_type
1039 } else {
1040 self.keyboard_type
1041 },
1042 capitalization: if self.capitalization == KeyboardCapitalization::Unspecified {
1043 other.capitalization
1044 } else {
1045 self.capitalization
1046 },
1047 ime_action: if self.ime_action == ImeAction::Unspecified {
1048 other.ime_action
1049 } else {
1050 self.ime_action
1051 },
1052 auto_correct_enabled: self.auto_correct_enabled.or(other.auto_correct_enabled),
1053 show_keyboard_on_focus: self.show_keyboard_on_focus.or(other.show_keyboard_on_focus),
1054 platform_ime_options: self.platform_ime_options.or(other.platform_ime_options),
1055 hint_locales: self.hint_locales.or(other.hint_locales),
1056 }
1057 }
1058
1059 pub fn merge(&self, other: Option<&KeyboardOptions>) -> KeyboardOptions {
1063 other.map_or(*self, |o| o.fill_unspecified_values_with(Some(self)))
1064 }
1065}
1066
1067impl Default for KeyboardOptions {
1068 fn default() -> Self {
1069 Self::DEFAULT
1070 }
1071}
1072
1073#[derive(Debug, Clone, PartialEq)]
1074pub struct SpanStyle {
1075 pub color: Option<Color>,
1076 pub font_size: Option<f32>,
1077 pub font_weight: Option<u16>,
1078 pub font_family: Option<&'static str>,
1079 pub font_style: Option<u8>,
1080 pub text_align: Option<crate::TextAlign>,
1081 pub letter_spacing: Option<f32>,
1082 pub line_height: Option<f32>,
1083 pub background: Option<Color>,
1084 pub text_decoration: Option<crate::TextDecoration>,
1085 pub text_direction: Option<crate::TextDirection>,
1086 pub font_synthesis: Option<FontSynthesis>,
1087 pub baseline_shift: Option<BaselineShift>,
1088 pub hyphens: Option<Hyphens>,
1089 pub line_break: Option<LineBreak>,
1090 pub text_indent: Option<TextIndent>,
1091 pub draw_style: Option<DrawStyle>,
1092 pub alpha: f32,
1093}
1094
1095impl SpanStyle {
1096 pub const fn default() -> Self {
1097 Self {
1098 color: None,
1099 font_size: None,
1100 font_weight: None,
1101 font_family: None,
1102 font_style: None,
1103 text_align: None,
1104 letter_spacing: None,
1105 line_height: None,
1106 background: None,
1107 text_decoration: None,
1108 text_direction: None,
1109 font_synthesis: None,
1110 baseline_shift: None,
1111 hyphens: None,
1112 line_break: None,
1113 text_indent: None,
1114 draw_style: None,
1115 alpha: 0.0,
1116 }
1117 }
1118
1119 pub fn color(mut self, c: Color) -> Self {
1120 self.color = Some(c);
1121 self
1122 }
1123
1124 pub fn font_size(mut self, px: f32) -> Self {
1125 self.font_size = Some(px);
1126 self
1127 }
1128
1129 pub fn text_decoration(mut self, d: TextDecoration) -> Self {
1130 self.text_decoration = Some(d);
1131 self
1132 }
1133
1134 pub fn font_weight(mut self, w: u16) -> Self {
1135 self.font_weight = Some(w);
1136 self
1137 }
1138
1139 pub fn font_style(mut self, s: u8) -> Self {
1140 self.font_style = Some(s);
1141 self
1142 }
1143
1144 pub fn draw_style(mut self, s: DrawStyle) -> Self {
1145 self.draw_style = Some(s);
1146 self
1147 }
1148
1149 pub fn background(mut self, c: Color) -> Self {
1150 self.background = Some(c);
1151 self
1152 }
1153
1154 pub fn baseline_shift(mut self, s: BaselineShift) -> Self {
1155 self.baseline_shift = Some(s);
1156 self
1157 }
1158}
1159
1160impl Default for SpanStyle {
1161 fn default() -> Self {
1162 Self::default()
1163 }
1164}
1165
1166#[derive(Debug, Clone, PartialEq)]
1168pub struct TextSpan {
1169 pub start: usize,
1171 pub end: usize,
1173 pub style: SpanStyle,
1174 pub url: Option<Arc<str>>,
1176}
1177
1178#[derive(Debug, Clone, PartialEq)]
1182pub struct AnnotatedString {
1183 pub text: String,
1184 pub spans: Arc<[TextSpan]>,
1185}
1186
1187impl AnnotatedString {
1188 pub fn new(text: impl Into<String>, spans: Vec<TextSpan>) -> Self {
1189 let text = text.into();
1190 Self {
1191 text,
1192 spans: spans.into(),
1193 }
1194 }
1195
1196 pub fn as_str(&self) -> &str {
1197 &self.text
1198 }
1199}
1200
1201impl From<String> for AnnotatedString {
1202 fn from(text: String) -> Self {
1203 Self {
1204 text,
1205 spans: Arc::from([]),
1206 }
1207 }
1208}
1209
1210impl From<&str> for AnnotatedString {
1211 fn from(text: &str) -> Self {
1212 Self {
1213 text: text.to_string(),
1214 spans: Arc::from([]),
1215 }
1216 }
1217}
1218
1219#[derive(Default)]
1221pub struct AnnotatedStringBuilder {
1222 text: String,
1223 spans: Vec<TextSpan>,
1224}
1225
1226impl AnnotatedStringBuilder {
1227 pub fn new() -> Self {
1228 Self::default()
1229 }
1230
1231 pub fn push(&mut self, text: &str) -> &mut Self {
1233 self.text.push_str(text);
1234 self
1235 }
1236
1237 pub fn push_with_style(&mut self, text: &str, style: SpanStyle) -> &mut Self {
1239 let start = self.text.len();
1240 self.text.push_str(text);
1241 let end = self.text.len();
1242 if start < end {
1243 self.spans.push(TextSpan {
1244 start,
1245 end,
1246 style,
1247 url: None,
1248 });
1249 }
1250 self
1251 }
1252
1253 pub fn push_color(&mut self, text: &str, color: Color) -> &mut Self {
1255 self.push_with_style(text, SpanStyle::default().color(color))
1256 }
1257
1258 pub fn push_link(&mut self, text: &str, url: impl Into<Arc<str>>) -> &mut Self {
1260 let start = self.text.len();
1261 self.text.push_str(text);
1262 let end = self.text.len();
1263 if start < end {
1264 self.spans.push(TextSpan {
1265 start,
1266 end,
1267 style: SpanStyle::default()
1268 .color(Color::from_rgba(0x15, 0x76, 0xFF, 255))
1269 .text_decoration(TextDecoration::UNDERLINE),
1270 url: Some(url.into()),
1271 });
1272 }
1273 self
1274 }
1275
1276 pub fn add_style(&mut self, start: usize, end: usize, style: SpanStyle) -> &mut Self {
1278 if start < end && end <= self.text.len() {
1279 self.spans.push(TextSpan {
1280 start,
1281 end,
1282 style,
1283 url: None,
1284 });
1285 }
1286 self
1287 }
1288
1289 pub fn build(&mut self) -> AnnotatedString {
1290 let text = std::mem::take(&mut self.text);
1291 self.spans.sort_by_key(|s| s.start);
1292 let mut merged: Vec<TextSpan> = Vec::new();
1294 for span in std::mem::take(&mut self.spans) {
1295 if let Some(last) = merged.last_mut()
1296 && last.end == span.start
1297 && last.style == span.style
1298 {
1299 last.end = span.end;
1300 continue;
1301 }
1302 merged.push(span);
1303 }
1304 AnnotatedString {
1305 text,
1306 spans: merged.into(),
1307 }
1308 }
1309}
1310
1311#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
1313pub enum TextAlign {
1314 Left,
1315 Right,
1316 Center,
1317 Justify,
1318 Start,
1319 End,
1320 Unspecified,
1321}
1322
1323impl Default for TextAlign {
1324 fn default() -> Self {
1325 TextAlign::Unspecified
1326 }
1327}
1328
1329#[derive(Clone, Copy, Debug, PartialEq)]
1331pub struct FontWeight(pub u16);
1332
1333impl FontWeight {
1334 pub const THIN: FontWeight = FontWeight(100);
1335 pub const EXTRA_LIGHT: FontWeight = FontWeight(200);
1336 pub const LIGHT: FontWeight = FontWeight(300);
1337 pub const NORMAL: FontWeight = FontWeight(400);
1338 pub const MEDIUM: FontWeight = FontWeight(500);
1339 pub const SEMI_BOLD: FontWeight = FontWeight(600);
1340 pub const BOLD: FontWeight = FontWeight(700);
1341 pub const EXTRA_BOLD: FontWeight = FontWeight(800);
1342 pub const BLACK: FontWeight = FontWeight(900);
1343}
1344
1345impl Default for FontWeight {
1346 fn default() -> Self {
1347 FontWeight::NORMAL
1348 }
1349}
1350
1351#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
1353pub enum FontStyle {
1354 Normal,
1355 Italic,
1356}
1357
1358impl Default for FontStyle {
1359 fn default() -> Self {
1360 FontStyle::Normal
1361 }
1362}
1363
1364#[derive(Clone, Copy, Debug, PartialEq)]
1366pub struct TextDecoration {
1367 pub underline: bool,
1368 pub strikethrough: bool,
1369 pub color: Option<Color>,
1370}
1371
1372impl TextDecoration {
1373 pub const UNDERLINE: TextDecoration = TextDecoration {
1374 underline: true,
1375 strikethrough: false,
1376 color: None,
1377 };
1378 pub const STRIKETHROUGH: TextDecoration = TextDecoration {
1379 underline: false,
1380 strikethrough: true,
1381 color: None,
1382 };
1383}
1384
1385impl Default for TextDecoration {
1386 fn default() -> Self {
1387 Self {
1388 underline: false,
1389 strikethrough: false,
1390 color: None,
1391 }
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}