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 })
129 .collect();
130 AnnotatedString::new(text, spans)
131 }
132
133 pub fn get_text_after_selection(&self, max_chars: usize) -> AnnotatedString {
134 let sel_max = self.selection.max();
135 let end = (sel_max + max_chars).min(self.annotated_string.text.len());
136 let text = self.annotated_string.text[sel_max..end].to_string();
137 let spans: Vec<TextSpan> = self
138 .annotated_string
139 .spans
140 .iter()
141 .filter(|s| s.start >= sel_max && s.end <= end)
142 .map(|s| TextSpan {
143 start: s.start - sel_max,
144 end: s.end - sel_max,
145 style: s.style.clone(),
146 })
147 .collect();
148 AnnotatedString::new(text, spans)
149 }
150
151 pub fn get_selected_text(&self) -> AnnotatedString {
152 let r = self.selection.min()..self.selection.max();
153 let text = self.annotated_string.text[r.clone()].to_string();
154 let spans: Vec<TextSpan> = self
155 .annotated_string
156 .spans
157 .iter()
158 .filter(|s| s.start >= r.start && s.end <= r.end)
159 .map(|s| TextSpan {
160 start: s.start - r.start,
161 end: s.end - r.start,
162 style: s.style.clone(),
163 })
164 .collect();
165 AnnotatedString::new(text, spans)
166 }
167
168 pub fn copy(&self, annotated_string: AnnotatedString) -> Self {
170 TextFieldValue {
171 annotated_string,
172 selection: self.selection,
173 composition: self.composition,
174 }
175 }
176
177 pub fn copy_text(&self, text: String) -> Self {
179 TextFieldValue {
180 annotated_string: AnnotatedString::from(text),
181 selection: self.selection,
182 composition: self.composition,
183 }
184 }
185}
186
187#[derive(Clone, Debug)]
190pub struct TextLayoutResult {
191 pub line_count: usize,
193 pub width_px: f32,
195 pub height_px: f32,
197 pub first_baseline: f32,
199 pub last_baseline: f32,
201 pub did_overflow_width: bool,
203 pub did_overflow_height: bool,
205 pub lines: Vec<TextLineInfo>,
207}
208
209#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
212pub enum TextObfuscationMode {
213 Visible,
215 RevealLastTyped,
217 Hidden,
219 #[default]
221 System,
222}
223
224#[derive(Clone, Debug)]
226pub struct TextLineInfo {
227 pub start: usize,
229 pub end: usize,
231 pub top: f32,
233 pub baseline: f32,
235 pub bottom: f32,
237 pub left: f32,
239 pub right: f32,
241 pub width: f32,
243}
244
245impl TextLayoutResult {
246 pub fn get_line_start(&self, line_index: usize) -> Option<usize> {
247 self.lines.get(line_index).map(|l| l.start)
248 }
249
250 pub fn get_line_end(&self, line_index: usize) -> Option<usize> {
251 self.lines.get(line_index).map(|l| l.end)
252 }
253
254 pub fn get_line_end_visible(&self, line_index: usize) -> Option<usize> {
255 self.lines.get(line_index).map(|l| l.end)
256 }
257
258 pub fn is_line_ellipsized(&self, _line_index: usize) -> bool {
259 false
260 }
261
262 pub fn get_line_top(&self, line_index: usize) -> Option<f32> {
263 self.lines.get(line_index).map(|l| l.top)
264 }
265
266 pub fn get_line_baseline(&self, line_index: usize) -> Option<f32> {
267 self.lines.get(line_index).map(|l| l.baseline)
268 }
269
270 pub fn get_line_bottom(&self, line_index: usize) -> Option<f32> {
271 self.lines.get(line_index).map(|l| l.bottom)
272 }
273
274 pub fn get_line_left(&self, line_index: usize) -> Option<f32> {
275 self.lines.get(line_index).map(|l| l.left)
276 }
277
278 pub fn get_line_right(&self, line_index: usize) -> Option<f32> {
279 self.lines.get(line_index).map(|l| l.right)
280 }
281
282 pub fn get_line_for_offset(&self, offset: usize) -> usize {
283 for (i, line) in self.lines.iter().enumerate() {
284 if offset >= line.start && offset < line.end {
285 return i;
286 }
287 }
288 self.line_count.saturating_sub(1)
289 }
290
291 pub fn get_line_for_vertical_position(&self, vertical: f32) -> usize {
292 for (i, line) in self.lines.iter().enumerate() {
293 if vertical >= line.top && vertical < line.bottom {
294 return i;
295 }
296 }
297 self.line_count.saturating_sub(1)
298 }
299
300 pub fn get_horizontal_position(&self, offset: usize, _use_primary_direction: bool) -> f32 {
301 self.lines
302 .iter()
303 .find(|l| offset >= l.start && offset <= l.end)
304 .map(|l| l.left)
305 .unwrap_or(0.0)
306 }
307
308 pub fn has_visual_overflow(&self) -> bool {
310 self.did_overflow_width || self.did_overflow_height
311 }
312
313 pub fn get_bounding_box(&self, offset: usize) -> crate::Rect {
316 if self.lines.is_empty() || offset > self.lines.last().unwrap().end {
317 return crate::Rect::default();
318 }
319 let line = self
320 .lines
321 .iter()
322 .find(|l| offset >= l.start && offset < l.end)
323 .unwrap_or_else(|| {
324 if offset >= self.lines.last().unwrap().end {
325 self.lines.last().unwrap()
326 } else {
327 &self.lines[0]
328 }
329 });
330 let _line_idx = self.get_line_for_offset(offset);
331 let char_in_line = offset - line.start;
332 let pos_in_line = if char_in_line == 0 {
333 line.left
334 } else {
335 let chars = line.end - line.start;
337 if chars == 0 {
338 line.left
339 } else {
340 line.left + (line.width * (char_in_line as f32) / (chars as f32))
341 }
342 };
343 crate::Rect {
344 x: pos_in_line,
345 y: line.top,
346 w: if char_in_line < (line.end - line.start) {
347 line.width / (line.end - line.start).max(1) as f32
348 } else {
349 1.0
350 },
351 h: line.bottom - line.top,
352 }
353 }
354
355 pub fn get_cursor_rect(&self, offset: usize) -> crate::Rect {
357 let line = self
358 .lines
359 .iter()
360 .find(|l| offset >= l.start && offset <= l.end)
361 .unwrap_or_else(|| {
362 if offset >= self.lines.last().map(|l| l.end).unwrap_or(0) {
363 self.lines.last().unwrap()
364 } else {
365 &self.lines[0]
366 }
367 });
368 let char_in_line = offset.saturating_sub(line.start);
369 let chars = (line.end - line.start).max(1);
370 let x = line.left + (line.width * (char_in_line as f32) / (chars as f32));
371 crate::Rect {
372 x,
373 y: line.top,
374 w: 1.0,
375 h: line.bottom - line.top,
376 }
377 }
378
379 pub fn get_offset_for_position(&self, position: (f32, f32)) -> Option<usize> {
381 let line_idx = self.get_line_for_vertical_position(position.1);
382 let line = self.lines.get(line_idx)?;
383 if position.0 <= line.left {
384 return Some(line.start);
385 }
386 if position.0 >= line.right {
387 return Some(line.end);
388 }
389 let fraction = (position.0 - line.left) / line.width.max(1.0);
390 let offset_in_line = ((line.end - line.start) as f32 * fraction).round() as usize;
391 Some((line.start + offset_in_line).min(line.end))
392 }
393
394 pub fn get_word_boundary(&self, _offset: usize) -> super::TextRange {
396 let text = ""; let start = if text.is_empty() { 0 } else { 0 };
399 let end = if text.is_empty() { 0 } else { 0 };
400 super::TextRange::new(start, end)
401 }
402
403 pub fn get_paragraph_direction(&self, _offset: usize) -> u8 {
405 0 }
407
408 pub fn get_bidi_run_direction(&self, _offset: usize) -> u8 {
410 0 }
412}
413
414pub trait OffsetMapping: Debug + Send + Sync + 'static {
416 fn original_to_transformed(&self, offset: usize) -> usize;
417 fn transformed_to_original(&self, offset: usize) -> usize;
418 fn clone_box(&self) -> Box<dyn OffsetMapping>;
419}
420
421#[derive(Clone, Copy, Debug)]
423pub struct IdentityOffsetMapping;
424
425impl OffsetMapping for IdentityOffsetMapping {
426 fn original_to_transformed(&self, offset: usize) -> usize {
427 offset
428 }
429 fn transformed_to_original(&self, offset: usize) -> usize {
430 offset
431 }
432 fn clone_box(&self) -> Box<dyn OffsetMapping> {
433 Box::new(*self)
434 }
435}
436
437pub trait VisualTransformation: Debug + Send + Sync + 'static {
440 fn filter(&self, text: &AnnotatedString) -> TransformedText;
443}
444
445pub struct TransformedText {
447 pub text: AnnotatedString,
449 pub offset_mapping: Box<dyn OffsetMapping>,
451}
452
453impl TransformedText {
454 pub fn new(text: AnnotatedString, offset_mapping: Box<dyn OffsetMapping>) -> Self {
455 TransformedText {
456 text,
457 offset_mapping,
458 }
459 }
460}
461
462impl Clone for TransformedText {
463 fn clone(&self) -> Self {
464 Self {
465 text: self.text.clone(),
466 offset_mapping: self.offset_mapping.clone_box(),
467 }
468 }
469}
470
471impl Debug for TransformedText {
472 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
473 f.debug_struct("TransformedText")
474 .field("text", &self.text.text)
475 .finish()
476 }
477}
478
479#[derive(Clone, Copy, Debug)]
482pub struct IdentityVisualTransformation;
483
484impl VisualTransformation for IdentityVisualTransformation {
485 fn filter(&self, text: &AnnotatedString) -> TransformedText {
486 TransformedText {
487 text: text.clone(),
488 offset_mapping: Box::new(IdentityOffsetMapping),
489 }
490 }
491}
492
493#[derive(Clone, Copy, Debug)]
496pub struct PasswordVisualTransformation {
497 pub mask: char,
499}
500
501impl Default for PasswordVisualTransformation {
502 fn default() -> Self {
503 Self { mask: '\u{2022}' }
504 }
505}
506
507impl VisualTransformation for PasswordVisualTransformation {
508 fn filter(&self, text: &AnnotatedString) -> TransformedText {
509 let masked_text: String = text.text.chars().map(|_| self.mask).collect();
510 TransformedText {
511 text: AnnotatedString::new(masked_text, vec![]),
512 offset_mapping: Box::new(IdentityOffsetMapping),
513 }
514 }
515}
516
517pub fn original_offset_to_display(original: &str, display: &str, original_byte: usize) -> usize {
520 original_offset_to_display_with_mapping(original, display, original_byte, None)
521}
522
523pub fn original_offset_to_display_with_mapping(
526 original: &str,
527 display: &str,
528 original_byte: usize,
529 offset_mapping: Option<&dyn OffsetMapping>,
530) -> usize {
531 if let Some(om) = offset_mapping {
532 om.original_to_transformed(original_byte)
533 } else {
534 let char_idx = original[..original_byte.min(original.len())]
535 .chars()
536 .count();
537 display
538 .char_indices()
539 .nth(char_idx)
540 .map(|(i, _)| i)
541 .unwrap_or(display.len())
542 }
543}
544
545#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
548pub enum KeyboardCapitalization {
549 #[default]
550 Unspecified,
551 None,
552 Characters,
553 Words,
554 Sentences,
555}
556
557#[derive(Clone, Copy, Debug, PartialEq)]
559pub struct Shadow {
560 pub color: Color,
561 pub offset_x: f32,
563 pub offset_y: f32,
565 pub blur_radius: f32,
567}
568
569#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
572pub enum FontSynthesis {
573 #[default]
574 Unspecified,
575 None,
576 Weight,
577 Style,
578 SmallCaps,
579 All,
580}
581
582#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
584pub enum BaselineShift {
585 #[default]
586 Unspecified,
587 Superscript,
588 Subscript,
589}
590
591#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
593pub enum Hyphens {
594 #[default]
595 Unspecified,
596 None,
597 Auto,
598}
599
600#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
602pub enum LineBreak {
603 #[default]
604 Unspecified,
605 Simple,
606 Heading,
607 Paragraph,
608}
609
610#[derive(Clone, Copy, Debug, PartialEq)]
612pub struct TextIndent {
613 pub first_line: f32,
614 pub rest_lines: f32,
615}
616
617impl Default for TextIndent {
618 fn default() -> Self {
619 Self {
620 first_line: 0.0,
621 rest_lines: 0.0,
622 }
623 }
624}
625
626#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
628pub enum DrawStyle {
629 #[default]
630 Fill,
631 Stroke,
632}
633
634#[derive(Clone, Debug)]
637pub struct TextStyle {
638 pub font_size: f32,
640 pub color: Option<Color>,
642 pub font_weight: Option<u16>,
644 pub font_family: Option<&'static str>,
646 pub font_style: Option<u8>,
648 pub text_align: crate::TextAlign,
650 pub letter_spacing: f32,
652 pub line_height: f32,
654 pub background: Option<Color>,
656 pub text_decoration: Option<crate::TextDecoration>,
658 pub shadow: Option<Shadow>,
660 pub text_direction: Option<crate::TextDirection>,
662 pub font_synthesis: FontSynthesis,
664 pub baseline_shift: BaselineShift,
666 pub hyphens: Hyphens,
668 pub line_break: LineBreak,
670 pub text_indent: Option<TextIndent>,
672 pub draw_style: DrawStyle,
674 pub alpha: f32,
676 pub locale_list: Option<String>,
678 pub font_feature_settings: Option<String>,
680}
681
682impl Default for TextStyle {
683 fn default() -> Self {
684 Self {
685 font_size: 0.0,
686 color: None,
687 font_weight: None,
688 font_family: None,
689 font_style: None,
690 text_align: crate::TextAlign::Unspecified,
691 letter_spacing: 0.0,
692 line_height: 0.0,
693 background: None,
694 text_decoration: None,
695 shadow: None,
696 text_direction: None,
697 font_synthesis: FontSynthesis::Unspecified,
698 baseline_shift: BaselineShift::Unspecified,
699 hyphens: Hyphens::Unspecified,
700 line_break: LineBreak::Unspecified,
701 text_indent: None,
702 draw_style: DrawStyle::Fill,
703 alpha: 0.0,
704 locale_list: None,
705 font_feature_settings: None,
706 }
707 }
708}
709
710#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
712pub enum KeyboardType {
713 #[default]
714 Unspecified,
715 Text,
716 Ascii,
717 Number,
718 Phone,
719 Uri,
720 Email,
721 Password,
722 NumberPassword,
723 Decimal,
724 PasswordVisible,
725 PostalAddress,
726 PersonName,
727 EmailSubject,
728 ShortMessage,
729 LongMessage,
730 Filter,
731 Phonetic,
732 DateTime,
733 Date,
734 Time,
735 NumberSigned,
736 DecimalSigned,
737 DecimalPassword,
738 NumberPasswordSigned,
739 DecimalPasswordSigned,
740}
741
742#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
744pub enum ImeAction {
745 #[default]
746 Unspecified,
747 None,
748 Default,
749 Go,
750 Search,
751 Send,
752 Previous,
753 Next,
754 Done,
755}
756
757pub trait KeyboardActionScope {
760 fn default_keyboard_action(&self, action: ImeAction);
761}
762
763#[derive(Clone)]
766pub struct KeyboardActions {
767 pub on_done: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
768 pub on_go: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
769 pub on_next: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
770 pub on_previous: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
771 pub on_search: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
772 pub on_send: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
773}
774
775impl KeyboardActions {
776 pub fn on_any(f: impl Fn(ImeAction, &dyn KeyboardActionScope) + 'static) -> Self {
777 let f = Rc::new(f);
778 KeyboardActions {
779 on_done: Some({
780 let f = f.clone();
781 Rc::new(move |scope| f(ImeAction::Done, scope))
782 }),
783 on_go: Some({
784 let f = f.clone();
785 Rc::new(move |scope| f(ImeAction::Go, scope))
786 }),
787 on_next: Some({
788 let f = f.clone();
789 Rc::new(move |scope| f(ImeAction::Next, scope))
790 }),
791 on_previous: Some({
792 let f = f.clone();
793 Rc::new(move |scope| f(ImeAction::Previous, scope))
794 }),
795 on_search: Some({
796 let f = f.clone();
797 Rc::new(move |scope| f(ImeAction::Search, scope))
798 }),
799 on_send: Some({ Rc::new(move |scope| f(ImeAction::Send, scope)) }),
800 }
801 }
802}
803
804impl Default for KeyboardActions {
805 fn default() -> Self {
806 KeyboardActions {
807 on_done: None,
808 on_go: None,
809 on_next: None,
810 on_previous: None,
811 on_search: None,
812 on_send: None,
813 }
814 }
815}
816
817struct NoopKeyboardActionScope;
818impl KeyboardActionScope for NoopKeyboardActionScope {
819 fn default_keyboard_action(&self, _action: ImeAction) {}
820}
821
822pub trait KeyboardActionHandler: Debug + 'static {
825 fn on_keyboard_action(&self, perform_default: &dyn Fn());
826}
827
828#[derive(Clone, Copy, Debug)]
830pub struct DefaultKeyboardActionHandler;
831
832impl KeyboardActionHandler for DefaultKeyboardActionHandler {
833 fn on_keyboard_action(&self, perform_default: &dyn Fn()) {
834 perform_default();
835 }
836}
837
838pub trait TextFieldBuffer {
841 fn text(&self) -> &str;
842 fn set_text(&mut self, text: &str);
843 fn selection(&self) -> TextRange;
844 fn set_selection(&mut self, sel: TextRange);
845 fn length(&self) -> usize;
846 fn replace(&mut self, start: usize, end: usize, text: &str);
847 fn insert(&mut self, index: usize, text: &str);
848 fn delete(&mut self, start: usize, end: usize);
849 fn place_cursor_before_char_at(&mut self, index: usize);
850 fn place_cursor_at_end(&mut self);
851 fn select_all(&mut self);
852 fn revert_all_changes(&mut self);
853 fn original_text(&self) -> &str;
854 fn original_selection(&self) -> TextRange;
855 fn has_selection(&self) -> bool;
856}
857
858#[derive(Clone, Copy, Debug, PartialEq, Eq)]
861pub enum TextFieldLineLimits {
862 SingleLine,
863 MultiLine {
864 min_height_in_lines: usize,
865 max_height_in_lines: usize,
866 },
867}
868
869impl TextFieldLineLimits {
870 pub fn default() -> Self {
871 TextFieldLineLimits::MultiLine {
872 min_height_in_lines: 1,
873 max_height_in_lines: usize::MAX,
874 }
875 }
876}
877
878pub trait TextFieldDecorator: Debug + 'static {
881 fn decorate(&self, inner: crate::View) -> crate::View;
882}
883
884#[derive(Clone, Copy, Debug)]
886pub struct DefaultTextFieldDecorator;
887
888impl TextFieldDecorator for DefaultTextFieldDecorator {
889 fn decorate(&self, inner: crate::View) -> crate::View {
890 inner
891 }
892}
893
894pub trait InputTransformation: Debug + 'static {
897 fn keyboard_options(&self) -> Option<KeyboardOptions> {
898 None
899 }
900 fn transform_input(&self, buffer: &mut dyn TextFieldBuffer);
901}
902
903pub trait OutputTransformation: Debug + 'static {
906 fn transform_output(&self, buffer: &mut dyn TextFieldBuffer);
907}
908
909pub struct CodepointTransformation {
912 pub transform: Box<dyn Fn(usize, char) -> char>,
913}
914
915impl Clone for CodepointTransformation {
916 fn clone(&self) -> Self {
917 panic!("CodepointTransformation::clone() is not supported — use Rc instead");
921 }
922}
923
924impl CodepointTransformation {
925 pub fn new(transform: impl Fn(usize, char) -> char + 'static) -> Self {
926 CodepointTransformation {
927 transform: Box::new(transform),
928 }
929 }
930
931 pub fn transform(&self, codepoint_index: usize, codepoint: char) -> char {
932 (self.transform)(codepoint_index, codepoint)
933 }
934}
935
936#[derive(Clone, Copy, Debug, PartialEq)]
939pub struct KeyboardOptions {
940 pub keyboard_type: KeyboardType,
941 pub capitalization: KeyboardCapitalization,
942 pub ime_action: ImeAction,
943 pub auto_correct_enabled: Option<bool>,
944 pub show_keyboard_on_focus: Option<bool>,
945 pub platform_ime_options: Option<&'static str>,
946 pub hint_locales: Option<&'static str>,
947}
948
949impl KeyboardOptions {
950 pub const DEFAULT: KeyboardOptions = KeyboardOptions {
951 keyboard_type: KeyboardType::Text,
952 capitalization: KeyboardCapitalization::Unspecified,
953 ime_action: ImeAction::Unspecified,
954 auto_correct_enabled: None,
955 show_keyboard_on_focus: None,
956 platform_ime_options: None,
957 hint_locales: None,
958 };
959
960 pub const SECURE_TEXT_FIELD: KeyboardOptions = KeyboardOptions {
961 keyboard_type: KeyboardType::Password,
962 capitalization: KeyboardCapitalization::Unspecified,
963 ime_action: ImeAction::Unspecified,
964 auto_correct_enabled: Some(false),
965 show_keyboard_on_focus: None,
966 platform_ime_options: None,
967 hint_locales: None,
968 };
969
970 pub fn fill_unspecified_values_with(&self, other: Option<&KeyboardOptions>) -> KeyboardOptions {
971 let other = match other {
972 Some(o) => o,
973 None => return *self,
974 };
975 KeyboardOptions {
976 keyboard_type: if self.keyboard_type == KeyboardType::Unspecified {
977 other.keyboard_type
978 } else {
979 self.keyboard_type
980 },
981 capitalization: if self.capitalization == KeyboardCapitalization::Unspecified {
982 other.capitalization
983 } else {
984 self.capitalization
985 },
986 ime_action: if self.ime_action == ImeAction::Unspecified {
987 other.ime_action
988 } else {
989 self.ime_action
990 },
991 auto_correct_enabled: self.auto_correct_enabled.or(other.auto_correct_enabled),
992 show_keyboard_on_focus: self.show_keyboard_on_focus.or(other.show_keyboard_on_focus),
993 platform_ime_options: self.platform_ime_options.or(other.platform_ime_options),
994 hint_locales: self.hint_locales.or(other.hint_locales),
995 }
996 }
997
998 pub fn merge(&self, other: Option<&KeyboardOptions>) -> KeyboardOptions {
1002 other.map_or(*self, |o| o.fill_unspecified_values_with(Some(self)))
1003 }
1004}
1005
1006impl Default for KeyboardOptions {
1007 fn default() -> Self {
1008 Self::DEFAULT
1009 }
1010}
1011
1012#[derive(Debug, Clone, Copy, PartialEq)]
1013pub struct SpanStyle {
1014 pub color: Option<Color>,
1015 pub font_size: Option<f32>,
1016 pub font_weight: Option<u16>,
1017 pub font_family: Option<&'static str>,
1018 pub font_style: Option<u8>,
1019 pub text_align: Option<crate::TextAlign>,
1020 pub letter_spacing: Option<f32>,
1021 pub line_height: Option<f32>,
1022 pub background: Option<Color>,
1023 pub text_decoration: Option<crate::TextDecoration>,
1024 pub text_direction: Option<crate::TextDirection>,
1025 pub font_synthesis: Option<FontSynthesis>,
1026 pub baseline_shift: Option<BaselineShift>,
1027 pub hyphens: Option<Hyphens>,
1028 pub line_break: Option<LineBreak>,
1029 pub text_indent: Option<TextIndent>,
1030 pub draw_style: Option<DrawStyle>,
1031 pub alpha: f32,
1032}
1033
1034impl SpanStyle {
1035 pub const fn default() -> Self {
1036 Self {
1037 color: None,
1038 font_size: None,
1039 font_weight: None,
1040 font_family: None,
1041 font_style: None,
1042 text_align: None,
1043 letter_spacing: None,
1044 line_height: None,
1045 background: None,
1046 text_decoration: None,
1047 text_direction: None,
1048 font_synthesis: None,
1049 baseline_shift: None,
1050 hyphens: None,
1051 line_break: None,
1052 text_indent: None,
1053 draw_style: None,
1054 alpha: 0.0,
1055 }
1056 }
1057
1058 pub fn color(mut self, c: Color) -> Self {
1059 self.color = Some(c);
1060 self
1061 }
1062
1063 pub fn font_size(mut self, px: f32) -> Self {
1064 self.font_size = Some(px);
1065 self
1066 }
1067}
1068
1069impl Default for SpanStyle {
1070 fn default() -> Self {
1071 Self::default()
1072 }
1073}
1074
1075#[derive(Debug, Clone, PartialEq)]
1077pub struct TextSpan {
1078 pub start: usize,
1080 pub end: usize,
1082 pub style: SpanStyle,
1083}
1084
1085#[derive(Debug, Clone, PartialEq)]
1089pub struct AnnotatedString {
1090 pub text: String,
1091 pub spans: Arc<[TextSpan]>,
1092}
1093
1094impl AnnotatedString {
1095 pub fn new(text: impl Into<String>, spans: Vec<TextSpan>) -> Self {
1096 let text = text.into();
1097 Self {
1098 text,
1099 spans: spans.into(),
1100 }
1101 }
1102
1103 pub fn as_str(&self) -> &str {
1104 &self.text
1105 }
1106}
1107
1108impl From<String> for AnnotatedString {
1109 fn from(text: String) -> Self {
1110 Self {
1111 text,
1112 spans: Arc::from([]),
1113 }
1114 }
1115}
1116
1117impl From<&str> for AnnotatedString {
1118 fn from(text: &str) -> Self {
1119 Self {
1120 text: text.to_string(),
1121 spans: Arc::from([]),
1122 }
1123 }
1124}
1125
1126#[derive(Default)]
1128pub struct AnnotatedStringBuilder {
1129 text: String,
1130 spans: Vec<TextSpan>,
1131}
1132
1133impl AnnotatedStringBuilder {
1134 pub fn new() -> Self {
1135 Self::default()
1136 }
1137
1138 pub fn push(&mut self, text: &str) -> &mut Self {
1140 self.text.push_str(text);
1141 self
1142 }
1143
1144 pub fn push_with_style(&mut self, text: &str, style: SpanStyle) -> &mut Self {
1146 let start = self.text.len();
1147 self.text.push_str(text);
1148 let end = self.text.len();
1149 if start < end {
1150 self.spans.push(TextSpan { start, end, style });
1151 }
1152 self
1153 }
1154
1155 pub fn push_color(&mut self, text: &str, color: Color) -> &mut Self {
1157 self.push_with_style(text, SpanStyle::default().color(color))
1158 }
1159
1160 pub fn add_style(&mut self, start: usize, end: usize, style: SpanStyle) -> &mut Self {
1162 if start < end && end <= self.text.len() {
1163 self.spans.push(TextSpan { start, end, style });
1164 }
1165 self
1166 }
1167
1168 pub fn build(&mut self) -> AnnotatedString {
1169 let text = std::mem::take(&mut self.text);
1170 self.spans.sort_by_key(|s| s.start);
1171 let mut merged: Vec<TextSpan> = Vec::new();
1173 for span in std::mem::take(&mut self.spans) {
1174 if let Some(last) = merged.last_mut()
1175 && last.end == span.start
1176 && last.style == span.style
1177 {
1178 last.end = span.end;
1179 continue;
1180 }
1181 merged.push(span);
1182 }
1183 AnnotatedString {
1184 text,
1185 spans: merged.into(),
1186 }
1187 }
1188}
1189
1190#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
1192pub enum TextAlign {
1193 Left,
1194 Right,
1195 Center,
1196 Justify,
1197 Start,
1198 End,
1199 Unspecified,
1200}
1201
1202impl Default for TextAlign {
1203 fn default() -> Self {
1204 TextAlign::Unspecified
1205 }
1206}
1207
1208#[derive(Clone, Copy, Debug, PartialEq)]
1210pub struct FontWeight(pub u16);
1211
1212impl FontWeight {
1213 pub const THIN: FontWeight = FontWeight(100);
1214 pub const EXTRA_LIGHT: FontWeight = FontWeight(200);
1215 pub const LIGHT: FontWeight = FontWeight(300);
1216 pub const NORMAL: FontWeight = FontWeight(400);
1217 pub const MEDIUM: FontWeight = FontWeight(500);
1218 pub const SEMI_BOLD: FontWeight = FontWeight(600);
1219 pub const BOLD: FontWeight = FontWeight(700);
1220 pub const EXTRA_BOLD: FontWeight = FontWeight(800);
1221 pub const BLACK: FontWeight = FontWeight(900);
1222}
1223
1224impl Default for FontWeight {
1225 fn default() -> Self {
1226 FontWeight::NORMAL
1227 }
1228}
1229
1230#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
1232pub enum FontStyle {
1233 Normal,
1234 Italic,
1235}
1236
1237impl Default for FontStyle {
1238 fn default() -> Self {
1239 FontStyle::Normal
1240 }
1241}
1242
1243#[derive(Clone, Copy, Debug, PartialEq)]
1245pub struct TextDecoration {
1246 pub underline: bool,
1247 pub strikethrough: bool,
1248 pub color: Option<Color>,
1249}
1250
1251impl Default for TextDecoration {
1252 fn default() -> Self {
1253 Self {
1254 underline: false,
1255 strikethrough: false,
1256 color: None,
1257 }
1258 }
1259}
1260
1261pub fn build_annotated_string(b: impl FnOnce(&mut AnnotatedStringBuilder)) -> AnnotatedString {
1263 let mut builder = AnnotatedStringBuilder::new();
1264 b(&mut builder);
1265 builder.build()
1266}