natty-ui 0.10.0 → 0.11.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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -11
  3. data/examples/24bit-colors.rb +8 -16
  4. data/examples/3bit-colors.rb +1 -1
  5. data/examples/8bit-colors.rb +15 -11
  6. data/examples/animate.rb +31 -11
  7. data/examples/attributes.rb +16 -14
  8. data/examples/attributes_list.rb +14 -0
  9. data/examples/demo.rb +4 -4
  10. data/examples/illustration.rb +52 -24
  11. data/examples/ls.rb +4 -4
  12. data/examples/message.rb +11 -9
  13. data/examples/progress.rb +2 -2
  14. data/examples/query.rb +1 -1
  15. data/examples/table.rb +23 -23
  16. data/lib/natty-ui/animation/binary.rb +37 -0
  17. data/lib/natty-ui/animation/default.rb +42 -0
  18. data/lib/natty-ui/animation/matrix.rb +55 -0
  19. data/lib/natty-ui/animation/rainbow.rb +28 -0
  20. data/lib/natty-ui/animation/type_writer.rb +44 -0
  21. data/lib/natty-ui/animation.rb +69 -0
  22. data/lib/natty-ui/ansi/constants.rb +1 -1
  23. data/lib/natty-ui/ansi.rb +46 -26
  24. data/lib/natty-ui/ansi_wrapper.rb +44 -51
  25. data/lib/natty-ui/key_map.rb +72 -49
  26. data/lib/natty-ui/preload.rb +1 -1
  27. data/lib/natty-ui/text.rb +76 -103
  28. data/lib/natty-ui/version.rb +1 -1
  29. data/lib/natty-ui/wrapper/animate.rb +1 -1
  30. data/lib/natty-ui/wrapper/element.rb +18 -10
  31. data/lib/natty-ui/wrapper/features.rb +0 -8
  32. data/lib/natty-ui/wrapper/framed.rb +4 -4
  33. data/lib/natty-ui/wrapper/section.rb +18 -18
  34. data/lib/natty-ui/wrapper/table.rb +432 -171
  35. data/lib/natty-ui/wrapper.rb +37 -20
  36. data/lib/natty-ui.rb +14 -10
  37. metadata +10 -8
  38. data/lib/natty-ui/line_animation/default.rb +0 -36
  39. data/lib/natty-ui/line_animation/matrix.rb +0 -29
  40. data/lib/natty-ui/line_animation/rainbow.rb +0 -31
  41. data/lib/natty-ui/line_animation/type_writer.rb +0 -45
  42. data/lib/natty-ui/line_animation.rb +0 -49
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NattyUI
4
+ module Animation
5
+ class TypeWriter < Base
6
+ protected
7
+
8
+ def write(stream)
9
+ num = 0
10
+ @style = attribute(:style, :default)
11
+ @cursor_style = attribute(:cursor_style, 0x2e)
12
+ @lines.each do |line, size|
13
+ if (num += 1).odd?
14
+ stream << @pos1
15
+ Text.plain(line).each_char { cursor(stream, _1).flush }
16
+ else
17
+ stream << Ansi.cursor_column(@prefix_width + size - 1)
18
+ Text
19
+ .plain(line)
20
+ .reverse
21
+ .each_char { (cursor(stream, _1) << CURSOR_2LEFT).flush }
22
+ end
23
+ stream << Ansi::RESET << @pos1 << line << Ansi::LINE_NEXT
24
+ end
25
+ end
26
+
27
+ def cursor(stream, char)
28
+ stream << @cursor_style
29
+ (SPACE.match?(char) ? '▁' : '▁▂▃▄▅▆▇█').each_char do |cursor|
30
+ (stream << cursor << CURSOR_1LEFT).flush
31
+ sleep(0.002)
32
+ end
33
+ stream << @style << char
34
+ end
35
+
36
+ CURSOR_1LEFT = Ansi.cursor_back(1).freeze
37
+ CURSOR_2LEFT = Ansi.cursor_back(2).freeze
38
+ SPACE = /[[:space:]]/
39
+ end
40
+
41
+ define type_writer: TypeWriter
42
+ private_constant :TypeWriter
43
+ end
44
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NattyUI
4
+ module Animation
5
+ def self.defined = @defined.keys
6
+ def self.defined?(name) = @defined.key?(name)
7
+ def self.define(**kwargs) = @defined.merge!(kwargs)
8
+
9
+ def self.[](name)
10
+ return if name.nil?
11
+ klass = @defined[name] || @defined[:default]
12
+ return klass unless klass.is_a?(String)
13
+ require(klass)
14
+ klass = @defined[name] and return klass
15
+ raise(LoadError, "unknown animation - #{name}")
16
+ end
17
+
18
+ class Base
19
+ attr_reader :lines_written
20
+
21
+ def initialize(wrapper, args, kwargs)
22
+ @prefix = Text.embellish(kwargs[:prefix])
23
+ @suffix = Text.embellish(kwargs[:suffix])
24
+ @prefix_width = kwargs[:prefix_width] || Text.width(@prefix)
25
+ @lines =
26
+ Text.as_lines(
27
+ args.map! { Ansi.blemish(_1) },
28
+ kwargs[:max_width] ||
29
+ wrapper.screen_columns - @prefix_width -
30
+ (kwargs[:suffix_width] || Text.width(@suffix))
31
+ )
32
+ diff = @lines.size - wrapper.screen_rows + 1
33
+ @lines = @lines[diff, wrapper.screen_rows] if diff.positive?
34
+ @options = kwargs
35
+ @prefix_width += 1
36
+ @top = Ansi.cursor_up(@lines_written = @lines.count)
37
+ @pos1 = Ansi.cursor_column(@prefix_width)
38
+ end
39
+
40
+ def perform(stream)
41
+ @lines.each { stream << "#{@prefix}#{' ' * _2}#{@suffix}\n" }
42
+ stream << @top
43
+ write(stream)
44
+ end
45
+
46
+ protected
47
+
48
+ def attribute(name, *default)
49
+ att = @options[name] or return Ansi[*default]
50
+ return Ansi[*att] if att.is_a?(Enumerable)
51
+ Ansi.try_convert(att.to_s) || Ansi[*default]
52
+ end
53
+
54
+ def write(stream) = @lines.each { |line, _| puts(stream, line) }
55
+ def puts(stream, line) = stream << @pos1 << line << Ansi::LINE_NEXT
56
+ end
57
+
58
+ private_constant :Base
59
+
60
+ dir = __dir__
61
+ @defined = {
62
+ binary: "#{dir}/animation/binary",
63
+ default: "#{dir}/animation/default",
64
+ matrix: "#{dir}/animation/matrix",
65
+ rainbow: "#{dir}/animation/rainbow",
66
+ type_writer: "#{dir}/animation/type_writer"
67
+ }.compare_by_identity
68
+ end
69
+ end
@@ -31,7 +31,7 @@ module NattyUI::Ansi
31
31
  RESET = self[:reset].freeze
32
32
 
33
33
  BOLD = self[:bold].freeze
34
- BOLD_OFF = self[:bold_off].freeze
34
+ BOLD_OFF = self[:bold].freeze
35
35
 
36
36
  FAINT = self[:faint].freeze
37
37
  FAINT_OFF = self[:faint_off].freeze
data/lib/natty-ui/ansi.rb CHANGED
@@ -257,7 +257,7 @@ module NattyUI
257
257
  # @return [String] combined ANSI attributes
258
258
  # @return [nil] when string does not contain valid attributes
259
259
  def try_convert(attributes)
260
- return if (attributes = attributes.to_s.split).empty?
260
+ return if !attributes || (attributes = attributes.to_s.split).empty?
261
261
  "\e[#{
262
262
  attributes
263
263
  .map! { ATTR[_1] || CLR[_1] || color(_1) || return }
@@ -349,36 +349,45 @@ module NattyUI
349
349
  PI2_THIRD = 2 * Math::PI / 3
350
350
  PI4_THIRD = 4 * Math::PI / 3
351
351
 
352
- SATTR =
352
+ ATTR =
353
353
  Module
354
354
  .new do
355
355
  def self.to_hash
356
356
  map = {
357
357
  # alternative names
358
- reset: '',
359
- slow_blink: 5,
360
- conceal: 8,
361
- default_font: 10,
362
- doubly: 21,
363
- faint_off: 22,
364
- fraktur_off: 23,
365
- spacing: 26,
366
- conceal_off: 28,
367
- spacing_off: 50,
368
- encircled_off: 54,
369
- subscript_off: 75,
358
+ 'reset' => '',
359
+ 'slow_blink' => 5,
360
+ 'conceal' => 8,
361
+ 'default_font' => 10,
362
+ 'doubly' => 21,
363
+ 'faint_off' => 22,
364
+ 'fraktur_off' => 23,
365
+ 'spacing' => 26,
366
+ 'conceal_off' => 28,
367
+ 'spacing_off' => 50,
368
+ 'encircled_off' => 54,
369
+ 'subscript_off' => 75,
370
370
  # special
371
- curly_underline_off: '4:0',
372
- dotted_underline_off: '4:0',
373
- dashed_underline_off: '4:0',
374
- curly_underline: '4:3',
375
- dotted_underline: '4:4',
376
- dashed_underline: '4:5'
371
+ 'curly_underline_off' => '4:0',
372
+ 'dotted_underline_off' => '4:0',
373
+ 'dashed_underline_off' => '4:0',
374
+ 'curly_underline' => '4:3',
375
+ 'dotted_underline' => '4:4',
376
+ 'dashed_underline' => '4:5',
377
+ # aliases
378
+ 'b' => 1, # bold
379
+ '/b' => 22, # bold_off
380
+ 'i' => 3, # italic
381
+ '/i' => 23, # italic_off
382
+ 'u' => 4, # underline
383
+ '/u' => 24, # underline_off
384
+ 'h' => 8, # hide
385
+ '/h' => 28 # reveal
377
386
  }
378
387
  add = ->(s, n) { n.each_with_index { |a, idx| map[a] = s + idx } }
379
388
  add[
380
389
  1,
381
- %i[
390
+ %w[
382
391
  bold
383
392
  faint
384
393
  italic
@@ -412,7 +421,7 @@ module NattyUI
412
421
  ]
413
422
  add[
414
423
  50,
415
- %i[
424
+ %w[
416
425
  proportional_off
417
426
  framed
418
427
  encircled
@@ -421,12 +430,20 @@ module NattyUI
421
430
  overlined_off
422
431
  ]
423
432
  ]
424
- add[73, %i[superscript subscript superscript_off]]
425
- map
433
+ add[73, %w[superscript subscript superscript_off]]
434
+
435
+ map.merge!(
436
+ map
437
+ .filter_map do |name, att|
438
+ if name.end_with?('_off')
439
+ ["/#{name.delete_suffix('_off')}", att]
440
+ end
441
+ end
442
+ .to_h
443
+ )
426
444
  end
427
445
  end
428
446
  .to_hash
429
- .compare_by_identity
430
447
  .freeze
431
448
 
432
449
  CLR =
@@ -451,9 +468,11 @@ module NattyUI
451
468
  add[90, 'bright_']
452
469
  add[30, 'fg_']
453
470
  map['fg_default'] = 39
471
+ map['/fg'] = 39
454
472
  add[90, 'fg_bright_']
455
473
  add[40, 'bg_']
456
474
  map['bg_default'] = 49
475
+ map['/bg'] = 49
457
476
  add[100, 'bg_bright_']
458
477
  add[40, 'on_']
459
478
  map['on_default'] = 49
@@ -468,6 +487,7 @@ module NattyUI
468
487
  'ul_cyan' => ul[0, 128, 128],
469
488
  'ul_white' => ul[128, 128, 128],
470
489
  'ul_default' => '59',
490
+ '/ul' => '59',
471
491
  'ul_bright_black' => ul[64, 64, 64],
472
492
  'ul_bright_red' => ul[255, 0, 0],
473
493
  'ul_bright_green' => ul[0, 255, 0],
@@ -482,7 +502,7 @@ module NattyUI
482
502
  .to_hash
483
503
  .freeze
484
504
 
485
- ATTR = SATTR.transform_keys(&:to_s).freeze
505
+ SATTR = ATTR.transform_keys(&:to_sym).compare_by_identity.freeze
486
506
  SCLR = CLR.transform_keys(&:to_sym).compare_by_identity.freeze
487
507
 
488
508
  private_constant(
@@ -8,39 +8,10 @@ module NattyUI
8
8
 
9
9
  def puts(*args, **kwargs)
10
10
  return super if args.empty? || (animation = kwargs[:animation]).nil?
11
-
12
- prefix = kwargs[:prefix]
13
- if prefix && !prefix.empty?
14
- prefix = Text.embellish(prefix)
15
- prefix_width = kwargs[:prefix_width] || Text.width(prefix)
16
- else
17
- prefix = nil
18
- prefix_width = 0
19
- end
20
- kwargs[:prefix] = prefix
21
- kwargs[:prefix_width] = prefix_width
22
-
23
- suffix = kwargs[:suffix]
24
- suffix = suffix.empty? ? nil : Texct.embellish(suffix) if suffix
25
-
26
- mw = kwargs[:max_width]
27
- unless mw
28
- mw = screen_columns - prefix_width
29
- mw -= kwargs[:suffix_width] || Text.width(suffix) if suffix
30
- end
31
- kwargs[:max_width] = mw
32
-
33
- (@stream << Ansi::CURSOR_HIDE).flush
34
- animation = LineAnimation[animation].new(@stream, kwargs)
35
- prefix = "#{Ansi::RESET}#{Ansi::CLL}#{prefix}"
36
-
37
- Text.each_embellished_line(args.map! { Ansi.blemish(_1) }, mw) do |line|
38
- @stream << prefix
39
- animation.print(line)
40
- (@stream << "#{prefix}#{line}#{suffix}\n").flush
41
- @lines_written += 1
42
- end
43
-
11
+ animation = Animation[animation].new(wrapper, args, kwargs)
12
+ @stream << Ansi::CURSOR_HIDE
13
+ animation.perform(@stream)
14
+ @lines_written += animation.lines_written
44
15
  (@stream << Ansi::CURSOR_SHOW).flush
45
16
  self
46
17
  end
@@ -65,17 +36,32 @@ module NattyUI
65
36
 
66
37
  protected
67
38
 
68
- def pprint(args, kwargs)
69
- prefix = kwargs[:prefix] and prefix = Text.embellish(prefix)
70
- suffix = kwargs[:suffix] and suffix = Text.embellish(suffix)
71
- return yield("#{prefix}#{suffix}") if args.empty?
72
- Text.each_embellished_line(
73
- args,
74
- kwargs.fetch(:max_width) do
75
- screen_columns - kwargs.fetch(:prefix_width) { Text.width(prefix) } -
76
- kwargs.fetch(:suffix_width) { Text.width(suffix) }
39
+ def pprint(strs, opts)
40
+ prefix = opts[:prefix] and prefix = Text.embellish(prefix)
41
+ suffix = opts[:suffix] and suffix = Text.embellish(suffix)
42
+ return yield("#{prefix}#{suffix}") if strs.empty?
43
+ max_width =
44
+ opts.fetch(:max_width) do
45
+ screen_columns - (opts[:prefix_width] || Text.width(prefix)) -
46
+ (opts[:suffix_width] || Text.width(suffix))
47
+ end
48
+ case opts[:align]
49
+ when :right
50
+ Text.each_line(strs, max_width) do |line, width|
51
+ width = max_width - width
52
+ yield("#{prefix}#{' ' * width}#{line}#{suffix}")
53
+ end
54
+ when :center
55
+ Text.each_line(strs, max_width) do |line, width|
56
+ width = max_width - width
57
+ right = width / 2
58
+ yield(
59
+ "#{prefix}#{' ' * (width - right)}#{line}#{' ' * right}#{suffix}"
60
+ )
77
61
  end
78
- ) { yield("#{prefix}#{_1}#{suffix}") }
62
+ else
63
+ Text.each_line(strs, max_width) { yield("#{prefix}#{_1}#{suffix}") }
64
+ end
79
65
  end
80
66
 
81
67
  def temp_func
@@ -163,18 +149,18 @@ module NattyUI
163
149
  def color(str) = "#{Ansi[39]}#{str}#{Ansi::RESET}"
164
150
 
165
151
  def init(type)
166
- @prefix = "#{color(type[0])} "
152
+ @prefix = "#{color(type[4])} "
167
153
  @suffix = "#{Ansi.cursor_column(parent.rcol)}#{color(type[4])}"
168
154
  aw = @parent.available_width - 2
169
- parent.puts(color("#{type[1]}#{type[2] * aw}#{type[3]}"))
170
- @finish = color("#{type[5]}#{type[6] * aw}#{type[7]}")
155
+ parent.puts(color("#{type[0]}#{type[5] * aw}#{type[1]}"))
156
+ @finish = color("#{type[2]}#{type[5] * aw}#{type[3]}")
171
157
  end
172
158
  end
173
159
 
174
160
  class Message < Section
175
161
  protected
176
162
 
177
- def initialize(parent, title:, glyph:)
163
+ def initialize(parent, title:, glyph:, **opts)
178
164
  color = COLORS[glyph] || COLORS[:default]
179
165
  glyph = NattyUI.glyph(glyph) || glyph
180
166
  prefix_width = Text.width(glyph) + 1
@@ -185,18 +171,25 @@ module NattyUI
185
171
  suffix: Ansi::RESET,
186
172
  suffix_width: 0
187
173
  )
188
- super(parent, prefix: ' ' * prefix_width, prefix_width: prefix_width)
174
+ super(
175
+ parent,
176
+ prefix: ' ' * prefix_width,
177
+ prefix_width: prefix_width,
178
+ **opts
179
+ )
189
180
  end
190
181
 
182
+ white = Ansi[255]
191
183
  COLORS = {
192
- default: Ansi[255],
193
- information: Ansi[255],
184
+ default: white,
185
+ point: white,
186
+ information: white,
194
187
  warning: Ansi[221],
195
188
  error: Ansi[208],
196
189
  completed: Ansi[82],
197
190
  failed: Ansi[196],
198
191
  task: Ansi[39],
199
- query: Ansi[255]
192
+ query: white
200
193
  }.compare_by_identity.freeze
201
194
  end
202
195
 
@@ -4,21 +4,36 @@ module NattyUI
4
4
  KEY_MAP =
5
5
  Module # generator
6
6
  .new do
7
- def self.add_mods(name, code)
8
- @mods.each_pair do |mod, prefix|
9
- @map["\e[1;#{mod}#{code}"] = "#{prefix}+#{name}"
7
+ def self.add_modifiers(**keys)
8
+ @mods.each_pair do |mod, pref|
9
+ @map.merge!(
10
+ keys.to_h do |name, code|
11
+ ["\e[1;#{mod}#{code}", "#{pref}+#{name}"]
12
+ end
13
+ )
10
14
  end
11
15
  end
12
16
 
13
- def self.add_akey(name, code)
14
- @map["\e[#{code}"] = name
15
- add_mods(name, code)
17
+ def self.add_keys(**keys)
18
+ @map.merge!(keys.to_h { |name, code| ["\e[#{code}", name] })
19
+ add_modifiers(**keys)
16
20
  end
17
21
 
18
- def self.add_fkey(name, code)
19
- @map["\e[#{code}~"] = name
22
+ def self.add_fkeys(**keys)
23
+ @map.merge!(keys.to_h { |name, code| ["\e[#{code}~", name] })
20
24
  @mods.each_pair do |mod, prefix|
21
- @map["\e[#{code};#{mod}~"] = "#{prefix}+#{name}"
25
+ @map.merge!(
26
+ keys.to_h do |name, code|
27
+ ["\e[#{code};#{mod}~", "#{prefix}+#{name}"]
28
+ end
29
+ )
30
+ end
31
+ end
32
+
33
+ def self.add_alt_keys(**keys)
34
+ keys.each_pair do |name, code|
35
+ @map[code] = name
36
+ @map["\e#{code}"] = "Alt+#{name}" # kitty
22
37
  end
23
38
  end
24
39
 
@@ -27,51 +42,59 @@ module NattyUI
27
42
  num = 0
28
43
  @map = ('A'..'Z').to_h { [(num += 1).chr, "Ctrl+#{_1}"] }
29
44
 
30
- add_akey('Up', 'A')
31
- add_akey('Down', 'B')
32
- add_akey('Right', 'C')
33
- add_akey('Left', 'D')
34
- add_akey('End', 'F')
35
- add_akey('Home', 'H')
45
+ add_modifiers('F1' => 'P', 'F2' => 'Q', 'F3' => 'R', 'F4' => 'S')
36
46
 
37
- add_mods('F1', 'P')
38
- add_mods('F2', 'Q')
39
- add_mods('F3', 'R')
40
- add_mods('F4', 'S')
47
+ add_keys(
48
+ 'Up' => 'A',
49
+ 'Down' => 'B',
50
+ 'Right' => 'C',
51
+ 'Left' => 'D',
52
+ 'End' => 'F',
53
+ 'Home' => 'H'
54
+ )
41
55
 
42
- add_fkey('DEL', '3')
43
- add_fkey('PgUp', '5')
44
- add_fkey('PgDown', '6')
45
- add_fkey('F1', 'F1')
46
- add_fkey('F2', 'F2')
47
- add_fkey('F3', 'F3')
48
- add_fkey('F4', 'F4')
49
- add_fkey('F5', '15')
50
- add_fkey('F6', '17')
51
- add_fkey('F7', '18')
52
- add_fkey('F8', '19')
53
- add_fkey('F9', '20')
54
- add_fkey('F10', '21')
55
- add_fkey('F11', '23')
56
- add_fkey('F12', '24')
57
- add_fkey('F13', '25')
58
- add_fkey('F14', '26')
59
- add_fkey('F15', '28')
60
- add_fkey('F16', '29')
61
- add_fkey('F17', '31')
62
- add_fkey('F18', '32')
63
- add_fkey('F19', '33')
64
- add_fkey('F20', '34')
56
+ add_fkeys(
57
+ 'DEL' => '3',
58
+ 'PgUp' => '5',
59
+ 'PgDown' => '6',
60
+ # -
61
+ 'F1' => 'F1',
62
+ 'F2' => 'F2',
63
+ 'F3' => 'F3',
64
+ 'F4' => 'F4',
65
+ # -
66
+ 'F5' => '15',
67
+ 'F6' => '17',
68
+ 'F7' => '18',
69
+ 'F8' => '19',
70
+ 'F9' => '20',
71
+ 'F10' => '21',
72
+ 'F11' => '23',
73
+ 'F12' => '24',
74
+ 'F13' => '25',
75
+ 'F14' => '26',
76
+ 'F15' => '28',
77
+ 'F16' => '29',
78
+ 'F17' => '31',
79
+ 'F18' => '32',
80
+ 'F19' => '33',
81
+ 'F20' => '34'
82
+ )
83
+
84
+ add_fkeys('F3' => '13') # kitty
85
+
86
+ add_alt_keys(
87
+ 'ESC' => "\e",
88
+ 'ENTER' => "\r",
89
+ 'TAB' => "\t",
90
+ 'BACK' => "\u007F",
91
+ 'Ctrl+BACK' => "\b",
92
+ 'Shift+TAB' => "\e[Z"
93
+ )
65
94
 
66
95
  # overrides and additionals
67
96
  @map.merge!(
68
- "\e" => 'ESC',
69
- "\4" => 'DEL', # = Ctrl+D
70
- "\u007F" => 'BACK',
71
- "\b" => 'Ctrl+BACK',
72
- "\r" => 'ENTER', # = Ctrl+M
73
- "\t" => 'TAB',
74
- "\e[Z" => 'Shift+TAB',
97
+ "\4" => 'DEL',
75
98
  "\e[5" => 'PgUp',
76
99
  "\e[6" => 'PgDown',
77
100
  # SS3 control (VT 100 etc)
@@ -3,7 +3,7 @@
3
3
  require_relative '../natty-ui'
4
4
 
5
5
  module NattyUI
6
- LineAnimation.defined?(:matrix)
6
+ Animation.defined?(:matrix)
7
7
  Text::EastAsianWidth[0]
8
8
  KEY_MAP[0]
9
9
  end