natty-ui 0.7.0 → 0.9.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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +7 -3
  3. data/README.md +25 -47
  4. data/examples/24bit-colors.rb +27 -0
  5. data/examples/3bit-colors.rb +14 -0
  6. data/examples/8bit-colors.rb +31 -0
  7. data/examples/animate.rb +24 -0
  8. data/examples/attributes.rb +25 -159
  9. data/examples/demo.rb +53 -0
  10. data/examples/illustration.png +0 -0
  11. data/examples/illustration.rb +29 -0
  12. data/examples/{list_in_columns.rb → ls.rb} +13 -20
  13. data/examples/message.rb +30 -0
  14. data/examples/progress.rb +25 -30
  15. data/examples/query.rb +26 -28
  16. data/examples/read_key.rb +13 -0
  17. data/examples/table.rb +36 -0
  18. data/lib/natty-ui/ansi.rb +351 -305
  19. data/lib/natty-ui/ansi_constants.rb +73 -0
  20. data/lib/natty-ui/ansi_wrapper.rb +136 -162
  21. data/lib/natty-ui/features.rb +11 -18
  22. data/lib/natty-ui/key_map.rb +119 -0
  23. data/lib/natty-ui/line_animation/default.rb +35 -0
  24. data/lib/natty-ui/line_animation/matrix.rb +28 -0
  25. data/lib/natty-ui/line_animation/rainbow.rb +30 -0
  26. data/lib/natty-ui/line_animation/test.rb +29 -0
  27. data/lib/natty-ui/line_animation/type_writer.rb +64 -0
  28. data/lib/natty-ui/line_animation.rb +54 -0
  29. data/lib/natty-ui/version.rb +2 -2
  30. data/lib/natty-ui/wrapper/animate.rb +17 -0
  31. data/lib/natty-ui/wrapper/ask.rb +21 -21
  32. data/lib/natty-ui/wrapper/element.rb +19 -23
  33. data/lib/natty-ui/wrapper/framed.rb +29 -19
  34. data/lib/natty-ui/wrapper/heading.rb +26 -53
  35. data/lib/natty-ui/wrapper/horizontal_rule.rb +37 -0
  36. data/lib/natty-ui/wrapper/list_in_columns.rb +71 -12
  37. data/lib/natty-ui/wrapper/message.rb +20 -27
  38. data/lib/natty-ui/wrapper/progress.rb +40 -13
  39. data/lib/natty-ui/wrapper/query.rb +34 -31
  40. data/lib/natty-ui/wrapper/quote.rb +25 -0
  41. data/lib/natty-ui/wrapper/request.rb +21 -10
  42. data/lib/natty-ui/wrapper/section.rb +55 -39
  43. data/lib/natty-ui/wrapper/table.rb +298 -0
  44. data/lib/natty-ui/wrapper/task.rb +6 -7
  45. data/lib/natty-ui/wrapper.rb +123 -41
  46. data/lib/natty-ui.rb +65 -40
  47. metadata +28 -9
  48. data/examples/basic.rb +0 -62
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NattyUI
4
+ module Ansi
5
+ CURSOR_SHOW = "\e[?25h"
6
+ CURSOR_HIDE = "\e[?25l"
7
+ CURSOR_HOME = cursor_pos(nil, nil)
8
+ CURSOR_FIRST_ROW = cursor_pos(1).freeze
9
+ CURSOR_FIRST_COLUMN = cursor_column(1).freeze
10
+ CURSOR_SAFE_POS_SCO = "\e[s"
11
+ CURSOR_RESTORE_POS_SCO = "\e[u"
12
+ CURSOR_SAFE_POS_DEC = "\e7"
13
+ CURSOR_RESTORE_POS_DEC = "\e8"
14
+ CURSOR_SAFE_POS = CURSOR_SAFE_POS_DEC
15
+ CURSOR_RESTORE_POS = CURSOR_RESTORE_POS_DEC
16
+
17
+ CURSOR_ALIGN_RIGHT = "\e[9999G\e[1D\e[1C" # @!visibility private
18
+
19
+ SCREEN_ERASE = screen_erase(:entire)
20
+ SCREEN_SAVE = "\e[?47h"
21
+ SCREEN_RESTORE = "\e[?47l"
22
+ SCREEN_ALTERNATE = "\e[?1049h"
23
+ SCREEN_ALTERNATE_OFF = "\e[?1049l"
24
+ SCREEN_SAVE_ATTRIBUTES = '\e[#{'
25
+ SCREEN_RESTORE_ATTRIBUTES = '\e[#}'
26
+
27
+ SCREEN_BLANK = "\e[0m\e[s\e[?47h\e[H\e[2J" # @!visibility private
28
+ SCREEN_UNBLANK = "\e[?47l\e[u\e[0m" # @!visibility private
29
+
30
+ LINE_PREVIOUS = cursor_previous_line(1).freeze
31
+ LINE_NEXT = cursor_next_line(1).freeze
32
+ LINE_ERASE = line_erase(:entire)
33
+
34
+ # ANSI control code sequence to erase the screen and set cursor position on
35
+ # upper left corner.
36
+ CLS = (CURSOR_HOME + SCREEN_ERASE).freeze
37
+
38
+ # ANSI control code sequence to erase current line and position to first
39
+ # column.
40
+ CLL = (LINE_ERASE + CURSOR_FIRST_COLUMN).freeze
41
+
42
+ # ANSI control code to reset all attributes.
43
+ RESET = self[:reset].freeze
44
+
45
+ BOLD = self[:bold].freeze
46
+ BOLD_OFF = self[:bold_off].freeze
47
+
48
+ FAINT = self[:faint].freeze
49
+ FAINT_OFF = self[:faint_off].freeze
50
+
51
+ ITALIC = self[:italic].freeze
52
+ ITALIC_OFF = self[:italic_off].freeze
53
+
54
+ STRIKE = self[:strike].freeze
55
+ STRIKE_OFF = self[:strike_off].freeze
56
+
57
+ UNDERLINE = self[:underline].freeze
58
+ DOUBLE_UNDERLINE = self[:double_underline].freeze
59
+ UNDERLINE_OFF = self[:underline_off].freeze
60
+
61
+ CURLY_UNDERLINE = self[:curly_underline].freeze
62
+ CURLY_UNDERLINE_OFF = self[:curly_underline_off].freeze
63
+
64
+ BLINK = self[:blink].freeze
65
+ BLINK_OFF = self[:blink_off].freeze
66
+
67
+ INVERT = self[:invert].freeze
68
+ INVERT_OFF = self[:invert_off].freeze
69
+
70
+ HIDE = self[:hide].freeze
71
+ REVEAL = self[:reveal].freeze
72
+ end
73
+ end
@@ -2,34 +2,85 @@
2
2
 
3
3
  require_relative 'ansi'
4
4
  require_relative 'wrapper'
5
+ require_relative 'line_animation'
5
6
 
6
7
  module NattyUI
7
8
  class AnsiWrapper < Wrapper
8
9
  def ansi? = true
9
10
 
11
+ def puts(*args, **kwargs)
12
+ return super if args.empty? || (animation = kwargs[:animation]).nil?
13
+
14
+ prefix = kwargs[:prefix]
15
+ if prefix && !prefix.empty?
16
+ prefix = NattyUI.embellish(prefix)
17
+ prefix_width = kwargs[:prefix_width] || NattyUI.display_width(prefix)
18
+ else
19
+ prefix = nil
20
+ prefix_width = 0
21
+ end
22
+ kwargs[:prefix] = prefix
23
+ kwargs[:prefix_width] = prefix_width
24
+
25
+ suffix = kwargs[:suffix]
26
+ suffix = suffix.empty? ? nil : NattyUI.embellish(suffix) if suffix
27
+
28
+ mw = kwargs[:max_width]
29
+ unless mw
30
+ mw = screen_columns - prefix_width
31
+ mw -= kwargs[:suffix_width] || NattyUI.display_width(suffix) if suffix
32
+ end
33
+ kwargs[:max_width] = mw
34
+
35
+ (@stream << Ansi::CURSOR_HIDE).flush
36
+ animation = LineAnimation[animation].new(@stream, kwargs)
37
+ prefix = "#{Ansi::RESET}#{Ansi::CLL}#{prefix}"
38
+
39
+ NattyUI
40
+ .each_line(
41
+ *args.map! { NattyUI.embellish(Ansi.blemish(_1)) },
42
+ max_width: mw
43
+ )
44
+ .each do |line|
45
+ @stream << prefix
46
+ animation.print(line)
47
+ (@stream << "#{prefix}#{line}#{suffix}\n").flush
48
+ @lines_written += 1
49
+ end
50
+ (@stream << Ansi::CURSOR_SHOW).flush
51
+ self
52
+ end
53
+
10
54
  def page
11
55
  unless block_given?
12
56
  @stream.flush
13
57
  return self
14
58
  end
15
- (@stream << PAGE_BEGIN).flush
59
+ (@stream << Ansi::SCREEN_BLANK).flush
16
60
  begin
17
61
  yield(self)
18
62
  ensure
19
- (@stream << PAGE_END).flush
63
+ (@stream << Ansi::SCREEN_UNBLANK).flush
20
64
  end
21
65
  end
22
66
 
67
+ def cls
68
+ (@stream << Ansi::CLS).flush
69
+ self
70
+ end
71
+
23
72
  protected
24
73
 
25
- def embellish(obj) = (obj = NattyUI.embellish(obj)).empty? ? nil : obj
74
+ def prepare_print(args, kwargs)
75
+ _prepare_print(args, kwargs) { NattyUI.embellish(_1) }
76
+ end
26
77
 
27
78
  def temp_func
28
79
  count = @lines_written
29
80
  lambda do
30
81
  count = @lines_written - count
31
82
  if count.nonzero?
32
- @stream << Ansi.cursor_line_up(count) << Ansi.screen_erase_below
83
+ @stream << Ansi.cursor_prev_line(count) << Ansi.display(:erase_below)
33
84
  @lines_written -= count
34
85
  end
35
86
  @stream.flush
@@ -37,41 +88,48 @@ module NattyUI
37
88
  end
38
89
  end
39
90
 
40
- class Message < Message
41
- protected
91
+ class Progress < Progress
92
+ def draw(title, spinner)
93
+ @msg =
94
+ "#{@parent.prefix}#{Ansi[:bold, 39]}➔#{Ansi[:reset, 39]} " \
95
+ "#{title}#{Ansi::RESET} "
96
+ (wrapper.stream << @msg << Ansi::CURSOR_HIDE).flush
97
+ @msg = "#{Ansi::CLL}#{@msg}"
98
+ return @msg << BAR_COLOR if @max_value
99
+ spinner_color = Ansi[:bold, 220]
100
+ @spinner =
101
+ Enumerator.new do |y|
102
+ spinner.each_char { y << "#{spinner_color}#{_1}" } while true
103
+ end
104
+ end
105
+
106
+ def redraw
107
+ (wrapper.stream << @msg << (@max_value ? fullbar : @spinner.next)).flush
108
+ end
42
109
 
43
- def title_attr(str, symbol)
44
- color = COLORS[symbol]
45
- {
46
- prefix:
47
- if color
48
- "#{Ansi[:bold, :italic, color]}#{str}" \
49
- "#{Ansi[:reset, :bold, color]} "
50
- else
51
- "#{Ansi[:bold, 231]}#{str} "
52
- end,
53
- suffix: Ansi.reset
54
- }
110
+ def end_draw
111
+ (wrapper.stream << Ansi::CLL << Ansi::CURSOR_SHOW).flush
55
112
  end
56
113
 
57
- COLORS = {
58
- default: 231,
59
- information: 117,
60
- warning: 220,
61
- error: 196,
62
- completed: 46,
63
- failed: 198,
64
- query: 220,
65
- task: 117
66
- }.compare_by_identity.freeze
114
+ def fullbar
115
+ percent = @value / @max_value
116
+ count = (30 * percent).to_i
117
+ mv = max_value.to_i.to_s
118
+ "#{'█' * count}#{BAR_BACK}#{'▁' * (30 - count)}" \
119
+ "#{BAR_INK} #{value.to_i.to_s.rjust(mv.size)}/#{mv} " \
120
+ "(#{(percent * 100).round(2).to_s.rjust(6)})"
121
+ end
122
+
123
+ BAR_COLOR = Ansi[39, 295].freeze
124
+ BAR_BACK = Ansi[236, 492].freeze
125
+ BAR_INK = Ansi[:bold, 255, :on_default].freeze
67
126
  end
68
- private_constant :Message
127
+ private_constant :Progress
69
128
 
70
- class Section < Section
129
+ module Temporary
71
130
  def temporary
72
- stream = wrapper.stream
73
131
  unless block_given?
74
- stream.flush
132
+ wrapper.stream.flush
75
133
  return self
76
134
  end
77
135
  count = wrapper.lines_written
@@ -80,163 +138,79 @@ module NattyUI
80
138
  ensure
81
139
  count = wrapper.lines_written - count
82
140
  if count.nonzero?
83
- stream << Ansi.cursor_line_up(count) << Ansi.screen_erase_below
141
+ wrapper.stream << Ansi.cursor_prev_line(count) <<
142
+ Ansi.display(:erase_below)
84
143
  end
85
- stream.flush
144
+ wrapper.stream.flush
86
145
  end
87
146
  end
147
+ end
148
+ private_constant :Temporary
88
149
 
89
- protected
90
-
91
- def initialize(parent, prefix_attr: nil, **opts)
92
- super
93
- return unless @prefix && prefix_attr
94
- @prefix = Ansi.embellish(@prefix, *prefix_attr)
95
- end
150
+ class Section < Section
151
+ include Temporary
96
152
  end
97
153
  private_constant :Section
98
154
 
99
- class Heading < Heading
100
- protected
155
+ class Quote < Quote
156
+ include Temporary
101
157
 
102
- def enclose(weight)
103
- prefix, suffix = super
104
- ["#{PREFIX}#{prefix}#{MSG}", "#{PREFIX}#{suffix}#{Ansi.reset}"]
158
+ def initialize(...)
159
+ super
160
+ @prefix = "#{Ansi[39]}#{@prefix}#{Ansi::RESET}"
105
161
  end
106
-
107
- PREFIX = Ansi[39].freeze
108
- MSG = Ansi[:bold, 231].freeze
109
162
  end
110
- private_constant :Heading
163
+ private_constant :Quote
111
164
 
112
- class Framed < Section
113
- protected
165
+ class Framed < Framed
166
+ def color(str) = "#{Ansi[39]}#{str}#{Ansi::RESET}"
114
167
 
115
- def initialize(parent, title:, type:, **opts)
116
- @parent = parent
117
- title = title.to_s.tr("\r\n\t", '')
118
- topl, topr, botl, botr, hor, vert = *components(type)
119
- width = available_width
120
- rcount = [width - _plain_width(title) - 6, 0].max
121
- parent.puts(
122
- "#{COLOR}#{topl}#{hor}#{hor}#{Ansi.reset} " \
123
- "#{TITLE_ATTR}#{title}#{Ansi.reset} " \
124
- "#{COLOR}#{hor * rcount}#{topr}#{Ansi.reset}"
125
- )
126
- @bottom = "#{COLOR}#{botl}#{hor * (width - 2)}#{botr}#{Ansi.reset}"
127
- vert = "#{COLOR}#{vert}#{Ansi.reset}"
128
- super(
129
- parent,
130
- prefix: "#{vert} ",
131
- suffix:
132
- "#{Ansi.cursor_right_aligned}" \
133
- "#{Ansi.cursor_left(suffix_width)}#{vert}",
134
- **opts
135
- )
136
- end
137
-
138
- def suffix = "#{super} "
139
- def finish = parent.puts(@bottom)
140
-
141
- def components(type)
142
- COMPONENTS[type] || raise(ArgumentError, "invalid frame type - #{type}")
168
+ def init(deco)
169
+ @prefix = "#{color(deco[0])} "
170
+ @suffix = "#{Ansi.cursor_column(parent.rcol)}#{color(deco[4])}"
171
+ aw = @parent.available_width - 2
172
+ parent.puts(color("#{deco[1]}#{deco[2] * aw}#{deco[3]}"))
173
+ @finish = color("#{deco[5]}#{deco[6] * aw}#{deco[7]}")
143
174
  end
144
-
145
- COLOR = Ansi[39].freeze
146
- TITLE_ATTR = Ansi[:bold, 231].freeze
147
- COMPONENTS = {
148
- rounded: %w[╭ ╮ ╰ ╯ ─ │],
149
- simple: %w[┌ ┐ └ ┘ ─ │],
150
- heavy: %w[┏ ┓ ┗ ┛ ━ ┃],
151
- semi: %w[┍ ┑ ┕ ┙ ━ │],
152
- double: %w[╔ ╗ ╚ ╝ ═ ║]
153
- }.compare_by_identity.freeze
154
175
  end
155
176
  private_constant :Framed
156
177
 
157
- class Ask < Ask
158
- protected
159
-
160
- def query(question)
161
- (wrapper.stream << "#{prefix}#{PREFIX} #{question}#{Ansi.reset} ").flush
162
- end
163
-
164
- def finish = (wrapper.stream << Ansi.line_clear).flush
165
-
166
- PREFIX = "#{Ansi[:bold, :italic, 220]}▶︎#{Ansi[:reset, 220]}".freeze
167
- end
168
- private_constant :Ask
169
-
170
- class Request < Request
171
- def prompt(question) = "#{prefix}#{PREFIX} #{question}#{Ansi.reset} "
172
- def finish = (wrapper.stream << FINISH).flush
173
-
174
- PREFIX = "#{Ansi[:bold, :italic, 220]}▶︎#{Ansi[:reset, 220]}".freeze
175
- FINISH = (Ansi.cursor_line_up + Ansi.line_erase_to_end).freeze
176
- end
177
- private_constant :Request
178
-
179
- class Query < Query
178
+ class Message < Section
180
179
  protected
181
180
 
182
- def read(choices, result_typye)
183
- wrapper.stream << "#{prefix}#{PROMPT} "
184
- super
181
+ def initialize(parent, title:, glyph:)
182
+ wrapper = parent.wrapper
183
+ color = COLORS[glyph] || COLORS[:default]
184
+ glyph = wrapper.glyph(glyph) || glyph
185
+ prefix_width = NattyUI.display_width(glyph) + 1
186
+ parent.puts(
187
+ title,
188
+ prefix: "#{glyph} #{color}",
189
+ prefix_width: prefix_width,
190
+ suffix: Ansi::RESET,
191
+ suffix_width: 0
192
+ )
193
+ super(parent, prefix: ' ' * prefix_width, prefix_width: prefix_width)
185
194
  end
186
195
 
187
- PROMPT = Ansi.embellish(':', :bold, 220).freeze
196
+ COLORS = {
197
+ default: Ansi[255],
198
+ information: Ansi[255],
199
+ warning: Ansi[221],
200
+ error: Ansi[208],
201
+ completed: Ansi[82],
202
+ failed: Ansi[196],
203
+ task: Ansi[39],
204
+ query: Ansi[255]
205
+ }.compare_by_identity.freeze
188
206
  end
189
- private_constant :Query
207
+ private_constant :Message
190
208
 
191
209
  class Task < Message
192
210
  include ProgressAttributes
193
211
  include TaskMethods
194
212
  end
195
213
  private_constant :Task
196
-
197
- class Progress < Progress
198
- protected
199
-
200
- def draw(title)
201
- @prefix = "#{prefix}#{TITLE_PREFIX}#{title}#{Ansi.reset} "
202
- (wrapper.stream << @prefix << Ansi.cursor_hide).flush
203
- @prefix = "#{Ansi.line_clear}#{@prefix}"
204
- return @prefix << BAR_COLOR if @max_value
205
- @prefix << INDICATOR_ATTRIBUTE
206
- @indicator = 0
207
- end
208
-
209
- def redraw
210
- (wrapper.stream << @prefix << (@max_value ? fullbar : indicator)).flush
211
- end
212
-
213
- def end_draw = (wrapper.stream << ERASE).flush
214
- def indicator = '─╲│╱'[(@indicator += 1) % 4]
215
- # def indicator = '⣷⣯⣟⡿⢿⣻⣽⣾'[(@indicator += 1) % 8]
216
-
217
- def fullbar
218
- percent = @value / @max_value
219
- count = (30 * percent).to_i
220
- mv = max_value.to_i.to_s
221
- "#{'█' * count}#{BAR_BACK}#{'▁' * (30 - count)}" \
222
- "#{BAR_INK} #{value.to_i.to_s.rjust(mv.size)}/#{mv} " \
223
- "(#{(percent * 100).round(2).to_s.rjust(6)})"
224
- end
225
-
226
- TITLE_PREFIX = "#{Ansi[:bold, :italic, 117]}➔#{Ansi[:reset, 117]} ".freeze
227
- INDICATOR_ATTRIBUTE = Ansi[:bold, 220].freeze
228
- BAR_COLOR = Ansi[39, 295].freeze
229
- BAR_BACK = Ansi[236, 492].freeze
230
- BAR_INK = Ansi[:bold, 255, :on_default].freeze
231
- ERASE = (Ansi.line_clear + Ansi.cursor_show).freeze
232
- end
233
- private_constant :Progress
234
-
235
- PAGE_BEGIN =
236
- "#{Ansi.reset}#{Ansi.cursor_save_pos}#{Ansi.screen_save}" \
237
- "#{Ansi.cursor_home}#{Ansi.screen_erase}".freeze
238
- PAGE_END =
239
- "#{Ansi.screen_restore}#{Ansi.cursor_restore_pos}#{Ansi.reset}".freeze
240
214
  end
241
215
 
242
216
  private_constant :AnsiWrapper
@@ -5,32 +5,25 @@ module NattyUI
5
5
  # Features of {NattyUI} - methods to display natty elements.
6
6
  #
7
7
  module Features
8
- # Print a horizontal rule
9
- #
10
- # @param [#to_s] symbol string to build the horizontal rule
11
- # @return [Wrapper, Wrapper::Element] itself
12
- def hr(symbol = '═')
13
- symbol = symbol.to_s
14
- size = _plain_width(symbol)
15
- return self if size.zero?
16
- msg = symbol * ((available_width - 1) / size)
17
- return puts(msg, prefix: Ansi[39], suffix: Ansi.reset) if wrapper.ansi?
18
- puts(msg)
8
+ # @return [Wrapper] assigned output stream wrapper
9
+ def wrapper
10
+ return @wrapper if @wrapper
11
+ @wrapper = @parent
12
+ @wrapper = @wrapper.parent until @wrapper.is_a?(Wrapper)
13
+ @wrapper
19
14
  end
20
15
 
21
16
  protected
22
17
 
23
- def _plain_width(str) = NattyUI.display_width(NattyUI.plain(str))
24
- def _blemish_width(str) = NattyUI.display_width(Ansi.blemish(str))
25
-
26
18
  def _element(type, ...)
27
- wrapper.class.const_get(type).__send__(:new, self).__send__(:_call, ...)
19
+ wrapper.class.const_get(type).__send__(:new, self).__send__(:call, ...)
28
20
  end
29
21
 
30
- def _section(owner, type, args, **opts, &block)
31
- sec = wrapper.class.const_get(type).__send__(:new, owner, **opts)
22
+ def _section(type, args = nil, owner: nil, **opts, &block)
23
+ sec = wrapper.class.const_get(type).__send__(:new, owner || self, **opts)
32
24
  sec.puts(*args) if args && !args.empty?
33
- block ? sec.__send__(:_call, &block) : sec
25
+ sec.__send__(:call, &block) if block
26
+ sec
34
27
  end
35
28
  end
36
29
  end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NattyUI
4
+ KEY_MAP =
5
+ Module # generator
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}"
10
+ end
11
+ end
12
+
13
+ def self.add_akey(name, code)
14
+ @map["\e[#{code}"] = name
15
+ add_mods(name, code)
16
+ end
17
+
18
+ def self.add_fkey(name, code)
19
+ @map["\e[#{code}~"] = name
20
+ @mods.each_pair do |mod, prefix|
21
+ @map["\e[#{code};#{mod}~"] = "#{prefix}+#{name}"
22
+ end
23
+ end
24
+
25
+ def self.to_hash
26
+ # control codes
27
+ num = 0
28
+ @map = ('A'..'Z').to_h { [(num += 1).chr, "Ctrl+#{_1}"] }
29
+
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')
36
+
37
+ add_mods('F1', 'P')
38
+ add_mods('F2', 'Q')
39
+ add_mods('F3', 'R')
40
+ add_mods('F4', 'S')
41
+
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')
65
+
66
+ # overrides and additionals
67
+ @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',
75
+ "\e[5" => 'PgUp',
76
+ "\e[6" => 'PgDown',
77
+ # SS3 control (VT 100 etc)
78
+ "\eOA" => 'Up',
79
+ "\eOB" => 'Down',
80
+ "\eOC" => 'Right',
81
+ "\eOD" => 'Left',
82
+ "\eOP" => 'F1',
83
+ "\eOQ" => 'F2',
84
+ "\eOR" => 'F3',
85
+ "\eOS" => 'F4',
86
+ "\eO2P" => 'Shift+F1',
87
+ "\eO2Q" => 'Shift+F2',
88
+ "\eO2R" => 'Shift+F3',
89
+ "\eO2S" => 'Shift+F4',
90
+ "\eOt" => 'F5',
91
+ "\eOu" => 'F6',
92
+ "\eOv" => 'F7',
93
+ "\eOw" => 'F8',
94
+ "\eOl" => 'F9',
95
+ "\eOx" => 'F10'
96
+ )
97
+ end
98
+
99
+ @mods = {
100
+ 2 => 'Shift',
101
+ 3 => 'Alt',
102
+ 4 => 'Alt-Shift',
103
+ 5 => 'Ctrl',
104
+ 6 => 'Ctrl-Shift',
105
+ 7 => 'Ctrl-Alt',
106
+ 8 => 'Ctrl-Alt-Shift',
107
+ 9 => 'Meta',
108
+ 10 => 'Meta-Shift',
109
+ 11 => 'Meta-Alt',
110
+ 12 => 'Meta-Alt-Shift',
111
+ 13 => 'Ctrl-Meta',
112
+ 14 => 'Ctrl-Meta-Shift',
113
+ 15 => 'Ctrl-Meta-Alt',
114
+ 16 => 'Ctrl-Meta-Alt-Shift'
115
+ }.compare_by_identity
116
+ end
117
+ .to_hash
118
+ .freeze
119
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NattyUI
4
+ module LineAnimation
5
+ class Default < None
6
+ def initialize(*_)
7
+ super
8
+ @column = @options[:prefix_width] + 1
9
+ @color = color
10
+ @num = 0
11
+ end
12
+
13
+ def print(line)
14
+ line = plain(line)
15
+ time = 0.5 / line.size
16
+ @stream << @color
17
+ if (@num += 1).odd?
18
+ line.each_char do |char|
19
+ (@stream << char).flush
20
+ sleep(time)
21
+ end
22
+ else
23
+ pos = @column + line.size
24
+ line.reverse!
25
+ line.each_char do |char|
26
+ (@stream << Ansi.cursor_column(pos -= 1) << char).flush
27
+ sleep(time)
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ define default: Default
34
+ end
35
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NattyUI
4
+ module LineAnimation
5
+ class Matrix < None
6
+ def initialize(*_)
7
+ super
8
+ @prefix = "#{to_column}#{color}"
9
+ end
10
+
11
+ def print(line)
12
+ line = plain(line)
13
+ str = Array.new(line.size) { CHARS.sample }.join
14
+ pos = Array.new(line.size, &:itself).shuffle
15
+ until pos.size < 4
16
+ pos.shift(pos.size / 4).each { str[_1] = line[_1] }
17
+ pos.sample(pos.size / 2).each { str[_1] = CHARS.sample }
18
+ (@stream << "#{@prefix}#{str}").flush
19
+ sleep(0.08)
20
+ end
21
+ end
22
+
23
+ CHARS = '2598Z*):.\=+-¦|_ヲアウエオカキケコサシスセソタツテナニヌネハヒホマミムメモヤユラリワ'.chars
24
+ end
25
+
26
+ define matrix: Matrix
27
+ end
28
+ end