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    #[inline]
80    pub(crate) fn move_right(&mut self, value: &Value) {
81        self.move_right_by_amount(value, 1);
82    }
83
84    #[inline]
85    pub(crate) fn move_right_by_words(&mut self, value: &Value) {
86        self.move_to(value.next_end_of_word(self.right(value)));
87    }
88
89    #[inline]
90    pub(crate) fn move_right_by_amount(&mut self, value: &Value, amount: usize) {
91        match self.state(value) {
92            State::Index(index) => self.move_to(index.saturating_add(amount).min(value.len())),
93            State::Selection { start, end } => self.move_to(end.max(start)),
94        }
95    }
96
97    #[inline]
98    pub(crate) fn move_left(&mut self, value: &Value) {
99        match self.state(value) {
100            State::Index(index) if index > 0 => self.move_to(index - 1),
101            State::Selection { start, end } => self.move_to(start.min(end)),
102            State::Index(_) => self.move_to(0),
103        }
104    }
105
106    #[inline]
107    pub(crate) fn move_left_by_words(&mut self, value: &Value) {
108        self.move_to(value.previous_start_of_word(self.left(value)));
109    }
110
111    #[inline]
112    pub(crate) fn select_range(&mut self, start: usize, end: usize) {
113        self.state = if start == end {
114            State::Index(start)
115        } else {
116            State::Selection { start, end }
117        };
118    }
119
120    #[inline]
121    pub(crate) fn select_left(&mut self, value: &Value) {
122        match self.state(value) {
123            State::Index(index) if index > 0 => self.select_range(index, index - 1),
124            State::Selection { start, end } if end > 0 => self.select_range(start, end - 1),
125            _ => {}
126        }
127    }
128
129    #[inline]
130    pub(crate) fn select_right(&mut self, value: &Value) {
131        match self.state(value) {
132            State::Index(index) if index < value.len() => self.select_range(index, index + 1),
133            State::Selection { start, end } if end < value.len() => {
134                self.select_range(start, end + 1);
135            }
136            _ => {}
137        }
138    }
139
140    #[inline]
141    pub(crate) fn select_left_by_words(&mut self, value: &Value) {
142        match self.state(value) {
143            State::Index(index) => self.select_range(index, value.previous_start_of_word(index)),
144            State::Selection { start, end } => {
145                self.select_range(start, value.previous_start_of_word(end));
146            }
147        }
148    }
149
150    #[inline]
151    pub(crate) fn select_right_by_words(&mut self, value: &Value) {
152        match self.state(value) {
153            State::Index(index) => self.select_range(index, value.next_end_of_word(index)),
154            State::Selection { start, end } => {
155                self.select_range(start, value.next_end_of_word(end));
156            }
157        }
158    }
159
160    #[inline]
161    pub(crate) fn select_all(&mut self, value: &Value) {
162        self.select_range(0, value.len());
163    }
164
165    #[inline]
166    pub(crate) fn start(&self, value: &Value) -> usize {
167        let start = match self.state {
168            State::Index(index) => index,
169            State::Selection { start, .. } => start,
170        };
171
172        start.min(value.len())
173    }
174
175    #[inline]
176    pub(crate) fn end(&self, value: &Value) -> usize {
177        let end = match self.state {
178            State::Index(index) => index,
179            State::Selection { end, .. } => end,
180        };
181
182        end.min(value.len())
183    }
184
185    #[inline]
186    fn left(&self, value: &Value) -> usize {
187        match self.state(value) {
188            State::Index(index) => index,
189            State::Selection { start, end } => start.min(end),
190        }
191    }
192
193    #[inline]
194    fn right(&self, value: &Value) -> usize {
195        match self.state(value) {
196            State::Index(index) => index,
197            State::Selection { start, end } => start.max(end),
198        }
199    }
200
201    /// Returns the current cursor [`Affinity`].
202    #[must_use]
203    pub fn affinity(&self) -> Affinity {
204        self.affinity
205    }
206
207    /// Sets the cursor [`Affinity`].
208    pub fn set_affinity(&mut self, affinity: Affinity) {
209        self.affinity = affinity;
210    }
211
212    /// Moves the cursor in a visual direction, accounting for RTL text.
213    ///
214    /// `forward` = `true` is visually rightward.
215    pub fn move_visual(&mut self, forward: bool, by_words: bool, rtl: bool, value: &Value) {
216        match (forward ^ rtl, by_words) {
217            (true, false) => self.move_right(value),
218            (true, true) => self.move_right_by_words(value),
219            (false, false) => self.move_left(value),
220            (false, true) => self.move_left_by_words(value),
221        }
222    }
223
224    /// Extends the selection in a visual direction, accounting for RTL text.
225    pub fn select_visual(&mut self, forward: bool, by_words: bool, rtl: bool, value: &Value) {
226        match (forward ^ rtl, by_words) {
227            (true, false) => self.select_right(value),
228            (true, true) => self.select_right_by_words(value),
229            (false, false) => self.select_left(value),
230            (false, true) => self.select_left_by_words(value),
231        }
232    }
233}