Selection

Struct Selection 

Source
#[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
Non-exhaustive structs could have additional fields added in future. Therefore, non-exhaustive structs cannot be constructed in external crates using the traditional 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

Source

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?
examples/edit_text.rs (line 297)
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}
Source

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?
examples/edit_text.rs (line 135)
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}
Source

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.

Source

pub fn constrained(self, s: &str) -> Self

Create a new selection that is guaranteed to be valid for the provided text.

Source

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?
examples/edit_text.rs (line 274)
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}
Source

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?
examples/edit_text.rs (line 272)
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}
Source

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?
examples/edit_text.rs (line 105)
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}
Source

pub fn len(&self) -> usize

The length, in bytes of the selected region.

If the selection is a caret, this is 0.

Source

pub fn is_caret(&self) -> bool

Returns true if the selection’s length is 0.

Examples found in repository?
examples/edit_text.rs (line 104)
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}

Trait Implementations§

Source§

impl Clone for Selection

Source§

fn clone(&self) -> Selection

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Selection

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Selection

Source§

fn default() -> Selection

Returns the “default value” for a type. Read more
Source§

impl PartialEq for Selection

Source§

fn eq(&self, other: &Selection) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl Copy for Selection

Source§

impl StructuralPartialEq for Selection

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> RoundFrom<T> for T

Source§

fn round_from(x: T) -> T

Performs the conversion.
Source§

impl<T, U> RoundInto<U> for T
where U: RoundFrom<T>,

Source§

fn round_into(self) -> U

Performs the conversion.
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more