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 super::TextRange::new(0, 0)
383 }
384
385 pub fn get_paragraph_direction(&self, _offset: usize) -> u8 {
386 0 }
388
389 pub fn get_bidi_run_direction(&self, _offset: usize) -> u8 {
390 0 }
392}
393
394pub trait OffsetMapping: Debug + Send + Sync + 'static {
395 fn original_to_transformed(&self, offset: usize) -> usize;
396 fn transformed_to_original(&self, offset: usize) -> usize;
397 fn clone_box(&self) -> Box<dyn OffsetMapping>;
398}
399
400#[derive(Clone, Copy, Debug)]
402pub struct IdentityOffsetMapping;
403
404impl OffsetMapping for IdentityOffsetMapping {
405 fn original_to_transformed(&self, offset: usize) -> usize {
406 offset
407 }
408 fn transformed_to_original(&self, offset: usize) -> usize {
409 offset
410 }
411 fn clone_box(&self) -> Box<dyn OffsetMapping> {
412 Box::new(*self)
413 }
414}
415
416pub trait VisualTransformation: Debug + Send + Sync + 'static {
418 fn filter(&self, text: &AnnotatedString) -> TransformedText;
421}
422
423pub struct TransformedText {
425 pub text: AnnotatedString,
426 pub offset_mapping: Box<dyn OffsetMapping>,
427}
428
429impl TransformedText {
430 pub fn new(text: AnnotatedString, offset_mapping: Box<dyn OffsetMapping>) -> Self {
431 TransformedText {
432 text,
433 offset_mapping,
434 }
435 }
436}
437
438impl Clone for TransformedText {
439 fn clone(&self) -> Self {
440 Self {
441 text: self.text.clone(),
442 offset_mapping: self.offset_mapping.clone_box(),
443 }
444 }
445}
446
447impl Debug for TransformedText {
448 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
449 f.debug_struct("TransformedText")
450 .field("text", &self.text.text)
451 .finish()
452 }
453}
454
455#[derive(Clone, Copy, Debug)]
456pub struct IdentityVisualTransformation;
457
458impl VisualTransformation for IdentityVisualTransformation {
459 fn filter(&self, text: &AnnotatedString) -> TransformedText {
460 TransformedText {
461 text: text.clone(),
462 offset_mapping: Box::new(IdentityOffsetMapping),
463 }
464 }
465}
466
467#[derive(Clone, Copy, Debug)]
470pub struct PasswordVisualTransformation {
471 pub mask: char,
472}
473
474impl Default for PasswordVisualTransformation {
475 fn default() -> Self {
476 Self { mask: '\u{2022}' }
477 }
478}
479
480impl VisualTransformation for PasswordVisualTransformation {
481 fn filter(&self, text: &AnnotatedString) -> TransformedText {
482 let masked_text: String = text.text.chars().map(|_| self.mask).collect();
483 TransformedText {
484 text: AnnotatedString::new(masked_text, vec![]),
485 offset_mapping: Box::new(IdentityOffsetMapping),
486 }
487 }
488}
489
490pub fn original_offset_to_display(original: &str, display: &str, original_byte: usize) -> usize {
493 original_offset_to_display_with_mapping(original, display, original_byte, None)
494}
495
496pub fn original_offset_to_display_with_mapping(
499 original: &str,
500 display: &str,
501 original_byte: usize,
502 offset_mapping: Option<&dyn OffsetMapping>,
503) -> usize {
504 if let Some(om) = offset_mapping {
505 om.original_to_transformed(original_byte)
506 } else {
507 let char_idx = original[..original_byte.min(original.len())]
508 .chars()
509 .count();
510 display
511 .char_indices()
512 .nth(char_idx)
513 .map(|(i, _)| i)
514 .unwrap_or(display.len())
515 }
516}
517
518#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
521pub enum KeyboardCapitalization {
522 #[default]
523 Unspecified,
524 None,
525 Characters,
526 Words,
527 Sentences,
528}
529
530#[derive(Clone, Copy, Debug, PartialEq)]
533pub struct Shadow {
534 pub color: Color,
535 pub offset_x: crate::units::Dp,
537 pub offset_y: crate::units::Dp,
539 pub blur_radius: crate::units::Dp,
541}
542
543#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
546pub enum FontSynthesis {
547 #[default]
548 Unspecified,
549 None,
550 Weight,
551 Style,
552 SmallCaps,
553 All,
554}
555
556#[derive(Clone, Copy, Debug, PartialEq)]
559pub struct BaselineShift(pub f32);
560
561#[allow(non_upper_case_globals)]
562impl BaselineShift {
563 pub const Unspecified: BaselineShift = BaselineShift(0.0);
565 pub const Superscript: BaselineShift = BaselineShift(-0.333);
567 pub const Subscript: BaselineShift = BaselineShift(0.2);
569}
570
571impl Default for BaselineShift {
572 fn default() -> Self {
573 BaselineShift::Unspecified
574 }
575}
576
577#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
579pub enum Hyphens {
580 #[default]
581 Unspecified,
582 None,
583 Auto,
584}
585
586#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
588pub enum LineBreak {
589 #[default]
590 Unspecified,
591 Simple,
592 Heading,
593 Paragraph,
594}
595
596#[derive(Clone, Copy, Debug, PartialEq)]
598pub struct TextIndent {
599 pub first_line: crate::units::Dp,
600 pub rest_lines: crate::units::Dp,
601}
602
603impl Default for TextIndent {
604 fn default() -> Self {
605 Self {
606 first_line: crate::units::Dp::ZERO,
607 rest_lines: crate::units::Dp::ZERO,
608 }
609 }
610}
611
612#[derive(Clone, Debug, PartialEq)]
615pub enum PathEffect {
616 Corner {
618 radius: f32,
620 },
621 Dash {
623 intervals: Vec<f32>,
625 phase: f32,
627 },
628}
629
630#[derive(Clone, Debug, PartialEq, Default)]
632pub enum DrawStyle {
633 #[default]
634 Fill,
635 Stroke {
636 width: f32,
638 cap: crate::StrokeCap,
640 join: crate::StrokeJoin,
642 miter: f32,
644 path_effect: Option<PathEffect>,
646 },
647}
648
649impl DrawStyle {
650 pub const fn stroke(width: f32) -> Self {
652 Self::Stroke {
653 width,
654 cap: crate::StrokeCap::Butt,
655 join: crate::StrokeJoin::Miter,
656 miter: 4.0,
657 path_effect: None,
658 }
659 }
660}
661
662#[derive(Clone, Debug)]
665pub struct TextStyle {
666 pub font_size: crate::units::Sp,
668 pub color: Option<Color>,
670 pub font_weight: Option<u16>,
672 pub font_family: Option<&'static str>,
674 pub font_style: Option<u8>,
676 pub text_align: crate::TextAlign,
678 pub letter_spacing: crate::units::Sp,
680 pub line_height: crate::units::Sp,
682 pub background: Option<Color>,
684 pub text_decoration: Option<crate::TextDecoration>,
686 pub shadow: Option<Shadow>,
688 pub text_direction: Option<crate::TextDirection>,
690 pub font_synthesis: FontSynthesis,
692 pub baseline_shift: BaselineShift,
694 pub hyphens: Hyphens,
696 pub line_break: LineBreak,
698 pub text_indent: Option<TextIndent>,
700 pub draw_style: DrawStyle,
702 pub alpha: f32,
704 pub locale_list: Option<String>,
706 pub font_feature_settings: Option<String>,
708 pub font_variation_settings: Option<String>,
710}
711
712impl Default for TextStyle {
713 fn default() -> Self {
714 Self {
715 font_size: crate::units::Sp::ZERO,
716 color: None,
717 font_weight: None,
718 font_family: Some("sans-serif"),
719 font_style: None,
720 text_align: crate::TextAlign::Unspecified,
721 letter_spacing: crate::units::Sp::ZERO,
722 line_height: crate::units::Sp::ZERO,
723 background: None,
724 text_decoration: None,
725 shadow: None,
726 text_direction: None,
727 font_synthesis: FontSynthesis::Unspecified,
728 baseline_shift: BaselineShift::Unspecified,
729 hyphens: Hyphens::Unspecified,
730 line_break: LineBreak::Unspecified,
731 text_indent: None,
732 draw_style: DrawStyle::Fill,
733 alpha: 0.0,
734 locale_list: None,
735 font_feature_settings: None,
736 font_variation_settings: None,
737 }
738 }
739}
740
741#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
743pub enum KeyboardType {
744 #[default]
745 Unspecified,
746 Text,
747 Ascii,
748 Number,
749 Phone,
750 Uri,
751 Email,
752 Password,
753 NumberPassword,
754 Decimal,
755 PasswordVisible,
756 PostalAddress,
757 PersonName,
758 EmailSubject,
759 ShortMessage,
760 LongMessage,
761 Filter,
762 Phonetic,
763 DateTime,
764 Date,
765 Time,
766 NumberSigned,
767 DecimalSigned,
768 DecimalPassword,
769 NumberPasswordSigned,
770 DecimalPasswordSigned,
771}
772
773#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
775pub enum ImeAction {
776 #[default]
777 Unspecified,
778 None,
779 Default,
780 Go,
781 Search,
782 Send,
783 Previous,
784 Next,
785 Done,
786}
787
788#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
795pub enum ImePurposeHint {
796 #[default]
797 Normal,
798 Password,
799 Email,
800 Url,
801 Phone,
802 Number,
803}
804
805impl KeyboardType {
806 pub fn ime_purpose_hint(self) -> ImePurposeHint {
808 use KeyboardType::*;
809 match self {
810 Password
811 | NumberPassword
812 | DecimalPassword
813 | NumberPasswordSigned
814 | DecimalPasswordSigned => ImePurposeHint::Password,
815 Email | EmailSubject => ImePurposeHint::Email,
816 Uri => ImePurposeHint::Url,
817 Phone => ImePurposeHint::Phone,
818 Number | NumberSigned | Decimal | DecimalSigned => ImePurposeHint::Number,
819 _ => ImePurposeHint::Normal,
820 }
821 }
822}
823
824pub trait KeyboardActionScope {
827 fn default_keyboard_action(&self, action: ImeAction);
828}
829
830#[derive(Clone, Default)]
833pub struct KeyboardActions {
834 pub on_done: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
835 pub on_go: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
836 pub on_next: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
837 pub on_previous: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
838 pub on_search: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
839 pub on_send: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
840}
841
842impl KeyboardActions {
843 pub fn on_any(f: impl Fn(ImeAction, &dyn KeyboardActionScope) + 'static) -> Self {
844 let f = Rc::new(f);
845 KeyboardActions {
846 on_done: Some({
847 let f = f.clone();
848 Rc::new(move |scope| f(ImeAction::Done, scope))
849 }),
850 on_go: Some({
851 let f = f.clone();
852 Rc::new(move |scope| f(ImeAction::Go, scope))
853 }),
854 on_next: Some({
855 let f = f.clone();
856 Rc::new(move |scope| f(ImeAction::Next, scope))
857 }),
858 on_previous: Some({
859 let f = f.clone();
860 Rc::new(move |scope| f(ImeAction::Previous, scope))
861 }),
862 on_search: Some({
863 let f = f.clone();
864 Rc::new(move |scope| f(ImeAction::Search, scope))
865 }),
866 on_send: Some(Rc::new(move |scope| f(ImeAction::Send, scope))),
867 }
868 }
869}
870
871pub trait KeyboardActionHandler: Debug + 'static {
874 fn on_keyboard_action(&self, perform_default: &dyn Fn());
875}
876
877#[derive(Clone, Copy, Debug)]
879pub struct DefaultKeyboardActionHandler;
880
881impl KeyboardActionHandler for DefaultKeyboardActionHandler {
882 fn on_keyboard_action(&self, perform_default: &dyn Fn()) {
883 perform_default();
884 }
885}
886
887pub trait TextFieldBuffer {
890 fn text(&self) -> &str;
891 fn set_text(&mut self, text: &str);
892 fn selection(&self) -> TextRange;
893 fn set_selection(&mut self, sel: TextRange);
894 fn length(&self) -> usize;
895 fn replace(&mut self, start: usize, end: usize, text: &str);
896 fn insert(&mut self, index: usize, text: &str);
897 fn delete(&mut self, start: usize, end: usize);
898 fn place_cursor_before_char_at(&mut self, index: usize);
899 fn place_cursor_at_end(&mut self);
900 fn select_all(&mut self);
901 fn revert_all_changes(&mut self);
902 fn original_text(&self) -> &str;
903 fn original_selection(&self) -> TextRange;
904 fn has_selection(&self) -> bool;
905}
906
907#[derive(Clone, Copy, Debug, PartialEq, Eq)]
910pub enum TextFieldLineLimits {
911 SingleLine,
912 MultiLine {
913 min_height_in_lines: usize,
914 max_height_in_lines: usize,
915 },
916}
917
918impl TextFieldLineLimits {
919 pub fn default() -> Self {
920 TextFieldLineLimits::MultiLine {
921 min_height_in_lines: 1,
922 max_height_in_lines: usize::MAX,
923 }
924 }
925}
926
927pub trait TextFieldDecorator: Debug + 'static {
930 fn decorate(&self, inner: crate::View) -> crate::View;
931}
932
933#[derive(Clone, Copy, Debug)]
935pub struct DefaultTextFieldDecorator;
936
937impl TextFieldDecorator for DefaultTextFieldDecorator {
938 fn decorate(&self, inner: crate::View) -> crate::View {
939 inner
940 }
941}
942
943pub trait InputTransformation: Debug + 'static {
946 fn keyboard_options(&self) -> Option<KeyboardOptions> {
947 None
948 }
949 fn transform_input(&self, buffer: &mut dyn TextFieldBuffer);
950}
951
952pub trait OutputTransformation: Debug + 'static {
955 fn transform_output(&self, buffer: &mut dyn TextFieldBuffer);
956}
957
958pub struct CodepointTransformation {
961 pub transform: Box<dyn Fn(usize, char) -> char>,
962}
963
964impl Clone for CodepointTransformation {
965 fn clone(&self) -> Self {
966 panic!("CodepointTransformation::clone() is not supported -> use Rc instead");
970 }
971}
972
973impl CodepointTransformation {
974 pub fn new(transform: impl Fn(usize, char) -> char + 'static) -> Self {
975 CodepointTransformation {
976 transform: Box::new(transform),
977 }
978 }
979
980 pub fn transform(&self, codepoint_index: usize, codepoint: char) -> char {
981 (self.transform)(codepoint_index, codepoint)
982 }
983}
984
985#[derive(Clone, Copy, Debug, PartialEq)]
988pub struct KeyboardOptions {
989 pub keyboard_type: KeyboardType,
990 pub capitalization: KeyboardCapitalization,
991 pub ime_action: ImeAction,
992 pub auto_correct_enabled: Option<bool>,
993 pub show_keyboard_on_focus: Option<bool>,
994 pub platform_ime_options: Option<&'static str>,
995 pub hint_locales: Option<&'static str>,
996}
997
998impl KeyboardOptions {
999 pub const DEFAULT: KeyboardOptions = KeyboardOptions {
1000 keyboard_type: KeyboardType::Text,
1001 capitalization: KeyboardCapitalization::Unspecified,
1002 ime_action: ImeAction::Unspecified,
1003 auto_correct_enabled: None,
1004 show_keyboard_on_focus: None,
1005 platform_ime_options: None,
1006 hint_locales: None,
1007 };
1008
1009 pub const SECURE_TEXT_FIELD: KeyboardOptions = KeyboardOptions {
1010 keyboard_type: KeyboardType::Password,
1011 capitalization: KeyboardCapitalization::Unspecified,
1012 ime_action: ImeAction::Unspecified,
1013 auto_correct_enabled: Some(false),
1014 show_keyboard_on_focus: None,
1015 platform_ime_options: None,
1016 hint_locales: None,
1017 };
1018
1019 pub fn fill_unspecified_values_with(&self, other: Option<&KeyboardOptions>) -> KeyboardOptions {
1020 let other = match other {
1021 Some(o) => o,
1022 None => return *self,
1023 };
1024 KeyboardOptions {
1025 keyboard_type: if self.keyboard_type == KeyboardType::Unspecified {
1026 other.keyboard_type
1027 } else {
1028 self.keyboard_type
1029 },
1030 capitalization: if self.capitalization == KeyboardCapitalization::Unspecified {
1031 other.capitalization
1032 } else {
1033 self.capitalization
1034 },
1035 ime_action: if self.ime_action == ImeAction::Unspecified {
1036 other.ime_action
1037 } else {
1038 self.ime_action
1039 },
1040 auto_correct_enabled: self.auto_correct_enabled.or(other.auto_correct_enabled),
1041 show_keyboard_on_focus: self.show_keyboard_on_focus.or(other.show_keyboard_on_focus),
1042 platform_ime_options: self.platform_ime_options.or(other.platform_ime_options),
1043 hint_locales: self.hint_locales.or(other.hint_locales),
1044 }
1045 }
1046
1047 pub fn merge(&self, other: Option<&KeyboardOptions>) -> KeyboardOptions {
1051 other.map_or(*self, |o| o.fill_unspecified_values_with(Some(self)))
1052 }
1053}
1054
1055impl Default for KeyboardOptions {
1056 fn default() -> Self {
1057 Self::DEFAULT
1058 }
1059}
1060
1061#[derive(Debug, Clone, PartialEq)]
1062pub struct SpanStyle {
1063 pub color: Option<Color>,
1064 pub font_size: Option<crate::units::Sp>,
1065 pub font_weight: Option<u16>,
1066 pub font_family: Option<&'static str>,
1067 pub font_style: Option<u8>,
1068 pub text_align: Option<crate::TextAlign>,
1069 pub letter_spacing: Option<crate::units::Sp>,
1070 pub line_height: Option<crate::units::Sp>,
1071 pub background: Option<Color>,
1072 pub text_decoration: Option<crate::TextDecoration>,
1073 pub text_direction: Option<crate::TextDirection>,
1074 pub font_synthesis: Option<FontSynthesis>,
1075 pub baseline_shift: Option<BaselineShift>,
1076 pub hyphens: Option<Hyphens>,
1077 pub line_break: Option<LineBreak>,
1078 pub text_indent: Option<TextIndent>,
1079 pub draw_style: Option<DrawStyle>,
1080 pub alpha: f32,
1081 pub font_variation_settings: Option<String>,
1083}
1084
1085impl SpanStyle {
1086 pub const fn default() -> Self {
1087 Self {
1088 color: None,
1089 font_size: None,
1090 font_weight: None,
1091 font_family: None,
1092 font_style: None,
1093 text_align: None,
1094 letter_spacing: None,
1095 line_height: None,
1096 background: None,
1097 text_decoration: None,
1098 text_direction: None,
1099 font_synthesis: None,
1100 baseline_shift: None,
1101 hyphens: None,
1102 line_break: None,
1103 text_indent: None,
1104 draw_style: None,
1105 alpha: 0.0,
1106 font_variation_settings: None,
1107 }
1108 }
1109
1110 pub fn color(mut self, c: Color) -> Self {
1111 self.color = Some(c);
1112 self
1113 }
1114
1115 pub fn font_size(mut self, size: crate::units::Sp) -> Self {
1116 self.font_size = Some(size);
1117 self
1118 }
1119
1120 pub fn text_decoration(mut self, d: TextDecoration) -> Self {
1121 self.text_decoration = Some(d);
1122 self
1123 }
1124
1125 pub fn font_weight(mut self, w: u16) -> Self {
1126 self.font_weight = Some(w);
1127 self
1128 }
1129
1130 pub fn font_style(mut self, s: u8) -> Self {
1131 self.font_style = Some(s);
1132 self
1133 }
1134
1135 pub fn draw_style(mut self, s: DrawStyle) -> Self {
1136 self.draw_style = Some(s);
1137 self
1138 }
1139
1140 pub fn background(mut self, c: Color) -> Self {
1141 self.background = Some(c);
1142 self
1143 }
1144
1145 pub fn baseline_shift(mut self, s: BaselineShift) -> Self {
1146 self.baseline_shift = Some(s);
1147 self
1148 }
1149}
1150
1151impl Default for SpanStyle {
1152 fn default() -> Self {
1153 Self::default()
1154 }
1155}
1156
1157#[derive(Debug, Clone, PartialEq)]
1159pub struct TextSpan {
1160 pub start: usize,
1162 pub end: usize,
1164 pub style: SpanStyle,
1165 pub url: Option<Arc<str>>,
1167}
1168
1169#[derive(Debug, Clone, PartialEq)]
1173pub struct AnnotatedString {
1174 pub text: String,
1175 pub spans: Arc<[TextSpan]>,
1176}
1177
1178impl AnnotatedString {
1179 pub fn new(text: impl Into<String>, spans: Vec<TextSpan>) -> Self {
1180 let text = text.into();
1181 Self {
1182 text,
1183 spans: spans.into(),
1184 }
1185 }
1186
1187 pub fn as_str(&self) -> &str {
1188 &self.text
1189 }
1190}
1191
1192impl From<String> for AnnotatedString {
1193 fn from(text: String) -> Self {
1194 Self {
1195 text,
1196 spans: Arc::from([]),
1197 }
1198 }
1199}
1200
1201impl From<&str> for AnnotatedString {
1202 fn from(text: &str) -> Self {
1203 Self {
1204 text: text.to_string(),
1205 spans: Arc::from([]),
1206 }
1207 }
1208}
1209
1210#[derive(Default)]
1212pub struct AnnotatedStringBuilder {
1213 text: String,
1214 spans: Vec<TextSpan>,
1215}
1216
1217impl AnnotatedStringBuilder {
1218 pub fn new() -> Self {
1219 Self::default()
1220 }
1221
1222 pub fn push(&mut self, text: &str) -> &mut Self {
1224 self.text.push_str(text);
1225 self
1226 }
1227
1228 pub fn push_with_style(&mut self, text: &str, style: SpanStyle) -> &mut Self {
1230 let start = self.text.len();
1231 self.text.push_str(text);
1232 let end = self.text.len();
1233 if start < end {
1234 self.spans.push(TextSpan {
1235 start,
1236 end,
1237 style,
1238 url: None,
1239 });
1240 }
1241 self
1242 }
1243
1244 pub fn push_color(&mut self, text: &str, color: Color) -> &mut Self {
1246 self.push_with_style(text, SpanStyle::default().color(color))
1247 }
1248
1249 pub fn push_link(&mut self, text: &str, url: impl Into<Arc<str>>) -> &mut Self {
1251 let start = self.text.len();
1252 self.text.push_str(text);
1253 let end = self.text.len();
1254 if start < end {
1255 self.spans.push(TextSpan {
1256 start,
1257 end,
1258 style: SpanStyle::default()
1259 .color(Color::from_rgba(0x15, 0x76, 0xFF, 255))
1260 .text_decoration(TextDecoration::UNDERLINE),
1261 url: Some(url.into()),
1262 });
1263 }
1264 self
1265 }
1266
1267 pub fn add_style(&mut self, start: usize, end: usize, style: SpanStyle) -> &mut Self {
1269 if start < end && end <= self.text.len() {
1270 self.spans.push(TextSpan {
1271 start,
1272 end,
1273 style,
1274 url: None,
1275 });
1276 }
1277 self
1278 }
1279
1280 pub fn build(&mut self) -> AnnotatedString {
1281 let text = std::mem::take(&mut self.text);
1282 self.spans.sort_by_key(|s| s.start);
1283 let mut merged: Vec<TextSpan> = Vec::new();
1285 for span in std::mem::take(&mut self.spans) {
1286 if let Some(last) = merged.last_mut()
1287 && last.end == span.start
1288 && last.style == span.style
1289 {
1290 last.end = span.end;
1291 continue;
1292 }
1293 merged.push(span);
1294 }
1295 AnnotatedString {
1296 text,
1297 spans: merged.into(),
1298 }
1299 }
1300}
1301
1302#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Default)]
1304pub enum TextAlign {
1305 Left,
1306 Right,
1307 Center,
1308 Justify,
1309 Start,
1310 End,
1311 #[default]
1312 Unspecified,
1313}
1314
1315#[derive(Clone, Copy, Debug, PartialEq)]
1317pub struct FontWeight(pub u16);
1318
1319impl FontWeight {
1320 pub const THIN: FontWeight = FontWeight(100);
1321 pub const EXTRA_LIGHT: FontWeight = FontWeight(200);
1322 pub const LIGHT: FontWeight = FontWeight(300);
1323 pub const NORMAL: FontWeight = FontWeight(400);
1324 pub const MEDIUM: FontWeight = FontWeight(500);
1325 pub const SEMI_BOLD: FontWeight = FontWeight(600);
1326 pub const BOLD: FontWeight = FontWeight(700);
1327 pub const EXTRA_BOLD: FontWeight = FontWeight(800);
1328 pub const BLACK: FontWeight = FontWeight(900);
1329}
1330
1331impl Default for FontWeight {
1332 fn default() -> Self {
1333 FontWeight::NORMAL
1334 }
1335}
1336
1337#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Default)]
1339pub enum FontStyle {
1340 #[default]
1341 Normal,
1342 Italic,
1343}
1344
1345#[derive(Clone, Copy, Debug, PartialEq, Default)]
1347pub struct TextDecoration {
1348 pub underline: bool,
1349 pub strikethrough: bool,
1350 pub color: Option<Color>,
1351}
1352
1353impl TextDecoration {
1354 pub const UNDERLINE: TextDecoration = TextDecoration {
1355 underline: true,
1356 strikethrough: false,
1357 color: None,
1358 };
1359 pub const STRIKETHROUGH: TextDecoration = TextDecoration {
1360 underline: false,
1361 strikethrough: true,
1362 color: None,
1363 };
1364}
1365
1366pub fn build_annotated_string(b: impl FnOnce(&mut AnnotatedStringBuilder)) -> AnnotatedString {
1368 let mut builder = AnnotatedStringBuilder::new();
1369 b(&mut builder);
1370 builder.build()
1371}