205 lines
7.5 KiB
Rust
205 lines
7.5 KiB
Rust
|
|
use ratatui::{
|
||
|
|
layout::{Constraint, Direction, Layout, Rect},
|
||
|
|
style::{Color, Style},
|
||
|
|
text::{Line, Span, Text},
|
||
|
|
widgets::{Block, Borders, Clear, List, ListItem, Paragraph, Wrap},
|
||
|
|
Frame,
|
||
|
|
};
|
||
|
|
|
||
|
|
use crate::app::{App, CurrentScreen, CurrentlyEditing};
|
||
|
|
|
||
|
|
// ANCHOR: method_sig
|
||
|
|
pub fn ui(frame: &mut Frame, app: &App) {
|
||
|
|
// ANCHOR_END: method_sig
|
||
|
|
// Create the layout sections.
|
||
|
|
// ANCHOR: ui_layout
|
||
|
|
let chunks = Layout::default()
|
||
|
|
.direction(Direction::Vertical)
|
||
|
|
.constraints([
|
||
|
|
Constraint::Length(3),
|
||
|
|
Constraint::Min(1),
|
||
|
|
Constraint::Length(3),
|
||
|
|
])
|
||
|
|
.split(frame.area());
|
||
|
|
// ANCHOR_END: ui_layout
|
||
|
|
|
||
|
|
// ANCHOR: title_paragraph
|
||
|
|
let title_block = Block::default()
|
||
|
|
.borders(Borders::ALL)
|
||
|
|
.style(Style::default());
|
||
|
|
|
||
|
|
let title = Paragraph::new(Text::styled(
|
||
|
|
"Create New Json",
|
||
|
|
Style::default().fg(Color::Green),
|
||
|
|
))
|
||
|
|
.block(title_block);
|
||
|
|
|
||
|
|
frame.render_widget(title, chunks[0]);
|
||
|
|
// ANCHOR_END: title_paragraph
|
||
|
|
// ANCHOR: key_value_list
|
||
|
|
let mut list_items = Vec::<ListItem>::new();
|
||
|
|
|
||
|
|
for key in app.pairs.keys() {
|
||
|
|
list_items.push(ListItem::new(Line::from(Span::styled(
|
||
|
|
format!("{: <25} : {}", key, app.pairs.get(key).unwrap()),
|
||
|
|
Style::default().fg(Color::Yellow),
|
||
|
|
))));
|
||
|
|
}
|
||
|
|
|
||
|
|
let list = List::new(list_items);
|
||
|
|
|
||
|
|
frame.render_widget(list, chunks[1]);
|
||
|
|
// ANCHOR_END: key_value_list
|
||
|
|
// ANCHOR: lower_navigation_current_screen
|
||
|
|
let current_navigation_text = vec![
|
||
|
|
// The first half of the text
|
||
|
|
match app.current_screen {
|
||
|
|
CurrentScreen::Main => Span::styled("Normal Mode", Style::default().fg(Color::Green)),
|
||
|
|
CurrentScreen::Editing => {
|
||
|
|
Span::styled("Editing Mode", Style::default().fg(Color::Yellow))
|
||
|
|
}
|
||
|
|
CurrentScreen::Exiting => Span::styled("Exiting", Style::default().fg(Color::LightRed)),
|
||
|
|
}
|
||
|
|
.to_owned(),
|
||
|
|
// A white divider bar to separate the two sections
|
||
|
|
Span::styled(" | ", Style::default().fg(Color::White)),
|
||
|
|
// The final section of the text, with hints on what the user is editing
|
||
|
|
{
|
||
|
|
if let Some(editing) = &app.currently_editing {
|
||
|
|
match editing {
|
||
|
|
CurrentlyEditing::Key => {
|
||
|
|
Span::styled("Editing Json Key", Style::default().fg(Color::Green))
|
||
|
|
}
|
||
|
|
CurrentlyEditing::Value => {
|
||
|
|
Span::styled("Editing Json Value", Style::default().fg(Color::LightGreen))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
Span::styled("Not Editing Anything", Style::default().fg(Color::DarkGray))
|
||
|
|
}
|
||
|
|
},
|
||
|
|
];
|
||
|
|
|
||
|
|
let mode_footer = Paragraph::new(Line::from(current_navigation_text))
|
||
|
|
.block(Block::default().borders(Borders::ALL));
|
||
|
|
// ANCHOR_END: lower_navigation_current_screen
|
||
|
|
|
||
|
|
// ANCHOR: lower_navigation_key_hint
|
||
|
|
let current_keys_hint = {
|
||
|
|
match app.current_screen {
|
||
|
|
CurrentScreen::Main => Span::styled(
|
||
|
|
"(q) to quit / (e) to make new pair",
|
||
|
|
Style::default().fg(Color::Red),
|
||
|
|
),
|
||
|
|
CurrentScreen::Editing => Span::styled(
|
||
|
|
"(ESC) to cancel/(Tab) to switch boxes/enter to complete",
|
||
|
|
Style::default().fg(Color::Red),
|
||
|
|
),
|
||
|
|
CurrentScreen::Exiting => Span::styled(
|
||
|
|
"(q) to quit / (e) to make new pair",
|
||
|
|
Style::default().fg(Color::Red),
|
||
|
|
),
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
let key_notes_footer =
|
||
|
|
Paragraph::new(Line::from(current_keys_hint)).block(Block::default().borders(Borders::ALL));
|
||
|
|
// ANCHOR_END: lower_navigation_key_hint
|
||
|
|
|
||
|
|
// ANCHOR: lower_navigation_layout
|
||
|
|
let footer_chunks = Layout::default()
|
||
|
|
.direction(Direction::Horizontal)
|
||
|
|
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
|
||
|
|
.split(chunks[2]);
|
||
|
|
// ANCHOR_END: lower_navigation_layout
|
||
|
|
|
||
|
|
// ANCHOR: lower_navigation_rendering
|
||
|
|
frame.render_widget(mode_footer, footer_chunks[0]);
|
||
|
|
frame.render_widget(key_notes_footer, footer_chunks[1]);
|
||
|
|
// ANCHOR_END: lower_navigation_rendering
|
||
|
|
|
||
|
|
// ANCHOR: editing_popup
|
||
|
|
if let Some(editing) = &app.currently_editing {
|
||
|
|
let popup_block = Block::default()
|
||
|
|
.title("Enter a new key-value pair")
|
||
|
|
.borders(Borders::NONE)
|
||
|
|
.style(Style::default().bg(Color::DarkGray));
|
||
|
|
|
||
|
|
let area = centered_rect(60, 25, frame.area());
|
||
|
|
frame.render_widget(popup_block, area);
|
||
|
|
// ANCHOR_END: editing_popup
|
||
|
|
|
||
|
|
// ANCHOR: popup_layout
|
||
|
|
let popup_chunks = Layout::default()
|
||
|
|
.direction(Direction::Horizontal)
|
||
|
|
.margin(1)
|
||
|
|
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
|
||
|
|
.split(area);
|
||
|
|
// ANCHOR_END: popup_layout
|
||
|
|
|
||
|
|
// ANCHOR: key_value_blocks
|
||
|
|
let mut key_block = Block::default().title("Key").borders(Borders::ALL);
|
||
|
|
let mut value_block = Block::default().title("Value").borders(Borders::ALL);
|
||
|
|
|
||
|
|
let active_style = Style::default().bg(Color::LightYellow).fg(Color::Black);
|
||
|
|
|
||
|
|
match editing {
|
||
|
|
CurrentlyEditing::Key => key_block = key_block.style(active_style),
|
||
|
|
CurrentlyEditing::Value => value_block = value_block.style(active_style),
|
||
|
|
};
|
||
|
|
|
||
|
|
let key_text = Paragraph::new(app.key_input.clone()).block(key_block);
|
||
|
|
frame.render_widget(key_text, popup_chunks[0]);
|
||
|
|
|
||
|
|
let value_text = Paragraph::new(app.value_input.clone()).block(value_block);
|
||
|
|
frame.render_widget(value_text, popup_chunks[1]);
|
||
|
|
}
|
||
|
|
// ANCHOR_END: key_value_blocks
|
||
|
|
|
||
|
|
// ANCHOR: exit_screen
|
||
|
|
if let CurrentScreen::Exiting = app.current_screen {
|
||
|
|
frame.render_widget(Clear, frame.area()); //this clears the entire screen and anything already drawn
|
||
|
|
let popup_block = Block::default()
|
||
|
|
.title("Y/N")
|
||
|
|
.borders(Borders::NONE)
|
||
|
|
.style(Style::default().bg(Color::DarkGray));
|
||
|
|
|
||
|
|
let exit_text = Text::styled(
|
||
|
|
"Would you like to output the buffer as json? (y/n)",
|
||
|
|
Style::default().fg(Color::Red),
|
||
|
|
);
|
||
|
|
// the `trim: false` will stop the text from being cut off when over the edge of the block
|
||
|
|
let exit_paragraph = Paragraph::new(exit_text)
|
||
|
|
.block(popup_block)
|
||
|
|
.wrap(Wrap { trim: false });
|
||
|
|
|
||
|
|
let area = centered_rect(60, 25, frame.area());
|
||
|
|
frame.render_widget(exit_paragraph, area);
|
||
|
|
}
|
||
|
|
// ANCHOR_END: exit_screen
|
||
|
|
}
|
||
|
|
|
||
|
|
// ANCHOR: centered_rect
|
||
|
|
/// helper function to create a centered rect using up certain percentage of the available rect `r`
|
||
|
|
fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
|
||
|
|
// Cut the given rectangle into three vertical pieces
|
||
|
|
let popup_layout = Layout::default()
|
||
|
|
.direction(Direction::Vertical)
|
||
|
|
.constraints([
|
||
|
|
Constraint::Percentage((100 - percent_y) / 2),
|
||
|
|
Constraint::Percentage(percent_y),
|
||
|
|
Constraint::Percentage((100 - percent_y) / 2),
|
||
|
|
])
|
||
|
|
.split(r);
|
||
|
|
|
||
|
|
// Then cut the middle vertical piece into three width-wise pieces
|
||
|
|
Layout::default()
|
||
|
|
.direction(Direction::Horizontal)
|
||
|
|
.constraints([
|
||
|
|
Constraint::Percentage((100 - percent_x) / 2),
|
||
|
|
Constraint::Percentage(percent_x),
|
||
|
|
Constraint::Percentage((100 - percent_x) / 2),
|
||
|
|
])
|
||
|
|
.split(popup_layout[1])[1] // Return the middle chunk
|
||
|
|
}
|