@@ -114,6 +114,45 @@ impl Color {
114
114
}
115
115
}
116
116
117
+ /// A Color used in a gradient
118
+ #[ derive( Copy , Clone , Debug , PartialEq , Eq ) ]
119
+ pub struct ColorGradient {
120
+ red : u8 ,
121
+ green : u8 ,
122
+ blue : u8 ,
123
+ }
124
+
125
+ impl ColorGradient {
126
+ fn new ( red : u8 , green : u8 , blue : u8 ) -> Self {
127
+ ColorGradient {
128
+ red : red. min ( 255 ) ,
129
+ green : green. min ( 255 ) ,
130
+ blue : blue. min ( 255 ) ,
131
+ }
132
+ }
133
+
134
+ fn from_hex ( hex : & str ) -> Self {
135
+ // Remove '#' if present
136
+ let s = hex. trim_start_matches ( '#' ) ;
137
+
138
+ // 6 char hex
139
+ let channels: Vec < u8 > = ( 0 ..5 )
140
+ . step_by ( 2 )
141
+ . map ( |i| u8:: from_str_radix ( & s[ i..i + 2 ] , 16 ) . unwrap_or_default ( ) )
142
+ . collect ( ) ;
143
+
144
+ ColorGradient :: new ( channels[ 0 ] , channels[ 1 ] , channels[ 2 ] )
145
+ }
146
+
147
+ pub ( crate ) fn interpolate ( & self , other : & ColorGradient , t : f32 ) -> ColorGradient {
148
+ ColorGradient {
149
+ red : ( self . red as f32 + ( other. red as f32 - self . red as f32 ) * t) as u8 ,
150
+ green : ( self . green as f32 + ( other. green as f32 - self . green as f32 ) * t) as u8 ,
151
+ blue : ( self . blue as f32 + ( other. blue as f32 - self . blue as f32 ) * t) as u8 ,
152
+ }
153
+ }
154
+ }
155
+
117
156
/// A terminal style attribute.
118
157
#[ derive( Copy , Clone , Debug , PartialEq , Eq , Ord , PartialOrd ) ]
119
158
pub enum Attribute {
@@ -160,6 +199,8 @@ pub struct Style {
160
199
bg : Option < Color > ,
161
200
fg_bright : bool ,
162
201
bg_bright : bool ,
202
+ fg_gradient : Vec < ColorGradient > ,
203
+ bg_gradient : Vec < ColorGradient > ,
163
204
attrs : BTreeSet < Attribute > ,
164
205
force : Option < bool > ,
165
206
for_stderr : bool ,
@@ -179,6 +220,8 @@ impl Style {
179
220
bg : None ,
180
221
fg_bright : false ,
181
222
bg_bright : false ,
223
+ fg_gradient : vec ! [ ] ,
224
+ bg_gradient : vec ! [ ] ,
182
225
attrs : BTreeSet :: new ( ) ,
183
226
force : None ,
184
227
for_stderr : false ,
@@ -222,6 +265,19 @@ impl Style {
222
265
"reverse" => rv. reverse ( ) ,
223
266
"hidden" => rv. hidden ( ) ,
224
267
"strikethrough" => rv. strikethrough ( ) ,
268
+ gradient if gradient. starts_with ( "gradient_" ) => {
269
+ for hex_color in gradient[ 9 ..] . split ( '_' ) {
270
+ rv = rv. gradient ( ColorGradient :: from_hex ( hex_color) ) ;
271
+ }
272
+ rv
273
+ }
274
+ on_gradient if on_gradient. starts_with ( "on_gradient_" ) => {
275
+ for hex_color in on_gradient[ 12 ..] . split ( '_' ) {
276
+ rv = rv. on_gradient ( ColorGradient :: from_hex ( hex_color) ) ;
277
+ }
278
+ rv
279
+ }
280
+
225
281
on_c if on_c. starts_with ( "on_" ) => {
226
282
if let Ok ( n) = on_c[ 3 ..] . parse :: < u8 > ( ) {
227
283
rv. on_color256 ( n)
@@ -295,6 +351,22 @@ impl Style {
295
351
self
296
352
}
297
353
354
+ /// Add a gradient color to the text gradient
355
+ /// Overrides basic foreground colors
356
+ #[ inline]
357
+ pub fn gradient ( mut self , color : ColorGradient ) -> Self {
358
+ self . fg_gradient . push ( color) ;
359
+ self
360
+ }
361
+
362
+ /// Add a gradient color to the text on_gradient
363
+ /// Overrides basic background colors
364
+ #[ inline]
365
+ pub fn on_gradient ( mut self , color : ColorGradient ) -> Self {
366
+ self . bg_gradient . push ( color) ;
367
+ self
368
+ }
369
+
298
370
#[ inline]
299
371
pub const fn black ( self ) -> Self {
300
372
self . fg ( Color :: Black )
@@ -486,6 +558,18 @@ impl<D> StyledObject<D> {
486
558
self
487
559
}
488
560
561
+ #[ inline]
562
+ pub fn gradient ( mut self , color : ColorGradient ) -> StyledObject < D > {
563
+ self . style = self . style . gradient ( color) ;
564
+ self
565
+ }
566
+
567
+ #[ inline]
568
+ pub fn on_gradient ( mut self , color : ColorGradient ) -> StyledObject < D > {
569
+ self . style = self . style . on_gradient ( color) ;
570
+ self
571
+ }
572
+
489
573
/// Adds a attr.
490
574
#[ inline]
491
575
pub fn attr ( mut self , attr : Attribute ) -> StyledObject < D > {
@@ -618,7 +702,7 @@ impl<D> StyledObject<D> {
618
702
}
619
703
620
704
macro_rules! impl_fmt {
621
- ( $name: ident) => {
705
+ ( $name: ident, $format_char : expr ) => {
622
706
impl <D : fmt:: $name> fmt:: $name for StyledObject <D > {
623
707
fn fmt( & self , f: & mut fmt:: Formatter ) -> fmt:: Result {
624
708
let mut reset = false ;
@@ -630,32 +714,49 @@ macro_rules! impl_fmt {
630
714
false => colors_enabled( ) ,
631
715
} )
632
716
{
633
- if let Some ( fg) = self . style. fg {
634
- if fg. is_color256( ) {
635
- write!( f, "\x1b [38;5;{}m" , fg. ansi_num( ) ) ?;
636
- } else if self . style. fg_bright {
637
- write!( f, "\x1b [38;5;{}m" , fg. ansi_num( ) + 8 ) ?;
638
- } else {
639
- write!( f, "\x1b [{}m" , fg. ansi_num( ) + 30 ) ?;
717
+ if self . style. fg_gradient. is_empty( ) {
718
+ if let Some ( fg) = self . style. fg {
719
+ if fg. is_color256( ) {
720
+ write!( f, "\x1b [38;5;{}m" , fg. ansi_num( ) ) ?;
721
+ } else if self . style. fg_bright {
722
+ write!( f, "\x1b [38;5;{}m" , fg. ansi_num( ) + 8 ) ?;
723
+ } else {
724
+ write!( f, "\x1b [{}m" , fg. ansi_num( ) + 30 ) ?;
725
+ }
726
+ reset = true ;
640
727
}
641
- reset = true ;
642
728
}
643
- if let Some ( bg) = self . style. bg {
644
- if bg. is_color256( ) {
645
- write!( f, "\x1b [48;5;{}m" , bg. ansi_num( ) ) ?;
646
- } else if self . style. bg_bright {
647
- write!( f, "\x1b [48;5;{}m" , bg. ansi_num( ) + 8 ) ?;
648
- } else {
649
- write!( f, "\x1b [{}m" , bg. ansi_num( ) + 40 ) ?;
729
+ if self . style. bg_gradient. is_empty( ) {
730
+ if let Some ( bg) = self . style. bg {
731
+ if bg. is_color256( ) {
732
+ write!( f, "\x1b [48;5;{}m" , bg. ansi_num( ) ) ?;
733
+ } else if self . style. bg_bright {
734
+ write!( f, "\x1b [48;5;{}m" , bg. ansi_num( ) + 8 ) ?;
735
+ } else {
736
+ write!( f, "\x1b [{}m" , bg. ansi_num( ) + 40 ) ?;
737
+ }
738
+ reset = true ;
650
739
}
651
- reset = true ;
652
740
}
653
741
for attr in & self . style. attrs {
654
742
write!( f, "\x1b [{}m" , attr. ansi_num( ) ) ?;
655
743
reset = true ;
656
744
}
657
745
}
658
- fmt:: $name:: fmt( & self . val, f) ?;
746
+ // Get the underlying value
747
+ let mut buf: String = format!( $format_char, & self . val) ;
748
+
749
+ if ( !self . style. fg_gradient. is_empty( ) ) {
750
+ buf = apply_gradient_impl( & buf, & self . style. fg_gradient, false , true ) ;
751
+ reset = true ;
752
+ }
753
+
754
+ if ( !self . style. bg_gradient. is_empty( ) ) {
755
+ buf = apply_gradient_impl( & buf, & self . style. bg_gradient, false , false ) ;
756
+ reset = true ;
757
+ }
758
+
759
+ write!( f, "{}" , buf) ?;
659
760
if reset {
660
761
write!( f, "\x1b [0m" ) ?;
661
762
}
@@ -665,15 +766,15 @@ macro_rules! impl_fmt {
665
766
} ;
666
767
}
667
768
668
- impl_fmt ! ( Binary ) ;
669
- impl_fmt ! ( Debug ) ;
670
- impl_fmt ! ( Display ) ;
671
- impl_fmt ! ( LowerExp ) ;
672
- impl_fmt ! ( LowerHex ) ;
673
- impl_fmt ! ( Octal ) ;
674
- impl_fmt ! ( Pointer ) ;
675
- impl_fmt ! ( UpperExp ) ;
676
- impl_fmt ! ( UpperHex ) ;
769
+ impl_fmt ! ( Binary , "{:b}" ) ;
770
+ impl_fmt ! ( Debug , "{:?}" ) ;
771
+ impl_fmt ! ( Display , "{}" ) ;
772
+ impl_fmt ! ( LowerExp , "{:e}" ) ;
773
+ impl_fmt ! ( LowerHex , "{:x}" ) ;
774
+ impl_fmt ! ( Octal , "{:o}" ) ;
775
+ impl_fmt ! ( Pointer , "{:p}" ) ;
776
+ impl_fmt ! ( UpperExp , "{:E}" ) ;
777
+ impl_fmt ! ( UpperHex , "{:X}" ) ;
677
778
678
779
/// "Intelligent" emoji formatter.
679
780
///
@@ -732,6 +833,112 @@ fn char_width(c: char) -> usize {
732
833
}
733
834
}
734
835
836
+ /// Returns the number of visible characters, ignoring style related chars
837
+ fn count_visible_chars ( text : & str ) -> usize {
838
+ let mut total_visible = 0 ;
839
+
840
+ let mut chars = text. chars ( ) ;
841
+ while let Some ( c) = chars. next ( ) {
842
+ if c == '\x1b' {
843
+ while let Some ( next) = chars. next ( ) {
844
+ if next == 'm' {
845
+ break ;
846
+ }
847
+ }
848
+ continue ;
849
+ }
850
+ if c != '\n' {
851
+ total_visible += 1 ;
852
+ }
853
+ }
854
+
855
+ total_visible
856
+ }
857
+
858
+ /// Applies the gradient over a string
859
+ fn apply_gradient_impl (
860
+ text : & str ,
861
+ gradients : & Vec < ColorGradient > ,
862
+ block : bool ,
863
+ foreground : bool ,
864
+ ) -> String {
865
+ let ascii_number = if foreground { 3 } else { 4 } ;
866
+ let skip_sequence = format ! ( "[{}8;2;" , ascii_number) ;
867
+
868
+ let mut visible_chars = 0 ;
869
+ let total_visible = count_visible_chars ( text) ;
870
+ // The pre-allocation is an estimate, chances are high a re-allocation will occur
871
+ // But it still less than without the estimate
872
+ // Since gradients are on the form `\x1b[{}8;2;{};{};{}m` for a single color, the estimation is 16 chars per visible char
873
+ let mut result = String :: with_capacity ( 16 * total_visible) ;
874
+
875
+ // Second pass: apply gradient
876
+ let mut chars = text. chars ( ) . peekable ( ) ;
877
+ while let Some ( c) = chars. next ( ) {
878
+ if c == '\x1b' {
879
+ let mut seq = String :: from ( c) ;
880
+ while let Some ( & next) = chars. peek ( ) {
881
+ seq. push ( next) ;
882
+ chars. next ( ) ;
883
+ if next == 'm' {
884
+ break ;
885
+ }
886
+ }
887
+
888
+ // Only skip foreground/background color sequences
889
+ if !seq. contains ( & skip_sequence) {
890
+ result. push_str ( & seq) ;
891
+ }
892
+ continue ;
893
+ }
894
+
895
+ if c == '\n' {
896
+ result. push ( c) ;
897
+ if block {
898
+ visible_chars = 0 ;
899
+ }
900
+ continue ;
901
+ }
902
+
903
+ let progress = if total_visible > 1 {
904
+ visible_chars as f32 / ( total_visible - 1 ) as f32
905
+ } else {
906
+ 0.0
907
+ } ;
908
+
909
+ // Find which gradient to use, along the progress for an individual gradient interpolation
910
+ let gradient_range =
911
+ map_range ( ( 0f32 , 1f32 ) , ( 0f32 , ( gradients. len ( ) - 1 ) as f32 ) , progress) ;
912
+ let mut current_gradient_index: usize = gradient_range as usize ;
913
+ let mut gradient_progress = gradient_range - current_gradient_index as f32 ;
914
+
915
+ if current_gradient_index == gradients. len ( ) - 1 && current_gradient_index > 0 {
916
+ // Edge case at the end of the gradient, don't continue looping
917
+ current_gradient_index -= 1 ;
918
+ gradient_progress = 1.0f32
919
+ }
920
+
921
+ let color = gradients[ current_gradient_index] . interpolate (
922
+ & gradients[ ( current_gradient_index + 1 ) . min ( gradients. len ( ) - 1 ) ] ,
923
+ gradient_progress,
924
+ ) ;
925
+
926
+ result. push_str ( & format ! (
927
+ "\x1b [{}8;2;{};{};{}m" ,
928
+ ascii_number, color. red, color. green, color. blue
929
+ ) ) ;
930
+ result. push ( c) ;
931
+
932
+ visible_chars += 1 ;
933
+ }
934
+ result
935
+ }
936
+
937
+ /// Map one range to another
938
+ fn map_range ( from_range : ( f32 , f32 ) , to_range : ( f32 , f32 ) , s : f32 ) -> f32 {
939
+ to_range. 0 + ( s - from_range. 0 ) * ( to_range. 1 - to_range. 0 ) / ( from_range. 1 - from_range. 0 )
940
+ }
941
+
735
942
/// Truncates a string to a certain number of characters.
736
943
///
737
944
/// This ensures that escape codes are not screwed up in the process.
0 commit comments