tui: Move shortcuts to root widget

This commit is contained in:
Erik Kundt 2023-04-18 18:06:46 +02:00 committed by Alexis Sellier
parent 95b0029267
commit e13bf0b75b
No known key found for this signature in database
4 changed files with 172 additions and 92 deletions

View File

@ -4,7 +4,6 @@ use tui_realm_stdlib::Phantom;
use tuirealm::application::PollStrategy; use tuirealm::application::PollStrategy;
use tuirealm::command::{Cmd, CmdResult, Direction as MoveDirection}; use tuirealm::command::{Cmd, CmdResult, Direction as MoveDirection};
use tuirealm::event::{Event, Key, KeyEvent}; use tuirealm::event::{Event, Key, KeyEvent};
use tuirealm::props::{AttrValue, Attribute};
use tuirealm::{Application, Frame, MockComponent, NoUserEvent, State, StateValue}; use tuirealm::{Application, Frame, MockComponent, NoUserEvent, State, StateValue};
use radicle_tui::cob::patch::{self}; use radicle_tui::cob::patch::{self};
@ -40,7 +39,6 @@ pub enum PatchCid {
Navigation, Navigation,
Activity, Activity,
Files, Files,
Context,
} }
/// All component ids known to this application. /// All component ids known to this application.
@ -48,7 +46,6 @@ pub enum PatchCid {
pub enum Cid { pub enum Cid {
Home(HomeCid), Home(HomeCid),
Patch(PatchCid), Patch(PatchCid),
Shortcuts,
GlobalListener, GlobalListener,
} }
@ -232,15 +229,6 @@ impl ViewPage for Home {
let issue_browser = ui::issue_browser(theme).to_boxed(); let issue_browser = ui::issue_browser(theme).to_boxed();
let patch_browser = ui::patch_browser(theme, &context.patches, &context.profile).to_boxed(); let patch_browser = ui::patch_browser(theme, &context.patches, &context.profile).to_boxed();
let shortcuts = ui::shortcuts(
theme,
vec![
ui::shortcut(theme, "tab", "section"),
ui::shortcut(theme, "q", "quit"),
],
)
.to_boxed();
app.remount( app.remount(
Cid::Home(HomeCid::Navigation), Cid::Home(HomeCid::Navigation),
navigation, navigation,
@ -251,7 +239,6 @@ impl ViewPage for Home {
app.remount(Cid::Home(HomeCid::IssueBrowser), issue_browser, vec![])?; app.remount(Cid::Home(HomeCid::IssueBrowser), issue_browser, vec![])?;
app.remount(Cid::Home(HomeCid::PatchBrowser), patch_browser, vec![])?; app.remount(Cid::Home(HomeCid::PatchBrowser), patch_browser, vec![])?;
app.remount(Cid::Shortcuts, shortcuts, vec![])?;
Ok(()) Ok(())
} }
@ -269,17 +256,10 @@ impl ViewPage for Home {
fn view(&mut self, app: &mut Application<Cid, Message, NoUserEvent>, frame: &mut Frame) { fn view(&mut self, app: &mut Application<Cid, Message, NoUserEvent>, frame: &mut Frame) {
let area = frame.size(); let area = frame.size();
let navigation_h = 2u16; let navigation_h = 2u16;
let shortcuts_h = app let layout = layout::default_page(area, navigation_h);
.query(&Cid::Shortcuts, Attribute::Height)
.ok()
.flatten()
.unwrap_or(AttrValue::Size(0))
.unwrap_size();
let layout = layout::default_page(area, navigation_h, shortcuts_h);
app.view(&Cid::Home(HomeCid::Navigation), frame, layout[0]); app.view(&Cid::Home(HomeCid::Navigation), frame, layout[0]);
app.view(&self.active_component, frame, layout[1]); app.view(&self.active_component, frame, layout[1]);
app.view(&Cid::Shortcuts, frame, layout[2]);
} }
fn activate(&self, app: &mut Application<Cid, Message, NoUserEvent>) -> Result<()> { fn activate(&self, app: &mut Application<Cid, Message, NoUserEvent>) -> Result<()> {
@ -312,18 +292,8 @@ impl ViewPage for PatchView {
) -> Result<()> { ) -> Result<()> {
if let Some((id, patch)) = context.patches.get(context.selected_patch) { if let Some((id, patch)) = context.patches.get(context.selected_patch) {
let navigation = ui::patch_navigation(theme).to_boxed(); let navigation = ui::patch_navigation(theme).to_boxed();
let activity = ui::patch_activity(theme).to_boxed(); let activity = ui::patch_activity(theme, (*id, patch), &context.profile).to_boxed();
let files = ui::patch_files(theme).to_boxed(); let files = ui::patch_files(theme, (*id, patch), &context.profile).to_boxed();
let context = ui::patch_context(theme, (*id, patch), &context.profile).to_boxed();
let shortcuts = ui::shortcuts(
theme,
vec![
ui::shortcut(theme, "esc", "back"),
ui::shortcut(theme, "tab", "section"),
ui::shortcut(theme, "q", "quit"),
],
)
.to_boxed();
app.remount( app.remount(
Cid::Patch(PatchCid::Navigation), Cid::Patch(PatchCid::Navigation),
@ -332,8 +302,6 @@ impl ViewPage for PatchView {
)?; )?;
app.remount(Cid::Patch(PatchCid::Activity), activity, vec![])?; app.remount(Cid::Patch(PatchCid::Activity), activity, vec![])?;
app.remount(Cid::Patch(PatchCid::Files), files, vec![])?; app.remount(Cid::Patch(PatchCid::Files), files, vec![])?;
app.remount(Cid::Patch(PatchCid::Context), context, vec![])?;
app.remount(Cid::Shortcuts, shortcuts, vec![])?;
} }
Ok(()) Ok(())
} }
@ -351,19 +319,11 @@ impl ViewPage for PatchView {
fn view(&mut self, app: &mut Application<Cid, Message, NoUserEvent>, frame: &mut Frame) { fn view(&mut self, app: &mut Application<Cid, Message, NoUserEvent>, frame: &mut Frame) {
let area = frame.size(); let area = frame.size();
let navigation_h = 2u16; let navigation_h = 2u16;
let context_h = 1u16;
let shortcuts_h = app let layout = layout::default_page(area, navigation_h);
.query(&Cid::Shortcuts, Attribute::Height)
.ok()
.flatten()
.unwrap_or(AttrValue::Size(0))
.unwrap_size();
let layout = layout::page_with_context(area, navigation_h, context_h, shortcuts_h);
app.view(&Cid::Patch(PatchCid::Navigation), frame, layout[0]); app.view(&Cid::Patch(PatchCid::Navigation), frame, layout[0]);
app.view(&self.active_component, frame, layout[1]); app.view(&self.active_component, frame, layout[1]);
app.view(&Cid::Patch(PatchCid::Context), frame, layout[2]);
app.view(&Cid::Shortcuts, frame, layout[3]);
} }
fn activate(&self, app: &mut Application<Cid, Message, NoUserEvent>) -> Result<()> { fn activate(&self, app: &mut Application<Cid, Message, NoUserEvent>) -> Result<()> {

View File

@ -122,8 +122,16 @@ pub fn table(theme: &theme::Theme, items: &[impl List], profile: &Profile) -> Wi
} }
pub fn issue_browser(theme: &theme::Theme) -> Widget<IssueBrowser> { pub fn issue_browser(theme: &theme::Theme) -> Widget<IssueBrowser> {
let shortcuts = shortcuts(
theme,
vec![
shortcut(theme, "tab", "section"),
shortcut(theme, "q", "quit"),
],
);
let not_implemented = label("not implemented").foreground(theme.colors.default_fg); let not_implemented = label("not implemented").foreground(theme.colors.default_fg);
let browser = IssueBrowser::new(not_implemented); let browser = IssueBrowser::new(not_implemented, shortcuts);
Widget::new(browser) Widget::new(browser)
} }
@ -133,6 +141,16 @@ pub fn patch_browser(
items: &[(PatchId, Patch)], items: &[(PatchId, Patch)],
profile: &Profile, profile: &Profile,
) -> Widget<Browser<(PatchId, Patch)>> { ) -> Widget<Browser<(PatchId, Patch)>> {
let shortcuts = shortcuts(
theme,
vec![
shortcut(theme, "tab", "section"),
shortcut(theme, "↑/↓", "navigate"),
shortcut(theme, "enter", "show"),
shortcut(theme, "q", "quit"),
],
);
let widths = AttrValue::Payload(PropPayload::Vec(vec![ let widths = AttrValue::Payload(PropPayload::Vec(vec![
PropValue::U16(2), PropValue::U16(2),
PropValue::U16(43), PropValue::U16(43),
@ -153,21 +171,51 @@ pub fn patch_browser(
let table = table(theme, items, profile) let table = table(theme, items, profile)
.custom("widths", widths) .custom("widths", widths)
.custom("header", header); .custom("header", header);
let browser: Browser<(PatchId, Patch)> = Browser::new(table); let browser: Browser<(PatchId, Patch)> = Browser::new(table, shortcuts);
Widget::new(browser) Widget::new(browser)
} }
pub fn patch_activity(theme: &theme::Theme) -> Widget<PatchActivity> { pub fn patch_activity(
theme: &theme::Theme,
patch: (PatchId, &Patch),
profile: &Profile,
) -> Widget<PatchActivity> {
let (id, patch) = patch;
let shortcuts = shortcuts(
theme,
vec![
shortcut(theme, "esc", "back"),
shortcut(theme, "tab", "section"),
shortcut(theme, "q", "quit"),
],
);
let context = patch_context(theme, (id, patch), profile);
let not_implemented = label("not implemented").foreground(theme.colors.default_fg); let not_implemented = label("not implemented").foreground(theme.colors.default_fg);
let activity = PatchActivity::new(not_implemented); let activity = PatchActivity::new(not_implemented, context, shortcuts);
Widget::new(activity) Widget::new(activity)
} }
pub fn patch_files(theme: &theme::Theme) -> Widget<PatchFiles> { pub fn patch_files(
theme: &theme::Theme,
patch: (PatchId, &Patch),
profile: &Profile,
) -> Widget<PatchFiles> {
let (id, patch) = patch;
let shortcuts = shortcuts(
theme,
vec![
shortcut(theme, "esc", "back"),
shortcut(theme, "tab", "section"),
shortcut(theme, "q", "quit"),
],
);
let context = patch_context(theme, (id, patch), profile);
let not_implemented = label("not implemented").foreground(theme.colors.default_fg); let not_implemented = label("not implemented").foreground(theme.colors.default_fg);
let files = PatchFiles::new(not_implemented); let files = PatchFiles::new(not_implemented, context, shortcuts);
Widget::new(files) Widget::new(files)
} }
@ -198,7 +246,7 @@ pub fn patch_context(
.background(Color::Rgb(50, 50, 50)); .background(Color::Rgb(50, 50, 50));
let context_bar = ContextBar::new(context, id, author, title, comments); let context_bar = ContextBar::new(context, id, author, title, comments);
Widget::new(context_bar) Widget::new(context_bar).height(1)
} }
pub fn home_navigation(theme: &theme::Theme) -> Widget<Tabs> { pub fn home_navigation(theme: &theme::Theme) -> Widget<Tabs> {
@ -226,7 +274,14 @@ pub fn dashboard(theme: &theme::Theme, id: &Id, project: &Project) -> Widget<Das
) )
.to_boxed(), .to_boxed(),
); );
let dashboard = Dashboard::new(about); let shortcuts = shortcuts(
theme,
vec![
shortcut(theme, "tab", "section"),
shortcut(theme, "q", "quit"),
],
);
let dashboard = Dashboard::new(about, shortcuts);
Widget::new(dashboard) Widget::new(dashboard)
} }

View File

@ -2,25 +2,28 @@ use std::marker::PhantomData;
use tuirealm::command::{Cmd, CmdResult}; use tuirealm::command::{Cmd, CmdResult};
use tuirealm::props::Props; use tuirealm::props::Props;
use tuirealm::tui::layout::{Constraint, Direction, Layout, Rect}; use tuirealm::tui::layout::Rect;
use tuirealm::{AttrValue, Attribute, Frame, MockComponent, State}; use tuirealm::{AttrValue, Attribute, Frame, MockComponent, State};
use crate::ui::layout; use crate::ui::layout;
use crate::ui::widget::{Widget, WidgetComponent}; use crate::ui::widget::{Widget, WidgetComponent};
use super::container::LabeledContainer; use super::container::LabeledContainer;
use super::context::{ContextBar, Shortcuts};
use super::label::Label; use super::label::Label;
use super::list::{List, Table}; use super::list::{List, Table};
pub struct Browser<T> { pub struct Browser<T> {
list: Widget<Table>, list: Widget<Table>,
shortcuts: Widget<Shortcuts>,
phantom: PhantomData<T>, phantom: PhantomData<T>,
} }
impl<T: List> Browser<T> { impl<T: List> Browser<T> {
pub fn new(list: Widget<Table>) -> Self { pub fn new(list: Widget<Table>, shortcuts: Widget<Shortcuts>) -> Self {
Self { Self {
list, list,
shortcuts,
phantom: PhantomData, phantom: PhantomData,
} }
} }
@ -28,12 +31,15 @@ impl<T: List> Browser<T> {
impl<T: List> WidgetComponent for Browser<T> { impl<T: List> WidgetComponent for Browser<T> {
fn view(&mut self, _properties: &Props, frame: &mut Frame, area: Rect) { fn view(&mut self, _properties: &Props, frame: &mut Frame, area: Rect) {
let layout = Layout::default() let shortcuts_h = self
.direction(Direction::Vertical) .shortcuts
.constraints(vec![Constraint::Min(1)].as_ref()) .query(Attribute::Height)
.split(area); .unwrap_or(AttrValue::Size(0))
.unwrap_size();
let layout = layout::root_component(area, shortcuts_h);
self.list.view(frame, layout[0]); self.list.view(frame, layout[0]);
self.shortcuts.view(frame, layout[1]);
} }
fn state(&self) -> State { fn state(&self) -> State {
@ -47,20 +53,25 @@ impl<T: List> WidgetComponent for Browser<T> {
pub struct Dashboard { pub struct Dashboard {
about: Widget<LabeledContainer>, about: Widget<LabeledContainer>,
shortcuts: Widget<Shortcuts>,
} }
impl Dashboard { impl Dashboard {
pub fn new(about: Widget<LabeledContainer>) -> Self { pub fn new(about: Widget<LabeledContainer>, shortcuts: Widget<Shortcuts>) -> Self {
Self { about } Self { about, shortcuts }
} }
} }
impl WidgetComponent for Dashboard { impl WidgetComponent for Dashboard {
fn view(&mut self, _properties: &Props, frame: &mut Frame, area: Rect) { fn view(&mut self, _properties: &Props, frame: &mut Frame, area: Rect) {
let layout = Layout::default() let shortcuts_h = self
.direction(Direction::Vertical) .shortcuts
.constraints(vec![Constraint::Length(4)].as_ref()) .query(Attribute::Height)
.split(area); .unwrap_or(AttrValue::Size(0))
.unwrap_size();
let layout = layout::root_component(area, shortcuts_h);
self.about.view(frame, layout[0]); self.about.view(frame, layout[0]);
self.shortcuts.view(frame, layout[1]);
} }
fn state(&self) -> State { fn state(&self) -> State {
@ -74,11 +85,12 @@ impl WidgetComponent for Dashboard {
pub struct IssueBrowser { pub struct IssueBrowser {
label: Widget<Label>, label: Widget<Label>,
shortcuts: Widget<Shortcuts>,
} }
impl IssueBrowser { impl IssueBrowser {
pub fn new(label: Widget<Label>) -> Self { pub fn new(label: Widget<Label>, shortcuts: Widget<Shortcuts>) -> Self {
Self { label } Self { label, shortcuts }
} }
} }
@ -89,9 +101,16 @@ impl WidgetComponent for IssueBrowser {
.query(Attribute::Width) .query(Attribute::Width)
.unwrap_or(AttrValue::Size(1)) .unwrap_or(AttrValue::Size(1))
.unwrap_size(); .unwrap_size();
let rect = layout::centered_label(label_w, area); let shortcuts_h = self
.shortcuts
.query(Attribute::Height)
.unwrap_or(AttrValue::Size(0))
.unwrap_size();
let layout = layout::root_component(area, shortcuts_h);
self.label.view(frame, rect); self.label
.view(frame, layout::centered_label(label_w, layout[0]));
self.shortcuts.view(frame, layout[1])
} }
fn state(&self) -> State { fn state(&self) -> State {
@ -105,11 +124,21 @@ impl WidgetComponent for IssueBrowser {
pub struct PatchActivity { pub struct PatchActivity {
label: Widget<Label>, label: Widget<Label>,
context: Widget<ContextBar>,
shortcuts: Widget<Shortcuts>,
} }
impl PatchActivity { impl PatchActivity {
pub fn new(label: Widget<Label>) -> Self { pub fn new(
Self { label } label: Widget<Label>,
context: Widget<ContextBar>,
shortcuts: Widget<Shortcuts>,
) -> Self {
Self {
label,
context,
shortcuts,
}
} }
} }
@ -120,9 +149,22 @@ impl WidgetComponent for PatchActivity {
.query(Attribute::Width) .query(Attribute::Width)
.unwrap_or(AttrValue::Size(1)) .unwrap_or(AttrValue::Size(1))
.unwrap_size(); .unwrap_size();
let rect = layout::centered_label(label_w, area); let context_h = self
.context
.query(Attribute::Height)
.unwrap_or(AttrValue::Size(0))
.unwrap_size();
let shortcuts_h = self
.shortcuts
.query(Attribute::Height)
.unwrap_or(AttrValue::Size(0))
.unwrap_size();
let layout = layout::root_component_with_context(area, context_h, shortcuts_h);
self.label.view(frame, rect); self.label
.view(frame, layout::centered_label(label_w, layout[0]));
self.context.view(frame, layout[1]);
self.shortcuts.view(frame, layout[2]);
} }
fn state(&self) -> State { fn state(&self) -> State {
@ -136,11 +178,21 @@ impl WidgetComponent for PatchActivity {
pub struct PatchFiles { pub struct PatchFiles {
label: Widget<Label>, label: Widget<Label>,
context: Widget<ContextBar>,
shortcuts: Widget<Shortcuts>,
} }
impl PatchFiles { impl PatchFiles {
pub fn new(label: Widget<Label>) -> Self { pub fn new(
Self { label } label: Widget<Label>,
context: Widget<ContextBar>,
shortcuts: Widget<Shortcuts>,
) -> Self {
Self {
label,
context,
shortcuts,
}
} }
} }
@ -151,9 +203,22 @@ impl WidgetComponent for PatchFiles {
.query(Attribute::Width) .query(Attribute::Width)
.unwrap_or(AttrValue::Size(1)) .unwrap_or(AttrValue::Size(1))
.unwrap_size(); .unwrap_size();
let rect = layout::centered_label(label_w, area); let context_h = self
.context
.query(Attribute::Height)
.unwrap_or(AttrValue::Size(0))
.unwrap_size();
let shortcuts_h = self
.shortcuts
.query(Attribute::Height)
.unwrap_or(AttrValue::Size(0))
.unwrap_size();
let layout = layout::root_component_with_context(area, context_h, shortcuts_h);
self.label.view(frame, rect); self.label
.view(frame, layout::centered_label(label_w, layout[0]));
self.context.view(frame, layout[1]);
self.shortcuts.view(frame, layout[2]);
} }
fn state(&self) -> State { fn state(&self) -> State {

View File

@ -46,18 +46,24 @@ pub fn h_stack(
widgets.into_iter().zip(layout.into_iter()).collect() widgets.into_iter().zip(layout.into_iter()).collect()
} }
pub fn default_page(area: Rect, nav_h: u16, shortcuts_h: u16) -> Vec<Rect> { pub fn default_page(area: Rect, nav_h: u16) -> Vec<Rect> {
let margin_h = 1u16; let margin_h = 1u16;
let content_h = area let content_h = area.height.saturating_sub(nav_h.saturating_add(margin_h));
.height
.saturating_sub(shortcuts_h.saturating_add(nav_h).saturating_add(margin_h));
Layout::default() Layout::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)
.horizontal_margin(margin_h) .horizontal_margin(margin_h)
.constraints([Constraint::Length(nav_h), Constraint::Length(content_h)].as_ref())
.split(area)
}
pub fn root_component(area: Rect, shortcuts_h: u16) -> Vec<Rect> {
let content_h = area.height.saturating_sub(shortcuts_h);
Layout::default()
.direction(Direction::Vertical)
.constraints( .constraints(
[ [
Constraint::Length(nav_h),
Constraint::Length(content_h), Constraint::Length(content_h),
Constraint::Length(shortcuts_h), Constraint::Length(shortcuts_h),
] ]
@ -66,21 +72,15 @@ pub fn default_page(area: Rect, nav_h: u16, shortcuts_h: u16) -> Vec<Rect> {
.split(area) .split(area)
} }
pub fn page_with_context(area: Rect, nav_h: u16, context_h: u16, shortcuts_h: u16) -> Vec<Rect> { pub fn root_component_with_context(area: Rect, context_h: u16, shortcuts_h: u16) -> Vec<Rect> {
let margin_h = 1u16; let content_h = area
let content_h = area.height.saturating_sub( .height
shortcuts_h .saturating_sub(shortcuts_h.saturating_add(context_h));
.saturating_add(nav_h)
.saturating_add(context_h)
.saturating_add(margin_h),
);
Layout::default() Layout::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)
.horizontal_margin(margin_h)
.constraints( .constraints(
[ [
Constraint::Length(nav_h),
Constraint::Length(content_h), Constraint::Length(content_h),
Constraint::Length(context_h), Constraint::Length(context_h),
Constraint::Length(shortcuts_h), Constraint::Length(shortcuts_h),