fret_runtime/
window_text_input_snapshot.rs1use std::collections::HashMap;
2use std::sync::Arc;
3
4use fret_core::{AppWindowId, Rect};
5
6#[derive(Debug, Clone, PartialEq, Eq, Hash)]
15pub struct WindowImeSurroundingText {
16 pub text: Arc<str>,
17 pub cursor: u32,
18 pub anchor: u32,
19}
20
21impl WindowImeSurroundingText {
22 pub const MAX_TEXT_BYTES: usize = 4000;
23
24 pub fn best_effort_for_str(text: &str, cursor: usize, anchor: usize) -> Self {
29 fn clamp_down_to_char_boundary(text: &str, idx: usize) -> usize {
30 let mut idx = idx.min(text.len());
31 while idx > 0 && !text.is_char_boundary(idx) {
32 idx = idx.saturating_sub(1);
33 }
34 idx
35 }
36
37 let cursor = clamp_down_to_char_boundary(text, cursor);
38 let mut anchor = clamp_down_to_char_boundary(text, anchor);
39 let len = text.len();
40
41 if len <= Self::MAX_TEXT_BYTES {
42 return Self {
43 text: Arc::<str>::from(text),
44 cursor: u32::try_from(cursor).unwrap_or(u32::MAX),
45 anchor: u32::try_from(anchor).unwrap_or(u32::MAX),
46 };
47 }
48
49 let mut low = cursor.min(anchor);
50 let mut high = cursor.max(anchor);
51 if high.saturating_sub(low) > Self::MAX_TEXT_BYTES {
52 anchor = cursor;
53 low = cursor;
54 high = cursor;
55 }
56
57 let needed = high.saturating_sub(low);
58 let slack = Self::MAX_TEXT_BYTES.saturating_sub(needed);
59 let before = slack / 2;
60
61 let mut start = low
62 .saturating_sub(before)
63 .min(len.saturating_sub(Self::MAX_TEXT_BYTES));
64 let mut end = (start + Self::MAX_TEXT_BYTES).min(len);
65
66 start = clamp_down_to_char_boundary(text, start);
67 end = clamp_down_to_char_boundary(text, end);
68 if end < start {
69 end = start;
70 }
71
72 let cursor_rel = cursor.saturating_sub(start).min(end.saturating_sub(start));
73 let anchor_rel = anchor.saturating_sub(start).min(end.saturating_sub(start));
74 let excerpt = &text[start..end];
75
76 Self {
77 text: Arc::<str>::from(excerpt),
78 cursor: u32::try_from(cursor_rel).unwrap_or(u32::MAX),
79 anchor: u32::try_from(anchor_rel).unwrap_or(u32::MAX),
80 }
81 }
82}
83
84#[derive(Debug, Clone, PartialEq, Default)]
92pub struct WindowTextInputSnapshot {
93 pub focus_is_text_input: bool,
94 pub is_composing: bool,
95 pub text_len_utf16: u32,
97 pub selection_utf16: Option<(u32, u32)>,
99 pub marked_utf16: Option<(u32, u32)>,
101 pub ime_cursor_area: Option<Rect>,
105 pub surrounding_text: Option<WindowImeSurroundingText>,
107}
108
109#[derive(Debug, Default)]
110pub struct WindowTextInputSnapshotService {
111 by_window: HashMap<AppWindowId, WindowTextInputSnapshot>,
112}
113
114impl WindowTextInputSnapshotService {
115 pub fn snapshot(&self, window: AppWindowId) -> Option<&WindowTextInputSnapshot> {
116 self.by_window.get(&window)
117 }
118
119 pub fn set_snapshot(&mut self, window: AppWindowId, snapshot: WindowTextInputSnapshot) {
120 self.by_window.insert(window, snapshot);
121 }
122
123 pub fn remove_window(&mut self, window: AppWindowId) {
124 self.by_window.remove(&window);
125 }
126}