termgui 0.0.4
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 +7 -0
- data/Gemfile +14 -0
- data/README.md +321 -0
- data/lib/termgui.rb +1 -0
- data/src/action.rb +58 -0
- data/src/box.rb +90 -0
- data/src/color.rb +174 -0
- data/src/cursor.rb +69 -0
- data/src/editor/editor_base.rb +152 -0
- data/src/editor/editor_base_handlers.rb +116 -0
- data/src/element.rb +61 -0
- data/src/element_bounds.rb +111 -0
- data/src/element_box.rb +64 -0
- data/src/element_render.rb +102 -0
- data/src/element_style.rb +51 -0
- data/src/emitter.rb +102 -0
- data/src/emitter_state.rb +19 -0
- data/src/enterable.rb +93 -0
- data/src/event.rb +92 -0
- data/src/focus.rb +102 -0
- data/src/geometry.rb +53 -0
- data/src/image.rb +60 -0
- data/src/input.rb +85 -0
- data/src/input_grab.rb +17 -0
- data/src/input_time.rb +97 -0
- data/src/key.rb +114 -0
- data/src/log.rb +24 -0
- data/src/node.rb +117 -0
- data/src/node_attributes.rb +27 -0
- data/src/node_visit.rb +52 -0
- data/src/renderer.rb +119 -0
- data/src/renderer_cursor.rb +18 -0
- data/src/renderer_draw.rb +28 -0
- data/src/renderer_image.rb +31 -0
- data/src/renderer_print.rb +40 -0
- data/src/screen.rb +96 -0
- data/src/screen_element.rb +59 -0
- data/src/screen_input.rb +43 -0
- data/src/screen_renderer.rb +53 -0
- data/src/style.rb +149 -0
- data/src/tco/colouring.rb +248 -0
- data/src/tco/config.rb +57 -0
- data/src/tco/palette.rb +603 -0
- data/src/tco/tco_termgui.rb +30 -0
- data/src/termgui.rb +29 -0
- data/src/util/css.rb +98 -0
- data/src/util/css_query.rb +23 -0
- data/src/util/easing.rb +364 -0
- data/src/util/hash_object.rb +131 -0
- data/src/util/imagemagick.rb +27 -0
- data/src/util/justify.rb +20 -0
- data/src/util/unicode-categories.rb +572 -0
- data/src/util/wrap.rb +102 -0
- data/src/util.rb +110 -0
- data/src/widget/button.rb +33 -0
- data/src/widget/checkbox.rb +47 -0
- data/src/widget/col.rb +30 -0
- data/src/widget/image.rb +106 -0
- data/src/widget/inline.rb +40 -0
- data/src/widget/input_number.rb +73 -0
- data/src/widget/inputbox.rb +85 -0
- data/src/widget/label.rb +33 -0
- data/src/widget/modal.rb +69 -0
- data/src/widget/row.rb +26 -0
- data/src/widget/selectbox.rb +100 -0
- data/src/widget/textarea.rb +54 -0
- data/src/xml/xml.rb +80 -0
- data/test/action_test.rb +34 -0
- data/test/box_test.rb +15 -0
- data/test/css_test.rb +39 -0
- data/test/editor/editor_base_test.rb +201 -0
- data/test/element_bounds_test.rb +77 -0
- data/test/element_box_test.rb +8 -0
- data/test/element_render_test.rb +124 -0
- data/test/element_style_test.rb +85 -0
- data/test/element_test.rb +10 -0
- data/test/emitter_test.rb +108 -0
- data/test/event_test.rb +19 -0
- data/test/focus_test.rb +37 -0
- data/test/geometry_test.rb +12 -0
- data/test/input_test.rb +47 -0
- data/test/key_test.rb +14 -0
- data/test/log_test.rb +21 -0
- data/test/node_test.rb +105 -0
- data/test/performance/performance1.rb +48 -0
- data/test/renderer_test.rb +74 -0
- data/test/renderer_test_rect.rb +4 -0
- data/test/screen_test.rb +58 -0
- data/test/style_test.rb +18 -0
- data/test/termgui_test.rb +10 -0
- data/test/test_all.rb +30 -0
- data/test/util_hash_object_test.rb +93 -0
- data/test/util_test.rb +26 -0
- data/test/widget/checkbox_test.rb +99 -0
- data/test/widget/col_test.rb +87 -0
- data/test/widget/inline_test.rb +40 -0
- data/test/widget/label_test.rb +94 -0
- data/test/widget/row_test.rb +40 -0
- data/test/wrap_test.rb +11 -0
- data/test/xml_test.rb +77 -0
- metadata +148 -0
data/src/input_time.rb
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
require_relative 'util'
|
2
|
+
|
3
|
+
# Add Input support for set_timeout and set_interval based on input event loop.
|
4
|
+
# Assumes `@time = Time.now` is assigned on each iteration.
|
5
|
+
module InputTime
|
6
|
+
def initialize(*_args)
|
7
|
+
@time = Time.now
|
8
|
+
@timeout_listeners = []
|
9
|
+
@interval_listeners = []
|
10
|
+
@wait_timeout = 5
|
11
|
+
@wait_interval = 0.1
|
12
|
+
end
|
13
|
+
|
14
|
+
# register a timeout listener that will be called in given seconds (aprox).
|
15
|
+
# Returns a listener object that can be used with clear_timeout to remove the listener
|
16
|
+
def set_timeout(seconds = @interval, block = nil, &proc_block)
|
17
|
+
the_block = block == nil ? proc_block : block
|
18
|
+
throw 'No block provided' if the_block == nil
|
19
|
+
listener = { seconds: seconds, block: the_block, target: @time + seconds }
|
20
|
+
@timeout_listeners.push listener
|
21
|
+
listener
|
22
|
+
end
|
23
|
+
|
24
|
+
# clears a previoulsy registered timeout listener. Use the same block (or what set_timeout returned)
|
25
|
+
def clear_timeout(listener)
|
26
|
+
@timeout_listeners.delete listener
|
27
|
+
end
|
28
|
+
|
29
|
+
# similar to HTML window.setInterval
|
30
|
+
def set_interval(seconds = @interval, block = nil, &proc_block)
|
31
|
+
seconds = seconds == nil ? @interval : seconds
|
32
|
+
the_block = block == nil ? proc_block : block
|
33
|
+
throw 'No block provided' if the_block == nil
|
34
|
+
listener = { seconds: seconds || @interval, block: the_block, next: @time + seconds }
|
35
|
+
@interval_listeners.push listener
|
36
|
+
listener
|
37
|
+
end
|
38
|
+
|
39
|
+
# clears a previoulsy registered interval listener. Use the same block (or what set_interval returned)
|
40
|
+
def clear_interval(listener)
|
41
|
+
@interval_listeners.delete listener
|
42
|
+
end
|
43
|
+
|
44
|
+
def wait_for(predicate, timeout = @wait_timeout, interval = @wait_interval, &block)
|
45
|
+
throw 'No block provided' unless block
|
46
|
+
interval_listener = nil
|
47
|
+
timeout_listener = set_timeout(timeout) do
|
48
|
+
clear_interval interval_listener
|
49
|
+
block.call true
|
50
|
+
end
|
51
|
+
interval_listener = set_interval(interval) do
|
52
|
+
if predicate.call
|
53
|
+
clear_interval interval_listener
|
54
|
+
clear_timeout timeout_listener
|
55
|
+
block.call false
|
56
|
+
end
|
57
|
+
end
|
58
|
+
[timeout_listener, interval_listener]
|
59
|
+
end
|
60
|
+
|
61
|
+
def clear_wait_for(listeners)
|
62
|
+
clear_interval listeners[1]
|
63
|
+
clear_timeout listeners[0]
|
64
|
+
end
|
65
|
+
|
66
|
+
def stop
|
67
|
+
super
|
68
|
+
@timeout_listeners = []
|
69
|
+
@interval_listeners = []
|
70
|
+
end
|
71
|
+
|
72
|
+
protected
|
73
|
+
|
74
|
+
def update_status
|
75
|
+
@time = Time.now
|
76
|
+
dispatch_set_interval
|
77
|
+
dispatch_set_timeout
|
78
|
+
end
|
79
|
+
|
80
|
+
def dispatch_set_timeout
|
81
|
+
@timeout_listeners.each do |listener|
|
82
|
+
if listener[:target] < @time
|
83
|
+
listener[:block].call
|
84
|
+
@timeout_listeners.delete listener
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def dispatch_set_interval
|
90
|
+
@interval_listeners.each do |listener|
|
91
|
+
if listener[:next] < @time
|
92
|
+
listener[:block].call
|
93
|
+
listener[:next] = @time + listener[:seconds]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
data/src/key.rb
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
# maps charsequences like '\n', '\e[1;7C' to objects like {name: 'enter'}, {name: 'right', control: true, meta: true}
|
2
|
+
# TODO: support meta-d ('\xC3': 'C-', '\xB0': 'C-') which is defined by two characters
|
3
|
+
|
4
|
+
require_relative 'log'
|
5
|
+
|
6
|
+
CSI = "\e[".freeze
|
7
|
+
|
8
|
+
CHAR_NAMES = {
|
9
|
+
|
10
|
+
'\e[A': 'up',
|
11
|
+
'\e[1;3A': 'M-up',
|
12
|
+
'e[1;2A': 'S-up',
|
13
|
+
'\e[1;6A': 'S-C-up',
|
14
|
+
'\e[1;7A': 'M-C-up',
|
15
|
+
|
16
|
+
'\e[B': 'down',
|
17
|
+
'\e[1;3B': 'M-down',
|
18
|
+
'e[1;2B': 'S-down',
|
19
|
+
'\e[1;6B': 'S-C-down',
|
20
|
+
'\e[1;7B': 'M-C-down',
|
21
|
+
|
22
|
+
'\e[D': 'left',
|
23
|
+
'\eb': 'M-left',
|
24
|
+
'e[1;2D': 'S-left',
|
25
|
+
'\e[1;6D': 'S-C-left',
|
26
|
+
'\e[1;7D': 'M-C-left',
|
27
|
+
|
28
|
+
'\e[C': 'right',
|
29
|
+
'\ef': 'M-right',
|
30
|
+
'e[1;2C': 'S-right',
|
31
|
+
'\e[1;6C': 'S-C-right',
|
32
|
+
'\e[1;7C': 'M-C-right',
|
33
|
+
|
34
|
+
'\e': 'escape',
|
35
|
+
'\r': 'enter',
|
36
|
+
'\t': 'tab',
|
37
|
+
'\e[Z': 'S-tab',
|
38
|
+
|
39
|
+
'\e[3~': 'delete',
|
40
|
+
"\u232B": 'delete',
|
41
|
+
|
42
|
+
'\x7F': 'backspace',
|
43
|
+
' ': 'space',
|
44
|
+
|
45
|
+
# control-x
|
46
|
+
'\x01': 'C-a',
|
47
|
+
'\x02': 'C-b',
|
48
|
+
'\x03': 'C-c',
|
49
|
+
'\x04': 'C-d',
|
50
|
+
'\x05': 'C-e',
|
51
|
+
'\x06': 'C-f',
|
52
|
+
'\a': 'C-g',
|
53
|
+
'\b': 'C-h',
|
54
|
+
# '\t': "C-i",
|
55
|
+
'\n': 'C-j',
|
56
|
+
'\v': 'C-k',
|
57
|
+
'\f': 'C-l',
|
58
|
+
'\x0E': 'C-n',
|
59
|
+
'\x0F': 'C-o',
|
60
|
+
'\x10': 'C-p',
|
61
|
+
'\x11': 'C-q',
|
62
|
+
'\x12': 'C-r',
|
63
|
+
'\x13': 'C-s',
|
64
|
+
'\x14': 'C-t',
|
65
|
+
'\x15': 'C-u',
|
66
|
+
'\x16': 'C-v',
|
67
|
+
'\x18': 'C-x',
|
68
|
+
'\x19': 'C-y',
|
69
|
+
'\x1A': 'C-z'
|
70
|
+
}.freeze
|
71
|
+
|
72
|
+
def name_to_char(name)
|
73
|
+
char = CHAR_NAMES.keys.find { |c| CHAR_NAMES[c] == name }
|
74
|
+
char ? char.to_s : name
|
75
|
+
end
|
76
|
+
|
77
|
+
def char_to_name(ch)
|
78
|
+
CHAR_NAMES[:"#{ch}"]
|
79
|
+
end
|
80
|
+
|
81
|
+
ALPHANUMERICS =
|
82
|
+
('a'...'z').to_a
|
83
|
+
.concat(('A'..'Z').to_a)
|
84
|
+
.concat(('0'...'9').to_a)
|
85
|
+
.concat(%w[z Z 9])
|
86
|
+
.concat(',.-;:_ç´Ç¨+`*^{}[]¡\'¡¿?ªº\\!"·$%&/()=|@#¢∞¬÷“”'.split(''))
|
87
|
+
|
88
|
+
def alphanumeric?(c)
|
89
|
+
ALPHANUMERICS.include? c
|
90
|
+
end
|
91
|
+
|
92
|
+
NUMERICS = ['.'].concat(('0'...'9').to_a).push('9')
|
93
|
+
|
94
|
+
def numeric?(c)
|
95
|
+
NUMERICS.include? c
|
96
|
+
end
|
97
|
+
|
98
|
+
BRAILE = ['⠋', ',⠇', '⠉', '⠃', '⠙', '⢮', '⢯', '⢱', '⢲', '⢳', '⢴', '⢵', '⢶', '⢷', '⢸', '⢹', '⢺', '⢻', '⢼', '⢽', '⢾', '⠂', '⠃', '⠄', '⠅', '⠆', '⠇', '⠈', '⠉', '⠊', '⠋', '⠍', '⠎', '⠏', '⠐', '⠑', '⠒', '⠓', '⠕', '⠖', '⠗', '⠘', '⠙', '⠚', '⠛', '⠜', '⠝', '⠞', '⠟', '⠠', '⠡', '⠢', '⠣', '⠤', '⠥', '⠦', '⠧', '⠨', '⠩', '⠫', '⠬', '⠭', '⠮', '⠯', '⠰', '⠱', '⠲', '⠳', '⠴', '⠶', '⠷', '⠹', '⠺', '⠻', '⠼', '⠽', '⠾', '⠿', '⡀', '⡁', '⡂', '⡄', '⡅', '⡆', '⡇', '⡈', '⡉', '⡊', '⡋', '⡌', '⡍', '⡏', '⡐', '⡑', '⡒', '⡓', '⡔', '⡕', '⡖', '⡗', '⡘', '⡚', '⡛', '⡝', '⡞', '⡟', '⡠', '⡡', '⡣', '⡤', '⡥', '⡦', '⡧', '⡨', '⡩', '⡪', '⡫', '⡬', '⡭', '⡮', '⡯', '⡱', '⡲', '⡳', '⡴', '⡵', '⡶', '⡷', '⡸', '⡹', '⡺', '⡻', '⡼', '⡽', '⡾', '⡿', '⢀', '⢂', '⢃', '⢄', '⢅', '⢆', '⢇', '⢈', '⢉', '⢊', '⢋', '⢍', '⢎', '⢏', '⢐', '⢑', '⢒', '⢓', '⢔', '⢕', '⢖', '⢗', '⢘', '⢙', '⢚', '⢛', '⢜', '⢝', '⢞', '⢟', '⢠', '⢡', '⢣', '⢤', '⢥', '⢦', '⢧', '⢨', '⢩', '⢪', '⢫', '⢬', '⢭', '⢮', '⢯', '⢱', '⢲', '⢳', '⢴', '⢵', '⢶', '⢷', '⢸', '⢹', '⢺', '⢻', '⢼', '⢽', '⢾', '⢿', '⣀', '⣂', '⣃', '⣄', '⣅', '⣆', '⣇', '⣈', '⣉', '⣊', '⣋', '⣍', '⣎', '⣏', '⣐', '⣑', '⣓', '⣔', '⣕', '⣖', '⣗', '⣙', '⣚', '⣛', '⣜', '⣝', '⣞', '⣟', '⣠', '⣢', '⣣', '⣥', '⣦', '⣧', '⣨', '⣩', '⣪', '⣫', '⣬', '⣭', '⣮', '⣯', '⣱', '⣲', '⣳', '⣴', '⣵', '⣶', '⣷', '⣸', '⣹', '⣺', '⣻', '⣼', '⣽', '⣾', '⣿'].freeze
|
99
|
+
|
100
|
+
BRAILE_FILLED = ['⢮', '⢯', '⢱', '⢲', '⢳', '⢴', '⢵', '⢶', '⢷', '⢻', '⢼', '⢽', '⢾', '⠟', '⠮', '⠯', '⠳', '⠴', '⠶', '⠷', '⠹', '⠺', '⠻', '⠼', '⠽', '⠾', '⠿', '⡝', '⡞', '⡟', '⡪', '⡫', '⡬', '⡭', '⡮', '⡯', '⡱', '⡲', '⡳', '⡴', '⡵', '⡶', '⡷', '⡸', '⡹', '⡺', '⡻', '⡼', '⡽', '⡾', '⡿', '⢜', '⢝', '⢞', '⢟', '⢭', '⢮', '⢯', '⢱', '⢲', '⢳', '⢴', '⢵', '⢶', '⢷', '⢸', '⢹', '⢺', '⢻', '⢼', '⢽', '⢾', '⢿', '⣀', '⣂', '⣃', '⣄', '⣅', '⣆', '⣇', '⣈', '⣉', '⣊', '⣋', '⣍', '⣎', '⣏', '⣐', '⣑', '⣓', '⣔', '⣕', '⣖', '⣗', '⣙', '⣚', '⣛', '⣜', '⣝', '⣞', '⣟', '⣠', '⣢', '⣣', '⣥', '⣦', '⣧', '⣨', '⣩', '⣪', '⣫', '⣬', '⣭', '⣮', '⣯', '⣱', '⣲', '⣳', '⣴', '⣵', '⣶', '⣷', '⣸', '⣹', '⣺', '⣻', '⣼', '⣽', '⣾', '⣿'].freeze
|
101
|
+
|
102
|
+
BLOCKS = ['▀', '▁', '▂', '▃', '▄', '▅', '▆', '▇', '█', '▉', '▊', '▋', '▌', '▍', '▎', '▏', '▐', '░', '▒', '▓', '▔', '▕', '▖', '▗', '▘', '▙', '▚', '▛', '▜', '▝', '▞', '▟', '■', '□', '▢', '▣', '▤', '▥', '▦', '▧', '▨', '▩', '▪', '▫', '▬', '▭', '▮', '▯'].freeze
|
103
|
+
|
104
|
+
GEOMETRIC_SHAPES = ['■', '□', '▢', '▣', '▤', '▥', '▦', '▧', '▨', '▩', '▪', '▫', '▬', '▭', '▮', '▯', '▰', '▱', '▲', '△', '▴', '▵', '▶', '▷', '▸', '▹', '►', '▻', '▼', '▽', '▾', '▿', '◀', '◁', '◂', '◃', '◄', '◅', '◆', '◇', '◈', '◉', '◊', '○', '◌', '◍', '◎', '●', '◐', '◑', '◒', '◓', '◔', '◕', '◖', '◗', '◘', '◙', '◚', '◛', '◜', '◝', '◞', '◟', '◠', '◡', '◢', '◣', '◤', '◥', '◦', '◧', '◨', '◩', '◪', '◫', '◬', '◭', '◮', '◯', '◰', '◱', '◲', '◳', '◴', '◵', '◶', '◷', '◸', '◹', '◺', '◻', '◼', '◽', '◾', '◿'].freeze
|
105
|
+
|
106
|
+
KANGXI = ['⼁', '⼂', '⼃', '⼄', '⼅', '⼆', '⼇', '⼈', '⼉', '⼊', '⼋', '⼌', '⼍', '⼎', '⼏', '⼐', '⼑', '⼒', '⼓', '⼔', '⼕', '⼖', '⼗', '⼘', '⼙', '⼚', '⼛', '⼜', '⼝', '⼞', '⼟', '⼠', '⼡', '⼢', '⼣', '⼤', '⼥', '⼦', '⼧', '⼨', '⼩', '⼪', '⼫', '⼬', '⼭', '⼮', '⼯', '⼰', '⼱', '⼲', '⼳', '⼴', '⼵', '⼶', '⼷', '⼸', '⼹', '⼺', '⼻', '⼼', '⼽', '⼾', '⼿', '⽀', '⽁', '⽂', '⽃', '⽄', '⽅', '⽆', '⽇', '⽈', '⽉', '⽊', '⽋', '⽌', '⽍', '⽎', '⽏', '⽐', '⽑', '⽒', '⽓', '⽔', '⽕', '⽖', '⽗', '⽘', '⽙', '⽚', '⽛', '⽜', '⽝', '⽞', '⽟', '⽠', '⽡', '⽢', '⽣', '⽤', '⽥', '⽦', '⽧', '⽨', '⽩', '⽪', '⽫', '⽬', '⽭', '⽮', '⽯', '⽰', '⽱', '⽲', '⽳', '⽴', '⽵', '⽶', '⽷', '⽸', '⽹', '⽺', '⽻', '⽼', '⽽', '⽾', '⽿', '⾀', '⾁', '⾂', '⾃', '⾄', '⾅', '⾆', '⾇', '⾈', '⾉', '⾊', '⾋', '⾌', '⾍', '⾎', '⾏', '⾐', '⾑', '⾒', '⾓', '⾔', '⾕', '⾖', '⾗', '⾘', '⾙', '⾚', '⾛', '⾜', '⾝', '⾞', '⾟', '⾠', '⾡', '⾢', '⾣', '⾤', '⾥', '⾦', '⾧', '⾨', '⾩', '⾪', '⾫', '⾬', '⾭', '⾮', '⾯', '⾰', '⾱', '⾲', '⾳', '⾴', '⾵', '⾶', '⾷', '⾸', '⾹', '⾺', '⾻', '⾼', '⾽', '⾾', '⾿', '⿀', '⿁', '⿂', '⿃', '⿄', '⿅', '⿆', '⿇', '⿈', '⿉', '⿊', '⿋', '⿌', '⿍', '⿎', '⿏', '⿐', '⿑', '⿒', '⿓', '⿔', '⿕'].freeze
|
107
|
+
|
108
|
+
Yijing = ['䷀', '䷁', '䷂', '䷃', '䷄', '䷅', '䷆', '䷇', '䷈', '䷉', '䷊', '䷋', '䷌', '䷍', '䷎', '䷏', '䷐', '䷑', '䷒', '䷓', '䷔', '䷕', '䷖', '䷗', '䷘', '䷙', '䷚', '䷛', '䷜', '䷝', '䷞', '䷟', '䷠', '䷡', '䷢', '䷣', '䷤', '䷥', '䷦', '䷧', '䷨', '䷩', '䷪', '䷫', '䷬', '䷭', '䷮', '䷯', '䷰', '䷱', '䷲', '䷳', '䷴', '䷵', '䷶', '䷷', '䷸', '䷹', '䷺', '䷻', '䷼', '䷽', '䷾', '䷿'].freeze
|
109
|
+
|
110
|
+
SAURASHTRA = ['ꢂ', 'ꢃ', 'ꢄ', 'ꢅ', 'ꢆ', 'ꢇ', 'ꢈ', 'ꢉ', 'ꢊ', 'ꢋ', 'ꢌ', 'ꢍ', 'ꢎ', 'ꢏ', 'ꢐ', 'ꢑ', 'ꢒ', 'ꢓ', 'ꢔ', 'ꢕ', 'ꢖ', 'ꢗ', 'ꢘ', 'ꢙ', 'ꢚ', 'ꢛ', 'ꢜ', 'ꢝ', 'ꢞ', 'ꢟ', 'ꢠ', 'ꢡ', 'ꢢ', 'ꢣ', 'ꢤ', 'ꢥ', 'ꢦ', 'ꢧ', 'ꢨ', 'ꢩ', 'ꢪ', 'ꢫ', 'ꢬ', 'ꢭ', 'ꢮ', 'ꢯ', 'ꢰ', 'ꢱ', 'ꢲ', 'ꢳ', '꣎', '꣏', '꣐', '꣑', '꣒', '꣓', '꣔', '꣕', '꣖', '꣗', '꣘', '꣙'].freeze
|
111
|
+
|
112
|
+
LINEAR_B_IDEOGRAMS = %w[𐁜 𐁝 𐂀 𐂁 𐂂 𐂃 𐂄 𐂅 𐂆 𐂇 𐂈 𐂉 𐂊 𐂋 𐂌 𐂍 𐂎 𐂏 𐂐 𐂑 𐂒 𐂓 𐂔 𐂕 𐂖 𐂗 𐂘 𐂙 𐂚 𐂛 𐂜 𐂝 𐂞 𐂟 𐂠 𐂡 𐂢 𐂣 𐂤 𐂥 𐂦 𐂧 𐂨 𐂩 𐂪 𐂫 𐂬 𐂭 𐂮 𐂯 𐂰 𐂱 𐂲 𐂳 𐂴 𐂵 𐂶 𐂷 𐂸 𐂹 𐂺 𐂻 𐂼 𐂽 𐂾 𐂿 𐃀 𐃁 𐃂 𐃃 𐃄 𐃅 𐃆 𐃇 𐃈 𐃉 𐃊 𐃋 𐃌 𐃍 𐃎 𐃏 𐃐 𐃑 𐃒 𐃓 𐃔 𐃕 𐃖 𐃗 𐃘 𐃙 𐃚 𐃛 𐃜 𐃝 𐃞 𐃟 𐃠 𐃡 𐃢 𐃣 𐃤 𐃥 𐃦 𐃧 𐃨 𐃩 𐃪 𐃫 𐃬 𐃭 𐃮 𐃯 𐃰 𐃱 𐃲 𐃳 𐃴 𐃵 𐃶 𐃷 𐃸 𐃹 𐃺].freeze
|
113
|
+
|
114
|
+
CUNEIFORM = %w[𒀀 𒀁 𒀂 𒀃 𒀄 𒀅 𒀆 𒀇 𒀈 𒀉 𒀊 𒀋 𒀌 𒀍 𒀎 𒀏 𒀐 𒀑 𒀒 𒀓 𒀔 𒀕 𒀖 𒀗 𒀘 𒀙 𒀚 𒀛 𒀜 𒀝 𒀞 𒀟 𒀠 𒀡 𒀢 𒀣 𒀤 𒀥 𒀦 𒀧 𒀨 𒀩 𒀪 𒀫 𒀬 𒀭 𒀮 𒀯 𒀰 𒀱 𒀲 𒀳 𒀴 𒀵 𒀶 𒀷 𒀸 𒀹 𒀺 𒀻 𒀼 𒀽 𒀾 𒀿 𒁀 𒁁 𒁂 𒁃 𒁄 𒁅 𒁆 𒁇 𒁈 𒁉 𒁊 𒁋 𒁌 𒁍 𒁎 𒁏 𒁐 𒁑 𒁒 𒁓 𒁔 𒁕 𒁖 𒁗 𒁘 𒁙 𒁚 𒁛 𒁜 𒁝 𒁞 𒁟 𒁠 𒁡 𒁢 𒁣 𒁤 𒁥 𒁦 𒁧 𒁨 𒁩 𒁪 𒁫 𒁬 𒁭 𒁮 𒁯 𒁰 𒁱 𒁲 𒁳 𒁴 𒁵 𒁶 𒁷 𒁸 𒁹 𒁺 𒁻 𒁼 𒁽 𒁾 𒁿 𒂀 𒂁 𒂂 𒂃 𒂄 𒂅 𒂆 𒂇 𒂈 𒂉 𒂊 𒂋 𒂌 𒂍 𒂎 𒂏 𒂐 𒂑 𒂒 𒂓 𒂔 𒂕 𒂖 𒂗 𒂘 𒂙 𒂚 𒂛 𒂜 𒂝 𒂞 𒂟 𒂠 𒂡 𒂢 𒂣 𒂤 𒂥 𒂦 𒂧 𒂨 𒂩 𒂪 𒂫 𒂬 𒂭 𒂮 𒂯 𒂰 𒂱 𒂲 𒂳 𒂴 𒂵 𒂶 𒂷 𒂸 𒂹 𒂺 𒂻 𒂼 𒂽 𒂾 𒂿 𒃀 𒃁 𒃂 𒃃 𒃄 𒃅 𒃆 𒃇 𒃈 𒃉 𒃊 𒃋 𒃌 𒃍 𒃎 𒃏 𒃐 𒃑 𒃒 𒃓 𒃔 𒃕 𒃖 𒃗 𒃘 𒃙 𒃚 𒃛 𒃜 𒃝 𒃞 𒃟 𒃠 𒃡 𒃢 𒃣 𒃤 𒃥 𒃦 𒃧 𒃨 𒃩 𒃪 𒃫 𒃬 𒃭 𒃮 𒃯 𒃰 𒃱 𒃲 𒃳 𒃴 𒃵 𒃶 𒃷 𒃸 𒃹 𒃺 𒃻 𒃼 𒃽 𒃾 𒃿 𒄀 𒄁 𒄂 𒄃 𒄄 𒄅 𒄆 𒄇 𒄈 𒄉 𒄊 𒄋 𒄌 𒄍 𒄎 𒄏 𒄐 𒄑 𒄒 𒄓 𒄔 𒄕 𒄖 𒄗 𒄘 𒄙 𒄚 𒄛 𒄜 𒄝 𒄞 𒄟 𒄠 𒄡 𒄢 𒄣 𒄤 𒄥 𒄦 𒄧 𒄨 𒄩 𒄪 𒄫 𒄬 𒄭 𒄮 𒄯 𒄰 𒄱 𒄲 𒄳 𒄴 𒄵 𒄶 𒄷 𒄸 𒄹 𒄺 𒄻 𒄼 𒄽 𒄾 𒄿 𒅀 𒅁 𒅂 𒅃 𒅄 𒅅 𒅆 𒅇 𒅈 𒅉 𒅊 𒅋 𒅌 𒅍 𒅎 𒅏 𒅐 𒅑 𒅒 𒅓 𒅔 𒅕 𒅖 𒅗 𒅘 𒅙 𒅚 𒅛 𒅜 𒅝 𒅞 𒅟 𒅠 𒅡 𒅢 𒅣 𒅤 𒅥 𒅦 𒅧 𒅨 𒅩 𒅪 𒅫 𒅬 𒅭 𒅮 𒅯 𒅰 𒅱 𒅲 𒅳 𒅴 𒅵 𒅶 𒅷 𒅸 𒅹 𒅺 𒅻 𒅼 𒅽 𒅾 𒅿 𒆀 𒆁 𒆂 𒆃 𒆄 𒆅 𒆆 𒆇 𒆈 𒆉 𒆊 𒆋 𒆌 𒆍 𒆎 𒆏 𒆐 𒆑 𒆒 𒆓 𒆔 𒆕 𒆖 𒆗 𒆘 𒆙 𒆚 𒆛 𒆜 𒆝 𒆞 𒆟 𒆠 𒆡 𒆢 𒆣 𒆤 𒆥 𒆦 𒆧 𒆨 𒆩 𒆪 𒆫 𒆬 𒆭 𒆮 𒆯 𒆰 𒆱 𒆲 𒆳 𒆴 𒆵 𒆶 𒆷 𒆸 𒆹 𒆺 𒆻 𒆼 𒆽 𒆾 𒆿 𒇀 𒇁 𒇂 𒇃 𒇄 𒇅 𒇆 𒇇 𒇈 𒇉 𒇊 𒇋 𒇌 𒇍 𒇎 𒇏 𒇐 𒇑 𒇒 𒇓 𒇔 𒇕 𒇖 𒇗 𒇘 𒇙 𒇚 𒇛 𒇜 𒇝 𒇞 𒇟 𒇠 𒇡 𒇢 𒇣 𒇤 𒇥 𒇦 𒇧 𒇨 𒇩 𒇪 𒇫 𒇬 𒇭 𒇮 𒇯 𒇰 𒇱 𒇲 𒇳 𒇴 𒇵 𒇶 𒇷 𒇸 𒇹 𒇺 𒇻 𒇼 𒇽 𒇾 𒇿 𒈀 𒈁 𒈂 𒈃 𒈄 𒈅 𒈆 𒈇 𒈈 𒈉 𒈊 𒈋 𒈌 𒈍 𒈎 𒈏 𒈐 𒈑 𒈒 𒈓 𒈔 𒈕 𒈖 𒈗 𒈘 𒈙 𒈚 𒈛 𒈜 𒈝 𒈞 𒈟 𒈠 𒈡 𒈢 𒈣 𒈤 𒈥 𒈦 𒈧 𒈨 𒈩 𒈪 𒈫 𒈬 𒈭 𒈮 𒈯 𒈰 𒈱 𒈲 𒈳 𒈴 𒈵 𒈶 𒈷 𒈸 𒈹 𒈺 𒈻 𒈼 𒈽 𒈾 𒈿 𒉀 𒉁 𒉂 𒉃 𒉄 𒉅 𒉆 𒉇 𒉈 𒉉 𒉊 𒉋 𒉌 𒉍 𒉎 𒉏 𒉐 𒉑 𒉒 𒉓 𒉔 𒉕 𒉖 𒉗 𒉘 𒉙 𒉚 𒉛 𒉜 𒉝 𒉞 𒉟 𒉠 𒉡 𒉢 𒉣 𒉤 𒉥 𒉦 𒉧 𒉨 𒉩 𒉪 𒉫 𒉬 𒉭 𒉮 𒉯 𒉰 𒉱 𒉲 𒉳 𒉴 𒉵 𒉶 𒉷 𒉸 𒉹 𒉺 𒉻 𒉼 𒉽 𒉾 𒉿 𒊀 𒊁 𒊂 𒊃 𒊄 𒊅 𒊆 𒊇 𒊈 𒊉 𒊊 𒊋 𒊌 𒊍 𒊎 𒊏 𒊐 𒊑 𒊒 𒊓 𒊔 𒊕 𒊖 𒊗 𒊘 𒊙 𒊚 𒊛 𒊜 𒊝 𒊞 𒊟 𒊠 𒊡 𒊢 𒊣 𒊤 𒊥 𒊦 𒊧 𒊨 𒊩 𒊪 𒊫 𒊬 𒊭 𒊮 𒊯 𒊰 𒊱 𒊲 𒊳 𒊴 𒊵 𒊶 𒊷 𒊸 𒊹 𒊺 𒊻 𒊼 𒊽 𒊾 𒊿 𒋀 𒋁 𒋂 𒋃 𒋄 𒋅 𒋆 𒋇 𒋈 𒋉 𒋊 𒋋 𒋌 𒋍 𒋎 𒋏 𒋐 𒋑 𒋒 𒋓 𒋔 𒋕 𒋖 𒋗 𒋘 𒋙 𒋚 𒋛 𒋜 𒋝 𒋞 𒋟 𒋠 𒋡 𒋢 𒋣 𒋤 𒋥 𒋦 𒋧 𒋨 𒋩 𒋪 𒋫 𒋬 𒋭 𒋮 𒋯 𒋰 𒋱 𒋲 𒋳 𒋴 𒋵 𒋶 𒋷 𒋸 𒋹 𒋺 𒋻 𒋼 𒋽 𒋾 𒋿 𒌀 𒌁 𒌂 𒌃 𒌄 𒌅 𒌆 𒌇 𒌈 𒌉 𒌊 𒌋 𒌌 𒌍 𒌎 𒌏 𒌐 𒌑 𒌒 𒌓 𒌔 𒌕 𒌖 𒌗 𒌘 𒌙 𒌚 𒌛 𒌜 𒌝 𒌞 𒌟 𒌠 𒌡 𒌢 𒌣 𒌤 𒌥 𒌦 𒌧 𒌨 𒌩 𒌪 𒌫 𒌬 𒌭 𒌮 𒌯 𒌰 𒌱 𒌲 𒌳 𒌴 𒌵 𒌶 𒌷 𒌸 𒌹 𒌺 𒌻 𒌼 𒌽 𒌾 𒌿 𒍀 𒍁 𒍂 𒍃 𒍄 𒍅 𒍆 𒍇 𒍈 𒍉 𒍊 𒍋 𒍌 𒍍 𒍎 𒍏 𒍐 𒍑 𒍒 𒍓 𒍔 𒍕 𒍖 𒍗 𒍘 𒍙 𒍚 𒍛 𒍜 𒍝 𒍞 𒍟 𒍠 𒍡 𒍢 𒍣 𒍤 𒍥 𒍦 𒍧 𒍨 𒍩 𒍪 𒍫 𒍬 𒍭 𒍮 𒍯 𒍰 𒍱 𒍲 𒍳 𒍴 𒍵 𒍶 𒍷 𒍸 𒍹 𒍺 𒍻 𒍼 𒍽 𒍾 𒍿 𒎀 𒎁 𒎂 𒎃 𒎄 𒎅 𒎆 𒎇 𒎈 𒎉 𒎊 𒎋 𒎌 𒎍 𒎎 𒎏 𒎐 𒎑 𒎒 𒎓 𒎔 𒎕 𒎖 𒎗 𒎘 𒎙].freeze
|
data/src/log.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# Adds logging to file and screen capabilities
|
2
|
+
module Log
|
3
|
+
def log_file=(file)
|
4
|
+
@file = file
|
5
|
+
end
|
6
|
+
|
7
|
+
def log(*args)
|
8
|
+
file = @file || 'tmp_log.txt'
|
9
|
+
s = args.nil? ? 'nil' : args.to_s
|
10
|
+
s = "------\n#{s}\n"
|
11
|
+
File.open(file, 'a') { |f| f.puts s }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# default logger class
|
16
|
+
class DefaultLogger
|
17
|
+
include Log
|
18
|
+
end
|
19
|
+
|
20
|
+
DEFAULT_LOGGER = DefaultLogger.new
|
21
|
+
|
22
|
+
def log(*args)
|
23
|
+
DEFAULT_LOGGER.log *args
|
24
|
+
end
|
data/src/node.rb
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
require_relative 'util'
|
2
|
+
require_relative 'node_attributes'
|
3
|
+
require_relative 'node_visit'
|
4
|
+
require_relative 'emitter'
|
5
|
+
|
6
|
+
module TermGui
|
7
|
+
# analog to HTML DOM Node class
|
8
|
+
# Ways of declaring node hierarchies by using parent and children props, or append_child or append_to methods. For declarative complex structures probably you want to use children
|
9
|
+
# `main = Row.new(parent: screen, height: 0.5, children: [Button.new(text: 'clickme', Col.new(width: 0.8, x: 0.2, children: [Label.new(text: 'hello')]))])`
|
10
|
+
# or
|
11
|
+
# `main = screen.append_child(Row.new(height: 0.5, children: [Button.new(text: 'clickme', Col.new(width: 0.8, x: 0.2, children: [Label.new(text: 'hello')]))]))`
|
12
|
+
# or
|
13
|
+
# `main = Row.new(height: 0.5, children: [Button.new(text: 'clickme', Col.new(width: 0.8, x: 0.2, children: [Label.new(text: 'hello')]))]).append_to(screen)`
|
14
|
+
class Node < Emitter
|
15
|
+
include NodeVisit
|
16
|
+
|
17
|
+
attr_reader :children, :text, :parent, :name
|
18
|
+
attr_writer :parent, :text
|
19
|
+
|
20
|
+
def initialize(**args)
|
21
|
+
@name = args[:name] || 'node'
|
22
|
+
@attributes = Attributes.new args[:attributes] || {}
|
23
|
+
@children = args[:children] || []
|
24
|
+
children.each { |child| child.parent = self }
|
25
|
+
@text = args[:text] || ''
|
26
|
+
args[:parent]&.append_children(self)
|
27
|
+
install(%i[after_render before_render])
|
28
|
+
end
|
29
|
+
|
30
|
+
# returns child so we can write: `button = screen.append_child Row.new(text: 'click me')`
|
31
|
+
def append_children(*children)
|
32
|
+
children.each do |child|
|
33
|
+
@children.push(child)
|
34
|
+
child.parent = self
|
35
|
+
end
|
36
|
+
children
|
37
|
+
end
|
38
|
+
|
39
|
+
def append_child(child)
|
40
|
+
(append_children child)[0]
|
41
|
+
end
|
42
|
+
|
43
|
+
def insert_children(index = 0, *children)
|
44
|
+
@children.insert(index, *children)
|
45
|
+
children
|
46
|
+
end
|
47
|
+
|
48
|
+
def prepend_child(child)
|
49
|
+
insert_child(0, child)
|
50
|
+
end
|
51
|
+
|
52
|
+
def insert_child(index, child)
|
53
|
+
(insert_children index, child)[0]
|
54
|
+
end
|
55
|
+
|
56
|
+
def remove_children(*children)
|
57
|
+
children.each do |child|
|
58
|
+
@children.delete(child)
|
59
|
+
child.parent = self
|
60
|
+
end
|
61
|
+
children
|
62
|
+
end
|
63
|
+
|
64
|
+
def remove_child(child)
|
65
|
+
(remove_children child)[0]
|
66
|
+
end
|
67
|
+
|
68
|
+
def append_to(parent)
|
69
|
+
parent.append_child(self)
|
70
|
+
self
|
71
|
+
end
|
72
|
+
|
73
|
+
def remove
|
74
|
+
parent&.remove_child(self)
|
75
|
+
self.parent = nil
|
76
|
+
end
|
77
|
+
|
78
|
+
def empty
|
79
|
+
children.each do |child|
|
80
|
+
child.parent = nil
|
81
|
+
end
|
82
|
+
@children = []
|
83
|
+
self
|
84
|
+
end
|
85
|
+
|
86
|
+
def attributes(attrs = nil)
|
87
|
+
attrs&.each_key { |key| set_attribute(key.to_s, attrs[key]) }
|
88
|
+
@attributes
|
89
|
+
end
|
90
|
+
|
91
|
+
def attributes=(attrs = nil)
|
92
|
+
attributes(attrs)
|
93
|
+
end
|
94
|
+
|
95
|
+
def set_attribute(name, value)
|
96
|
+
@attributes.set_attribute(name, value)
|
97
|
+
self
|
98
|
+
end
|
99
|
+
|
100
|
+
def get_attribute(name)
|
101
|
+
@attributes.get_attribute(name)
|
102
|
+
end
|
103
|
+
|
104
|
+
def to_s
|
105
|
+
"Node(name: #{name}, children: [#{children.map(&:to_s).join(', ')}])"
|
106
|
+
end
|
107
|
+
|
108
|
+
def pretty_print(d = 0)
|
109
|
+
"#{(' ' * d)}<#{name} #{(attributes.pairs.map { |p| "#{p[:name]}=#{pretty_print_attribute p[:value]}" }).join(' ')}>\n#{' ' * (d + 1)}#{text ? " #{text}\n#{' ' * d}" : ''}#{children.map { |c| c.pretty_print d + 1 }.join("\n" + (' ' * d))}\n#{(' ' * (d + 1))}</#{name}>"
|
110
|
+
end
|
111
|
+
|
112
|
+
def pretty_print_attribute(a)
|
113
|
+
a.respond_to?(:pretty_print) ? a.pretty_print : a.to_s
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
Node = TermGui::Node
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# Manages Node's attributes
|
2
|
+
class Attributes
|
3
|
+
def initialize(attrs = {})
|
4
|
+
@attrs = attrs
|
5
|
+
end
|
6
|
+
|
7
|
+
def names
|
8
|
+
@attrs.keys
|
9
|
+
end
|
10
|
+
|
11
|
+
def pairs
|
12
|
+
@attrs.keys.map { |n| { name: n, value: @attrs[n] } }
|
13
|
+
end
|
14
|
+
|
15
|
+
def set_attribute(name, value)
|
16
|
+
@attrs[name.to_sym] = value
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
def get_attribute(name)
|
21
|
+
@attrs[name.to_sym]
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s
|
25
|
+
@attrs.to_s
|
26
|
+
end
|
27
|
+
end
|
data/src/node_visit.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# adds node recirsive visit support and node query operations
|
2
|
+
module NodeVisit
|
3
|
+
def query_by_attribute(attr, value)
|
4
|
+
result = []
|
5
|
+
visit_node(self, proc { |n|
|
6
|
+
result.push n if n.attributes.get_attribute(attr) == value
|
7
|
+
false
|
8
|
+
})
|
9
|
+
result
|
10
|
+
end
|
11
|
+
|
12
|
+
def query_by_name(name)
|
13
|
+
result = []
|
14
|
+
visit_node(self, proc { |n|
|
15
|
+
result.push n if n.name == name
|
16
|
+
false
|
17
|
+
})
|
18
|
+
result
|
19
|
+
end
|
20
|
+
|
21
|
+
def query_one_by_attribute(attr, value)
|
22
|
+
result = nil
|
23
|
+
p = proc do |n|
|
24
|
+
if n.attributes.get_attribute(attr) == value
|
25
|
+
result = n
|
26
|
+
true
|
27
|
+
else
|
28
|
+
false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
visit_node(self, p)
|
32
|
+
result
|
33
|
+
end
|
34
|
+
|
35
|
+
def visit(visitor, children_first = true)
|
36
|
+
visit_node(self, visitor, children_first)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# visit given node children bottom-up. If visitor returns truthy then visiting finishes
|
41
|
+
def visit_node(node, visitor, children_first = true)
|
42
|
+
result = nil
|
43
|
+
unless children_first
|
44
|
+
result = visitor.call node
|
45
|
+
return result if result
|
46
|
+
end
|
47
|
+
result = some(node.children, proc { |child| visit_node child, visitor, children_first })
|
48
|
+
return result if result
|
49
|
+
|
50
|
+
result = visitor.call node if children_first
|
51
|
+
result
|
52
|
+
end
|
data/src/renderer.rb
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
require_relative 'style'
|
2
|
+
require_relative 'key'
|
3
|
+
require_relative 'renderer_print'
|
4
|
+
require_relative 'renderer_cursor'
|
5
|
+
require_relative 'renderer_image'
|
6
|
+
require_relative 'renderer_draw'
|
7
|
+
|
8
|
+
module TermGui
|
9
|
+
# Responsible of (TODO: we should split Renderer into several delegate classes
|
10
|
+
# * build charsequences to render text on a position. these are directly write to $stdout by screen
|
11
|
+
# * maintain bitmap-like buffer of current screen state
|
12
|
+
# * manages current applied style
|
13
|
+
# TODO: add line, empty-rect and more drawing primitives
|
14
|
+
class Renderer
|
15
|
+
include RendererPrint
|
16
|
+
include RendererCursor
|
17
|
+
include RendererImage
|
18
|
+
include RendererDraw
|
19
|
+
|
20
|
+
attr_reader :width, :height, :buffer, :style
|
21
|
+
attr_writer :style, :no_buffer
|
22
|
+
|
23
|
+
def initialize(width = 80, height = 20, no_buffer = false)
|
24
|
+
@width = width
|
25
|
+
@height = height
|
26
|
+
@style = Style.new
|
27
|
+
@no_buffer = no_buffer
|
28
|
+
self.fast_colouring = true
|
29
|
+
unless no_buffer
|
30
|
+
@buffer = (0...@height).to_a.map do
|
31
|
+
(0...@width).to_a.map do
|
32
|
+
Pixel.new
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# all writing must be done using me
|
39
|
+
def write(x, y, s, style = nil)
|
40
|
+
if y < @height && y >= 0
|
41
|
+
# TODO: x could be negative now, trunc ch to respect screen bounds - if not negative values are printed at the right
|
42
|
+
if x < 0
|
43
|
+
s = s[[x * -1, s.length].min..s.length]
|
44
|
+
x = 0
|
45
|
+
end
|
46
|
+
unless @no_buffer
|
47
|
+
(x...[x + s.length, @width].min).to_a.each do |i|
|
48
|
+
@buffer[y][i].ch = s[i - x]
|
49
|
+
@buffer[y][i].style = (style || @style).clone
|
50
|
+
end
|
51
|
+
end
|
52
|
+
# apply the style after writing to the buffer so it don't contains escape secuences just the chars
|
53
|
+
s = style == nil ? s : style.print(s)
|
54
|
+
"#{move x, y + 1}#{s}" # TODO: investigate why y + 1
|
55
|
+
else
|
56
|
+
''
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def move(x, y)
|
61
|
+
Renderer.move x, y
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.move(x, y)
|
65
|
+
"#{CSI}#{y};#{x}H"
|
66
|
+
end
|
67
|
+
|
68
|
+
def text(x: 0, y: 0, text: ' ', style: nil)
|
69
|
+
write(x, y, text, style)
|
70
|
+
end
|
71
|
+
|
72
|
+
def rect(x: 0, y: 0, width: 5, height: 3, ch: Pixel.EMPTY_CH, style: nil)
|
73
|
+
s = []
|
74
|
+
ch = Pixel.EMPTY_CH if ch == nil
|
75
|
+
height.times do |y_|
|
76
|
+
s .push write(x, y + y_, ch * width, style).to_s
|
77
|
+
end
|
78
|
+
s.join('')
|
79
|
+
end
|
80
|
+
|
81
|
+
def clear
|
82
|
+
@style = Style.new
|
83
|
+
unless @no_buffer
|
84
|
+
@buffer.each_index do |y|
|
85
|
+
@buffer[y].each do |p|
|
86
|
+
p.ch = Pixel.EMPTY_CH
|
87
|
+
p.style.reset
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
"#{CSI}0m#{CSI}2J"
|
92
|
+
end
|
93
|
+
|
94
|
+
def style_assign(style)
|
95
|
+
@style.assign(style)
|
96
|
+
end
|
97
|
+
|
98
|
+
def fast_colouring=(value)
|
99
|
+
Style.fast_colouring(value)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Represents a pixel in renderer's buffer
|
104
|
+
class Pixel
|
105
|
+
attr_accessor :ch, :style
|
106
|
+
|
107
|
+
def self.EMPTY_CH
|
108
|
+
' '
|
109
|
+
end
|
110
|
+
|
111
|
+
def initialize(ch = Pixel.EMPTY_CH, style = Style.new)
|
112
|
+
@ch = ch
|
113
|
+
@style = style
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
Renderer = TermGui::Renderer
|
119
|
+
Pixel = TermGui::Pixel
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# takes care of cursor related ansi escape sequences
|
2
|
+
module RendererCursor
|
3
|
+
def cursor_save
|
4
|
+
"#{CSI}s"
|
5
|
+
end
|
6
|
+
|
7
|
+
def cursor_restore
|
8
|
+
"#{CSI}u"
|
9
|
+
end
|
10
|
+
|
11
|
+
def cursor_show
|
12
|
+
"#{CSI}?25h"
|
13
|
+
end
|
14
|
+
|
15
|
+
def cursor_hide
|
16
|
+
"#{CSI}?25l"
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'chunky_png'
|
2
|
+
|
3
|
+
# takes care of drawing shapes. Based on Image (uses chunky_png Canvas)
|
4
|
+
# TODO: we should use TermGui::Image and not ChunkyPNG's so we add value there too
|
5
|
+
module RendererDraw
|
6
|
+
def circle(x: nil, y: nil, radius: nil, stroke_ch: ' ', stroke: nil, fill: nil, fill_ch: stroke_ch)
|
7
|
+
canvas = draw_canvas
|
8
|
+
canvas.circle(x, y, radius, ChunkyPNG::Color::BLACK, ChunkyPNG::Color::WHITE)
|
9
|
+
radius = []
|
10
|
+
canvas.height.times do |y2|
|
11
|
+
canvas.width.times do |x2|
|
12
|
+
if canvas[x2, y2] == ChunkyPNG::Color::BLACK && stroke
|
13
|
+
radius.push text(x: x2, y: y2, text: stroke_ch, style: stroke)
|
14
|
+
elsif canvas[x2, y2] == ChunkyPNG::Color::WHITE && fill
|
15
|
+
radius.push text(x: x2, y: y2, text: fill_ch, style: fill)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
radius.join
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def draw_canvas
|
25
|
+
# TODO: reuse the canvas
|
26
|
+
ChunkyPNG::Canvas.new(width, height, ChunkyPNG::Color::TRANSPARENT)
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require_relative 'image'
|
2
|
+
|
3
|
+
# takes care rendering given Images
|
4
|
+
module RendererImage
|
5
|
+
# renders given Image or file path at given x, y coords. Currently only PNG format supported.
|
6
|
+
# by default bg attribute is used and space is printed for each pixel but this could be configured using fg, bg, and ch
|
7
|
+
# ch can be an array of chars in which case a random one is taken for each pixel
|
8
|
+
def image(x: 0, y: 0, image: nil, ch: ' ', style: Style.new, fg: false, bg: true, h: height - y, w: width - x,
|
9
|
+
transparent_color: nil) # if a [r,g,b] color is given, then alpha channel will be considered to mix colors accordingly
|
10
|
+
output = []
|
11
|
+
image = image.is_a?(String) ? TermGui::Image.new(image) : image
|
12
|
+
(y..[y + image.height - 1, height - 1].min).to_a.each do |y2|
|
13
|
+
(x..[x + image.width - 1, width - 1].min).to_a.each do |x2|
|
14
|
+
pixel = image.rgb(x2 - x, y2 - y, transparent_color)
|
15
|
+
style.bg = pixel if bg
|
16
|
+
style.fg = pixel if fg
|
17
|
+
output .push text(x: x2, y: y2, text: ch.is_a?(Array) ? ch.sample : ch, style: style) if x2 < w && y2 < h
|
18
|
+
end
|
19
|
+
end
|
20
|
+
output.join('')
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def mix_colors(fg, bg, alpha = 0.5)
|
26
|
+
r = (fg[0] * alpha + bg[0] * (1 - alpha)).to_i
|
27
|
+
g = (fg[1] * alpha + bg[1] * (1 - alpha)).to_i
|
28
|
+
b = (fg[2] * alpha + bg[2] * (1 - alpha)).to_i
|
29
|
+
[r, g, b]
|
30
|
+
end
|
31
|
+
end
|