diff --git a/Cargo.lock b/Cargo.lock index a8fe3ba..638d48e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -309,7 +309,7 @@ dependencies = [ "time", "unicode-segmentation", "unicode-truncate", - "unicode-width", + "unicode-width 0.1.14", ] [[package]] @@ -327,6 +327,7 @@ version = "0.1.0" dependencies = [ "crossterm", "ratatui", + "unicode-width 0.2.0", ] [[package]] @@ -496,7 +497,7 @@ checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" dependencies = [ "itertools", "unicode-segmentation", - "unicode-width", + "unicode-width 0.1.14", ] [[package]] @@ -505,6 +506,12 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" diff --git a/Cargo.toml b/Cargo.toml index 1764462..6f8c9fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,3 +6,4 @@ edition = "2021" [dependencies] crossterm = { version = "0.28.1", features = ["event-stream"] } ratatui = { version = "0.28.1", features = ["all-widgets"] } +unicode-width = "0.2.0" diff --git a/src/app.rs b/src/app.rs index a554fd5..9b7737f 100644 --- a/src/app.rs +++ b/src/app.rs @@ -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(()) } diff --git a/src/view.rs b/src/view.rs index fe6c552..07fb0c1 100644 --- a/src/view.rs +++ b/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 } diff --git a/src/view/component.rs b/src/view/component.rs index feaca9d..e169aa8 100644 --- a/src/view/component.rs +++ b/src/view/component.rs @@ -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<()>; } diff --git a/src/view/component/block.rs b/src/view/component/block.rs index 8a96276..dfe6923 100644 --- a/src/view/component/block.rs +++ b/src/view/component/block.rs @@ -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 } } diff --git a/src/view/component/help.rs b/src/view/component/help.rs new file mode 100644 index 0000000..78e354c --- /dev/null +++ b/src/view/component/help.rs @@ -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(()) + } +} diff --git a/src/view/component/input.rs b/src/view/component/input.rs index e2cfed2..6b7f813 100644 --- a/src/view/component/input.rs +++ b/src/view/component/input.rs @@ -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(()) diff --git a/src/view/component/list.rs b/src/view/component/list.rs new file mode 100644 index 0000000..7e53d39 --- /dev/null +++ b/src/view/component/list.rs @@ -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, + cursor: usize, + selectable: bool, +} + +impl ListComponent { + pub fn add_item(&mut self, _item_keys: Vec) { + 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 = 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(()) + } +} diff --git a/src/view/component/table.rs b/src/view/component/table.rs deleted file mode 100644 index d574461..0000000 --- a/src/view/component/table.rs +++ /dev/null @@ -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(()) - } -} diff --git a/src/view/home.rs b/src/view/home.rs index 2fc6a20..ab8711b 100644 --- a/src/view/home.rs +++ b/src/view/home.rs @@ -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(()) } } diff --git a/src/view/setting.rs b/src/view/setting.rs new file mode 100644 index 0000000..e00617f --- /dev/null +++ b/src/view/setting.rs @@ -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) {} +} diff --git a/src/view/util.rs b/src/view/util.rs new file mode 100644 index 0000000..139597f --- /dev/null +++ b/src/view/util.rs @@ -0,0 +1,2 @@ + +