2nd commit
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
/target
|
||||||
Generated
+1730
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,9 @@
|
|||||||
|
[package]
|
||||||
|
name = "klipper-tui"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
ratatui = "0.30.0"
|
||||||
|
crossterm = "0.29.0"
|
||||||
|
ratatui-crossterm = "0.1.0"
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
pub enum CurrentScreen {
|
||||||
|
Main,
|
||||||
|
Editing,
|
||||||
|
Exiting,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum CurrentlyEditing {
|
||||||
|
Key,
|
||||||
|
Value,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct App {
|
||||||
|
pub key_input: String, // the currently being edited json key.
|
||||||
|
pub value_input: String, // the currently being edited json value.
|
||||||
|
pub pairs: HashMap<String, String>, // The representation of our key and value pairs with serde Serialize support
|
||||||
|
pub current_screen: CurrentScreen, // the current screen the user is looking at, and will later determine what is rendered.
|
||||||
|
pub currently_editing: Option<CurrentlyEditing>, // the optional state containing which of the key or value pair the user is editing. It is an option, because when the user is not directly editing a key-value pair, this will be set to `None`.
|
||||||
|
}
|
||||||
|
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
use ratatui::backend::CrosstermBackend;
|
||||||
|
use ratatui::Terminal;
|
||||||
|
use std::io::{stdout, Write};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let backend = CrosstermBackend::new(stdout());
|
||||||
|
let mut terminal = Terminal::new(backend).unwrap();
|
||||||
|
//todo: setup moonraker interface
|
||||||
|
//todo: setup custom klipper ui
|
||||||
|
}
|
||||||
@@ -0,0 +1,204 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user