define StatefulDrawableComponent

pull/114/head
Takayuki Maeda 3 years ago
parent 08be3dbeac
commit 62ab3275fd

@ -1,5 +1,7 @@
use crate::clipboard::copy_to_clipboard; use crate::clipboard::copy_to_clipboard;
use crate::components::{CommandInfo, Component as _, DrawableComponent as _, EventState}; use crate::components::{
CommandInfo, Component as _, DrawableComponent as _, EventState, StatefulDrawableComponent,
};
use crate::database::{MySqlPool, Pool, PostgresPool, SqlitePool, RECORDS_LIMIT_PER_PAGE}; use crate::database::{MySqlPool, Pool, PostgresPool, SqlitePool, RECORDS_LIMIT_PER_PAGE};
use crate::event::Key; use crate::event::Key;
use crate::{ use crate::{

@ -1,4 +1,4 @@
use super::{Component, DrawableComponent, EventState}; use super::{Component, EventState, StatefulDrawableComponent};
use crate::components::command::CommandInfo; use crate::components::command::CommandInfo;
use crate::config::{Connection, KeyConfig}; use crate::config::{Connection, KeyConfig};
use crate::event::Key; use crate::event::Key;
@ -81,7 +81,7 @@ impl ConnectionsComponent {
} }
} }
impl DrawableComponent for ConnectionsComponent { impl StatefulDrawableComponent for ConnectionsComponent {
fn draw<B: Backend>(&mut self, f: &mut Frame<B>, _area: Rect, _focused: bool) -> Result<()> { fn draw<B: Backend>(&mut self, f: &mut Frame<B>, _area: Rect, _focused: bool) -> Result<()> {
let width = 80; let width = 80;
let height = 20; let height = 20;

@ -0,0 +1,144 @@
use super::{
compute_character_width, CompletionComponent, Component, DrawableComponent, EventState,
MovableComponent,
};
use crate::components::command::CommandInfo;
use crate::config::KeyConfig;
use crate::event::Key;
use anyhow::Result;
use database_tree::Table;
use tui::{
backend::Backend,
layout::Rect,
style::{Color, Style},
text::{Span, Spans},
widgets::{Block, Borders, Paragraph},
Frame,
};
use unicode_width::UnicodeWidthStr;
pub struct DatabaseFilterComponent {
pub table: Option<Table>,
input: Vec<char>,
input_idx: usize,
input_cursor_position: u16,
}
impl DatabaseFilterComponent {
pub fn new() -> Self {
Self {
table: None,
input: Vec::new(),
input_idx: 0,
input_cursor_position: 0,
}
}
pub fn input_str(&self) -> String {
self.input.iter().collect()
}
pub fn reset(&mut self) {
self.table = None;
self.input = Vec::new();
self.input_idx = 0;
self.input_cursor_position = 0;
}
}
impl DrawableComponent for DatabaseFilterComponent {
fn draw<B: Backend>(&self, f: &mut Frame<B>, area: Rect, focused: bool) -> Result<()> {
let query = Paragraph::new(Spans::from(vec![
Span::styled(
self.table
.as_ref()
.map_or("-".to_string(), |table| table.name.to_string()),
Style::default().fg(Color::Blue),
),
Span::from(format!(
"{}{:w$}",
if self.input.is_empty() && !focused {
"Filter tables".to_string()
} else {
self.input_str()
},
w = area.width as usize
)),
]))
.style(if focused {
Style::default()
} else {
Style::default().fg(Color::DarkGray)
})
.block(Block::default().borders(Borders::BOTTOM));
f.render_widget(query, area);
if focused {
f.set_cursor(
(area.x + self.input_cursor_position + 1).min(area.right().saturating_sub(2)),
area.y,
)
}
Ok(())
}
}
impl Component for DatabaseFilterComponent {
fn commands(&self, _out: &mut Vec<CommandInfo>) {}
fn event(&mut self, key: Key) -> Result<EventState> {
let input_str: String = self.input.iter().collect();
match key {
Key::Char(c) => {
self.input.insert(self.input_idx, c);
self.input_idx += 1;
self.input_cursor_position += compute_character_width(c);
return Ok(EventState::Consumed);
}
Key::Delete | Key::Backspace => {
if input_str.width() > 0 && !self.input.is_empty() && self.input_idx > 0 {
let last_c = self.input.remove(self.input_idx - 1);
self.input_idx -= 1;
self.input_cursor_position -= compute_character_width(last_c);
}
return Ok(EventState::Consumed);
}
Key::Left => {
if !self.input.is_empty() && self.input_idx > 0 {
self.input_idx -= 1;
self.input_cursor_position = self
.input_cursor_position
.saturating_sub(compute_character_width(self.input[self.input_idx]));
}
return Ok(EventState::Consumed);
}
Key::Ctrl('a') => {
if !self.input.is_empty() && self.input_idx > 0 {
self.input_idx = 0;
self.input_cursor_position = 0
}
return Ok(EventState::Consumed);
}
Key::Right => {
if self.input_idx < self.input.len() {
let next_c = self.input[self.input_idx];
self.input_idx += 1;
self.input_cursor_position += compute_character_width(next_c);
}
return Ok(EventState::Consumed);
}
Key::Ctrl('e') => {
if self.input_idx < self.input.len() {
self.input_idx = self.input.len();
self.input_cursor_position = self.input_str().width() as u16;
}
return Ok(EventState::Consumed);
}
_ => (),
}
Ok(EventState::NotConsumed)
}
}

@ -1,6 +1,6 @@
use super::{ use super::{
compute_character_width, utils::scroll_vertical::VerticalScroll, Component, DrawableComponent, compute_character_width, utils::scroll_vertical::VerticalScroll, Component,
EventState, DatabaseFilterComponent, DrawableComponent, EventState,
}; };
use crate::components::command::{self, CommandInfo}; use crate::components::command::{self, CommandInfo};
use crate::config::KeyConfig; use crate::config::KeyConfig;
@ -35,6 +35,7 @@ pub enum Focus {
pub struct DatabasesComponent { pub struct DatabasesComponent {
tree: DatabaseTree, tree: DatabaseTree,
filter: DatabaseFilterComponent,
filterd_tree: Option<DatabaseTree>, filterd_tree: Option<DatabaseTree>,
scroll: VerticalScroll, scroll: VerticalScroll,
input: Vec<char>, input: Vec<char>,
@ -48,6 +49,7 @@ impl DatabasesComponent {
pub fn new(key_config: KeyConfig) -> Self { pub fn new(key_config: KeyConfig) -> Self {
Self { Self {
tree: DatabaseTree::default(), tree: DatabaseTree::default(),
filter: DatabaseFilterComponent::new(),
filterd_tree: None, filterd_tree: None,
scroll: VerticalScroll::new(false, false), scroll: VerticalScroll::new(false, false),
input: Vec::new(), input: Vec::new(),
@ -147,7 +149,7 @@ impl DatabasesComponent {
)) ))
} }
fn draw_tree<B: Backend>(&self, f: &mut Frame<B>, area: Rect, focused: bool) { fn draw_tree<B: Backend>(&self, f: &mut Frame<B>, area: Rect, focused: bool) -> Result<()> {
f.render_widget( f.render_widget(
Block::default() Block::default()
.title("Databases") .title("Databases")
@ -167,24 +169,26 @@ impl DatabasesComponent {
.constraints([Constraint::Length(2), Constraint::Min(1)].as_ref()) .constraints([Constraint::Length(2), Constraint::Min(1)].as_ref())
.split(area); .split(area);
let filter = Paragraph::new(Span::styled( // let filter = Paragraph::new(Span::styled(
format!( // format!(
"{}{:w$}", // "{}{:w$}",
if self.input.is_empty() && matches!(self.focus, Focus::Tree) { // if self.input.is_empty() && matches!(self.focus, Focus::Tree) {
"Filter tables".to_string() // "Filter tables".to_string()
} else { // } else {
self.input_str() // self.input_str()
}, // },
w = area.width as usize // w = area.width as usize
), // ),
if let Focus::Filter = self.focus { // if let Focus::Filter = self.focus {
Style::default() // Style::default()
} else { // } else {
Style::default().fg(Color::DarkGray) // Style::default().fg(Color::DarkGray)
}, // },
)) // ))
.block(Block::default().borders(Borders::BOTTOM)); // .block(Block::default().borders(Borders::BOTTOM));
f.render_widget(filter, chunks[0]); // f.render_widget(filter, chunks[0]);
// self.filter
// .draw(f, chunks[0], matches!(self.focus, Focus::Filter))?;
let tree_height = chunks[1].height as usize; let tree_height = chunks[1].height as usize;
let tree = if let Some(tree) = self.filterd_tree.as_ref() { let tree = if let Some(tree) = self.filterd_tree.as_ref() {
@ -220,20 +224,18 @@ impl DatabasesComponent {
draw_list_block(f, chunks[1], Block::default().borders(Borders::NONE), items); draw_list_block(f, chunks[1], Block::default().borders(Borders::NONE), items);
self.scroll.draw(f, chunks[1]); self.scroll.draw(f, chunks[1]);
if let Focus::Filter = self.focus { Ok(())
f.set_cursor(area.x + self.input_cursor_position + 1, area.y + 1)
}
} }
} }
impl DrawableComponent for DatabasesComponent { impl DrawableComponent for DatabasesComponent {
fn draw<B: Backend>(&mut self, f: &mut Frame<B>, area: Rect, focused: bool) -> Result<()> { fn draw<B: Backend>(&self, f: &mut Frame<B>, area: Rect, focused: bool) -> Result<()> {
let chunks = Layout::default() let chunks = Layout::default()
.direction(Direction::Horizontal) .direction(Direction::Horizontal)
.constraints([Constraint::Percentage(100)].as_ref()) .constraints([Constraint::Percentage(100)].as_ref())
.split(area); .split(area);
self.draw_tree(f, chunks[0], focused); self.draw_tree(f, chunks[0], focused)?;
Ok(()) Ok(())
} }
} }
@ -249,64 +251,70 @@ impl Component for DatabasesComponent {
self.focus = Focus::Filter; self.focus = Focus::Filter;
return Ok(EventState::Consumed); return Ok(EventState::Consumed);
} }
match key {
Key::Char(c) if self.focus == Focus::Filter => {
self.input.insert(self.input_idx, c);
self.input_idx += 1;
self.input_cursor_position += compute_character_width(c);
self.filterd_tree = Some(self.tree.filter(self.input_str()));
return Ok(EventState::Consumed);
}
Key::Delete | Key::Backspace if matches!(self.focus, Focus::Filter) => {
if input_str.width() > 0 {
if !self.input.is_empty() && self.input_idx > 0 {
let last_c = self.input.remove(self.input_idx - 1);
self.input_idx -= 1;
self.input_cursor_position -= compute_character_width(last_c);
}
self.filterd_tree = if self.input.is_empty() { match key {
None // Key::Char(c) if self.focus == Focus::Filter => {
} else { // self.input.insert(self.input_idx, c);
Some(self.tree.filter(self.input_str())) // self.input_idx += 1;
}; // self.input_cursor_position += compute_character_width(c);
return Ok(EventState::Consumed); // self.filterd_tree = Some(self.tree.filter(self.input_str()));
} // return Ok(EventState::Consumed);
} // }
Key::Left if matches!(self.focus, Focus::Filter) => { // Key::Delete | Key::Backspace if matches!(self.focus, Focus::Filter) => {
if !self.input.is_empty() && self.input_idx > 0 { // if input_str.width() > 0 {
self.input_idx -= 1; // if !self.input.is_empty() && self.input_idx > 0 {
self.input_cursor_position = self // let last_c = self.input.remove(self.input_idx - 1);
.input_cursor_position // self.input_idx -= 1;
.saturating_sub(compute_character_width(self.input[self.input_idx])); // self.input_cursor_position -= compute_character_width(last_c);
} // }
return Ok(EventState::Consumed);
} // self.filterd_tree = if self.input.is_empty() {
Key::Ctrl('a') => { // None
if !self.input.is_empty() && self.input_idx > 0 { // } else {
self.input_idx = 0; // Some(self.tree.filter(self.input_str()))
self.input_cursor_position = 0 // };
} // return Ok(EventState::Consumed);
return Ok(EventState::Consumed); // }
} // }
Key::Right if matches!(self.focus, Focus::Filter) => { // Key::Left if matches!(self.focus, Focus::Filter) => {
if self.input_idx < self.input.len() { // if !self.input.is_empty() && self.input_idx > 0 {
let next_c = self.input[self.input_idx]; // self.input_idx -= 1;
self.input_idx += 1; // self.input_cursor_position = self
self.input_cursor_position += compute_character_width(next_c); // .input_cursor_position
} // .saturating_sub(compute_character_width(self.input[self.input_idx]));
// }
// return Ok(EventState::Consumed);
// }
// Key::Ctrl('a') => {
// if !self.input.is_empty() && self.input_idx > 0 {
// self.input_idx = 0;
// self.input_cursor_position = 0
// }
// return Ok(EventState::Consumed);
// }
// Key::Right if matches!(self.focus, Focus::Filter) => {
// if self.input_idx < self.input.len() {
// let next_c = self.input[self.input_idx];
// self.input_idx += 1;
// self.input_cursor_position += compute_character_width(next_c);
// }
// return Ok(EventState::Consumed);
// }
// Key::Ctrl('e') => {
// if self.input_idx < self.input.len() {
// self.input_idx = self.input.len();
// self.input_cursor_position = self.input_str().width() as u16;
// }
// return Ok(EventState::Consumed);
// }
Key::Enter if matches!(self.focus, Focus::Filter) => {
self.focus = Focus::Tree;
return Ok(EventState::Consumed); return Ok(EventState::Consumed);
} }
Key::Ctrl('e') => { key if matches!(self.focus, Focus::Filter) => {
if self.input_idx < self.input.len() { if self.filter.event(key)?.is_consumed() {
self.input_idx = self.input.len();
self.input_cursor_position = self.input_str().width() as u16;
}
return Ok(EventState::Consumed); return Ok(EventState::Consumed);
} }
Key::Enter if matches!(self.focus, Focus::Filter) => {
self.focus = Focus::Tree;
return Ok(EventState::Consumed);
} }
key => { key => {
if tree_nav( if tree_nav(

@ -28,7 +28,7 @@ impl DebugComponent {
} }
impl DrawableComponent for DebugComponent { impl DrawableComponent for DebugComponent {
fn draw<B: Backend>(&mut self, f: &mut Frame<B>, _area: Rect, _focused: bool) -> Result<()> { fn draw<B: Backend>(&self, f: &mut Frame<B>, _area: Rect, _focused: bool) -> Result<()> {
if true { if true {
let width = 65; let width = 65;
let height = 10; let height = 10;

@ -35,7 +35,7 @@ impl ErrorComponent {
} }
impl DrawableComponent for ErrorComponent { impl DrawableComponent for ErrorComponent {
fn draw<B: Backend>(&mut self, f: &mut Frame<B>, _area: Rect, _focused: bool) -> Result<()> { fn draw<B: Backend>(&self, f: &mut Frame<B>, _area: Rect, _focused: bool) -> Result<()> {
if self.visible { if self.visible {
let width = 65; let width = 65;
let height = 10; let height = 10;

@ -23,7 +23,7 @@ pub struct HelpComponent {
} }
impl DrawableComponent for HelpComponent { impl DrawableComponent for HelpComponent {
fn draw<B: Backend>(&mut self, f: &mut Frame<B>, _area: Rect, _focused: bool) -> Result<()> { fn draw<B: Backend>(&self, f: &mut Frame<B>, _area: Rect, _focused: bool) -> Result<()> {
if self.visible { if self.visible {
const SIZE: (u16, u16) = (65, 24); const SIZE: (u16, u16) = (65, 24);
let scroll_threshold = SIZE.1 / 3; let scroll_threshold = SIZE.1 / 3;

@ -1,6 +1,7 @@
pub mod command; pub mod command;
pub mod completion; pub mod completion;
pub mod connections; pub mod connections;
pub mod database_filter;
pub mod databases; pub mod databases;
pub mod error; pub mod error;
pub mod help; pub mod help;
@ -18,6 +19,7 @@ pub mod debug;
pub use command::{CommandInfo, CommandText}; pub use command::{CommandInfo, CommandText};
pub use completion::CompletionComponent; pub use completion::CompletionComponent;
pub use connections::ConnectionsComponent; pub use connections::ConnectionsComponent;
pub use database_filter::DatabaseFilterComponent;
pub use databases::DatabasesComponent; pub use databases::DatabasesComponent;
pub use error::ErrorComponent; pub use error::ErrorComponent;
pub use help::HelpComponent; pub use help::HelpComponent;
@ -60,6 +62,10 @@ impl From<bool> for EventState {
} }
pub trait DrawableComponent { pub trait DrawableComponent {
fn draw<B: Backend>(&self, f: &mut Frame<B>, rect: Rect, focused: bool) -> Result<()>;
}
pub trait StatefulDrawableComponent {
fn draw<B: Backend>(&mut self, f: &mut Frame<B>, rect: Rect, focused: bool) -> Result<()>; fn draw<B: Backend>(&mut self, f: &mut Frame<B>, rect: Rect, focused: bool) -> Result<()>;
} }

@ -1,4 +1,4 @@
use super::{Component, DrawableComponent, EventState}; use super::{Component, DrawableComponent, EventState, StatefulDrawableComponent};
use crate::components::command::CommandInfo; use crate::components::command::CommandInfo;
use crate::components::{TableComponent, TableFilterComponent}; use crate::components::{TableComponent, TableFilterComponent};
use crate::config::KeyConfig; use crate::config::KeyConfig;
@ -54,7 +54,7 @@ impl RecordTableComponent {
} }
} }
impl DrawableComponent for RecordTableComponent { impl StatefulDrawableComponent for RecordTableComponent {
fn draw<B: Backend>(&mut self, f: &mut Frame<B>, area: Rect, focused: bool) -> Result<()> { fn draw<B: Backend>(&mut self, f: &mut Frame<B>, area: Rect, focused: bool) -> Result<()> {
let layout = Layout::default() let layout = Layout::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)

@ -57,7 +57,7 @@ impl TabComponent {
} }
impl DrawableComponent for TabComponent { impl DrawableComponent for TabComponent {
fn draw<B: Backend>(&mut self, f: &mut Frame<B>, area: Rect, _focused: bool) -> Result<()> { fn draw<B: Backend>(&self, f: &mut Frame<B>, area: Rect, _focused: bool) -> Result<()> {
let titles = self.names().iter().cloned().map(Spans::from).collect(); let titles = self.names().iter().cloned().map(Spans::from).collect();
let tabs = Tabs::new(titles) let tabs = Tabs::new(titles)
.block(Block::default().borders(Borders::ALL)) .block(Block::default().borders(Borders::ALL))

@ -1,6 +1,6 @@
use super::{ use super::{
utils::scroll_vertical::VerticalScroll, Component, DrawableComponent, EventState, utils::scroll_vertical::VerticalScroll, Component, DrawableComponent, EventState,
TableStatusComponent, TableValueComponent, StatefulDrawableComponent, TableStatusComponent, TableValueComponent,
}; };
use crate::components::command::{self, CommandInfo}; use crate::components::command::{self, CommandInfo};
use crate::config::KeyConfig; use crate::config::KeyConfig;
@ -400,7 +400,7 @@ impl TableComponent {
} }
} }
impl DrawableComponent for TableComponent { impl StatefulDrawableComponent for TableComponent {
fn draw<B: Backend>(&mut self, f: &mut Frame<B>, area: Rect, focused: bool) -> Result<()> { fn draw<B: Backend>(&mut self, f: &mut Frame<B>, area: Rect, focused: bool) -> Result<()> {
let chunks = Layout::default() let chunks = Layout::default()
.vertical_margin(1) .vertical_margin(1)

@ -1,6 +1,6 @@
use super::{ use super::{
compute_character_width, CompletionComponent, Component, DrawableComponent, EventState, compute_character_width, CompletionComponent, Component, EventState, MovableComponent,
MovableComponent, StatefulDrawableComponent,
}; };
use crate::components::command::CommandInfo; use crate::components::command::CommandInfo;
use crate::config::KeyConfig; use crate::config::KeyConfig;
@ -129,7 +129,7 @@ impl TableFilterComponent {
} }
} }
impl DrawableComponent for TableFilterComponent { impl StatefulDrawableComponent for TableFilterComponent {
fn draw<B: Backend>(&mut self, f: &mut Frame<B>, area: Rect, focused: bool) -> Result<()> { fn draw<B: Backend>(&mut self, f: &mut Frame<B>, area: Rect, focused: bool) -> Result<()> {
let query = Paragraph::new(Spans::from(vec![ let query = Paragraph::new(Spans::from(vec![
Span::styled( Span::styled(

@ -43,7 +43,7 @@ impl TableStatusComponent {
} }
impl DrawableComponent for TableStatusComponent { impl DrawableComponent for TableStatusComponent {
fn draw<B: Backend>(&mut self, f: &mut Frame<B>, area: Rect, focused: bool) -> Result<()> { fn draw<B: Backend>(&self, f: &mut Frame<B>, area: Rect, focused: bool) -> Result<()> {
let status = Paragraph::new(Spans::from(vec![ let status = Paragraph::new(Spans::from(vec![
Span::from(format!( Span::from(format!(
"rows: {}, ", "rows: {}, ",

@ -21,7 +21,7 @@ impl TableValueComponent {
} }
impl DrawableComponent for TableValueComponent { impl DrawableComponent for TableValueComponent {
fn draw<B: Backend>(&mut self, f: &mut Frame<B>, area: Rect, focused: bool) -> Result<()> { fn draw<B: Backend>(&self, f: &mut Frame<B>, area: Rect, focused: bool) -> Result<()> {
let paragraph = Paragraph::new(self.value.clone()) let paragraph = Paragraph::new(self.value.clone())
.block(Block::default().borders(Borders::BOTTOM)) .block(Block::default().borders(Borders::BOTTOM))
.style(if focused { .style(if focused {

Loading…
Cancel
Save