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,
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,
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,
165 url: s.url.clone(),
166 })
167 .collect();
168 AnnotatedString::new(text, spans)
169 }
170
171 pub fn copy(&self, annotated_string: AnnotatedString) -> Self {
173 TextFieldValue {
174 annotated_string,
175 selection: self.selection,
176 composition: self.composition,
177 }
178 }
179
180 pub fn copy_text(&self, text: String) -> Self {
182 TextFieldValue {
183 annotated_string: AnnotatedString::from(text),
184 selection: self.selection,
185 composition: self.composition,
186 }
187 }
188}
189
190#[derive(Clone, Debug)]
193pub struct TextLayoutResult {
194 pub line_count: usize,
196 pub width_px: f32,
198 pub height_px: f32,
200 pub first_baseline: f32,
202 pub last_baseline: f32,
204 pub did_overflow_width: bool,
206 pub did_overflow_height: bool,
208 pub lines: Vec<TextLineInfo>,
210}
211
212#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
215pub enum TextObfuscationMode {
216 Visible,
218 RevealLastTyped,
220 Hidden,
222 #[default]
224 System,
225}
226
227#[derive(Clone, Debug)]
229pub struct TextLineInfo {
230 pub start: usize,
232 pub end: usize,
234 pub top: f32,
236 pub baseline: f32,
238 pub bottom: f32,
240 pub left: f32,
242 pub right: f32,
244 pub width: f32,
246}
247
248impl TextLayoutResult {
249 pub fn get_line_start(&self, line_index: usize) -> Option<usize> {
250 self.lines.get(line_index).map(|l| l.start)
251 }
252
253 pub fn get_line_end(&self, line_index: usize) -> Option<usize> {
254 self.lines.get(line_index).map(|l| l.end)
255 }
256
257 pub fn get_line_end_visible(&self, line_index: usize) -> Option<usize> {
258 self.lines.get(line_index).map(|l| l.end)
259 }
260
261 pub fn is_line_ellipsized(&self, _line_index: usize) -> bool {
262 false
263 }
264
265 pub fn get_line_top(&self, line_index: usize) -> Option<f32> {
266 self.lines.get(line_index).map(|l| l.top)
267 }
268
269 pub fn get_line_baseline(&self, line_index: usize) -> Option<f32> {
270 self.lines.get(line_index).map(|l| l.baseline)
271 }
272
273 pub fn get_line_bottom(&self, line_index: usize) -> Option<f32> {
274 self.lines.get(line_index).map(|l| l.bottom)
275 }
276
277 pub fn get_line_left(&self, line_index: usize) -> Option<f32> {
278 self.lines.get(line_index).map(|l| l.left)
279 }
280
281 pub fn get_line_right(&self, line_index: usize) -> Option<f32> {
282 self.lines.get(line_index).map(|l| l.right)
283 }
284
285 pub fn get_line_for_offset(&self, offset: usize) -> usize {
286 for (i, line) in self.lines.iter().enumerate() {
287 if offset >= line.start && offset < line.end {
288 return i;
289 }
290 }
291 self.line_count.saturating_sub(1)
292 }
293
294 pub fn get_line_for_vertical_position(&self, vertical: f32) -> usize {
295 for (i, line) in self.lines.iter().enumerate() {
296 if vertical >= line.top && vertical < line.bottom {
297 return i;
298 }
299 }
300 self.line_count.saturating_sub(1)
301 }
302
303 pub fn get_horizontal_position(&self, offset: usize, _use_primary_direction: bool) -> f32 {
304 self.lines
305 .iter()
306 .find(|l| offset >= l.start && offset <= l.end)
307 .map(|l| l.left)
308 .unwrap_or(0.0)
309 }
310
311 pub fn has_visual_overflow(&self) -> bool {
313 self.did_overflow_width || self.did_overflow_height
314 }
315
316 pub fn get_bounding_box(&self, offset: usize) -> crate::Rect {
319 if self.lines.is_empty() || offset > self.lines.last().unwrap().end {
320 return crate::Rect::default();
321 }
322 let line = self
323 .lines
324 .iter()
325 .find(|l| offset >= l.start && offset < l.end)
326 .unwrap_or_else(|| {
327 if offset >= self.lines.last().unwrap().end {
328 self.lines.last().unwrap()
329 } else {
330 &self.lines[0]
331 }
332 });
333 let _line_idx = self.get_line_for_offset(offset);
334 let char_in_line = offset - line.start;
335 let pos_in_line = if char_in_line == 0 {
336 line.left
337 } else {
338 let chars = line.end - line.start;
340 if chars == 0 {
341 line.left
342 } else {
343 line.left + (line.width * (char_in_line as f32) / (chars as f32))
344 }
345 };
346 crate::Rect {
347 x: pos_in_line,
348 y: line.top,
349 w: if char_in_line < (line.end - line.start) {
350 line.width / (line.end - line.start).max(1) as f32
351 } else {
352 1.0
353 },
354 h: line.bottom - line.top,
355 }
356 }
357
358 pub fn get_cursor_rect(&self, offset: usize) -> crate::Rect {
360 let line = self
361 .lines
362 .iter()
363 .find(|l| offset >= l.start && offset <= l.end)
364 .unwrap_or_else(|| {
365 if offset >= self.lines.last().map(|l| l.end).unwrap_or(0) {
366 self.lines.last().unwrap()
367 } else {
368 &self.lines[0]
369 }
370 });
371 let char_in_line = offset.saturating_sub(line.start);
372 let chars = (line.end - line.start).max(1);
373 let x = line.left + (line.width * (char_in_line as f32) / (chars as f32));
374 crate::Rect {
375 x,
376 y: line.top,
377 w: 1.0,
378 h: line.bottom - line.top,
379 }
380 }
381
382 pub fn get_offset_for_position(&self, position: (f32, f32)) -> Option<usize> {
384 let line_idx = self.get_line_for_vertical_position(position.1);
385 let line = self.lines.get(line_idx)?;
386 if position.0 <= line.left {
387 return Some(line.start);
388 }
389 if position.0 >= line.right {
390 return Some(line.end);
391 }
392 let fraction = (position.0 - line.left) / line.width.max(1.0);
393 let offset_in_line = ((line.end - line.start) as f32 * fraction).round() as usize;
394 Some((line.start + offset_in_line).min(line.end))
395 }
396
397 pub fn get_word_boundary(&self, _offset: usize) -> super::TextRange {
399 let text = ""; let start = if text.is_empty() { 0 } else { 0 };
402 let end = if text.is_empty() { 0 } else { 0 };
403 super::TextRange::new(start, end)
404 }
405
406 pub fn get_paragraph_direction(&self, _offset: usize) -> u8 {
408 0 }
410
411 pub fn get_bidi_run_direction(&self, _offset: usize) -> u8 {
413 0 }
415}
416
417pub trait OffsetMapping: Debug + Send + Sync + 'static {
419 fn original_to_transformed(&self, offset: usize) -> usize;
420 fn transformed_to_original(&self, offset: usize) -> usize;
421 fn clone_box(&self) -> Box<dyn OffsetMapping>;
422}
423
424#[derive(Clone, Copy, Debug)]
426pub struct IdentityOffsetMapping;
427
428impl OffsetMapping for IdentityOffsetMapping {
429 fn original_to_transformed(&self, offset: usize) -> usize {
430 offset
431 }
432 fn transformed_to_original(&self, offset: usize) -> usize {
433 offset
434 }
435 fn clone_box(&self) -> Box<dyn OffsetMapping> {
436 Box::new(*self)
437 }
438}
439
440pub trait VisualTransformation: Debug + Send + Sync + 'static {
443 fn filter(&self, text: &AnnotatedString) -> TransformedText;
446}
447
448pub struct TransformedText {
450 pub text: AnnotatedString,
452 pub offset_mapping: Box<dyn OffsetMapping>,
454}
455
456impl TransformedText {
457 pub fn new(text: AnnotatedString, offset_mapping: Box<dyn OffsetMapping>) -> Self {
458 TransformedText {
459 text,
460 offset_mapping,
461 }
462 }
463}
464
465impl Clone for TransformedText {
466 fn clone(&self) -> Self {
467 Self {
468 text: self.text.clone(),
469 offset_mapping: self.offset_mapping.clone_box(),
470 }
471 }
472}
473
474impl Debug for TransformedText {
475 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
476 f.debug_struct("TransformedText")
477 .field("text", &self.text.text)
478 .finish()
479 }
480}
481
482#[derive(Clone, Copy, Debug)]
485pub struct IdentityVisualTransformation;
486
487impl VisualTransformation for IdentityVisualTransformation {
488 fn filter(&self, text: &AnnotatedString) -> TransformedText {
489 TransformedText {
490 text: text.clone(),
491 offset_mapping: Box::new(IdentityOffsetMapping),
492 }
493 }
494}
495
496#[derive(Clone, Copy, Debug)]
499pub struct PasswordVisualTransformation {
500 pub mask: char,
502}
503
504impl Default for PasswordVisualTransformation {
505 fn default() -> Self {
506 Self { mask: '\u{2022}' }
507 }
508}
509
510impl VisualTransformation for PasswordVisualTransformation {
511 fn filter(&self, text: &AnnotatedString) -> TransformedText {
512 let masked_text: String = text.text.chars().map(|_| self.mask).collect();
513 TransformedText {
514 text: AnnotatedString::new(masked_text, vec![]),
515 offset_mapping: Box::new(IdentityOffsetMapping),
516 }
517 }
518}
519
520pub fn original_offset_to_display(original: &str, display: &str, original_byte: usize) -> usize {
523 original_offset_to_display_with_mapping(original, display, original_byte, None)
524}
525
526pub fn original_offset_to_display_with_mapping(
529 original: &str,
530 display: &str,
531 original_byte: usize,
532 offset_mapping: Option<&dyn OffsetMapping>,
533) -> usize {
534 if let Some(om) = offset_mapping {
535 om.original_to_transformed(original_byte)
536 } else {
537 let char_idx = original[..original_byte.min(original.len())]
538 .chars()
539 .count();
540 display
541 .char_indices()
542 .nth(char_idx)
543 .map(|(i, _)| i)
544 .unwrap_or(display.len())
545 }
546}
547
548#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
551pub enum KeyboardCapitalization {
552 #[default]
553 Unspecified,
554 None,
555 Characters,
556 Words,
557 Sentences,
558}
559
560#[derive(Clone, Copy, Debug, PartialEq)]
562pub struct Shadow {
563 pub color: Color,
564 pub offset_x: f32,
566 pub offset_y: f32,
568 pub blur_radius: f32,
570}
571
572#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
575pub enum FontSynthesis {
576 #[default]
577 Unspecified,
578 None,
579 Weight,
580 Style,
581 SmallCaps,
582 All,
583}
584
585#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
587pub enum BaselineShift {
588 #[default]
589 Unspecified,
590 Superscript,
591 Subscript,
592}
593
594#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
596pub enum Hyphens {
597 #[default]
598 Unspecified,
599 None,
600 Auto,
601}
602
603#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
605pub enum LineBreak {
606 #[default]
607 Unspecified,
608 Simple,
609 Heading,
610 Paragraph,
611}
612
613#[derive(Clone, Copy, Debug, PartialEq)]
615pub struct TextIndent {
616 pub first_line: f32,
617 pub rest_lines: f32,
618}
619
620impl Default for TextIndent {
621 fn default() -> Self {
622 Self {
623 first_line: 0.0,
624 rest_lines: 0.0,
625 }
626 }
627}
628
629#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
631pub enum DrawStyle {
632 #[default]
633 Fill,
634 Stroke,
635}
636
637#[derive(Clone, Debug)]
640pub struct TextStyle {
641 pub font_size: f32,
643 pub color: Option<Color>,
645 pub font_weight: Option<u16>,
647 pub font_family: Option<&'static str>,
649 pub font_style: Option<u8>,
651 pub text_align: crate::TextAlign,
653 pub letter_spacing: f32,
655 pub line_height: f32,
657 pub background: Option<Color>,
659 pub text_decoration: Option<crate::TextDecoration>,
661 pub shadow: Option<Shadow>,
663 pub text_direction: Option<crate::TextDirection>,
665 pub font_synthesis: FontSynthesis,
667 pub baseline_shift: BaselineShift,
669 pub hyphens: Hyphens,
671 pub line_break: LineBreak,
673 pub text_indent: Option<TextIndent>,
675 pub draw_style: DrawStyle,
677 pub alpha: f32,
679 pub locale_list: Option<String>,
681 pub font_feature_settings: Option<String>,
683}
684
685impl Default for TextStyle {
686 fn default() -> Self {
687 Self {
688 font_size: 0.0,
689 color: None,
690 font_weight: None,
691 font_family: None,
692 font_style: None,
693 text_align: crate::TextAlign::Unspecified,
694 letter_spacing: 0.0,
695 line_height: 0.0,
696 background: None,
697 text_decoration: None,
698 shadow: None,
699 text_direction: None,
700 font_synthesis: FontSynthesis::Unspecified,
701 baseline_shift: BaselineShift::Unspecified,
702 hyphens: Hyphens::Unspecified,
703 line_break: LineBreak::Unspecified,
704 text_indent: None,
705 draw_style: DrawStyle::Fill,
706 alpha: 0.0,
707 locale_list: None,
708 font_feature_settings: None,
709 }
710 }
711}
712
713#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
715pub enum KeyboardType {
716 #[default]
717 Unspecified,
718 Text,
719 Ascii,
720 Number,
721 Phone,
722 Uri,
723 Email,
724 Password,
725 NumberPassword,
726 Decimal,
727 PasswordVisible,
728 PostalAddress,
729 PersonName,
730 EmailSubject,
731 ShortMessage,
732 LongMessage,
733 Filter,
734 Phonetic,
735 DateTime,
736 Date,
737 Time,
738 NumberSigned,
739 DecimalSigned,
740 DecimalPassword,
741 NumberPasswordSigned,
742 DecimalPasswordSigned,
743}
744
745#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
747pub enum ImeAction {
748 #[default]
749 Unspecified,
750 None,
751 Default,
752 Go,
753 Search,
754 Send,
755 Previous,
756 Next,
757 Done,
758}
759
760pub trait KeyboardActionScope {
763 fn default_keyboard_action(&self, action: ImeAction);
764}
765
766#[derive(Clone)]
769pub struct KeyboardActions {
770 pub on_done: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
771 pub on_go: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
772 pub on_next: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
773 pub on_previous: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
774 pub on_search: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
775 pub on_send: Option<Rc<dyn Fn(&dyn KeyboardActionScope)>>,
776}
777
778impl KeyboardActions {
779 pub fn on_any(f: impl Fn(ImeAction, &dyn KeyboardActionScope) + 'static) -> Self {
780 let f = Rc::new(f);
781 KeyboardActions {
782 on_done: Some({
783 let f = f.clone();
784 Rc::new(move |scope| f(ImeAction::Done, scope))
785 }),
786 on_go: Some({
787 let f = f.clone();
788 Rc::new(move |scope| f(ImeAction::Go, scope))
789 }),
790 on_next: Some({
791 let f = f.clone();
792 Rc::new(move |scope| f(ImeAction::Next, scope))
793 }),
794 on_previous: Some({
795 let f = f.clone();
796 Rc::new(move |scope| f(ImeAction::Previous, scope))
797 }),
798 on_search: Some({
799 let f = f.clone();
800 Rc::new(move |scope| f(ImeAction::Search, scope))
801 }),
802 on_send: Some({ Rc::new(move |scope| f(ImeAction::Send, scope)) }),
803 }
804 }
805}
806
807impl Default for KeyboardActions {
808 fn default() -> Self {
809 KeyboardActions {
810 on_done: None,
811 on_go: None,
812 on_next: None,
813 on_previous: None,
814 on_search: None,
815 on_send: None,
816 }
817 }
818}
819
820struct NoopKeyboardActionScope;
821impl KeyboardActionScope for NoopKeyboardActionScope {
822 fn default_keyboard_action(&self, _action: ImeAction) {}
823}
824
825pub trait KeyboardActionHandler: Debug + 'static {
828 fn on_keyboard_action(&self, perform_default: &dyn Fn());
829}
830
831#[derive(Clone, Copy, Debug)]
833pub struct DefaultKeyboardActionHandler;
834
835impl KeyboardActionHandler for DefaultKeyboardActionHandler {
836 fn on_keyboard_action(&self, perform_default: &dyn Fn()) {
837 perform_default();
838 }
839}
840
841pub trait TextFieldBuffer {
844 fn text(&self) -> &str;
845 fn set_text(&mut self, text: &str);
846 fn selection(&self) -> TextRange;
847 fn set_selection(&mut self, sel: TextRange);
848 fn length(&self) -> usize;
849 fn replace(&mut self, start: usize, end: usize, text: &str);
850 fn insert(&mut self, index: usize, text: &str);
851 fn delete(&mut self, start: usize, end: usize);
852 fn place_cursor_before_char_at(&mut self, index: usize);
853 fn place_cursor_at_end(&mut self);
854 fn select_all(&mut self);
855 fn revert_all_changes(&mut self);
856 fn original_text(&self) -> &str;
857 fn original_selection(&self) -> TextRange;
858 fn has_selection(&self) -> bool;
859}
860
861#[derive(Clone, Copy, Debug, PartialEq, Eq)]
864pub enum TextFieldLineLimits {
865 SingleLine,
866 MultiLine {
867 min_height_in_lines: usize,
868 max_height_in_lines: usize,
869 },
870}
871
872impl TextFieldLineLimits {
873 pub fn default() -> Self {
874 TextFieldLineLimits::MultiLine {
875 min_height_in_lines: 1,
876 max_height_in_lines: usize::MAX,
877 }
878 }
879}
880
881pub trait TextFieldDecorator: Debug + 'static {
884 fn decorate(&self, inner: crate::View) -> crate::View;
885}
886
887#[derive(Clone, Copy, Debug)]
889pub struct DefaultTextFieldDecorator;
890
891impl TextFieldDecorator for DefaultTextFieldDecorator {
892 fn decorate(&self, inner: crate::View) -> crate::View {
893 inner
894 }
895}
896
897pub trait InputTransformation: Debug + 'static {
900 fn keyboard_options(&self) -> Option<KeyboardOptions> {
901 None
902 }
903 fn transform_input(&self, buffer: &mut dyn TextFieldBuffer);
904}
905
906pub trait OutputTransformation: Debug + 'static {
909 fn transform_output(&self, buffer: &mut dyn TextFieldBuffer);
910}
911
912pub struct CodepointTransformation {
915 pub transform: Box<dyn Fn(usize, char) -> char>,
916}
917
918impl Clone for CodepointTransformation {
919 fn clone(&self) -> Self {
920 panic!("CodepointTransformation::clone() is not supported -> use Rc instead");
924 }
925}
926
927impl CodepointTransformation {
928 pub fn new(transform: impl Fn(usize, char) -> char + 'static) -> Self {
929 CodepointTransformation {
930 transform: Box::new(transform),
931 }
932 }
933
934 pub fn transform(&self, codepoint_index: usize, codepoint: char) -> char {
935 (self.transform)(codepoint_index, codepoint)
936 }
937}
938
939#[derive(Clone, Copy, Debug, PartialEq)]
942pub struct KeyboardOptions {
943 pub keyboard_type: KeyboardType,
944 pub capitalization: KeyboardCapitalization,
945 pub ime_action: ImeAction,
946 pub auto_correct_enabled: Option<bool>,
947 pub show_keyboard_on_focus: Option<bool>,
948 pub platform_ime_options: Option<&'static str>,
949 pub hint_locales: Option<&'static str>,
950}
951
952impl KeyboardOptions {
953 pub const DEFAULT: KeyboardOptions = KeyboardOptions {
954 keyboard_type: KeyboardType::Text,
955 capitalization: KeyboardCapitalization::Unspecified,
956 ime_action: ImeAction::Unspecified,
957 auto_correct_enabled: None,
958 show_keyboard_on_focus: None,
959 platform_ime_options: None,
960 hint_locales: None,
961 };
962
963 pub const SECURE_TEXT_FIELD: KeyboardOptions = KeyboardOptions {
964 keyboard_type: KeyboardType::Password,
965 capitalization: KeyboardCapitalization::Unspecified,
966 ime_action: ImeAction::Unspecified,
967 auto_correct_enabled: Some(false),
968 show_keyboard_on_focus: None,
969 platform_ime_options: None,
970 hint_locales: None,
971 };
972
973 pub fn fill_unspecified_values_with(&self, other: Option<&KeyboardOptions>) -> KeyboardOptions {
974 let other = match other {
975 Some(o) => o,
976 None => return *self,
977 };
978 KeyboardOptions {
979 keyboard_type: if self.keyboard_type == KeyboardType::Unspecified {
980 other.keyboard_type
981 } else {
982 self.keyboard_type
983 },
984 capitalization: if self.capitalization == KeyboardCapitalization::Unspecified {
985 other.capitalization
986 } else {
987 self.capitalization
988 },
989 ime_action: if self.ime_action == ImeAction::Unspecified {
990 other.ime_action
991 } else {
992 self.ime_action
993 },
994 auto_correct_enabled: self.auto_correct_enabled.or(other.auto_correct_enabled),
995 show_keyboard_on_focus: self.show_keyboard_on_focus.or(other.show_keyboard_on_focus),
996 platform_ime_options: self.platform_ime_options.or(other.platform_ime_options),
997 hint_locales: self.hint_locales.or(other.hint_locales),
998 }
999 }
1000
1001 pub fn merge(&self, other: Option<&KeyboardOptions>) -> KeyboardOptions {
1005 other.map_or(*self, |o| o.fill_unspecified_values_with(Some(self)))
1006 }
1007}
1008
1009impl Default for KeyboardOptions {
1010 fn default() -> Self {
1011 Self::DEFAULT
1012 }
1013}
1014
1015#[derive(Debug, Clone, Copy, PartialEq)]
1016pub struct SpanStyle {
1017 pub color: Option<Color>,
1018 pub font_size: Option<f32>,
1019 pub font_weight: Option<u16>,
1020 pub font_family: Option<&'static str>,
1021 pub font_style: Option<u8>,
1022 pub text_align: Option<crate::TextAlign>,
1023 pub letter_spacing: Option<f32>,
1024 pub line_height: Option<f32>,
1025 pub background: Option<Color>,
1026 pub text_decoration: Option<crate::TextDecoration>,
1027 pub text_direction: Option<crate::TextDirection>,
1028 pub font_synthesis: Option<FontSynthesis>,
1029 pub baseline_shift: Option<BaselineShift>,
1030 pub hyphens: Option<Hyphens>,
1031 pub line_break: Option<LineBreak>,
1032 pub text_indent: Option<TextIndent>,
1033 pub draw_style: Option<DrawStyle>,
1034 pub alpha: f32,
1035}
1036
1037impl SpanStyle {
1038 pub const fn default() -> Self {
1039 Self {
1040 color: None,
1041 font_size: None,
1042 font_weight: None,
1043 font_family: None,
1044 font_style: None,
1045 text_align: None,
1046 letter_spacing: None,
1047 line_height: None,
1048 background: None,
1049 text_decoration: None,
1050 text_direction: None,
1051 font_synthesis: None,
1052 baseline_shift: None,
1053 hyphens: None,
1054 line_break: None,
1055 text_indent: None,
1056 draw_style: None,
1057 alpha: 0.0,
1058 }
1059 }
1060
1061 pub fn color(mut self, c: Color) -> Self {
1062 self.color = Some(c);
1063 self
1064 }
1065
1066 pub fn font_size(mut self, px: f32) -> Self {
1067 self.font_size = Some(px);
1068 self
1069 }
1070
1071 pub fn text_decoration(mut self, d: TextDecoration) -> Self {
1072 self.text_decoration = Some(d);
1073 self
1074 }
1075
1076 pub fn font_weight(mut self, w: u16) -> Self {
1077 self.font_weight = Some(w);
1078 self
1079 }
1080
1081 pub fn font_style(mut self, s: u8) -> Self {
1082 self.font_style = Some(s);
1083 self
1084 }
1085
1086 pub fn background(mut self, c: Color) -> Self {
1087 self.background = Some(c);
1088 self
1089 }
1090}
1091
1092impl Default for SpanStyle {
1093 fn default() -> Self {
1094 Self::default()
1095 }
1096}
1097
1098#[derive(Debug, Clone, PartialEq)]
1100pub struct TextSpan {
1101 pub start: usize,
1103 pub end: usize,
1105 pub style: SpanStyle,
1106 pub url: Option<Arc<str>>,
1108}
1109
1110#[derive(Debug, Clone, PartialEq)]
1114pub struct AnnotatedString {
1115 pub text: String,
1116 pub spans: Arc<[TextSpan]>,
1117}
1118
1119impl AnnotatedString {
1120 pub fn new(text: impl Into<String>, spans: Vec<TextSpan>) -> Self {
1121 let text = text.into();
1122 Self {
1123 text,
1124 spans: spans.into(),
1125 }
1126 }
1127
1128 pub fn as_str(&self) -> &str {
1129 &self.text
1130 }
1131}
1132
1133impl From<String> for AnnotatedString {
1134 fn from(text: String) -> Self {
1135 Self {
1136 text,
1137 spans: Arc::from([]),
1138 }
1139 }
1140}
1141
1142impl From<&str> for AnnotatedString {
1143 fn from(text: &str) -> Self {
1144 Self {
1145 text: text.to_string(),
1146 spans: Arc::from([]),
1147 }
1148 }
1149}
1150
1151#[derive(Default)]
1153pub struct AnnotatedStringBuilder {
1154 text: String,
1155 spans: Vec<TextSpan>,
1156}
1157
1158impl AnnotatedStringBuilder {
1159 pub fn new() -> Self {
1160 Self::default()
1161 }
1162
1163 pub fn push(&mut self, text: &str) -> &mut Self {
1165 self.text.push_str(text);
1166 self
1167 }
1168
1169 pub fn push_with_style(&mut self, text: &str, style: SpanStyle) -> &mut Self {
1171 let start = self.text.len();
1172 self.text.push_str(text);
1173 let end = self.text.len();
1174 if start < end {
1175 self.spans.push(TextSpan {
1176 start,
1177 end,
1178 style,
1179 url: None,
1180 });
1181 }
1182 self
1183 }
1184
1185 pub fn push_color(&mut self, text: &str, color: Color) -> &mut Self {
1187 self.push_with_style(text, SpanStyle::default().color(color))
1188 }
1189
1190 pub fn push_link(&mut self, text: &str, url: impl Into<Arc<str>>) -> &mut Self {
1192 let start = self.text.len();
1193 self.text.push_str(text);
1194 let end = self.text.len();
1195 if start < end {
1196 self.spans.push(TextSpan {
1197 start,
1198 end,
1199 style: SpanStyle::default()
1200 .color(Color::from_rgba(0x15, 0x76, 0xFF, 255))
1201 .text_decoration(TextDecoration::UNDERLINE),
1202 url: Some(url.into()),
1203 });
1204 }
1205 self
1206 }
1207
1208 pub fn add_style(&mut self, start: usize, end: usize, style: SpanStyle) -> &mut Self {
1210 if start < end && end <= self.text.len() {
1211 self.spans.push(TextSpan {
1212 start,
1213 end,
1214 style,
1215 url: None,
1216 });
1217 }
1218 self
1219 }
1220
1221 pub fn build(&mut self) -> AnnotatedString {
1222 let text = std::mem::take(&mut self.text);
1223 self.spans.sort_by_key(|s| s.start);
1224 let mut merged: Vec<TextSpan> = Vec::new();
1226 for span in std::mem::take(&mut self.spans) {
1227 if let Some(last) = merged.last_mut()
1228 && last.end == span.start
1229 && last.style == span.style
1230 {
1231 last.end = span.end;
1232 continue;
1233 }
1234 merged.push(span);
1235 }
1236 AnnotatedString {
1237 text,
1238 spans: merged.into(),
1239 }
1240 }
1241}
1242
1243#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
1245pub enum TextAlign {
1246 Left,
1247 Right,
1248 Center,
1249 Justify,
1250 Start,
1251 End,
1252 Unspecified,
1253}
1254
1255impl Default for TextAlign {
1256 fn default() -> Self {
1257 TextAlign::Unspecified
1258 }
1259}
1260
1261#[derive(Clone, Copy, Debug, PartialEq)]
1263pub struct FontWeight(pub u16);
1264
1265impl FontWeight {
1266 pub const THIN: FontWeight = FontWeight(100);
1267 pub const EXTRA_LIGHT: FontWeight = FontWeight(200);
1268 pub const LIGHT: FontWeight = FontWeight(300);
1269 pub const NORMAL: FontWeight = FontWeight(400);
1270 pub const MEDIUM: FontWeight = FontWeight(500);
1271 pub const SEMI_BOLD: FontWeight = FontWeight(600);
1272 pub const BOLD: FontWeight = FontWeight(700);
1273 pub const EXTRA_BOLD: FontWeight = FontWeight(800);
1274 pub const BLACK: FontWeight = FontWeight(900);
1275}
1276
1277impl Default for FontWeight {
1278 fn default() -> Self {
1279 FontWeight::NORMAL
1280 }
1281}
1282
1283#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
1285pub enum FontStyle {
1286 Normal,
1287 Italic,
1288}
1289
1290impl Default for FontStyle {
1291 fn default() -> Self {
1292 FontStyle::Normal
1293 }
1294}
1295
1296#[derive(Clone, Copy, Debug, PartialEq)]
1298pub struct TextDecoration {
1299 pub underline: bool,
1300 pub strikethrough: bool,
1301 pub color: Option<Color>,
1302}
1303
1304impl TextDecoration {
1305 pub const UNDERLINE: TextDecoration = TextDecoration {
1306 underline: true,
1307 strikethrough: false,
1308 color: None,
1309 };
1310 pub const STRIKETHROUGH: TextDecoration = TextDecoration {
1311 underline: false,
1312 strikethrough: true,
1313 color: None,
1314 };
1315}
1316
1317impl Default for TextDecoration {
1318 fn default() -> Self {
1319 Self {
1320 underline: false,
1321 strikethrough: false,
1322 color: None,
1323 }
1324 }
1325}
1326
1327pub fn build_annotated_string(b: impl FnOnce(&mut AnnotatedStringBuilder)) -> AnnotatedString {
1329 let mut builder = AnnotatedStringBuilder::new();
1330 b(&mut builder);
1331 builder.build()
1332}
1333
1334