#[non_exhaustive]pub struct Selection {
pub anchor: usize,
pub active: usize,
pub h_pos: Option<f64>,
}
Expand description
A range of selected text, or a caret.
A caret is the blinking vertical bar where text is to be inserted. We
represent it as a selection with zero length, where anchor == active
.
Indices are always expressed in UTF-8 bytes, and must be between 0 and the
document length, inclusive.
As an example, if the input caret is at the start of the document hello world
, we would expect both anchor
and active
to be 0
. If the user
holds shift and presses the right arrow key five times, we would expect the
word hello
to be selected, the anchor
to still be 0
, and the active
to now be 5
.
Fields (Non-exhaustive)§
This struct is marked as non-exhaustive
Struct { .. }
syntax; cannot be matched against without a wildcard ..
; and struct update syntax will not work.anchor: usize
The ‘anchor’ end of the selection.
This is the end of the selection that stays unchanged while holding shift and pressing the arrow keys.
active: usize
The ‘active’ end of the selection.
This is the end of the selection that moves while holding shift and pressing the arrow keys.
h_pos: Option<f64>
The saved horizontal position, during vertical movement.
This should not be set by the IME; it will be tracked and handled by the text field.
Implementations§
Source§impl Selection
impl Selection
Sourcepub fn new(anchor: usize, active: usize) -> Selection
pub fn new(anchor: usize, active: usize) -> Selection
Create a new Selection
with the provided anchor
and active
positions.
Both positions refer to UTF-8 byte indices in some text.
If your selection is a caret, you can use Selection::caret
instead.
Examples found in repository?
266fn apply_default_behavior(handler: &mut AppInputHandler, action: Action) -> bool {
267 let is_caret = handler.selection().is_caret();
268 match action {
269 Action::Move(movement) => {
270 let selection = handler.selection();
271 let index = if movement_goes_downstream(movement) {
272 selection.max()
273 } else {
274 selection.min()
275 };
276 let updated_index = if let (false, text::Movement::Grapheme(_)) = (is_caret, movement) {
277 // handle special cases of pressing left/right when the selection is not a caret
278 index
279 } else {
280 match apply_movement(handler, movement, index) {
281 Some(v) => v,
282 None => return false,
283 }
284 };
285 handler.set_selection(Selection::caret(updated_index));
286 }
287 Action::MoveSelecting(movement) => {
288 let mut selection = handler.selection();
289 selection.active = match apply_movement(handler, movement, selection.active) {
290 Some(v) => v,
291 None => return false,
292 };
293 handler.set_selection(selection);
294 }
295 Action::SelectAll => {
296 let len = handler.len();
297 let selection = Selection::new(0, len);
298 handler.set_selection(selection);
299 }
300 Action::Delete(_) if !is_caret => {
301 // movement is ignored for non-caret selections
302 let selection = handler.selection();
303 handler.replace_range(selection.range(), "");
304 }
305 Action::Delete(movement) => {
306 let mut selection = handler.selection();
307 selection.active = match apply_movement(handler, movement, selection.active) {
308 Some(v) => v,
309 None => return false,
310 };
311 handler.replace_range(selection.range(), "");
312 }
313 _ => return false,
314 }
315 true
316}
Sourcepub fn caret(index: usize) -> Selection
pub fn caret(index: usize) -> Selection
Create a new caret (zero-length selection) at the provided UTF-8 byte index.
index
must be a grapheme cluster boundary.
Examples found in repository?
129 fn key_down(&mut self, event: KeyEvent) -> bool {
130 if event.key == Key::Character("c".to_string()) {
131 // custom hotkey for pressing "c"
132 println!("user pressed c! wow! setting selection to 0");
133
134 // update internal selection state
135 self.document.borrow_mut().selection = Selection::caret(0);
136
137 // notify the OS that we've updated the selection
138 self.handle
139 .update_text_field(self.text_input_token.unwrap(), Event::SelectionChanged);
140
141 // repaint window
142 self.handle.request_anim_frame();
143
144 // return true prevents the keypress event from being handled as text input
145 return true;
146 }
147 false
148 }
149
150 fn acquire_input_lock(
151 &mut self,
152 _token: TextFieldToken,
153 _mutable: bool,
154 ) -> Box<dyn InputHandler> {
155 Box::new(AppInputHandler {
156 state: self.document.clone(),
157 window_size: self.size,
158 window_handle: self.handle.clone(),
159 })
160 }
161
162 fn release_input_lock(&mut self, _token: TextFieldToken) {
163 // no action required; this example is simple enough that this
164 // state is not actually shared.
165 }
166
167 fn size(&mut self, size: Size) {
168 self.size = size;
169 }
170
171 fn request_close(&mut self) {
172 self.handle.close();
173 }
174
175 fn destroy(&mut self) {
176 Application::global().quit()
177 }
178
179 fn as_any(&mut self) -> &mut dyn Any {
180 self
181 }
182}
183
184struct AppInputHandler {
185 state: Rc<RefCell<DocumentState>>,
186 window_size: Size,
187 window_handle: WindowHandle,
188}
189
190impl InputHandler for AppInputHandler {
191 fn selection(&self) -> Selection {
192 self.state.borrow().selection
193 }
194 fn composition_range(&self) -> Option<Range<usize>> {
195 self.state.borrow().composition.clone()
196 }
197 fn set_selection(&mut self, range: Selection) {
198 self.state.borrow_mut().selection = range;
199 self.window_handle.request_anim_frame();
200 }
201 fn set_composition_range(&mut self, range: Option<Range<usize>>) {
202 self.state.borrow_mut().composition = range;
203 self.window_handle.request_anim_frame();
204 }
205 fn replace_range(&mut self, range: Range<usize>, text: &str) {
206 let mut doc = self.state.borrow_mut();
207 doc.text.replace_range(range.clone(), text);
208 if doc.selection.anchor < range.start && doc.selection.active < range.start {
209 // no need to update selection
210 } else if doc.selection.anchor > range.end && doc.selection.active > range.end {
211 doc.selection.anchor -= range.len();
212 doc.selection.active -= range.len();
213 doc.selection.anchor += text.len();
214 doc.selection.active += text.len();
215 } else {
216 doc.selection.anchor = range.start + text.len();
217 doc.selection.active = range.start + text.len();
218 }
219 doc.refresh_layout();
220 doc.composition = None;
221 self.window_handle.request_anim_frame();
222 }
223 fn slice(&self, range: Range<usize>) -> Cow<str> {
224 self.state.borrow().text[range].to_string().into()
225 }
226 fn is_char_boundary(&self, i: usize) -> bool {
227 self.state.borrow().text.is_char_boundary(i)
228 }
229 fn len(&self) -> usize {
230 self.state.borrow().text.len()
231 }
232 fn hit_test_point(&self, point: Point) -> HitTestPoint {
233 self.state
234 .borrow()
235 .layout
236 .as_ref()
237 .unwrap()
238 .hit_test_point(point)
239 }
240 fn bounding_box(&self) -> Option<Rect> {
241 Some(Rect::new(
242 0.0,
243 0.0,
244 self.window_size.width,
245 self.window_size.height,
246 ))
247 }
248 fn slice_bounding_box(&self, range: Range<usize>) -> Option<Rect> {
249 let doc = self.state.borrow();
250 let layout = doc.layout.as_ref().unwrap();
251 let range_start_x = layout.hit_test_text_position(range.start).point.x;
252 let range_end_x = layout.hit_test_text_position(range.end).point.x;
253 Some(Rect::new(range_start_x, 0.0, range_end_x, FONT_SIZE))
254 }
255 fn line_range(&self, _char_index: usize, _affinity: text::Affinity) -> Range<usize> {
256 // we don't have multiple lines, so no matter the input, output is the whole document
257 0..self.state.borrow().text.len()
258 }
259
260 fn handle_action(&mut self, action: Action) {
261 let handled = apply_default_behavior(self, action);
262 println!("action: {action:?} handled: {handled:?}");
263 }
264}
265
266fn apply_default_behavior(handler: &mut AppInputHandler, action: Action) -> bool {
267 let is_caret = handler.selection().is_caret();
268 match action {
269 Action::Move(movement) => {
270 let selection = handler.selection();
271 let index = if movement_goes_downstream(movement) {
272 selection.max()
273 } else {
274 selection.min()
275 };
276 let updated_index = if let (false, text::Movement::Grapheme(_)) = (is_caret, movement) {
277 // handle special cases of pressing left/right when the selection is not a caret
278 index
279 } else {
280 match apply_movement(handler, movement, index) {
281 Some(v) => v,
282 None => return false,
283 }
284 };
285 handler.set_selection(Selection::caret(updated_index));
286 }
287 Action::MoveSelecting(movement) => {
288 let mut selection = handler.selection();
289 selection.active = match apply_movement(handler, movement, selection.active) {
290 Some(v) => v,
291 None => return false,
292 };
293 handler.set_selection(selection);
294 }
295 Action::SelectAll => {
296 let len = handler.len();
297 let selection = Selection::new(0, len);
298 handler.set_selection(selection);
299 }
300 Action::Delete(_) if !is_caret => {
301 // movement is ignored for non-caret selections
302 let selection = handler.selection();
303 handler.replace_range(selection.range(), "");
304 }
305 Action::Delete(movement) => {
306 let mut selection = handler.selection();
307 selection.active = match apply_movement(handler, movement, selection.active) {
308 Some(v) => v,
309 None => return false,
310 };
311 handler.replace_range(selection.range(), "");
312 }
313 _ => return false,
314 }
315 true
316}
Sourcepub fn with_h_pos(self, h_pos: Option<f64>) -> Self
pub fn with_h_pos(self, h_pos: Option<f64>) -> Self
Construct a new selection from this selection, with the provided h_pos.
§Note
h_pos
is used to track the pixel location of the cursor when moving
vertically; lines may have available cursor positions at different
positions, and arrowing down and then back up should always result
in a cursor at the original starting location; doing this correctly
requires tracking this state.
You probably don’t need to use this, unless you are implementing a new text field, or otherwise implementing vertical cursor motion, in which case you will want to set this during vertical motion if it is not already set.
Sourcepub fn constrained(self, s: &str) -> Self
pub fn constrained(self, s: &str) -> Self
Create a new selection that is guaranteed to be valid for the provided text.
Sourcepub fn min(&self) -> usize
pub fn min(&self) -> usize
Return the position of the upstream end of the selection.
This is end with the lesser byte index.
Because of bidirectional text, this is not necessarily “left”.
Examples found in repository?
266fn apply_default_behavior(handler: &mut AppInputHandler, action: Action) -> bool {
267 let is_caret = handler.selection().is_caret();
268 match action {
269 Action::Move(movement) => {
270 let selection = handler.selection();
271 let index = if movement_goes_downstream(movement) {
272 selection.max()
273 } else {
274 selection.min()
275 };
276 let updated_index = if let (false, text::Movement::Grapheme(_)) = (is_caret, movement) {
277 // handle special cases of pressing left/right when the selection is not a caret
278 index
279 } else {
280 match apply_movement(handler, movement, index) {
281 Some(v) => v,
282 None => return false,
283 }
284 };
285 handler.set_selection(Selection::caret(updated_index));
286 }
287 Action::MoveSelecting(movement) => {
288 let mut selection = handler.selection();
289 selection.active = match apply_movement(handler, movement, selection.active) {
290 Some(v) => v,
291 None => return false,
292 };
293 handler.set_selection(selection);
294 }
295 Action::SelectAll => {
296 let len = handler.len();
297 let selection = Selection::new(0, len);
298 handler.set_selection(selection);
299 }
300 Action::Delete(_) if !is_caret => {
301 // movement is ignored for non-caret selections
302 let selection = handler.selection();
303 handler.replace_range(selection.range(), "");
304 }
305 Action::Delete(movement) => {
306 let mut selection = handler.selection();
307 selection.active = match apply_movement(handler, movement, selection.active) {
308 Some(v) => v,
309 None => return false,
310 };
311 handler.replace_range(selection.range(), "");
312 }
313 _ => return false,
314 }
315 true
316}
Sourcepub fn max(&self) -> usize
pub fn max(&self) -> usize
Return the position of the downstream end of the selection.
This is the end with the greater byte index.
Because of bidirectional text, this is not necessarily “right”.
Examples found in repository?
266fn apply_default_behavior(handler: &mut AppInputHandler, action: Action) -> bool {
267 let is_caret = handler.selection().is_caret();
268 match action {
269 Action::Move(movement) => {
270 let selection = handler.selection();
271 let index = if movement_goes_downstream(movement) {
272 selection.max()
273 } else {
274 selection.min()
275 };
276 let updated_index = if let (false, text::Movement::Grapheme(_)) = (is_caret, movement) {
277 // handle special cases of pressing left/right when the selection is not a caret
278 index
279 } else {
280 match apply_movement(handler, movement, index) {
281 Some(v) => v,
282 None => return false,
283 }
284 };
285 handler.set_selection(Selection::caret(updated_index));
286 }
287 Action::MoveSelecting(movement) => {
288 let mut selection = handler.selection();
289 selection.active = match apply_movement(handler, movement, selection.active) {
290 Some(v) => v,
291 None => return false,
292 };
293 handler.set_selection(selection);
294 }
295 Action::SelectAll => {
296 let len = handler.len();
297 let selection = Selection::new(0, len);
298 handler.set_selection(selection);
299 }
300 Action::Delete(_) if !is_caret => {
301 // movement is ignored for non-caret selections
302 let selection = handler.selection();
303 handler.replace_range(selection.range(), "");
304 }
305 Action::Delete(movement) => {
306 let mut selection = handler.selection();
307 selection.active = match apply_movement(handler, movement, selection.active) {
308 Some(v) => v,
309 None => return false,
310 };
311 handler.replace_range(selection.range(), "");
312 }
313 _ => return false,
314 }
315 true
316}
Sourcepub fn range(&self) -> Range<usize>
pub fn range(&self) -> Range<usize>
The sequential range of the document represented by this selection.
This is the range that would be replaced if text were inserted at this selection.
Examples found in repository?
93 fn paint(&mut self, piet: &mut piet_common::Piet, _: &Region) {
94 let rect = self.size.to_rect();
95 piet.fill(rect, &BG_COLOR);
96 let doc = self.document.borrow();
97 let layout = doc.layout.as_ref().unwrap();
98 // TODO(lord): rects for range on layout
99 if let Some(composition_range) = doc.composition.as_ref() {
100 for rect in layout.rects_for_range(composition_range.clone()) {
101 piet.fill(rect, &COMPOSITION_BG_COLOR);
102 }
103 }
104 if !doc.selection.is_caret() {
105 for rect in layout.rects_for_range(doc.selection.range()) {
106 piet.fill(rect, &SELECTION_BG_COLOR);
107 }
108 }
109 piet.draw_text(layout, (0.0, 0.0));
110
111 // draw caret
112 let caret_x = layout.hit_test_text_position(doc.selection.active).point.x;
113 piet.fill(
114 Rect::new(caret_x - 1.0, 0.0, caret_x + 1.0, FONT_SIZE),
115 &CARET_COLOR,
116 );
117 }
118
119 fn command(&mut self, id: u32) {
120 match id {
121 0x100 => {
122 self.handle.close();
123 Application::global().quit()
124 }
125 _ => println!("unexpected id {id}"),
126 }
127 }
128
129 fn key_down(&mut self, event: KeyEvent) -> bool {
130 if event.key == Key::Character("c".to_string()) {
131 // custom hotkey for pressing "c"
132 println!("user pressed c! wow! setting selection to 0");
133
134 // update internal selection state
135 self.document.borrow_mut().selection = Selection::caret(0);
136
137 // notify the OS that we've updated the selection
138 self.handle
139 .update_text_field(self.text_input_token.unwrap(), Event::SelectionChanged);
140
141 // repaint window
142 self.handle.request_anim_frame();
143
144 // return true prevents the keypress event from being handled as text input
145 return true;
146 }
147 false
148 }
149
150 fn acquire_input_lock(
151 &mut self,
152 _token: TextFieldToken,
153 _mutable: bool,
154 ) -> Box<dyn InputHandler> {
155 Box::new(AppInputHandler {
156 state: self.document.clone(),
157 window_size: self.size,
158 window_handle: self.handle.clone(),
159 })
160 }
161
162 fn release_input_lock(&mut self, _token: TextFieldToken) {
163 // no action required; this example is simple enough that this
164 // state is not actually shared.
165 }
166
167 fn size(&mut self, size: Size) {
168 self.size = size;
169 }
170
171 fn request_close(&mut self) {
172 self.handle.close();
173 }
174
175 fn destroy(&mut self) {
176 Application::global().quit()
177 }
178
179 fn as_any(&mut self) -> &mut dyn Any {
180 self
181 }
182}
183
184struct AppInputHandler {
185 state: Rc<RefCell<DocumentState>>,
186 window_size: Size,
187 window_handle: WindowHandle,
188}
189
190impl InputHandler for AppInputHandler {
191 fn selection(&self) -> Selection {
192 self.state.borrow().selection
193 }
194 fn composition_range(&self) -> Option<Range<usize>> {
195 self.state.borrow().composition.clone()
196 }
197 fn set_selection(&mut self, range: Selection) {
198 self.state.borrow_mut().selection = range;
199 self.window_handle.request_anim_frame();
200 }
201 fn set_composition_range(&mut self, range: Option<Range<usize>>) {
202 self.state.borrow_mut().composition = range;
203 self.window_handle.request_anim_frame();
204 }
205 fn replace_range(&mut self, range: Range<usize>, text: &str) {
206 let mut doc = self.state.borrow_mut();
207 doc.text.replace_range(range.clone(), text);
208 if doc.selection.anchor < range.start && doc.selection.active < range.start {
209 // no need to update selection
210 } else if doc.selection.anchor > range.end && doc.selection.active > range.end {
211 doc.selection.anchor -= range.len();
212 doc.selection.active -= range.len();
213 doc.selection.anchor += text.len();
214 doc.selection.active += text.len();
215 } else {
216 doc.selection.anchor = range.start + text.len();
217 doc.selection.active = range.start + text.len();
218 }
219 doc.refresh_layout();
220 doc.composition = None;
221 self.window_handle.request_anim_frame();
222 }
223 fn slice(&self, range: Range<usize>) -> Cow<str> {
224 self.state.borrow().text[range].to_string().into()
225 }
226 fn is_char_boundary(&self, i: usize) -> bool {
227 self.state.borrow().text.is_char_boundary(i)
228 }
229 fn len(&self) -> usize {
230 self.state.borrow().text.len()
231 }
232 fn hit_test_point(&self, point: Point) -> HitTestPoint {
233 self.state
234 .borrow()
235 .layout
236 .as_ref()
237 .unwrap()
238 .hit_test_point(point)
239 }
240 fn bounding_box(&self) -> Option<Rect> {
241 Some(Rect::new(
242 0.0,
243 0.0,
244 self.window_size.width,
245 self.window_size.height,
246 ))
247 }
248 fn slice_bounding_box(&self, range: Range<usize>) -> Option<Rect> {
249 let doc = self.state.borrow();
250 let layout = doc.layout.as_ref().unwrap();
251 let range_start_x = layout.hit_test_text_position(range.start).point.x;
252 let range_end_x = layout.hit_test_text_position(range.end).point.x;
253 Some(Rect::new(range_start_x, 0.0, range_end_x, FONT_SIZE))
254 }
255 fn line_range(&self, _char_index: usize, _affinity: text::Affinity) -> Range<usize> {
256 // we don't have multiple lines, so no matter the input, output is the whole document
257 0..self.state.borrow().text.len()
258 }
259
260 fn handle_action(&mut self, action: Action) {
261 let handled = apply_default_behavior(self, action);
262 println!("action: {action:?} handled: {handled:?}");
263 }
264}
265
266fn apply_default_behavior(handler: &mut AppInputHandler, action: Action) -> bool {
267 let is_caret = handler.selection().is_caret();
268 match action {
269 Action::Move(movement) => {
270 let selection = handler.selection();
271 let index = if movement_goes_downstream(movement) {
272 selection.max()
273 } else {
274 selection.min()
275 };
276 let updated_index = if let (false, text::Movement::Grapheme(_)) = (is_caret, movement) {
277 // handle special cases of pressing left/right when the selection is not a caret
278 index
279 } else {
280 match apply_movement(handler, movement, index) {
281 Some(v) => v,
282 None => return false,
283 }
284 };
285 handler.set_selection(Selection::caret(updated_index));
286 }
287 Action::MoveSelecting(movement) => {
288 let mut selection = handler.selection();
289 selection.active = match apply_movement(handler, movement, selection.active) {
290 Some(v) => v,
291 None => return false,
292 };
293 handler.set_selection(selection);
294 }
295 Action::SelectAll => {
296 let len = handler.len();
297 let selection = Selection::new(0, len);
298 handler.set_selection(selection);
299 }
300 Action::Delete(_) if !is_caret => {
301 // movement is ignored for non-caret selections
302 let selection = handler.selection();
303 handler.replace_range(selection.range(), "");
304 }
305 Action::Delete(movement) => {
306 let mut selection = handler.selection();
307 selection.active = match apply_movement(handler, movement, selection.active) {
308 Some(v) => v,
309 None => return false,
310 };
311 handler.replace_range(selection.range(), "");
312 }
313 _ => return false,
314 }
315 true
316}
Sourcepub fn len(&self) -> usize
pub fn len(&self) -> usize
The length, in bytes of the selected region.
If the selection is a caret, this is 0
.
Sourcepub fn is_caret(&self) -> bool
pub fn is_caret(&self) -> bool
Returns true
if the selection’s length is 0
.
Examples found in repository?
93 fn paint(&mut self, piet: &mut piet_common::Piet, _: &Region) {
94 let rect = self.size.to_rect();
95 piet.fill(rect, &BG_COLOR);
96 let doc = self.document.borrow();
97 let layout = doc.layout.as_ref().unwrap();
98 // TODO(lord): rects for range on layout
99 if let Some(composition_range) = doc.composition.as_ref() {
100 for rect in layout.rects_for_range(composition_range.clone()) {
101 piet.fill(rect, &COMPOSITION_BG_COLOR);
102 }
103 }
104 if !doc.selection.is_caret() {
105 for rect in layout.rects_for_range(doc.selection.range()) {
106 piet.fill(rect, &SELECTION_BG_COLOR);
107 }
108 }
109 piet.draw_text(layout, (0.0, 0.0));
110
111 // draw caret
112 let caret_x = layout.hit_test_text_position(doc.selection.active).point.x;
113 piet.fill(
114 Rect::new(caret_x - 1.0, 0.0, caret_x + 1.0, FONT_SIZE),
115 &CARET_COLOR,
116 );
117 }
118
119 fn command(&mut self, id: u32) {
120 match id {
121 0x100 => {
122 self.handle.close();
123 Application::global().quit()
124 }
125 _ => println!("unexpected id {id}"),
126 }
127 }
128
129 fn key_down(&mut self, event: KeyEvent) -> bool {
130 if event.key == Key::Character("c".to_string()) {
131 // custom hotkey for pressing "c"
132 println!("user pressed c! wow! setting selection to 0");
133
134 // update internal selection state
135 self.document.borrow_mut().selection = Selection::caret(0);
136
137 // notify the OS that we've updated the selection
138 self.handle
139 .update_text_field(self.text_input_token.unwrap(), Event::SelectionChanged);
140
141 // repaint window
142 self.handle.request_anim_frame();
143
144 // return true prevents the keypress event from being handled as text input
145 return true;
146 }
147 false
148 }
149
150 fn acquire_input_lock(
151 &mut self,
152 _token: TextFieldToken,
153 _mutable: bool,
154 ) -> Box<dyn InputHandler> {
155 Box::new(AppInputHandler {
156 state: self.document.clone(),
157 window_size: self.size,
158 window_handle: self.handle.clone(),
159 })
160 }
161
162 fn release_input_lock(&mut self, _token: TextFieldToken) {
163 // no action required; this example is simple enough that this
164 // state is not actually shared.
165 }
166
167 fn size(&mut self, size: Size) {
168 self.size = size;
169 }
170
171 fn request_close(&mut self) {
172 self.handle.close();
173 }
174
175 fn destroy(&mut self) {
176 Application::global().quit()
177 }
178
179 fn as_any(&mut self) -> &mut dyn Any {
180 self
181 }
182}
183
184struct AppInputHandler {
185 state: Rc<RefCell<DocumentState>>,
186 window_size: Size,
187 window_handle: WindowHandle,
188}
189
190impl InputHandler for AppInputHandler {
191 fn selection(&self) -> Selection {
192 self.state.borrow().selection
193 }
194 fn composition_range(&self) -> Option<Range<usize>> {
195 self.state.borrow().composition.clone()
196 }
197 fn set_selection(&mut self, range: Selection) {
198 self.state.borrow_mut().selection = range;
199 self.window_handle.request_anim_frame();
200 }
201 fn set_composition_range(&mut self, range: Option<Range<usize>>) {
202 self.state.borrow_mut().composition = range;
203 self.window_handle.request_anim_frame();
204 }
205 fn replace_range(&mut self, range: Range<usize>, text: &str) {
206 let mut doc = self.state.borrow_mut();
207 doc.text.replace_range(range.clone(), text);
208 if doc.selection.anchor < range.start && doc.selection.active < range.start {
209 // no need to update selection
210 } else if doc.selection.anchor > range.end && doc.selection.active > range.end {
211 doc.selection.anchor -= range.len();
212 doc.selection.active -= range.len();
213 doc.selection.anchor += text.len();
214 doc.selection.active += text.len();
215 } else {
216 doc.selection.anchor = range.start + text.len();
217 doc.selection.active = range.start + text.len();
218 }
219 doc.refresh_layout();
220 doc.composition = None;
221 self.window_handle.request_anim_frame();
222 }
223 fn slice(&self, range: Range<usize>) -> Cow<str> {
224 self.state.borrow().text[range].to_string().into()
225 }
226 fn is_char_boundary(&self, i: usize) -> bool {
227 self.state.borrow().text.is_char_boundary(i)
228 }
229 fn len(&self) -> usize {
230 self.state.borrow().text.len()
231 }
232 fn hit_test_point(&self, point: Point) -> HitTestPoint {
233 self.state
234 .borrow()
235 .layout
236 .as_ref()
237 .unwrap()
238 .hit_test_point(point)
239 }
240 fn bounding_box(&self) -> Option<Rect> {
241 Some(Rect::new(
242 0.0,
243 0.0,
244 self.window_size.width,
245 self.window_size.height,
246 ))
247 }
248 fn slice_bounding_box(&self, range: Range<usize>) -> Option<Rect> {
249 let doc = self.state.borrow();
250 let layout = doc.layout.as_ref().unwrap();
251 let range_start_x = layout.hit_test_text_position(range.start).point.x;
252 let range_end_x = layout.hit_test_text_position(range.end).point.x;
253 Some(Rect::new(range_start_x, 0.0, range_end_x, FONT_SIZE))
254 }
255 fn line_range(&self, _char_index: usize, _affinity: text::Affinity) -> Range<usize> {
256 // we don't have multiple lines, so no matter the input, output is the whole document
257 0..self.state.borrow().text.len()
258 }
259
260 fn handle_action(&mut self, action: Action) {
261 let handled = apply_default_behavior(self, action);
262 println!("action: {action:?} handled: {handled:?}");
263 }
264}
265
266fn apply_default_behavior(handler: &mut AppInputHandler, action: Action) -> bool {
267 let is_caret = handler.selection().is_caret();
268 match action {
269 Action::Move(movement) => {
270 let selection = handler.selection();
271 let index = if movement_goes_downstream(movement) {
272 selection.max()
273 } else {
274 selection.min()
275 };
276 let updated_index = if let (false, text::Movement::Grapheme(_)) = (is_caret, movement) {
277 // handle special cases of pressing left/right when the selection is not a caret
278 index
279 } else {
280 match apply_movement(handler, movement, index) {
281 Some(v) => v,
282 None => return false,
283 }
284 };
285 handler.set_selection(Selection::caret(updated_index));
286 }
287 Action::MoveSelecting(movement) => {
288 let mut selection = handler.selection();
289 selection.active = match apply_movement(handler, movement, selection.active) {
290 Some(v) => v,
291 None => return false,
292 };
293 handler.set_selection(selection);
294 }
295 Action::SelectAll => {
296 let len = handler.len();
297 let selection = Selection::new(0, len);
298 handler.set_selection(selection);
299 }
300 Action::Delete(_) if !is_caret => {
301 // movement is ignored for non-caret selections
302 let selection = handler.selection();
303 handler.replace_range(selection.range(), "");
304 }
305 Action::Delete(movement) => {
306 let mut selection = handler.selection();
307 selection.active = match apply_movement(handler, movement, selection.active) {
308 Some(v) => v,
309 None => return false,
310 };
311 handler.replace_range(selection.range(), "");
312 }
313 _ => return false,
314 }
315 true
316}