Skip to main content

repose_core/
text.rs

1use crate::Color;
2use std::sync::Arc;
3
4#[derive(Debug, Clone, Copy, PartialEq)]
5pub struct SpanStyle {
6    pub color: Option<Color>,
7    pub font_size: Option<f32>,
8}
9
10impl SpanStyle {
11    pub const fn default() -> Self {
12        Self {
13            color: None,
14            font_size: None,
15        }
16    }
17
18    pub fn color(mut self, c: Color) -> Self {
19        self.color = Some(c);
20        self
21    }
22
23    pub fn font_size(mut self, px: f32) -> Self {
24        self.font_size = Some(px);
25        self
26    }
27}
28
29impl Default for SpanStyle {
30    fn default() -> Self {
31        Self::default()
32    }
33}
34
35/// A span of text with an associated style.
36#[derive(Debug, Clone, PartialEq)]
37pub struct TextSpan {
38    /// Byte offset start in the original text.
39    pub start: usize,
40    /// Byte offset end (exclusive) in the original text.
41    pub end: usize,
42    pub style: SpanStyle,
43}
44
45/// Text with multiple styled spans.
46///
47/// Analogous to Compose's `AnnotatedString`.
48#[derive(Debug, Clone)]
49pub struct AnnotatedString {
50    pub text: String,
51    pub spans: Arc<[TextSpan]>,
52}
53
54impl AnnotatedString {
55    pub fn new(text: impl Into<String>, spans: Vec<TextSpan>) -> Self {
56        let text = text.into();
57        Self {
58            text,
59            spans: spans.into(),
60        }
61    }
62
63    pub fn as_str(&self) -> &str {
64        &self.text
65    }
66}
67
68impl From<String> for AnnotatedString {
69    fn from(text: String) -> Self {
70        Self {
71            text,
72            spans: Arc::from([]),
73        }
74    }
75}
76
77impl From<&str> for AnnotatedString {
78    fn from(text: &str) -> Self {
79        Self {
80            text: text.to_string(),
81            spans: Arc::from([]),
82        }
83    }
84}
85
86/// Builder for constructing an `AnnotatedString`.
87#[derive(Default)]
88pub struct AnnotatedStringBuilder {
89    text: String,
90    spans: Vec<TextSpan>,
91}
92
93impl AnnotatedStringBuilder {
94    pub fn new() -> Self {
95        Self::default()
96    }
97
98    /// Append plain text (inherits parent style, or default if at top level).
99    pub fn push(&mut self, text: &str) -> &mut Self {
100        self.text.push_str(text);
101        self
102    }
103
104    /// Append text with a specific style.
105    pub fn push_with_style(&mut self, text: &str, style: SpanStyle) -> &mut Self {
106        let start = self.text.len();
107        self.text.push_str(text);
108        let end = self.text.len();
109        if start < end {
110            self.spans.push(TextSpan { start, end, style });
111        }
112        self
113    }
114
115    /// Append text in a specific color.
116    pub fn push_color(&mut self, text: &str, color: Color) -> &mut Self {
117        self.push_with_style(text, SpanStyle::default().color(color))
118    }
119
120    /// Apply a style to a range of already-appended text.
121    pub fn add_style(&mut self, start: usize, end: usize, style: SpanStyle) -> &mut Self {
122        if start < end && end <= self.text.len() {
123            self.spans.push(TextSpan { start, end, style });
124        }
125        self
126    }
127
128    pub fn build(&mut self) -> AnnotatedString {
129        let text = std::mem::take(&mut self.text);
130        self.spans.sort_by_key(|s| s.start);
131        // Merge overlapping/adjacent spans with same style
132        let mut merged: Vec<TextSpan> = Vec::new();
133        for span in std::mem::take(&mut self.spans) {
134            if let Some(last) = merged.last_mut() {
135                if last.end == span.start && last.style == span.style {
136                    last.end = span.end;
137                    continue;
138                }
139            }
140            merged.push(span);
141        }
142        AnnotatedString {
143            text,
144            spans: merged.into(),
145        }
146    }
147}
148
149/// Convenience function to build an `AnnotatedString`.
150pub fn build_annotated_string(b: impl FnOnce(&mut AnnotatedStringBuilder)) -> AnnotatedString {
151    let mut builder = AnnotatedStringBuilder::new();
152    b(&mut builder);
153    builder.build()
154}