natty-ui 0.8.0 → 0.9.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/.yardopts +3 -2
- data/examples/24bit-colors.rb +11 -7
- data/examples/3bit-colors.rb +3 -2
- data/examples/8bit-colors.rb +2 -2
- data/examples/animate.rb +24 -0
- data/examples/attributes.rb +20 -65
- data/examples/demo.rb +39 -203
- data/examples/illustration.rb +27 -23
- data/examples/{list_in_columns.rb → ls.rb} +11 -11
- data/examples/message.rb +30 -0
- data/examples/progress.rb +2 -2
- data/examples/query.rb +6 -2
- data/examples/read_key.rb +13 -0
- data/examples/table.rb +26 -8
- data/lib/natty-ui/ansi.rb +364 -328
- data/lib/natty-ui/ansi_constants.rb +73 -0
- data/lib/natty-ui/ansi_wrapper.rb +68 -23
- data/lib/natty-ui/key_map.rb +119 -0
- data/lib/natty-ui/line_animation/default.rb +35 -0
- data/lib/natty-ui/line_animation/matrix.rb +28 -0
- data/lib/natty-ui/line_animation/rainbow.rb +30 -0
- data/lib/natty-ui/line_animation/test.rb +29 -0
- data/lib/natty-ui/line_animation/type_writer.rb +64 -0
- data/lib/natty-ui/line_animation.rb +54 -0
- data/lib/natty-ui/version.rb +1 -1
- data/lib/natty-ui/wrapper/animate.rb +17 -0
- data/lib/natty-ui/wrapper/ask.rb +2 -8
- data/lib/natty-ui/wrapper/framed.rb +26 -21
- data/lib/natty-ui/wrapper/heading.rb +1 -1
- data/lib/natty-ui/wrapper/horizontal_rule.rb +3 -3
- data/lib/natty-ui/wrapper/list_in_columns.rb +7 -4
- data/lib/natty-ui/wrapper/message.rb +4 -4
- data/lib/natty-ui/wrapper/progress.rb +33 -8
- data/lib/natty-ui/wrapper/query.rb +9 -13
- data/lib/natty-ui/wrapper/request.rb +2 -2
- data/lib/natty-ui/wrapper/section.rb +25 -11
- data/lib/natty-ui/wrapper/table.rb +11 -11
- data/lib/natty-ui/wrapper.rb +19 -14
- data/lib/natty-ui.rb +64 -32
- metadata +16 -5
- data/examples/illustration.png +0 -0
@@ -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,41 +2,77 @@
|
|
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 << Ansi::
|
59
|
+
(@stream << Ansi::SCREEN_BLANK).flush
|
16
60
|
begin
|
17
61
|
yield(self)
|
18
62
|
ensure
|
19
|
-
(@stream << Ansi::
|
63
|
+
(@stream << Ansi::SCREEN_UNBLANK).flush
|
20
64
|
end
|
21
65
|
end
|
22
66
|
|
23
67
|
def cls
|
24
|
-
(@stream << Ansi::
|
68
|
+
(@stream << Ansi::CLS).flush
|
25
69
|
self
|
26
70
|
end
|
27
71
|
|
28
72
|
protected
|
29
73
|
|
30
74
|
def prepare_print(args, kwargs)
|
31
|
-
|
32
|
-
suffix = kwargs[:suffix] and suffix = NattyUI.embellish(suffix)
|
33
|
-
return ["#{prefix}#{suffix}"] if args.empty?
|
34
|
-
NattyUI
|
35
|
-
.each_line(
|
36
|
-
*args.map! { NattyUI.embellish(_1) },
|
37
|
-
max_width: max_with(prefix, suffix, kwargs)
|
38
|
-
)
|
39
|
-
.map { "#{prefix}#{_1}#{suffix}" }
|
75
|
+
_prepare_print(args, kwargs) { NattyUI.embellish(_1) }
|
40
76
|
end
|
41
77
|
|
42
78
|
def temp_func
|
@@ -44,7 +80,7 @@ module NattyUI
|
|
44
80
|
lambda do
|
45
81
|
count = @lines_written - count
|
46
82
|
if count.nonzero?
|
47
|
-
@stream << Ansi.
|
83
|
+
@stream << Ansi.cursor_prev_line(count) << Ansi.display(:erase_below)
|
48
84
|
@lines_written -= count
|
49
85
|
end
|
50
86
|
@stream.flush
|
@@ -53,27 +89,28 @@ module NattyUI
|
|
53
89
|
end
|
54
90
|
|
55
91
|
class Progress < Progress
|
56
|
-
def draw(title)
|
92
|
+
def draw(title, spinner)
|
57
93
|
@msg =
|
58
94
|
"#{@parent.prefix}#{Ansi[:bold, 39]}➔#{Ansi[:reset, 39]} " \
|
59
95
|
"#{title}#{Ansi::RESET} "
|
60
96
|
(wrapper.stream << @msg << Ansi::CURSOR_HIDE).flush
|
61
|
-
@msg = "#{Ansi::
|
97
|
+
@msg = "#{Ansi::CLL}#{@msg}"
|
62
98
|
return @msg << BAR_COLOR if @max_value
|
63
|
-
|
64
|
-
@
|
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
|
65
104
|
end
|
66
105
|
|
67
106
|
def redraw
|
68
|
-
(wrapper.stream << @msg << (@max_value ? fullbar :
|
107
|
+
(wrapper.stream << @msg << (@max_value ? fullbar : @spinner.next)).flush
|
69
108
|
end
|
70
109
|
|
71
110
|
def end_draw
|
72
|
-
(wrapper.stream << Ansi::
|
111
|
+
(wrapper.stream << Ansi::CLL << Ansi::CURSOR_SHOW).flush
|
73
112
|
end
|
74
113
|
|
75
|
-
def indicator = '◐◓◑◒'[(@indicator += 1) % 4]
|
76
|
-
|
77
114
|
def fullbar
|
78
115
|
percent = @value / @max_value
|
79
116
|
count = (30 * percent).to_i
|
@@ -101,8 +138,8 @@ module NattyUI
|
|
101
138
|
ensure
|
102
139
|
count = wrapper.lines_written - count
|
103
140
|
if count.nonzero?
|
104
|
-
wrapper.stream << Ansi.
|
105
|
-
Ansi
|
141
|
+
wrapper.stream << Ansi.cursor_prev_line(count) <<
|
142
|
+
Ansi.display(:erase_below)
|
106
143
|
end
|
107
144
|
wrapper.stream.flush
|
108
145
|
end
|
@@ -127,6 +164,14 @@ module NattyUI
|
|
127
164
|
|
128
165
|
class Framed < Framed
|
129
166
|
def color(str) = "#{Ansi[39]}#{str}#{Ansi::RESET}"
|
167
|
+
|
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]}")
|
174
|
+
end
|
130
175
|
end
|
131
176
|
private_constant :Framed
|
132
177
|
|
@@ -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
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module NattyUI
|
4
|
+
module LineAnimation
|
5
|
+
class Rainbow < None
|
6
|
+
def initialize(*_)
|
7
|
+
super
|
8
|
+
@prefix = to_column
|
9
|
+
end
|
10
|
+
|
11
|
+
def print(line)
|
12
|
+
line = plain(line)
|
13
|
+
11.upto(200) do |spread|
|
14
|
+
(
|
15
|
+
@stream << @prefix <<
|
16
|
+
Ansi.rainbow(
|
17
|
+
line,
|
18
|
+
frequence: 0.1,
|
19
|
+
spread: spread / 100.0,
|
20
|
+
seed: 0
|
21
|
+
)
|
22
|
+
).flush
|
23
|
+
sleep(0.01)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
define rainbow: Rainbow
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module NattyUI
|
4
|
+
module LineAnimation
|
5
|
+
class Test < None
|
6
|
+
def initialize(*_)
|
7
|
+
super
|
8
|
+
@color = attribute(:color, :default)
|
9
|
+
end
|
10
|
+
|
11
|
+
def print(line, column)
|
12
|
+
prefix = "#{Ansi.cursor_column(column)}#{@color}"
|
13
|
+
line = plain(line)
|
14
|
+
str = Array.new(line.size) { CHARS.sample }.join
|
15
|
+
pos = Array.new(line.size, &:itself).shuffle
|
16
|
+
until pos.size < 4
|
17
|
+
pos.shift(pos.size / 4).each { str[_1] = line[_1] }
|
18
|
+
pos.sample(pos.size / 2).each { str[_1] = CHARS.sample }
|
19
|
+
(@stream << "#{prefix}#{str}").flush
|
20
|
+
sleep(0.08)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
CHARS = '2598Z*):.\=+-¦|_ヲアウエオカキケコサシスセソタツテナニヌネハヒホマミムメモヤユラリワ'.chars
|
25
|
+
end
|
26
|
+
|
27
|
+
define test: Test
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module NattyUI
|
4
|
+
module LineAnimation
|
5
|
+
class TypeWriter < None
|
6
|
+
def initialize(*_)
|
7
|
+
super
|
8
|
+
@color = color
|
9
|
+
@cursor_color = attribute(:cursor_color, 0x2e)
|
10
|
+
@column = @options[:prefix_width] + 1
|
11
|
+
@num = 0
|
12
|
+
end
|
13
|
+
|
14
|
+
def print(line)
|
15
|
+
line = plain(line)
|
16
|
+
if (@num += 1).odd?
|
17
|
+
line.each_char do |char|
|
18
|
+
cursor(char)
|
19
|
+
(@stream << char).flush
|
20
|
+
end
|
21
|
+
else
|
22
|
+
pos = @column + line.size
|
23
|
+
line.reverse!
|
24
|
+
line.each_char do |char|
|
25
|
+
@stream << Ansi.cursor_column(pos -= 1)
|
26
|
+
cursor(char)
|
27
|
+
(@stream << char).flush
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def cursor(char)
|
33
|
+
return sleep(0.016) if SPACE.match?(char)
|
34
|
+
@stream << @cursor_color
|
35
|
+
'▁▂▃▄▅▆▇█'.each_char do |cursor|
|
36
|
+
(@stream << cursor).flush
|
37
|
+
sleep(0.002)
|
38
|
+
@stream << CURSOR_BACK
|
39
|
+
end
|
40
|
+
@stream << @color
|
41
|
+
end
|
42
|
+
|
43
|
+
def print_org(line)
|
44
|
+
plain(line).each_char do |char|
|
45
|
+
if SPACE.match?(char)
|
46
|
+
sleep(0.016)
|
47
|
+
else
|
48
|
+
@stream << @cursor_color
|
49
|
+
'▁▂▃▄▅▆▇█'.each_char do |cursor|
|
50
|
+
(@stream << cursor).flush
|
51
|
+
sleep(0.002)
|
52
|
+
@stream << CURSOR_BACK
|
53
|
+
end
|
54
|
+
end
|
55
|
+
(@stream << @color << char).flush
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
CURSOR_BACK = Ansi.cursor_back(1)
|
60
|
+
end
|
61
|
+
|
62
|
+
define type_writer: TypeWriter
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'ansi'
|
4
|
+
|
5
|
+
module NattyUI
|
6
|
+
module LineAnimation
|
7
|
+
def self.defined = @defined.keys
|
8
|
+
def self.defined?(name) = @defined.key?(name)
|
9
|
+
def self.define(**kwargs) = @defined.merge!(kwargs)
|
10
|
+
|
11
|
+
def self.[](name)
|
12
|
+
return if name.nil?
|
13
|
+
klass = @defined[name] || @defined[:default]
|
14
|
+
return klass unless klass.is_a?(String)
|
15
|
+
require(klass)
|
16
|
+
klass = @defined[name] and return klass
|
17
|
+
raise(LoadError, "unknown animation - #{name}")
|
18
|
+
end
|
19
|
+
|
20
|
+
class None
|
21
|
+
def initialize(stream, options)
|
22
|
+
@stream = stream
|
23
|
+
@options = options
|
24
|
+
end
|
25
|
+
|
26
|
+
def print(_line) = nil
|
27
|
+
|
28
|
+
protected
|
29
|
+
|
30
|
+
def to_column = Ansi.cursor_column(@options[:prefix_width] + 1)
|
31
|
+
def color = attribute(:color, :default)
|
32
|
+
def plain(str) = NattyUI.plain(str, ansi: false)
|
33
|
+
|
34
|
+
def attribute(name, *default)
|
35
|
+
att = @options[name] or return Ansi[*default]
|
36
|
+
return Ansi[*att] if att.is_a?(Enumerable)
|
37
|
+
Ansi.try_convert(att.to_s) || Ansi[*default]
|
38
|
+
end
|
39
|
+
|
40
|
+
SPACE = /[[:space:]]/
|
41
|
+
end
|
42
|
+
|
43
|
+
dir = __dir__
|
44
|
+
@defined = {
|
45
|
+
default: "#{dir}/line_animation/default",
|
46
|
+
matrix: "#{dir}/line_animation/matrix",
|
47
|
+
rainbow: "#{dir}/line_animation/rainbow",
|
48
|
+
type_writer: "#{dir}/line_animation/type_writer",
|
49
|
+
test: "#{dir}/line_animation/test"
|
50
|
+
}.compare_by_identity
|
51
|
+
end
|
52
|
+
|
53
|
+
private_constant :LineAnimation
|
54
|
+
end
|
data/lib/natty-ui/version.rb
CHANGED
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module NattyUI
|
4
|
+
module Features
|
5
|
+
# Print given arguments line-wise with animation.
|
6
|
+
#
|
7
|
+
# @overload animate(..., animation: :default)
|
8
|
+
# @param [#to_s] ... objects to print
|
9
|
+
# @param [:default, :matrix, :rainbow, :type_writer]
|
10
|
+
# animation type of animation
|
11
|
+
# @return [Wrapper::Section, Wrapper] it's parent object
|
12
|
+
def animate(*args, **kwargs)
|
13
|
+
kwargs[:animation] ||= :default
|
14
|
+
puts(*args, **kwargs)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/natty-ui/wrapper/ask.rb
CHANGED
@@ -46,19 +46,13 @@ module NattyUI
|
|
46
46
|
yes, no = grab(yes, no)
|
47
47
|
draw(question)
|
48
48
|
while true
|
49
|
-
char = NattyUI.
|
49
|
+
char = NattyUI.read_key(mode: :raw)
|
50
50
|
return if "\3\4".include?(char)
|
51
51
|
return true if yes.include?(char)
|
52
52
|
return false if no.include?(char)
|
53
53
|
end
|
54
|
-
rescue Interrupt, SystemCallError
|
55
|
-
nil
|
56
54
|
ensure
|
57
|
-
|
58
|
-
(wrapper.stream << Ansi::LINE_CLEAR).flush
|
59
|
-
else
|
60
|
-
@parent.puts
|
61
|
-
end
|
55
|
+
wrapper.ansi? ? (wrapper.stream << Ansi::CLL).flush : @parent.puts
|
62
56
|
end
|
63
57
|
|
64
58
|
def draw(question)
|