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