tui: New labeled container design
This commit is contained in:
parent
e268e75280
commit
b1c92f4a0d
|
|
@ -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]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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> {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue