termgui 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +14 -0
- data/LICENSE +19 -0
- data/README.md +321 -0
- data/TODO.md +259 -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.rb +110 -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/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 +101 -1
data/src/util/wrap.rb
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
# taken from https://github.com/pazdera/word_wrap/blob/master/lib/word_wrap/wrapper.rb
|
2
|
+
#
|
3
|
+
# Copyright (c) 2014, 2015 Radek Pazdera
|
4
|
+
# Distributed under the MIT License
|
5
|
+
#
|
6
|
+
# TODO: Refactor similar passages out of the two functions into a common one
|
7
|
+
class Wrapper
|
8
|
+
def initialize(text, width)
|
9
|
+
@text = text
|
10
|
+
@width = width
|
11
|
+
end
|
12
|
+
|
13
|
+
def fit
|
14
|
+
lines = []
|
15
|
+
next_line = ''
|
16
|
+
@text.lines do |line|
|
17
|
+
line.chomp! "\n"
|
18
|
+
if line.empty?
|
19
|
+
unless next_line.empty?
|
20
|
+
lines.push next_line
|
21
|
+
next_line = ''
|
22
|
+
end
|
23
|
+
lines.push ''
|
24
|
+
end
|
25
|
+
|
26
|
+
words = line.split ' '
|
27
|
+
|
28
|
+
words.each do |word|
|
29
|
+
word.chomp! "\n"
|
30
|
+
|
31
|
+
if next_line.length + word.length < @width
|
32
|
+
if !next_line.empty?
|
33
|
+
next_line << ' ' << word
|
34
|
+
else
|
35
|
+
next_line = word
|
36
|
+
end
|
37
|
+
else
|
38
|
+
if word.length >= @width
|
39
|
+
lines.push next_line unless next_line == ''
|
40
|
+
lines.push word
|
41
|
+
next_line = ''
|
42
|
+
else
|
43
|
+
lines.push next_line
|
44
|
+
next_line = word
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
lines.push next_line
|
51
|
+
if next_line.length <= 0
|
52
|
+
lines.join("\n")
|
53
|
+
else
|
54
|
+
lines.join("\n") + "\n"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def wrap
|
59
|
+
output = []
|
60
|
+
|
61
|
+
@text.lines do |line|
|
62
|
+
line.chomp! "\n"
|
63
|
+
if line.length > @width
|
64
|
+
new_lines = split_line(line, @width)
|
65
|
+
while new_lines.length > 1 && new_lines[1].length > @width
|
66
|
+
output.push new_lines[0]
|
67
|
+
new_lines = split_line new_lines[1], @width
|
68
|
+
end
|
69
|
+
output += new_lines
|
70
|
+
else
|
71
|
+
output.push line
|
72
|
+
end
|
73
|
+
end
|
74
|
+
output.map(&:rstrip!)
|
75
|
+
output.join("\n") + "\n"
|
76
|
+
end
|
77
|
+
|
78
|
+
def split_line(line, width)
|
79
|
+
at = line.index(/\s/)
|
80
|
+
last_at = at
|
81
|
+
|
82
|
+
while !at.nil? && at < width
|
83
|
+
last_at = at
|
84
|
+
at = line.index(/\s/, last_at + 1)
|
85
|
+
end
|
86
|
+
|
87
|
+
if last_at.nil?
|
88
|
+
[line]
|
89
|
+
else
|
90
|
+
[line[0, last_at], line[last_at + 1, line.length]]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def wrap_text(text, width)
|
96
|
+
lines = text.split("\n")
|
97
|
+
a = lines.map do |line|
|
98
|
+
w = Wrapper.new(line, width).wrap
|
99
|
+
w.split("\n")
|
100
|
+
end
|
101
|
+
a.flatten
|
102
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require_relative 'label'
|
2
|
+
|
3
|
+
module TermGui
|
4
|
+
module Widget
|
5
|
+
# A button widget. Usage:
|
6
|
+
# Button.new(text: 'click me', style: {bg: 'blue'}, action: proc {|e| p 'actioned!'})
|
7
|
+
class Button < Label
|
8
|
+
def initialize(**args, &block)
|
9
|
+
super
|
10
|
+
@name = 'button'
|
11
|
+
install(:action)
|
12
|
+
set_attribute(:focusable, true)
|
13
|
+
end
|
14
|
+
|
15
|
+
def default_style
|
16
|
+
s = super
|
17
|
+
s.border = Border.new(fg: '#779966')
|
18
|
+
s.bg = '#336688'
|
19
|
+
s.fg = '#111111'
|
20
|
+
s.focus&.fg = 'red'
|
21
|
+
s.focus.bold = true
|
22
|
+
s.focus.underline = true
|
23
|
+
s.focus&.bg = 'grey'
|
24
|
+
s.focus&.border = Border.new(fg: 'green')
|
25
|
+
s.action&.bg = 'red'
|
26
|
+
s.action&.border = Border.new(fg: 'magenta', bold: true, bg: 'white')
|
27
|
+
s
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
Button = TermGui::Widget::Button
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require_relative 'button'
|
2
|
+
require_relative '../log'
|
3
|
+
require_relative '../util'
|
4
|
+
require_relative '../screen'
|
5
|
+
|
6
|
+
module TermGui
|
7
|
+
module Widget
|
8
|
+
# Similar to HTMLInputElement type="checkbox"
|
9
|
+
class CheckBox < Button
|
10
|
+
def initialize(**args)
|
11
|
+
super
|
12
|
+
@name = 'checkbox'
|
13
|
+
set_attribute('action-keys', %w[enter space])
|
14
|
+
set_attribute('label', text || get_attribute('label') || unique('Option'))
|
15
|
+
update_text get_attribute('value') || get_attribute('checked') || args[:value] || args[:checked] || false
|
16
|
+
on(:action) do |e|
|
17
|
+
update_text !get_attribute('value')
|
18
|
+
render
|
19
|
+
trigger(:input, TermGui::InputEvent.new(self, value, e))
|
20
|
+
trigger(:change, TermGui::ChangeEvent.new(self, value, e))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def value
|
25
|
+
get_attribute('value')
|
26
|
+
end
|
27
|
+
|
28
|
+
def value=(v)
|
29
|
+
update_text v
|
30
|
+
end
|
31
|
+
|
32
|
+
def update_text(v = nil)
|
33
|
+
set_attribute('value', v) unless v == nil
|
34
|
+
self.text = "#{get_attribute('value') ? '[x]' : '[ ]'} #{get_attribute('label')}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def default_style
|
38
|
+
s = super
|
39
|
+
s.border = nil
|
40
|
+
s.action = nil
|
41
|
+
s
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
CheckBox = TermGui::Widget::CheckBox
|
data/src/widget/col.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require_relative '../element'
|
2
|
+
module TermGui
|
3
|
+
module Widget
|
4
|
+
# Column container. A column child is rendered at the bottom of the previous child - all of them in one column.
|
5
|
+
# By default it will have height==0.999
|
6
|
+
class Col < Element
|
7
|
+
attr_accessor :gap
|
8
|
+
def initialize(**args)
|
9
|
+
# p(({height: 0.99999}.merge(args))[:height])
|
10
|
+
super({ height: 0.9999999 }.merge(args))
|
11
|
+
# p height, abs_height
|
12
|
+
@name = 'col'
|
13
|
+
@gap = args[:gap]||0
|
14
|
+
end
|
15
|
+
|
16
|
+
def layout
|
17
|
+
# init_y = abs_content_y
|
18
|
+
last_y = abs_content_y
|
19
|
+
@children.each do |c|
|
20
|
+
# last_y += 1 if c.style.border
|
21
|
+
c.abs_y = last_y
|
22
|
+
last_y += c.abs_height + @gap
|
23
|
+
# last_y += 1 if c.style.border
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
Col = TermGui::Widget::Col
|
data/src/widget/image.rb
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
require_relative '../termgui'
|
2
|
+
|
3
|
+
module TermGui
|
4
|
+
module Widget
|
5
|
+
# analog to HTMLImageElement. Note that the image will be loaded only on render. Also it will be resized according to abs_content
|
6
|
+
# Properties:
|
7
|
+
# src: file path or TermGui::Image ot load
|
8
|
+
# transparent_color color to blend transparent pixels, by default style.bg
|
9
|
+
# ignore_alpha. if true alpha channel is ignored (could be faster)
|
10
|
+
# use_bg (true by default) will paint pixels using style.bg
|
11
|
+
# use_fg (false by default) will paint using style.fg
|
12
|
+
class Image < Element
|
13
|
+
attr_accessor :pan_x, :pan_y, :zoom
|
14
|
+
def initialize(**args)
|
15
|
+
super
|
16
|
+
@name = 'image'
|
17
|
+
@src = args[:src]
|
18
|
+
@use_bg = args[:use_bg] == nil ? true : args[:use_bg]
|
19
|
+
@use_fg = args[:use_fg] == nil ? false : args[:use_fg]
|
20
|
+
@transparent_color = args[:transparent_color]
|
21
|
+
@ignore_alpha = args[:ignore_alpha]
|
22
|
+
@zoom = args[:zoom] || 1.0
|
23
|
+
@pan_x = args[:pan_x] || 0.0
|
24
|
+
@pan_y = args[:pan_y] || 0.0
|
25
|
+
@chs = args[:chs] || [get_attribute('ch') || ' ']
|
26
|
+
@x_factor = args[:x_factor] || 2.0 # to improve non squared terminal "pixels"
|
27
|
+
@y_factor = args[:y_factor] || 1.2
|
28
|
+
end
|
29
|
+
|
30
|
+
def image
|
31
|
+
if !@image && @src
|
32
|
+
@image = @src.is_a?(String) ? TermGui::Image.new(@src) : @src
|
33
|
+
end
|
34
|
+
factor = (@image.width * 2.0 - abs_content_width) < (@image.height - abs_content_height) ?
|
35
|
+
@zoom * @x_factor * @image.width.to_f / abs_content_width.to_f :
|
36
|
+
@zoom * @y_factor * @image.height.to_f / abs_content_height.to_f
|
37
|
+
|
38
|
+
@resized_image = @image.scale(
|
39
|
+
width: (@image.width.to_f * @x_factor / factor).to_i,
|
40
|
+
height: (@image.height.to_f * @y_factor / factor).to_i
|
41
|
+
)
|
42
|
+
# root_screen.text(text: " #{[factor, @image.height, @image.width, @resized_image.width, @resized_image.height, abs_content_width,abs_content_height, abs_content_x, abs_content_y]}", y: 3, x: 55)
|
43
|
+
if @pan_x + @pan_y > 0
|
44
|
+
@resized_image = @resized_image.crop(
|
45
|
+
x: (@resized_image.width.to_f * @pan_x).to_i,
|
46
|
+
y: (@resized_image.height.to_f * @pan_y).to_i,
|
47
|
+
width: (@resized_image.width.to_f - @resized_image.width.to_f * @pan_x).to_i,
|
48
|
+
height: (@resized_image.height.to_f - @resized_image.height.to_f * @pan_y).to_i
|
49
|
+
)
|
50
|
+
end
|
51
|
+
@image
|
52
|
+
end
|
53
|
+
|
54
|
+
def chs=(v)
|
55
|
+
if @chs.join != v.join
|
56
|
+
@chs = v
|
57
|
+
self.dirty = true
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def original_image
|
62
|
+
@image
|
63
|
+
end
|
64
|
+
|
65
|
+
def current_image
|
66
|
+
@resized_image
|
67
|
+
end
|
68
|
+
|
69
|
+
def render_self(screen)
|
70
|
+
[
|
71
|
+
super,
|
72
|
+
image ? screen.image(
|
73
|
+
x: abs_content_x,
|
74
|
+
y: abs_content_y,
|
75
|
+
w: abs_content_width,
|
76
|
+
h: abs_content_height,
|
77
|
+
transparent_color: !@ignore_alpha ? TermGui.to_rgb(@transparent_color || final_style.bg || '#000000') : nil,
|
78
|
+
image: @resized_image,
|
79
|
+
bg: @use_bg,
|
80
|
+
fg: @use_fg,
|
81
|
+
style: final_style,
|
82
|
+
ch: @chs
|
83
|
+
) : ''
|
84
|
+
].join('')
|
85
|
+
end
|
86
|
+
|
87
|
+
def src=(v)
|
88
|
+
@src = v
|
89
|
+
@image = nil
|
90
|
+
refresh
|
91
|
+
end
|
92
|
+
|
93
|
+
def refresh(force_render = false)
|
94
|
+
@resized_image = nil
|
95
|
+
image
|
96
|
+
if force_render
|
97
|
+
self.dirty = true
|
98
|
+
clear
|
99
|
+
render
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
Image = TermGui::Widget::Image
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require_relative '../element'
|
2
|
+
module TermGui
|
3
|
+
module Widget
|
4
|
+
# an inline layout - similar to HTML display: inline - so I can add arbitrary elements.
|
5
|
+
# it will break when there is no more space and resize itself.
|
6
|
+
# TODO: almost there...
|
7
|
+
class Inline < Element
|
8
|
+
attr_accessor :horizontal_gap, :vertical_gap
|
9
|
+
def initialize(**args)
|
10
|
+
super({ width: 0.9999999 }.merge(args))
|
11
|
+
@name = 'inline'
|
12
|
+
@horizontal_gap = args[:horizontal_gap]||0
|
13
|
+
@vertical_gap = args[:vertical_gap]||0
|
14
|
+
end
|
15
|
+
|
16
|
+
def layout
|
17
|
+
last_y = abs_content_y
|
18
|
+
row_max_height = 1+ @vertical_gap
|
19
|
+
last_x = abs_content_x
|
20
|
+
total_height=0
|
21
|
+
@children.each do |c|
|
22
|
+
c.abs_x = last_x
|
23
|
+
c.abs_y = last_y
|
24
|
+
row_max_height = [row_max_height, c.abs_height].max
|
25
|
+
last_x += c.abs_width + @horizontal_gap
|
26
|
+
if last_x + @horizontal_gap+c.abs_width > abs_content_y + abs_content_width
|
27
|
+
last_x = abs_content_x
|
28
|
+
last_y += row_max_height + @vertical_gap
|
29
|
+
total_height+=row_max_height+@vertical_gap
|
30
|
+
row_max_height = 1+ @vertical_gap
|
31
|
+
end
|
32
|
+
end
|
33
|
+
self.height = [total_height + abs_padding.top + abs_padding.bottom + row_max_height, self.abs_height].max ##TODO borders
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
Inline = TermGui::Widget::Inline
|
40
|
+
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require_relative 'button'
|
2
|
+
require_relative '../enterable'
|
3
|
+
require_relative '../util'
|
4
|
+
require_relative '../screen'
|
5
|
+
|
6
|
+
module TermGui
|
7
|
+
module Widget
|
8
|
+
# analog to HTMLInputElement type="number"
|
9
|
+
class InputNumber < Button
|
10
|
+
include Enterable
|
11
|
+
def initialize(**args)
|
12
|
+
super
|
13
|
+
@name = 'input-number'
|
14
|
+
set_attribute('escape-on-blur', get_attribute('escape-on-blur') == nil ? true : get_attribute('escape-on-blur'))
|
15
|
+
set_attribute('action-on-focus', get_attribute('action-on-focus') == nil ? true : get_attribute('action-on-focus'))
|
16
|
+
end
|
17
|
+
|
18
|
+
def value
|
19
|
+
v = parse_float(text)
|
20
|
+
v == nil ? v : (v.to_i - v == 0 ? v.to_i : v)
|
21
|
+
end
|
22
|
+
|
23
|
+
def value=(v)
|
24
|
+
self.text = v.to_s
|
25
|
+
end
|
26
|
+
|
27
|
+
def handle_key(event)
|
28
|
+
if !super(event)
|
29
|
+
if event.key == 'up'
|
30
|
+
on_input value + 1, event
|
31
|
+
true
|
32
|
+
elsif event.key == 'down'
|
33
|
+
on_input value - 1, event
|
34
|
+
true
|
35
|
+
elsif numeric? event.key
|
36
|
+
self.text = text + event.key
|
37
|
+
if parse_float(text) == nil
|
38
|
+
render
|
39
|
+
else
|
40
|
+
on_input value, event
|
41
|
+
end
|
42
|
+
true
|
43
|
+
elsif event.key == 'backspace'
|
44
|
+
self.text = text.slice(0, [text.length - 1, 0].max)
|
45
|
+
if parse_float(text) == nil
|
46
|
+
render
|
47
|
+
else
|
48
|
+
on_input value, event
|
49
|
+
end
|
50
|
+
true
|
51
|
+
end
|
52
|
+
else
|
53
|
+
true
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
InputNumber = TermGui::Widget::InputNumber
|
61
|
+
|
62
|
+
# # p parse_float('9.')
|
63
|
+
|
64
|
+
# sb = nil
|
65
|
+
# s = Screen.new(children: [
|
66
|
+
# Button.new(text: 'hello', x: 0.7, y: 0.6, action: proc { |e|
|
67
|
+
# e.target.text = sb.value.to_s
|
68
|
+
# e.target.render
|
69
|
+
# }),
|
70
|
+
# (sb = InputNumber.new(x: 2, y: 1, width: 0.5, height: 0.5, value: 12))
|
71
|
+
# ])
|
72
|
+
|
73
|
+
# s.start
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require_relative 'button'
|
2
|
+
require_relative '../enterable'
|
3
|
+
require_relative '../log'
|
4
|
+
require_relative '../cursor'
|
5
|
+
|
6
|
+
module TermGui
|
7
|
+
module Widget
|
8
|
+
# One line text input box, analog to HTMLInputElement
|
9
|
+
class InputBox < Button
|
10
|
+
include Enterable
|
11
|
+
def initialize(**args)
|
12
|
+
super({height: 1, text: args[:value]||args[:text] }.merge(args))
|
13
|
+
# super
|
14
|
+
@name = 'input'
|
15
|
+
if args[:dynamic_width]
|
16
|
+
on(:input) do |e|
|
17
|
+
update_width(e.value)
|
18
|
+
cursor.x = abs_content_x + e.value.length - 2
|
19
|
+
end
|
20
|
+
end
|
21
|
+
# set_attribute('escape-on-blur', get_attribute('escape-on-blur') == nil ? true : get_attribute('escape-on-blur'))
|
22
|
+
# set_attribute('action-on-focus', get_attribute('action-on-focus') == nil ? true : get_attribute('action-on-focus'))
|
23
|
+
on(:enter) do
|
24
|
+
cursor.enable
|
25
|
+
end
|
26
|
+
on(:escape) do
|
27
|
+
cursor.disable
|
28
|
+
args[:change]&.call(value)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def cursor
|
33
|
+
@cursor ||= Cursor.new(x: abs_content_x + value.length - 2, y: abs_content_y, enabled: false, screen: root_screen)
|
34
|
+
@cursor
|
35
|
+
end
|
36
|
+
|
37
|
+
def value=(value)
|
38
|
+
@text = value
|
39
|
+
end
|
40
|
+
|
41
|
+
def value
|
42
|
+
text
|
43
|
+
end
|
44
|
+
|
45
|
+
def between(v, min, max)
|
46
|
+
[[v, min].max, max].min
|
47
|
+
end
|
48
|
+
|
49
|
+
def current_x
|
50
|
+
cursor.x - abs_content_x
|
51
|
+
end
|
52
|
+
|
53
|
+
def handle_key(event)
|
54
|
+
if !super(event)
|
55
|
+
if event.key == 'backspace'
|
56
|
+
on_input value[0..between(current_x - 1, 0, value.length - 1)] + value[between(current_x + 1, 0, value.length - 1)..(value.length - 1)], event
|
57
|
+
cursor.x = [cursor.x - 1, abs_content_x - 1].max
|
58
|
+
render
|
59
|
+
true
|
60
|
+
elsif alphanumeric? event.key
|
61
|
+
on_input value[0..between(current_x, 0, value.length - 1)] + event.key + value[between(current_x + 1, 0, value.length - 1)..(value.length - 1)], event
|
62
|
+
cursor.x = cursor.x + 1
|
63
|
+
render
|
64
|
+
true
|
65
|
+
elsif ['right'].include? event.key
|
66
|
+
cursor.x = [cursor.x + 1, abs_content_x + value.length - 1].min
|
67
|
+
render
|
68
|
+
true
|
69
|
+
elsif ['left'].include? event.key
|
70
|
+
cursor.x = [cursor.x - 1, abs_content_x - 1].max
|
71
|
+
render
|
72
|
+
true
|
73
|
+
else
|
74
|
+
false
|
75
|
+
end
|
76
|
+
else
|
77
|
+
true
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
InputBox = TermGui::Widget::InputBox
|
85
|
+
|