Popup
This commit is contained in:
@@ -3,7 +3,7 @@ use std::io;
|
||||
use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind};
|
||||
use ratatui::{DefaultTerminal, Frame};
|
||||
|
||||
use crate::view::home::{self, Home};
|
||||
use crate::view::{home::Home, View};
|
||||
|
||||
pub struct App {
|
||||
running: bool,
|
||||
@@ -39,7 +39,7 @@ impl App {
|
||||
code: KeyCode::Esc,
|
||||
..
|
||||
}) => self.running = false,
|
||||
event => self.view.handle_event(event)?,
|
||||
event => self.view.handle_event(&event)?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
24
src/view.rs
24
src/view.rs
@@ -1,7 +1,29 @@
|
||||
use component::Component;
|
||||
use std::io;
|
||||
|
||||
use crossterm::event::Event;
|
||||
use ratatui::{
|
||||
layout::{Flex, Layout, Rect},
|
||||
Frame,
|
||||
};
|
||||
|
||||
pub mod component;
|
||||
pub mod home;
|
||||
pub mod setting;
|
||||
|
||||
pub trait View {
|
||||
fn draw(&self, frame: &mut Frame);
|
||||
fn handle_event(&mut self, event: &Event) -> io::Result<()>;
|
||||
}
|
||||
|
||||
pub trait PopupView {
|
||||
fn draw(&self, frame: &mut Frame);
|
||||
fn handle_event(&mut self, event: &Event);
|
||||
}
|
||||
|
||||
fn center_rect(area: Rect, width: u16, height: u16) -> Rect {
|
||||
let horizontal = Layout::horizontal([width]).flex(Flex::Center);
|
||||
let vertical = Layout::vertical([height]).flex(Flex::Center);
|
||||
let [area] = vertical.areas(area);
|
||||
let [area] = horizontal.areas(area);
|
||||
area
|
||||
}
|
||||
|
||||
@@ -4,10 +4,11 @@ use crossterm::event::Event;
|
||||
use ratatui::widgets::Widget;
|
||||
|
||||
pub mod block;
|
||||
pub mod help;
|
||||
pub mod input;
|
||||
pub mod table;
|
||||
pub mod list;
|
||||
|
||||
pub trait Component {
|
||||
fn widget(&self) -> impl Widget;
|
||||
fn event_handler(&mut self, event: Event) -> io::Result<()>;
|
||||
fn event_handler(&mut self, event: &Event) -> io::Result<()>;
|
||||
}
|
||||
|
||||
@@ -3,12 +3,20 @@ use std::marker::PhantomData;
|
||||
use ratatui::widgets::{Block, Borders};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct BlockWidget<'a> {
|
||||
pub struct BlockComponent<'a> {
|
||||
_phantom: PhantomData<&'a ()>,
|
||||
title: String,
|
||||
}
|
||||
|
||||
impl<'a> BlockWidget<'a> {
|
||||
impl<'a> BlockComponent<'a> {
|
||||
pub fn widget(&self) -> Block<'a> {
|
||||
Block::default().borders(Borders::ALL)
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.title(self.title.clone())
|
||||
}
|
||||
|
||||
pub fn title(mut self, text: String) -> Self {
|
||||
self.title = text;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
20
src/view/component/help.rs
Normal file
20
src/view/component/help.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
use std::io;
|
||||
|
||||
use crossterm::event::Event;
|
||||
use ratatui::widgets::{Paragraph, Widget};
|
||||
|
||||
use super::{block::BlockComponent, Component};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct HelpComponent {}
|
||||
|
||||
impl Component for HelpComponent {
|
||||
fn widget(&self) -> impl Widget {
|
||||
let block = BlockComponent::default().title("Help".to_string()).widget();
|
||||
let help = Paragraph::new("Temp").block(block);
|
||||
help
|
||||
}
|
||||
fn event_handler(&mut self, _event: &Event) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,33 +1,39 @@
|
||||
use std::{io, marker::PhantomData, u32};
|
||||
use std::io;
|
||||
|
||||
use super::{block::BlockWidget, Component};
|
||||
use super::{block::BlockComponent, Component};
|
||||
use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
|
||||
use ratatui::{
|
||||
prelude::Rect,
|
||||
widgets::{Block, Paragraph, Widget},
|
||||
layout::{Position, Rect},
|
||||
widgets::{Paragraph, Widget},
|
||||
Frame,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct InputWidget {
|
||||
pub struct InputComponent {
|
||||
name: String,
|
||||
value: String,
|
||||
cursor_idx: usize,
|
||||
cursor: usize,
|
||||
}
|
||||
|
||||
impl InputWidget {
|
||||
pub fn new() -> Self {
|
||||
InputWidget::default()
|
||||
impl InputComponent {
|
||||
pub fn name(mut self, name: String) -> Self {
|
||||
self.name = name;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn show_cursor(&self, frame: &mut Frame, area: Rect) {
|
||||
let position = Position::new(area.x + 1 + self.cursor as u16, area.y + 1);
|
||||
frame.set_cursor_position(position);
|
||||
}
|
||||
}
|
||||
impl Component for InputWidget {
|
||||
impl Component for InputComponent {
|
||||
fn widget(&self) -> impl Widget {
|
||||
let block = BlockWidget::default().widget();
|
||||
let block = BlockComponent::default().title(self.name.clone()).widget();
|
||||
let input = Paragraph::new(self.value.clone()).block(block);
|
||||
input
|
||||
}
|
||||
|
||||
fn event_handler(&mut self, event: Event) -> io::Result<()> {
|
||||
fn event_handler(&mut self, event: &Event) -> io::Result<()> {
|
||||
match event {
|
||||
Event::Key(KeyEvent {
|
||||
kind: KeyEventKind::Press,
|
||||
@@ -37,12 +43,18 @@ impl Component for InputWidget {
|
||||
}) => {
|
||||
use KeyCode::*;
|
||||
match (code, modifiers) {
|
||||
(Char(c), KeyModifiers::SHIFT | KeyModifiers::NONE) => {
|
||||
self.value.push(c);
|
||||
(Char(c), &KeyModifiers::SHIFT | &KeyModifiers::NONE) => {
|
||||
self.value.push(*c);
|
||||
self.cursor += 1;
|
||||
}
|
||||
(Backspace, &KeyModifiers::NONE) => {
|
||||
if self.value.pop().is_some() {
|
||||
self.cursor -= 1;
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
},
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
97
src/view/component/list.rs
Normal file
97
src/view/component/list.rs
Normal file
@@ -0,0 +1,97 @@
|
||||
use super::{block::BlockComponent, Component};
|
||||
use crossterm::{
|
||||
event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers},
|
||||
style::Color,
|
||||
};
|
||||
use ratatui::{
|
||||
style::Stylize,
|
||||
widgets::{List, ListItem, Widget},
|
||||
};
|
||||
|
||||
// #[derive(Default)]
|
||||
pub struct ListComponent {
|
||||
name: String,
|
||||
item_keys: Vec<String>,
|
||||
cursor: usize,
|
||||
selectable: bool,
|
||||
}
|
||||
|
||||
impl ListComponent {
|
||||
pub fn add_item(&mut self, _item_keys: Vec<String>) {
|
||||
self.item_keys.extend(vec!["S".to_string()]);
|
||||
}
|
||||
pub fn name(mut self, name: String) -> Self {
|
||||
self.name = name;
|
||||
self
|
||||
}
|
||||
pub fn selectable(mut self) -> Self {
|
||||
self.selectable = true;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ListComponent {
|
||||
fn default() -> Self {
|
||||
ListComponent {
|
||||
name: String::default(),
|
||||
item_keys: vec!["1".to_string(), "2".to_string(), "3".to_string()],
|
||||
cursor: 0,
|
||||
selectable: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for ListComponent {
|
||||
fn widget(&self) -> impl Widget {
|
||||
let block = BlockComponent::default().title(self.name.clone()).widget();
|
||||
let list_items: Vec<ListItem> = self
|
||||
.item_keys
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, key)| {
|
||||
if self.selectable && idx == self.cursor {
|
||||
ListItem::new(key.clone()).bg(Color::Grey).fg(Color::Black)
|
||||
} else {
|
||||
ListItem::new(key.clone())
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let list = List::default().items(list_items).block(block);
|
||||
list
|
||||
}
|
||||
|
||||
fn event_handler(&mut self, event: &Event) -> std::io::Result<()> {
|
||||
if !self.selectable {
|
||||
return Ok(());
|
||||
}
|
||||
match event {
|
||||
Event::Key(KeyEvent {
|
||||
code,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
kind: KeyEventKind::Press,
|
||||
..
|
||||
}) => {
|
||||
use KeyCode::*;
|
||||
match code {
|
||||
Up => {
|
||||
self.cursor = if self.cursor == 0 {
|
||||
self.item_keys.len() - 1
|
||||
} else {
|
||||
self.cursor - 1
|
||||
}
|
||||
}
|
||||
Down => {
|
||||
self.cursor = if self.cursor == self.item_keys.len() - 1 {
|
||||
0
|
||||
} else {
|
||||
self.cursor + 1
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use super::{block::BlockWidget, Component};
|
||||
use crossterm::event::{self, Event};
|
||||
use ratatui::{
|
||||
prelude::Rect,
|
||||
widgets::{Block, Paragraph, Widget},
|
||||
Frame,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct TableWidget {}
|
||||
|
||||
impl TableWidget {
|
||||
pub fn new() -> Self {
|
||||
TableWidget::default()
|
||||
}
|
||||
}
|
||||
impl Component for TableWidget {
|
||||
fn widget(&self) -> impl Widget {
|
||||
let block = BlockWidget::default().widget();
|
||||
let input = Paragraph::new("sss").block(block);
|
||||
input
|
||||
}
|
||||
|
||||
fn event_handler(&mut self, event: Event) -> std::io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,48 @@
|
||||
use std::io;
|
||||
|
||||
use crossterm::event::{ Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
|
||||
use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind};
|
||||
use ratatui::{
|
||||
layout::{Constraint, Direction, Layout, Rect},
|
||||
widgets::Paragraph,
|
||||
style::{Color, Style},
|
||||
widgets::{Block, BorderType, Borders},
|
||||
Frame,
|
||||
};
|
||||
|
||||
use super::component::{input::InputWidget, table::TableWidget, Component};
|
||||
use super::{
|
||||
center_rect,
|
||||
component::{
|
||||
help::HelpComponent,
|
||||
input::{self, InputComponent},
|
||||
list::ListComponent,
|
||||
Component,
|
||||
},
|
||||
setting::SettingView,
|
||||
PopupView, View,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
// #[derive(Default)]
|
||||
pub struct Home {
|
||||
input: InputWidget,
|
||||
table: TableWidget,
|
||||
input: InputComponent,
|
||||
table: ListComponent,
|
||||
help: HelpComponent,
|
||||
setting: SettingView,
|
||||
}
|
||||
|
||||
impl Home {
|
||||
pub fn draw(&self, frame: &mut Frame) {
|
||||
impl Default for Home {
|
||||
fn default() -> Self {
|
||||
Home {
|
||||
input: InputComponent::default().name("Name".to_string()),
|
||||
table: ListComponent::default()
|
||||
.selectable()
|
||||
.name("Connections".to_string()),
|
||||
help: HelpComponent::default(),
|
||||
setting: SettingView::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl View for Home {
|
||||
fn draw(&self, frame: &mut Frame) {
|
||||
let layout = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints(vec![
|
||||
@@ -25,12 +51,16 @@ impl Home {
|
||||
Constraint::Length(3),
|
||||
])
|
||||
.split(frame.area());
|
||||
self.input.show_cursor(frame, layout[0]);
|
||||
frame.render_widget(self.input.widget(), layout[0]);
|
||||
frame.render_widget(self.table.widget(), layout[1]);
|
||||
frame.render_widget(self.help.widget(), layout[2]);
|
||||
self.setting.draw(frame);
|
||||
}
|
||||
|
||||
pub fn handle_event(&mut self, event: Event) -> io::Result<()> {
|
||||
fn handle_event(&mut self, event: &Event) -> io::Result<()> {
|
||||
self.input.event_handler(event)?;
|
||||
self.table.event_handler(event)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
35
src/view/setting.rs
Normal file
35
src/view/setting.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
use ratatui::layout::{Constraint, Layout, Rect};
|
||||
|
||||
use super::{
|
||||
center_rect,
|
||||
component::{list::ListComponent, Component},
|
||||
PopupView,
|
||||
};
|
||||
|
||||
pub struct SettingView {
|
||||
menu: ListComponent,
|
||||
area: Rect,
|
||||
}
|
||||
|
||||
impl SettingView {}
|
||||
|
||||
impl Default for SettingView {
|
||||
fn default() -> Self {
|
||||
SettingView {
|
||||
area: Rect::new(0, 0, 10, 5),
|
||||
menu: ListComponent::default().selectable(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PopupView for SettingView {
|
||||
fn draw(&self, frame: &mut ratatui::Frame) {
|
||||
let area = center_rect(frame.area(), self.area.width, self.area.height);
|
||||
let layout = Layout::default()
|
||||
.constraints([Constraint::default()])
|
||||
.split(area);
|
||||
frame.render_widget(self.menu.widget(), layout[0]);
|
||||
}
|
||||
|
||||
fn handle_event(&mut self, _event: &crossterm::event::Event) {}
|
||||
}
|
||||
2
src/view/util.rs
Normal file
2
src/view/util.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
|
||||
|
||||
Reference in New Issue
Block a user