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 super::value::Value;
7
8/// The cursor of a text input.
9#[derive(Debug, Copy, Clone)]
10pub struct Cursor {
11    state: State,
12}
13
14/// The state of a [`Cursor`].
15#[derive(Debug, Copy, Clone)]
16pub enum State {
17    /// Cursor without a selection
18    Index(usize),
19
20    /// Cursor selecting a range of text
21    Selection {
22        /// The start of the selection
23        start: usize,
24        /// The end of the selection
25        end: usize,
26    },
27}
28
29impl Default for Cursor {
30    #[inline]
31    fn default() -> Self {
32        Self {
33            state: State::Index(0),
34        }
35    }
36}
37
38impl Cursor {
39    /// Returns the [`State`] of the [`Cursor`].
40    #[must_use]
41    #[inline(never)]
42    pub fn state(&self, value: &Value) -> State {
43        match self.state {
44            State::Index(index) => State::Index(index.min(value.len())),
45            State::Selection { start, end } => {
46                let start = start.min(value.len());
47                let end = end.min(value.len());
48
49                if start == end {
50                    State::Index(start)
51                } else {
52                    State::Selection { start, end }
53                }
54            }
55        }
56    }
57
58    /// Returns the current selection of the [`Cursor`] for the given [`Value`].
59    ///
60    /// `start` is guaranteed to be <= than `end`.
61    #[must_use]
62    #[inline]
63    pub fn selection(&self, value: &Value) -> Option<(usize, usize)> {
64        match self.state(value) {
65            State::Selection { start, end } => Some((start.min(end), start.max(end))),
66            State::Index(_) => None,
67        }
68    }
69
70    #[inline]
71    pub(crate) fn move_to(&mut self, position: usize) {
72        self.state = State::Index(position);
73    }
74
75    #[inline]
76    pub(crate) fn move_right(&mut self, value: &Value) {
77        self.move_right_by_amount(value, 1);
78    }
79
80    #[inline]
81    pub(crate) fn move_right_by_words(&mut self, value: &Value) {
82        self.move_to(value.next_end_of_word(self.right(value)));
83    }
84
85    #[inline]
86    pub(crate) fn move_right_by_amount(&mut self, value: &Value, amount: usize) {
87        match self.state(value) {
88            State::Index(index) => self.move_to(index.saturating_add(amount).min(value.len())),
89            State::Selection { start, end } => self.move_to(end.max(start)),
90        }
91    }
92
93    #[inline]
94    pub(crate) fn move_left(&mut self, value: &Value) {
95        match self.state(value) {
96            State::Index(index) if index > 0 => self.move_to(index - 1),
97            State::Selection { start, end } => self.move_to(start.min(end)),
98            State::Index(_) => self.move_to(0),
99        }
100    }
101
102    #[inline]
103    pub(crate) fn move_left_by_words(&mut self, value: &Value) {
104        self.move_to(value.previous_start_of_word(self.left(value)));
105    }
106
107    #[inline]
108    pub(crate) fn select_range(&mut self, start: usize, end: usize) {
109        self.state = if start == end {
110            State::Index(start)
111        } else {
112            State::Selection { start, end }
113        };
114    }
115
116    #[inline]
117    pub(crate) fn select_left(&mut self, value: &Value) {
118        match self.state(value) {
119            State::Index(index) if index > 0 => self.select_range(index, index - 1),
120            State::Selection { start, end } if end > 0 => self.select_range(start, end - 1),
121            _ => {}
122        }
123    }
124
125    #[inline]
126    pub(crate) fn select_right(&mut self, value: &Value) {
127        match self.state(value) {
128            State::Index(index) if index < value.len() => self.select_range(index, index + 1),
129            State::Selection { start, end } if end < value.len() => {
130                self.select_range(start, end + 1);
131            }
132            _ => {}
133        }
134    }
135
136    #[inline]
137    pub(crate) fn select_left_by_words(&mut self, value: &Value) {
138        match self.state(value) {
139            State::Index(index) => self.select_range(index, value.previous_start_of_word(index)),
140            State::Selection { start, end } => {
141                self.select_range(start, value.previous_start_of_word(end));
142            }
143        }
144    }
145
146    #[inline]
147    pub(crate) fn select_right_by_words(&mut self, value: &Value) {
148        match self.state(value) {
149            State::Index(index) => self.select_range(index, value.next_end_of_word(index)),
150            State::Selection { start, end } => {
151                self.select_range(start, value.next_end_of_word(end));
152            }
153        }
154    }
155
156    #[inline]
157    pub(crate) fn select_all(&mut self, value: &Value) {
158        self.select_range(0, value.len());
159    }
160
161    #[inline]
162    pub(crate) fn start(&self, value: &Value) -> usize {
163        let start = match self.state {
164            State::Index(index) => index,
165            State::Selection { start, .. } => start,
166        };
167
168        start.min(value.len())
169    }
170
171    #[inline]
172    pub(crate) fn end(&self, value: &Value) -> usize {
173        let end = match self.state {
174            State::Index(index) => index,
175            State::Selection { end, .. } => end,
176        };
177
178        end.min(value.len())
179    }
180
181    #[inline]
182    fn left(&self, value: &Value) -> usize {
183        match self.state(value) {
184            State::Index(index) => index,
185            State::Selection { start, end } => start.min(end),
186        }
187    }
188
189    #[inline]
190    fn right(&self, value: &Value) -> usize {
191        match self.state(value) {
192            State::Index(index) => index,
193            State::Selection { start, end } => start.max(end),
194        }
195    }
196}