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