cranpose_ui/text/
layout_options.rs1use std::hash::{Hash, Hasher};
2
3const MIN_SCALE_DOWN_FONT_SIZE_SP: f32 = 1.0;
4
5#[derive(Clone, Copy, Debug, Default)]
7pub enum TextOverflow {
8 #[default]
10 Clip,
11 Ellipsis,
13 Visible,
15 StartEllipsis,
18 MiddleEllipsis,
21 ScaleDown {
24 min_font_size_sp: f32,
27 },
28}
29
30impl TextOverflow {
31 pub fn normalized(self) -> Self {
33 match self {
34 Self::ScaleDown { min_font_size_sp } => Self::ScaleDown {
35 min_font_size_sp: normalize_scale_down_min_font_size_sp(min_font_size_sp),
36 },
37 other => other,
38 }
39 }
40
41 pub fn scale_down_min_font_size_sp(self) -> Option<f32> {
44 match self.normalized() {
45 Self::ScaleDown { min_font_size_sp } => Some(min_font_size_sp),
46 _ => None,
47 }
48 }
49
50 fn is_ellipsis(self) -> bool {
51 matches!(
52 self,
53 Self::Ellipsis | Self::StartEllipsis | Self::MiddleEllipsis
54 )
55 }
56}
57
58impl PartialEq for TextOverflow {
59 fn eq(&self, other: &Self) -> bool {
60 match ((*self).normalized(), (*other).normalized()) {
61 (Self::Clip, Self::Clip)
62 | (Self::Ellipsis, Self::Ellipsis)
63 | (Self::Visible, Self::Visible)
64 | (Self::StartEllipsis, Self::StartEllipsis)
65 | (Self::MiddleEllipsis, Self::MiddleEllipsis) => true,
66 (
67 Self::ScaleDown {
68 min_font_size_sp: left,
69 },
70 Self::ScaleDown {
71 min_font_size_sp: right,
72 },
73 ) => left.to_bits() == right.to_bits(),
74 _ => false,
75 }
76 }
77}
78
79impl Eq for TextOverflow {}
80
81impl Hash for TextOverflow {
82 fn hash<H: Hasher>(&self, state: &mut H) {
83 match (*self).normalized() {
84 Self::Clip => 0u8.hash(state),
85 Self::Ellipsis => 1u8.hash(state),
86 Self::Visible => 2u8.hash(state),
87 Self::StartEllipsis => 3u8.hash(state),
88 Self::MiddleEllipsis => 4u8.hash(state),
89 Self::ScaleDown { min_font_size_sp } => {
90 5u8.hash(state);
91 min_font_size_sp.to_bits().hash(state);
92 }
93 }
94 }
95}
96
97#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
99pub struct TextLayoutOptions {
100 pub overflow: TextOverflow,
102 pub soft_wrap: bool,
105 pub max_lines: usize,
107 pub min_lines: usize,
109}
110
111impl Default for TextLayoutOptions {
112 fn default() -> Self {
113 Self {
114 overflow: TextOverflow::Clip,
115 soft_wrap: true,
116 max_lines: usize::MAX,
117 min_lines: 1,
118 }
119 }
120}
121
122impl TextLayoutOptions {
123 pub fn normalized(self) -> Self {
130 let min_lines = self.min_lines.max(1);
131 let max_lines = if !self.soft_wrap && self.overflow.is_ellipsis() {
132 1
133 } else {
134 self.max_lines.max(min_lines)
135 };
136 Self {
137 overflow: self.overflow.normalized(),
138 soft_wrap: self.soft_wrap,
139 max_lines,
140 min_lines,
141 }
142 }
143}
144
145fn normalize_scale_down_min_font_size_sp(value: f32) -> f32 {
146 if value.is_finite() && value >= MIN_SCALE_DOWN_FONT_SIZE_SP {
147 value
148 } else {
149 MIN_SCALE_DOWN_FONT_SIZE_SP
150 }
151}
152
153#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
157pub struct TextOptions {
158 pub overflow: TextOverflow,
160 pub soft_wrap: bool,
163 pub max_lines: Option<usize>,
165 pub min_lines: usize,
167}
168
169impl Default for TextOptions {
170 fn default() -> Self {
171 Self {
172 overflow: TextOverflow::Clip,
173 soft_wrap: true,
174 max_lines: None,
175 min_lines: 1,
176 }
177 }
178}
179
180impl From<TextOptions> for TextLayoutOptions {
181 fn from(options: TextOptions) -> Self {
182 Self {
183 overflow: options.overflow,
184 soft_wrap: options.soft_wrap,
185 max_lines: options.max_lines.unwrap_or(usize::MAX),
186 min_lines: options.min_lines,
187 }
188 .normalized()
189 }
190}
191
192impl From<TextLayoutOptions> for TextOptions {
193 fn from(options: TextLayoutOptions) -> Self {
194 let options = options.normalized();
195 Self {
196 overflow: options.overflow,
197 soft_wrap: options.soft_wrap,
198 max_lines: (options.max_lines != usize::MAX).then_some(options.max_lines),
199 min_lines: options.min_lines,
200 }
201 }
202}
203
204#[cfg(test)]
205#[path = "tests/layout_options_tests.rs"]
206mod tests;