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