@@ -58,6 +58,49 @@ fn hsv_to_rgb(h: f32, s: f32, v: f32) -> (u8, u8, u8) {
5858 )
5959}
6060
61+ // Color constants for consistent theming
62+ mod colors {
63+ pub const GREEN : ( u8 , u8 , u8 ) = ( 142 , 192 , 124 ) ;
64+ pub const RED : ( u8 , u8 , u8 ) = ( 204 , 36 , 29 ) ;
65+ pub const GRAY : ( u8 , u8 , u8 ) = ( 128 , 128 , 128 ) ;
66+ pub const GOLD : ( u8 , u8 , u8 ) = ( 215 , 153 , 33 ) ;
67+ pub const TREE : ( u8 , u8 , u8 ) = ( 55 , 55 , 50 ) ;
68+ pub const YELLOW : ( u8 , u8 , u8 ) = ( 250 , 189 , 47 ) ;
69+ pub const PURPLE : ( u8 , u8 , u8 ) = ( 180 , 142 , 173 ) ;
70+ pub const MUTED : ( u8 , u8 , u8 ) = ( 90 , 90 , 90 ) ;
71+ pub const PR_NUMBER : ( u8 , u8 , u8 ) = ( 90 , 78 , 98 ) ;
72+ pub const PR_ARROW : ( u8 , u8 , u8 ) = ( 100 , 105 , 105 ) ;
73+ pub const UPSTREAM : ( u8 , u8 , u8 ) = ( 88 , 88 , 88 ) ;
74+ pub const STACKED_ON : ( u8 , u8 , u8 ) = ( 90 , 120 , 87 ) ;
75+ }
76+
77+ /// Dimming factor for display. Wraps RGB values and applies a multiplier.
78+ #[ derive( Clone , Copy ) ]
79+ struct Dim ( f32 ) ;
80+
81+ impl Dim {
82+ fn full ( ) -> Self {
83+ Dim ( 1.0 )
84+ }
85+ fn dimmed ( ) -> Self {
86+ Dim ( 0.75 )
87+ }
88+
89+ /// Apply dimming to RGB values
90+ fn rgb ( & self , r : u8 , g : u8 , b : u8 ) -> ( u8 , u8 , u8 ) {
91+ (
92+ ( r as f32 * self . 0 ) as u8 ,
93+ ( g as f32 * self . 0 ) as u8 ,
94+ ( b as f32 * self . 0 ) as u8 ,
95+ )
96+ }
97+
98+ /// Apply dimming to a color tuple
99+ fn apply ( & self , color : ( u8 , u8 , u8 ) ) -> ( u8 , u8 , u8 ) {
100+ self . rgb ( color. 0 , color. 1 , color. 2 )
101+ }
102+ }
103+
61104// This is an important refactoring.
62105#[ derive( Parser ) ]
63106#[ command( author, version, about) ]
@@ -490,6 +533,11 @@ fn recur_tree(
490533 } else {
491534 pr_author. is_some_and ( |author| !display_authors. contains ( & author. to_string ( ) ) )
492535 } ;
536+ let dim = if is_dimmed {
537+ Dim :: dimmed ( )
538+ } else {
539+ Dim :: full ( )
540+ } ;
493541
494542 // Check if branch is remote-only (not local)
495543 let is_remote_only = !git_repo. branch_exists ( & branch. name ) ;
@@ -512,18 +560,21 @@ fn recur_tree(
512560 } else {
513561 print ! ( " " ) ;
514562 }
563+ // Tree lines stay at full color regardless of dimming
515564 for _ in 0 ..depth {
516- print ! ( "{}" , "┃ " . truecolor( 55 , 55 , 50 ) ) ;
565+ print ! (
566+ "{}" ,
567+ "┃ " . truecolor( colors:: TREE . 0 , colors:: TREE . 1 , colors:: TREE . 2 )
568+ ) ;
517569 }
518- let branch_name_colored = if is_dimmed {
519- branch. name . truecolor ( 90 , 90 , 90 )
520- } else {
521- branch. name . truecolor ( 128 , 128 , 128 )
522- } ;
570+ let branch_color = dim. apply ( colors:: GRAY ) ;
571+ let muted_color = dim. apply ( colors:: MUTED ) ;
523572 println ! (
524573 "{} {}" ,
525- branch_name_colored,
526- "(remote)" . truecolor( 90 , 90 , 90 )
574+ branch
575+ . name
576+ . truecolor( branch_color. 0 , branch_color. 1 , branch_color. 2 ) ,
577+ "(remote)" . truecolor( muted_color. 0 , muted_color. 1 , muted_color. 2 )
527578 ) ;
528579
529580 // Recurse into children
@@ -552,38 +603,51 @@ fn recur_tree(
552603 false
553604 } ;
554605
606+ // Tree lines stay at full color regardless of dimming
555607 for _ in 0 ..depth {
556- print ! ( "{}" , "┃ " . truecolor( 55 , 55 , 50 ) ) ;
608+ print ! (
609+ "{}" ,
610+ "┃ " . truecolor( colors:: TREE . 0 , colors:: TREE . 1 , colors:: TREE . 2 )
611+ ) ;
557612 }
558613
559614 // Branch name coloring: green for synced, red for diverged, bold for current branch
560- // Dimmed branches (filtered by display_authors) are shown in gray
561- let branch_name_colored = if is_dimmed {
562- branch. name . truecolor ( 90 , 90 , 90 )
615+ let branch_color = if branch_status. is_descendent {
616+ dim. apply ( colors:: GREEN )
563617 } else {
564- match ( is_current_branch, branch_status. is_descendent ) {
565- ( true , true ) => branch. name . truecolor ( 142 , 192 , 124 ) . bold ( ) ,
566- ( true , false ) => branch. name . red ( ) . bold ( ) ,
567- ( false , true ) => branch. name . truecolor ( 142 , 192 , 124 ) ,
568- ( false , false ) => branch. name . red ( ) ,
569- }
618+ dim. apply ( colors:: RED )
619+ } ;
620+ let branch_name_colored = if is_current_branch {
621+ branch
622+ . name
623+ . truecolor ( branch_color. 0 , branch_color. 1 , branch_color. 2 )
624+ . bold ( )
625+ } else {
626+ branch
627+ . name
628+ . truecolor ( branch_color. 0 , branch_color. 1 , branch_color. 2 )
570629 } ;
571630
572631 // Remote-only indicator
573632 let remote_indicator = if is_remote_only {
574- " (remote)" . truecolor ( 90 , 90 , 90 ) . to_string ( )
633+ let muted = dim. apply ( colors:: MUTED ) ;
634+ format ! ( " {}" , "(remote)" . truecolor( muted. 0 , muted. 1 , muted. 2 ) )
575635 } else {
576636 String :: new ( )
577637 } ;
578638
579639 // Get diff stats from LKG ancestor to current branch
580640 let diff_stats = if let Some ( lkg_parent) = branch. lkg_parent . as_ref ( ) {
581641 match git_repo. diff_stats ( lkg_parent, & branch_status. sha ) {
582- Ok ( ( adds, dels) ) => format ! (
583- " {} {}" ,
584- format!( "+{}" , adds) . green( ) ,
585- format!( "-{}" , dels) . red( )
586- ) ,
642+ Ok ( ( adds, dels) ) => {
643+ let green = dim. apply ( colors:: GREEN ) ;
644+ let red = dim. apply ( colors:: RED ) ;
645+ format ! (
646+ " {} {}" ,
647+ format!( "+{}" , adds) . truecolor( green. 0 , green. 1 , green. 2 ) ,
648+ format!( "-{}" , dels) . truecolor( red. 0 , red. 1 , red. 2 )
649+ )
650+ }
587651 Err ( _) => String :: new ( ) , // Silently skip on error
588652 }
589653 } else {
@@ -595,16 +659,27 @@ fn recur_tree(
595659 match get_local_status ( ) {
596660 Ok ( status) if !status. is_clean ( ) => {
597661 let mut parts = Vec :: new ( ) ;
662+ let green = dim. apply ( colors:: GREEN ) ;
663+ let yellow = dim. apply ( colors:: YELLOW ) ;
664+ let gray = dim. apply ( colors:: GRAY ) ;
598665 if status. staged > 0 {
599- parts. push ( format ! ( "+{}" , status. staged) . green ( ) . to_string ( ) ) ;
666+ parts. push (
667+ format ! ( "+{}" , status. staged)
668+ . truecolor ( green. 0 , green. 1 , green. 2 )
669+ . to_string ( ) ,
670+ ) ;
600671 }
601672 if status. unstaged > 0 {
602- parts. push ( format ! ( "~{}" , status. unstaged) . yellow ( ) . to_string ( ) ) ;
673+ parts. push (
674+ format ! ( "~{}" , status. unstaged)
675+ . truecolor ( yellow. 0 , yellow. 1 , yellow. 2 )
676+ . to_string ( ) ,
677+ ) ;
603678 }
604679 if status. untracked > 0 {
605680 parts. push (
606681 format ! ( "?{}" , status. untracked)
607- . truecolor ( 128 , 128 , 128 )
682+ . truecolor ( gray . 0 , gray . 1 , gray . 2 )
608683 . to_string ( ) ,
609684 ) ;
610685 }
@@ -617,70 +692,100 @@ fn recur_tree(
617692 } ;
618693
619694 if verbose {
695+ let gold = dim. apply ( colors:: GOLD ) ;
696+ let stacked_on = dim. apply ( colors:: STACKED_ON ) ;
697+ let yellow = dim. apply ( colors:: YELLOW ) ;
698+ let red = dim. apply ( colors:: RED ) ;
699+ let green = dim. apply ( colors:: GREEN ) ;
700+ let upstream_color = dim. apply ( colors:: UPSTREAM ) ;
701+
620702 println ! (
621703 "{}{}{}{} ({}) {}{}{}{}" ,
622704 branch_name_colored,
623705 remote_indicator,
624706 diff_stats,
625707 local_status,
626- branch_status. sha[ ..8 ] . truecolor( 215 , 153 , 33 ) ,
708+ branch_status. sha[ ..8 ] . truecolor( gold . 0 , gold . 1 , gold . 2 ) ,
627709 {
628710 let details: String = if branch_status. exists {
629711 if branch_status. is_descendent {
630712 format!(
631713 "{} {}" ,
632- "is stacked on" . truecolor( 90 , 120 , 87 ) ,
633- branch_status. parent_branch. yellow( )
714+ "is stacked on" . truecolor( stacked_on. 0 , stacked_on. 1 , stacked_on. 2 ) ,
715+ branch_status
716+ . parent_branch
717+ . truecolor( yellow. 0 , yellow. 1 , yellow. 2 )
634718 )
635719 } else {
636720 format!(
637721 "{} {}" ,
638- "diverges from" . red( ) ,
639- branch_status. parent_branch. yellow( )
722+ "diverges from" . truecolor( red. 0 , red. 1 , red. 2 ) ,
723+ branch_status
724+ . parent_branch
725+ . truecolor( yellow. 0 , yellow. 1 , yellow. 2 )
640726 )
641727 }
642728 } else {
643- "does not exist!" . bright_red ( ) . to_string( )
729+ "does not exist!" . truecolor ( red . 0 , red . 1 , red . 2 ) . to_string( )
644730 } ;
645731 details
646732 } ,
647733 {
648734 if let Some ( upstream_status) = branch_status. upstream_status {
649735 format!(
650736 " (upstream {} is {})" ,
651- upstream_status. symbolic_name. truecolor( 88 , 88 , 88 ) ,
737+ upstream_status. symbolic_name. truecolor(
738+ upstream_color. 0 ,
739+ upstream_color. 1 ,
740+ upstream_color. 2
741+ ) ,
652742 if upstream_status. synced {
653- "synced" . truecolor( 142 , 192 , 124 )
743+ "synced" . truecolor( green . 0 , green . 1 , green . 2 )
654744 } else {
655- "not synced" . bright_red ( )
745+ "not synced" . truecolor ( red . 0 , red . 1 , red . 2 )
656746 }
657747 )
658748 } else {
659- format!( " ({})" , "no upstream" . truecolor( 215 , 153 , 33 ) )
749+ format!( " ({})" , "no upstream" . truecolor( gold . 0 , gold . 1 , gold . 2 ) )
660750 }
661751 } ,
662752 {
663753 if let Some ( lkg_parent) = branch. lkg_parent. as_ref( ) {
664- format!( " (lkg parent {})" , lkg_parent[ ..8 ] . truecolor( 215 , 153 , 33 ) )
754+ format!(
755+ " (lkg parent {})" ,
756+ lkg_parent[ ..8 ] . truecolor( gold. 0 , gold. 1 , gold. 2 )
757+ )
665758 } else {
666759 String :: new( )
667760 }
668761 } ,
669- match branch. stack_method {
670- StackMethod :: ApplyMerge => " (apply-merge)" . truecolor( 142 , 192 , 124 ) ,
671- StackMethod :: Merge => " (merge)" . truecolor( 142 , 192 , 124 ) ,
762+ {
763+ let method_color = dim. apply( colors:: GREEN ) ;
764+ match branch. stack_method {
765+ StackMethod :: ApplyMerge => {
766+ " (apply-merge)" . truecolor( method_color. 0 , method_color. 1 , method_color. 2 )
767+ }
768+ StackMethod :: Merge => {
769+ " (merge)" . truecolor( method_color. 0 , method_color. 1 , method_color. 2 )
770+ }
771+ }
672772 } ,
673773 ) ;
674774 if let Some ( note) = & branch. note {
675775 print ! ( " " ) ;
776+ // Tree lines stay at full color regardless of dimming
676777 for _ in 0 ..depth {
677- print ! ( "{}" , "┃ " . truecolor( 55 , 55 , 50 ) ) ;
778+ print ! (
779+ "{}" ,
780+ "┃ " . truecolor( colors:: TREE . 0 , colors:: TREE . 1 , colors:: TREE . 2 )
781+ ) ;
678782 }
679783
680784 let first_line = note. lines ( ) . next ( ) . unwrap_or ( "" ) ;
785+ // Note: keeping blue for notes as it's distinct from status colors
681786 println ! (
682787 " {} {}" ,
683- "›" . truecolor( 55 , 55 , 50 ) ,
788+ "›" . truecolor( colors :: TREE . 0 , colors :: TREE . 1 , colors :: TREE . 2 ) ,
684789 if is_current_branch {
685790 first_line. bright_blue( ) . bold( )
686791 } else {
@@ -693,22 +798,38 @@ fn recur_tree(
693798 let pr_info = if let Some ( cache) = pr_cache {
694799 if let Some ( pr) = cache. get ( & branch. name ) {
695800 let state = pr. display_state ( ) ;
801+ let gray = dim. apply ( colors:: GRAY ) ;
802+ let green = dim. apply ( colors:: GREEN ) ;
803+ let purple = dim. apply ( colors:: PURPLE ) ;
804+ let red = dim. apply ( colors:: RED ) ;
696805 let state_colored = match state {
697806 github:: PrDisplayState :: Draft => {
698- format ! ( "[{}]" , state) . truecolor ( 128 , 128 , 128 )
807+ format ! ( "[{}]" , state) . truecolor ( gray. 0 , gray. 1 , gray. 2 )
808+ }
809+ github:: PrDisplayState :: Open => {
810+ format ! ( "[{}]" , state) . truecolor ( green. 0 , green. 1 , green. 2 )
699811 }
700- github:: PrDisplayState :: Open => format ! ( "[{}]" , state) . truecolor ( 142 , 192 , 124 ) ,
701812 github:: PrDisplayState :: Merged => {
702- format ! ( "[{}]" , state) . truecolor ( 180 , 142 , 173 )
813+ format ! ( "[{}]" , state) . truecolor ( purple. 0 , purple. 1 , purple. 2 )
814+ }
815+ github:: PrDisplayState :: Closed => {
816+ format ! ( "[{}]" , state) . truecolor ( red. 0 , red. 1 , red. 2 )
703817 }
704- github:: PrDisplayState :: Closed => format ! ( "[{}]" , state) . truecolor ( 204 , 36 , 29 ) ,
705818 } ;
706- let ( r, g, b) = string_to_rgb ( & pr. user . login ) ;
707- let author_colored = format ! ( "@{}" , pr. user. login) . truecolor ( r, g, b) ;
708- let number_colored = format ! ( "#{}" , pr. number) . truecolor ( 90 , 78 , 98 ) ;
819+ let author_rgb = string_to_rgb ( & pr. user . login ) ;
820+ let author_color = dim. apply ( author_rgb) ;
821+ let author_colored = format ! ( "@{}" , pr. user. login) . truecolor (
822+ author_color. 0 ,
823+ author_color. 1 ,
824+ author_color. 2 ,
825+ ) ;
826+ let pr_num = dim. apply ( colors:: PR_NUMBER ) ;
827+ let number_colored =
828+ format ! ( "#{}" , pr. number) . truecolor ( pr_num. 0 , pr_num. 1 , pr_num. 2 ) ;
829+ let arrow = dim. apply ( colors:: PR_ARROW ) ;
709830 format ! (
710831 " {} {} {} {}" ,
711- " " . truecolor( 100 , 105 , 105 ) ,
832+ "" . truecolor( arrow . 0 , arrow . 1 , arrow . 2 ) ,
712833 author_colored,
713834 number_colored,
714835 state_colored
0 commit comments