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
480#[derive(Clone, Debug)]
485pub struct PasswordOffsetMapping {
486 original: Vec<usize>,
487 transformed: Vec<usize>,
488}
489
490impl PasswordOffsetMapping {
491 pub fn new(original: &str, mask: char, masked: &str) -> Self {
492 let mut o = Vec::with_capacity(original.chars().count() + 1);
493 for (i, _) in original.char_indices() {
494 o.push(i);
495 }
496 o.push(original.len());
497 let mut t = vec![0usize];
498 let n = original.chars().count();
499 let mut byte = 0usize;
500 for _ in 0..n {
501 byte += mask.len_utf8();
502 t.push(byte);
503 }
504 if byte != masked.len() {
505 t.clear();
506 t.push(0);
507 let mut b = 0usize;
508 for ch in masked.chars() {
509 b += ch.len_utf8();
510 t.push(b);
511 }
512 }
513 Self {
514 original: o,
515 transformed: t,
516 }
517 }
518
519 fn snap_back(offsets: &[usize], text_len: usize, offset: usize) -> usize {
520 let o = offset.min(text_len);
521 match offsets.binary_search(&o) {
522 Ok(_) => o,
523 Err(i) => offsets.get(i.saturating_sub(1)).copied().unwrap_or(0),
524 }
525 }
526 fn index_of(offsets: &[usize], snapped: usize) -> usize {
527 match offsets.binary_search(&snapped) {
528 Ok(i) => i,
529 Err(i) => i.min(offsets.len().saturating_sub(1)),
530 }
531 }
532}
533
534impl OffsetMapping for PasswordOffsetMapping {
535 fn original_to_transformed(&self, offset: usize) -> usize {
536 let o_text_len = *self.original.last().unwrap_or(&0);
537 let snapped = Self::snap_back(&self.original, o_text_len, offset);
538 let idx = Self::index_of(&self.original, snapped);
539 self.transformed.get(idx).copied().unwrap_or(0)
540 }
541 fn transformed_to_original(&self, offset: usize) -> usize {
542 let t_text_len = *self.transformed.last().unwrap_or(&0);
543 let snapped = Self::snap_back(&self.transformed, t_text_len, offset);
544 let idx = Self::index_of(&self.transformed, snapped);
545 self.original.get(idx).copied().unwrap_or(0)
546 }
547 fn clone_box(&self) -> Box<dyn OffsetMapping> {
548 Box::new(self.clone())
549 }
550}
551
552impl VisualTransformation for PasswordVisualTransformation {
553 fn filter(&self, text: &AnnotatedString) -> TransformedText {
554 let masked_text: String = text.text.chars().map(|_| self.mask).collect();
555 let mapping = PasswordOffsetMapping::new(&text.text, self.mask, &masked_text);
556 TransformedText {
557 text: AnnotatedString::new(masked_text, vec![]),
558 offset_mapping: Box::new(mapping),
559 }
560 }
561}
562
563pub fn original_offset_to_display(original: &str, display: &str, original_byte: usize) -> usize {
566 original_offset_to_display_with_mapping(original, display, original_byte, None)
567}
568
569pub fn original_offset_to_display_with_mapping(
572 original: &str,
573 display: &str,
574 original_byte: usize,
575 offset_mapping: Option<&dyn OffsetMapping>,
576) -> usize {
577 if let Some(om) = offset_mapping {
578 om.original_to_transformed(original_byte)
579 } else {
580 let char_idx = original[..original_byte.min(original.len())]
581 .chars()
582 .count();
583 display
584 .char_indices()
585 .nth(char_idx)
586 .map(|(i, _)| i)
587 .unwrap_or(display.len())
588 }
589}
590
591#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
594pub enum KeyboardCapitalization {
595 #[default]
596 Unspecified,
597 None,
598 Characters,
599 Words,
600 Sentences,
601}
602
603#[derive(Clone, Copy, Debug, PartialEq)]
606pub struct Shadow {
607 pub color: Color,
608 pub offset_x: crate::units::Dp,
610 pub offset_y: crate::units::Dp,
612 pub blur_radius: crate::units::Dp,
614}
615
616#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
619pub enum FontSynthesis {
620 #[default]
621 Unspecified,
622 None,
623 Weight,
624 Style,
625 SmallCaps,
626 All,
627}
628
629#[derive(Clone, Copy, Debug, PartialEq)]
632pub struct BaselineShift(pub f32);
633
634#[allow(non_upper_case_globals)]
635impl BaselineShift {
636 pub const Unspecified: BaselineShift = BaselineShift(0.0);
638 pub const Superscript: BaselineShift = BaselineShift(-0.333);
640 pub const Subscript: BaselineShift = BaselineShift(0.2);
642}
643
644impl Default for BaselineShift {
645 fn default() -> Self {
646 BaselineShift::Unspecified
647 }
648}
649
650#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
652pub enum Hyphens {
653 #[default]
654 Unspecified,
655 None,
656 Auto,
657}
658
659#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
661pub enum LineBreak {
662 #[default]
663 Unspecified,
664 Simple,
665 Heading,
666 Paragraph,
667}
668
669#[derive(Clone, Copy, Debug, PartialEq)]
671pub struct TextIndent {
672 pub first_line: crate::units::Dp,
673 pub rest_lines: crate::units::Dp,
674}
675
676impl Default for TextIndent {
677 fn default() -> Self {
678 Self {
679 first_line: crate::units::Dp::ZERO,
680 rest_lines: crate::units::Dp::ZERO,
681 }
682 }
683}
684
685#[derive(Clone, Debug, PartialEq)]
688pub enum PathEffect {
689 Corner {
691 radius: f32,
693 },
694 Dash {
696 intervals: Vec<f32>,
698 phase: f32,
700 },
701}
702
703#[derive(Clone, Debug, PartialEq, Default)]
705pub enum DrawStyle {
706 #[default]
707 Fill,
708 Stroke {
709 width: f32,
711 cap: crate::StrokeCap,
713 join: crate::StrokeJoin,
715 miter: f32,
717 path_effect: Option<PathEffect>,
719 },
720 FillAndStroke {
725 width: f32,
727 cap: crate::StrokeCap,
729 join: crate::StrokeJoin,
731 miter: f32,
733 path_effect: Option<PathEffect>,
735 },
736}
737
738impl DrawStyle {
739 pub const fn stroke(width: f32) -> Self {
741 Self::Stroke {
742 width,
743 cap: crate::StrokeCap::Butt,
744 join: crate::StrokeJoin::Miter,
745 miter: 4.0,
746 path_effect: None,
747 }
748 }
749
750 pub const fn fill_and_stroke(width: f32) -> Self {
753 Self::FillAndStroke {
754 width,
755 cap: crate::StrokeCap::Butt,
756 join: crate::StrokeJoin::Miter,
757 miter: 4.0,
758 path_effect: None,
759 }
760 }
761
762 pub const fn stroke_params(
764 &self,
765 ) -> Option<(
766 f32,
767 crate::StrokeCap,
768 crate::StrokeJoin,
769 f32,
770 &Option<PathEffect>,
771 )> {
772 match self {
773 Self::Stroke {
774 width,
775 cap,
776 join,
777 miter,
778 path_effect,
779 }
780 | Self::FillAndStroke {
781 width,
782 cap,
783 join,
784 miter,
785 path_effect,
786 } => Some((*width, *cap, *join, *miter, path_effect)),
787 Self::Fill => None,
788 }
789 }
790
791 pub const fn draws_fill(&self) -> bool {
793 !matches!(self, Self::Stroke { .. })
794 }
795}
796
797#[derive(Clone, Debug)]
800pub struct TextStyle {
801 pub font_size: crate::units::Sp,
803 pub color: Option<Color>,
805 pub font_weight: Option<u16>,
807 pub font_family: Option<&'static str>,
809 pub font_style: Option<u8>,
811 pub text_align: crate::TextAlign,
813 pub letter_spacing: crate::units::Sp,
815 pub line_height: crate::units::Sp,
817 pub background: Option<Color>,
819 pub text_decoration: Option<crate::TextDecoration>,
821 pub shadow: Option<Shadow>,
823 pub text_direction: Option<crate::TextDirection>,
825 pub font_synthesis: FontSynthesis,
827 pub baseline_shift: BaselineShift,
829 pub hyphens: Hyphens,
831 pub line_break: LineBreak,
833 pub text_indent: Option<TextIndent>,
835 pub draw_style: DrawStyle,
837 pub alpha: f32,
839 pub locale_list: Option<String>,
841 pub font_feature_settings: Option<String>,
843 pub font_variation_settings: Option<String>,
845}
846
847impl Default for TextStyle {
848 fn default() -> Self {
849 Self {
850 font_size: crate::units::Sp::ZERO,
851 color: None,
852 font_weight: None,
853 font_family: Some("sans-serif"),
854 font_style: None,
855 text_align: crate::TextAlign::Unspecified,
856 letter_spacing: crate::units::Sp::ZERO,
857 line_height: crate::units::Sp::ZERO,
858 background: None,
859 text_decoration: None,
860 shadow: None,
861 text_direction: None,
862 font_synthesis: FontSynthesis::Unspecified,
863 baseline_shift: BaselineShift::Unspecified,
864 hyphens: Hyphens::Unspecified,
865 line_break: LineBreak::Unspecified,
866 text_indent: None,
867 draw_style: DrawStyle::Fill,
868 alpha: 0.0,
869 locale_list: None,
870 font_feature_settings: None,
871 font_variation_settings: None,
872 }
873 }
874}
875
876#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
878pub enum KeyboardType {
879 #[default]
880 Unspecified,
881 Text,
882 Ascii,
883 Number,
884 Phone,
885 Uri,
886 Email,
887 Password,
888 NumberPassword,
889 Decimal,
890 PasswordVisible,
891 PostalAddress,
892 PersonName,
893 EmailSubject,
894 ShortMessage,
895 LongMessage,
896 Filter,
897 Phonetic,
898 DateTime,
899 Date,
900 Time,
901 NumberSigned,
902 DecimalSigned,
903 DecimalPassword,
904 NumberPasswordSigned,
905 DecimalPasswordSigned,
906}
907
908#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
910pub enum ImeAction {
911 #[default]
912 Unspecified,
913 None,
914 Default,
915 Go,
916 Search,
917 Send,
918 Previous,
919 Next,
920 Done,
921}
922
923#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
930pub enum ImePurposeHint {
931 #[default]
932 Normal,
933 Password,
934 Email,
935 Url,
936 Phone,
937 Number,
938}
939
940impl KeyboardType {
941 pub fn ime_purpose_hint(self) -> ImePurposeHint {
943 use KeyboardType::*;
944 match self {
945 Password
946 | NumberPassword
947 | DecimalPassword
948 | NumberPasswordSigned
949 | DecimalPasswordSigned => ImePurposeHint::Password,
950 Email | EmailSubject => ImePurposeHint::Email,
951 Uri => ImePurposeHint::Url,
952 Phone => ImePurposeHint::Phone,
953 Number | NumberSigned | Decimal | DecimalSigned => ImePurposeHint::Number,
954 _ => ImePurposeHint::Normal,
955 }
956 }
957}
958
959pub trait KeyboardActionScope {
962 fn default_keyboard_action(&self, action: ImeAction);
963}
964
965#[derive(Clone, Default)]
968pub struct KeyboardActions {
969 pub on_done: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
970 pub on_go: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
971 pub on_next: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
972 pub on_previous: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
973 pub on_search: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
974 pub on_send: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
975}
976
977impl KeyboardActions {
978 pub fn on_any(f: impl Fn(ImeAction, &dyn KeyboardActionScope) + 'static) -> Self {
979 let f = Rc::new(f);
980 KeyboardActions {
981 on_done: Some({
982 let f = f.clone();
983 Rc::new(move |scope| f(ImeAction::Done, scope))
984 }),
985 on_go: Some({
986 let f = f.clone();
987 Rc::new(move |scope| f(ImeAction::Go, scope))
988 }),
989 on_next: Some({
990 let f = f.clone();
991 Rc::new(move |scope| f(ImeAction::Next, scope))
992 }),
993 on_previous: Some({
994 let f = f.clone();
995 Rc::new(move |scope| f(ImeAction::Previous, scope))
996 }),
997 on_search: Some({
998 let f = f.clone();
999 Rc::new(move |scope| f(ImeAction::Search, scope))
1000 }),
1001 on_send: Some(Rc::new(move |scope| f(ImeAction::Send, scope))),
1002 }
1003 }
1004}
1005
1006pub trait KeyboardActionHandler: Debug + 'static {
1009 fn on_keyboard_action(&self, perform_default: &dyn Fn());
1010}
1011
1012#[derive(Clone, Copy, Debug)]
1014pub struct DefaultKeyboardActionHandler;
1015
1016impl KeyboardActionHandler for DefaultKeyboardActionHandler {
1017 fn on_keyboard_action(&self, perform_default: &dyn Fn()) {
1018 perform_default();
1019 }
1020}
1021
1022pub trait TextFieldBuffer {
1025 fn text(&self) -> &str;
1026 fn set_text(&mut self, text: &str);
1027 fn selection(&self) -> TextRange;
1028 fn set_selection(&mut self, sel: TextRange);
1029 fn length(&self) -> usize;
1030 fn replace(&mut self, start: usize, end: usize, text: &str);
1031 fn insert(&mut self, index: usize, text: &str);
1032 fn delete(&mut self, start: usize, end: usize);
1033 fn place_cursor_before_char_at(&mut self, index: usize);
1034 fn place_cursor_at_end(&mut self);
1035 fn select_all(&mut self);
1036 fn revert_all_changes(&mut self);
1037 fn original_text(&self) -> &str;
1038 fn original_selection(&self) -> TextRange;
1039 fn has_selection(&self) -> bool;
1040}
1041
1042#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1045pub enum TextFieldLineLimits {
1046 SingleLine,
1047 MultiLine {
1048 min_height_in_lines: usize,
1049 max_height_in_lines: usize,
1050 },
1051}
1052
1053impl TextFieldLineLimits {
1054 pub fn default() -> Self {
1055 TextFieldLineLimits::MultiLine {
1056 min_height_in_lines: 1,
1057 max_height_in_lines: usize::MAX,
1058 }
1059 }
1060}
1061
1062pub trait TextFieldDecorator: Debug + 'static {
1065 fn decorate(&self, inner: crate::View) -> crate::View;
1066}
1067
1068#[derive(Clone, Copy, Debug)]
1070pub struct DefaultTextFieldDecorator;
1071
1072impl TextFieldDecorator for DefaultTextFieldDecorator {
1073 fn decorate(&self, inner: crate::View) -> crate::View {
1074 inner
1075 }
1076}
1077
1078pub trait InputTransformation: Debug + 'static {
1081 fn keyboard_options(&self) -> Option<KeyboardOptions> {
1082 None
1083 }
1084 fn transform_input(&self, buffer: &mut dyn TextFieldBuffer);
1085}
1086
1087pub trait OutputTransformation: Debug + 'static {
1090 fn transform_output(&self, buffer: &mut dyn TextFieldBuffer);
1091}
1092
1093pub struct CodepointTransformation {
1096 pub transform: Box<dyn Fn(usize, char) -> char>,
1097}
1098
1099impl Clone for CodepointTransformation {
1100 fn clone(&self) -> Self {
1101 panic!("CodepointTransformation::clone() is not supported -> use Rc instead");
1105 }
1106}
1107
1108impl CodepointTransformation {
1109 pub fn new(transform: impl Fn(usize, char) -> char + 'static) -> Self {
1110 CodepointTransformation {
1111 transform: Box::new(transform),
1112 }
1113 }
1114
1115 pub fn transform(&self, codepoint_index: usize, codepoint: char) -> char {
1116 (self.transform)(codepoint_index, codepoint)
1117 }
1118}
1119
1120#[derive(Clone, Copy, Debug, PartialEq)]
1123pub struct KeyboardOptions {
1124 pub keyboard_type: KeyboardType,
1125 pub capitalization: KeyboardCapitalization,
1126 pub ime_action: ImeAction,
1127 pub auto_correct_enabled: Option<bool>,
1128 pub show_keyboard_on_focus: Option<bool>,
1129 pub platform_ime_options: Option<&'static str>,
1130 pub hint_locales: Option<&'static str>,
1131}
1132
1133impl KeyboardOptions {
1134 pub const DEFAULT: KeyboardOptions = KeyboardOptions {
1135 keyboard_type: KeyboardType::Text,
1136 capitalization: KeyboardCapitalization::Unspecified,
1137 ime_action: ImeAction::Unspecified,
1138 auto_correct_enabled: None,
1139 show_keyboard_on_focus: None,
1140 platform_ime_options: None,
1141 hint_locales: None,
1142 };
1143
1144 pub const SECURE_TEXT_FIELD: KeyboardOptions = KeyboardOptions {
1145 keyboard_type: KeyboardType::Password,
1146 capitalization: KeyboardCapitalization::Unspecified,
1147 ime_action: ImeAction::Unspecified,
1148 auto_correct_enabled: Some(false),
1149 show_keyboard_on_focus: None,
1150 platform_ime_options: None,
1151 hint_locales: None,
1152 };
1153
1154 pub fn fill_unspecified_values_with(&self, other: Option<&KeyboardOptions>) -> KeyboardOptions {
1155 let other = match other {
1156 Some(o) => o,
1157 None => return *self,
1158 };
1159 KeyboardOptions {
1160 keyboard_type: if self.keyboard_type == KeyboardType::Unspecified {
1161 other.keyboard_type
1162 } else {
1163 self.keyboard_type
1164 },
1165 capitalization: if self.capitalization == KeyboardCapitalization::Unspecified {
1166 other.capitalization
1167 } else {
1168 self.capitalization
1169 },
1170 ime_action: if self.ime_action == ImeAction::Unspecified {
1171 other.ime_action
1172 } else {
1173 self.ime_action
1174 },
1175 auto_correct_enabled: self.auto_correct_enabled.or(other.auto_correct_enabled),
1176 show_keyboard_on_focus: self.show_keyboard_on_focus.or(other.show_keyboard_on_focus),
1177 platform_ime_options: self.platform_ime_options.or(other.platform_ime_options),
1178 hint_locales: self.hint_locales.or(other.hint_locales),
1179 }
1180 }
1181
1182 pub fn merge(&self, other: Option<&KeyboardOptions>) -> KeyboardOptions {
1186 other.map_or(*self, |o| o.fill_unspecified_values_with(Some(self)))
1187 }
1188}
1189
1190impl Default for KeyboardOptions {
1191 fn default() -> Self {
1192 Self::DEFAULT
1193 }
1194}
1195
1196#[derive(Debug, Clone, PartialEq)]
1197pub struct SpanStyle {
1198 pub color: Option<Color>,
1199 pub font_size: Option<crate::units::Sp>,
1200 pub font_weight: Option<u16>,
1201 pub font_family: Option<&'static str>,
1202 pub font_style: Option<u8>,
1203 pub text_align: Option<crate::TextAlign>,
1204 pub letter_spacing: Option<crate::units::Sp>,
1205 pub line_height: Option<crate::units::Sp>,
1206 pub background: Option<Color>,
1207 pub text_decoration: Option<crate::TextDecoration>,
1208 pub text_direction: Option<crate::TextDirection>,
1209 pub font_synthesis: Option<FontSynthesis>,
1210 pub baseline_shift: Option<BaselineShift>,
1211 pub hyphens: Option<Hyphens>,
1212 pub line_break: Option<LineBreak>,
1213 pub text_indent: Option<TextIndent>,
1214 pub draw_style: Option<DrawStyle>,
1215 pub alpha: f32,
1216 pub font_variation_settings: Option<String>,
1218}
1219
1220impl SpanStyle {
1221 pub const fn default() -> Self {
1222 Self {
1223 color: None,
1224 font_size: None,
1225 font_weight: None,
1226 font_family: None,
1227 font_style: None,
1228 text_align: None,
1229 letter_spacing: None,
1230 line_height: None,
1231 background: None,
1232 text_decoration: None,
1233 text_direction: None,
1234 font_synthesis: None,
1235 baseline_shift: None,
1236 hyphens: None,
1237 line_break: None,
1238 text_indent: None,
1239 draw_style: None,
1240 alpha: 0.0,
1241 font_variation_settings: None,
1242 }
1243 }
1244
1245 pub fn color(mut self, c: Color) -> Self {
1246 self.color = Some(c);
1247 self
1248 }
1249
1250 pub fn font_size(mut self, size: crate::units::Sp) -> Self {
1251 self.font_size = Some(size);
1252 self
1253 }
1254
1255 pub fn text_decoration(mut self, d: TextDecoration) -> Self {
1256 self.text_decoration = Some(d);
1257 self
1258 }
1259
1260 pub fn font_weight(mut self, w: u16) -> Self {
1261 self.font_weight = Some(w);
1262 self
1263 }
1264
1265 pub fn font_style(mut self, s: u8) -> Self {
1266 self.font_style = Some(s);
1267 self
1268 }
1269
1270 pub fn draw_style(mut self, s: DrawStyle) -> Self {
1271 self.draw_style = Some(s);
1272 self
1273 }
1274
1275 pub fn background(mut self, c: Color) -> Self {
1276 self.background = Some(c);
1277 self
1278 }
1279
1280 pub fn baseline_shift(mut self, s: BaselineShift) -> Self {
1281 self.baseline_shift = Some(s);
1282 self
1283 }
1284}
1285
1286impl Default for SpanStyle {
1287 fn default() -> Self {
1288 Self::default()
1289 }
1290}
1291
1292#[derive(Debug, Clone, PartialEq)]
1294pub struct TextSpan {
1295 pub start: usize,
1297 pub end: usize,
1299 pub style: SpanStyle,
1300 pub url: Option<Arc<str>>,
1302}
1303
1304#[derive(Debug, Clone, PartialEq)]
1308pub struct AnnotatedString {
1309 pub text: String,
1310 pub spans: Arc<[TextSpan]>,
1311}
1312
1313impl AnnotatedString {
1314 pub fn new(text: impl Into<String>, spans: Vec<TextSpan>) -> Self {
1315 let text = text.into();
1316 Self {
1317 text,
1318 spans: spans.into(),
1319 }
1320 }
1321
1322 pub fn as_str(&self) -> &str {
1323 &self.text
1324 }
1325}
1326
1327impl From<String> for AnnotatedString {
1328 fn from(text: String) -> Self {
1329 Self {
1330 text,
1331 spans: Arc::from([]),
1332 }
1333 }
1334}
1335
1336impl From<&str> for AnnotatedString {
1337 fn from(text: &str) -> Self {
1338 Self {
1339 text: text.to_string(),
1340 spans: Arc::from([]),
1341 }
1342 }
1343}
1344
1345#[derive(Default)]
1347pub struct AnnotatedStringBuilder {
1348 text: String,
1349 spans: Vec<TextSpan>,
1350}
1351
1352impl AnnotatedStringBuilder {
1353 pub fn new() -> Self {
1354 Self::default()
1355 }
1356
1357 pub fn push(&mut self, text: &str) -> &mut Self {
1359 self.text.push_str(text);
1360 self
1361 }
1362
1363 pub fn push_with_style(&mut self, text: &str, style: SpanStyle) -> &mut Self {
1365 let start = self.text.len();
1366 self.text.push_str(text);
1367 let end = self.text.len();
1368 if start < end {
1369 self.spans.push(TextSpan {
1370 start,
1371 end,
1372 style,
1373 url: None,
1374 });
1375 }
1376 self
1377 }
1378
1379 pub fn push_color(&mut self, text: &str, color: Color) -> &mut Self {
1381 self.push_with_style(text, SpanStyle::default().color(color))
1382 }
1383
1384 pub fn push_link(&mut self, text: &str, url: impl Into<Arc<str>>) -> &mut Self {
1386 let start = self.text.len();
1387 self.text.push_str(text);
1388 let end = self.text.len();
1389 if start < end {
1390 self.spans.push(TextSpan {
1391 start,
1392 end,
1393 style: SpanStyle::default()
1394 .color(Color::from_rgba(0x15, 0x76, 0xFF, 255))
1395 .text_decoration(TextDecoration::UNDERLINE),
1396 url: Some(url.into()),
1397 });
1398 }
1399 self
1400 }
1401
1402 pub fn add_style(&mut self, start: usize, end: usize, style: SpanStyle) -> &mut Self {
1404 if start < end && end <= self.text.len() {
1405 self.spans.push(TextSpan {
1406 start,
1407 end,
1408 style,
1409 url: None,
1410 });
1411 }
1412 self
1413 }
1414
1415 pub fn build(&mut self) -> AnnotatedString {
1416 let text = std::mem::take(&mut self.text);
1417 self.spans.sort_by_key(|s| s.start);
1418 let mut merged: Vec<TextSpan> = Vec::new();
1420 for span in std::mem::take(&mut self.spans) {
1421 if let Some(last) = merged.last_mut()
1422 && last.end == span.start
1423 && last.style == span.style
1424 {
1425 last.end = span.end;
1426 continue;
1427 }
1428 merged.push(span);
1429 }
1430 AnnotatedString {
1431 text,
1432 spans: merged.into(),
1433 }
1434 }
1435}
1436
1437#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Default)]
1439pub enum TextAlign {
1440 Left,
1441 Right,
1442 Center,
1443 Justify,
1444 Start,
1445 End,
1446 #[default]
1447 Unspecified,
1448}
1449
1450#[derive(Clone, Copy, Debug, PartialEq)]
1452pub struct FontWeight(pub u16);
1453
1454impl FontWeight {
1455 pub const THIN: FontWeight = FontWeight(100);
1456 pub const EXTRA_LIGHT: FontWeight = FontWeight(200);
1457 pub const LIGHT: FontWeight = FontWeight(300);
1458 pub const NORMAL: FontWeight = FontWeight(400);
1459 pub const MEDIUM: FontWeight = FontWeight(500);
1460 pub const SEMI_BOLD: FontWeight = FontWeight(600);
1461 pub const BOLD: FontWeight = FontWeight(700);
1462 pub const EXTRA_BOLD: FontWeight = FontWeight(800);
1463 pub const BLACK: FontWeight = FontWeight(900);
1464}
1465
1466impl Default for FontWeight {
1467 fn default() -> Self {
1468 FontWeight::NORMAL
1469 }
1470}
1471
1472#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Default)]
1474pub enum FontStyle {
1475 #[default]
1476 Normal,
1477 Italic,
1478}
1479
1480#[derive(Clone, Copy, Debug, PartialEq, Default)]
1482pub struct TextDecoration {
1483 pub underline: bool,
1484 pub strikethrough: bool,
1485 pub color: Option<Color>,
1486}
1487
1488impl TextDecoration {
1489 pub const UNDERLINE: TextDecoration = TextDecoration {
1490 underline: true,
1491 strikethrough: false,
1492 color: None,
1493 };
1494 pub const STRIKETHROUGH: TextDecoration = TextDecoration {
1495 underline: false,
1496 strikethrough: true,
1497 color: None,
1498 };
1499}
1500
1501pub fn build_annotated_string(b: impl FnOnce(&mut AnnotatedStringBuilder)) -> AnnotatedString {
1503 let mut builder = AnnotatedStringBuilder::new();
1504 b(&mut builder);
1505 builder.build()
1506}