natty-ui 0.12.0 → 0.25.0
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/LICENSE +1 -1
- data/README.md +23 -24
- data/examples/24bit-colors.rb +4 -9
- data/examples/3bit-colors.rb +28 -8
- data/examples/8bit-colors.rb +18 -23
- data/examples/attributes.rb +30 -25
- data/examples/cols.rb +40 -0
- data/examples/elements.rb +31 -0
- data/examples/examples.rb +45 -0
- data/examples/illustration.rb +56 -54
- data/examples/ls.rb +16 -18
- data/examples/named-colors.rb +23 -0
- data/examples/sections.rb +29 -0
- data/examples/tables.rb +62 -0
- data/examples/tasks.rb +52 -0
- data/lib/natty-ui/attributes.rb +604 -0
- data/lib/natty-ui/choice.rb +56 -0
- data/lib/natty-ui/dumb_choice.rb +45 -0
- data/lib/natty-ui/element.rb +78 -0
- data/lib/natty-ui/features.rb +798 -0
- data/lib/natty-ui/framed.rb +51 -0
- data/lib/natty-ui/ls_renderer.rb +93 -0
- data/lib/natty-ui/progress.rb +187 -0
- data/lib/natty-ui/section.rb +69 -0
- data/lib/natty-ui/table.rb +241 -0
- data/lib/natty-ui/table_renderer.rb +147 -0
- data/lib/natty-ui/task.rb +44 -0
- data/lib/natty-ui/temporary.rb +38 -0
- data/lib/natty-ui/theme.rb +303 -0
- data/lib/natty-ui/utils.rb +79 -0
- data/lib/natty-ui/version.rb +1 -1
- data/lib/natty-ui/width_finder.rb +125 -0
- data/lib/natty-ui.rb +89 -147
- metadata +47 -56
- data/examples/animate.rb +0 -44
- data/examples/attributes_list.rb +0 -14
- data/examples/demo.rb +0 -53
- data/examples/message.rb +0 -32
- data/examples/progress.rb +0 -68
- data/examples/query.rb +0 -41
- data/examples/read_key.rb +0 -13
- data/examples/table.rb +0 -41
- data/lib/natty-ui/animation/binary.rb +0 -36
- data/lib/natty-ui/animation/default.rb +0 -38
- data/lib/natty-ui/animation/matrix.rb +0 -51
- data/lib/natty-ui/animation/rainbow.rb +0 -28
- data/lib/natty-ui/animation/type_writer.rb +0 -44
- data/lib/natty-ui/animation.rb +0 -69
- data/lib/natty-ui/ansi/constants.rb +0 -75
- data/lib/natty-ui/ansi.rb +0 -521
- data/lib/natty-ui/ansi_wrapper.rb +0 -199
- data/lib/natty-ui/frame.rb +0 -53
- data/lib/natty-ui/glyph.rb +0 -64
- data/lib/natty-ui/key_map.rb +0 -142
- data/lib/natty-ui/preload.rb +0 -12
- data/lib/natty-ui/spinner.rb +0 -120
- data/lib/natty-ui/text/east_asian_width.rb +0 -2529
- data/lib/natty-ui/text.rb +0 -203
- data/lib/natty-ui/wrapper/animate.rb +0 -17
- data/lib/natty-ui/wrapper/ask.rb +0 -78
- data/lib/natty-ui/wrapper/element.rb +0 -79
- data/lib/natty-ui/wrapper/features.rb +0 -21
- data/lib/natty-ui/wrapper/framed.rb +0 -45
- data/lib/natty-ui/wrapper/heading.rb +0 -60
- data/lib/natty-ui/wrapper/horizontal_rule.rb +0 -37
- data/lib/natty-ui/wrapper/list_in_columns.rb +0 -138
- data/lib/natty-ui/wrapper/message.rb +0 -109
- data/lib/natty-ui/wrapper/mixins.rb +0 -67
- data/lib/natty-ui/wrapper/progress.rb +0 -74
- data/lib/natty-ui/wrapper/query.rb +0 -89
- data/lib/natty-ui/wrapper/quote.rb +0 -25
- data/lib/natty-ui/wrapper/request.rb +0 -54
- data/lib/natty-ui/wrapper/section.rb +0 -118
- data/lib/natty-ui/wrapper/table.rb +0 -551
- data/lib/natty-ui/wrapper/task.rb +0 -55
- data/lib/natty-ui/wrapper.rb +0 -230
data/examples/tables.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../lib/natty-ui'
|
4
|
+
|
5
|
+
ui.message '[b]ᓚᕠᗢ NattyUI[/b] [i green]Print Tables[/]' do
|
6
|
+
ui.space
|
7
|
+
ui.table(border: :rounded, border_around: true) do |table|
|
8
|
+
table.add(
|
9
|
+
'Header Col 0',
|
10
|
+
'Header Col 1',
|
11
|
+
'Header Col 2',
|
12
|
+
align: :centered,
|
13
|
+
style: %i[bold red]
|
14
|
+
)
|
15
|
+
|
16
|
+
table.add do |row|
|
17
|
+
row.add '[blue]Row 1 Col 0', align: :left, vertical: :middle
|
18
|
+
row.add '[blue]Row 1 Col 1', "Line 2\nLine 3", align: :right
|
19
|
+
row.add '[blue]Row 1 Col 2', align: :centered, vertical: :bottom
|
20
|
+
row.padding = [0, 2]
|
21
|
+
end
|
22
|
+
|
23
|
+
filler_text = <<~FILLER.tr("\n", ' ')
|
24
|
+
This is some filler text to demonstrate word-wise line breaks inside
|
25
|
+
a table cell. Please, just ignore this nonsense-text gently.
|
26
|
+
FILLER
|
27
|
+
table.add do |row|
|
28
|
+
row.add '[blue]Row 2 Col 0', filler_text, align: :right
|
29
|
+
row.add '[blue]Row 2 Col 1', filler_text, align: :centered
|
30
|
+
row.add '[blue]Row 2 Col 2', filler_text, align: :left
|
31
|
+
row.padding = [1, 2]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
ui.space
|
36
|
+
ui.table(border_style: :bright_blue, border: :default) do |table|
|
37
|
+
table.add(*('A'..'Z').each_slice(2).map(&:join))
|
38
|
+
table.add(*('😀'..'😌'))
|
39
|
+
table.add(*(3..15).map { _1.to_s(3) })
|
40
|
+
table.each do |row|
|
41
|
+
row.align = :centered
|
42
|
+
row.style = :bright_yellow
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
ui.space
|
47
|
+
ui.table do |table|
|
48
|
+
table.add do |row|
|
49
|
+
row.add 'green', style: :on_green
|
50
|
+
row.add 'blue', style: :on_blue
|
51
|
+
row.add 'red', style: :on_red
|
52
|
+
row.width = 15
|
53
|
+
row.align = :centered
|
54
|
+
end
|
55
|
+
table.add do |row|
|
56
|
+
row.add 'yellow', style: :on_yellow
|
57
|
+
row.add 'magenta', style: :on_magenta
|
58
|
+
row.add 'cyan', style: :on_cyan
|
59
|
+
row.align = :centered
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/examples/tasks.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../lib/natty-ui'
|
4
|
+
|
5
|
+
ui.message '[b]ᓚᕠᗢ NattyUI[/b] [i green]Tasks[/]' do
|
6
|
+
ui.space
|
7
|
+
ui.puts <<~TEXT, ignore_newline: true
|
8
|
+
Tasks are sections that are closed either successfully or with an error message.
|
9
|
+
If successful, their content is only displayed temporarily and can consist of all
|
10
|
+
other features, in particular further (sub)tasks. As an exception to this, some
|
11
|
+
elements can be “pinned” as permanent content.
|
12
|
+
TEXT
|
13
|
+
|
14
|
+
# to simulate some work:
|
15
|
+
def foo = sleep(0.25)
|
16
|
+
def some = sleep(0.75)
|
17
|
+
|
18
|
+
ui.space
|
19
|
+
ui.task 'Actualize Reading List' do
|
20
|
+
ui.puts('This is a simple which actualizes the book reading list.')
|
21
|
+
|
22
|
+
ui.task('Connect to Library') do
|
23
|
+
foo
|
24
|
+
ui.mark('Server Found', mark: :checkmark)
|
25
|
+
ui.task('Login...') { some }
|
26
|
+
end
|
27
|
+
|
28
|
+
ui.task('Request New Books') { some }
|
29
|
+
|
30
|
+
bar = ui.progress('Loading Books...', pin: true)
|
31
|
+
11.times do
|
32
|
+
foo
|
33
|
+
bar.step
|
34
|
+
end
|
35
|
+
bar.ok 'Books Loaded'
|
36
|
+
|
37
|
+
ui.task('Disconnect from Library') { some }
|
38
|
+
|
39
|
+
ui.progress('Read Cover Images', max: 11) do |progress|
|
40
|
+
while progress.value < progress.max
|
41
|
+
foo
|
42
|
+
progress.step
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
ui.pin('New Books Marked', mark: :checkmark)
|
47
|
+
|
48
|
+
ui.task('Optimize Database') { some }
|
49
|
+
|
50
|
+
ui.task('Remove Dust') { some }
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,604 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module NattyUI
|
4
|
+
# @todo This chapter needs more documentation.
|
5
|
+
#
|
6
|
+
module Attributes
|
7
|
+
class Base
|
8
|
+
# @return [Attributes] updated copy of itself
|
9
|
+
def merge(**attributes)
|
10
|
+
attributes.empty? ? dup : dup._assign(attributes)
|
11
|
+
end
|
12
|
+
|
13
|
+
# @return [Attributes] itself
|
14
|
+
def merge!(**attributes)
|
15
|
+
attributes.empty? ? self : _assign(attributes)
|
16
|
+
end
|
17
|
+
|
18
|
+
# @!visibility private
|
19
|
+
def to_hash
|
20
|
+
_store({})
|
21
|
+
end
|
22
|
+
|
23
|
+
# @!visibility private
|
24
|
+
def to_h(&block)
|
25
|
+
block ? _store({}).to_h(&block) : _store({})
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def initialize(**attributes)
|
31
|
+
_init
|
32
|
+
_assign(attributes) unless attributes.empty?
|
33
|
+
end
|
34
|
+
|
35
|
+
def _init = nil
|
36
|
+
def _assign(_opt) = self
|
37
|
+
def _store(opt) = opt
|
38
|
+
|
39
|
+
def as_uint(value) = [0, value.to_i].max
|
40
|
+
def as_nint(value) = ([0, value.to_i].max if value)
|
41
|
+
|
42
|
+
def as_wh(value)
|
43
|
+
return unless value
|
44
|
+
return (value = value.to_i) > 0 ? value : nil if value.is_a?(Numeric)
|
45
|
+
value.is_a?(Range) ? wh_from(value.begin.to_i, value.end.to_i) : nil
|
46
|
+
end
|
47
|
+
|
48
|
+
def wh_from(min, max)
|
49
|
+
min = nil if min < 1
|
50
|
+
max = nil if max < 1
|
51
|
+
return max ? (..max) : nil unless min
|
52
|
+
return(min..) unless max
|
53
|
+
min == max ? min : Range.new(*[min, max].minmax)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
module Align
|
58
|
+
# Horizontal element alignment.
|
59
|
+
#
|
60
|
+
# @return [:left, :right, :centered]
|
61
|
+
attr_reader :align
|
62
|
+
|
63
|
+
# @attribute [w] align
|
64
|
+
def align=(value)
|
65
|
+
@align = Utils.align(value)
|
66
|
+
end
|
67
|
+
|
68
|
+
protected
|
69
|
+
|
70
|
+
def _init
|
71
|
+
@align = :left
|
72
|
+
super
|
73
|
+
end
|
74
|
+
|
75
|
+
def _assign(opt)
|
76
|
+
self.align = opt[:align] if opt.key?(:align)
|
77
|
+
super
|
78
|
+
end
|
79
|
+
|
80
|
+
def _store(opt)
|
81
|
+
opt[:align] = @align if @align != :left
|
82
|
+
super
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
module Vertical
|
87
|
+
# Vertical element alignment.
|
88
|
+
#
|
89
|
+
# @return [:top, :bottom, :middle]
|
90
|
+
attr_reader :vertical
|
91
|
+
|
92
|
+
# @attribute [w] vertical
|
93
|
+
def vertical=(value)
|
94
|
+
@vertical = Utils.vertical(value)
|
95
|
+
end
|
96
|
+
|
97
|
+
protected
|
98
|
+
|
99
|
+
def _init
|
100
|
+
@vertical = :top
|
101
|
+
super
|
102
|
+
end
|
103
|
+
|
104
|
+
def _assign(opt)
|
105
|
+
self.vertical = opt[:vertical] if opt.key?(:vertical)
|
106
|
+
super
|
107
|
+
end
|
108
|
+
|
109
|
+
def _store(opt)
|
110
|
+
opt[:vertical] = @vertical if @vertical != :top
|
111
|
+
super
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
module Width
|
116
|
+
# Element width.
|
117
|
+
#
|
118
|
+
# @return [Integer] dedicated element width
|
119
|
+
# @return [Range] width range: {#min_width}..{#max_width}
|
120
|
+
# @return [nil] when unassigned
|
121
|
+
attr_reader :width
|
122
|
+
|
123
|
+
# @attribute [w] width
|
124
|
+
def width=(value)
|
125
|
+
@width = as_wh(value)
|
126
|
+
end
|
127
|
+
|
128
|
+
# Minimum element width.
|
129
|
+
#
|
130
|
+
# @attribute [r] min_width
|
131
|
+
# @return [Integer, nil]
|
132
|
+
def min_width
|
133
|
+
width.is_a?(Range) ? @width.begin : @width
|
134
|
+
end
|
135
|
+
|
136
|
+
# @attribute [w] min_width
|
137
|
+
def min_width=(value)
|
138
|
+
@width = wh_from(value.to_i, max_width.to_i)
|
139
|
+
end
|
140
|
+
|
141
|
+
# Maximum element width.
|
142
|
+
#
|
143
|
+
# @attribute [r] max_width
|
144
|
+
# @return [Integer, nil]
|
145
|
+
def max_width
|
146
|
+
width.is_a?(Range) ? @width.end : @width
|
147
|
+
end
|
148
|
+
|
149
|
+
# @attribute [w] max_width
|
150
|
+
def max_width=(value)
|
151
|
+
@width = wh_from(min_width.to_i, value.to_i)
|
152
|
+
end
|
153
|
+
|
154
|
+
protected
|
155
|
+
|
156
|
+
def _assign(opt)
|
157
|
+
@width = as_wh(opt[:width]) if opt.key?(:width)
|
158
|
+
self.min_width = opt[:min_width] if opt.key?(:min_width)
|
159
|
+
self.max_width = opt[:max_width] if opt.key?(:max_width)
|
160
|
+
super
|
161
|
+
end
|
162
|
+
|
163
|
+
def _store(opt)
|
164
|
+
opt[:width] = @width if @width
|
165
|
+
super
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
module Height
|
170
|
+
# Element height.
|
171
|
+
#
|
172
|
+
# @return [Integer] dedicated element height
|
173
|
+
# @return [Range] height range: {#min_height}..{#max_height}
|
174
|
+
# @return [nil] when unassigned
|
175
|
+
attr_reader :height
|
176
|
+
|
177
|
+
# @attribute [w] height
|
178
|
+
def height=(value)
|
179
|
+
@height = as_wh(value)
|
180
|
+
end
|
181
|
+
|
182
|
+
# Minimum element height.
|
183
|
+
#
|
184
|
+
# @attribute [r] min_height
|
185
|
+
# @return [Integer, nil]
|
186
|
+
def min_height
|
187
|
+
@height.is_a?(Range) ? @height.begin : @height
|
188
|
+
end
|
189
|
+
|
190
|
+
# @attribute [w] min_height
|
191
|
+
def min_height=(value)
|
192
|
+
@height = wh_from(value.to_i, max_height)
|
193
|
+
end
|
194
|
+
|
195
|
+
# Maximum element height.
|
196
|
+
#
|
197
|
+
# @attribute [r] max_height
|
198
|
+
# @return [Integer, nil]
|
199
|
+
def max_height
|
200
|
+
@height.is_a?(Range) ? @height.begin : @height
|
201
|
+
end
|
202
|
+
|
203
|
+
# @attribute [w] max_height
|
204
|
+
def max_height=(value)
|
205
|
+
@height = wh_from(min_height, value.to_i)
|
206
|
+
end
|
207
|
+
|
208
|
+
protected
|
209
|
+
|
210
|
+
def _assign(opt)
|
211
|
+
@height = as_wh(opt[:height]) if opt.key?(:height)
|
212
|
+
self.min_height = opt[:min_height] if opt.key?(:min_height)
|
213
|
+
self.max_height = opt[:max_height] if opt.key?(:max_height)
|
214
|
+
super
|
215
|
+
end
|
216
|
+
|
217
|
+
def _store(opt)
|
218
|
+
opt[:height] = @height if @height
|
219
|
+
super
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
module Padding
|
224
|
+
# Text padding within the element.
|
225
|
+
#
|
226
|
+
# @return [Array<Integer>] top, right, bottom, left
|
227
|
+
attr_reader :padding
|
228
|
+
|
229
|
+
# @attribute [w] padding
|
230
|
+
def padding=(*value)
|
231
|
+
@padding = Utils.padding(*value).freeze
|
232
|
+
end
|
233
|
+
|
234
|
+
# Text top padding.
|
235
|
+
#
|
236
|
+
# @attribute [r] padding_top
|
237
|
+
# @return [Integer]
|
238
|
+
def padding_top = @padding[0]
|
239
|
+
|
240
|
+
# @attribute [w] padding_top
|
241
|
+
def padding_top=(value)
|
242
|
+
@padding[0] = as_uint(value)
|
243
|
+
end
|
244
|
+
|
245
|
+
# Text right padding.
|
246
|
+
#
|
247
|
+
# @attribute [r] padding_right
|
248
|
+
# @return [Integer]
|
249
|
+
def padding_right = @padding[1]
|
250
|
+
|
251
|
+
# @attribute [w] padding_right
|
252
|
+
def padding_right=(value)
|
253
|
+
@padding[1] = as_uint(value)
|
254
|
+
end
|
255
|
+
|
256
|
+
# Text bottom padding.
|
257
|
+
#
|
258
|
+
# @attribute [r] padding_bottom
|
259
|
+
# @return [Integer]
|
260
|
+
def padding_bottom = @padding[2]
|
261
|
+
|
262
|
+
# @attribute [w] padding_bottom
|
263
|
+
def padding_bottom=(value)
|
264
|
+
@padding[2] = as_uint(value)
|
265
|
+
end
|
266
|
+
|
267
|
+
# Text left padding.
|
268
|
+
#
|
269
|
+
# @attribute [r] padding_left
|
270
|
+
# @return [Integer]
|
271
|
+
def padding_left = @padding[3]
|
272
|
+
|
273
|
+
# @attribute [w] padding_left
|
274
|
+
def padding_left=(value)
|
275
|
+
@padding[3] = as_uint(value)
|
276
|
+
end
|
277
|
+
|
278
|
+
protected
|
279
|
+
|
280
|
+
def _init
|
281
|
+
@padding = Array.new(4, 0)
|
282
|
+
super
|
283
|
+
end
|
284
|
+
|
285
|
+
def _assign(opt)
|
286
|
+
self.padding = opt[:padding] if opt.key?(:padding)
|
287
|
+
@padding[0] = as_uint(opt[:padding_top]) if opt.key?(:padding_top)
|
288
|
+
@padding[1] = as_uint(opt[:padding_right]) if opt.key?(:padding_right)
|
289
|
+
@padding[2] = as_uint(opt[:padding_bottom]) if opt.key?(:padding_bottom)
|
290
|
+
@padding[3] = as_uint(opt[:padding_left]) if opt.key?(:padding_left)
|
291
|
+
super
|
292
|
+
end
|
293
|
+
|
294
|
+
def _store(opt)
|
295
|
+
val = @padding.dup
|
296
|
+
if val[1] == val[3]
|
297
|
+
val.pop
|
298
|
+
if val[0] == val[2]
|
299
|
+
if val[0] == val[1]
|
300
|
+
opt[:padding] = val[0] if val[0] != 0
|
301
|
+
return super
|
302
|
+
end
|
303
|
+
val.pop
|
304
|
+
end
|
305
|
+
end
|
306
|
+
opt[:padding] = val
|
307
|
+
super
|
308
|
+
end
|
309
|
+
|
310
|
+
def initialize_copy(*_)
|
311
|
+
super
|
312
|
+
@padding = @padding.dup
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
module Margin
|
317
|
+
# Element margin.
|
318
|
+
#
|
319
|
+
# @return [Array<Integer>] [top, right, bottom, left]
|
320
|
+
attr_reader :margin
|
321
|
+
|
322
|
+
# @attribute [w] margin
|
323
|
+
def margin=(*value)
|
324
|
+
@margin = Utils.margin(*value).freeze
|
325
|
+
end
|
326
|
+
|
327
|
+
# Element top margin.
|
328
|
+
#
|
329
|
+
# @attribute [r] margin_top
|
330
|
+
# @return [Integer]
|
331
|
+
def margin_top = @margin[0]
|
332
|
+
|
333
|
+
# @attribute [w] margin_top
|
334
|
+
def margin_top=(value)
|
335
|
+
@margin[0] = as_uint(value)
|
336
|
+
end
|
337
|
+
|
338
|
+
# Element right margin.
|
339
|
+
#
|
340
|
+
# @attribute [r] margin_right
|
341
|
+
# @return [Integer]
|
342
|
+
def margin_right = @margin[1]
|
343
|
+
|
344
|
+
# @attribute [w] margin_right
|
345
|
+
def margin_right=(value)
|
346
|
+
@margin[1] = as_uint(value)
|
347
|
+
end
|
348
|
+
|
349
|
+
# Element bottom margin.
|
350
|
+
#
|
351
|
+
# @attribute [r] margin_bottom
|
352
|
+
# @return [Integer]
|
353
|
+
def margin_bottom = @margin[2]
|
354
|
+
|
355
|
+
# @attribute [w] margin_bottom
|
356
|
+
def margin_bottom=(value)
|
357
|
+
@margin[2] = as_uint(value)
|
358
|
+
end
|
359
|
+
|
360
|
+
# Element left margin.
|
361
|
+
#
|
362
|
+
# @attribute [r] margin_left
|
363
|
+
# @return [Integer]
|
364
|
+
def margin_left = @margin[3]
|
365
|
+
|
366
|
+
# @attribute [w] margin_left
|
367
|
+
def margin_left=(value)
|
368
|
+
@margin[3] = as_uint(value)
|
369
|
+
end
|
370
|
+
|
371
|
+
protected
|
372
|
+
|
373
|
+
def _init
|
374
|
+
@margin = Array.new(4, 0)
|
375
|
+
super
|
376
|
+
end
|
377
|
+
|
378
|
+
def _assign(opt)
|
379
|
+
self.margin = opt[:margin] if opt.key?(:margin)
|
380
|
+
@margin[0] = as_uint(opt[:margin_top]) if opt.key?(:margin_top)
|
381
|
+
@margin[1] = as_uint(opt[:margin_right]) if opt.key?(:margin_right)
|
382
|
+
@margin[2] = as_uint(opt[:margin_bottom]) if opt.key?(:margin_bottom)
|
383
|
+
@margin[3] = as_uint(opt[:margin_left]) if opt.key?(:margin_left)
|
384
|
+
super
|
385
|
+
end
|
386
|
+
|
387
|
+
def _store(opt)
|
388
|
+
val = @margin.dup
|
389
|
+
if val[1] == val[3]
|
390
|
+
val.pop
|
391
|
+
if val[0] == val[2]
|
392
|
+
if val[0] == val[1]
|
393
|
+
opt[:margin] = val[0] if val[0] != 0
|
394
|
+
return super
|
395
|
+
end
|
396
|
+
val.pop
|
397
|
+
end
|
398
|
+
end
|
399
|
+
opt[:margin] = val
|
400
|
+
super
|
401
|
+
end
|
402
|
+
|
403
|
+
def initialize_copy(*_)
|
404
|
+
super
|
405
|
+
@margin = @margin.dup
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
module Style
|
410
|
+
# Text style.
|
411
|
+
#
|
412
|
+
# @return [Array, nil]
|
413
|
+
attr_reader :style
|
414
|
+
|
415
|
+
# @attribute [w] style
|
416
|
+
def style=(value)
|
417
|
+
@style = Utils.style(value)
|
418
|
+
end
|
419
|
+
|
420
|
+
def style_bbcode
|
421
|
+
"[#{@style.join(' ')}]" if @style
|
422
|
+
end
|
423
|
+
|
424
|
+
protected
|
425
|
+
|
426
|
+
def _assign(opt)
|
427
|
+
@style = Utils.style(opt[:style]) if opt.key?(:style)
|
428
|
+
super
|
429
|
+
end
|
430
|
+
|
431
|
+
def _store(opt)
|
432
|
+
opt[:style] = @style if @style
|
433
|
+
super
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
module BorderStyle
|
438
|
+
# Border style.
|
439
|
+
#
|
440
|
+
# @return [Array, nil]
|
441
|
+
attr_reader :border_style
|
442
|
+
|
443
|
+
# @attribute [w] border_style
|
444
|
+
def border_style=(value)
|
445
|
+
@border_style = Utils.style(value)
|
446
|
+
end
|
447
|
+
|
448
|
+
def border_style_bbcode
|
449
|
+
"[#{@border_style.join(' ')}]" if @border_style
|
450
|
+
end
|
451
|
+
|
452
|
+
protected
|
453
|
+
|
454
|
+
def _assign(opt)
|
455
|
+
@border_style = Utils.style(opt[:border_style]) if opt.key?(
|
456
|
+
:border_style
|
457
|
+
)
|
458
|
+
super
|
459
|
+
end
|
460
|
+
|
461
|
+
def _store(opt)
|
462
|
+
opt[:border_style] = @border_style if @border_style
|
463
|
+
super
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
467
|
+
module BorderAround
|
468
|
+
# Whether the border is around an element.
|
469
|
+
#
|
470
|
+
# @return [true, false]
|
471
|
+
attr_reader :border_around
|
472
|
+
|
473
|
+
# @attribute [w] border_around
|
474
|
+
def border_around=(value)
|
475
|
+
@border_around = value ? true : false
|
476
|
+
end
|
477
|
+
|
478
|
+
protected
|
479
|
+
|
480
|
+
def _assign(opt)
|
481
|
+
@border_around = opt[:border_around]
|
482
|
+
super
|
483
|
+
end
|
484
|
+
|
485
|
+
def _store(opt)
|
486
|
+
opt[:border_around] = true if @border_around
|
487
|
+
super
|
488
|
+
end
|
489
|
+
end
|
490
|
+
|
491
|
+
module Border
|
492
|
+
# Border type.
|
493
|
+
#
|
494
|
+
# @return [
|
495
|
+
# :default,
|
496
|
+
# :rounded,
|
497
|
+
# :heavy,
|
498
|
+
# :double,
|
499
|
+
# :vintage,
|
500
|
+
# :defaulth,
|
501
|
+
# :defaultv,
|
502
|
+
# :heavyh,
|
503
|
+
# :heavyv,
|
504
|
+
# :doubleh,
|
505
|
+
# :doublev
|
506
|
+
# ]
|
507
|
+
# @return [nil] when element has no border
|
508
|
+
attr_reader :border
|
509
|
+
|
510
|
+
# @attribute [w] border
|
511
|
+
def border=(value)
|
512
|
+
if value
|
513
|
+
@border_chars = Theme.current.border(value)
|
514
|
+
@border = value
|
515
|
+
else
|
516
|
+
@border_chars = @border = nil
|
517
|
+
end
|
518
|
+
end
|
519
|
+
|
520
|
+
# @!visibility private
|
521
|
+
# @return [String, nil]
|
522
|
+
attr_reader :border_chars
|
523
|
+
|
524
|
+
protected
|
525
|
+
|
526
|
+
def _assign(opt)
|
527
|
+
self.border = opt[:border] if opt.key?(:border)
|
528
|
+
super
|
529
|
+
end
|
530
|
+
|
531
|
+
def _store(opt)
|
532
|
+
opt[:border] = @border if @border_chars
|
533
|
+
super
|
534
|
+
end
|
535
|
+
end
|
536
|
+
end
|
537
|
+
|
538
|
+
module WithAttributes
|
539
|
+
attr_reader :attributes
|
540
|
+
|
541
|
+
def attributes=(value)
|
542
|
+
@attributes =
|
543
|
+
if value.is_a?(self.class::Attributes)
|
544
|
+
value.dup
|
545
|
+
elsif value.respond_to?(:to_hash)
|
546
|
+
self.class::Attributes.new(**value.to_hash)
|
547
|
+
else
|
548
|
+
self.class::Attributes.new(**value.to_h)
|
549
|
+
end
|
550
|
+
end
|
551
|
+
|
552
|
+
private
|
553
|
+
|
554
|
+
def initialize(**attributes)
|
555
|
+
@attributes = self.class::Attributes.new(**attributes)
|
556
|
+
end
|
557
|
+
|
558
|
+
def initialize_copy(*_)
|
559
|
+
super
|
560
|
+
@attributes = @attributes.dup
|
561
|
+
end
|
562
|
+
end
|
563
|
+
private_constant :WithAttributes
|
564
|
+
|
565
|
+
module TextWithAttributes
|
566
|
+
include WithAttributes
|
567
|
+
|
568
|
+
attr_reader :text
|
569
|
+
|
570
|
+
def empty? = @text.empty?
|
571
|
+
|
572
|
+
alias _to_s to_s
|
573
|
+
private :_to_s
|
574
|
+
|
575
|
+
def to_str = @text.join("\n")
|
576
|
+
alias to_s to_str
|
577
|
+
|
578
|
+
def inspect
|
579
|
+
if (att = @attributes.to_hash).empty?
|
580
|
+
"#{_to_s.chop} @text=#{to_s.inspect}>"
|
581
|
+
else
|
582
|
+
"#{_to_s.chop} @attributes=#{att} @text=#{to_s.inspect}>"
|
583
|
+
end
|
584
|
+
end
|
585
|
+
|
586
|
+
private
|
587
|
+
|
588
|
+
def initialize(*text, **attributes)
|
589
|
+
@text = text
|
590
|
+
@attributes =
|
591
|
+
if text.last.is_a?(self.class::Attributes)
|
592
|
+
text.pop.merge(**@attributes)
|
593
|
+
else
|
594
|
+
self.class::Attributes.new(**attributes)
|
595
|
+
end
|
596
|
+
end
|
597
|
+
|
598
|
+
def initialize_copy(*_)
|
599
|
+
super
|
600
|
+
@text = @text.map(&:dup)
|
601
|
+
end
|
602
|
+
end
|
603
|
+
private_constant :TextWithAttributes
|
604
|
+
end
|