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, Copy, Debug, Default, PartialEq)]
74pub struct TextLayoutGeometry {
75 pub wrap_width_px: f32,
77 pub line_height_px: f32,
79 pub origin: (f32, f32),
81}
82
83#[derive(Clone, Default)]
90pub struct TextSelection {
91 range: Rc<std::cell::RefCell<Option<TextRange>>>,
92 geometry: Rc<std::cell::RefCell<TextLayoutGeometry>>,
93}
94
95impl TextSelection {
96 pub fn range(&self) -> Option<TextRange> {
97 *self.range.borrow()
98 }
99
100 pub fn set_range(&self, range: Option<TextRange>) {
101 *self.range.borrow_mut() = range;
102 }
103
104 pub fn geometry(&self) -> TextLayoutGeometry {
105 *self.geometry.borrow()
106 }
107
108 pub fn set_geometry(&self, geometry: TextLayoutGeometry) {
110 *self.geometry.borrow_mut() = geometry;
111 }
112}
113
114impl Debug for TextSelection {
115 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116 f.debug_struct("TextSelection")
117 .field("range", &self.range())
118 .finish_non_exhaustive()
119 }
120}
121
122#[derive(Clone, Debug, PartialEq)]
125pub struct TextFieldValue {
126 pub annotated_string: AnnotatedString,
128 pub selection: TextRange,
130 pub composition: Option<TextRange>,
132}
133
134impl TextFieldValue {
135 pub fn new(text: impl Into<String>) -> Self {
137 let annotated = AnnotatedString::from(text.into());
138 let len = annotated.text.len();
139 Self {
140 selection: TextRange::collapsed(len),
141 annotated_string: annotated,
142 composition: None,
143 }
144 }
145
146 pub fn from_annotated(annotated: AnnotatedString) -> Self {
148 let len = annotated.text.len();
149 Self {
150 selection: TextRange::collapsed(len),
151 annotated_string: annotated,
152 composition: None,
153 }
154 }
155
156 pub fn text(&self) -> &str {
158 &self.annotated_string.text
159 }
160
161 pub fn with_selection(mut self, start: usize, end: usize) -> Self {
162 let len = self.annotated_string.text.len();
163 self.selection = TextRange::new(start.min(len), end.min(len));
164 self
165 }
166
167 pub fn get_text_before_selection(&self, max_chars: usize) -> AnnotatedString {
168 let sel_min = self.selection.min();
169 let start = sel_min.saturating_sub(max_chars);
170 let text = self.annotated_string.text[start..sel_min].to_string();
171 let spans: Vec<TextSpan> = self
173 .annotated_string
174 .spans
175 .iter()
176 .filter(|s| s.start >= start && s.end <= sel_min)
177 .map(|s| TextSpan {
178 start: s.start - start,
179 end: s.end - start,
180 style: s.style.clone(),
181 url: s.url.clone(),
182 })
183 .collect();
184 AnnotatedString::new(text, spans)
185 }
186
187 pub fn get_text_after_selection(&self, max_chars: usize) -> AnnotatedString {
188 let sel_max = self.selection.max();
189 let end = (sel_max + max_chars).min(self.annotated_string.text.len());
190 let text = self.annotated_string.text[sel_max..end].to_string();
191 let spans: Vec<TextSpan> = self
192 .annotated_string
193 .spans
194 .iter()
195 .filter(|s| s.start >= sel_max && s.end <= end)
196 .map(|s| TextSpan {
197 start: s.start - sel_max,
198 end: s.end - sel_max,
199 style: s.style.clone(),
200 url: s.url.clone(),
201 })
202 .collect();
203 AnnotatedString::new(text, spans)
204 }
205
206 pub fn get_selected_text(&self) -> AnnotatedString {
207 let r = self.selection.min()..self.selection.max();
208 let text = self.annotated_string.text[r.clone()].to_string();
209 let spans: Vec<TextSpan> = self
210 .annotated_string
211 .spans
212 .iter()
213 .filter(|s| s.start >= r.start && s.end <= r.end)
214 .map(|s| TextSpan {
215 start: s.start - r.start,
216 end: s.end - r.start,
217 style: s.style.clone(),
218 url: s.url.clone(),
219 })
220 .collect();
221 AnnotatedString::new(text, spans)
222 }
223
224 pub fn copy(&self, annotated_string: AnnotatedString) -> Self {
225 TextFieldValue {
226 annotated_string,
227 selection: self.selection,
228 composition: self.composition,
229 }
230 }
231
232 pub fn copy_text(&self, text: String) -> Self {
233 TextFieldValue {
234 annotated_string: AnnotatedString::from(text),
235 selection: self.selection,
236 composition: self.composition,
237 }
238 }
239}
240
241#[derive(Clone, Debug)]
243pub struct TextLayoutResult {
244 pub line_count: usize,
245 pub width_px: f32,
246 pub height_px: f32,
247 pub first_baseline: f32,
248 pub last_baseline: f32,
249 pub did_overflow_width: bool,
250 pub did_overflow_height: bool,
251 pub lines: Vec<TextLineInfo>,
252}
253
254#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
256pub enum TextObfuscationMode {
257 Visible,
259 RevealLastTyped,
261 Hidden,
263 #[default]
265 System,
266}
267
268#[derive(Clone, Debug)]
270pub struct TextLineInfo {
271 pub start: usize,
273 pub end: usize,
275 pub top: f32,
277 pub baseline: f32,
279 pub bottom: f32,
281 pub left: f32,
283 pub right: f32,
285 pub width: f32,
287}
288
289impl TextLayoutResult {
290 pub fn get_line_start(&self, line_index: usize) -> Option<usize> {
291 self.lines.get(line_index).map(|l| l.start)
292 }
293
294 pub fn get_line_end(&self, line_index: usize) -> Option<usize> {
295 self.lines.get(line_index).map(|l| l.end)
296 }
297
298 pub fn get_line_end_visible(&self, line_index: usize) -> Option<usize> {
299 self.lines.get(line_index).map(|l| l.end)
300 }
301
302 pub fn is_line_ellipsized(&self, _line_index: usize) -> bool {
303 false
304 }
305
306 pub fn get_line_top(&self, line_index: usize) -> Option<f32> {
307 self.lines.get(line_index).map(|l| l.top)
308 }
309
310 pub fn get_line_baseline(&self, line_index: usize) -> Option<f32> {
311 self.lines.get(line_index).map(|l| l.baseline)
312 }
313
314 pub fn get_line_bottom(&self, line_index: usize) -> Option<f32> {
315 self.lines.get(line_index).map(|l| l.bottom)
316 }
317
318 pub fn get_line_left(&self, line_index: usize) -> Option<f32> {
319 self.lines.get(line_index).map(|l| l.left)
320 }
321
322 pub fn get_line_right(&self, line_index: usize) -> Option<f32> {
323 self.lines.get(line_index).map(|l| l.right)
324 }
325
326 pub fn get_line_for_offset(&self, offset: usize) -> usize {
327 for (i, line) in self.lines.iter().enumerate() {
328 if offset >= line.start && offset < line.end {
329 return i;
330 }
331 }
332 self.line_count.saturating_sub(1)
333 }
334
335 pub fn get_line_for_vertical_position(&self, vertical: f32) -> usize {
336 for (i, line) in self.lines.iter().enumerate() {
337 if vertical >= line.top && vertical < line.bottom {
338 return i;
339 }
340 }
341 self.line_count.saturating_sub(1)
342 }
343
344 pub fn get_horizontal_position(&self, offset: usize, _use_primary_direction: bool) -> f32 {
345 self.lines
346 .iter()
347 .find(|l| offset >= l.start && offset <= l.end)
348 .map(|l| l.left)
349 .unwrap_or(0.0)
350 }
351
352 pub fn has_visual_overflow(&self) -> bool {
353 self.did_overflow_width || self.did_overflow_height
354 }
355
356 pub fn get_bounding_box(&self, offset: usize) -> crate::Rect {
357 if self.lines.is_empty() || offset > self.lines.last().unwrap().end {
358 return crate::Rect::default();
359 }
360 let line = self
361 .lines
362 .iter()
363 .find(|l| offset >= l.start && offset < l.end)
364 .unwrap_or_else(|| {
365 if offset >= self.lines.last().unwrap().end {
366 self.lines.last().unwrap()
367 } else {
368 &self.lines[0]
369 }
370 });
371 let _line_idx = self.get_line_for_offset(offset);
372 let char_in_line = offset - line.start;
373 let pos_in_line = if char_in_line == 0 {
374 line.left
375 } else {
376 let chars = line.end - line.start;
377 if chars == 0 {
378 line.left
379 } else {
380 line.left + (line.width * (char_in_line as f32) / (chars as f32))
381 }
382 };
383 crate::Rect {
384 x: pos_in_line,
385 y: line.top,
386 w: if char_in_line < (line.end - line.start) {
387 line.width / (line.end - line.start).max(1) as f32
388 } else {
389 1.0
390 },
391 h: line.bottom - line.top,
392 }
393 }
394
395 pub fn get_cursor_rect(&self, offset: usize) -> crate::Rect {
396 let line = self
397 .lines
398 .iter()
399 .find(|l| offset >= l.start && offset <= l.end)
400 .unwrap_or_else(|| {
401 if offset >= self.lines.last().map(|l| l.end).unwrap_or(0) {
402 self.lines.last().unwrap()
403 } else {
404 &self.lines[0]
405 }
406 });
407 let char_in_line = offset.saturating_sub(line.start);
408 let chars = (line.end - line.start).max(1);
409 let x = line.left + (line.width * (char_in_line as f32) / (chars as f32));
410 crate::Rect {
411 x,
412 y: line.top,
413 w: 1.0,
414 h: line.bottom - line.top,
415 }
416 }
417
418 pub fn get_offset_for_position(&self, position: (f32, f32)) -> Option<usize> {
419 let line_idx = self.get_line_for_vertical_position(position.1);
420 let line = self.lines.get(line_idx)?;
421 if position.0 <= line.left {
422 return Some(line.start);
423 }
424 if position.0 >= line.right {
425 return Some(line.end);
426 }
427 let fraction = (position.0 - line.left) / line.width.max(1.0);
428 let offset_in_line = ((line.end - line.start) as f32 * fraction).round() as usize;
429 Some((line.start + offset_in_line).min(line.end))
430 }
431
432 pub fn get_word_boundary(&self, _offset: usize) -> super::TextRange {
433 super::TextRange::new(0, 0)
436 }
437
438 pub fn get_paragraph_direction(&self, _offset: usize) -> u8 {
439 0 }
441
442 pub fn get_bidi_run_direction(&self, _offset: usize) -> u8 {
443 0 }
445}
446
447pub trait OffsetMapping: Debug + Send + Sync + 'static {
448 fn original_to_transformed(&self, offset: usize) -> usize;
449 fn transformed_to_original(&self, offset: usize) -> usize;
450 fn clone_box(&self) -> Box<dyn OffsetMapping>;
451}
452
453#[derive(Clone, Copy, Debug)]
455pub struct IdentityOffsetMapping;
456
457impl OffsetMapping for IdentityOffsetMapping {
458 fn original_to_transformed(&self, offset: usize) -> usize {
459 offset
460 }
461 fn transformed_to_original(&self, offset: usize) -> usize {
462 offset
463 }
464 fn clone_box(&self) -> Box<dyn OffsetMapping> {
465 Box::new(*self)
466 }
467}
468
469pub trait VisualTransformation: Debug + Send + Sync + 'static {
471 fn filter(&self, text: &AnnotatedString) -> TransformedText;
474}
475
476pub struct TransformedText {
478 pub text: AnnotatedString,
479 pub offset_mapping: Box<dyn OffsetMapping>,
480}
481
482impl TransformedText {
483 pub fn new(text: AnnotatedString, offset_mapping: Box<dyn OffsetMapping>) -> Self {
484 TransformedText {
485 text,
486 offset_mapping,
487 }
488 }
489}
490
491impl Clone for TransformedText {
492 fn clone(&self) -> Self {
493 Self {
494 text: self.text.clone(),
495 offset_mapping: self.offset_mapping.clone_box(),
496 }
497 }
498}
499
500impl Debug for TransformedText {
501 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
502 f.debug_struct("TransformedText")
503 .field("text", &self.text.text)
504 .finish()
505 }
506}
507
508#[derive(Clone, Copy, Debug)]
509pub struct IdentityVisualTransformation;
510
511impl VisualTransformation for IdentityVisualTransformation {
512 fn filter(&self, text: &AnnotatedString) -> TransformedText {
513 TransformedText {
514 text: text.clone(),
515 offset_mapping: Box::new(IdentityOffsetMapping),
516 }
517 }
518}
519
520#[derive(Clone, Copy, Debug)]
523pub struct PasswordVisualTransformation {
524 pub mask: char,
525}
526
527impl Default for PasswordVisualTransformation {
528 fn default() -> Self {
529 Self { mask: '\u{2022}' }
530 }
531}
532
533#[derive(Clone, Debug)]
538pub struct PasswordOffsetMapping {
539 original: Vec<usize>,
540 transformed: Vec<usize>,
541}
542
543impl PasswordOffsetMapping {
544 pub fn new(original: &str, mask: char, masked: &str) -> Self {
545 let mut o = Vec::with_capacity(original.chars().count() + 1);
546 for (i, _) in original.char_indices() {
547 o.push(i);
548 }
549 o.push(original.len());
550 let mut t = vec![0usize];
551 let n = original.chars().count();
552 let mut byte = 0usize;
553 for _ in 0..n {
554 byte += mask.len_utf8();
555 t.push(byte);
556 }
557 if byte != masked.len() {
558 t.clear();
559 t.push(0);
560 let mut b = 0usize;
561 for ch in masked.chars() {
562 b += ch.len_utf8();
563 t.push(b);
564 }
565 }
566 Self {
567 original: o,
568 transformed: t,
569 }
570 }
571
572 fn snap_back(offsets: &[usize], text_len: usize, offset: usize) -> usize {
573 let o = offset.min(text_len);
574 match offsets.binary_search(&o) {
575 Ok(_) => o,
576 Err(i) => offsets.get(i.saturating_sub(1)).copied().unwrap_or(0),
577 }
578 }
579 fn index_of(offsets: &[usize], snapped: usize) -> usize {
580 match offsets.binary_search(&snapped) {
581 Ok(i) => i,
582 Err(i) => i.min(offsets.len().saturating_sub(1)),
583 }
584 }
585}
586
587impl OffsetMapping for PasswordOffsetMapping {
588 fn original_to_transformed(&self, offset: usize) -> usize {
589 let o_text_len = *self.original.last().unwrap_or(&0);
590 let snapped = Self::snap_back(&self.original, o_text_len, offset);
591 let idx = Self::index_of(&self.original, snapped);
592 self.transformed.get(idx).copied().unwrap_or(0)
593 }
594 fn transformed_to_original(&self, offset: usize) -> usize {
595 let t_text_len = *self.transformed.last().unwrap_or(&0);
596 let snapped = Self::snap_back(&self.transformed, t_text_len, offset);
597 let idx = Self::index_of(&self.transformed, snapped);
598 self.original.get(idx).copied().unwrap_or(0)
599 }
600 fn clone_box(&self) -> Box<dyn OffsetMapping> {
601 Box::new(self.clone())
602 }
603}
604
605impl VisualTransformation for PasswordVisualTransformation {
606 fn filter(&self, text: &AnnotatedString) -> TransformedText {
607 let masked_text: String = text.text.chars().map(|_| self.mask).collect();
608 let mapping = PasswordOffsetMapping::new(&text.text, self.mask, &masked_text);
609 TransformedText {
610 text: AnnotatedString::new(masked_text, vec![]),
611 offset_mapping: Box::new(mapping),
612 }
613 }
614}
615
616pub fn original_offset_to_display(original: &str, display: &str, original_byte: usize) -> usize {
619 original_offset_to_display_with_mapping(original, display, original_byte, None)
620}
621
622pub fn original_offset_to_display_with_mapping(
625 original: &str,
626 display: &str,
627 original_byte: usize,
628 offset_mapping: Option<&dyn OffsetMapping>,
629) -> usize {
630 if let Some(om) = offset_mapping {
631 om.original_to_transformed(original_byte)
632 } else {
633 let char_idx = original[..original_byte.min(original.len())]
634 .chars()
635 .count();
636 display
637 .char_indices()
638 .nth(char_idx)
639 .map(|(i, _)| i)
640 .unwrap_or(display.len())
641 }
642}
643
644#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
647pub enum KeyboardCapitalization {
648 #[default]
649 Unspecified,
650 None,
651 Characters,
652 Words,
653 Sentences,
654}
655
656#[derive(Clone, Copy, Debug, PartialEq)]
659pub struct Shadow {
660 pub color: Color,
661 pub offset_x: crate::units::Dp,
663 pub offset_y: crate::units::Dp,
665 pub blur_radius: crate::units::Dp,
667}
668
669#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
672pub enum FontSynthesis {
673 #[default]
674 Unspecified,
675 None,
676 Weight,
677 Style,
678 SmallCaps,
679 All,
680}
681
682#[derive(Clone, Copy, Debug, PartialEq)]
685pub struct BaselineShift(pub f32);
686
687#[allow(non_upper_case_globals)]
688impl BaselineShift {
689 pub const Unspecified: BaselineShift = BaselineShift(0.0);
691 pub const Superscript: BaselineShift = BaselineShift(-0.333);
693 pub const Subscript: BaselineShift = BaselineShift(0.2);
695}
696
697impl Default for BaselineShift {
698 fn default() -> Self {
699 BaselineShift::Unspecified
700 }
701}
702
703#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
705pub enum Hyphens {
706 #[default]
707 Unspecified,
708 None,
709 Auto,
710}
711
712#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
714pub enum LineBreak {
715 #[default]
716 Unspecified,
717 Simple,
718 Heading,
719 Paragraph,
720}
721
722#[derive(Clone, Copy, Debug, PartialEq)]
724pub struct TextIndent {
725 pub first_line: crate::units::Dp,
726 pub rest_lines: crate::units::Dp,
727}
728
729impl Default for TextIndent {
730 fn default() -> Self {
731 Self {
732 first_line: crate::units::Dp::ZERO,
733 rest_lines: crate::units::Dp::ZERO,
734 }
735 }
736}
737
738#[derive(Clone, Debug, PartialEq)]
741pub enum PathEffect {
742 Corner {
744 radius: f32,
746 },
747 Dash {
749 intervals: Vec<f32>,
751 phase: f32,
753 },
754}
755
756#[derive(Clone, Debug, PartialEq, Default)]
758pub enum DrawStyle {
759 #[default]
760 Fill,
761 Stroke {
762 width: f32,
764 cap: crate::StrokeCap,
766 join: crate::StrokeJoin,
768 miter: f32,
770 path_effect: Option<PathEffect>,
772 },
773 FillAndStroke {
778 width: f32,
780 cap: crate::StrokeCap,
782 join: crate::StrokeJoin,
784 miter: f32,
786 path_effect: Option<PathEffect>,
788 },
789}
790
791impl DrawStyle {
792 pub const fn stroke(width: f32) -> Self {
794 Self::Stroke {
795 width,
796 cap: crate::StrokeCap::Butt,
797 join: crate::StrokeJoin::Miter,
798 miter: 4.0,
799 path_effect: None,
800 }
801 }
802
803 pub const fn fill_and_stroke(width: f32) -> Self {
806 Self::FillAndStroke {
807 width,
808 cap: crate::StrokeCap::Butt,
809 join: crate::StrokeJoin::Miter,
810 miter: 4.0,
811 path_effect: None,
812 }
813 }
814
815 pub const fn stroke_params(
817 &self,
818 ) -> Option<(
819 f32,
820 crate::StrokeCap,
821 crate::StrokeJoin,
822 f32,
823 &Option<PathEffect>,
824 )> {
825 match self {
826 Self::Stroke {
827 width,
828 cap,
829 join,
830 miter,
831 path_effect,
832 }
833 | Self::FillAndStroke {
834 width,
835 cap,
836 join,
837 miter,
838 path_effect,
839 } => Some((*width, *cap, *join, *miter, path_effect)),
840 Self::Fill => None,
841 }
842 }
843
844 pub const fn draws_fill(&self) -> bool {
846 !matches!(self, Self::Stroke { .. })
847 }
848}
849
850#[derive(Clone, Debug)]
853pub struct TextStyle {
854 pub font_size: crate::units::Sp,
856 pub color: Option<Color>,
858 pub font_weight: Option<u16>,
860 pub font_family: Option<&'static str>,
862 pub font_style: Option<u8>,
864 pub text_align: crate::TextAlign,
866 pub letter_spacing: crate::units::Sp,
868 pub line_height: crate::units::Sp,
870 pub background: Option<Color>,
872 pub text_decoration: Option<crate::TextDecoration>,
874 pub shadow: Option<Shadow>,
876 pub text_direction: Option<crate::TextDirection>,
878 pub font_synthesis: FontSynthesis,
880 pub baseline_shift: BaselineShift,
882 pub hyphens: Hyphens,
884 pub line_break: LineBreak,
886 pub text_indent: Option<TextIndent>,
888 pub draw_style: DrawStyle,
890 pub alpha: f32,
892 pub locale_list: Option<String>,
894 pub font_feature_settings: Option<String>,
896 pub font_variation_settings: Option<String>,
898}
899
900impl Default for TextStyle {
901 fn default() -> Self {
902 Self {
903 font_size: crate::units::Sp::ZERO,
904 color: None,
905 font_weight: None,
906 font_family: Some("sans-serif"),
907 font_style: None,
908 text_align: crate::TextAlign::Unspecified,
909 letter_spacing: crate::units::Sp::ZERO,
910 line_height: crate::units::Sp::ZERO,
911 background: None,
912 text_decoration: None,
913 shadow: None,
914 text_direction: None,
915 font_synthesis: FontSynthesis::Unspecified,
916 baseline_shift: BaselineShift::Unspecified,
917 hyphens: Hyphens::Unspecified,
918 line_break: LineBreak::Unspecified,
919 text_indent: None,
920 draw_style: DrawStyle::Fill,
921 alpha: 0.0,
922 locale_list: None,
923 font_feature_settings: None,
924 font_variation_settings: None,
925 }
926 }
927}
928
929#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
931pub enum KeyboardType {
932 #[default]
933 Unspecified,
934 Text,
935 Ascii,
936 Number,
937 Phone,
938 Uri,
939 Email,
940 Password,
941 NumberPassword,
942 Decimal,
943 PasswordVisible,
944 PostalAddress,
945 PersonName,
946 EmailSubject,
947 ShortMessage,
948 LongMessage,
949 Filter,
950 Phonetic,
951 DateTime,
952 Date,
953 Time,
954 NumberSigned,
955 DecimalSigned,
956 DecimalPassword,
957 NumberPasswordSigned,
958 DecimalPasswordSigned,
959}
960
961#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
963pub enum ImeAction {
964 #[default]
965 Unspecified,
966 None,
967 Default,
968 Go,
969 Search,
970 Send,
971 Previous,
972 Next,
973 Done,
974}
975
976#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
983pub enum ImePurposeHint {
984 #[default]
985 Normal,
986 Password,
987 Email,
988 Url,
989 Phone,
990 Number,
991}
992
993impl KeyboardType {
994 pub fn ime_purpose_hint(self) -> ImePurposeHint {
996 use KeyboardType::*;
997 match self {
998 Password
999 | NumberPassword
1000 | DecimalPassword
1001 | NumberPasswordSigned
1002 | DecimalPasswordSigned => ImePurposeHint::Password,
1003 Email | EmailSubject => ImePurposeHint::Email,
1004 Uri => ImePurposeHint::Url,
1005 Phone => ImePurposeHint::Phone,
1006 Number | NumberSigned | Decimal | DecimalSigned => ImePurposeHint::Number,
1007 _ => ImePurposeHint::Normal,
1008 }
1009 }
1010}
1011
1012pub trait KeyboardActionScope {
1015 fn default_keyboard_action(&self, action: ImeAction);
1016}
1017
1018#[derive(Clone, Default)]
1021pub struct KeyboardActions {
1022 pub on_done: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
1023 pub on_go: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
1024 pub on_next: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
1025 pub on_previous: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
1026 pub on_search: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
1027 pub on_send: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
1028}
1029
1030impl KeyboardActions {
1031 pub fn on_any(f: impl Fn(ImeAction, &dyn KeyboardActionScope) + 'static) -> Self {
1032 let f = Rc::new(f);
1033 KeyboardActions {
1034 on_done: Some({
1035 let f = f.clone();
1036 Rc::new(move |scope| f(ImeAction::Done, scope))
1037 }),
1038 on_go: Some({
1039 let f = f.clone();
1040 Rc::new(move |scope| f(ImeAction::Go, scope))
1041 }),
1042 on_next: Some({
1043 let f = f.clone();
1044 Rc::new(move |scope| f(ImeAction::Next, scope))
1045 }),
1046 on_previous: Some({
1047 let f = f.clone();
1048 Rc::new(move |scope| f(ImeAction::Previous, scope))
1049 }),
1050 on_search: Some({
1051 let f = f.clone();
1052 Rc::new(move |scope| f(ImeAction::Search, scope))
1053 }),
1054 on_send: Some(Rc::new(move |scope| f(ImeAction::Send, scope))),
1055 }
1056 }
1057}
1058
1059pub trait KeyboardActionHandler: Debug + 'static {
1062 fn on_keyboard_action(&self, perform_default: &dyn Fn());
1063}
1064
1065#[derive(Clone, Copy, Debug)]
1067pub struct DefaultKeyboardActionHandler;
1068
1069impl KeyboardActionHandler for DefaultKeyboardActionHandler {
1070 fn on_keyboard_action(&self, perform_default: &dyn Fn()) {
1071 perform_default();
1072 }
1073}
1074
1075pub trait TextFieldBuffer {
1078 fn text(&self) -> &str;
1079 fn set_text(&mut self, text: &str);
1080 fn selection(&self) -> TextRange;
1081 fn set_selection(&mut self, sel: TextRange);
1082 fn length(&self) -> usize;
1083 fn replace(&mut self, start: usize, end: usize, text: &str);
1084 fn insert(&mut self, index: usize, text: &str);
1085 fn delete(&mut self, start: usize, end: usize);
1086 fn place_cursor_before_char_at(&mut self, index: usize);
1087 fn place_cursor_at_end(&mut self);
1088 fn select_all(&mut self);
1089 fn revert_all_changes(&mut self);
1090 fn original_text(&self) -> &str;
1091 fn original_selection(&self) -> TextRange;
1092 fn has_selection(&self) -> bool;
1093}
1094
1095#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1098pub enum TextFieldLineLimits {
1099 SingleLine,
1100 MultiLine {
1101 min_height_in_lines: usize,
1102 max_height_in_lines: usize,
1103 },
1104}
1105
1106impl TextFieldLineLimits {
1107 pub fn default() -> Self {
1108 TextFieldLineLimits::MultiLine {
1109 min_height_in_lines: 1,
1110 max_height_in_lines: usize::MAX,
1111 }
1112 }
1113}
1114
1115pub trait TextFieldDecorator: Debug + 'static {
1118 fn decorate(&self, inner: crate::View) -> crate::View;
1119}
1120
1121#[derive(Clone, Copy, Debug)]
1123pub struct DefaultTextFieldDecorator;
1124
1125impl TextFieldDecorator for DefaultTextFieldDecorator {
1126 fn decorate(&self, inner: crate::View) -> crate::View {
1127 inner
1128 }
1129}
1130
1131pub trait InputTransformation: Debug + 'static {
1134 fn keyboard_options(&self) -> Option<KeyboardOptions> {
1135 None
1136 }
1137 fn transform_input(&self, buffer: &mut dyn TextFieldBuffer);
1138}
1139
1140pub trait OutputTransformation: Debug + 'static {
1143 fn transform_output(&self, buffer: &mut dyn TextFieldBuffer);
1144}
1145
1146pub struct CodepointTransformation {
1149 pub transform: Box<dyn Fn(usize, char) -> char>,
1150}
1151
1152impl Clone for CodepointTransformation {
1153 fn clone(&self) -> Self {
1154 panic!("CodepointTransformation::clone() is not supported -> use Rc instead");
1158 }
1159}
1160
1161impl CodepointTransformation {
1162 pub fn new(transform: impl Fn(usize, char) -> char + 'static) -> Self {
1163 CodepointTransformation {
1164 transform: Box::new(transform),
1165 }
1166 }
1167
1168 pub fn transform(&self, codepoint_index: usize, codepoint: char) -> char {
1169 (self.transform)(codepoint_index, codepoint)
1170 }
1171}
1172
1173#[derive(Clone, Copy, Debug, PartialEq)]
1176pub struct KeyboardOptions {
1177 pub keyboard_type: KeyboardType,
1178 pub capitalization: KeyboardCapitalization,
1179 pub ime_action: ImeAction,
1180 pub auto_correct_enabled: Option<bool>,
1181 pub show_keyboard_on_focus: Option<bool>,
1182 pub platform_ime_options: Option<&'static str>,
1183 pub hint_locales: Option<&'static str>,
1184}
1185
1186impl KeyboardOptions {
1187 pub const DEFAULT: KeyboardOptions = KeyboardOptions {
1188 keyboard_type: KeyboardType::Text,
1189 capitalization: KeyboardCapitalization::Unspecified,
1190 ime_action: ImeAction::Unspecified,
1191 auto_correct_enabled: None,
1192 show_keyboard_on_focus: None,
1193 platform_ime_options: None,
1194 hint_locales: None,
1195 };
1196
1197 pub const SECURE_TEXT_FIELD: KeyboardOptions = KeyboardOptions {
1198 keyboard_type: KeyboardType::Password,
1199 capitalization: KeyboardCapitalization::Unspecified,
1200 ime_action: ImeAction::Unspecified,
1201 auto_correct_enabled: Some(false),
1202 show_keyboard_on_focus: None,
1203 platform_ime_options: None,
1204 hint_locales: None,
1205 };
1206
1207 pub fn fill_unspecified_values_with(&self, other: Option<&KeyboardOptions>) -> KeyboardOptions {
1208 let other = match other {
1209 Some(o) => o,
1210 None => return *self,
1211 };
1212 KeyboardOptions {
1213 keyboard_type: if self.keyboard_type == KeyboardType::Unspecified {
1214 other.keyboard_type
1215 } else {
1216 self.keyboard_type
1217 },
1218 capitalization: if self.capitalization == KeyboardCapitalization::Unspecified {
1219 other.capitalization
1220 } else {
1221 self.capitalization
1222 },
1223 ime_action: if self.ime_action == ImeAction::Unspecified {
1224 other.ime_action
1225 } else {
1226 self.ime_action
1227 },
1228 auto_correct_enabled: self.auto_correct_enabled.or(other.auto_correct_enabled),
1229 show_keyboard_on_focus: self.show_keyboard_on_focus.or(other.show_keyboard_on_focus),
1230 platform_ime_options: self.platform_ime_options.or(other.platform_ime_options),
1231 hint_locales: self.hint_locales.or(other.hint_locales),
1232 }
1233 }
1234
1235 pub fn merge(&self, other: Option<&KeyboardOptions>) -> KeyboardOptions {
1239 other.map_or(*self, |o| o.fill_unspecified_values_with(Some(self)))
1240 }
1241}
1242
1243impl Default for KeyboardOptions {
1244 fn default() -> Self {
1245 Self::DEFAULT
1246 }
1247}
1248
1249#[derive(Debug, Clone, PartialEq)]
1250pub struct SpanStyle {
1251 pub color: Option<Color>,
1252 pub font_size: Option<crate::units::Sp>,
1253 pub font_weight: Option<u16>,
1254 pub font_family: Option<&'static str>,
1255 pub font_style: Option<u8>,
1256 pub text_align: Option<crate::TextAlign>,
1257 pub letter_spacing: Option<crate::units::Sp>,
1258 pub line_height: Option<crate::units::Sp>,
1259 pub background: Option<Color>,
1260 pub text_decoration: Option<crate::TextDecoration>,
1261 pub text_direction: Option<crate::TextDirection>,
1262 pub font_synthesis: Option<FontSynthesis>,
1263 pub baseline_shift: Option<BaselineShift>,
1264 pub hyphens: Option<Hyphens>,
1265 pub line_break: Option<LineBreak>,
1266 pub text_indent: Option<TextIndent>,
1267 pub draw_style: Option<DrawStyle>,
1268 pub alpha: f32,
1269 pub font_variation_settings: Option<String>,
1271}
1272
1273impl SpanStyle {
1274 pub const fn default() -> Self {
1275 Self {
1276 color: None,
1277 font_size: None,
1278 font_weight: None,
1279 font_family: None,
1280 font_style: None,
1281 text_align: None,
1282 letter_spacing: None,
1283 line_height: None,
1284 background: None,
1285 text_decoration: None,
1286 text_direction: None,
1287 font_synthesis: None,
1288 baseline_shift: None,
1289 hyphens: None,
1290 line_break: None,
1291 text_indent: None,
1292 draw_style: None,
1293 alpha: 0.0,
1294 font_variation_settings: None,
1295 }
1296 }
1297
1298 pub fn color(mut self, c: Color) -> Self {
1299 self.color = Some(c);
1300 self
1301 }
1302
1303 pub fn font_size(mut self, size: crate::units::Sp) -> Self {
1304 self.font_size = Some(size);
1305 self
1306 }
1307
1308 pub fn text_decoration(mut self, d: TextDecoration) -> Self {
1309 self.text_decoration = Some(d);
1310 self
1311 }
1312
1313 pub fn font_weight(mut self, w: u16) -> Self {
1314 self.font_weight = Some(w);
1315 self
1316 }
1317
1318 pub fn font_style(mut self, s: u8) -> Self {
1319 self.font_style = Some(s);
1320 self
1321 }
1322
1323 pub fn draw_style(mut self, s: DrawStyle) -> Self {
1324 self.draw_style = Some(s);
1325 self
1326 }
1327
1328 pub fn background(mut self, c: Color) -> Self {
1329 self.background = Some(c);
1330 self
1331 }
1332
1333 pub fn baseline_shift(mut self, s: BaselineShift) -> Self {
1334 self.baseline_shift = Some(s);
1335 self
1336 }
1337}
1338
1339impl Default for SpanStyle {
1340 fn default() -> Self {
1341 Self::default()
1342 }
1343}
1344
1345#[derive(Debug, Clone, PartialEq)]
1347pub struct TextSpan {
1348 pub start: usize,
1350 pub end: usize,
1352 pub style: SpanStyle,
1353 pub url: Option<Arc<str>>,
1355}
1356
1357#[derive(Debug, Clone, PartialEq)]
1361pub struct AnnotatedString {
1362 pub text: String,
1363 pub spans: Arc<[TextSpan]>,
1364}
1365
1366impl AnnotatedString {
1367 pub fn new(text: impl Into<String>, spans: Vec<TextSpan>) -> Self {
1368 let text = text.into();
1369 Self {
1370 text,
1371 spans: spans.into(),
1372 }
1373 }
1374
1375 pub fn as_str(&self) -> &str {
1376 &self.text
1377 }
1378}
1379
1380impl From<String> for AnnotatedString {
1381 fn from(text: String) -> Self {
1382 Self {
1383 text,
1384 spans: Arc::from([]),
1385 }
1386 }
1387}
1388
1389impl From<&str> for AnnotatedString {
1390 fn from(text: &str) -> Self {
1391 Self {
1392 text: text.to_string(),
1393 spans: Arc::from([]),
1394 }
1395 }
1396}
1397
1398#[derive(Default)]
1400pub struct AnnotatedStringBuilder {
1401 text: String,
1402 spans: Vec<TextSpan>,
1403}
1404
1405impl AnnotatedStringBuilder {
1406 pub fn new() -> Self {
1407 Self::default()
1408 }
1409
1410 pub fn push(&mut self, text: &str) -> &mut Self {
1412 self.text.push_str(text);
1413 self
1414 }
1415
1416 pub fn push_with_style(&mut self, text: &str, style: SpanStyle) -> &mut Self {
1418 let start = self.text.len();
1419 self.text.push_str(text);
1420 let end = self.text.len();
1421 if start < end {
1422 self.spans.push(TextSpan {
1423 start,
1424 end,
1425 style,
1426 url: None,
1427 });
1428 }
1429 self
1430 }
1431
1432 pub fn push_color(&mut self, text: &str, color: Color) -> &mut Self {
1434 self.push_with_style(text, SpanStyle::default().color(color))
1435 }
1436
1437 pub fn push_link(&mut self, text: &str, url: impl Into<Arc<str>>) -> &mut Self {
1439 let start = self.text.len();
1440 self.text.push_str(text);
1441 let end = self.text.len();
1442 if start < end {
1443 self.spans.push(TextSpan {
1444 start,
1445 end,
1446 style: SpanStyle::default()
1447 .color(Color::from_rgba(0x15, 0x76, 0xFF, 255))
1448 .text_decoration(TextDecoration::UNDERLINE),
1449 url: Some(url.into()),
1450 });
1451 }
1452 self
1453 }
1454
1455 pub fn add_style(&mut self, start: usize, end: usize, style: SpanStyle) -> &mut Self {
1457 if start < end && end <= self.text.len() {
1458 self.spans.push(TextSpan {
1459 start,
1460 end,
1461 style,
1462 url: None,
1463 });
1464 }
1465 self
1466 }
1467
1468 pub fn build(&mut self) -> AnnotatedString {
1469 let text = std::mem::take(&mut self.text);
1470 self.spans.sort_by_key(|s| s.start);
1471 let mut merged: Vec<TextSpan> = Vec::new();
1473 for span in std::mem::take(&mut self.spans) {
1474 if let Some(last) = merged.last_mut()
1475 && last.end == span.start
1476 && last.style == span.style
1477 {
1478 last.end = span.end;
1479 continue;
1480 }
1481 merged.push(span);
1482 }
1483 AnnotatedString {
1484 text,
1485 spans: merged.into(),
1486 }
1487 }
1488}
1489
1490#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Default)]
1492pub enum TextAlign {
1493 Left,
1494 Right,
1495 Center,
1496 Justify,
1497 Start,
1498 End,
1499 #[default]
1500 Unspecified,
1501}
1502
1503#[derive(Clone, Copy, Debug, PartialEq)]
1505pub struct FontWeight(pub u16);
1506
1507impl FontWeight {
1508 pub const THIN: FontWeight = FontWeight(100);
1509 pub const EXTRA_LIGHT: FontWeight = FontWeight(200);
1510 pub const LIGHT: FontWeight = FontWeight(300);
1511 pub const NORMAL: FontWeight = FontWeight(400);
1512 pub const MEDIUM: FontWeight = FontWeight(500);
1513 pub const SEMI_BOLD: FontWeight = FontWeight(600);
1514 pub const BOLD: FontWeight = FontWeight(700);
1515 pub const EXTRA_BOLD: FontWeight = FontWeight(800);
1516 pub const BLACK: FontWeight = FontWeight(900);
1517}
1518
1519impl Default for FontWeight {
1520 fn default() -> Self {
1521 FontWeight::NORMAL
1522 }
1523}
1524
1525#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Default)]
1527pub enum FontStyle {
1528 #[default]
1529 Normal,
1530 Italic,
1531}
1532
1533#[derive(Clone, Copy, Debug, PartialEq, Default)]
1535pub struct TextDecoration {
1536 pub underline: bool,
1537 pub strikethrough: bool,
1538 pub color: Option<Color>,
1539}
1540
1541impl TextDecoration {
1542 pub const UNDERLINE: TextDecoration = TextDecoration {
1543 underline: true,
1544 strikethrough: false,
1545 color: None,
1546 };
1547 pub const STRIKETHROUGH: TextDecoration = TextDecoration {
1548 underline: false,
1549 strikethrough: true,
1550 color: None,
1551 };
1552}
1553
1554pub fn build_annotated_string(b: impl FnOnce(&mut AnnotatedStringBuilder)) -> AnnotatedString {
1556 let mut builder = AnnotatedStringBuilder::new();
1557 b(&mut builder);
1558 builder.build()
1559}