implement side-scrolling

pull/1/head
Takayuki Maeda 3 years ago
parent 8a0f5b9d3b
commit a4d9dc1562

@ -1,6 +1,5 @@
use sqlx::mysql::MySqlPool;
use sqlx::Row;
use tui::widgets::List;
use tui::widgets::{ListState, TableState};
pub enum InputMode {
@ -26,6 +25,66 @@ pub struct Table {
pub name: String,
}
pub struct RecordTable {
pub state: TableState,
pub headers: Vec<String>,
pub rows: Vec<Vec<String>>,
pub column_index: u64,
}
impl Default for RecordTable {
fn default() -> Self {
Self {
state: TableState::default(),
headers: vec![],
rows: vec![],
column_index: 0,
}
}
}
impl RecordTable {
pub fn next(&mut self) {
let i = match self.state.selected() {
Some(i) => {
if i >= self.rows.len() - 1 {
0
} else {
i + 1
}
}
None => 0,
};
self.state.select(Some(i));
}
pub fn previous(&mut self) {
let i = match self.state.selected() {
Some(i) => {
if i == 0 {
self.rows.len() - 1
} else {
i - 1
}
}
None => 0,
};
self.state.select(Some(i));
}
pub fn next_column(&mut self) {
if (self.column_index as usize) < self.headers.len() - 9 {
self.column_index += 1
}
}
pub fn previous_column(&mut self) {
if self.column_index != 0 {
self.column_index -= 1
}
}
}
impl Database {
pub async fn new(name: String, pool: &MySqlPool) -> anyhow::Result<Self> {
let tables = sqlx::query(format!("show tables from {}", name).as_str())
@ -76,6 +135,7 @@ pub struct App {
pub messages: Vec<Vec<String>>,
pub selected_database: ListState,
pub databases: Vec<Database>,
pub record_table: RecordTable,
pub focus_type: FocusType,
}
@ -87,6 +147,7 @@ impl Default for App {
messages: Vec::new(),
selected_database: ListState::default(),
databases: Vec::new(),
record_table: RecordTable::default(),
focus_type: FocusType::Dabatases(false),
}
}

@ -1,3 +1,13 @@
pub mod database_list;
pub mod record_table;
pub mod table_list;
use crate::app::App;
use crossterm::event::KeyCode;
pub fn handle_app(key: KeyCode, app: &mut App) {
match key {
KeyCode::Char('e') => (),
_ => (),
}
}

@ -35,43 +35,6 @@ pub struct StatefulTable {
items: Vec<Vec<String>>,
}
impl StatefulTable {
fn new() -> StatefulTable {
StatefulTable {
state: TableState::default(),
headers: vec![],
items: vec![],
}
}
pub fn next(&mut self) {
let i = match self.state.selected() {
Some(i) => {
if i >= self.items.len() - 1 {
0
} else {
i + 1
}
}
None => 0,
};
self.state.select(Some(i));
}
pub fn previous(&mut self) {
let i = match self.state.selected() {
Some(i) => {
if i == 0 {
self.items.len() - 1
} else {
i - 1
}
}
None => 0,
};
self.state.select(Some(i));
}
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
enable_raw_mode()?;
@ -149,14 +112,13 @@ async fn main() -> anyhow::Result<()> {
}
records.push(row_vec)
}
app.record_table.rows = records;
app.record_table.headers = headers;
terminal.clear()?;
let mut table = StatefulTable::new();
table.items = records;
table.headers = headers;
loop {
terminal.draw(|f| ui::draw(f, &mut app, &mut table).unwrap())?;
terminal.draw(|f| ui::draw(f, &mut app).unwrap())?;
match rx.recv()? {
Event::Input(event) => match app.input_mode {
InputMode::Normal => match event.code {
@ -185,8 +147,16 @@ async fn main() -> anyhow::Result<()> {
app.focus_type = FocusType::Dabatases(false)
}
}
KeyCode::Right => match app.focus_type {
FocusType::Records(true) => app.record_table.next_column(),
_ => (),
},
KeyCode::Left => match app.focus_type {
FocusType::Records(true) => app.record_table.previous_column(),
_ => (),
},
KeyCode::Up => match app.focus_type {
FocusType::Records(true) => table.previous(),
FocusType::Records(true) => app.record_table.previous(),
FocusType::Dabatases(true) => app.previous(),
FocusType::Tables(true) => match app.selected_database.selected() {
Some(index) => app.databases[index].previous(),
@ -195,7 +165,7 @@ async fn main() -> anyhow::Result<()> {
_ => (),
},
KeyCode::Down => match app.focus_type {
FocusType::Records(true) => table.next(),
FocusType::Records(true) => app.record_table.next(),
FocusType::Dabatases(true) => app.next(),
FocusType::Tables(true) => match app.selected_database.selected() {
Some(index) => {
@ -223,7 +193,6 @@ async fn main() -> anyhow::Result<()> {
let mut row_vec = vec![];
for col in row.columns() {
let col_name = col.name();
// println!("{}", col.type_info().name());
match col.type_info().clone().name() {
"INT" => {
let value: i32 = row.try_get(col_name).unwrap_or(0);
@ -240,8 +209,8 @@ async fn main() -> anyhow::Result<()> {
records.push(row_vec)
}
table.items = records;
table.headers = headers;
app.record_table.rows = records;
app.record_table.headers = headers;
}
None => (),
},

@ -16,11 +16,7 @@ use tui::{
};
use unicode_width::UnicodeWidthStr;
pub fn draw<B: Backend>(
f: &mut Frame<'_, B>,
app: &mut App,
table: &mut StatefulTable,
) -> anyhow::Result<()> {
pub fn draw<B: Backend>(f: &mut Frame<'_, B>, app: &mut App) -> anyhow::Result<()> {
let main_chunks = Layout::default()
.direction(Direction::Vertical)
.margin(2)
@ -78,72 +74,31 @@ pub fn draw<B: Backend>(
let right_chunks = Layout::default()
.direction(Direction::Vertical)
.constraints(
[
Constraint::Length(1),
Constraint::Length(3),
Constraint::Min(1),
]
.as_ref(),
)
.constraints([Constraint::Length(3), Constraint::Min(1)].as_ref())
.split(main_chunks[1]);
let (msg, style) = match app.input_mode {
InputMode::Normal => (
vec![
Span::raw("Press "),
Span::styled("q", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(" to exit, "),
Span::styled("e", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(" to start editing."),
],
Style::default().add_modifier(Modifier::RAPID_BLINK),
),
InputMode::Editing => (
vec![
Span::raw("Press "),
Span::styled("Esc", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(" to stop editing, "),
Span::styled("Enter", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(" to record the message"),
],
Style::default(),
),
};
let mut text = Text::from(Spans::from(msg));
text.patch_style(style);
let help_message = Paragraph::new(text);
f.render_widget(help_message, right_chunks[0]);
let input = Paragraph::new(app.input.as_ref())
.style(match app.input_mode {
InputMode::Normal => Style::default(),
InputMode::Editing => Style::default().fg(Color::Yellow),
})
.block(Block::default().borders(Borders::ALL).title("Input"));
f.render_widget(input, right_chunks[1]);
.block(Block::default().borders(Borders::ALL).title("Query"));
f.render_widget(input, right_chunks[0]);
match app.input_mode {
InputMode::Normal =>
// Hide the cursor. `Frame` does this by default, so we don't need to do anything here
{}
InputMode::Editing => {
// Make the cursor visible and ask tui-rs to put it at the specified coordinates after rendering
f.set_cursor(
// Put cursor past the end of the input text
right_chunks[1].x + app.input.width() as u16 + 1,
// Move one line down, from the border to the input line
right_chunks[1].y + 1,
)
}
InputMode::Normal => (),
InputMode::Editing => f.set_cursor(
right_chunks[0].x + app.input.width() as u16 + 1,
right_chunks[0].y + 1,
),
}
let header_cells = table
let header_cells = app
.record_table
.headers
.iter()
.map(|h| Cell::from(h.to_string()).style(Style::default().fg(Color::White)));
let header = Row::new(header_cells).height(1).bottom_margin(1);
let rows = table.items.iter().map(|item| {
let rows = app.record_table.rows.iter().map(|item| {
let height = item
.iter()
.map(|content| content.chars().filter(|c| *c == '\n').count())
@ -155,6 +110,17 @@ pub fn draw<B: Backend>(
.map(|c| Cell::from(c.to_string()).style(Style::default().fg(Color::White)));
Row::new(cells).height(height as u16).bottom_margin(1)
});
let widths = (0..app.record_table.headers.len() + 1)
.map(|idx| {
if idx >= app.record_table.column_index as usize
&& idx <= app.record_table.column_index as usize + 9
{
Constraint::Percentage(10)
} else {
Constraint::Percentage(0)
}
})
.collect::<Vec<Constraint>>();
let t = Table::new(rows)
.header(header)
.block(Block::default().borders(Borders::ALL).title("Records"))
@ -164,19 +130,8 @@ pub fn draw<B: Backend>(
FocusType::Records(true) => Style::default().fg(Color::Green),
_ => Style::default(),
})
.widths(&[
Constraint::Percentage(10),
Constraint::Percentage(10),
Constraint::Percentage(10),
Constraint::Percentage(10),
Constraint::Percentage(10),
Constraint::Percentage(10),
Constraint::Percentage(10),
Constraint::Percentage(10),
Constraint::Percentage(10),
Constraint::Percentage(10),
]);
f.render_stateful_widget(t, right_chunks[2], &mut table.state);
.widths(&widths);
f.render_stateful_widget(t, right_chunks[1], &mut app.record_table.state);
Ok(())
}

Loading…
Cancel
Save