@@ -3,14 +3,14 @@ use hypr_transcript::WordState;
33use ratatui:: {
44 Frame ,
55 layout:: { Constraint , Layout , Position , Rect } ,
6- style:: { Color , Modifier , Style } ,
76 text:: { Line , Span } ,
87 widgets:: { Block , Borders , Padding , Paragraph , Wrap } ,
98} ;
109
11- use crate :: app:: App ;
10+ use crate :: { app:: App , theme :: Theme } ;
1211
1312pub fn draw ( frame : & mut Frame , app : & App ) {
13+ let theme = Theme :: default ( ) ;
1414 let [ content_area, status_area] =
1515 Layout :: vertical ( [ Constraint :: Min ( 8 ) , Constraint :: Length ( 1 ) ] ) . areas ( frame. area ( ) ) ;
1616
@@ -20,25 +20,25 @@ pub fn draw(frame: &mut Frame, app: &App) {
2020 ] )
2121 . areas ( content_area) ;
2222
23- draw_notepad ( frame, app, notepad_area) ;
24- draw_sidebar ( frame, app, sidebar_area) ;
25- draw_status_bar ( frame, app, status_area) ;
23+ draw_notepad ( frame, app, notepad_area, & theme ) ;
24+ draw_sidebar ( frame, app, sidebar_area, & theme ) ;
25+ draw_status_bar ( frame, app, status_area, & theme ) ;
2626}
2727
28- fn draw_sidebar ( frame : & mut Frame , app : & App , area : Rect ) {
28+ fn draw_sidebar ( frame : & mut Frame , app : & App , area : Rect , theme : & Theme ) {
2929 let [ metadata_area, meters_area, transcript_area] = Layout :: vertical ( [
3030 Constraint :: Length ( 8 ) ,
3131 Constraint :: Length ( 5 ) ,
3232 Constraint :: Min ( 3 ) ,
3333 ] )
3434 . areas ( area) ;
3535
36- draw_sidebar_metadata ( frame, app, metadata_area) ;
37- draw_sidebar_meters ( frame, app, meters_area) ;
38- draw_transcript ( frame, app, transcript_area) ;
36+ draw_sidebar_metadata ( frame, app, metadata_area, theme ) ;
37+ draw_sidebar_meters ( frame, app, meters_area, theme ) ;
38+ draw_transcript ( frame, app, transcript_area, theme ) ;
3939}
4040
41- fn draw_sidebar_metadata ( frame : & mut Frame , app : & App , area : Rect ) {
41+ fn draw_sidebar_metadata ( frame : & mut Frame , app : & App , area : Rect , theme : & Theme ) {
4242 let elapsed = app. elapsed ( ) ;
4343 let secs = elapsed. as_secs ( ) ;
4444 let time_str = format ! (
@@ -49,10 +49,10 @@ fn draw_sidebar_metadata(frame: &mut Frame, app: &App, area: Rect) {
4949 ) ;
5050
5151 let state_style = match app. state {
52- State :: Active if app. degraded . is_some ( ) => Style :: new ( ) . fg ( Color :: Yellow ) ,
53- State :: Active => Style :: new ( ) . fg ( Color :: Green ) ,
54- State :: Finalizing => Style :: new ( ) . fg ( Color :: Yellow ) ,
55- State :: Inactive => Style :: new ( ) . fg ( Color :: Red ) ,
52+ State :: Active if app. degraded . is_some ( ) => theme . status_degraded ,
53+ State :: Active => theme . status_active ,
54+ State :: Finalizing => theme . status_degraded ,
55+ State :: Inactive => theme . status_inactive ,
5656 } ;
5757
5858 let mut lines = vec ! [
@@ -66,29 +66,23 @@ fn draw_sidebar_metadata(frame: &mut Frame, app: &App, area: Rect) {
6666
6767 if let Some ( err) = app. errors . last ( ) {
6868 lines. push ( Line :: default ( ) ) ;
69- lines. push ( Line :: from ( vec ! [ Span :: styled(
70- "Last error" ,
71- Style :: new( ) . fg( Color :: Red ) ,
72- ) ] ) ) ;
73- lines. push ( Line :: from ( vec ! [ Span :: styled(
74- err,
75- Style :: new( ) . fg( Color :: DarkGray ) ,
76- ) ] ) ) ;
69+ lines. push ( Line :: from ( vec ! [ Span :: styled( "Last error" , theme. error) ] ) ) ;
70+ lines. push ( Line :: from ( vec ! [ Span :: styled( err, theme. muted) ] ) ) ;
7771 }
7872
7973 let block = Block :: new ( )
8074 . borders ( Borders :: ALL )
81- . border_style ( Style :: new ( ) . fg ( Color :: DarkGray ) )
75+ . border_style ( theme . border )
8276 . title ( " Session " )
8377 . padding ( Padding :: new ( 1 , 1 , 0 , 0 ) ) ;
8478
8579 frame. render_widget ( Paragraph :: new ( lines) . block ( block) , area) ;
8680}
8781
88- fn draw_sidebar_meters ( frame : & mut Frame , app : & App , area : Rect ) {
82+ fn draw_sidebar_meters ( frame : & mut Frame , app : & App , area : Rect , theme : & Theme ) {
8983 let block = Block :: new ( )
9084 . borders ( Borders :: ALL )
91- . border_style ( Style :: new ( ) . fg ( Color :: DarkGray ) )
85+ . border_style ( theme . border )
9286 . title ( " Audio " )
9387 . padding ( Padding :: new ( 1 , 1 , 0 , 0 ) ) ;
9488 let inner = block. inner ( area) ;
@@ -107,7 +101,7 @@ fn draw_sidebar_meters(frame: &mut Frame, app: &App, area: Rect) {
107101 let active_width = ( ( waveform_width as f64 ) * 0.78 ) . round ( ) as usize ;
108102 let active_width = active_width. clamp ( 8 , waveform_width) ;
109103 let side_gutter = ( waveform_width. saturating_sub ( active_width) ) / 2 ;
110- let waveform = build_waveform_spans ( app, active_width) ;
104+ let waveform = build_waveform_spans ( app, active_width, theme ) ;
111105
112106 let mut lines = ( 0 ..inner. height )
113107 . map ( |_| Line :: from ( " " . repeat ( inner. width as usize ) ) )
@@ -125,13 +119,13 @@ fn draw_sidebar_meters(frame: &mut Frame, app: &App, area: Rect) {
125119 lines[ middle_row] = Line :: from ( middle_spans) ;
126120
127121 if app. mic_muted {
128- lines[ 0 ] = Line :: from ( Span :: styled ( "mic muted" , Style :: new ( ) . fg ( Color :: DarkGray ) ) ) ;
122+ lines[ 0 ] = Line :: from ( Span :: styled ( "mic muted" , theme . muted ) ) ;
129123 }
130124
131125 frame. render_widget ( Paragraph :: new ( lines) , inner) ;
132126}
133127
134- fn build_waveform_spans ( app : & App , width : usize ) -> Vec < Span < ' static > > {
128+ fn build_waveform_spans ( app : & App , width : usize , theme : & Theme ) -> Vec < Span < ' static > > {
135129 if width == 0 {
136130 return Vec :: new ( ) ;
137131 }
@@ -156,7 +150,7 @@ fn build_waveform_spans(app: &App, width: usize) -> Vec<Span<'static>> {
156150 let raw_value = column_energy ( & combined, x, width) as f64 ;
157151 let envelope = edge_envelope ( x, width) ;
158152 let value = ( raw_value * envelope) . round ( ) as u64 ;
159- let mut style = Style :: new ( ) . fg ( Color :: Red ) ;
153+ let mut style = theme . waveform_normal ;
160154
161155 let normalized = ( value as f64 / 1000.0 ) . clamp ( 0.0 , 1.0 ) ;
162156 let level = if value == 0 {
@@ -166,13 +160,13 @@ fn build_waveform_spans(app: &App, width: usize) -> Vec<Span<'static>> {
166160 } ;
167161
168162 if level == 0 || envelope < 0.22 {
169- style = Style :: new ( ) . fg ( Color :: DarkGray ) ;
163+ style = theme . waveform_silent ;
170164 } else if level >= 6 && envelope > 0.7 {
171- style = Style :: new ( ) . fg ( Color :: LightRed ) ;
165+ style = theme . waveform_hot ;
172166 }
173167
174168 if app. mic_muted {
175- style = Style :: new ( ) . fg ( Color :: DarkGray ) ;
169+ style = theme . waveform_silent ;
176170 }
177171
178172 spans. push ( Span :: styled ( level_char ( level) . to_string ( ) , style) ) ;
@@ -263,43 +257,35 @@ fn level_char(level: u8) -> char {
263257 }
264258}
265259
266- fn draw_transcript ( frame : & mut Frame , app : & App , area : Rect ) {
260+ fn draw_transcript ( frame : & mut Frame , app : & App , area : Rect , theme : & Theme ) {
267261 let mut spans: Vec < Span > = Vec :: new ( ) ;
268262
269263 for word in & app. words {
270264 let style = match word. state {
271- WordState :: Final => Style :: new ( ) ,
272- WordState :: Pending => Style :: new ( ) . fg ( Color :: Yellow ) ,
265+ WordState :: Final => theme . transcript_final ,
266+ WordState :: Pending => theme . transcript_pending ,
273267 } ;
274268 spans. push ( Span :: styled ( & word. text , style) ) ;
275269 spans. push ( Span :: raw ( " " ) ) ;
276270 }
277271
278272 if !app. partials . is_empty ( ) {
279273 for partial in & app. partials {
280- spans. push ( Span :: styled (
281- & partial. text ,
282- Style :: new ( )
283- . fg ( Color :: DarkGray )
284- . add_modifier ( Modifier :: ITALIC ) ,
285- ) ) ;
274+ spans. push ( Span :: styled ( & partial. text , theme. transcript_partial ) ) ;
286275 spans. push ( Span :: raw ( " " ) ) ;
287276 }
288277 }
289278
290279 if spans. is_empty ( ) {
291- spans. push ( Span :: styled (
292- "Waiting for speech..." ,
293- Style :: new ( ) . fg ( Color :: DarkGray ) . italic ( ) ,
294- ) ) ;
280+ spans. push ( Span :: styled ( "Waiting for speech..." , theme. placeholder ) ) ;
295281 }
296282
297283 let text = vec ! [ Line :: from( spans) ] ;
298284
299285 let border_style = if app. transcript_focused && !app. memo_focused {
300- Style :: new ( ) . fg ( Color :: Cyan )
286+ theme . border_focused
301287 } else {
302- Style :: new ( ) . fg ( Color :: DarkGray )
288+ theme . border
303289 } ;
304290
305291 let block = Block :: new ( )
@@ -316,15 +302,15 @@ fn draw_transcript(frame: &mut Frame, app: &App, area: Rect) {
316302 frame. render_widget ( paragraph, area) ;
317303}
318304
319- fn draw_notepad ( frame : & mut Frame , app : & App , area : Rect ) {
305+ fn draw_notepad ( frame : & mut Frame , app : & App , area : Rect , theme : & Theme ) {
320306 if area. width < 3 || area. height < 3 {
321307 return ;
322308 }
323309
324310 let border_style = if app. memo_focused {
325- Style :: new ( ) . fg ( Color :: Cyan )
311+ theme . border_focused
326312 } else {
327- Style :: new ( ) . fg ( Color :: DarkGray )
313+ theme . border
328314 } ;
329315
330316 let block = Block :: new ( )
@@ -339,7 +325,7 @@ fn draw_notepad(frame: &mut Frame, app: &App, area: Rect) {
339325 let lines = if app. memo_is_empty ( ) && !app. memo_focused {
340326 vec ! [ Line :: from( vec![ Span :: styled(
341327 "press [m] to start writing notes..." ,
342- Style :: new ( ) . fg ( Color :: DarkGray ) . italic ( ) ,
328+ theme . placeholder ,
343329 ) ] ) ]
344330 } else {
345331 view. lines . into_iter ( ) . map ( Line :: from) . collect :: < Vec < _ > > ( )
@@ -356,38 +342,35 @@ fn draw_notepad(frame: &mut Frame, app: &App, area: Rect) {
356342 }
357343}
358344
359- fn draw_status_bar ( frame : & mut Frame , app : & App , area : Rect ) {
345+ fn draw_status_bar ( frame : & mut Frame , app : & App , area : Rect , theme : & Theme ) {
360346 let word_count = app. words . len ( ) ;
361347
362348 let line = if app. memo_focused {
363349 Line :: from ( vec ! [
364- Span :: styled( " [esc]" , Style :: new ( ) . fg ( Color :: DarkGray ) ) ,
350+ Span :: styled( " [esc]" , theme . shortcut_key ) ,
365351 Span :: raw( " transcript " ) ,
366- Span :: styled( "[tab]" , Style :: new ( ) . fg ( Color :: DarkGray ) ) ,
352+ Span :: styled( "[tab]" , theme . shortcut_key ) ,
367353 Span :: raw( " toggle " ) ,
368- Span :: styled( "[ctrl+left/right]" , Style :: new ( ) . fg ( Color :: DarkGray ) ) ,
354+ Span :: styled( "[ctrl+left/right]" , theme . shortcut_key ) ,
369355 Span :: raw( " panes " ) ,
370- Span :: styled( "[ctrl+u]" , Style :: new ( ) . fg ( Color :: DarkGray ) ) ,
356+ Span :: styled( "[ctrl+u]" , theme . shortcut_key ) ,
371357 Span :: raw( " clear " ) ,
372- Span :: styled( "[ctrl+c]" , Style :: new ( ) . fg ( Color :: DarkGray ) ) ,
358+ Span :: styled( "[ctrl+c]" , theme . shortcut_key ) ,
373359 Span :: raw( " quit " ) ,
374360 ] )
375361 } else {
376362 Line :: from ( vec ! [
377- Span :: styled( " [q]" , Style :: new ( ) . fg ( Color :: DarkGray ) ) ,
363+ Span :: styled( " [q]" , theme . shortcut_key ) ,
378364 Span :: raw( " quit " ) ,
379- Span :: styled( "[j/k]" , Style :: new ( ) . fg ( Color :: DarkGray ) ) ,
365+ Span :: styled( "[j/k]" , theme . shortcut_key ) ,
380366 Span :: raw( " transcript " ) ,
381- Span :: styled( "[m]" , Style :: new ( ) . fg ( Color :: DarkGray ) ) ,
367+ Span :: styled( "[m]" , theme . shortcut_key ) ,
382368 Span :: raw( " notepad " ) ,
383- Span :: styled( "[tab]" , Style :: new ( ) . fg ( Color :: DarkGray ) ) ,
369+ Span :: styled( "[tab]" , theme . shortcut_key ) ,
384370 Span :: raw( " toggle " ) ,
385- Span :: styled( "[ctrl+left/right]" , Style :: new ( ) . fg ( Color :: DarkGray ) ) ,
371+ Span :: styled( "[ctrl+left/right]" , theme . shortcut_key ) ,
386372 Span :: raw( " panes " ) ,
387- Span :: styled(
388- format!( "{word_count} words" ) ,
389- Style :: new( ) . fg( Color :: DarkGray ) ,
390- ) ,
373+ Span :: styled( format!( "{word_count} words" ) , theme. muted) ,
391374 ] )
392375 } ;
393376
0 commit comments