Skip to main content

fastui_cosmic/
buffer_line.rs

1#[cfg(not(feature = "std"))]
2use alloc::{string::String, vec::Vec};
3use core::mem;
4
5use crate::{
6    Align, Attrs, AttrsList, Cached, FontSystem, LayoutLine, LineEnding, ShapeLine, Shaping, Wrap,
7};
8
9/// A line (or paragraph) of text that is shaped and laid out
10#[derive(Clone, Debug)]
11pub struct BufferLine {
12    text: String,
13    ending: LineEnding,
14    attrs_list: AttrsList,
15    align: Option<Align>,
16    shape_opt: Cached<ShapeLine>,
17    layout_opt: Cached<Vec<LayoutLine>>,
18    shaping: Shaping,
19    metadata: Option<usize>,
20}
21
22impl BufferLine {
23    /// Create a new line with the given text and attributes list
24    /// Cached shaping and layout can be done using the [`Self::shape`] and
25    /// [`Self::layout`] functions
26    pub fn new<T: Into<String>>(
27        text: T,
28        ending: LineEnding,
29        attrs_list: AttrsList,
30        shaping: Shaping,
31    ) -> Self {
32        Self {
33            text: text.into(),
34            ending,
35            attrs_list,
36            align: None,
37            shape_opt: Cached::Empty,
38            layout_opt: Cached::Empty,
39            shaping,
40            metadata: None,
41        }
42    }
43
44    /// Resets the current line with new internal values.
45    ///
46    /// Avoids deallocating internal caches so they can be reused.
47    pub fn reset_new<T: Into<String>>(
48        &mut self,
49        text: T,
50        ending: LineEnding,
51        attrs_list: AttrsList,
52        shaping: Shaping,
53    ) {
54        self.text = text.into();
55        self.ending = ending;
56        self.attrs_list = attrs_list;
57        self.align = None;
58        self.shape_opt.set_unused();
59        self.layout_opt.set_unused();
60        self.shaping = shaping;
61        self.metadata = None;
62    }
63
64    /// Get current text
65    pub fn text(&self) -> &str {
66        &self.text
67    }
68
69    /// Set text and attributes list
70    ///
71    /// Will reset shape and layout if it differs from current text and attributes list.
72    /// Returns true if the line was reset
73    pub fn set_text<T: AsRef<str>>(
74        &mut self,
75        text: T,
76        ending: LineEnding,
77        attrs_list: AttrsList,
78    ) -> bool {
79        let text = text.as_ref();
80        if text != self.text || ending != self.ending || attrs_list != self.attrs_list {
81            self.text.clear();
82            self.text.push_str(text);
83            self.ending = ending;
84            self.attrs_list = attrs_list;
85            self.reset();
86            true
87        } else {
88            false
89        }
90    }
91
92    /// Consume this line, returning only its text contents as a String.
93    pub fn into_text(self) -> String {
94        self.text
95    }
96
97    /// Get line ending
98    pub fn ending(&self) -> LineEnding {
99        self.ending
100    }
101
102    /// Set line ending
103    ///
104    /// Will reset shape and layout if it differs from current line ending.
105    /// Returns true if the line was reset
106    pub fn set_ending(&mut self, ending: LineEnding) -> bool {
107        if ending != self.ending {
108            self.ending = ending;
109            self.reset_shaping();
110            true
111        } else {
112            false
113        }
114    }
115
116    /// Get attributes list
117    pub fn attrs_list(&self) -> &AttrsList {
118        &self.attrs_list
119    }
120
121    /// Set attributes list
122    ///
123    /// Will reset shape and layout if it differs from current attributes list.
124    /// Returns true if the line was reset
125    pub fn set_attrs_list(&mut self, attrs_list: AttrsList) -> bool {
126        if attrs_list != self.attrs_list {
127            self.attrs_list = attrs_list;
128            self.reset_shaping();
129            true
130        } else {
131            false
132        }
133    }
134
135    /// Get the Text alignment
136    pub fn align(&self) -> Option<Align> {
137        self.align
138    }
139
140    /// Set the text alignment
141    ///
142    /// Will reset layout if it differs from current alignment.
143    /// Setting to None will use `Align::Right` for RTL lines, and `Align::Left` for LTR lines.
144    /// Returns true if the line was reset
145    pub fn set_align(&mut self, align: Option<Align>) -> bool {
146        if align != self.align {
147            self.align = align;
148            self.reset_layout();
149            true
150        } else {
151            false
152        }
153    }
154
155    /// Append line at end of this line
156    ///
157    /// The wrap setting of the appended line will be lost
158    pub fn append(&mut self, other: Self) {
159        let len = self.text.len();
160        self.text.push_str(other.text());
161
162        if other.attrs_list.defaults() != self.attrs_list.defaults() {
163            // If default formatting does not match, make a new span for it
164            self.attrs_list
165                .add_span(len..len + other.text().len(), &other.attrs_list.defaults());
166        }
167
168        for (other_range, attrs) in other.attrs_list.spans_iter() {
169            // Add previous attrs spans
170            let range = other_range.start + len..other_range.end + len;
171            self.attrs_list.add_span(range, &attrs.as_attrs());
172        }
173
174        self.reset();
175    }
176
177    /// Split off new line at index
178    pub fn split_off(&mut self, index: usize) -> Self {
179        let text = self.text.split_off(index);
180        let attrs_list = self.attrs_list.split_off(index);
181        self.reset();
182
183        let mut new = Self::new(text, self.ending, attrs_list, self.shaping);
184        new.align = self.align;
185        new
186    }
187
188    /// Reset shaping, layout, and metadata caches
189    pub fn reset(&mut self) {
190        self.metadata = None;
191        self.reset_shaping();
192    }
193
194    /// Reset shaping and layout caches
195    pub fn reset_shaping(&mut self) {
196        self.shape_opt.set_unused();
197        self.reset_layout();
198    }
199
200    /// Reset only layout cache
201    pub fn reset_layout(&mut self) {
202        self.layout_opt.set_unused();
203    }
204
205    /// Shape line, will cache results
206    #[allow(clippy::missing_panics_doc)]
207    pub fn shape(&mut self, font_system: &mut FontSystem, tab_width: u16) -> &ShapeLine {
208        if self.shape_opt.is_unused() {
209            let mut line = self
210                .shape_opt
211                .take_unused()
212                .unwrap_or_else(ShapeLine::empty);
213            line.build(
214                font_system,
215                &self.text,
216                &self.attrs_list,
217                self.shaping,
218                tab_width,
219            );
220            self.shape_opt.set_used(line);
221            self.layout_opt.set_unused();
222        }
223        self.shape_opt.get().expect("shape not found")
224    }
225
226    /// Get line shaping cache
227    pub fn shape_opt(&self) -> Option<&ShapeLine> {
228        self.shape_opt.get()
229    }
230
231    /// Layout line, will cache results
232    #[allow(clippy::missing_panics_doc)]
233    pub fn layout(
234        &mut self,
235        font_system: &mut FontSystem,
236        font_size: f32,
237        width_opt: Option<f32>,
238        first_line_indent: Option<f32>,
239        wrap: Wrap,
240        match_mono_width: Option<f32>,
241        tab_width: u16,
242    ) -> &[LayoutLine] {
243        if self.layout_opt.is_unused() {
244            let align = self.align;
245            let mut layout = self
246                .layout_opt
247                .take_unused()
248                .unwrap_or_else(|| Vec::with_capacity(1));
249            let shape = self.shape(font_system, tab_width);
250            shape.layout_to_buffer(
251                &mut font_system.shape_buffer,
252                font_size,
253                width_opt,
254                wrap,
255                align,
256                first_line_indent,
257                &mut layout,
258                match_mono_width,
259            );
260            self.layout_opt.set_used(layout);
261        }
262        self.layout_opt.get().expect("layout not found")
263    }
264
265    /// Get line layout cache
266    pub fn layout_opt(&self) -> Option<&Vec<LayoutLine>> {
267        self.layout_opt.get()
268    }
269
270    /// Get line metadata. This will be None if [`BufferLine::set_metadata`] has not been called
271    /// after the last reset of shaping and layout caches
272    pub fn metadata(&self) -> Option<usize> {
273        self.metadata
274    }
275
276    /// Set line metadata. This is stored until the next line reset
277    pub fn set_metadata(&mut self, metadata: usize) {
278        self.metadata = Some(metadata);
279    }
280
281    /// Makes an empty buffer line.
282    ///
283    /// The buffer line is in an invalid state after this is called. See [`Self::reset_new`].
284    pub(crate) fn empty() -> Self {
285        Self {
286            text: String::default(),
287            ending: LineEnding::default(),
288            attrs_list: AttrsList::new(&Attrs::new()),
289            align: None,
290            shape_opt: Cached::Empty,
291            layout_opt: Cached::Empty,
292            shaping: Shaping::Advanced,
293            metadata: None,
294        }
295    }
296
297    /// Reclaim attributes list memory that isn't needed any longer.
298    ///
299    /// The buffer line is in an invalid state after this is called. See [`Self::reset_new`].
300    pub(crate) fn reclaim_attrs(&mut self) -> AttrsList {
301        mem::replace(&mut self.attrs_list, AttrsList::new(&Attrs::new()))
302    }
303
304    /// Reclaim text memory that isn't needed any longer.
305    ///
306    /// The buffer line is in an invalid state after this is called. See [`Self::reset_new`].
307    pub(crate) fn reclaim_text(&mut self) -> String {
308        let mut text = mem::take(&mut self.text);
309        text.clear();
310        text
311    }
312}