sai 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -1
- data/README.md +11 -3
- data/docs/USAGE.md +57 -9
- data/lib/sai/ansi/color_parser.rb +109 -0
- data/lib/sai/ansi/sequence_processor.rb +15 -126
- data/lib/sai/ansi/style_parser.rb +66 -0
- data/lib/sai/ansi.rb +0 -27
- data/lib/sai/conversion/color_sequence.rb +4 -4
- data/lib/sai/conversion/rgb/color_classifier.rb +209 -0
- data/lib/sai/conversion/rgb/color_indexer.rb +48 -0
- data/lib/sai/conversion/rgb/color_space.rb +192 -0
- data/lib/sai/conversion/rgb/color_transformer.rb +140 -0
- data/lib/sai/conversion/rgb.rb +23 -269
- data/lib/sai/decorator/color_manipulations.rb +157 -0
- data/lib/sai/decorator/delegation.rb +84 -0
- data/lib/sai/decorator/gradients.rb +363 -0
- data/lib/sai/decorator/hex_colors.rb +56 -0
- data/lib/sai/decorator/named_colors.rb +780 -0
- data/lib/sai/decorator/named_styles.rb +276 -0
- data/lib/sai/decorator/rgb_colors.rb +64 -0
- data/lib/sai/decorator.rb +29 -775
- data/lib/sai/named_colors.rb +437 -0
- data/lib/sai.rb +731 -23
- data/sig/sai/ansi/color_parser.rbs +77 -0
- data/sig/sai/ansi/sequence_processor.rbs +0 -75
- data/sig/sai/ansi/style_parser.rbs +59 -0
- data/sig/sai/ansi.rbs +0 -10
- data/sig/sai/conversion/rgb/color_classifier.rbs +165 -0
- data/sig/sai/conversion/rgb/color_indexer.rbs +41 -0
- data/sig/sai/conversion/rgb/color_space.rbs +129 -0
- data/sig/sai/conversion/rgb/color_transformer.rbs +99 -0
- data/sig/sai/conversion/rgb.rbs +15 -198
- data/sig/sai/decorator/color_manipulations.rbs +125 -0
- data/sig/sai/decorator/delegation.rbs +47 -0
- data/sig/sai/decorator/gradients.rbs +267 -0
- data/sig/sai/decorator/hex_colors.rbs +48 -0
- data/sig/sai/decorator/named_colors.rbs +1491 -0
- data/sig/sai/decorator/named_styles.rbs +72 -0
- data/sig/sai/decorator/rgb_colors.rbs +52 -0
- data/sig/sai/decorator.rbs +21 -195
- data/sig/sai/named_colors.rbs +65 -0
- data/sig/sai.rbs +1468 -44
- metadata +32 -4
data/lib/sai/conversion/rgb.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'sai/ansi'
|
4
|
+
require 'sai/conversion/rgb/color_classifier'
|
5
|
+
require 'sai/conversion/rgb/color_indexer'
|
6
|
+
require 'sai/conversion/rgb/color_space'
|
7
|
+
require 'sai/conversion/rgb/color_transformer'
|
4
8
|
|
5
9
|
module Sai
|
6
10
|
module Conversion
|
@@ -12,60 +16,30 @@ module Sai
|
|
12
16
|
# @api private
|
13
17
|
module RGB
|
14
18
|
class << self
|
15
|
-
#
|
19
|
+
# Color classification utilities
|
16
20
|
#
|
17
21
|
# @author {https://aaronmallen.me Aaron Allen}
|
18
|
-
# @since 0.1
|
22
|
+
# @since 0.3.1
|
19
23
|
#
|
20
24
|
# @api private
|
21
25
|
#
|
22
|
-
# @
|
23
|
-
# @
|
24
|
-
|
25
|
-
|
26
|
-
# @return [Symbol] the closest ANSI color name
|
27
|
-
# @rbs (Float red, Float green, Float blue) -> Symbol
|
28
|
-
def closest_ansi_color(red, green, blue)
|
29
|
-
return :black if dark?(red, green, blue)
|
30
|
-
return :white if grayscale?(red, green, blue)
|
31
|
-
return primary_color(red, green, blue) if primary?(red, green, blue)
|
32
|
-
return secondary_color(red, green, blue) if secondary?(red, green, blue)
|
33
|
-
|
34
|
-
:white
|
26
|
+
# @return [Module<ColorClassifier>] the ColorClassifier module
|
27
|
+
# @rbs () -> singleton(ColorClassifier)
|
28
|
+
def classify
|
29
|
+
ColorClassifier
|
35
30
|
end
|
36
31
|
|
37
|
-
#
|
32
|
+
# Color indexing utilities
|
38
33
|
#
|
39
34
|
# @author {https://aaronmallen.me Aaron Allen}
|
40
|
-
# @since 0.1
|
35
|
+
# @since 0.3.1
|
41
36
|
#
|
42
37
|
# @api private
|
43
38
|
#
|
44
|
-
# @
|
45
|
-
# @
|
46
|
-
|
47
|
-
|
48
|
-
# @return [Boolean] true if color is dark
|
49
|
-
# @rbs (Float red, Float green, Float blue) -> bool
|
50
|
-
def dark?(red, green, blue)
|
51
|
-
[red, green, blue].max < 0.3
|
52
|
-
end
|
53
|
-
|
54
|
-
# Determine if a color is grayscale
|
55
|
-
#
|
56
|
-
# @author {https://aaronmallen.me Aaron Allen}
|
57
|
-
# @since 0.1.0
|
58
|
-
#
|
59
|
-
# @api private
|
60
|
-
#
|
61
|
-
# @param red [Float] the red component (0-1)
|
62
|
-
# @param green [Float] the green component (0-1)
|
63
|
-
# @param blue [Float] the blue component (0-1)
|
64
|
-
#
|
65
|
-
# @return [Boolean] true if color is grayscale
|
66
|
-
# @rbs (Float red, Float green, Float blue) -> bool
|
67
|
-
def grayscale?(red, green, blue)
|
68
|
-
red == green && green == blue
|
39
|
+
# @return [Module<ColorIndexer>] the ColorIndexer module
|
40
|
+
# @rbs () -> singleton(ColorIndexer)
|
41
|
+
def index
|
42
|
+
ColorIndexer
|
69
43
|
end
|
70
44
|
|
71
45
|
# Convert a color value to RGB components
|
@@ -81,240 +55,20 @@ module Sai
|
|
81
55
|
# @return [Array<Integer>] the RGB components
|
82
56
|
# @rbs (Array[Integer] | String | Symbol color) -> Array[Integer]
|
83
57
|
def resolve(color)
|
84
|
-
|
85
|
-
when Array then validate_rgb(color)
|
86
|
-
when /^#?([A-Fa-f0-9]{6})$/
|
87
|
-
hex_to_rgb(
|
88
|
-
Regexp.last_match(1) #: String
|
89
|
-
)
|
90
|
-
when String, Symbol then named_to_rgb(color.to_s.downcase)
|
91
|
-
else
|
92
|
-
raise ArgumentError, "Invalid color format: #{color}"
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
# Convert RGB values to 256-color cube index
|
97
|
-
#
|
98
|
-
# @author {https://aaronmallen.me Aaron Allen}
|
99
|
-
# @since 0.1.0
|
100
|
-
#
|
101
|
-
# @api private
|
102
|
-
#
|
103
|
-
# @param rgb [Array<Integer>] RGB values (0-255)
|
104
|
-
#
|
105
|
-
# @return [Integer] the color cube index
|
106
|
-
# @rbs (Array[Integer] rgb) -> Integer
|
107
|
-
def to_color_cube_index(rgb)
|
108
|
-
r, g, b = rgb.map { |c| ((c / 255.0) * 5).round } #: [Integer, Integer, Integer]
|
109
|
-
16 + (r * 36) + (g * 6) + b
|
110
|
-
end
|
111
|
-
|
112
|
-
# Convert RGB values to grayscale index
|
113
|
-
#
|
114
|
-
# @author {https://aaronmallen.me Aaron Allen}
|
115
|
-
# @since 0.1.0
|
116
|
-
#
|
117
|
-
# @api private
|
118
|
-
#
|
119
|
-
# @param rgb [Array<Integer>] RGB values
|
120
|
-
#
|
121
|
-
# @return [Integer] the grayscale index
|
122
|
-
# @rbs (Array[Integer] rgb) -> Integer
|
123
|
-
def to_grayscale_index(rgb)
|
124
|
-
232 + ((rgb[0] / 255.0) * 23).round
|
125
|
-
end
|
126
|
-
|
127
|
-
private
|
128
|
-
|
129
|
-
# Check if RGB values represent cyan
|
130
|
-
#
|
131
|
-
# @author {https://aaronmallen.me Aaron Allen}
|
132
|
-
# @since 0.1.0
|
133
|
-
#
|
134
|
-
# @api private
|
135
|
-
#
|
136
|
-
# @param red [Float] the red component (0-1)
|
137
|
-
# @param green [Float] the green component (0-1)
|
138
|
-
# @param blue [Float] the blue component (0-1)
|
139
|
-
#
|
140
|
-
# @return [Boolean] true if color is cyan
|
141
|
-
# @rbs (Float red, Float green, Float blue) -> bool
|
142
|
-
def cyan?(red, green, blue)
|
143
|
-
green > red && blue > red
|
144
|
-
end
|
145
|
-
|
146
|
-
# Convert a hex string to RGB values
|
147
|
-
#
|
148
|
-
# @author {https://aaronmallen.me Aaron Allen}
|
149
|
-
# @since 0.1.0
|
150
|
-
#
|
151
|
-
# @api private
|
152
|
-
#
|
153
|
-
# @param hex [String] the hex color code
|
154
|
-
#
|
155
|
-
# @return [Array<Integer>] the RGB components
|
156
|
-
# @rbs (String hex) -> Array[Integer]
|
157
|
-
def hex_to_rgb(hex)
|
158
|
-
hex = hex.delete_prefix('#') #: String
|
159
|
-
[
|
160
|
-
hex[0..1].to_i(16), # steep:ignore UnexpectedPositionalArgument
|
161
|
-
hex[2..3].to_i(16), # steep:ignore UnexpectedPositionalArgument
|
162
|
-
hex[4..5].to_i(16) # steep:ignore UnexpectedPositionalArgument
|
163
|
-
]
|
164
|
-
end
|
165
|
-
|
166
|
-
# Check if RGB values represent magenta
|
167
|
-
#
|
168
|
-
# @author {https://aaronmallen.me Aaron Allen}
|
169
|
-
# @since 0.1.0
|
170
|
-
#
|
171
|
-
# @api private
|
172
|
-
#
|
173
|
-
# @param red [Float] the red component (0-1)
|
174
|
-
# @param green [Float] the green component (0-1)
|
175
|
-
# @param blue [Float] the blue component (0-1)
|
176
|
-
#
|
177
|
-
# @return [Boolean] true if color is magenta
|
178
|
-
# @rbs (Float red, Float green, Float blue) -> bool
|
179
|
-
def magenta?(red, green, blue)
|
180
|
-
red > green && blue > green
|
181
|
-
end
|
182
|
-
|
183
|
-
# Convert a named color to RGB values
|
184
|
-
#
|
185
|
-
# @author {https://aaronmallen.me Aaron Allen}
|
186
|
-
# @since 0.1.0
|
187
|
-
#
|
188
|
-
# @api private
|
189
|
-
#
|
190
|
-
# @param color_name [String] the color name
|
191
|
-
#
|
192
|
-
# @raise [ArgumentError] if the color name is unknown
|
193
|
-
# @return [Array<Integer>] the RGB components
|
194
|
-
# @rbs (String color_name) -> Array[Integer]
|
195
|
-
def named_to_rgb(color_name)
|
196
|
-
ANSI::COLOR_NAMES.fetch(color_name.to_sym) do
|
197
|
-
raise ArgumentError, "Unknown color name: #{color_name}"
|
198
|
-
end
|
199
|
-
end
|
200
|
-
|
201
|
-
# Determine if RGB values represent a primary color
|
202
|
-
#
|
203
|
-
# @author {https://aaronmallen.me Aaron Allen}
|
204
|
-
# @since 0.1.0
|
205
|
-
#
|
206
|
-
# @api private
|
207
|
-
#
|
208
|
-
# @param red [Float] the red component (0-1)
|
209
|
-
# @param green [Float] the green component (0-1)
|
210
|
-
# @param blue [Float] the blue component (0-1)
|
211
|
-
#
|
212
|
-
# @return [Boolean] true if color is primary
|
213
|
-
# @rbs (Float red, Float green, Float blue) -> bool
|
214
|
-
def primary?(red, green, blue)
|
215
|
-
max = [red, green, blue].max
|
216
|
-
mid = [red, green, blue].sort[1]
|
217
|
-
(max - mid) > 0.3
|
218
|
-
end
|
219
|
-
|
220
|
-
# Get the closest primary color
|
221
|
-
#
|
222
|
-
# @author {https://aaronmallen.me Aaron Allen}
|
223
|
-
# @since 0.1.0
|
224
|
-
#
|
225
|
-
# @api private
|
226
|
-
#
|
227
|
-
# @param red [Float] the red component (0-1)
|
228
|
-
# @param green [Float] the green component (0-1)
|
229
|
-
# @param blue [Float] the blue component (0-1)
|
230
|
-
#
|
231
|
-
# @return [Symbol] the primary color name
|
232
|
-
# @rbs (Float red, Float green, Float blue) -> Symbol
|
233
|
-
def primary_color(red, green, blue)
|
234
|
-
max = [red, green, blue].max
|
235
|
-
case max
|
236
|
-
when red then :red
|
237
|
-
when green then :green
|
238
|
-
else :blue
|
239
|
-
end
|
240
|
-
end
|
241
|
-
|
242
|
-
# Determine if RGB values represent a secondary color
|
243
|
-
#
|
244
|
-
# @author {https://aaronmallen.me Aaron Allen}
|
245
|
-
# @since 0.1.0
|
246
|
-
#
|
247
|
-
# @api private
|
248
|
-
#
|
249
|
-
# @param red [Float] the red component (0-1)
|
250
|
-
# @param green [Float] the green component (0-1)
|
251
|
-
# @param blue [Float] the blue component (0-1)
|
252
|
-
#
|
253
|
-
# @return [Boolean] true if color is secondary
|
254
|
-
# @rbs (Float red, Float green, Float blue) -> bool
|
255
|
-
def secondary?(red, green, blue)
|
256
|
-
return true if yellow?(red, green, blue)
|
257
|
-
return true if magenta?(red, green, blue)
|
258
|
-
return true if cyan?(red, green, blue)
|
259
|
-
|
260
|
-
false
|
261
|
-
end
|
262
|
-
|
263
|
-
# Get the closest secondary color
|
264
|
-
#
|
265
|
-
# @author {https://aaronmallen.me Aaron Allen}
|
266
|
-
# @since 0.1.0
|
267
|
-
#
|
268
|
-
# @api private
|
269
|
-
#
|
270
|
-
# @param red [Float] the red component (0-1)
|
271
|
-
# @param green [Float] the green component (0-1)
|
272
|
-
# @param blue [Float] the blue component (0-1)
|
273
|
-
#
|
274
|
-
# @return [Symbol] the secondary color name
|
275
|
-
# @rbs (Float red, Float green, Float blue) -> Symbol
|
276
|
-
def secondary_color(red, green, blue)
|
277
|
-
return :yellow if yellow?(red, green, blue)
|
278
|
-
return :magenta if magenta?(red, green, blue)
|
279
|
-
return :cyan if cyan?(red, green, blue)
|
280
|
-
|
281
|
-
:white
|
58
|
+
ColorSpace.resolve(color)
|
282
59
|
end
|
283
60
|
|
284
|
-
#
|
61
|
+
# Transform RGB values
|
285
62
|
#
|
286
63
|
# @author {https://aaronmallen.me Aaron Allen}
|
287
|
-
# @since 0.1
|
64
|
+
# @since 0.3.1
|
288
65
|
#
|
289
66
|
# @api private
|
290
67
|
#
|
291
|
-
# @
|
292
|
-
# @
|
293
|
-
|
294
|
-
|
295
|
-
def validate_rgb(color)
|
296
|
-
unless color.size == 3 && color.all? { |c| c.is_a?(Integer) && c.between?(0, 255) }
|
297
|
-
raise ArgumentError, "Invalid RGB values: #{color}"
|
298
|
-
end
|
299
|
-
|
300
|
-
color
|
301
|
-
end
|
302
|
-
|
303
|
-
# Check if RGB values represent yellow
|
304
|
-
#
|
305
|
-
# @author {https://aaronmallen.me Aaron Allen}
|
306
|
-
# @since 0.1.0
|
307
|
-
#
|
308
|
-
# @api private
|
309
|
-
#
|
310
|
-
# @param red [Float] the red component (0-1)
|
311
|
-
# @param green [Float] the green component (0-1)
|
312
|
-
# @param blue [Float] the blue component (0-1)
|
313
|
-
#
|
314
|
-
# @return [Boolean] true if color is yellow
|
315
|
-
# @rbs (Float red, Float green, Float blue) -> bool
|
316
|
-
def yellow?(red, green, blue)
|
317
|
-
red > blue && green > blue && (red - green).abs < 0.3
|
68
|
+
# @return [Module<ColorTransformer>] the color transformer
|
69
|
+
# @rbs () -> singleton(ColorTransformer)
|
70
|
+
def transform
|
71
|
+
ColorTransformer
|
318
72
|
end
|
319
73
|
end
|
320
74
|
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sai
|
4
|
+
class Decorator
|
5
|
+
# Color manipulation methods for the {Decorator} class
|
6
|
+
#
|
7
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
8
|
+
# @since 0.3.1
|
9
|
+
#
|
10
|
+
# @abstract This module is meant to be included in the {Decorator} class to provide color manipulation methods
|
11
|
+
# @api private
|
12
|
+
module ColorManipulations
|
13
|
+
# Darken the background color by a percentage
|
14
|
+
#
|
15
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
16
|
+
# @since 0.3.1
|
17
|
+
#
|
18
|
+
# @api public
|
19
|
+
#
|
20
|
+
# @example
|
21
|
+
# decorator.on_blue.darken_text(0.5).decorate('Hello, world!').to_s #=> "\e[48;2;0;0;238mHello, world!\e[0m"
|
22
|
+
#
|
23
|
+
# @param amount [Float] the amount to darken the background color (0.0...1.0)
|
24
|
+
#
|
25
|
+
# @raise [ArgumentError] if the percentage is out of range
|
26
|
+
# @return [Decorator] a new instance of Decorator with the darkened background color
|
27
|
+
# @rbs (Float amount) -> Decorator
|
28
|
+
def darken_background(amount)
|
29
|
+
raise ArgumentError, "Invalid percentage: #{amount}" unless amount >= 0.0 && amount <= 1.0
|
30
|
+
|
31
|
+
darken(amount, :background)
|
32
|
+
end
|
33
|
+
alias darken_bg darken_background
|
34
|
+
|
35
|
+
# Darken the text color by a percentage
|
36
|
+
#
|
37
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
38
|
+
# @since 0.3.1
|
39
|
+
#
|
40
|
+
# @api public
|
41
|
+
#
|
42
|
+
# @example
|
43
|
+
# decorator.blue.darken_text(0.5).decorate('Hello, world!').to_s #=> "\e[38;2;0;0;119mHello, world!\e[0m"
|
44
|
+
#
|
45
|
+
# @param amount [Float] the amount to darken the text color (0.0...1.0)
|
46
|
+
#
|
47
|
+
# @raise [ArgumentError] if the percentage is out of range
|
48
|
+
# @return [Decorator] a new instance of Decorator with the darkened text color
|
49
|
+
# @rbs (Float amount) -> Decorator
|
50
|
+
def darken_text(amount)
|
51
|
+
raise ArgumentError, "Invalid percentage: #{amount}" unless amount >= 0.0 && amount <= 1.0
|
52
|
+
|
53
|
+
darken(amount, :foreground)
|
54
|
+
end
|
55
|
+
alias darken_fg darken_text
|
56
|
+
alias darken_foreground darken_text
|
57
|
+
|
58
|
+
# Lighten the background color by a percentage
|
59
|
+
#
|
60
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
61
|
+
# @since 0.3.1
|
62
|
+
#
|
63
|
+
# @api public
|
64
|
+
#
|
65
|
+
# @example
|
66
|
+
# decorator.on_blue.lighten_background(0.5).decorate('Hello, world!').to_s
|
67
|
+
# #=> "\e[48;2;0;0;255mHello, world!\e[0m"
|
68
|
+
#
|
69
|
+
# @param amount [Float] the amount to lighten the background color (0.0...1.0)
|
70
|
+
#
|
71
|
+
# @raise [ArgumentError] if the percentage is out of range
|
72
|
+
# @return [Decorator] a new instance of Decorator with the lightened background color
|
73
|
+
# @rbs (Float amount) -> Decorator
|
74
|
+
def lighten_background(amount)
|
75
|
+
raise ArgumentError, "Invalid percentage: #{amount}" unless amount >= 0.0 && amount <= 1.0
|
76
|
+
|
77
|
+
lighten(amount, :background)
|
78
|
+
end
|
79
|
+
alias lighten_bg lighten_background
|
80
|
+
|
81
|
+
# Lighten the text color by a percentage
|
82
|
+
#
|
83
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
84
|
+
# @since 0.3.1
|
85
|
+
#
|
86
|
+
# @api public
|
87
|
+
#
|
88
|
+
# @example
|
89
|
+
# decorator.blue.lighten_text(0.5).decorate('Hello, world!').to_s #=> "\e[38;2;0;0;127mHello, world!\e[0m"
|
90
|
+
#
|
91
|
+
# @param amount [Float] the amount to lighten the text color (0.0...1.0)
|
92
|
+
#
|
93
|
+
# @raise [ArgumentError] if the percentage is out of range
|
94
|
+
# @return [Decorator] a new instance of Decorator with the lightened text color
|
95
|
+
# @rbs (Float amount) -> Decorator
|
96
|
+
def lighten_text(amount)
|
97
|
+
raise ArgumentError, "Invalid percentage: #{amount}" unless amount >= 0.0 && amount <= 1.0
|
98
|
+
|
99
|
+
lighten(amount, :foreground)
|
100
|
+
end
|
101
|
+
alias lighten_fg lighten_text
|
102
|
+
alias lighten_foreground lighten_text
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
# Darken the foreground or background color by a specified amount
|
107
|
+
#
|
108
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
109
|
+
# @since 0.3.1
|
110
|
+
#
|
111
|
+
# @api private
|
112
|
+
#
|
113
|
+
# @param amount [Float] a value between 0.0 and 1.0 to darken the color by
|
114
|
+
# @param component [Symbol] the color component to darken
|
115
|
+
#
|
116
|
+
# @return [Decorator] a new instance of Decorator with the color darkened
|
117
|
+
# @rbs (Float amount, Symbol component) -> Decorator
|
118
|
+
def darken(amount, component)
|
119
|
+
color = instance_variable_get(:"@#{component}")
|
120
|
+
|
121
|
+
# @type self: Decorator
|
122
|
+
|
123
|
+
dup.tap do |duped|
|
124
|
+
if color
|
125
|
+
rgb = Conversion::RGB.transform.darken(color, amount)
|
126
|
+
duped.instance_variable_set(:"@#{component}", rgb)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Lighten the foreground or background color by a specified amount
|
132
|
+
#
|
133
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
134
|
+
# @since 0.3.1
|
135
|
+
#
|
136
|
+
# @api private
|
137
|
+
#
|
138
|
+
# @param amount [Float] a value between 0.0 and 1.0 to lighten the color by
|
139
|
+
# @param component [Symbol] the color component to lighten
|
140
|
+
#
|
141
|
+
# @return [Decorator] a new instance of Decorator with the color lightened
|
142
|
+
# @rbs (Float amount, Symbol component) -> Decorator
|
143
|
+
def lighten(amount, component)
|
144
|
+
color = instance_variable_get(:"@#{component}")
|
145
|
+
|
146
|
+
# @type self: Decorator
|
147
|
+
|
148
|
+
dup.tap do |duped|
|
149
|
+
if color
|
150
|
+
rgb = Conversion::RGB.transform.lighten(color, amount)
|
151
|
+
duped.instance_variable_set(:"@#{component}", rgb)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'sai/decorator'
|
4
|
+
require 'sai/decorator/color_manipulations'
|
5
|
+
require 'sai/decorator/gradients'
|
6
|
+
require 'sai/decorator/hex_colors'
|
7
|
+
require 'sai/decorator/named_colors'
|
8
|
+
require 'sai/decorator/named_styles'
|
9
|
+
require 'sai/decorator/rgb_colors'
|
10
|
+
|
11
|
+
module Sai
|
12
|
+
class Decorator
|
13
|
+
# Delegates all methods from the Decorator class and its component modules
|
14
|
+
#
|
15
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
16
|
+
# @since 0.3.1
|
17
|
+
#
|
18
|
+
# @api private
|
19
|
+
module Delegation
|
20
|
+
# The list of component modules to delegate methods from
|
21
|
+
#
|
22
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
23
|
+
# @since 0.3.1
|
24
|
+
#
|
25
|
+
# @api private
|
26
|
+
#
|
27
|
+
# @return [Array<Symbol>] the list of component modules
|
28
|
+
COMPONENT_MODULES = %i[
|
29
|
+
ColorManipulations
|
30
|
+
Gradients
|
31
|
+
HexColors
|
32
|
+
NamedColors
|
33
|
+
NamedStyles
|
34
|
+
RGBColors
|
35
|
+
].freeze #: Array[Symbol]
|
36
|
+
private_constant :COMPONENT_MODULES
|
37
|
+
|
38
|
+
class << self
|
39
|
+
# Install delegated methods on the given class or module
|
40
|
+
#
|
41
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
42
|
+
# @since 0.3.1
|
43
|
+
#
|
44
|
+
# @api private
|
45
|
+
#
|
46
|
+
# @param klass [Class, Module] the class or module to install the methods on
|
47
|
+
#
|
48
|
+
# @return [void]
|
49
|
+
# @rbs (Class | Module) -> void
|
50
|
+
def install(klass)
|
51
|
+
ignored_methods = %i[apply call decorate encode]
|
52
|
+
methods = collect_delegatable_methods.reject { |m| ignored_methods.include?(m) }
|
53
|
+
|
54
|
+
methods.each do |method|
|
55
|
+
klass.define_method(method) do |*args, **kwargs|
|
56
|
+
Decorator.new(mode: Sai.mode.auto).public_send(method, *args, **kwargs)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
# Collect all methods from the Decorator class and its component modules
|
64
|
+
#
|
65
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
66
|
+
# @since 0.3.1
|
67
|
+
#
|
68
|
+
# @api private
|
69
|
+
#
|
70
|
+
# @return [Array<Symbol>] the list of methods to delegate
|
71
|
+
# @rbs () -> Array[Symbol]
|
72
|
+
def collect_delegatable_methods
|
73
|
+
methods = Decorator.instance_methods(false)
|
74
|
+
|
75
|
+
COMPONENT_MODULES.each do |mod|
|
76
|
+
methods.concat(Decorator.const_get(mod).instance_methods(false))
|
77
|
+
end
|
78
|
+
|
79
|
+
methods.uniq
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|