Skip to content

Commit 7fb0911

Browse files
committed
BAT-6 Update dependencies and refactor UI layout
This commit updates the `unicode-width` dependency to version 0.2.2 and modifies the `Cargo.toml` and `Cargo.lock` files accordingly. Additionally, the UI layout in `board.rs` has been refactored to improve the organization of the drawing logic, enhancing the overall structure and readability of the code. The command cursor position handling has also been integrated into the UI rendering process.
1 parent f68ec07 commit 7fb0911

4 files changed

Lines changed: 85 additions & 26 deletions

File tree

Cargo.lock

Lines changed: 9 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ ratatui = "0.26"
1919
serde = { version = "1", features = ["derive"] }
2020
serde_json = "1"
2121
ulid = "1"
22+
unicode-width = "0.2"
2223

2324
[dev-dependencies]
2425
tempfile = "3"

src/ui/board.rs

Lines changed: 59 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,71 @@
11
use chrono::Local;
22
use ratatui::{
3-
layout::{Constraint, Direction, Layout},
3+
layout::{Constraint, Direction, Layout, Rect},
44
style::{Color, Modifier, Style},
55
widgets::{Block, Borders, List, ListItem, Paragraph},
66
Frame,
77
};
8+
use unicode_width::UnicodeWidthStr;
89

910
use crate::state::task::Task;
1011
use crate::ui::App;
1112

12-
pub fn draw(f: &mut Frame, app: &App) {
13-
let size = f.size();
14-
13+
fn split_main(area: Rect) -> (Rect, Rect, Rect) {
1514
let rows = Layout::default()
1615
.direction(Direction::Vertical)
1716
.constraints([
1817
Constraint::Length(3),
1918
Constraint::Min(0),
2019
Constraint::Length(7),
2120
])
22-
.split(size);
21+
.split(area);
22+
(rows[0], rows[1], rows[2])
23+
}
24+
25+
fn split_footer(footer: Rect) -> (Rect, Rect, Rect) {
26+
let chunks = Layout::default()
27+
.direction(Direction::Vertical)
28+
.constraints([
29+
Constraint::Length(3),
30+
Constraint::Length(1),
31+
Constraint::Length(3),
32+
])
33+
.split(footer);
34+
(chunks[0], chunks[1], chunks[2])
35+
}
36+
37+
fn input_block_title(app: &App) -> &'static str {
38+
if app.command_focused {
39+
" command "
40+
} else {
41+
" command (press :) "
42+
}
43+
}
44+
45+
/// Terminal cursor position `(column, row)` after `> ` + buffer (insert point), using default cursor style.
46+
pub fn command_cursor_position(term_area: Rect, app: &App) -> Option<(u16, u16)> {
47+
if !app.command_focused {
48+
return None;
49+
}
50+
let (_, _, footer) = split_main(term_area);
51+
let (_, _, input_outer) = split_footer(footer);
52+
let block = Block::default()
53+
.borders(Borders::ALL)
54+
.title(input_block_title(app));
55+
let inner = block.inner(input_outer);
56+
let prefix = "> ";
57+
let w = UnicodeWidthStr::width(prefix)
58+
.saturating_add(UnicodeWidthStr::width(app.command_buffer.as_str()));
59+
let w_u16 = u16::try_from(w).unwrap_or(u16::MAX);
60+
let col = inner.x.saturating_add(w_u16);
61+
let row = inner.y;
62+
Some((col, row))
63+
}
64+
65+
pub fn draw(f: &mut Frame, app: &App) {
66+
let size = f.size();
67+
68+
let (header_area, cols_area, footer_area) = split_main(size);
2369

2470
let today = Local::now().format("%Y-%m-%d").to_string();
2571
let mode_label = match app.view_mode {
@@ -31,7 +77,7 @@ pub fn draw(f: &mut Frame, app: &App) {
3177
let header = Paragraph::new(format!(" TaskWAL — today {} | {}", today, mode_label))
3278
.style(Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD))
3379
.block(Block::default().borders(Borders::ALL));
34-
f.render_widget(header, rows[0]);
80+
f.render_widget(header, header_area);
3581

3682
let cols = Layout::default()
3783
.direction(Direction::Horizontal)
@@ -40,7 +86,7 @@ pub fn draw(f: &mut Frame, app: &App) {
4086
Constraint::Percentage(33),
4187
Constraint::Percentage(34),
4288
])
43-
.split(rows[1]);
89+
.split(cols_area);
4490

4591
render_column(
4692
f,
@@ -70,21 +116,14 @@ pub fn draw(f: &mut Frame, app: &App) {
70116
Color::Green,
71117
);
72118

73-
let footer = Layout::default()
74-
.direction(Direction::Vertical)
75-
.constraints([
76-
Constraint::Length(3),
77-
Constraint::Length(1),
78-
Constraint::Length(3),
79-
])
80-
.split(rows[2]);
119+
let (hint_area, status_area, input_area) = split_footer(footer_area);
81120

82121
let hint = Paragraph::new(
83-
" s start | d done | b back | a Done view | Tab | g stats | q | : command ",
122+
" s start | d done | b back | a Done view | Tab | g stats | q | : command | Esc ",
84123
)
85124
.style(Style::default().fg(Color::DarkGray))
86125
.block(Block::default().borders(Borders::ALL));
87-
f.render_widget(hint, footer[0]);
126+
f.render_widget(hint, hint_area);
88127

89128
let status_text = app
90129
.status_line
@@ -93,13 +132,9 @@ pub fn draw(f: &mut Frame, app: &App) {
93132
let status = Paragraph::new(status_text)
94133
.style(Style::default().fg(Color::DarkGray))
95134
.block(Block::default().borders(Borders::NONE));
96-
f.render_widget(status, footer[1]);
135+
f.render_widget(status, status_area);
97136

98-
let input_label = if app.command_focused {
99-
" command "
100-
} else {
101-
" command (press :) "
102-
};
137+
let input_label = input_block_title(app);
103138
let prompt = if app.command_focused {
104139
format!("> {}", app.command_buffer)
105140
} else {
@@ -121,7 +156,7 @@ pub fn draw(f: &mut Frame, app: &App) {
121156
})
122157
.title(input_label),
123158
);
124-
f.render_widget(input, footer[2]);
159+
f.render_widget(input, input_area);
125160
}
126161

127162
fn render_column(

src/ui/mod.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ pub mod stats;
44
use anyhow::Result;
55
use chrono::{Local, Utc};
66
use crossterm::{
7+
cursor::{Hide, MoveTo, Show},
78
event::{self, Event, KeyCode, KeyModifiers},
89
execute,
910
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
@@ -131,6 +132,20 @@ pub fn run(initial_mode: DailyViewMode) -> Result<()> {
131132
ActiveScreen::Stats => stats::draw(f, &app),
132133
})?;
133134

135+
let size = terminal.size()?;
136+
match (&app.screen, app.command_focused) {
137+
(ActiveScreen::Board, true) => {
138+
if let Some((col, row)) = board::command_cursor_position(size, &app) {
139+
execute!(terminal.backend_mut(), MoveTo(col, row), Show)?;
140+
} else {
141+
execute!(terminal.backend_mut(), Hide)?;
142+
}
143+
}
144+
_ => {
145+
execute!(terminal.backend_mut(), Hide)?;
146+
}
147+
}
148+
134149
if let Event::Key(key) = event::read()? {
135150
match app.screen {
136151
ActiveScreen::Stats => match key.code {
@@ -256,6 +271,7 @@ pub fn run(initial_mode: DailyViewMode) -> Result<()> {
256271
}
257272

258273
disable_raw_mode()?;
274+
execute!(terminal.backend_mut(), Show)?;
259275
execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
260276
Ok(())
261277
}

0 commit comments

Comments
 (0)