11module Gradientty
22 # Represents a gradient color instance for rendering strings with color transitions.
3+ #
4+ # Create an instance using `Gradientty.gradient(["#hex1", "#hex2", ...])`.
5+ # Use `#call` for single-line strings or `#multiline` for multi-line strings.
36 struct ColorInstance
47 # The array of RGB color stops used for the gradient.
8+ # Each color is an Array of three Int32 values: [R, G, B].
59 property colors : Array (Array (Int32 ))
610
711 # Initializes a ColorInstance with an array of RGB color stops.
12+ #
13+ # *colors* - An array of RGB color stops.
814 def initialize (colors : Array (Array (Int32 )))
915 @colors = colors
1016 end
1117
12- # Applies the gradient to a string, returning the string with ANSI color codes.
13- # Does not add a line break at the end.
18+ # Applies the gradient to a single-line string.
19+ #
20+ # Spaces and newlines are preserved.
21+ # The string is returned with ANSI color codes applied.
1422 #
15- # Skips coloring for spaces and newlines .
23+ # *str* - The string to apply the gradient to .
1624 #
1725 # Example:
1826 # ```
19- # puts Gradientty.gradient(["#ff0000", "#0000ff"]).call("Hello")
27+ # custom = Gradientty.gradient(["#ff0000", "#00ff00"])
28+ # puts custom.call("Hello")
2029 # ```
2130 def call (str : String )
2231 return " " if str.empty?
2332 len = str.size
24- result = " "
2533
26- str.chars.each_with_index do |c , i |
27- if c == " " || c == " \n "
28- result += c
29- next
30- end
34+ String .build do |io |
35+ str.chars.each_with_index do |c , i |
36+ if c == ' ' || c == '\n'
37+ io << c
38+ next
39+ end
3140
32- r = interpolate(@colors , i, len, 0 )
33- g = interpolate(@colors , i, len, 1 )
34- b = interpolate(@colors , i, len, 2 )
41+ r = interpolate(@colors , i, len, 0 )
42+ g = interpolate(@colors , i, len, 1 )
43+ b = interpolate(@colors , i, len, 2 )
3544
36- result += " \e [38;2;#{ r } ;#{ g } ;#{ b } m#{ c } "
45+ io << " \e [38;2;#{ r } ;#{ g } ;#{ b } m#{ c } "
46+ end
47+ io << " \e [0m"
3748 end
38-
39- result + " \e [0m"
4049 end
4150
42- # Applies a gradient to a multiline string.
51+ # Applies the gradient to a multi-line string.
4352 #
4453 # Each line receives the full gradient individually by default.
45- # If `continuous` is true, the gradient is applied as if the entire
46- # string were a single long line, so the color flows across lines.
54+ # If *continuous* is true, the gradient flows continuously across all lines.
4755 #
48- # Parameters:
49- # - *str* = The multiline string to color.
50- # - *continuous* = false - Whether to apply the gradient continuously across all lines.
56+ # *str* - The multi-line string to color.
57+ # *continuous* - Whether to apply gradient continuously across all lines (default: false).
5158 #
52- # Returns:
53- # - String - The colored string with ANSI codes, ready for terminal output.
59+ # Example:
60+ # ```
61+ # custom = Gradientty.gradient(["#ff0000", "#00ff00", "#0000ff"])
62+ # puts custom.multiline("Hello\nWorld", true)
63+ # ```
5464 def multiline (str : String , continuous = false )
5565 if continuous
5666 call(str.chomp) + " \e [0m"
@@ -60,53 +70,110 @@ module Gradientty
6070 end
6171
6272 # Linearly interpolates between color stops for a given channel (R, G, or B).
73+ #
6374 # Used internally for gradient calculation.
6475 private def interpolate (colors : Array (Array (Int32 )), i : Int32 , len : Int32 , channel : Int32 ) : Int32
65- start_c = colors.first[channel]
66- end_c = colors.last[channel]
67- ((start_c + ((end_c - start_c) * i.to_f / (len - 1 ))).round).to_i
76+ return colors.first[channel] if len <= 1
77+
78+ pos = i.to_f / (len - 1 ) * (colors.size - 1 )
79+ idx = pos.floor.to_i
80+ next_idx = Math .min(idx + 1 , colors.size - 1 )
81+ t = pos - idx
82+
83+ start_c = colors[idx][channel]
84+ end_c = colors[next_idx][channel]
85+
86+ (start_c + (end_c - start_c) * t).round.to_i
6887 end
6988 end
7089
71- # Converts a hex color string (e.g., "#ff00aa") to an array of RGB `Int32` values.
72- # Used internally.
90+ # Converts a hex color string (e.g., "#ff00aa") to an array of RGB Int32 values.
91+ #
92+ # *hex* - The hex color string.
93+ #
94+ # Example:
95+ # ```
96+ # Gradientty.hex_to_rgb("#ff00aa") # => [255, 0, 170]
97+ # ```
7398 private def self.hex_to_rgb (hex : String ) : Array (Int32 )
74- hex = hex.gsub(" #" , " " )
75- [
76- hex[0 ..1].to_i(16 ),
77- hex[2 ..3].to_i(16 ),
78- hex[4 ..5].to_i(16 ),
79- ]
99+ hex = hex.lstrip('#' )
100+ [0 , 2 , 4 ].map { |i | hex[i, 2 ].to_i(16 ) }
80101 end
81102
82- # Factory method: creates a `ColorInstance` from an array of hex color strings.
103+ # Factory method: creates a ColorInstance from an array of hex color strings.
104+ #
105+ # *colors* - Array of hex color strings for the gradient.
83106 #
84107 # Example:
85108 # ```
86- # = Gradientty.gradient(["#ff0000", "#00ff00"])
109+ # custom = Gradientty.gradient(["#ff0000", "#00ff00"])
110+ # puts custom.call("Hello")
87111 # ```
88112 def self.gradient (colors : Array (String ))
89113 rgb_colors = colors.map { |h | hex_to_rgb(h) }
90114 ColorInstance .new(rgb_colors)
91115 end
92116
117+ # Private helper to apply gradient directly to a string.
93118 private def self.apply_gradient (colors : Array (String ), str : String )
94119 instance = gradient(colors)
95120 str.includes?(" \n " ) ? instance.multiline(str) : instance.call(str)
96121 end
97122
123+ # Predefined crystal-style gradient.
124+ #
125+ # *str* - The string to color.
126+ #
127+ # Example:
128+ # ```
129+ # puts Gradientty.crystal("Hello Crystal!")
130+ # ```
98131 def self.crystal (str : String )
99132 apply_gradient([" #000000" , " #777777" , " #FFFFFF" ], str)
100133 end
101134
135+ # Predefined rainbow gradient.
136+ #
137+ # *str* - The string to color.
138+ #
139+ # Example:
140+ # ```
141+ # puts Gradientty.rainbow("Hello Rainbow!")
142+ # ```
102143 def self.rainbow (str : String )
103- apply_gradient([" #ff0000" , " #ff7f00" , " #ffff00" , " #00ff00" , " #0000ff" , " #4b0082" , " #9400d3" ], str)
144+ apply_gradient([
145+ " #FF0000" ,
146+ " #FF7F00" ,
147+ " #FFFF00" ,
148+ " #7FFF00" ,
149+ " #00FF00" ,
150+ " #00FF7F" ,
151+ " #00FFFF" ,
152+ " #0000FF" ,
153+ " #8B00FF" ,
154+ ], str)
104155 end
105156
157+ # Predefined pastel gradient.
158+ #
159+ # *str* - The string to color.
160+ #
161+ # Example:
162+ # ```
163+ # puts Gradientty.pastel("Hello Pastel!")
164+ # ```
106165 def self.pastel (str : String )
107166 apply_gradient([" #74ebd5" , " #74ecd5" ], str)
108167 end
109168
169+ # Predefined vice-style gradient.
170+ #
171+ # *str* - The string to color.
172+ #
173+ # Example:
174+ # ```
175+ # puts Gradientty.vice("Hello Vice!")
176+ # ```
110177 def self.vice (str : String )
111178 apply_gradient([" #5ee7df" , " #b490ca" ], str)
112179 end
0 commit comments