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}
721
722impl DrawStyle {
723 pub const fn stroke(width: f32) -> Self {
725 Self::Stroke {
726 width,
727 cap: crate::StrokeCap::Butt,
728 join: crate::StrokeJoin::Miter,
729 miter: 4.0,
730 path_effect: None,
731 }
732 }
733}
734
735#[derive(Clone, Debug)]
738pub struct TextStyle {
739 pub font_size: crate::units::Sp,
741 pub color: Option<Color>,
743 pub font_weight: Option<u16>,
745 pub font_family: Option<&'static str>,
747 pub font_style: Option<u8>,
749 pub text_align: crate::TextAlign,
751 pub letter_spacing: crate::units::Sp,
753 pub line_height: crate::units::Sp,
755 pub background: Option<Color>,
757 pub text_decoration: Option<crate::TextDecoration>,
759 pub shadow: Option<Shadow>,
761 pub text_direction: Option<crate::TextDirection>,
763 pub font_synthesis: FontSynthesis,
765 pub baseline_shift: BaselineShift,
767 pub hyphens: Hyphens,
769 pub line_break: LineBreak,
771 pub text_indent: Option<TextIndent>,
773 pub draw_style: DrawStyle,
775 pub alpha: f32,
777 pub locale_list: Option<String>,
779 pub font_feature_settings: Option<String>,
781 pub font_variation_settings: Option<String>,
783}
784
785impl Default for TextStyle {
786 fn default() -> Self {
787 Self {
788 font_size: crate::units::Sp::ZERO,
789 color: None,
790 font_weight: None,
791 font_family: Some("sans-serif"),
792 font_style: None,
793 text_align: crate::TextAlign::Unspecified,
794 letter_spacing: crate::units::Sp::ZERO,
795 line_height: crate::units::Sp::ZERO,
796 background: None,
797 text_decoration: None,
798 shadow: None,
799 text_direction: None,
800 font_synthesis: FontSynthesis::Unspecified,
801 baseline_shift: BaselineShift::Unspecified,
802 hyphens: Hyphens::Unspecified,
803 line_break: LineBreak::Unspecified,
804 text_indent: None,
805 draw_style: DrawStyle::Fill,
806 alpha: 0.0,
807 locale_list: None,
808 font_feature_settings: None,
809 font_variation_settings: None,
810 }
811 }
812}
813
814#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
816pub enum KeyboardType {
817 #[default]
818 Unspecified,
819 Text,
820 Ascii,
821 Number,
822 Phone,
823 Uri,
824 Email,
825 Password,
826 NumberPassword,
827 Decimal,
828 PasswordVisible,
829 PostalAddress,
830 PersonName,
831 EmailSubject,
832 ShortMessage,
833 LongMessage,
834 Filter,
835 Phonetic,
836 DateTime,
837 Date,
838 Time,
839 NumberSigned,
840 DecimalSigned,
841 DecimalPassword,
842 NumberPasswordSigned,
843 DecimalPasswordSigned,
844}
845
846#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
848pub enum ImeAction {
849 #[default]
850 Unspecified,
851 None,
852 Default,
853 Go,
854 Search,
855 Send,
856 Previous,
857 Next,
858 Done,
859}
860
861#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
868pub enum ImePurposeHint {
869 #[default]
870 Normal,
871 Password,
872 Email,
873 Url,
874 Phone,
875 Number,
876}
877
878impl KeyboardType {
879 pub fn ime_purpose_hint(self) -> ImePurposeHint {
881 use KeyboardType::*;
882 match self {
883 Password
884 | NumberPassword
885 | DecimalPassword
886 | NumberPasswordSigned
887 | DecimalPasswordSigned => ImePurposeHint::Password,
888 Email | EmailSubject => ImePurposeHint::Email,
889 Uri => ImePurposeHint::Url,
890 Phone => ImePurposeHint::Phone,
891 Number | NumberSigned | Decimal | DecimalSigned => ImePurposeHint::Number,
892 _ => ImePurposeHint::Normal,
893 }
894 }
895}
896
897pub trait KeyboardActionScope {
900 fn default_keyboard_action(&self, action: ImeAction);
901}
902
903#[derive(Clone, Default)]
906pub struct KeyboardActions {
907 pub on_done: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
908 pub on_go: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
909 pub on_next: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
910 pub on_previous: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
911 pub on_search: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
912 pub on_send: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
913}
914
915impl KeyboardActions {
916 pub fn on_any(f: impl Fn(ImeAction, &dyn KeyboardActionScope) + 'static) -> Self {
917 let f = Rc::new(f);
918 KeyboardActions {
919 on_done: Some({
920 let f = f.clone();
921 Rc::new(move |scope| f(ImeAction::Done, scope))
922 }),
923 on_go: Some({
924 let f = f.clone();
925 Rc::new(move |scope| f(ImeAction::Go, scope))
926 }),
927 on_next: Some({
928 let f = f.clone();
929 Rc::new(move |scope| f(ImeAction::Next, scope))
930 }),
931 on_previous: Some({
932 let f = f.clone();
933 Rc::new(move |scope| f(ImeAction::Previous, scope))
934 }),
935 on_search: Some({
936 let f = f.clone();
937 Rc::new(move |scope| f(ImeAction::Search, scope))
938 }),
939 on_send: Some(Rc::new(move |scope| f(ImeAction::Send, scope))),
940 }
941 }
942}
943
944pub trait KeyboardActionHandler: Debug + 'static {
947 fn on_keyboard_action(&self, perform_default: &dyn Fn());
948}
949
950#[derive(Clone, Copy, Debug)]
952pub struct DefaultKeyboardActionHandler;
953
954impl KeyboardActionHandler for DefaultKeyboardActionHandler {
955 fn on_keyboard_action(&self, perform_default: &dyn Fn()) {
956 perform_default();
957 }
958}
959
960pub trait TextFieldBuffer {
963 fn text(&self) -> &str;
964 fn set_text(&mut self, text: &str);
965 fn selection(&self) -> TextRange;
966 fn set_selection(&mut self, sel: TextRange);
967 fn length(&self) -> usize;
968 fn replace(&mut self, start: usize, end: usize, text: &str);
969 fn insert(&mut self, index: usize, text: &str);
970 fn delete(&mut self, start: usize, end: usize);
971 fn place_cursor_before_char_at(&mut self, index: usize);
972 fn place_cursor_at_end(&mut self);
973 fn select_all(&mut self);
974 fn revert_all_changes(&mut self);
975 fn original_text(&self) -> &str;
976 fn original_selection(&self) -> TextRange;
977 fn has_selection(&self) -> bool;
978}
979
980#[derive(Clone, Copy, Debug, PartialEq, Eq)]
983pub enum TextFieldLineLimits {
984 SingleLine,
985 MultiLine {
986 min_height_in_lines: usize,
987 max_height_in_lines: usize,
988 },
989}
990
991impl TextFieldLineLimits {
992 pub fn default() -> Self {
993 TextFieldLineLimits::MultiLine {
994 min_height_in_lines: 1,
995 max_height_in_lines: usize::MAX,
996 }
997 }
998}
999
1000pub trait TextFieldDecorator: Debug + 'static {
1003 fn decorate(&self, inner: crate::View) -> crate::View;
1004}
1005
1006#[derive(Clone, Copy, Debug)]
1008pub struct DefaultTextFieldDecorator;
1009
1010impl TextFieldDecorator for DefaultTextFieldDecorator {
1011 fn decorate(&self, inner: crate::View) -> crate::View {
1012 inner
1013 }
1014}
1015
1016pub trait InputTransformation: Debug + 'static {
1019 fn keyboard_options(&self) -> Option<KeyboardOptions> {
1020 None
1021 }
1022 fn transform_input(&self, buffer: &mut dyn TextFieldBuffer);
1023}
1024
1025pub trait OutputTransformation: Debug + 'static {
1028 fn transform_output(&self, buffer: &mut dyn TextFieldBuffer);
1029}
1030
1031pub struct CodepointTransformation {
1034 pub transform: Box<dyn Fn(usize, char) -> char>,
1035}
1036
1037impl Clone for CodepointTransformation {
1038 fn clone(&self) -> Self {
1039 panic!("CodepointTransformation::clone() is not supported -> use Rc instead");
1043 }
1044}
1045
1046impl CodepointTransformation {
1047 pub fn new(transform: impl Fn(usize, char) -> char + 'static) -> Self {
1048 CodepointTransformation {
1049 transform: Box::new(transform),
1050 }
1051 }
1052
1053 pub fn transform(&self, codepoint_index: usize, codepoint: char) -> char {
1054 (self.transform)(codepoint_index, codepoint)
1055 }
1056}
1057
1058#[derive(Clone, Copy, Debug, PartialEq)]
1061pub struct KeyboardOptions {
1062 pub keyboard_type: KeyboardType,
1063 pub capitalization: KeyboardCapitalization,
1064 pub ime_action: ImeAction,
1065 pub auto_correct_enabled: Option<bool>,
1066 pub show_keyboard_on_focus: Option<bool>,
1067 pub platform_ime_options: Option<&'static str>,
1068 pub hint_locales: Option<&'static str>,
1069}
1070
1071impl KeyboardOptions {
1072 pub const DEFAULT: KeyboardOptions = KeyboardOptions {
1073 keyboard_type: KeyboardType::Text,
1074 capitalization: KeyboardCapitalization::Unspecified,
1075 ime_action: ImeAction::Unspecified,
1076 auto_correct_enabled: None,
1077 show_keyboard_on_focus: None,
1078 platform_ime_options: None,
1079 hint_locales: None,
1080 };
1081
1082 pub const SECURE_TEXT_FIELD: KeyboardOptions = KeyboardOptions {
1083 keyboard_type: KeyboardType::Password,
1084 capitalization: KeyboardCapitalization::Unspecified,
1085 ime_action: ImeAction::Unspecified,
1086 auto_correct_enabled: Some(false),
1087 show_keyboard_on_focus: None,
1088 platform_ime_options: None,
1089 hint_locales: None,
1090 };
1091
1092 pub fn fill_unspecified_values_with(&self, other: Option<&KeyboardOptions>) -> KeyboardOptions {
1093 let other = match other {
1094 Some(o) => o,
1095 None => return *self,
1096 };
1097 KeyboardOptions {
1098 keyboard_type: if self.keyboard_type == KeyboardType::Unspecified {
1099 other.keyboard_type
1100 } else {
1101 self.keyboard_type
1102 },
1103 capitalization: if self.capitalization == KeyboardCapitalization::Unspecified {
1104 other.capitalization
1105 } else {
1106 self.capitalization
1107 },
1108 ime_action: if self.ime_action == ImeAction::Unspecified {
1109 other.ime_action
1110 } else {
1111 self.ime_action
1112 },
1113 auto_correct_enabled: self.auto_correct_enabled.or(other.auto_correct_enabled),
1114 show_keyboard_on_focus: self.show_keyboard_on_focus.or(other.show_keyboard_on_focus),
1115 platform_ime_options: self.platform_ime_options.or(other.platform_ime_options),
1116 hint_locales: self.hint_locales.or(other.hint_locales),
1117 }
1118 }
1119
1120 pub fn merge(&self, other: Option<&KeyboardOptions>) -> KeyboardOptions {
1124 other.map_or(*self, |o| o.fill_unspecified_values_with(Some(self)))
1125 }
1126}
1127
1128impl Default for KeyboardOptions {
1129 fn default() -> Self {
1130 Self::DEFAULT
1131 }
1132}
1133
1134#[derive(Debug, Clone, PartialEq)]
1135pub struct SpanStyle {
1136 pub color: Option<Color>,
1137 pub font_size: Option<crate::units::Sp>,
1138 pub font_weight: Option<u16>,
1139 pub font_family: Option<&'static str>,
1140 pub font_style: Option<u8>,
1141 pub text_align: Option<crate::TextAlign>,
1142 pub letter_spacing: Option<crate::units::Sp>,
1143 pub line_height: Option<crate::units::Sp>,
1144 pub background: Option<Color>,
1145 pub text_decoration: Option<crate::TextDecoration>,
1146 pub text_direction: Option<crate::TextDirection>,
1147 pub font_synthesis: Option<FontSynthesis>,
1148 pub baseline_shift: Option<BaselineShift>,
1149 pub hyphens: Option<Hyphens>,
1150 pub line_break: Option<LineBreak>,
1151 pub text_indent: Option<TextIndent>,
1152 pub draw_style: Option<DrawStyle>,
1153 pub alpha: f32,
1154 pub font_variation_settings: Option<String>,
1156}
1157
1158impl SpanStyle {
1159 pub const fn default() -> Self {
1160 Self {
1161 color: None,
1162 font_size: None,
1163 font_weight: None,
1164 font_family: None,
1165 font_style: None,
1166 text_align: None,
1167 letter_spacing: None,
1168 line_height: None,
1169 background: None,
1170 text_decoration: None,
1171 text_direction: None,
1172 font_synthesis: None,
1173 baseline_shift: None,
1174 hyphens: None,
1175 line_break: None,
1176 text_indent: None,
1177 draw_style: None,
1178 alpha: 0.0,
1179 font_variation_settings: None,
1180 }
1181 }
1182
1183 pub fn color(mut self, c: Color) -> Self {
1184 self.color = Some(c);
1185 self
1186 }
1187
1188 pub fn font_size(mut self, size: crate::units::Sp) -> Self {
1189 self.font_size = Some(size);
1190 self
1191 }
1192
1193 pub fn text_decoration(mut self, d: TextDecoration) -> Self {
1194 self.text_decoration = Some(d);
1195 self
1196 }
1197
1198 pub fn font_weight(mut self, w: u16) -> Self {
1199 self.font_weight = Some(w);
1200 self
1201 }
1202
1203 pub fn font_style(mut self, s: u8) -> Self {
1204 self.font_style = Some(s);
1205 self
1206 }
1207
1208 pub fn draw_style(mut self, s: DrawStyle) -> Self {
1209 self.draw_style = Some(s);
1210 self
1211 }
1212
1213 pub fn background(mut self, c: Color) -> Self {
1214 self.background = Some(c);
1215 self
1216 }
1217
1218 pub fn baseline_shift(mut self, s: BaselineShift) -> Self {
1219 self.baseline_shift = Some(s);
1220 self
1221 }
1222}
1223
1224impl Default for SpanStyle {
1225 fn default() -> Self {
1226 Self::default()
1227 }
1228}
1229
1230#[derive(Debug, Clone, PartialEq)]
1232pub struct TextSpan {
1233 pub start: usize,
1235 pub end: usize,
1237 pub style: SpanStyle,
1238 pub url: Option<Arc<str>>,
1240}
1241
1242#[derive(Debug, Clone, PartialEq)]
1246pub struct AnnotatedString {
1247 pub text: String,
1248 pub spans: Arc<[TextSpan]>,
1249}
1250
1251impl AnnotatedString {
1252 pub fn new(text: impl Into<String>, spans: Vec<TextSpan>) -> Self {
1253 let text = text.into();
1254 Self {
1255 text,
1256 spans: spans.into(),
1257 }
1258 }
1259
1260 pub fn as_str(&self) -> &str {
1261 &self.text
1262 }
1263}
1264
1265impl From<String> for AnnotatedString {
1266 fn from(text: String) -> Self {
1267 Self {
1268 text,
1269 spans: Arc::from([]),
1270 }
1271 }
1272}
1273
1274impl From<&str> for AnnotatedString {
1275 fn from(text: &str) -> Self {
1276 Self {
1277 text: text.to_string(),
1278 spans: Arc::from([]),
1279 }
1280 }
1281}
1282
1283#[derive(Default)]
1285pub struct AnnotatedStringBuilder {
1286 text: String,
1287 spans: Vec<TextSpan>,
1288}
1289
1290impl AnnotatedStringBuilder {
1291 pub fn new() -> Self {
1292 Self::default()
1293 }
1294
1295 pub fn push(&mut self, text: &str) -> &mut Self {
1297 self.text.push_str(text);
1298 self
1299 }
1300
1301 pub fn push_with_style(&mut self, text: &str, style: SpanStyle) -> &mut Self {
1303 let start = self.text.len();
1304 self.text.push_str(text);
1305 let end = self.text.len();
1306 if start < end {
1307 self.spans.push(TextSpan {
1308 start,
1309 end,
1310 style,
1311 url: None,
1312 });
1313 }
1314 self
1315 }
1316
1317 pub fn push_color(&mut self, text: &str, color: Color) -> &mut Self {
1319 self.push_with_style(text, SpanStyle::default().color(color))
1320 }
1321
1322 pub fn push_link(&mut self, text: &str, url: impl Into<Arc<str>>) -> &mut Self {
1324 let start = self.text.len();
1325 self.text.push_str(text);
1326 let end = self.text.len();
1327 if start < end {
1328 self.spans.push(TextSpan {
1329 start,
1330 end,
1331 style: SpanStyle::default()
1332 .color(Color::from_rgba(0x15, 0x76, 0xFF, 255))
1333 .text_decoration(TextDecoration::UNDERLINE),
1334 url: Some(url.into()),
1335 });
1336 }
1337 self
1338 }
1339
1340 pub fn add_style(&mut self, start: usize, end: usize, style: SpanStyle) -> &mut Self {
1342 if start < end && end <= self.text.len() {
1343 self.spans.push(TextSpan {
1344 start,
1345 end,
1346 style,
1347 url: None,
1348 });
1349 }
1350 self
1351 }
1352
1353 pub fn build(&mut self) -> AnnotatedString {
1354 let text = std::mem::take(&mut self.text);
1355 self.spans.sort_by_key(|s| s.start);
1356 let mut merged: Vec<TextSpan> = Vec::new();
1358 for span in std::mem::take(&mut self.spans) {
1359 if let Some(last) = merged.last_mut()
1360 && last.end == span.start
1361 && last.style == span.style
1362 {
1363 last.end = span.end;
1364 continue;
1365 }
1366 merged.push(span);
1367 }
1368 AnnotatedString {
1369 text,
1370 spans: merged.into(),
1371 }
1372 }
1373}
1374
1375#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Default)]
1377pub enum TextAlign {
1378 Left,
1379 Right,
1380 Center,
1381 Justify,
1382 Start,
1383 End,
1384 #[default]
1385 Unspecified,
1386}
1387
1388#[derive(Clone, Copy, Debug, PartialEq)]
1390pub struct FontWeight(pub u16);
1391
1392impl FontWeight {
1393 pub const THIN: FontWeight = FontWeight(100);
1394 pub const EXTRA_LIGHT: FontWeight = FontWeight(200);
1395 pub const LIGHT: FontWeight = FontWeight(300);
1396 pub const NORMAL: FontWeight = FontWeight(400);
1397 pub const MEDIUM: FontWeight = FontWeight(500);
1398 pub const SEMI_BOLD: FontWeight = FontWeight(600);
1399 pub const BOLD: FontWeight = FontWeight(700);
1400 pub const EXTRA_BOLD: FontWeight = FontWeight(800);
1401 pub const BLACK: FontWeight = FontWeight(900);
1402}
1403
1404impl Default for FontWeight {
1405 fn default() -> Self {
1406 FontWeight::NORMAL
1407 }
1408}
1409
1410#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Default)]
1412pub enum FontStyle {
1413 #[default]
1414 Normal,
1415 Italic,
1416}
1417
1418#[derive(Clone, Copy, Debug, PartialEq, Default)]
1420pub struct TextDecoration {
1421 pub underline: bool,
1422 pub strikethrough: bool,
1423 pub color: Option<Color>,
1424}
1425
1426impl TextDecoration {
1427 pub const UNDERLINE: TextDecoration = TextDecoration {
1428 underline: true,
1429 strikethrough: false,
1430 color: None,
1431 };
1432 pub const STRIKETHROUGH: TextDecoration = TextDecoration {
1433 underline: false,
1434 strikethrough: true,
1435 color: None,
1436 };
1437}
1438
1439pub fn build_annotated_string(b: impl FnOnce(&mut AnnotatedStringBuilder)) -> AnnotatedString {
1441 let mut builder = AnnotatedStringBuilder::new();
1442 b(&mut builder);
1443 builder.build()
1444}