1use crate::layout_management::CotisLayoutRun;
2use crate::layout_struct::TextDrawPayload;
3use crate::layout_struct::info_types::TextConfig as MeasureTextConfig;
4use crate::layout_struct::layout_states::ChildWrapping;
5use crate::layout_traits::CotisLayoutCompatible;
6use crate::text::{
7 TextLineChildSpec, line_word_space_pieces, measure_text_fragment, newline_line_ranges,
8 text_line_child_specs,
9};
10use cotis::utils::ElementId;
11use cotis_defaults::colors::Color;
12#[cfg(feature = "complex_color")]
13use cotis_defaults::colors::ColorLayer;
14use cotis_defaults::element_configs::style::sizing::AxisSizing;
15use cotis_defaults::element_configs::style::types::{LayoutDirection, Padding};
16use cotis_defaults::element_configs::style::{BorderElementStyle, BorderWidthStyle, CotisStyle};
17use cotis_defaults::element_configs::text_config::{
18 TextConfig, TextElementConfig, TextElementConfigWrapMode, TextStyle,
19};
20use cotis_utils::math::Dimensions;
21use std::ops::Range;
22use std::sync::Arc;
23
24pub trait TextConfigurable: Sized {
55 fn synthetic_text_child(
60 &self,
61 layout_direction: LayoutDirection,
62 child_gap: f32,
63 width: AxisSizing,
64 height: AxisSizing,
65 ) -> Self;
66}
67
68pub(crate) fn reset_text_child_style<S>(
70 style: &mut CotisStyle<S>,
71 layout_direction: LayoutDirection,
72 child_gap: f32,
73) {
74 #[cfg(not(feature = "complex_color"))]
75 {
76 style.background_color.a = 0.0;
77 }
78 #[cfg(feature = "complex_color")]
79 {
80 style.background_color = ColorLayer::Solid(Color::rgba(0.0, 0.0, 0.0, 0.0));
81 }
82 style.layout.layout_direction = layout_direction;
83 style.layout.child_gap = child_gap;
84 style.layout.padding = Padding {
85 top: 0.0,
86 bottom: 0.0,
87 left: 0.0,
88 right: 0.0,
89 };
90 style.floating = None;
91 style.border = BorderElementStyle {
92 color: Color {
93 r: 0.0,
94 g: 0.0,
95 b: 0.0,
96 a: 0.0,
97 },
98 width: BorderWidthStyle {
99 left: 0.0,
100 right: 0.0,
101 top: 0.0,
102 bottom: 0.0,
103 between_children: 0.0,
104 },
105 };
106}
107
108fn measuring_text_config(config: &TextConfig) -> MeasureTextConfig {
111 MeasureTextConfig {
112 font_id: config.font_id as usize,
113 font_size: config.font_size,
114 letter_spacing: config.letter_spacing,
115 line_height: config.line_height,
116 wrap_mode: config.wrap_mode,
117 alignment: config.alignment,
118 }
119}
120
121impl<'layout, 'frame, Config: TextConfigurable + CotisLayoutCompatible<'frame>>
122 CotisLayoutRun<'layout, Config>
123{
124 fn create_text_child(
127 &self,
128 container_id: ElementId,
129 layout_direction: LayoutDirection,
130 child_gap: f32,
131 measured: Dimensions,
132 intrinsic_fit: bool,
133 ) -> Config {
134 let w = measured.width.max(0.0);
135 let h = measured.height.max(0.0);
136
137 let width = if intrinsic_fit {
138 AxisSizing::Fit(0.0, w)
139 } else {
140 AxisSizing::Fixed(w)
141 };
142 let height = if intrinsic_fit {
143 AxisSizing::Fit(h, f32::MAX)
144 } else {
145 AxisSizing::Fixed(h)
146 };
147
148 let parent = self
149 .get_config(container_id)
150 .expect("[Cotis Layout] text leaf needs set_config before set_leaf_config");
151
152 parent.synthetic_text_child(layout_direction, child_gap, width, height)
153 }
154
155 fn push_spacer(&mut self, container_id: ElementId, measured: Dimensions) {
156 self.new_child_element();
157
158 let spacer_el = self.create_text_child(
159 container_id,
160 LayoutDirection::LeftToRight,
161 0.0,
162 measured,
163 false,
164 );
165
166 let layout = spacer_el.get_style();
167 let id = self.open_element_id();
168
169 self.set_open_element_config(layout, id, None, spacer_el);
170
171 self.close_open_element();
172 }
173
174 fn push_text_fragment(
175 &mut self,
176 container_id: ElementId,
177 source: &Arc<str>,
178 range: Range<usize>,
179 text_config: TextConfig,
180 text_style: TextStyle,
181 measured: Dimensions,
182 ) {
183 self.new_child_element();
184
185 let word_el = self.create_text_child(
186 container_id,
187 LayoutDirection::LeftToRight,
188 0.0,
189 measured,
190 false,
191 );
192
193 let layout = word_el.get_style();
194 let id = self.open_element_id();
195
196 self.set_open_element_config(layout, id, None, word_el);
197
198 let wid = self.open_element_id();
199
200 self.text_fragments.insert(
201 wid,
202 TextDrawPayload::new_from_arc(source, range, text_config, text_style),
203 );
204
205 self.close_open_element();
206 }
207
208 pub(crate) fn apply_text_leaf(&mut self, leaf: TextElementConfig<'frame>) {
209 let container_id = self.open_element_id();
210
211 let source: Arc<str> = Arc::from(leaf.text.as_ref());
212
213 let text_style = leaf.style;
214 let text_config = leaf.config;
215 let measure_style = measuring_text_config(&text_config);
216
217 self.set_open_element_layout_direction(LayoutDirection::TopToBottom);
218
219 let word_row_gap = match text_config.wrap_mode {
220 TextElementConfigWrapMode::Words => {
221 measure_text_fragment(" ", &measure_style, self.layout_manager.text_dim_fun()).width
222 }
223 _ => 0.0,
224 };
225
226 for line_range in newline_line_ranges(source.as_ref()) {
227 let line_slice_full = &source[line_range.start..line_range.end];
228
229 let (line_dims, words_specs) = match text_config.wrap_mode {
230 TextElementConfigWrapMode::Words => {
231 let empty_dims = measure_text_fragment(
232 "",
233 &measure_style,
234 self.layout_manager.text_dim_fun(),
235 );
236
237 let pieces = line_word_space_pieces(line_slice_full);
238
239 let specs = text_line_child_specs(&pieces);
240
241 let dims = if specs.is_empty() {
242 empty_dims
243 } else {
244 let mut sum_w = 0.0f32;
245 let mut max_h = empty_dims.height;
246
247 for spec in &specs {
248 if let TextLineChildSpec::Word(r) = spec {
249 let fragment = &line_slice_full[r.clone()];
250
251 let d = measure_text_fragment(
252 fragment,
253 &measure_style,
254 self.layout_manager.text_dim_fun(),
255 );
256
257 sum_w += d.width;
258 max_h = max_h.max(d.height);
259 }
260 }
261
262 let gap_total = (specs.len().saturating_sub(1)) as f32 * word_row_gap;
263
264 Dimensions {
265 width: sum_w + gap_total,
266 height: max_h,
267 }
268 };
269
270 (dims, specs)
271 }
272
273 TextElementConfigWrapMode::Newline | TextElementConfigWrapMode::None => {
274 let dims = measure_text_fragment(
275 line_slice_full,
276 &measure_style,
277 self.layout_manager.text_dim_fun(),
278 );
279
280 (dims, Vec::new())
281 }
282 };
283
284 self.new_child_element();
285
286 let line_el = self.create_text_child(
287 container_id,
288 LayoutDirection::LeftToRight,
289 word_row_gap,
290 line_dims,
291 true,
292 );
293
294 let mut line_layout = line_el.get_style();
295 line_layout.layout.wrapping = ChildWrapping::Text;
296
297 let line_id = self.open_element_id();
298
299 self.set_open_element_config(line_layout, line_id, None, line_el);
300
301 match text_config.wrap_mode {
302 TextElementConfigWrapMode::Words => {
303 let spacer_measured = Dimensions {
304 width: 0.0,
305 height: line_dims.height,
306 };
307
308 for spec in words_specs {
309 match spec {
310 TextLineChildSpec::Spacer => {
311 self.push_spacer(container_id, spacer_measured);
312 }
313
314 TextLineChildSpec::Word(w) => {
315 let global = line_range.start + w.start..line_range.start + w.end;
316
317 let word_text = &source[global.clone()];
318
319 let measured = measure_text_fragment(
320 word_text,
321 &measure_style,
322 self.layout_manager.text_dim_fun(),
323 );
324
325 self.push_text_fragment(
326 container_id,
327 &source,
328 global,
329 text_config,
330 text_style.clone(),
331 measured,
332 );
333 }
334 }
335 }
336 }
337
338 TextElementConfigWrapMode::Newline | TextElementConfigWrapMode::None => {
339 if !line_range.is_empty() {
340 let global = line_range.clone();
341
342 let measured = measure_text_fragment(
343 &source[global.clone()],
344 &measure_style,
345 self.layout_manager.text_dim_fun(),
346 );
347
348 self.push_text_fragment(
349 container_id,
350 &source,
351 global,
352 text_config,
353 text_style.clone(),
354 measured,
355 );
356 }
357 }
358 }
359
360 self.close_open_element();
361 }
362 }
363}