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::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::text::{Span, Spans};
use tuirealm::tui::widgets::Block;
use tuirealm::tui::widgets::{Block, Cell, Row};
use tuirealm::{Frame, MockComponent, State, StateValue};
use crate::ui::components::common::label::Label;
use crate::ui::layout;
use crate::ui::ext::HeaderBlock;
use crate::ui::state::TabState;
use crate::ui::widget::{Widget, WidgetComponent};
@ -104,14 +106,24 @@ impl WidgetComponent for Tabs {
}
/// A labeled container header.
#[derive(Clone)]
struct Header {
content: Widget<Label>,
}
#[derive(Default)]
pub struct Header;
impl Header {
pub fn new(content: Widget<Label>) -> Self {
Self { content }
fn content<'a>(spans: Vec<PropValue>) -> Row<'a> {
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
.get_or(Attribute::Display, AttrValue::Flag(true))
.unwrap_flag();
let spacer = Widget::new(Label::default())
.content(AttrValue::String(String::default()))
.to_boxed();
let content = properties
.get_or(
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 {
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);
for (mut shortcut, area) in layout {
shortcut.view(frame, area);
}
let layout = Layout::default()
.direction(Direction::Vertical)
.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 {
pub fn new(content: Widget<Label>, component: Box<dyn MockComponent>) -> Self {
Self {
header: Widget::new(Header::new(content)),
component,
}
pub fn new(header: Widget<Header>, component: Box<dyn MockComponent>) -> Self {
Self { header, component }
}
}
@ -162,13 +197,10 @@ impl WidgetComponent for LabeledContainer {
let display = properties
.get_or(Attribute::Display, AttrValue::Flag(true))
.unwrap_flag();
let background = properties
.get_or(Attribute::Background, AttrValue::Color(Color::Reset))
.unwrap_color();
let header_height = self
.header
.query(Attribute::Height)
.unwrap_or(AttrValue::Size(1))
.unwrap_or(AttrValue::Size(3))
.unwrap_size();
if display {
@ -177,18 +209,22 @@ impl WidgetComponent for LabeledContainer {
.constraints([Constraint::Length(header_height), Constraint::Length(0)].as_ref())
.split(area);
self.header.view(frame, layout[0]);
// Make some space on the left
let inner_layout = Layout::default()
.direction(Direction::Horizontal)
.horizontal_margin(1)
.constraints(vec![Constraint::Length(1), Constraint::Min(0)].as_ref())
.split(layout[1]);
// reverse draw order: child needs to be drawn first?
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]);
self.header.view(frame, layout[0]);
}
}

View File

@ -1,8 +1,9 @@
use radicle::Profile;
use tuirealm::props::{AttrValue, Attribute};
use tuirealm::props::{AttrValue, Attribute, PropPayload, PropValue, TextSpan};
use tuirealm::MockComponent;
use crate::ui;
use crate::ui::components::common::container::Header;
use ui::components::common::container::{GlobalListener, LabeledContainer, Tabs};
use ui::components::common::context::{Shortcut, Shortcuts};
@ -26,17 +27,26 @@ pub fn label(content: &str) -> Widget<Label> {
.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(
theme: &Theme,
title: &str,
component: Box<dyn MockComponent>,
) -> Widget<LabeledContainer> {
let title = label(&format!(" {title} "))
.foreground(theme.colors.default_fg)
.background(theme.colors.labeled_container_bg);
let container = LabeledContainer::new(title, component);
let header = container_header(theme, title);
let container = LabeledContainer::new(header, component);
Widget::new(container).background(theme.colors.labeled_container_bg)
Widget::new(container)
}
pub fn shortcut(theme: &Theme, short: &str, long: &str) -> Widget<Shortcut> {