Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-ui-core/src/ui_components/text.rs
1use std::borrow::Cow;
2 
3use crate::elements::{Highlight, HighlightedRange, DEFAULT_UI_LINE_HEIGHT_RATIO};
4use crate::{
5 elements::{Container, Element, Text},
6 fonts::Properties,
7 ui_components::components::{UiComponent, UiComponentStyles},
8};
9use itertools::Itertools;
10 
11#[derive(Debug, Clone, Default)]
12pub struct WrappableText {
13 text: Cow<'static, str>,
14 styles: UiComponentStyles,
15 wrap: bool,
16 line_height_ratio: f32,
17 highlights: Vec<HighlightedRange>,
18 /// Whether the text is selectable when rendered as a descendant of a [`SelectableArea`].
19 is_selectable: bool,
20}
21 
22impl WrappableText {
23 pub fn new(text: Cow<'static, str>, soft_wrap: bool, styles: UiComponentStyles) -> Self {
24 WrappableText {
25 text,
26 styles,
27 wrap: soft_wrap,
28 line_height_ratio: DEFAULT_UI_LINE_HEIGHT_RATIO,
29 highlights: vec![],
30 is_selectable: true,
31 }
32 }
33 
34 pub fn with_highlights(mut self, highlight_indices: Vec<usize>, highlight: Highlight) -> Self {
35 if highlight_indices.is_empty() {
36 return self;
37 }
38 self.highlights = vec![HighlightedRange {
39 highlight,
40 highlight_indices,
41 }];
42 self
43 }
44 
45 pub fn with_line_height_ratio(mut self, line_height_ratio: f32) -> Self {
46 self.line_height_ratio = line_height_ratio;
47 self
48 }
49 
50 pub fn with_selectable(mut self, is_selectable: bool) -> Self {
51 self.is_selectable = is_selectable;
52 self
53 }
54}
55 
56impl UiComponent for WrappableText {
57 type ElementType = Container;
58 fn build(self) -> Container {
59 let styles = self.styles;
60 let mut text = Text::new(
61 self.text,
62 styles.font_family_id.unwrap(),
63 styles.font_size.unwrap_or_default(),
64 )
65 .soft_wrap(self.wrap)
66 .with_line_height_ratio(self.line_height_ratio)
67 .with_selectable(self.is_selectable);
68 if let Some(color) = styles.font_color {
69 text = text.with_color(color);
70 }
71 if let Some(weight) = styles.font_weight {
72 text = text.with_style(Properties::default().weight(weight))
73 }
74 
75 // The text element assumes that highlights are sorted by character index.
76 text = text.with_highlights(
77 self.highlights
78 .iter()
79 .sorted_by_key(|highlighted_range| highlighted_range.highlight_indices.first())
80 .cloned(),
81 );
82 
83 let mut container = Container::new(text.finish());
84 if let Some(margin) = styles.margin {
85 container = container
86 .with_margin_left(margin.left)
87 .with_margin_top(margin.top)
88 .with_margin_right(margin.right)
89 .with_margin_bottom(margin.bottom);
90 }
91 container
92 }
93 
94 /// Overwrites _some_ styles passed in `style` parameter
95 fn with_style(self, styles: UiComponentStyles) -> Self {
96 Self {
97 text: self.text,
98 styles: self.styles.merge(styles),
99 ..self
100 }
101 }
102}
103 
104#[derive(Debug, Clone, Default)]
105pub struct Span {
106 text: WrappableText,
107}
108 
109impl Span {
110 pub fn new(text: impl Into<Cow<'static, str>>, styles: UiComponentStyles) -> Self {
111 Span {
112 text: WrappableText::new(text.into(), false, styles),
113 }
114 }
115 
116 pub fn with_highlights(mut self, highlight_indices: Vec<usize>, highlight: Highlight) -> Self {
117 self.text = self.text.with_highlights(highlight_indices, highlight);
118 self
119 }
120 
121 pub fn with_soft_wrap(mut self) -> Self {
122 self.text.wrap = true;
123 self
124 }
125 
126 pub fn with_line_height_ratio(mut self, line_height_ratio: f32) -> Self {
127 self.text.line_height_ratio = line_height_ratio;
128 self
129 }
130 
131 pub fn with_selectable(mut self, is_selectable: bool) -> Self {
132 self.text.is_selectable = is_selectable;
133 self
134 }
135}
136 
137impl UiComponent for Span {
138 type ElementType = Container;
139 fn build(self) -> Container {
140 self.text.build()
141 }
142 
143 /// Overwrites _some_ styles passed in `style` parameter
144 fn with_style(self, styles: UiComponentStyles) -> Self {
145 Self {
146 text: self.text.with_style(styles),
147 }
148 }
149}
150 
151// Main difference between Span vs Paragraph is that Paragraph wraps the text
152// and it's intention is to be used for longer text blocks whereas Span is good
153// for short labels etc.
154#[derive(Debug, Clone, Default)]
155pub struct Paragraph {
156 text: WrappableText,
157}
158 
159impl Paragraph {
160 pub fn new(text: impl Into<Cow<'static, str>>, styles: UiComponentStyles) -> Self {
161 Paragraph {
162 text: WrappableText::new(text.into(), true, styles),
163 }
164 }
165 
166 pub fn with_highlights(mut self, highlight_indices: Vec<usize>, highlight: Highlight) -> Self {
167 self.text = self.text.with_highlights(highlight_indices, highlight);
168 self
169 }
170 
171 // TODO(alokedesai): Make it clear throughout the text rendering code that highlights are
172 // indexed by _character_, not byte.
173 pub fn add_highlight(&mut self, highlight_indices: Vec<usize>, highlight: Highlight) {
174 if highlight_indices.is_empty() {
175 return;
176 }
177 self.text.highlights.push(HighlightedRange {
178 highlight,
179 highlight_indices,
180 });
181 }
182}
183 
184impl UiComponent for Paragraph {
185 type ElementType = Container;
186 fn build(self) -> Container {
187 self.text.build()
188 }
189 
190 /// Overwrites _some_ styles passed in `style` parameter
191 fn with_style(self, styles: UiComponentStyles) -> Self {
192 Self {
193 text: self.text.with_style(styles),
194 }
195 }
196}
197