ori_core/text/
paragraph.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
use std::{
    fmt::Display,
    hash::{Hash, Hasher},
};

use smallvec::SmallVec;
use smol_str::{format_smolstr, SmolStr};

use super::{FontAttributes, TextAlign, TextWrap};

/// A paragraph of rich text, that can contain multiple segments with different [`FontAttributes`].
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Paragraph {
    /// The line height of the text.
    pub line_height: f32,

    /// The alignment of the text.
    pub align: TextAlign,

    /// The text wrapping mode.
    pub wrap: TextWrap,

    text: SmolStr,
    segments: SmallVec<[Segment; 1]>,
}

impl Paragraph {
    /// Create a new empty paragraph.
    pub fn new(line_height: f32, align: TextAlign, wrap: TextWrap) -> Self {
        Self {
            line_height,
            align,
            wrap,
            text: SmolStr::default(),
            segments: SmallVec::new(),
        }
    }

    /// Clear the paragraph.
    pub fn clear(&mut self) {
        self.text = SmolStr::default();
        self.segments.clear();
    }

    /// Set the text of the paragraph with the given [`FontAttributes`].
    pub fn set_text(&mut self, text: impl Display, attrs: FontAttributes) {
        self.clear();
        self.push_text(text, attrs);
    }

    /// Push a new segment of text with the given [`FontAttributes`] to the paragraph.
    pub fn push_text(&mut self, text: impl Display, attrs: FontAttributes) {
        self.text = format_smolstr!("{}{}", self.text, text);
        self.segments.push(Segment {
            end: self.text.len(),
            attrs,
        });
    }

    /// Push a new segment of text with the given [`FontAttributes`] to the paragraph.
    pub fn with_text(mut self, text: impl Display, attrs: FontAttributes) -> Self {
        self.push_text(text, attrs);
        self
    }

    /// Get the text of the paragraph.
    pub fn text(&self) -> &str {
        &self.text
    }

    /// Get an iterator over the segments of the paragraph.
    pub fn iter(&self) -> impl DoubleEndedIterator<Item = (&str, &FontAttributes)> {
        self.segments.iter().map(|segment| {
            let start = segment.end - self.text.len();
            let end = segment.end;
            let text = &self.text[start..end];
            (text, &segment.attrs)
        })
    }

    /// Get an iterator over the segments of the paragraph mutably.
    pub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = (&str, &mut FontAttributes)> {
        let text = &self.text;

        self.segments.iter_mut().map(|segment| {
            let start = segment.end - self.text.len();
            let end = segment.end;
            let text = &text[start..end];
            (text, &mut segment.attrs)
        })
    }
}

impl Eq for Paragraph {}

impl Hash for Paragraph {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.line_height.to_bits().hash(state);
        self.align.hash(state);
        self.wrap.hash(state);
        self.text.hash(state);
        self.segments.hash(state);
    }
}

/// A segment of a [`Paragraph`], that starts at the end of the previous segment, or at index 0,
/// and ends at [`Segment::end`]. The purpose of a segment is to specify the text attributes for
/// the range of the text that the segment covers.
#[derive(Clone, Debug, PartialEq, Hash)]
struct Segment {
    end: usize,
    attrs: FontAttributes,
}