limn_text_layout/
cursor.rs

1/// Logic related to the positioning of the cursor within text.
2
3use std;
4use types::{Range, Align, Rect, RectExt, Point};
5use rusttype;
6use rusttype::LayoutIter;
7
8use super::line::{LineRects, LineInfo};
9use super::Font;
10
11/// Every possible cursor position within each line of text yielded by the given iterator.
12///
13/// Yields `(xs, y_range)`, where `y_range` is the `Range` occupied by the line across the *y*
14/// axis and `xs` is every possible cursor position along the *x* axis
15#[derive(Clone)]
16pub struct XysPerLine<'a, I> {
17    lines_with_rects: I,
18    font: &'a Font,
19    text: &'a str,
20    font_size: f32,
21}
22
23/// Similarly to `XysPerLine`, yields every possible cursor position within each line of text
24/// yielded by the given iterator.
25///
26/// Rather than taking an iterator type yielding lines and positioning data, this method
27/// constructs its own iterator to do so internally, saving some boilerplate involved in common
28/// `XysPerLine` use cases.
29///
30/// Yields `(xs, y_range)`, where `y_range` is the `Range` occupied by the line across the *y*
31/// axis and `xs` is every possible cursor position along the *x* axis.
32#[derive(Clone)]
33pub struct XysPerLineFromText<'a> {
34        xys_per_line: XysPerLine<'a,
35            std::iter::Zip<std::iter::Cloned<std::slice::Iter<'a, LineInfo>>,
36            LineRects<std::iter::Cloned<std::slice::Iter<'a, LineInfo>>>>
37        >,
38    }
39
40/// Each possible cursor position along the *x* axis within a line of text.
41///
42/// `Xs` iterators are produced by the `XysPerLine` iterator.
43pub struct Xs<'a, 'b> {
44    next_x: Option<f32>,
45    layout: LayoutIter<'a, 'b>,
46}
47
48/// An index representing the position of a cursor within some text.
49#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
50pub struct Index {
51    /// The index of the line upon which the cursor is situated.
52    pub line: usize,
53    /// The index within all possible cursor positions for the line.
54    ///
55    /// For example, for the line `foo`, a `char` of `1` would indicate the cursor's position
56    /// as `f|oo` where `|` is the cursor.
57    pub char: usize,
58}
59
60
61impl Index {
62    /// The cursor index of the beginning of the word (block of non-whitespace) before `self`.
63    ///
64    /// If `self` is at the beginning of the line, call previous, which returns the last
65    /// index position of the previous line, or None if it's the first line
66    ///
67    /// If `self` points to whitespace, skip past that whitespace, then return the index of
68    /// the start of the word that precedes the whitespace
69    ///
70    /// If `self` is in the middle or end of a word, return the index of the start of that word
71    pub fn previous_word_start<I>(self, text: &str, mut line_infos: I) -> Option<Self>
72        where I: Iterator<Item = LineInfo>
73    {
74        let Index { line, char } = self;
75        if char > 0 {
76            line_infos.nth(line).and_then(|line_info| {
77                let line_count = line_info.char_range().count();
78                let mut chars_rev = (&text[line_info.byte_range()]).chars().rev();
79                if char != line_count {
80                    chars_rev.nth(line_count - char - 1);
81                }
82                let mut new_char = 0;
83                let mut hit_non_whitespace = false;
84                for (i, char_) in chars_rev.enumerate() {
85                    // loop until word starts, then continue until the word ends
86                    if !char_.is_whitespace() {
87                        hit_non_whitespace = true;
88                    }
89                    if char_.is_whitespace() && hit_non_whitespace {
90                        new_char = char - i;
91                        break;
92                    }
93                }
94                Some(Index {
95                    line: line,
96                    char: new_char,
97                })
98            })
99        } else {
100            self.previous(line_infos)
101        }
102    }
103
104    /// The cursor index of the end of the first word (block of non-whitespace) after `self`.
105    ///
106    /// If `self` is at the end of the text, this returns `None`.
107    ///
108    /// If `self` is at the end of a line other than the last, this returns the first index of
109    /// the next line.
110    ///
111    /// If `self` points to whitespace, skip past that whitespace, then return the index of
112    /// the end of the word after the whitespace
113    ///
114    /// If `self` is in the middle or start of a word, return the index of the end of that word
115    pub fn next_word_end<I>(self, text: &str, mut line_infos: I) -> Option<Self>
116        where I: Iterator<Item = LineInfo>
117    {
118        let Index { line, char } = self;
119        line_infos.nth(line)
120            .and_then(|line_info| {
121                let line_count = line_info.char_range().count();
122                if char < line_count {
123                    let mut chars = (&text[line_info.byte_range()]).chars();
124                    let mut new_char = line_count;
125                    let mut hit_non_whitespace = false;
126                    if char != 0 {
127                        chars.nth(char - 1);
128                    }
129                    for (i, char_) in chars.enumerate() {
130                        // loop until word starts, then continue until the word ends
131                        if !char_.is_whitespace() {
132                            hit_non_whitespace = true;
133                        }
134                        if char_.is_whitespace() && hit_non_whitespace {
135                            new_char = char + i;
136                            break;
137                        }
138                    }
139                    Some(Index {
140                        line: line,
141                        char: new_char,
142                    })
143                } else {
144                    line_infos.next().map(|_| {
145                        Index {
146                            line: line + 1,
147                            char: 0,
148                        }
149                    })
150                }
151            })
152    }
153
154    /// The cursor index that comes before `self`.
155    ///
156    /// If `self` is at the beginning of the text, this returns `None`.
157    ///
158    /// If `self` is at the beginning of a line other than the first, this returns the last
159    /// index position of the previous line.
160    ///
161    /// If `self` is a position other than the start of a line, it will return the position
162    /// that is immediately to the left.
163    pub fn previous<I>(self, mut line_infos: I) -> Option<Self>
164        where I: Iterator<Item = LineInfo>
165    {
166        let Index { line, char } = self;
167        if char > 0 {
168            let new_char = char - 1;
169            line_infos.nth(line)
170                .and_then(|info| if new_char <= info.char_range().count() {
171                    Some(Index {
172                        line: line,
173                        char: new_char,
174                    })
175                } else {
176                    None
177                })
178        } else if line > 0 {
179            let new_line = line - 1;
180            line_infos.nth(new_line)
181                .map(|info| {
182                    let new_char = info.end_char() - info.start_char;
183                    Index {
184                        line: new_line,
185                        char: new_char,
186                    }
187                })
188        } else {
189            None
190        }
191    }
192
193    /// The cursor index that follows `self`.
194    ///
195    /// If `self` is at the end of the text, this returns `None`.
196    ///
197    /// If `self` is at the end of a line other than the last, this returns the first index of
198    /// the next line.
199    ///
200    /// If `self` is a position other than the end of a line, it will return the position that
201    /// is immediately to the right.
202    pub fn next<I>(self, mut line_infos: I) -> Option<Self>
203        where I: Iterator<Item = LineInfo>
204    {
205        let Index { line, char } = self;
206        line_infos.nth(line)
207            .and_then(|info| if char >= info.char_range().count() {
208                line_infos.next().map(|_| {
209                    Index {
210                        line: line + 1,
211                        char: 0,
212                    }
213                })
214            } else {
215                Some(Index {
216                    line: line,
217                    char: char + 1,
218                })
219            })
220    }
221
222    /// Clamps `self` to the given lines.
223    ///
224    /// If `self` would lie after the end of the last line, return the index at the end of the
225    /// last line.
226    ///
227    /// If `line_infos` is empty, returns cursor at line=0 char=0.
228    pub fn clamp_to_lines<I>(self, line_infos: I) -> Self
229        where I: Iterator<Item = LineInfo>
230    {
231        let mut last = None;
232        for (i, info) in line_infos.enumerate() {
233            if i == self.line {
234                let num_chars = info.char_range().len();
235                let char = std::cmp::min(self.char, num_chars);
236                return Index {
237                    line: i,
238                    char: char,
239                };
240            }
241            last = Some((i, info));
242        }
243        match last {
244            Some((i, info)) => {
245                Index {
246                    line: i,
247                    char: info.char_range().len(),
248                }
249            }
250            None => Index { line: 0, char: 0 },
251        }
252    }
253}
254
255
256/// Every possible cursor position within each line of text yielded by the given iterator.
257///
258/// Yields `(xs, y_range)`, where `y_range` is the `Range` occupied by the line across the *y*
259/// axis and `xs` is every possible cursor position along the *x* axis
260pub fn xys_per_line<'a, I>(lines_with_rects: I,
261                           font: &'a Font,
262                           text: &'a str,
263                           font_size: f32)
264                           -> XysPerLine<'a, I> {
265    XysPerLine {
266        lines_with_rects: lines_with_rects,
267        font: font,
268        text: text,
269        font_size: font_size,
270    }
271}
272
273/// Similarly to `xys_per_line`, this produces an iterator yielding every possible cursor
274/// position within each line of text yielded by the given iterator.
275///
276/// Rather than taking an iterator yielding lines and their positioning data, this method
277/// constructs its own iterator to do so internally, saving some boilerplate involved in common
278/// `xys_per_line` use cases.
279///
280/// Yields `(xs, y_range)`, where `y_range` is the `Range` occupied by the line across the *y*
281/// axis and `xs` is every possible cursor position along the *x* axis.
282pub fn xys_per_line_from_text<'a>(text: &'a str,
283                                  line_infos: &'a [LineInfo],
284                                  font: &'a Font,
285                                  font_size: f32,
286                                  align: Align,
287                                  line_spacing: f32,
288                                  rect: Rect)
289                                  -> XysPerLineFromText<'a> {
290    let line_infos = line_infos.iter().cloned();
291    let line_rects = LineRects::new(line_infos.clone(),
292                                    font_size,
293                                    rect,
294                                    align,
295                                    line_spacing);
296    let lines = line_infos.clone();
297    let lines_with_rects = lines.zip(line_rects.clone());
298    XysPerLineFromText {
299        xys_per_line: xys_per_line(lines_with_rects, font, text, font_size),
300    }
301}
302
303/// Convert the given character index into a cursor `Index`.
304pub fn index_before_char<I>(line_infos: I, char_index: usize) -> Option<Index>
305    where I: Iterator<Item = LineInfo>
306{
307    for (i, line_info) in line_infos.enumerate() {
308        let start_char = line_info.start_char;
309        let end_char = line_info.end_char();
310        if start_char <= char_index && char_index <= end_char {
311            return Some(Index {
312                line: i,
313                char: char_index - start_char,
314            });
315        }
316    }
317    None
318}
319
320/// Determine the *xy* location of the cursor at the given cursor `Index`.
321pub fn xy_at<'a, I>(xys_per_line: I, idx: Index) -> Option<(f32, Range)>
322    where I: Iterator<Item = (Xs<'a, 'a>, Range)>
323{
324    for (i, (xs, y)) in xys_per_line.enumerate() {
325        if i == idx.line {
326            for (j, x) in xs.enumerate() {
327                if j == idx.char {
328                    return Some((x, y));
329                }
330            }
331        }
332    }
333    None
334}
335
336/// Find the closest line for the given `y` position, and
337/// return the line index, Xs iterator, and y-range of that line
338///
339/// Returns `None` if there are no lines
340pub fn closest_line<'a, I>(y_pos: f32, xys_per_line: I) -> Option<(usize, Xs<'a, 'a>, Range)>
341    where I: Iterator<Item = (Xs<'a, 'a>, Range)>
342{
343    let mut xys_per_line_enumerated = xys_per_line.enumerate();
344    xys_per_line_enumerated.next().and_then(|(first_line_idx, (first_line_xs, first_line_y))| {
345        let mut closest_line = (first_line_idx, first_line_xs, first_line_y);
346        let mut closest_diff = (y_pos - first_line_y.middle()).abs();
347        for (line_idx, (line_xs, line_y)) in xys_per_line_enumerated {
348            if line_y.is_over(y_pos) {
349                closest_line = (line_idx, line_xs, line_y);
350                break;
351            } else {
352                let diff = (y_pos - line_y.middle()).abs();
353                if diff < closest_diff {
354                    closest_line = (line_idx, line_xs, line_y);
355                    closest_diff = diff;
356                } else {
357                    break;
358                }
359            }
360        }
361        Some(closest_line)
362    })
363}
364
365/// Find the closest cursor index to the given `xy` position, and the center `Point` of that
366/// cursor.
367///
368/// Returns `None` if the given `text` is empty.
369pub fn closest_cursor_index_and_xy<'a, I>(point: Point, xys_per_line: I) -> Option<(Index, Point)>
370    where I: Iterator<Item = (Xs<'a, 'a>, Range)>
371{
372    closest_line(point.x, xys_per_line)
373        .and_then(|(closest_line_idx, closest_line_xs, closest_line_y)| {
374            let (closest_char_idx, closest_x) = closest_cursor_index_on_line(point.y,
375                                                                             closest_line_xs);
376            let index = Index {
377                line: closest_line_idx,
378                char: closest_char_idx,
379            };
380            let point = Point::new(closest_x, closest_line_y.middle());
381            Some((index, point))
382        })
383}
384
385/// Find the closest cursor index to the given `x` position on the given line along with the
386/// `x` position of that cursor.
387pub fn closest_cursor_index_on_line<'a>(x_pos: f32, line_xs: Xs<'a, 'a>) -> (usize, f32) {
388    let mut xs_enumerated = line_xs.enumerate();
389    // `xs` always yields at least one `x` (the start of the line).
390    let (first_idx, first_x) = xs_enumerated.next().unwrap();
391    let first_diff = (x_pos - first_x).abs();
392    let mut closest = (first_idx, first_x);
393    let mut closest_diff = first_diff;
394    for (i, x) in xs_enumerated {
395        let diff = (x_pos - x).abs();
396        if diff < closest_diff {
397            closest = (i, x);
398            closest_diff = diff;
399        } else {
400            break;
401        }
402    }
403    closest
404}
405
406
407impl<'a, I> Iterator for XysPerLine<'a, I>
408    where I: Iterator<Item = (LineInfo, Rect)>
409{
410    // The `Range` occupied by the line across the *y* axis, along with an iterator yielding
411    // each possible cursor position along the *x* axis.
412    type Item = (Xs<'a, 'a>, Range);
413    fn next(&mut self) -> Option<Self::Item> {
414        let XysPerLine { ref mut lines_with_rects, font, text, font_size } = *self;
415        let scale = super::pt_to_scale(font_size);
416        lines_with_rects.next().map(|(line_info, line_rect)| {
417            let line = &text[line_info.byte_range()];
418            let (x, y) = (line_rect.left(), line_rect.top());
419            let point = rusttype::Point { x: x, y: y };
420            let y = line_rect.y_range();
421            let layout = font.layout(line, scale, point);
422            let xs = Xs {
423                next_x: Some(line_rect.left()),
424                layout: layout,
425            };
426            (xs, y)
427        })
428    }
429}
430
431impl<'a> Iterator for XysPerLineFromText<'a> {
432    type Item = (Xs<'a, 'a>, Range);
433    fn next(&mut self) -> Option<Self::Item> {
434        self.xys_per_line.next()
435    }
436}
437
438impl<'a, 'b> Iterator for Xs<'a, 'b> {
439    // Each possible cursor position along the *x* axis.
440    type Item = f32;
441    fn next(&mut self) -> Option<Self::Item> {
442        self.next_x.map(|x| {
443            self.next_x = self.layout
444                .next()
445                .map(|g| {
446                    g.pixel_bounding_box()
447                        .map(|r| r.max.x as f32)
448                        .unwrap_or_else(|| x + g.unpositioned().h_metrics().advance_width)
449                });
450            x
451        })
452    }
453}