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 style_unfocused: Style,
155 pub symbol: LineStatic,
157}
158
159impl Default for CommonHighlight {
160 fn default() -> Self {
161 Self {
162 style: Style::default().add_modifier(TextModifiers::REVERSED),
163 style_unfocused: Style::default(),
164 symbol: LineStatic::default(),
165 }
166 }
167}
168
169impl CommonHighlight {
170 pub fn set(&mut self, attr: Attribute, value: AttrValue) -> Option<AttrValue> {
172 match (attr, value) {
173 (Attribute::HighlightStyle, AttrValue::Style(val)) => self.style = val,
174 (Attribute::HighlightStyleUnfocused, AttrValue::Style(val)) => {
175 self.style_unfocused = val
176 }
177 (Attribute::HighlightedStr, AttrValue::TextLine(val)) => self.symbol = val,
178
179 (_, value) => return Some(value),
181 }
182
183 None
184 }
185
186 pub fn get<'a>(&'a self, attr: Attribute) -> Option<AttrValueRef<'a>> {
188 match attr {
189 Attribute::HighlightStyle => Some(AttrValueRef::Style(self.style)),
190 Attribute::HighlightStyleUnfocused => Some(AttrValueRef::Style(self.style_unfocused)),
191 Attribute::HighlightedStr => Some(AttrValueRef::TextLine(&self.symbol)),
192
193 _ => None,
195 }
196 }
197
198 #[inline]
200 pub fn get_for_query<'a>(&'a self, attr: Attribute) -> Option<QueryResult<'a>> {
201 self.get(attr).map(QueryResult::Borrowed)
202 }
203
204 pub fn get_symbol(&self) -> Option<Line<'_>> {
206 if self.symbol.spans.is_empty() {
207 None
208 } else {
209 Some(borrow_clone_line(&self.symbol))
210 }
211 }
212
213 #[inline]
215 pub fn get_style(&self, normal_style: Style) -> Style {
216 normal_style.patch(self.style)
217 }
218
219 #[inline]
221 pub fn get_style_focus(&self, normal_style: Style, focus: bool) -> Style {
222 let style = normal_style.patch(self.style);
223
224 if focus {
225 style
226 } else {
227 style.patch(self.style_unfocused)
228 }
229 }
230}
231
232#[cfg(test)]
233mod tests {
234 use pretty_assertions::assert_eq;
235 use tuirealm::props::{
236 AttrValue, Attribute, BorderSides, BorderType, Borders, Color, HorizontalAlignment,
237 LineStatic, Style, TextModifiers, Title,
238 };
239 use tuirealm::ratatui::widgets::{Block, TitlePosition};
240
241 use crate::prop_ext::{CommonHighlight, CommonProps};
242
243 #[test]
244 fn common_should_have_expected_defaults() {
245 let props = CommonProps::default();
246
247 assert_eq!(
249 props.get(Attribute::Style).unwrap().unwrap_style(),
250 Style::new()
251 );
252 assert_eq!(
253 props
254 .get(Attribute::UnfocusedBorderStyle)
255 .unwrap()
256 .unwrap_style(),
257 Style::new()
258 );
259 assert!(props.get(Attribute::Foreground).is_none());
260 assert!(props.get(Attribute::Background).is_none());
261 assert_eq!(
262 props
263 .get(Attribute::TextProps)
264 .unwrap()
265 .unwrap_text_modifiers(),
266 TextModifiers::default()
267 );
268
269 assert!(props.get(Attribute::Display).unwrap().unwrap_flag());
270 assert!(!props.get(Attribute::Focus).unwrap().unwrap_flag());
271 assert!(!props.get(Attribute::AlwaysActive).unwrap().unwrap_flag());
272
273 assert!(props.get(Attribute::Borders).is_none());
274 assert!(props.get(Attribute::Title).is_none());
275 }
276
277 #[test]
278 fn common_should_get_set() {
279 let mut props = CommonProps::default();
280
281 props.set(Attribute::Foreground, AttrValue::Color(Color::Black));
283 props.set(Attribute::Background, AttrValue::Color(Color::Gray));
284 props.set(
285 Attribute::TextProps,
286 AttrValue::TextModifiers(TextModifiers::BOLD),
287 );
288
289 assert_eq!(
290 props.get(Attribute::Style).unwrap().unwrap_style(),
291 Style::new()
292 .fg(Color::Black)
293 .bg(Color::Gray)
294 .add_modifier(TextModifiers::BOLD)
295 );
296 assert_eq!(
297 props.get(Attribute::Foreground).unwrap().unwrap_color(),
298 Color::Black
299 );
300 assert_eq!(
301 props.get(Attribute::Background).unwrap().unwrap_color(),
302 Color::Gray
303 );
304 assert_eq!(
305 props
306 .get(Attribute::TextProps)
307 .unwrap()
308 .unwrap_text_modifiers(),
309 TextModifiers::BOLD
310 );
311
312 props.set(
314 Attribute::Style,
315 AttrValue::Style(
316 Style::new()
317 .fg(Color::Blue)
318 .bg(Color::DarkGray)
319 .add_modifier(TextModifiers::DIM),
320 ),
321 );
322
323 assert_eq!(
324 props.get(Attribute::Style).unwrap().unwrap_style(),
325 Style::new()
326 .fg(Color::Blue)
327 .bg(Color::DarkGray)
328 .add_modifier(TextModifiers::DIM)
329 );
330 assert_eq!(
331 props.get(Attribute::Foreground).unwrap().unwrap_color(),
332 Color::Blue
333 );
334 assert_eq!(
335 props.get(Attribute::Background).unwrap().unwrap_color(),
336 Color::DarkGray
337 );
338 assert_eq!(
339 props
340 .get(Attribute::TextProps)
341 .unwrap()
342 .unwrap_text_modifiers(),
343 TextModifiers::DIM
344 );
345
346 props.set(
348 Attribute::UnfocusedBorderStyle,
349 AttrValue::Style(Style::new().add_modifier(TextModifiers::REVERSED)),
350 );
351
352 assert_eq!(
353 props
354 .get(Attribute::UnfocusedBorderStyle)
355 .unwrap()
356 .unwrap_style(),
357 Style::new().add_modifier(TextModifiers::REVERSED)
358 );
359
360 props.set(Attribute::Display, AttrValue::Flag(false));
363 props.set(Attribute::Focus, AttrValue::Flag(true));
364 props.set(Attribute::AlwaysActive, AttrValue::Flag(true));
365
366 assert!(!props.get(Attribute::Display).unwrap().unwrap_flag());
367 assert!(props.get(Attribute::Focus).unwrap().unwrap_flag());
368 assert!(props.get(Attribute::AlwaysActive).unwrap().unwrap_flag());
369
370 props.set(
373 Attribute::Borders,
374 AttrValue::Borders(
375 Borders::default()
376 .color(Color::Black)
377 .modifiers(BorderType::Double)
378 .sides(BorderSides::TOP),
379 ),
380 );
381 props.set(
382 Attribute::Title,
383 AttrValue::Title(
384 Title::default()
385 .content("Hello".into())
386 .alignment(HorizontalAlignment::Center)
387 .position(TitlePosition::Bottom),
388 ),
389 );
390
391 assert_eq!(
392 props.get(Attribute::Borders).unwrap().unwrap_borders(),
393 Borders::default()
394 .color(Color::Black)
395 .modifiers(BorderType::Double)
396 .sides(BorderSides::TOP)
397 );
398 assert_eq!(
399 props.get(Attribute::Title).unwrap().unwrap_title(),
400 &Title::default()
401 .content("Hello".into())
402 .alignment(HorizontalAlignment::Center)
403 .position(TitlePosition::Bottom)
404 );
405 }
406
407 #[test]
408 fn common_should_get_block() {
409 let mut props = CommonProps::default();
410
411 assert!(props.get_block().is_none());
412
413 props.set(
414 Attribute::Borders,
415 AttrValue::Borders(
416 Borders::default()
417 .color(Color::Black)
418 .modifiers(BorderType::Double)
419 .sides(BorderSides::TOP),
420 ),
421 );
422 props.set(
423 Attribute::Title,
424 AttrValue::Title(
425 Title::default()
426 .content("Hello".into())
427 .alignment(HorizontalAlignment::Center)
428 .position(TitlePosition::Bottom),
429 ),
430 );
431
432 let block = props.get_block().unwrap();
434 assert_eq!(
435 block,
436 Block::new()
437 .title_bottom(LineStatic::from("Hello").centered())
438 .borders(BorderSides::TOP)
439 .border_type(BorderType::Double)
440 );
441
442 props.set(Attribute::Focus, AttrValue::Flag(true));
444
445 let block = props.get_block().unwrap();
446 assert_eq!(
447 block,
448 Block::new()
449 .border_style(Style::new().black())
450 .title_bottom(LineStatic::from("Hello").centered())
451 .borders(BorderSides::TOP)
452 .border_type(BorderType::Double)
453 );
454 }
455
456 #[test]
457 fn block_should_be_active_with_alwaysactive() {
458 let mut props = CommonProps::default();
459 props.set(
460 Attribute::Borders,
461 AttrValue::Borders(
462 Borders::default()
463 .color(Color::Black)
464 .modifiers(BorderType::Double)
465 .sides(BorderSides::TOP),
466 ),
467 );
468 props.set(
469 Attribute::Title,
470 AttrValue::Title(
471 Title::default()
472 .content("Hello".into())
473 .alignment(HorizontalAlignment::Center)
474 .position(TitlePosition::Bottom),
475 ),
476 );
477 props.set(
478 Attribute::UnfocusedBorderStyle,
479 AttrValue::Style(Style::new().gray()),
480 );
481 props.set(Attribute::AlwaysActive, AttrValue::Flag(true));
482
483 let block = props.get_block().unwrap();
484
485 assert_eq!(
486 block,
487 Block::new()
488 .border_style(Style::new().black())
489 .title_bottom(LineStatic::from("Hello").centered())
490 .borders(BorderSides::TOP)
491 .border_type(BorderType::Double)
492 );
493 }
494
495 #[test]
496 fn common_highlight_should_have_expected_defaults() {
497 let props = CommonHighlight::default();
498
499 assert_eq!(
501 props.get(Attribute::HighlightStyle).unwrap().unwrap_style(),
502 Style::new().add_modifier(TextModifiers::REVERSED)
503 );
504 assert_eq!(
505 props
506 .get(Attribute::HighlightStyleUnfocused)
507 .unwrap()
508 .unwrap_style(),
509 Style::new()
510 );
511 assert_eq!(
512 props
513 .get(Attribute::HighlightedStr)
514 .unwrap()
515 .unwrap_textline(),
516 &LineStatic::default()
517 );
518 }
519
520 #[test]
521 fn common_highlight_should_get_set() {
522 let mut props = CommonHighlight::default();
523
524 props.set(
526 Attribute::HighlightStyle,
527 AttrValue::Style(
528 Style::new()
529 .fg(Color::Blue)
530 .add_modifier(TextModifiers::DIM),
531 ),
532 );
533
534 assert_eq!(
535 props.get(Attribute::HighlightStyle).unwrap().unwrap_style(),
536 Style::new()
537 .fg(Color::Blue)
538 .add_modifier(TextModifiers::DIM)
539 );
540
541 props.set(
543 Attribute::HighlightStyleUnfocused,
544 AttrValue::Style(Style::new().remove_modifier(TextModifiers::DIM)),
545 );
546
547 assert_eq!(
548 props
549 .get(Attribute::HighlightStyleUnfocused)
550 .unwrap()
551 .unwrap_style(),
552 Style::new().remove_modifier(TextModifiers::DIM)
553 );
554
555 props.set(
557 Attribute::HighlightedStr,
558 AttrValue::TextLine(LineStatic::raw(">>")),
559 );
560
561 assert_eq!(
562 props
563 .get(Attribute::HighlightedStr)
564 .unwrap()
565 .unwrap_textline(),
566 &LineStatic::raw(">>")
567 );
568 }
569}