flux_tui/components/
textblock.rs1use unicode_width::UnicodeWidthStr;
2
3use super::*;
4use crate::canvas::*;
5
6#[derive(Default)]
10pub struct TextBlock {
11 base: WidgetBase,
12 text: String,
13 text_alignment: HorizontalAlignment,
14 has_focus: bool,
15 line_wrap: bool,
16 focus_color: Option<Color>,
17}
18
19impl TextBlock {
20 pub const LINE_BREAK: char = '\n';
21 pub const WORD_BREAK: char = ' ';
22
23 pub fn new(text: String) -> Self {
24 Self {
25 text,
26 ..Default::default()
27 }
28 }
29
30 pub fn get_text(&self) -> &str {
32 &self.text
33 }
34
35 pub fn set_text(&mut self, text: &str) {
37 self.text.clear();
38 self.text.push_str(text);
39 }
40
41 pub fn set_text_alignment(&mut self, alignment: HorizontalAlignment) {
43 self.text_alignment = alignment;
44 }
45
46 pub fn set_line_wrapping(&mut self, wrapping: bool) {
48 self.line_wrap = wrapping;
49 }
50
51 pub fn set_focus_color(&mut self, color: Option<Color>) {
52 self.focus_color = color;
53 }
54}
55
56impl Window for TextBlock {
57 fn render(&self, canvas: &mut Canvas) {
58 let fg = match self.base.enabled {
59 true => match self.has_focus {
60 true => self.focus_color,
61 false => None,
62 },
63 false => self.base.colors.disabled,
64 };
65
66 let mut row_index = 0;
67 let mut row = canvas.get_row_variable_width(row_index).unwrap();
68
69 let space = Grapheme::from(&Self::WORD_BREAK.to_string()).unwrap();
70 let dot3 = VariableWidthGlyphRow::DOT3_REPLACEMENT;
71
72 match self.line_wrap {
73 true => {
74 for ln in self.text.split(Self::LINE_BREAK) {
75 let words = ln.split(Self::WORD_BREAK).collect::<Vec<_>>();
76 let mut counter = 0;
77 while counter < words.len() {
78 let mut word_count = 0;
79 let mut text_w = 0;
80 for word in words[counter..].iter() {
81 text_w += (word_count > 0) as TSize;
82 let width = word.width() as TSize;
83 if (text_w + width) > row.width() {
84 text_w = text_w.checked_sub(1).unwrap_or_default();
85 break;
86 }
87 text_w += width;
88 word_count += 1;
89 }
90
91 if word_count == 0 {
92 row.add_string(words[counter], fg, None, Style::None, Some(dot3))
93 .unwrap();
94 counter += 1;
95 }
96 else {
97 match self.text_alignment {
98 HorizontalAlignment::Left => {}
99 HorizontalAlignment::Center => {
100 row.skip(
101 row.width().checked_sub(text_w).unwrap_or_default() / 2,
102 );
103 }
104 HorizontalAlignment::Right => {
105 row.skip(row.width().checked_sub(text_w).unwrap_or_default());
106 }
107 }
108 for n in 0..word_count {
109 row.add_string(words[counter + n], fg, None, Style::None, None)
110 .unwrap();
111 if n != word_count - 1 {
112 row.add_grapheme(space, fg, None, Style::None).unwrap();
113 }
114 }
115 }
116
117 counter += word_count;
118 row_index += 1;
119 row = match canvas.get_row_variable_width(row_index) {
120 Some(l) => l,
121 None => break,
122 };
123 }
124
125 row_index += 1;
126 row = match canvas.get_row_variable_width(row_index) {
127 Some(l) => l,
128 None => break,
129 };
130 }
131 }
132 false => {
133 for ln in self.text.split(Self::LINE_BREAK) {
134 let text_w = ln.width() as TSize;
135 match self.text_alignment {
136 HorizontalAlignment::Left => {}
137 HorizontalAlignment::Center => {
138 row.skip(row.width().checked_sub(text_w).unwrap_or_default() / 2);
139 }
140 HorizontalAlignment::Right => {
141 row.skip(row.width().checked_sub(text_w).unwrap_or_default());
142 }
143 }
144 row.add_string(ln, fg, None, Style::None, Some(dot3))
145 .unwrap();
146 row_index += 1;
147 row = match canvas.get_row_variable_width(row_index) {
148 Some(l) => l,
149 None => break,
150 };
151 }
152 }
153 }
154 }
155
156 fn handle_event(&mut self, event: &mut WindowEvent) {
157 match event.raw() {
158 Event::FocusGained => self.has_focus = true,
159 Event::FocusLost => self.has_focus = false,
160 _ => {}
161 }
162 }
163
164 fn is_enabled(&self) -> bool {
165 self.base.enabled
166 }
167}
168
169impl WindowLayout for TextBlock {
170 fn desired_size(&self, available_size: TPoint) -> TPoint {
171 self.base.desired_size(available_size)
172 }
173
174 fn border(&self) -> BorderStyle {
175 self.base.border
176 }
177
178 fn alignment(&self) -> (HorizontalAlignment, VerticalAlignment) {
179 self.base.alignment
180 }
181
182 fn is_visible(&self) -> bool {
183 self.base.visibility
184 }
185
186 fn margin(&self) -> Thickness {
187 self.base.margin
188 }
189}
190
191impl HasWindowUID for TextBlock {
192 fn uid(&self) -> WindowUID {
193 self.base.uid
194 }
195}
196
197impl Widget for TextBlock {
198 fn set_visibility(&mut self, visibility: bool) {
199 self.base.visibility = visibility;
200 self.provoke_changed_property(WindowProperty::IsVisible);
201 }
202
203 fn set_width(&mut self, width: Size) {
204 self.base.size.x = width;
205 self.provoke_changed_property(WindowProperty::Size);
206 }
207
208 fn set_height(&mut self, height: Size) {
209 self.base.size.y = height;
210 self.provoke_changed_property(WindowProperty::Size);
211 }
212
213 fn set_margin(&mut self, margin: Thickness) {
214 self.base.margin = margin;
215 self.provoke_changed_property(WindowProperty::Margin);
216 }
217
218 fn set_border(&mut self, border: BorderStyle) {
219 self.base.border = border;
220 self.provoke_changed_property(WindowProperty::Border);
221 }
222
223 fn set_alignment(&mut self, horizontal: HorizontalAlignment, vertical: VerticalAlignment) {
224 self.base.alignment = (horizontal, vertical);
225 self.provoke_changed_property(WindowProperty::Alignment);
226 }
227
228 fn set_enabled_state(&mut self, is_enabled: bool) {
229 self.base.enabled = is_enabled;
230 }
231
232 fn set_width_constraint(&mut self, width: SizeConstraint) {
233 self.base.constraints.x = width;
234 self.provoke_changed_property(WindowProperty::Size);
235 }
236
237 fn set_height_constraint(&mut self, height: SizeConstraint) {
238 self.base.constraints.y = height;
239 self.provoke_changed_property(WindowProperty::Size);
240 }
241}
242
243impl WidgetColors for TextBlock {
244 fn set_disabled_color(&mut self, color: Option<Color>) {
245 self.base.colors.disabled = color;
246 }
247
248 fn set_base_fg_color(&mut self, color: Option<Color>) {
249 self.base.colors.base_fg = color;
250 }
251
252 fn set_base_bg_color(&mut self, color: Option<Color>) {
253 self.base.colors.base_bg = color;
254 }
255}