11use chrono:: Local ;
22use 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
910use crate :: state:: task:: Task ;
1011use 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
127162fn render_column (
0 commit comments