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]
9 Clip,
10 Ellipsis,
11 Visible,
12 StartEllipsis,
13 MiddleEllipsis,
14 ScaleDown {
15 min_font_size_sp: f32,
16 },
17}
18
19impl TextOverflow {
20 pub fn normalized(self) -> Self {
21 match self {
22 Self::ScaleDown { min_font_size_sp } => Self::ScaleDown {
23 min_font_size_sp: normalize_scale_down_min_font_size_sp(min_font_size_sp),
24 },
25 other => other,
26 }
27 }
28
29 pub fn scale_down_min_font_size_sp(self) -> Option<f32> {
30 match self.normalized() {
31 Self::ScaleDown { min_font_size_sp } => Some(min_font_size_sp),
32 _ => None,
33 }
34 }
35
36 fn is_ellipsis(self) -> bool {
37 matches!(
38 self,
39 Self::Ellipsis | Self::StartEllipsis | Self::MiddleEllipsis
40 )
41 }
42}
43
44impl PartialEq for TextOverflow {
45 fn eq(&self, other: &Self) -> bool {
46 match ((*self).normalized(), (*other).normalized()) {
47 (Self::Clip, Self::Clip)
48 | (Self::Ellipsis, Self::Ellipsis)
49 | (Self::Visible, Self::Visible)
50 | (Self::StartEllipsis, Self::StartEllipsis)
51 | (Self::MiddleEllipsis, Self::MiddleEllipsis) => true,
52 (
53 Self::ScaleDown {
54 min_font_size_sp: left,
55 },
56 Self::ScaleDown {
57 min_font_size_sp: right,
58 },
59 ) => left.to_bits() == right.to_bits(),
60 _ => false,
61 }
62 }
63}
64
65impl Eq for TextOverflow {}
66
67impl Hash for TextOverflow {
68 fn hash<H: Hasher>(&self, state: &mut H) {
69 match (*self).normalized() {
70 Self::Clip => 0u8.hash(state),
71 Self::Ellipsis => 1u8.hash(state),
72 Self::Visible => 2u8.hash(state),
73 Self::StartEllipsis => 3u8.hash(state),
74 Self::MiddleEllipsis => 4u8.hash(state),
75 Self::ScaleDown { min_font_size_sp } => {
76 5u8.hash(state);
77 min_font_size_sp.to_bits().hash(state);
78 }
79 }
80 }
81}
82
83#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
85pub struct TextLayoutOptions {
86 pub overflow: TextOverflow,
87 pub soft_wrap: bool,
88 pub max_lines: usize,
89 pub min_lines: usize,
90}
91
92impl Default for TextLayoutOptions {
93 fn default() -> Self {
94 Self {
95 overflow: TextOverflow::Clip,
96 soft_wrap: true,
97 max_lines: usize::MAX,
98 min_lines: 1,
99 }
100 }
101}
102
103impl TextLayoutOptions {
104 pub fn normalized(self) -> Self {
111 let min_lines = self.min_lines.max(1);
112 let max_lines = if !self.soft_wrap && self.overflow.is_ellipsis() {
113 1
114 } else {
115 self.max_lines.max(min_lines)
116 };
117 Self {
118 overflow: self.overflow.normalized(),
119 soft_wrap: self.soft_wrap,
120 max_lines,
121 min_lines,
122 }
123 }
124}
125
126fn normalize_scale_down_min_font_size_sp(value: f32) -> f32 {
127 if value.is_finite() && value >= MIN_SCALE_DOWN_FONT_SIZE_SP {
128 value
129 } else {
130 MIN_SCALE_DOWN_FONT_SIZE_SP
131 }
132}
133
134#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
138pub struct TextOptions {
139 pub overflow: TextOverflow,
140 pub soft_wrap: bool,
141 pub max_lines: Option<usize>,
142 pub min_lines: usize,
143}
144
145impl Default for TextOptions {
146 fn default() -> Self {
147 Self {
148 overflow: TextOverflow::Clip,
149 soft_wrap: true,
150 max_lines: None,
151 min_lines: 1,
152 }
153 }
154}
155
156impl From<TextOptions> for TextLayoutOptions {
157 fn from(options: TextOptions) -> Self {
158 Self {
159 overflow: options.overflow,
160 soft_wrap: options.soft_wrap,
161 max_lines: options.max_lines.unwrap_or(usize::MAX),
162 min_lines: options.min_lines,
163 }
164 .normalized()
165 }
166}
167
168impl From<TextLayoutOptions> for TextOptions {
169 fn from(options: TextLayoutOptions) -> Self {
170 let options = options.normalized();
171 Self {
172 overflow: options.overflow,
173 soft_wrap: options.soft_wrap,
174 max_lines: (options.max_lines != usize::MAX).then_some(options.max_lines),
175 min_lines: options.min_lines,
176 }
177 }
178}
179
180#[cfg(test)]
181#[path = "tests/layout_options_tests.rs"]
182mod tests;