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
@@ -0,0 +1,147 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'terminal/text'
|
4
|
+
require_relative 'width_finder'
|
5
|
+
|
6
|
+
module NattyUI
|
7
|
+
class TableRenderer
|
8
|
+
def self.[](table, max_width)
|
9
|
+
columns = table.columns.map(&:width)
|
10
|
+
return [] if columns.empty?
|
11
|
+
attributes = table.attributes
|
12
|
+
unless attributes.border_chars.nil?
|
13
|
+
max_width -= (columns.size - 1)
|
14
|
+
max_width -= 2 if attributes.border_around
|
15
|
+
end
|
16
|
+
return [] if max_width < columns.size
|
17
|
+
new(columns, table.each.to_a, attributes, max_width).lines
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :lines
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def initialize(columns, rows, attributes, max_width)
|
25
|
+
@max_width, @columns = WidthFinder.find(columns, max_width)
|
26
|
+
init_borders(attributes)
|
27
|
+
@columns = @columns.each.with_index
|
28
|
+
@lines = render(rows.shift)
|
29
|
+
@lines.unshift(@b_top) if @b_top
|
30
|
+
rows.each do |row|
|
31
|
+
@lines << @b_between if @b_between
|
32
|
+
@lines += render(row)
|
33
|
+
end
|
34
|
+
@lines << @b_bottom if @b_bottom
|
35
|
+
end
|
36
|
+
|
37
|
+
def render(row)
|
38
|
+
cells = @columns.map { |cw, i| Cell.new(row[i], cw) }
|
39
|
+
height = cells.max_by { _1.text.size }.text.size
|
40
|
+
cells.each { _1.correct_height(height) }
|
41
|
+
Array.new(height) do |line|
|
42
|
+
"#{@b_outer}#{cells.map { _1.text[line] }.join(@b_inner)}#{@b_outer}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def init_borders(attributes)
|
47
|
+
chars = attributes.border_chars or return
|
48
|
+
style = border_style(attributes)
|
49
|
+
@b_inner = style[chars[9]]
|
50
|
+
return init_borders_around(chars, style) if attributes.border_around
|
51
|
+
@b_between = chars[10] * (@max_width + @columns.size - 1)
|
52
|
+
i = -1
|
53
|
+
@columns[0..-2].each { |w| @b_between[i += w + 1] = chars[4] }
|
54
|
+
@b_between = style[@b_between]
|
55
|
+
end
|
56
|
+
|
57
|
+
def init_borders_around(chars, style)
|
58
|
+
fw = chars[10] * (@max_width + @columns.size - 1)
|
59
|
+
@b_top = "#{chars[0]}#{fw}#{chars[2]}"
|
60
|
+
@b_bottom = "#{chars[6]}#{fw}#{chars[8]}"
|
61
|
+
@b_between = "#{chars[3]}#{fw}#{chars[5]}"
|
62
|
+
i = 0
|
63
|
+
@columns[0..-2].each do |w|
|
64
|
+
@b_top[i += w + 1] = chars[1]
|
65
|
+
@b_bottom[i] = chars[7]
|
66
|
+
@b_between[i] = chars[4]
|
67
|
+
end
|
68
|
+
@b_top = style[@b_top]
|
69
|
+
@b_bottom = style[@b_bottom]
|
70
|
+
@b_between = style[@b_between]
|
71
|
+
@b_outer = @b_inner
|
72
|
+
end
|
73
|
+
|
74
|
+
def border_style(attributes)
|
75
|
+
style = attributes.border_style_bbcode
|
76
|
+
style ? ->(line) { "#{style}#{line}[/]" } : lambda(&:itself)
|
77
|
+
end
|
78
|
+
|
79
|
+
class Cell
|
80
|
+
attr_reader :width, :text
|
81
|
+
|
82
|
+
def correct_height(height)
|
83
|
+
return self if (diff = height - @text.size) <= 0
|
84
|
+
@text =
|
85
|
+
case @vertical
|
86
|
+
when :bottom
|
87
|
+
[Array.new(diff, @empty), @text]
|
88
|
+
when :middle
|
89
|
+
tc = diff / 2
|
90
|
+
[Array.new(tc, @empty), @text, Array.new(diff - tc, @empty)]
|
91
|
+
else
|
92
|
+
[@text, Array.new(diff, @empty)]
|
93
|
+
end.flatten(1)
|
94
|
+
self
|
95
|
+
end
|
96
|
+
|
97
|
+
def initialize(cell, width)
|
98
|
+
return @text = [] unless cell
|
99
|
+
att = cell.attributes
|
100
|
+
@padding = att.padding.dup
|
101
|
+
@align = att.align
|
102
|
+
@vertical = att.vertical
|
103
|
+
@style = att.style_bbcode
|
104
|
+
@text = width_corrected(cell.text, width)
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def width_corrected(text, width)
|
110
|
+
@width, @padding[3], @padding[1] =
|
111
|
+
WidthFinder.adjust(width, @padding[3], @padding[1])
|
112
|
+
@empty = @style ? "#{@style}#{' ' * width}[/]" : ' ' * width
|
113
|
+
[
|
114
|
+
Array.new(@padding[0], @empty),
|
115
|
+
Text.each_line_with_size(
|
116
|
+
*text,
|
117
|
+
limit: @width,
|
118
|
+
bbcode: true,
|
119
|
+
ansi: Terminal.ansi?
|
120
|
+
).map(&txt_fmt),
|
121
|
+
Array.new(@padding[2], @empty)
|
122
|
+
].flatten(1)
|
123
|
+
end
|
124
|
+
|
125
|
+
def txt_fmt
|
126
|
+
lpad, rpad = pads
|
127
|
+
case @align
|
128
|
+
when :right
|
129
|
+
->(txt, w) { "#{lpad}#{' ' * (@width - w)}#{txt}#{rpad}" }
|
130
|
+
when :centered
|
131
|
+
lambda do |txt, w|
|
132
|
+
s = @width - w
|
133
|
+
"#{lpad}#{' ' * (lw = s / 2)}#{txt}#{' ' * (s - lw)}#{rpad}"
|
134
|
+
end
|
135
|
+
else
|
136
|
+
->(txt, w) { "#{lpad}#{txt}#{' ' * (@width - w)}#{rpad}" }
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def pads
|
141
|
+
return ' ' * @padding[3], "[/]#{' ' * @padding[1]}" unless @style
|
142
|
+
["#{@style}#{' ' * @padding[3]}", "#{' ' * @padding[1]}[/]"]
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
private_constant :TableRenderer
|
147
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'element'
|
4
|
+
|
5
|
+
module NattyUI
|
6
|
+
# {Element} implemting a task display section used by {Features.task}.
|
7
|
+
#
|
8
|
+
class Task < Temporary
|
9
|
+
include StateMixin
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def finish_ok(text)
|
14
|
+
NattyUI.back_to_line(@start_line)
|
15
|
+
@start_line = nil
|
16
|
+
cm = Theme.current.mark(:checkmark)
|
17
|
+
text << @title if text.empty?
|
18
|
+
@parent.puts(
|
19
|
+
*text,
|
20
|
+
first_line_prefix: cm,
|
21
|
+
first_line_prefix_width: cm.width,
|
22
|
+
pin: @pin
|
23
|
+
)
|
24
|
+
super()
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize(parent, title, msg, pin)
|
28
|
+
super(parent)
|
29
|
+
@title = title
|
30
|
+
@prefix = ' '
|
31
|
+
@prefix_width = 2
|
32
|
+
@pin = pin
|
33
|
+
style = Theme.current.task_style
|
34
|
+
parent.puts(
|
35
|
+
title,
|
36
|
+
first_line_prefix: "#{style}➔ ",
|
37
|
+
first_line_prefix_width: 2,
|
38
|
+
prefix: "#{style} ",
|
39
|
+
prefix_width: 2
|
40
|
+
)
|
41
|
+
puts(*msg) unless msg.empty?
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'element'
|
4
|
+
|
5
|
+
module NattyUI
|
6
|
+
# {Element} implemting a temprary display section used by
|
7
|
+
# {Features.temporary}.
|
8
|
+
#
|
9
|
+
class Temporary < Element
|
10
|
+
# @!visibility private
|
11
|
+
def puts(*objects, **options)
|
12
|
+
return self if @state
|
13
|
+
if options.delete(:pin)
|
14
|
+
@pins ||= []
|
15
|
+
@pins << [objects, options.except(:prefix_width, :suffix_width)]
|
16
|
+
end
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
# @!visibility private
|
21
|
+
def done
|
22
|
+
return self if @state
|
23
|
+
NattyUI.back_to_line(@start_line) if @start_line
|
24
|
+
@pins&.each { |objects, options| puts(*objects, **options) }
|
25
|
+
@state = :ok
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
alias finish_ok done
|
32
|
+
|
33
|
+
def initialize(parent)
|
34
|
+
@start_line = NattyUI.lines_written
|
35
|
+
super
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,303 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'utils'
|
4
|
+
|
5
|
+
module NattyUI
|
6
|
+
# @todo This chapter needs more documentation.
|
7
|
+
#
|
8
|
+
# A theme defines the style of elements.
|
9
|
+
#
|
10
|
+
class Theme
|
11
|
+
class << self
|
12
|
+
# Currently used theme
|
13
|
+
#
|
14
|
+
# @return [Compiled]
|
15
|
+
attr_reader :current
|
16
|
+
|
17
|
+
# @attribute [w] current
|
18
|
+
def current=(value)
|
19
|
+
case value
|
20
|
+
when Theme::Compiled
|
21
|
+
@current = value
|
22
|
+
when Theme
|
23
|
+
@current = value.compiled
|
24
|
+
else
|
25
|
+
raise(TypeError, 'Theme expected')
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Create a theme.
|
30
|
+
#
|
31
|
+
# @return [Theme] new theme
|
32
|
+
def create
|
33
|
+
theme = new
|
34
|
+
yield(theme) if block_given?
|
35
|
+
theme
|
36
|
+
end
|
37
|
+
|
38
|
+
# Default theme.
|
39
|
+
#
|
40
|
+
# @attribute [r] default
|
41
|
+
# @return [Theme] default theme
|
42
|
+
def default
|
43
|
+
create do |theme|
|
44
|
+
theme.heading_sytle = :bright_blue
|
45
|
+
theme.task_style = %i[bright_green b]
|
46
|
+
# theme.choice_style =
|
47
|
+
theme.choice_current_style = %i[bright_white on_blue b]
|
48
|
+
theme.define_marker(
|
49
|
+
bullet: '[bright_white]•[/fg]',
|
50
|
+
checkmark: '[bright_green]✓[/fg]',
|
51
|
+
quote: '[bright_blue]▍[/fg]',
|
52
|
+
information: '[bright_yellow]𝒊[/fg]',
|
53
|
+
warning: '[bright_yellow]![/fg]',
|
54
|
+
error: '[red]𝙓[/fg]',
|
55
|
+
failed: '[bright_red]𝑭[/fg]',
|
56
|
+
choice: '[bright_white]◦[/fg]',
|
57
|
+
current_choice: '[bright_green]◉[/fg]'
|
58
|
+
)
|
59
|
+
theme.define_section(
|
60
|
+
default: :bright_blue,
|
61
|
+
message: :bright_blue,
|
62
|
+
information: :bright_blue,
|
63
|
+
warning: :bright_yellow,
|
64
|
+
error: :red,
|
65
|
+
failed: :bright_red
|
66
|
+
)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
attr_accessor :section_border
|
72
|
+
attr_reader :mark,
|
73
|
+
:border,
|
74
|
+
:heading,
|
75
|
+
:heading_sytle,
|
76
|
+
:section_styles,
|
77
|
+
:task_style,
|
78
|
+
:choice_current_style,
|
79
|
+
:choice_style
|
80
|
+
|
81
|
+
def heading_sytle=(value)
|
82
|
+
@heading_sytle = Utils.style(value)
|
83
|
+
end
|
84
|
+
|
85
|
+
def task_style=(value)
|
86
|
+
@task_style = Utils.style(value)
|
87
|
+
end
|
88
|
+
|
89
|
+
def choice_current_style=(value)
|
90
|
+
@choice_current_style = Utils.style(value)
|
91
|
+
end
|
92
|
+
|
93
|
+
def choice_style=(value)
|
94
|
+
@choice_style = Utils.style(value)
|
95
|
+
end
|
96
|
+
|
97
|
+
def compiled = Compiled.new(self).freeze
|
98
|
+
|
99
|
+
def define_marker(**defs)
|
100
|
+
@mark.merge!(defs)
|
101
|
+
self
|
102
|
+
end
|
103
|
+
|
104
|
+
def define_border(**defs)
|
105
|
+
defs.each_pair do |name, str|
|
106
|
+
s = str.to_s
|
107
|
+
case Text.width(s, bbcode: false)
|
108
|
+
when 1
|
109
|
+
@border[name.to_sym] = "#{s * 11} "
|
110
|
+
when 11
|
111
|
+
@border[name.to_sym] = "#{s} "
|
112
|
+
when 13
|
113
|
+
@border[name.to_sym] = s
|
114
|
+
else
|
115
|
+
raise(
|
116
|
+
TypeError,
|
117
|
+
"invalid boder definition for #{name} - #{str.inspect}"
|
118
|
+
)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
self
|
122
|
+
end
|
123
|
+
|
124
|
+
def define_heading(*defs)
|
125
|
+
@heading = defs.flatten.take(6)
|
126
|
+
@heading += Array.new(6 - @heading.size, @heading[-1])
|
127
|
+
self
|
128
|
+
end
|
129
|
+
|
130
|
+
def define_section(**defs)
|
131
|
+
defs.each_pair do |name, style|
|
132
|
+
style = Utils.style(style)
|
133
|
+
@section_styles[name.to_sym] = style if style
|
134
|
+
end
|
135
|
+
self
|
136
|
+
end
|
137
|
+
|
138
|
+
class Compiled
|
139
|
+
attr_reader :task_style, :choice_current_style, :choice_style
|
140
|
+
|
141
|
+
def defined_marks = @mark.keys.sort!
|
142
|
+
def defined_borders = @border.keys.sort!
|
143
|
+
|
144
|
+
def heading(index) = @heading[index.to_i.clamp(1, 6) - 1]
|
145
|
+
|
146
|
+
def mark(value)
|
147
|
+
return @mark[value] if value.is_a?(Symbol)
|
148
|
+
(element = Str.new(value, true)).empty? ? @mark[:default] : element
|
149
|
+
end
|
150
|
+
|
151
|
+
def border(value)
|
152
|
+
return @border[value] if value.is_a?(Symbol)
|
153
|
+
case Text.width(value = value.to_s, bbcode: false)
|
154
|
+
when 1
|
155
|
+
"#{value * 11} "
|
156
|
+
when 11
|
157
|
+
"#{value} "
|
158
|
+
when 13
|
159
|
+
value
|
160
|
+
else
|
161
|
+
@border[:default]
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def section_border(kind)
|
166
|
+
kind.is_a?(Symbol) ? @sections[kind] : @sections[:default]
|
167
|
+
end
|
168
|
+
|
169
|
+
def initialize(theme)
|
170
|
+
@heading = create_heading(theme.heading, theme.heading_sytle).freeze
|
171
|
+
@border = create_border(theme.border).freeze
|
172
|
+
@mark = create_mark(theme.mark).freeze
|
173
|
+
@task_style = as_style(theme.task_style)
|
174
|
+
@choice_current_style = as_style(theme.choice_current_style)
|
175
|
+
@choice_style = as_style(theme.choice_style)
|
176
|
+
@sections =
|
177
|
+
create_sections(
|
178
|
+
SectionBorder.create(border(theme.section_border)),
|
179
|
+
theme.section_styles.dup.compare_by_identity
|
180
|
+
)
|
181
|
+
end
|
182
|
+
|
183
|
+
private
|
184
|
+
|
185
|
+
def as_style(value) = (Ansi[*value].freeze if value)
|
186
|
+
|
187
|
+
def create_sections(template, styles)
|
188
|
+
Hash
|
189
|
+
.new do |h, kind|
|
190
|
+
h[kind] = SectionBorder.new(*template.parts(styles[kind])).freeze
|
191
|
+
end
|
192
|
+
.compare_by_identity
|
193
|
+
end
|
194
|
+
|
195
|
+
def create_mark(mark)
|
196
|
+
return {} if mark.empty?
|
197
|
+
with_default(mark.to_h { |n, e| [n.to_sym, Str.new("#{e} ")] })
|
198
|
+
end
|
199
|
+
|
200
|
+
def create_border(border)
|
201
|
+
return {} if border.empty?
|
202
|
+
with_default(border.transform_values { _1.dup.freeze })
|
203
|
+
end
|
204
|
+
|
205
|
+
def create_heading(heading, style)
|
206
|
+
return create_styled_heading(heading, style) if style
|
207
|
+
heading.map do |left|
|
208
|
+
right = " #{left.reverse}"
|
209
|
+
[left = Str.new("#{left} ", true), Str.new(right, left.size)]
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def create_styled_heading(heading, style)
|
214
|
+
heading.map do |left|
|
215
|
+
right = Ansi.decorate(left.reverse, *style)
|
216
|
+
[
|
217
|
+
left = Str.new("#{Ansi.decorate(left, *style)} ", true),
|
218
|
+
Str.new(" #{right}", left.size)
|
219
|
+
]
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
def with_default(map)
|
224
|
+
map.default = (map[:default] ||= map[map.first.first])
|
225
|
+
map.compare_by_identity
|
226
|
+
end
|
227
|
+
|
228
|
+
SectionBorder =
|
229
|
+
Struct.new(:top, :top_left, :top_right, :bottom, :prefix) do
|
230
|
+
def self.create(border)
|
231
|
+
mid = border[10] * 2
|
232
|
+
mid2 = mid * 2
|
233
|
+
right = "#{border[11]}#{border[12] * 2}"
|
234
|
+
new(
|
235
|
+
border[0] + mid2 + right,
|
236
|
+
border[0] + mid,
|
237
|
+
mid + right,
|
238
|
+
border[6] + mid2 + right,
|
239
|
+
border[9]
|
240
|
+
)
|
241
|
+
end
|
242
|
+
|
243
|
+
def parts(style)
|
244
|
+
unless style
|
245
|
+
return [
|
246
|
+
Str.new(top, 6),
|
247
|
+
Str.new("#{top_left} ", 4),
|
248
|
+
Str.new(" #{top_right}", 6),
|
249
|
+
Str.new(bottom, 6),
|
250
|
+
Str.new("#{prefix} ", 2)
|
251
|
+
]
|
252
|
+
end
|
253
|
+
style = Ansi[*style]
|
254
|
+
[
|
255
|
+
# TODO: use rather [/fg]
|
256
|
+
Str.new("#{style}#{top}#{Ansi::RESET}", 6),
|
257
|
+
Str.new("#{style}#{top_left}#{Ansi::RESET} ", 4),
|
258
|
+
Str.new(" #{style}#{top_right}#{Ansi::RESET}", 6),
|
259
|
+
Str.new("#{style}#{bottom}#{Ansi::RESET}", 6),
|
260
|
+
Str.new("#{style}#{prefix}#{Ansi::RESET} ", 2)
|
261
|
+
]
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
private_constant :SectionBorder
|
266
|
+
end
|
267
|
+
|
268
|
+
private
|
269
|
+
|
270
|
+
def initialize
|
271
|
+
define_heading(%w[╸╸╺╸╺━━━ ╴╶╴╶─═══ ╴╶╴╶─── ════ ━━━━ ────])
|
272
|
+
@mark = {
|
273
|
+
default: '•',
|
274
|
+
bullet: '•',
|
275
|
+
checkmark: '✓',
|
276
|
+
quote: '▍',
|
277
|
+
information: '𝒊',
|
278
|
+
warning: '!',
|
279
|
+
error: '𝙓',
|
280
|
+
failed: '𝑭',
|
281
|
+
choice: '◦',
|
282
|
+
current_choice: '◉'
|
283
|
+
}
|
284
|
+
@border = {
|
285
|
+
######### 0123456789012
|
286
|
+
default: '┌┬┐├┼┤└┴┘│─╶╴',
|
287
|
+
defaulth: '───────── ─╶╴',
|
288
|
+
defaultv: '││││││││││ ',
|
289
|
+
double: '╔╦╗╠╬╣╚╩╝║═',
|
290
|
+
doubleh: '═════════ ═',
|
291
|
+
doublev: '║║║║║║║║║║ ',
|
292
|
+
heavy: '┏┳┓┣╋┫┗┻┛┃━╺╸',
|
293
|
+
heavyh: '━━━━━━━━━ ━╺╸',
|
294
|
+
heavyv: '┃┃┃┃┃┃┃┃┃┃ ',
|
295
|
+
rounded: '╭┬╮├┼┤╰┴╯│─╶╴'
|
296
|
+
}
|
297
|
+
@section_border = :rounded
|
298
|
+
@section_styles = {}
|
299
|
+
end
|
300
|
+
|
301
|
+
self.current = Terminal.colors == 2 ? new : default
|
302
|
+
end
|
303
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module NattyUI
|
4
|
+
module Utils
|
5
|
+
class << self
|
6
|
+
# @!visibility private
|
7
|
+
def style(value)
|
8
|
+
value =
|
9
|
+
case value
|
10
|
+
when Array
|
11
|
+
value.dup
|
12
|
+
when Enumerable
|
13
|
+
value.to_a
|
14
|
+
when Symbol, Integer
|
15
|
+
[value]
|
16
|
+
when nil
|
17
|
+
return
|
18
|
+
else
|
19
|
+
value.to_s.delete_prefix('[').delete_suffix(']').split
|
20
|
+
end
|
21
|
+
value.uniq!
|
22
|
+
value.keep_if { Ansi.valid?(_1) }.empty? ? nil : value
|
23
|
+
end
|
24
|
+
|
25
|
+
# @!visibility private
|
26
|
+
def align(value)
|
27
|
+
value == :right || value == :centered ? value : :left
|
28
|
+
end
|
29
|
+
|
30
|
+
# @!visibility private
|
31
|
+
def vertical(value)
|
32
|
+
value == :bottom || value == :middle ? value : :top
|
33
|
+
end
|
34
|
+
|
35
|
+
# @!visibility private
|
36
|
+
def padding(*value)
|
37
|
+
value = value.flatten.take(4).map! { [0, _1.to_i].max }
|
38
|
+
case value.size
|
39
|
+
when 0
|
40
|
+
[0, 0, 0, 0]
|
41
|
+
when 1
|
42
|
+
[value[0], 0, 0, 0]
|
43
|
+
when 2
|
44
|
+
value * 2
|
45
|
+
when 3
|
46
|
+
value << value[1]
|
47
|
+
else
|
48
|
+
value
|
49
|
+
end
|
50
|
+
end
|
51
|
+
alias margin padding
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class Str
|
56
|
+
attr_reader :to_s
|
57
|
+
|
58
|
+
alias to_str to_s
|
59
|
+
def inspect = to_s.inspect
|
60
|
+
def empty? = size == 0
|
61
|
+
|
62
|
+
def size
|
63
|
+
return @size if @size
|
64
|
+
@size = Text.width(self)
|
65
|
+
freeze
|
66
|
+
@size
|
67
|
+
end
|
68
|
+
alias width size
|
69
|
+
|
70
|
+
def initialize(str, size = nil)
|
71
|
+
@to_s = Ansi.bbcode(str).freeze
|
72
|
+
return unless size
|
73
|
+
@size = @size.is_a?(Integer) ? size : Text.width(self)
|
74
|
+
freeze
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
private_constant :Utils, :Str
|
79
|
+
end
|
data/lib/natty-ui/version.rb
CHANGED