From d331389ada13decc046efa655410d52ec27d996f Mon Sep 17 00:00:00 2001 From: Erik Kundt Date: Wed, 8 Mar 2023 16:49:08 +0100 Subject: [PATCH] tui: Implement workspaces container Signed-off-by: Erik Kundt --- radicle-tui/src/app.rs | 67 ++++++++--- radicle-tui/src/ui.rs | 26 +++- radicle-tui/src/ui/components.rs | 200 ++++++++++++++++++++++++++++++- radicle-tui/src/ui/state.rs | 17 +++ radicle-tui/src/ui/theme.rs | 8 ++ radicle-tui/src/ui/widget.rs | 10 ++ 6 files changed, 311 insertions(+), 17 deletions(-) create mode 100644 radicle-tui/src/ui/state.rs diff --git a/radicle-tui/src/app.rs b/radicle-tui/src/app.rs index 9835d452..c2a82523 100644 --- a/radicle-tui/src/app.rs +++ b/radicle-tui/src/app.rs @@ -2,14 +2,20 @@ use std::time::Duration; use anyhow::Result; +use tui_realm_stdlib::Phantom; use tuirealm::application::PollStrategy; +use tuirealm::command::{Cmd, Direction as MoveDirection}; use tuirealm::event::{Event, Key, KeyEvent, KeyModifiers}; use tuirealm::props::{AttrValue, Attribute}; use tuirealm::tui::layout::{Constraint, Direction, Layout}; -use tuirealm::{Application, Component, Frame, NoUserEvent, Sub, SubClause, SubEventClause}; +use tuirealm::{ + Application, Component, Frame, MockComponent, NoUserEvent, Sub, SubClause, SubEventClause, +}; use radicle_tui::ui; -use radicle_tui::ui::components::{GlobalListener, LabeledContainer, PropertyList, ShortcutBar}; +use radicle_tui::ui::components::{ + GlobalListener, LabeledContainer, PropertyList, ShortcutBar, Workspaces, +}; use radicle_tui::ui::theme; use radicle_tui::ui::widget::Widget; @@ -34,7 +40,7 @@ pub enum Message { #[derive(Debug, Eq, PartialEq, Clone, Hash)] pub enum ComponentId { Workspaces, - ShortcutBar, + Shortcuts, GlobalListener, } @@ -54,27 +60,46 @@ impl Tui for App { fn init(&mut self, app: &mut Application) -> Result<()> { let theme = theme::default_dark(); + let dashboard = ui::labeled_container( + &theme, + "about", + ui::property_list( + &theme, + vec![ + ui::property(&theme, "id", &self.id.to_string()), + ui::property(&theme, "name", self.project.name()), + ui::property(&theme, "description", self.project.description()), + ], + ) + .to_boxed(), + ) + .to_boxed(); + app.mount( ComponentId::Workspaces, - ui::labeled_container( + ui::workspaces( &theme, - "about", - ui::property_list( + self.project.name(), + ui::tabs( &theme, vec![ - ui::property(&theme, "id", &self.id.to_string()), - ui::property(&theme, "name", self.project.name()), - ui::property(&theme, "description", self.project.description()), + ui::label("dashboard"), + ui::label("issues"), + ui::label("patches"), ], - ) - .to_boxed(), + ), + vec![ + dashboard, + Box::::default(), + Box::::default(), + ], ) .to_boxed(), vec![], )?; app.mount( - ComponentId::ShortcutBar, + ComponentId::Shortcuts, ui::shortcut_bar( &theme, vec![ @@ -100,7 +125,7 @@ impl Tui for App { )?; // We need to give focus to a component then - app.active(&ComponentId::ShortcutBar)?; + app.active(&ComponentId::Workspaces)?; Ok(()) } @@ -113,7 +138,7 @@ impl Tui for App { let area = frame.size(); let margin_h = 1u16; let shortcuts_h = app - .query(&ComponentId::ShortcutBar, Attribute::Height) + .query(&ComponentId::Shortcuts, Attribute::Height) .ok() .flatten() .unwrap_or(AttrValue::Size(0)) @@ -135,7 +160,7 @@ impl Tui for App { .split(area); app.view(&ComponentId::Workspaces, frame, layout[0]); - app.view(&ComponentId::ShortcutBar, frame, layout[1]); + app.view(&ComponentId::Shortcuts, frame, layout[1]); } fn update(&mut self, app: &mut Application, interval: u64) { @@ -168,6 +193,18 @@ impl Component for Widget { } } +impl Component for Widget { + fn on(&mut self, event: Event) -> Option { + match event { + Event::Keyboard(KeyEvent { code: Key::Tab, .. }) => { + self.perform(Cmd::Move(MoveDirection::Right)); + None + } + _ => None, + } + } +} + impl Component for Widget { fn on(&mut self, _event: Event) -> Option { None diff --git a/radicle-tui/src/ui.rs b/radicle-tui/src/ui.rs index 757f43fb..bbcb2091 100644 --- a/radicle-tui/src/ui.rs +++ b/radicle-tui/src/ui.rs @@ -1,5 +1,6 @@ pub mod components; pub mod layout; +mod state; pub mod theme; pub mod widget; @@ -8,7 +9,7 @@ use tuirealm::{MockComponent, StateValue}; use components::{ ContainerHeader, GlobalListener, Label, LabeledContainer, Property, PropertyList, Shortcut, - ShortcutBar, + ShortcutBar, Tabs, Workspaces, WorkspacesHeader, }; use widget::Widget; @@ -87,3 +88,26 @@ pub fn property_list( Widget::new(property_list) } + +pub fn tabs(theme: &theme::Theme, tabs: Vec>) -> Widget { + let divider = label(&theme.icons.tab_divider.to_string()); + let tabs = Tabs::new(tabs, divider); + + Widget::new(tabs) + .height(1) + .foreground(theme.colors.tabs_fg) + .highlight(theme.colors.tabs_highlighted_fg) +} + +pub fn workspaces( + theme: &theme::Theme, + info: &str, + tabs: Widget, + children: Vec>, +) -> Widget { + let info = label(info).foreground(theme.colors.workspaces_info_fg); + let header = Widget::new(WorkspacesHeader::new(tabs, info)); + let workspaces = Workspaces::new(header, children); + + Widget::new(workspaces) +} diff --git a/radicle-tui/src/ui/components.rs b/radicle-tui/src/ui/components.rs index f4403455..5882a700 100644 --- a/radicle-tui/src/ui/components.rs +++ b/radicle-tui/src/ui/components.rs @@ -3,10 +3,12 @@ use tui_realm_stdlib::Phantom; use tuirealm::command::{Cmd, CmdResult}; use tuirealm::props::{AttrValue, Attribute, Color, Props, Style}; use tuirealm::tui::layout::{Constraint, Direction, Layout, Rect}; -use tuirealm::tui::widgets::Block; +use tuirealm::tui::text::{Span, Spans}; +use tuirealm::tui::widgets::{Block, Tabs as TuiTabs}; use tuirealm::{Frame, MockComponent, State, StateValue}; use super::layout; +use super::state::TabState; use super::widget::{Widget, WidgetComponent}; /// Some user events need to be handled globally (e.g. user presses key `q` to quit @@ -88,6 +90,12 @@ impl WidgetComponent for Label { } } +impl From<&Widget