TUI #1
@ -199,7 +199,6 @@ impl<'a> Widget for &mut App<'a> {
|
|||||||
])
|
])
|
||||||
.split(horizontal[1]);
|
.split(horizontal[1]);
|
||||||
|
|
||||||
self.title_bar.render(vertical[0], buf);
|
|
||||||
self.draw_bottom_status(vertical[3], buf);
|
self.draw_bottom_status(vertical[3], buf);
|
||||||
self.draw_tabs(editor_layout[0], buf);
|
self.draw_tabs(editor_layout[0], buf);
|
||||||
let id = App::id().to_string();
|
let id = App::id().to_string();
|
||||||
@ -209,6 +208,9 @@ impl<'a> Widget for &mut App<'a> {
|
|||||||
.context("Failed to render Explorer")
|
.context("Failed to render Explorer")
|
||||||
.unwrap_or_else(|e| error!(target:id.as_str(), "{}", e));
|
.unwrap_or_else(|e| error!(target:id.as_str(), "{}", e));
|
||||||
self.logger.render(vertical[2], buf);
|
self.logger.render(vertical[2], buf);
|
||||||
|
|
||||||
|
// The title bar is rendered last to overlay any popups created for drop-down menus.
|
||||||
|
self.title_bar.render(vertical[0], buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,21 +2,22 @@ use crate::tui::component::{Action, Component, ComponentState};
|
|||||||
use ratatui::buffer::Buffer;
|
use ratatui::buffer::Buffer;
|
||||||
use ratatui::crossterm::event::{KeyCode, KeyEvent};
|
use ratatui::crossterm::event::{KeyCode, KeyEvent};
|
||||||
use ratatui::layout::Rect;
|
use ratatui::layout::Rect;
|
||||||
use ratatui::style::{Color, Style};
|
use ratatui::style::{Color, Modifier, Style};
|
||||||
use ratatui::text::Line;
|
use ratatui::text::Line;
|
||||||
use ratatui::widgets::{Block, Borders, Tabs, Widget};
|
use ratatui::widgets::{
|
||||||
|
Block, Borders, Clear, List, ListItem, ListState, StatefulWidget, Tabs, Widget,
|
||||||
|
};
|
||||||
use strum::{EnumIter, FromRepr, IntoEnumIterator};
|
use strum::{EnumIter, FromRepr, IntoEnumIterator};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, FromRepr, EnumIter)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, FromRepr, EnumIter)]
|
||||||
enum TitleBarItem {
|
enum TitleBarItem {
|
||||||
File,
|
File,
|
||||||
Edit,
|
|
||||||
View,
|
View,
|
||||||
Help,
|
Help,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TitleBarItem {
|
impl TitleBarItem {
|
||||||
pub fn next(mut self) -> Self {
|
pub fn next(self) -> Self {
|
||||||
let cur = self as usize;
|
let cur = self as usize;
|
||||||
let next = cur.saturating_add(1);
|
let next = cur.saturating_add(1);
|
||||||
Self::from_repr(next).unwrap_or(self)
|
Self::from_repr(next).unwrap_or(self)
|
||||||
@ -31,26 +32,35 @@ impl TitleBarItem {
|
|||||||
pub fn id(&self) -> &str {
|
pub fn id(&self) -> &str {
|
||||||
match self {
|
match self {
|
||||||
TitleBarItem::File => "File",
|
TitleBarItem::File => "File",
|
||||||
TitleBarItem::Edit => "Edit",
|
|
||||||
TitleBarItem::View => "View",
|
TitleBarItem::View => "View",
|
||||||
TitleBarItem::Help => "Help",
|
TitleBarItem::Help => "Help",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn options(&self) -> &[&str] {
|
||||||
|
match self {
|
||||||
|
TitleBarItem::File => &["Save", "Reload"],
|
||||||
|
TitleBarItem::View => &["Show/hide explorer", "Show/hide logger"],
|
||||||
|
TitleBarItem::Help => &["About"],
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TitleBar {
|
pub struct TitleBar {
|
||||||
selected: TitleBarItem,
|
selected: TitleBarItem,
|
||||||
opened: Option<TitleBarItem>,
|
opened: Option<TitleBarItem>,
|
||||||
pub(crate) component_state: ComponentState,
|
pub(crate) component_state: ComponentState,
|
||||||
|
list_state: ListState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TitleBar {
|
impl TitleBar {
|
||||||
|
const DEFAULT_HELP: &str = "(←/h)/(→/l): Select option | Enter: Choose selection";
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
selected: TitleBarItem::File,
|
selected: TitleBarItem::File,
|
||||||
opened: None,
|
opened: None,
|
||||||
component_state: ComponentState::default()
|
component_state: ComponentState::default().with_help_text(Self::DEFAULT_HELP),
|
||||||
.with_help_text(concat!("TODO: Title bar help text.").as_ref()),
|
list_state: ListState::default().with_selected(Some(0)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,10 +81,44 @@ impl TitleBar {
|
|||||||
.select(self.selected as usize)
|
.select(self.selected as usize)
|
||||||
.render(area, buf);
|
.render(area, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_drop_down(
|
||||||
|
&mut self,
|
||||||
|
title_bar_anchor: Rect,
|
||||||
|
area: Rect,
|
||||||
|
buf: &mut Buffer,
|
||||||
|
opened: TitleBarItem,
|
||||||
|
) {
|
||||||
|
let popup_area = Self::rect_under_option(title_bar_anchor, area, 40, 10);
|
||||||
|
Clear::default().render(popup_area, buf);
|
||||||
|
let options = opened.options().iter().map(|i| ListItem::new(*i));
|
||||||
|
StatefulWidget::render(
|
||||||
|
List::new(options)
|
||||||
|
.block(Block::bordered().title(self.selected.id()))
|
||||||
|
.highlight_style(
|
||||||
|
Style::default()
|
||||||
|
.bg(Color::Blue)
|
||||||
|
.fg(Color::White)
|
||||||
|
.add_modifier(Modifier::BOLD),
|
||||||
|
)
|
||||||
|
.highlight_symbol(">> "),
|
||||||
|
popup_area,
|
||||||
|
buf,
|
||||||
|
&mut self.list_state,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for &TitleBar {
|
fn rect_under_option(anchor: Rect, area: Rect, width: u16, height: u16) -> Rect {
|
||||||
fn render(self, area: Rect, buf: &mut Buffer)
|
// TODO: X offset for item option? It's fine as-is, but it might look nicer.
|
||||||
|
Rect {
|
||||||
|
x: anchor.x,
|
||||||
|
y: anchor.y + anchor.height,
|
||||||
|
width: width.min(area.width),
|
||||||
|
height,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(&mut self, area: Rect, buf: &mut Buffer)
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
@ -85,14 +129,40 @@ impl Widget for &TitleBar {
|
|||||||
height: 3,
|
height: 3,
|
||||||
};
|
};
|
||||||
self.render_title_bar(title_bar_area, buf);
|
self.render_title_bar(title_bar_area, buf);
|
||||||
|
if let Some(opened) = self.opened {
|
||||||
|
self.render_drop_down(title_bar_area, area, buf, opened);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component for TitleBar {
|
impl Component for TitleBar {
|
||||||
fn handle_key_events(&mut self, key: KeyEvent) -> anyhow::Result<Action> {
|
fn handle_key_events(&mut self, key: KeyEvent) -> anyhow::Result<Action> {
|
||||||
|
if self.opened.is_some() {
|
||||||
|
// Keybinds for popup menu.
|
||||||
|
match key.code {
|
||||||
|
KeyCode::Up | KeyCode::Char('k') => {
|
||||||
|
self.list_state.select_previous();
|
||||||
|
Ok(Action::Handled)
|
||||||
|
}
|
||||||
|
KeyCode::Down | KeyCode::Char('j') => {
|
||||||
|
self.list_state.select_next();
|
||||||
|
Ok(Action::Handled)
|
||||||
|
}
|
||||||
|
KeyCode::Enter => {
|
||||||
|
// TODO: Handle action for the item.
|
||||||
|
Ok(Action::Handled)
|
||||||
|
}
|
||||||
|
KeyCode::Esc | KeyCode::Char('q') => {
|
||||||
|
self.opened = None;
|
||||||
|
self.component_state.help_text = Self::DEFAULT_HELP.to_string();
|
||||||
|
self.list_state.select_first();
|
||||||
|
Ok(Action::Handled)
|
||||||
|
}
|
||||||
|
_ => Ok(Action::Noop),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Keybinds for title bar.
|
||||||
match key.code {
|
match key.code {
|
||||||
// KeyCode::Up | KeyCode::Char('k') => self.selected.key_up(),
|
|
||||||
// KeyCode::Down | KeyCode::Char('j') => self.selected.key_down(),
|
|
||||||
KeyCode::Left | KeyCode::Char('h') => {
|
KeyCode::Left | KeyCode::Char('h') => {
|
||||||
self.selected = self.selected.prev();
|
self.selected = self.selected.prev();
|
||||||
Ok(Action::Handled)
|
Ok(Action::Handled)
|
||||||
@ -103,9 +173,15 @@ impl Component for TitleBar {
|
|||||||
}
|
}
|
||||||
KeyCode::Enter => {
|
KeyCode::Enter => {
|
||||||
self.opened = Some(self.selected);
|
self.opened = Some(self.selected);
|
||||||
|
self.component_state.help_text = concat!(
|
||||||
|
"(↑/k)/(↓/j): Select option | Enter: Choose selection |",
|
||||||
|
" ESC/Q: Close drop-down menu"
|
||||||
|
)
|
||||||
|
.to_string();
|
||||||
Ok(Action::Handled)
|
Ok(Action::Handled)
|
||||||
}
|
}
|
||||||
_ => Ok(Action::Noop),
|
_ => Ok(Action::Noop),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user