tui: New labeled container design

This commit is contained in:
Erik Kundt 2023-04-21 16:20:22 +02:00 committed by Alexis Sellier
parent e268e75280
commit b1c92f4a0d
No known key found for this signature in database
2 changed files with 81 additions and 35 deletions

View File

@ -1,12 +1,14 @@
use tuirealm::command::{Cmd, CmdResult}; use tuirealm::command::{Cmd, CmdResult};
use tuirealm::props::{AttrValue, Attribute, Color, Props, Style}; use tuirealm::props::{
AttrValue, Attribute, BorderSides, BorderType, Color, PropPayload, PropValue, Props, Style,
};
use tuirealm::tui::layout::{Constraint, Direction, Layout, Rect}; use tuirealm::tui::layout::{Constraint, Direction, Layout, Rect};
use tuirealm::tui::text::{Span, Spans}; use tuirealm::tui::text::{Span, Spans};
use tuirealm::tui::widgets::Block; use tuirealm::tui::widgets::{Block, Cell, Row};
use tuirealm::{Frame, MockComponent, State, StateValue}; use tuirealm::{Frame, MockComponent, State, StateValue};
use crate::ui::components::common::label::Label; use crate::ui::components::common::label::Label;
use crate::ui::layout; use crate::ui::ext::HeaderBlock;
use crate::ui::state::TabState; use crate::ui::state::TabState;
use crate::ui::widget::{Widget, WidgetComponent}; use crate::ui::widget::{Widget, WidgetComponent};
@ -104,14 +106,24 @@ impl WidgetComponent for Tabs {
} }
/// A labeled container header. /// A labeled container header.
#[derive(Clone)] #[derive(Default)]
struct Header { pub struct Header;
content: Widget<Label>,
}
impl Header { impl Header {
pub fn new(content: Widget<Label>) -> Self { fn content<'a>(spans: Vec<PropValue>) -> Row<'a> {
Self { content } Row::new(
spans
.iter()
.map(|span| Cell::from(span.clone().unwrap_text_span().content))
.collect::<Vec<_>>(),
)
}
fn widths(widths: Vec<PropValue>) -> Vec<Constraint> {
widths
.iter()
.map(|prop| Constraint::Percentage(prop.clone().unwrap_u16()))
.collect()
} }
} }
@ -120,17 +132,43 @@ impl WidgetComponent for Header {
let display = properties let display = properties
.get_or(Attribute::Display, AttrValue::Flag(true)) .get_or(Attribute::Display, AttrValue::Flag(true))
.unwrap_flag(); .unwrap_flag();
let spacer = Widget::new(Label::default()) let content = properties
.content(AttrValue::String(String::default())) .get_or(
.to_boxed(); Attribute::Content,
AttrValue::Payload(PropPayload::Vec(vec![])),
)
.unwrap_payload()
.unwrap_vec();
let widths = properties
.get_or(
Attribute::Custom("widths"),
AttrValue::Payload(PropPayload::Vec(vec![])),
)
.unwrap_payload()
.unwrap_vec();
if display { if display {
let labels: Vec<Box<dyn MockComponent>> = vec![self.content.clone().to_boxed(), spacer]; let block = HeaderBlock::default()
.borders(BorderSides::all())
.border_style(Style::default().fg(Color::Rgb(48, 48, 48)))
.border_type(BorderType::Rounded);
frame.render_widget(block, area);
let layout = layout::h_stack(labels, area); let layout = Layout::default()
for (mut shortcut, area) in layout { .direction(Direction::Vertical)
shortcut.view(frame, area); .constraints(vec![Constraint::Min(1)])
} .vertical_margin(1)
.horizontal_margin(1)
.split(area);
let header = Self::content(content);
let widths = Self::widths(widths);
let table = tuirealm::tui::widgets::Table::new(vec![])
.column_spacing(3u16)
.header(header)
.widths(&widths);
frame.render_widget(table, layout[0]);
} }
} }
@ -149,11 +187,8 @@ pub struct LabeledContainer {
} }
impl LabeledContainer { impl LabeledContainer {
pub fn new(content: Widget<Label>, component: Box<dyn MockComponent>) -> Self { pub fn new(header: Widget<Header>, component: Box<dyn MockComponent>) -> Self {
Self { Self { header, component }
header: Widget::new(Header::new(content)),
component,
}
} }
} }
@ -162,13 +197,10 @@ impl WidgetComponent for LabeledContainer {
let display = properties let display = properties
.get_or(Attribute::Display, AttrValue::Flag(true)) .get_or(Attribute::Display, AttrValue::Flag(true))
.unwrap_flag(); .unwrap_flag();
let background = properties
.get_or(Attribute::Background, AttrValue::Color(Color::Reset))
.unwrap_color();
let header_height = self let header_height = self
.header .header
.query(Attribute::Height) .query(Attribute::Height)
.unwrap_or(AttrValue::Size(1)) .unwrap_or(AttrValue::Size(3))
.unwrap_size(); .unwrap_size();
if display { if display {
@ -177,18 +209,22 @@ impl WidgetComponent for LabeledContainer {
.constraints([Constraint::Length(header_height), Constraint::Length(0)].as_ref()) .constraints([Constraint::Length(header_height), Constraint::Length(0)].as_ref())
.split(area); .split(area);
self.header.view(frame, layout[0]);
// Make some space on the left // Make some space on the left
let inner_layout = Layout::default() let inner_layout = Layout::default()
.direction(Direction::Horizontal) .direction(Direction::Horizontal)
.horizontal_margin(1)
.constraints(vec![Constraint::Length(1), Constraint::Min(0)].as_ref()) .constraints(vec![Constraint::Length(1), Constraint::Min(0)].as_ref())
.split(layout[1]); .split(layout[1]);
// reverse draw order: child needs to be drawn first? // reverse draw order: child needs to be drawn first?
self.component.view(frame, inner_layout[1]); self.component.view(frame, inner_layout[1]);
let block = Block::default().style(Style::default().bg(background)); let block = Block::default()
.borders(BorderSides::BOTTOM | BorderSides::LEFT | BorderSides::RIGHT)
.border_style(Style::default().fg(Color::Rgb(48, 48, 48)))
.border_type(BorderType::Rounded);
frame.render_widget(block, layout[1]); frame.render_widget(block, layout[1]);
self.header.view(frame, layout[0]);
} }
} }

View File

@ -1,8 +1,9 @@
use radicle::Profile; use radicle::Profile;
use tuirealm::props::{AttrValue, Attribute}; use tuirealm::props::{AttrValue, Attribute, PropPayload, PropValue, TextSpan};
use tuirealm::MockComponent; use tuirealm::MockComponent;
use crate::ui; use crate::ui;
use crate::ui::components::common::container::Header;
use ui::components::common::container::{GlobalListener, LabeledContainer, Tabs}; use ui::components::common::container::{GlobalListener, LabeledContainer, Tabs};
use ui::components::common::context::{Shortcut, Shortcuts}; use ui::components::common::context::{Shortcut, Shortcuts};
@ -26,17 +27,26 @@ pub fn label(content: &str) -> Widget<Label> {
.width(width) .width(width)
} }
pub fn container_header(theme: &Theme, label: &str) -> Widget<Header> {
let content = AttrValue::Payload(PropPayload::Vec(vec![PropValue::TextSpan(
TextSpan::from(&format!(" {label} ")).fg(theme.colors.default_fg),
)]));
let widths = AttrValue::Payload(PropPayload::Vec(vec![PropValue::U16(100)]));
Widget::new(Header::default())
.content(content)
.custom("widths", widths)
}
pub fn labeled_container( pub fn labeled_container(
theme: &Theme, theme: &Theme,
title: &str, title: &str,
component: Box<dyn MockComponent>, component: Box<dyn MockComponent>,
) -> Widget<LabeledContainer> { ) -> Widget<LabeledContainer> {
let title = label(&format!(" {title} ")) let header = container_header(theme, title);
.foreground(theme.colors.default_fg) let container = LabeledContainer::new(header, component);
.background(theme.colors.labeled_container_bg);
let container = LabeledContainer::new(title, component);
Widget::new(container).background(theme.colors.labeled_container_bg) Widget::new(container)
} }
pub fn shortcut(theme: &Theme, short: &str, long: &str) -> Widget<Shortcut> { pub fn shortcut(theme: &Theme, short: &str, long: &str) -> Widget<Shortcut> {