1use tuirealm::props::{
4 AttrValue, AttrValueRef, Attribute, Borders, LineStatic, QueryResult, Style, TextModifiers,
5 Title,
6};
7use tuirealm::ratatui::text::Line;
8use tuirealm::ratatui::widgets::Block;
9
10use crate::utils::borrow_clone_line;
11
12#[derive(Debug, Clone, PartialEq, Eq)]
20#[non_exhaustive]
21pub struct CommonProps {
22 pub style: Style,
24 pub border_unfocused_style: Style,
26
27 pub border: Option<Borders>,
29 pub title: Option<Title>,
31
32 pub display: bool,
34 pub focused: bool,
36
37 pub always_active: bool,
39}
40
41impl Default for CommonProps {
42 fn default() -> Self {
43 Self {
44 style: Style::default(),
45 border_unfocused_style: Style::default(),
46 border: Option::default(),
47 title: Option::default(),
48 display: true,
49 focused: false,
50 always_active: false,
51 }
52 }
53}
54
55impl CommonProps {
56 pub fn set(&mut self, attr: Attribute, value: AttrValue) -> Option<AttrValue> {
58 match (attr, value) {
59 (Attribute::Style, AttrValue::Style(val)) => self.style = val,
61 (Attribute::Foreground, AttrValue::Color(val)) => self.style = self.style.fg(val),
62 (Attribute::Background, AttrValue::Color(val)) => self.style = self.style.bg(val),
63 (Attribute::UnfocusedBorderStyle, AttrValue::Style(val)) => {
64 self.border_unfocused_style = val
65 }
66 (Attribute::TextProps, AttrValue::TextModifiers(val)) => {
67 self.style = self.style.add_modifier(val)
68 }
69
70 (Attribute::Display, AttrValue::Flag(val)) => self.display = val,
72 (Attribute::Focus, AttrValue::Flag(val)) => self.focused = val,
73 (Attribute::AlwaysActive, AttrValue::Flag(val)) => self.always_active = val,
74
75 (Attribute::Borders, AttrValue::Borders(val)) => self.border = Some(val),
77 (Attribute::Title, AttrValue::Title(val)) => self.title = Some(val),
78
79 (_, value) => return Some(value),
81 }
82
83 None
84 }
85
86 pub fn get<'a>(&'a self, attr: Attribute) -> Option<AttrValueRef<'a>> {
88 match attr {
89 Attribute::Style => Some(AttrValueRef::Style(self.style)),
91 Attribute::Foreground => self.style.fg.map(AttrValueRef::Color),
92 Attribute::Background => self.style.bg.map(AttrValueRef::Color),
93 Attribute::UnfocusedBorderStyle => {
94 Some(AttrValueRef::Style(self.border_unfocused_style))
95 }
96 Attribute::TextProps => Some(AttrValueRef::TextModifiers(self.style.add_modifier)),
97
98 Attribute::Display => Some(AttrValueRef::Flag(self.display)),
100 Attribute::Focus => Some(AttrValueRef::Flag(self.focused)),
101 Attribute::AlwaysActive => Some(AttrValueRef::Flag(self.always_active)),
102
103 Attribute::Borders => self.border.map(AttrValueRef::Borders),
105 Attribute::Title => self.title.as_ref().map(AttrValueRef::Title),
106
107 _ => None,
109 }
110 }
111
112 #[inline]
114 pub fn get_for_query<'a>(&'a self, attr: Attribute) -> Option<QueryResult<'a>> {
115 self.get(attr).map(QueryResult::Borrowed)
116 }
117
118 pub fn get_block(&self) -> Option<Block<'_>> {
122 let borders = self.border?;
123 let title = self.title.as_ref();
124
125 let block = crate::utils::get_block(
126 borders,
127 title,
128 self.is_active(),
129 Some(self.border_unfocused_style),
130 );
131
132 Some(block)
133 }
134
135 pub fn is_active(&self) -> bool {
137 self.always_active || self.focused
138 }
139}
140
141#[derive(Debug, Clone, PartialEq, Eq)]
149#[non_exhaustive]
150pub struct CommonHighlight {
151 pub style: Style,
153 pub symbol: LineStatic,
155}
156
157impl Default for CommonHighlight {
158 fn default() -> Self {
159 Self {
160 style: Style::default().add_modifier(TextModifiers::REVERSED),
161 symbol: LineStatic::default(),
162 }
163 }
164}
165
166impl CommonHighlight {
167 pub fn set(&mut self, attr: Attribute, value: AttrValue) -> Option<AttrValue> {
169 match (attr, value) {
170 (Attribute::HighlightStyle, AttrValue::Style(val)) => self.style = val,
171 (Attribute::HighlightedStr, AttrValue::TextLine(val)) => self.symbol = val,
172
173 (_, value) => return Some(value),
175 }
176
177 None
178 }
179
180 pub fn get<'a>(&'a self, attr: Attribute) -> Option<AttrValueRef<'a>> {
182 match attr {
183 Attribute::HighlightStyle => Some(AttrValueRef::Style(self.style)),
184 Attribute::HighlightedStr => Some(AttrValueRef::TextLine(&self.symbol)),
185
186 _ => None,
188 }
189 }
190
191 #[inline]
193 pub fn get_for_query<'a>(&'a self, attr: Attribute) -> Option<QueryResult<'a>> {
194 self.get(attr).map(QueryResult::Borrowed)
195 }
196
197 pub fn get_symbol(&self) -> Option<Line<'_>> {
198 if self.symbol.spans.is_empty() {
199 None
200 } else {
201 Some(borrow_clone_line(&self.symbol))
202 }
203 }
204
205 #[inline]
206 pub fn get_style(&self, normal_style: Style) -> Style {
207 normal_style.patch(self.style)
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use pretty_assertions::assert_eq;
214 use tuirealm::props::{
215 AttrValue, Attribute, BorderSides, BorderType, Borders, Color, HorizontalAlignment,
216 LineStatic, Style, TextModifiers, Title,
217 };
218 use tuirealm::ratatui::widgets::{Block, TitlePosition};
219
220 use crate::prop_ext::{CommonHighlight, CommonProps};
221
222 #[test]
223 fn common_should_have_expected_defaults() {
224 let props = CommonProps::default();
225
226 assert_eq!(
228 props.get(Attribute::Style).unwrap().unwrap_style(),
229 Style::new()
230 );
231 assert_eq!(
232 props
233 .get(Attribute::UnfocusedBorderStyle)
234 .unwrap()
235 .unwrap_style(),
236 Style::new()
237 );
238 assert!(props.get(Attribute::Foreground).is_none());
239 assert!(props.get(Attribute::Background).is_none());
240 assert_eq!(
241 props
242 .get(Attribute::TextProps)
243 .unwrap()
244 .unwrap_text_modifiers(),
245 TextModifiers::default()
246 );
247
248 assert!(props.get(Attribute::Display).unwrap().unwrap_flag());
249 assert!(!props.get(Attribute::Focus).unwrap().unwrap_flag());
250 assert!(!props.get(Attribute::AlwaysActive).unwrap().unwrap_flag());
251
252 assert!(props.get(Attribute::Borders).is_none());
253 assert!(props.get(Attribute::Title).is_none());
254 }
255
256 #[test]
257 fn common_should_get_set() {
258 let mut props = CommonProps::default();
259
260 props.set(Attribute::Foreground, AttrValue::Color(Color::Black));
262 props.set(Attribute::Background, AttrValue::Color(Color::Gray));
263 props.set(
264 Attribute::TextProps,
265 AttrValue::TextModifiers(TextModifiers::BOLD),
266 );
267
268 assert_eq!(
269 props.get(Attribute::Style).unwrap().unwrap_style(),
270 Style::new()
271 .fg(Color::Black)
272 .bg(Color::Gray)
273 .add_modifier(TextModifiers::BOLD)
274 );
275 assert_eq!(
276 props.get(Attribute::Foreground).unwrap().unwrap_color(),
277 Color::Black
278 );
279 assert_eq!(
280 props.get(Attribute::Background).unwrap().unwrap_color(),
281 Color::Gray
282 );
283 assert_eq!(
284 props
285 .get(Attribute::TextProps)
286 .unwrap()
287 .unwrap_text_modifiers(),
288 TextModifiers::BOLD
289 );
290
291 props.set(
293 Attribute::Style,
294 AttrValue::Style(
295 Style::new()
296 .fg(Color::Blue)
297 .bg(Color::DarkGray)
298 .add_modifier(TextModifiers::DIM),
299 ),
300 );
301
302 assert_eq!(
303 props.get(Attribute::Style).unwrap().unwrap_style(),
304 Style::new()
305 .fg(Color::Blue)
306 .bg(Color::DarkGray)
307 .add_modifier(TextModifiers::DIM)
308 );
309 assert_eq!(
310 props.get(Attribute::Foreground).unwrap().unwrap_color(),
311 Color::Blue
312 );
313 assert_eq!(
314 props.get(Attribute::Background).unwrap().unwrap_color(),
315 Color::DarkGray
316 );
317 assert_eq!(
318 props
319 .get(Attribute::TextProps)
320 .unwrap()
321 .unwrap_text_modifiers(),
322 TextModifiers::DIM
323 );
324
325 props.set(
327 Attribute::UnfocusedBorderStyle,
328 AttrValue::Style(Style::new().add_modifier(TextModifiers::REVERSED)),
329 );
330
331 assert_eq!(
332 props
333 .get(Attribute::UnfocusedBorderStyle)
334 .unwrap()
335 .unwrap_style(),
336 Style::new().add_modifier(TextModifiers::REVERSED)
337 );
338
339 props.set(Attribute::Display, AttrValue::Flag(false));
342 props.set(Attribute::Focus, AttrValue::Flag(true));
343 props.set(Attribute::AlwaysActive, AttrValue::Flag(true));
344
345 assert!(!props.get(Attribute::Display).unwrap().unwrap_flag());
346 assert!(props.get(Attribute::Focus).unwrap().unwrap_flag());
347 assert!(props.get(Attribute::AlwaysActive).unwrap().unwrap_flag());
348
349 props.set(
352 Attribute::Borders,
353 AttrValue::Borders(
354 Borders::default()
355 .color(Color::Black)
356 .modifiers(BorderType::Double)
357 .sides(BorderSides::TOP),
358 ),
359 );
360 props.set(
361 Attribute::Title,
362 AttrValue::Title(
363 Title::default()
364 .content("Hello".into())
365 .alignment(HorizontalAlignment::Center)
366 .position(TitlePosition::Bottom),
367 ),
368 );
369
370 assert_eq!(
371 props.get(Attribute::Borders).unwrap().unwrap_borders(),
372 Borders::default()
373 .color(Color::Black)
374 .modifiers(BorderType::Double)
375 .sides(BorderSides::TOP)
376 );
377 assert_eq!(
378 props.get(Attribute::Title).unwrap().unwrap_title(),
379 &Title::default()
380 .content("Hello".into())
381 .alignment(HorizontalAlignment::Center)
382 .position(TitlePosition::Bottom)
383 );
384 }
385
386 #[test]
387 fn common_should_get_block() {
388 let mut props = CommonProps::default();
389
390 assert!(props.get_block().is_none());
391
392 props.set(
393 Attribute::Borders,
394 AttrValue::Borders(
395 Borders::default()
396 .color(Color::Black)
397 .modifiers(BorderType::Double)
398 .sides(BorderSides::TOP),
399 ),
400 );
401 props.set(
402 Attribute::Title,
403 AttrValue::Title(
404 Title::default()
405 .content("Hello".into())
406 .alignment(HorizontalAlignment::Center)
407 .position(TitlePosition::Bottom),
408 ),
409 );
410
411 let block = props.get_block().unwrap();
413 assert_eq!(
414 block,
415 Block::new()
416 .title_bottom(LineStatic::from("Hello").centered())
417 .borders(BorderSides::TOP)
418 .border_type(BorderType::Double)
419 );
420
421 props.set(Attribute::Focus, AttrValue::Flag(true));
423
424 let block = props.get_block().unwrap();
425 assert_eq!(
426 block,
427 Block::new()
428 .border_style(Style::new().black())
429 .title_bottom(LineStatic::from("Hello").centered())
430 .borders(BorderSides::TOP)
431 .border_type(BorderType::Double)
432 );
433 }
434
435 #[test]
436 fn block_should_be_active_with_alwaysactive() {
437 let mut props = CommonProps::default();
438 props.set(
439 Attribute::Borders,
440 AttrValue::Borders(
441 Borders::default()
442 .color(Color::Black)
443 .modifiers(BorderType::Double)
444 .sides(BorderSides::TOP),
445 ),
446 );
447 props.set(
448 Attribute::Title,
449 AttrValue::Title(
450 Title::default()
451 .content("Hello".into())
452 .alignment(HorizontalAlignment::Center)
453 .position(TitlePosition::Bottom),
454 ),
455 );
456 props.set(
457 Attribute::UnfocusedBorderStyle,
458 AttrValue::Style(Style::new().gray()),
459 );
460 props.set(Attribute::AlwaysActive, AttrValue::Flag(true));
461
462 let block = props.get_block().unwrap();
463
464 assert_eq!(
465 block,
466 Block::new()
467 .border_style(Style::new().black())
468 .title_bottom(LineStatic::from("Hello").centered())
469 .borders(BorderSides::TOP)
470 .border_type(BorderType::Double)
471 );
472 }
473
474 #[test]
475 fn common_highlight_should_have_expected_defaults() {
476 let props = CommonHighlight::default();
477
478 assert_eq!(
480 props.get(Attribute::HighlightStyle).unwrap().unwrap_style(),
481 Style::new().add_modifier(TextModifiers::REVERSED)
482 );
483 assert_eq!(
484 props
485 .get(Attribute::HighlightedStr)
486 .unwrap()
487 .unwrap_textline(),
488 &LineStatic::default()
489 );
490 }
491
492 #[test]
493 fn common_highlight_should_get_set() {
494 let mut props = CommonHighlight::default();
495
496 props.set(
498 Attribute::HighlightStyle,
499 AttrValue::Style(
500 Style::new()
501 .fg(Color::Blue)
502 .add_modifier(TextModifiers::DIM),
503 ),
504 );
505
506 assert_eq!(
507 props.get(Attribute::HighlightStyle).unwrap().unwrap_style(),
508 Style::new()
509 .fg(Color::Blue)
510 .add_modifier(TextModifiers::DIM)
511 );
512
513 props.set(
515 Attribute::HighlightedStr,
516 AttrValue::TextLine(LineStatic::raw(">>")),
517 );
518
519 assert_eq!(
520 props
521 .get(Attribute::HighlightedStr)
522 .unwrap()
523 .unwrap_textline(),
524 &LineStatic::raw(">>")
525 );
526 }
527}