Skip to main content

cosmic/widget/text_input/
cursor.rs

1// Copyright 2019 H�ctor Ram�n, Iced contributors
2// Copyright 2023 System76 <info@system76.com>
3// SPDX-License-Identifier: MIT
4
5//! Track the cursor of a text input.
6use iced_core::text::Affinity;
7
8use super::value::Value;
9
10/// The cursor of a text input.
11#[derive(Debug, Copy, Clone, PartialEq, Eq)]
12pub struct Cursor {
13    state: State,
14    affinity: Affinity,
15}
16
17/// The state of a [`Cursor`].
18#[derive(Debug, Copy, Clone, PartialEq, Eq)]
19pub enum State {
20    /// Cursor without a selection
21    Index(usize),
22
23    /// Cursor selecting a range of text
24    Selection {
25        /// The start of the selection
26        start: usize,
27        /// The end of the selection
28        end: usize,
29    },
30}
31
32impl Default for Cursor {
33    #[inline]
34    fn default() -> Self {
35        Self {
36            state: State::Index(0),
37            affinity: Affinity::Before,
38        }
39    }
40}
41
42impl Cursor {
43    /// Returns the [`State`] of the [`Cursor`].
44    #[must_use]
45    #[inline(never)]
46    pub fn state(&self, value: &Value) -> State {
47        match self.state {
48            State::Index(index) => State::Index(index.min(value.len())),
49            State::Selection { start, end } => {
50                let start = start.min(value.len());
51                let end = end.min(value.len());
52
53                if start == end {
54                    State::Index(start)
55                } else {
56                    State::Selection { start, end }
57                }
58            }
59        }
60    }
61
62    /// Returns the current selection of the [`Cursor`] for the given [`Value`].
63    ///
64    /// `start` is guaranteed to be <= than `end`.
65    #[must_use]
66    #[inline]
67    pub fn selection(&self, value: &Value) -> Option<(usize, usize)> {
68        match self.state(value) {
69            State::Selection { start, end } => Some((start.min(end), start.max(end))),
70            State::Index(_) => None,
71        }
72    }
73
74    #[inline]
75    pub(crate) fn move_to(&mut self, position: usize) {
76        self.state = State::Index(position);
77    }
78
79    pub(crate) fn clear_selection(&mut self) {
80        if let State::Selection { end, .. } = self.state {
81            self.state = State::Index(end);
82        }
83    }
84
85    #[inline]
86    pub(crate) fn move_right(&mut self, value: &Value) {
87        self.move_right_by_amount(value, 1);
88    }
89
90    #[inline]
91    pub(crate) fn move_right_by_words(&mut self, value: &Value) {
92        self.move_to(value.next_end_of_word(self.right(value)));
93    }
94
95    #[inline]
96    pub(crate) fn move_right_by_amount(&mut self, value: &Value, amount: usize) {
97        match self.state(value) {
98            State::Index(index) => self.move_to(index.saturating_add(amount).min(value.len())),
99            State::Selection { start, end } => self.move_to(end.max(start)),
100        }
101    }
102
103    #[inline]
104    pub(crate) fn move_left(&mut self, value: &Value) {
105        match self.state(value) {
106            State::Index(index) if index > 0 => self.move_to(index - 1),
107            State::Selection { start, end } => self.move_to(start.min(end)),
108            State::Index(_) => self.move_to(0),
109        }
110    }
111
112    #[inline]
113    pub(crate) fn move_left_by_words(&mut self, value: &Value) {
114        self.move_to(value.previous_start_of_word(self.left(value)));
115    }
116
117    #[inline]
118    pub(crate) fn select_range(&mut self, start: usize, end: usize) {
119        self.state = if start == end {
120            State::Index(start)
121        } else {
122            State::Selection { start, end }
123        };
124    }
125
126    #[inline]
127    pub(crate) fn select_left(&mut self, value: &Value) {
128        match self.state(value) {
129            State::Index(index) if index > 0 => self.select_range(index, index - 1),
130            State::Selection { start, end } if end > 0 => self.select_range(start, end - 1),
131            _ => {}
132        }
133    }
134
135    #[inline]
136    pub(crate) fn select_right(&mut self, value: &Value) {
137        match self.state(value) {
138            State::Index(index) if index < value.len() => self.select_range(index, index + 1),
139            State::Selection { start, end } if end < value.len() => {
140                self.select_range(start, end + 1);
141            }
142            _ => {}
143        }
144    }
145
146    #[inline]
147    pub(crate) fn select_left_by_words(&mut self, value: &Value) {
148        match self.state(value) {
149            State::Index(index) => self.select_range(index, value.previous_start_of_word(index)),
150            State::Selection { start, end } => {
151                self.select_range(start, value.previous_start_of_word(end));
152            }
153        }
154    }
155
156    #[inline]
157    pub(crate) fn select_right_by_words(&mut self, value: &Value) {
158        match self.state(value) {
159            State::Index(index) => self.select_range(index, value.next_end_of_word(index)),
160            State::Selection { start, end } => {
161                self.select_range(start, value.next_end_of_word(end));
162            }
163        }
164    }
165
166    #[inline]
167    pub(crate) fn select_all(&mut self, value: &Value) {
168        self.select_range(0, value.len());
169    }
170
171    #[inline]
172    pub(crate) fn start(&self, value: &Value) -> usize {
173        let start = match self.state {
174            State::Index(index) => index,
175            State::Selection { start, .. } => start,
176        };
177
178        start.min(value.len())
179    }
180
181    #[inline]
182    pub(crate) fn end(&self, value: &Value) -> usize {
183        let end = match self.state {
184            State::Index(index) => index,
185            State::Selection { end, .. } => end,
186        };
187
188        end.min(value.len())
189    }
190
191    #[inline]
192    fn left(&self, value: &Value) -> usize {
193        match self.state(value) {
194            State::Index(index) => index,
195            State::Selection { start, end } => start.min(end),
196        }
197    }
198
199    #[inline]
200    fn right(&self, value: &Value) -> usize {
201        match self.state(value) {
202            State::Index(index) => index,
203            State::Selection { start, end } => start.max(end),
204        }
205    }
206
207    /// Returns the current cursor [`Affinity`].
208    #[must_use]
209    pub fn affinity(&self) -> Affinity {
210        self.affinity
211    }
212
213    /// Sets the cursor [`Affinity`].
214    pub fn set_affinity(&mut self, affinity: Affinity) {
215        self.affinity = affinity;
216    }
217
218    /// Moves the cursor in a visual direction, accounting for RTL text.
219    ///
220    /// `forward` = `true` is visually rightward.
221    pub fn move_visual(&mut self, forward: bool, by_words: bool, rtl: bool, value: &Value) {
222        match (forward ^ rtl, by_words) {
223            (true, false) => self.move_right(value),
224            (true, true) => self.move_right_by_words(value),
225            (false, false) => self.move_left(value),
226            (false, true) => self.move_left_by_words(value),
227        }
228    }
229
230    /// Extends the selection in a visual direction, accounting for RTL text.
231    pub fn select_visual(&mut self, forward: bool, by_words: bool, rtl: bool, value: &Value) {
232        match (forward ^ rtl, by_words) {
233            (true, false) => self.select_right(value),
234            (true, true) => self.select_right_by_words(value),
235            (false, false) => self.select_left(value),
236            (false, true) => self.select_left_by_words(value),
237        }
238    }
239}