1use crate::{
4 TextAlign, TextEngine, TextLayout, TextLayoutOptions, TextOverflow, TextScaler, TextStyle,
5};
6use incular_core::Size;
7use std::{fmt, ops::Range, sync::Arc};
8
9#[derive(Clone, Copy, Debug, PartialEq)]
13pub struct WidgetSpan {
14 pub id: u64,
15 pub size: Size,
16 pub alignment: WidgetSpanAlignment,
17}
18
19#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
20pub enum WidgetSpanAlignment {
21 #[default]
22 Baseline,
23 AboveBaseline,
24 BelowBaseline,
25 Middle,
26 TextTop,
27 TextBottom,
28}
29
30impl WidgetSpan {
31 #[must_use]
32 pub const fn new(id: u64) -> Self {
33 Self {
34 id,
35 size: Size::ZERO,
36 alignment: WidgetSpanAlignment::Baseline,
37 }
38 }
39
40 #[must_use]
41 pub const fn sized(id: u64, size: Size) -> Self {
42 Self {
43 id,
44 size,
45 alignment: WidgetSpanAlignment::Baseline,
46 }
47 }
48
49 #[must_use]
50 pub const fn with_size(mut self, size: Size) -> Self {
51 self.size = size;
52 self
53 }
54
55 #[must_use]
56 pub const fn alignment(mut self, alignment: WidgetSpanAlignment) -> Self {
57 self.alignment = alignment;
58 self
59 }
60}
61
62#[derive(Clone, Debug, PartialEq)]
64#[allow(clippy::large_enum_variant)]
65pub enum InlineSpan {
66 Text(TextSpan),
67 Widget(WidgetSpan),
68}
69
70impl From<TextSpan> for InlineSpan {
71 fn from(value: TextSpan) -> Self {
72 Self::Text(value)
73 }
74}
75
76impl From<WidgetSpan> for InlineSpan {
77 fn from(value: WidgetSpan) -> Self {
78 Self::Widget(value)
79 }
80}
81
82#[derive(Clone, Debug, PartialEq)]
85pub struct TextSpan {
86 pub text: String,
87 pub style: Option<TextStyle>,
88 pub children: Vec<InlineSpan>,
89}
90
91impl TextSpan {
92 #[must_use]
93 pub fn new(text: impl Into<String>) -> Self {
94 Self {
95 text: text.into(),
96 style: None,
97 children: Vec::new(),
98 }
99 }
100
101 #[must_use]
102 pub fn empty() -> Self {
103 Self::new("")
104 }
105
106 #[must_use]
107 pub fn style(mut self, style: TextStyle) -> Self {
108 self.style = Some(style);
109 self
110 }
111
112 #[must_use]
113 pub fn child(mut self, child: impl Into<InlineSpan>) -> Self {
114 self.children.push(child.into());
115 self
116 }
117
118 #[must_use]
119 pub fn children<I, S>(mut self, children: I) -> Self
120 where
121 I: IntoIterator<Item = S>,
122 S: Into<InlineSpan>,
123 {
124 self.children = children.into_iter().map(Into::into).collect();
125 self
126 }
127
128 #[must_use]
129 pub fn with_children<I, S>(text: impl Into<String>, children: I) -> Self
130 where
131 I: IntoIterator<Item = S>,
132 S: Into<InlineSpan>,
133 {
134 Self::new(text).children(children)
135 }
136
137 #[must_use]
138 pub fn plain_text(&self) -> String {
139 let mut output = String::new();
140 self.write_plain_text(&mut output);
141 output
142 }
143
144 #[must_use]
145 pub fn text_len(&self) -> usize {
146 self.plain_text().len()
147 }
148
149 #[must_use]
152 pub fn flatten(&self, inherited: &TextStyle) -> Vec<StyledTextRun> {
153 let mut runs = Vec::new();
154 let mut offset = 0;
155 self.flatten_into(inherited, &mut offset, &mut runs);
156 runs
157 }
158
159 pub fn visit(&self, visitor: &mut impl TextSpanVisitor) {
160 self.visit_with(&TextStyle::default(), visitor);
161 }
162
163 fn write_plain_text(&self, output: &mut String) {
164 output.push_str(&self.text);
165 for child in &self.children {
166 match child {
167 InlineSpan::Text(span) => span.write_plain_text(output),
168 InlineSpan::Widget(_) => output.push('\u{fffc}'),
169 }
170 }
171 }
172
173 fn flatten_into(
174 &self,
175 inherited: &TextStyle,
176 offset: &mut usize,
177 runs: &mut Vec<StyledTextRun>,
178 ) {
179 let style = self
180 .style
181 .as_ref()
182 .map_or_else(|| inherited.clone(), |local| inherited.merge(local));
183 if !self.text.is_empty() {
184 let start = *offset;
185 *offset += self.text.len();
186 runs.push(StyledTextRun {
187 text: self.text.clone(),
188 style: style.clone(),
189 range: start..*offset,
190 });
191 }
192 for child in &self.children {
193 match child {
194 InlineSpan::Text(span) => span.flatten_into(&style, offset, runs),
195 InlineSpan::Widget(widget) => {
196 let start = *offset;
197 *offset += '\u{fffc}'.len_utf8();
198 runs.push(StyledTextRun {
199 text: '\u{fffc}'.to_string(),
200 style: style.clone(),
201 range: start..*offset,
202 });
203 visitor_placeholder(widget, &style, start..*offset, runs);
204 }
205 }
206 }
207 }
208
209 fn visit_with(&self, inherited: &TextStyle, visitor: &mut impl TextSpanVisitor) {
210 let style = self
211 .style
212 .as_ref()
213 .map_or_else(|| inherited.clone(), |local| inherited.merge(local));
214 if !self.text.is_empty() {
215 visitor.visit_text(&self.text, &style);
216 }
217 for child in &self.children {
218 match child {
219 InlineSpan::Text(span) => span.visit_with(&style, visitor),
220 InlineSpan::Widget(widget) => visitor.visit_widget(*widget, &style),
221 }
222 }
223 }
224}
225
226#[derive(Clone, Debug, PartialEq)]
228pub struct StyledTextRun {
229 pub text: String,
230 pub style: TextStyle,
231 pub range: Range<usize>,
232}
233
234pub trait TextSpanVisitor {
237 fn visit_text(&mut self, _text: &str, _style: &TextStyle) {}
238 fn visit_widget(&mut self, _widget: WidgetSpan, _style: &TextStyle) {}
239}
240
241fn visitor_placeholder(
244 _widget: &WidgetSpan,
245 _style: &TextStyle,
246 _range: Range<usize>,
247 _runs: &mut Vec<StyledTextRun>,
248) {
249}
250
251#[derive(Clone, Debug, PartialEq)]
253pub struct RichText {
254 pub text: TextSpan,
255 pub text_align: TextAlign,
256 pub text_scaler: TextScaler,
257 pub max_lines: Option<usize>,
258 pub soft_wrap: bool,
259 pub overflow: TextOverflow,
260}
261
262impl RichText {
263 #[must_use]
264 pub fn new(text: TextSpan) -> Self {
265 Self {
266 text,
267 text_align: TextAlign::Start,
268 text_scaler: TextScaler::default(),
269 max_lines: None,
270 soft_wrap: true,
271 overflow: TextOverflow::Clip,
272 }
273 }
274
275 #[must_use]
276 pub fn text_align(mut self, text_align: TextAlign) -> Self {
277 self.text_align = text_align;
278 self
279 }
280
281 #[must_use]
282 pub fn align(self, text_align: TextAlign) -> Self {
283 self.text_align(text_align)
284 }
285
286 #[must_use]
287 pub fn text_scaler(mut self, text_scaler: TextScaler) -> Self {
288 self.text_scaler = text_scaler;
289 self
290 }
291
292 #[must_use]
293 pub fn max_lines(mut self, max_lines: Option<usize>) -> Self {
294 self.max_lines = max_lines;
295 self
296 }
297
298 #[must_use]
299 pub fn soft_wrap(mut self, soft_wrap: bool) -> Self {
300 self.soft_wrap = soft_wrap;
301 self
302 }
303
304 #[must_use]
305 pub fn overflow(mut self, overflow: TextOverflow) -> Self {
306 self.overflow = overflow;
307 self
308 }
309
310 #[must_use]
311 pub fn plain_text(&self) -> String {
312 self.text.plain_text()
313 }
314
315 #[must_use]
316 pub fn flatten(&self) -> Vec<StyledTextRun> {
317 self.text
318 .flatten(&TextStyle::default().scaled(self.text_scaler))
319 }
320
321 #[must_use]
322 pub fn layout(&self, engine: &mut TextEngine, max_width: Option<f32>) -> Arc<TextLayout> {
323 let style = self
324 .flatten()
325 .first()
326 .map_or_else(TextStyle::default, |run| run.style.clone());
327 engine.layout_with_options(
328 &self.plain_text(),
329 &style,
330 TextLayoutOptions::new(max_width, self.text_align)
331 .soft_wrap(self.soft_wrap)
332 .max_lines(self.max_lines)
333 .overflow(self.overflow),
334 )
335 }
336}
337
338#[derive(Clone, Debug, PartialEq)]
341pub struct Text {
342 pub text: String,
343 pub style: TextStyle,
344 pub text_align: TextAlign,
345 pub text_scaler: TextScaler,
346 pub max_lines: Option<usize>,
347 pub soft_wrap: bool,
348 pub overflow: TextOverflow,
349}
350
351impl Text {
352 #[must_use]
353 pub fn new(text: impl Into<String>) -> Self {
354 Self {
355 text: text.into(),
356 style: TextStyle::default(),
357 text_align: TextAlign::Start,
358 text_scaler: TextScaler::default(),
359 max_lines: None,
360 soft_wrap: true,
361 overflow: TextOverflow::Clip,
362 }
363 }
364
365 #[must_use]
366 pub fn style(mut self, style: TextStyle) -> Self {
367 self.style = style;
368 self
369 }
370
371 #[must_use]
372 pub fn align(mut self, align: TextAlign) -> Self {
373 self.text_align = align;
374 self
375 }
376
377 #[must_use]
378 pub fn text_align(self, align: TextAlign) -> Self {
379 self.align(align)
380 }
381
382 #[must_use]
383 pub fn text_scaler(mut self, scaler: TextScaler) -> Self {
384 self.text_scaler = scaler;
385 self
386 }
387
388 #[must_use]
389 pub fn max_lines(mut self, max_lines: Option<usize>) -> Self {
390 self.max_lines = max_lines;
391 self
392 }
393
394 #[must_use]
395 pub fn soft_wrap(mut self, soft_wrap: bool) -> Self {
396 self.soft_wrap = soft_wrap;
397 self
398 }
399
400 #[must_use]
401 pub fn overflow(mut self, overflow: TextOverflow) -> Self {
402 self.overflow = overflow;
403 self
404 }
405
406 #[must_use]
407 pub fn span(&self) -> TextSpan {
408 TextSpan::new(self.text.clone()).style(self.style.clone())
409 }
410
411 #[must_use]
412 pub fn rich_text(&self) -> RichText {
413 RichText::new(self.span())
414 .align(self.text_align)
415 .text_scaler(self.text_scaler)
416 .max_lines(self.max_lines)
417 .soft_wrap(self.soft_wrap)
418 .overflow(self.overflow)
419 }
420
421 #[must_use]
422 pub fn layout(&self, engine: &mut TextEngine, max_width: Option<f32>) -> Arc<TextLayout> {
423 engine.layout_with_options(
424 &self.text,
425 &self.style.scaled(self.text_scaler),
426 TextLayoutOptions::new(max_width, self.text_align)
427 .soft_wrap(self.soft_wrap)
428 .max_lines(self.max_lines)
429 .overflow(self.overflow),
430 )
431 }
432}
433
434impl From<&str> for Text {
435 fn from(value: &str) -> Self {
436 Self::new(value)
437 }
438}
439
440impl From<String> for Text {
441 fn from(value: String) -> Self {
442 Self::new(value)
443 }
444}
445
446impl fmt::Display for Text {
447 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
448 formatter.write_str(&self.text)
449 }
450}