use crate::layout_management::CotisLayoutRun;
use crate::layout_struct::TextDrawPayload;
use crate::layout_struct::info_types::TextConfig as MeasureTextConfig;
use crate::layout_struct::layout_states::ChildWrapping;
use crate::layout_traits::CotisLayoutCompatible;
use crate::text::{
TextLineChildSpec, line_word_space_pieces, measure_text_fragment, newline_line_ranges,
text_line_child_specs,
};
use cotis::utils::ElementId;
use cotis_defaults::colors::Color;
use cotis_defaults::element_configs::style::sizing::AxisSizing;
use cotis_defaults::element_configs::style::types::{LayoutDirection, Padding};
use cotis_defaults::element_configs::style::{
BorderElementStyle, BorderWidthStyle, CotisStyle, TRANSPARENT_BACKGROUND,
};
use cotis_defaults::element_configs::text_config::{
TextConfig, TextElementConfig, TextElementConfigWrapMode, TextStyle,
};
use cotis_utils::math::Dimensions;
use std::ops::Range;
use std::sync::Arc;
pub trait TextConfigurable: Sized {
fn synthetic_text_child(
&self,
layout_direction: LayoutDirection,
child_gap: f32,
width: AxisSizing,
height: AxisSizing,
) -> Self;
}
pub(crate) fn reset_text_child_style<S>(
style: &mut CotisStyle<S>,
layout_direction: LayoutDirection,
child_gap: f32,
) {
style.background_color = TRANSPARENT_BACKGROUND;
style.layout.layout_direction = layout_direction;
style.layout.child_gap = child_gap;
style.layout.padding = Padding {
top: 0.0,
bottom: 0.0,
left: 0.0,
right: 0.0,
};
style.floating = None;
style.border = BorderElementStyle {
color: Color {
r: 0.0,
g: 0.0,
b: 0.0,
a: 0.0,
},
width: BorderWidthStyle {
left: 0.0,
right: 0.0,
top: 0.0,
bottom: 0.0,
between_children: 0.0,
},
};
}
fn measuring_text_config(config: &TextConfig) -> MeasureTextConfig {
MeasureTextConfig {
font_id: config.font_id as usize,
font_size: config.font_size,
letter_spacing: config.letter_spacing,
line_height: config.line_height,
wrap_mode: config.wrap_mode,
alignment: config.alignment,
}
}
impl<'layout, 'frame, Config: TextConfigurable + CotisLayoutCompatible<'frame>>
CotisLayoutRun<'layout, Config>
{
fn create_text_child(
&self,
container_id: ElementId,
layout_direction: LayoutDirection,
child_gap: f32,
measured: Dimensions,
intrinsic_fit: bool,
) -> Config {
let w = measured.width.max(0.0);
let h = measured.height.max(0.0);
let width = if intrinsic_fit {
AxisSizing::Fit(0.0, w)
} else {
AxisSizing::Fixed(w)
};
let height = if intrinsic_fit {
AxisSizing::Fit(h, f32::MAX)
} else {
AxisSizing::Fixed(h)
};
let parent = self
.get_config(container_id)
.expect("[Cotis Layout] text leaf needs set_config before set_leaf_config");
parent.synthetic_text_child(layout_direction, child_gap, width, height)
}
fn push_spacer(&mut self, container_id: ElementId, measured: Dimensions) {
self.new_child_element();
let spacer_el = self.create_text_child(
container_id,
LayoutDirection::LeftToRight,
0.0,
measured,
false,
);
let layout = spacer_el.get_style();
let id = self.open_element_id();
self.set_open_element_config(layout, id, None, spacer_el);
self.close_open_element();
}
fn push_text_fragment(
&mut self,
container_id: ElementId,
source: &Arc<str>,
range: Range<usize>,
text_config: TextConfig,
text_style: TextStyle,
measured: Dimensions,
) {
self.new_child_element();
let word_el = self.create_text_child(
container_id,
LayoutDirection::LeftToRight,
0.0,
measured,
false,
);
let layout = word_el.get_style();
let id = self.open_element_id();
self.set_open_element_config(layout, id, None, word_el);
let wid = self.open_element_id();
self.text_fragments.insert(
wid,
TextDrawPayload::new_from_arc(source, range, text_config, text_style),
);
self.close_open_element();
}
pub(crate) fn apply_text_leaf(&mut self, leaf: TextElementConfig<'frame>) {
let container_id = self.open_element_id();
let source: Arc<str> = Arc::from(leaf.text.as_ref());
#[allow(clippy::clone_on_copy)] let text_style = leaf.style.clone();
let text_config = leaf.config;
let measure_style = measuring_text_config(&text_config);
self.set_open_element_layout_direction(LayoutDirection::TopToBottom);
let word_row_gap = match text_config.wrap_mode {
TextElementConfigWrapMode::Words => {
measure_text_fragment(" ", &measure_style, self.layout_manager.text_dim_fun()).width
}
_ => 0.0,
};
for line_range in newline_line_ranges(source.as_ref()) {
let line_slice_full = &source[line_range.start..line_range.end];
let (line_dims, words_specs) = match text_config.wrap_mode {
TextElementConfigWrapMode::Words => {
let empty_dims = measure_text_fragment(
"",
&measure_style,
self.layout_manager.text_dim_fun(),
);
let pieces = line_word_space_pieces(line_slice_full);
let specs = text_line_child_specs(&pieces);
let dims = if specs.is_empty() {
empty_dims
} else {
let mut sum_w = 0.0f32;
let mut max_h = empty_dims.height;
for spec in &specs {
if let TextLineChildSpec::Word(r) = spec {
let fragment = &line_slice_full[r.clone()];
let d = measure_text_fragment(
fragment,
&measure_style,
self.layout_manager.text_dim_fun(),
);
sum_w += d.width;
max_h = max_h.max(d.height);
}
}
let gap_total = (specs.len().saturating_sub(1)) as f32 * word_row_gap;
Dimensions {
width: sum_w + gap_total,
height: max_h,
}
};
(dims, specs)
}
TextElementConfigWrapMode::Newline | TextElementConfigWrapMode::None => {
let dims = measure_text_fragment(
line_slice_full,
&measure_style,
self.layout_manager.text_dim_fun(),
);
(dims, Vec::new())
}
};
self.new_child_element();
let line_el = self.create_text_child(
container_id,
LayoutDirection::LeftToRight,
word_row_gap,
line_dims,
true,
);
let mut line_layout = line_el.get_style();
line_layout.layout.wrapping = ChildWrapping::Text;
let line_id = self.open_element_id();
self.set_open_element_config(line_layout, line_id, None, line_el);
match text_config.wrap_mode {
TextElementConfigWrapMode::Words => {
let spacer_measured = Dimensions {
width: 0.0,
height: line_dims.height,
};
for spec in words_specs {
match spec {
TextLineChildSpec::Spacer => {
self.push_spacer(container_id, spacer_measured);
}
TextLineChildSpec::Word(w) => {
let global = line_range.start + w.start..line_range.start + w.end;
let word_text = &source[global.clone()];
let measured = measure_text_fragment(
word_text,
&measure_style,
self.layout_manager.text_dim_fun(),
);
self.push_text_fragment(
container_id,
&source,
global,
text_config,
#[allow(clippy::clone_on_copy)]
text_style.clone(),
measured,
);
}
}
}
}
TextElementConfigWrapMode::Newline | TextElementConfigWrapMode::None => {
if !line_range.is_empty() {
let global = line_range.clone();
let measured = measure_text_fragment(
&source[global.clone()],
&measure_style,
self.layout_manager.text_dim_fun(),
);
self.push_text_fragment(
container_id,
&source,
global,
text_config,
#[allow(clippy::clone_on_copy)] text_style.clone(),
measured,
);
}
}
}
self.close_open_element();
}
}
}