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.
Files changed (101) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +14 -0
  3. data/README.md +321 -0
  4. data/lib/termgui.rb +1 -0
  5. data/src/action.rb +58 -0
  6. data/src/box.rb +90 -0
  7. data/src/color.rb +174 -0
  8. data/src/cursor.rb +69 -0
  9. data/src/editor/editor_base.rb +152 -0
  10. data/src/editor/editor_base_handlers.rb +116 -0
  11. data/src/element.rb +61 -0
  12. data/src/element_bounds.rb +111 -0
  13. data/src/element_box.rb +64 -0
  14. data/src/element_render.rb +102 -0
  15. data/src/element_style.rb +51 -0
  16. data/src/emitter.rb +102 -0
  17. data/src/emitter_state.rb +19 -0
  18. data/src/enterable.rb +93 -0
  19. data/src/event.rb +92 -0
  20. data/src/focus.rb +102 -0
  21. data/src/geometry.rb +53 -0
  22. data/src/image.rb +60 -0
  23. data/src/input.rb +85 -0
  24. data/src/input_grab.rb +17 -0
  25. data/src/input_time.rb +97 -0
  26. data/src/key.rb +114 -0
  27. data/src/log.rb +24 -0
  28. data/src/node.rb +117 -0
  29. data/src/node_attributes.rb +27 -0
  30. data/src/node_visit.rb +52 -0
  31. data/src/renderer.rb +119 -0
  32. data/src/renderer_cursor.rb +18 -0
  33. data/src/renderer_draw.rb +28 -0
  34. data/src/renderer_image.rb +31 -0
  35. data/src/renderer_print.rb +40 -0
  36. data/src/screen.rb +96 -0
  37. data/src/screen_element.rb +59 -0
  38. data/src/screen_input.rb +43 -0
  39. data/src/screen_renderer.rb +53 -0
  40. data/src/style.rb +149 -0
  41. data/src/tco/colouring.rb +248 -0
  42. data/src/tco/config.rb +57 -0
  43. data/src/tco/palette.rb +603 -0
  44. data/src/tco/tco_termgui.rb +30 -0
  45. data/src/termgui.rb +29 -0
  46. data/src/util/css.rb +98 -0
  47. data/src/util/css_query.rb +23 -0
  48. data/src/util/easing.rb +364 -0
  49. data/src/util/hash_object.rb +131 -0
  50. data/src/util/imagemagick.rb +27 -0
  51. data/src/util/justify.rb +20 -0
  52. data/src/util/unicode-categories.rb +572 -0
  53. data/src/util/wrap.rb +102 -0
  54. data/src/util.rb +110 -0
  55. data/src/widget/button.rb +33 -0
  56. data/src/widget/checkbox.rb +47 -0
  57. data/src/widget/col.rb +30 -0
  58. data/src/widget/image.rb +106 -0
  59. data/src/widget/inline.rb +40 -0
  60. data/src/widget/input_number.rb +73 -0
  61. data/src/widget/inputbox.rb +85 -0
  62. data/src/widget/label.rb +33 -0
  63. data/src/widget/modal.rb +69 -0
  64. data/src/widget/row.rb +26 -0
  65. data/src/widget/selectbox.rb +100 -0
  66. data/src/widget/textarea.rb +54 -0
  67. data/src/xml/xml.rb +80 -0
  68. data/test/action_test.rb +34 -0
  69. data/test/box_test.rb +15 -0
  70. data/test/css_test.rb +39 -0
  71. data/test/editor/editor_base_test.rb +201 -0
  72. data/test/element_bounds_test.rb +77 -0
  73. data/test/element_box_test.rb +8 -0
  74. data/test/element_render_test.rb +124 -0
  75. data/test/element_style_test.rb +85 -0
  76. data/test/element_test.rb +10 -0
  77. data/test/emitter_test.rb +108 -0
  78. data/test/event_test.rb +19 -0
  79. data/test/focus_test.rb +37 -0
  80. data/test/geometry_test.rb +12 -0
  81. data/test/input_test.rb +47 -0
  82. data/test/key_test.rb +14 -0
  83. data/test/log_test.rb +21 -0
  84. data/test/node_test.rb +105 -0
  85. data/test/performance/performance1.rb +48 -0
  86. data/test/renderer_test.rb +74 -0
  87. data/test/renderer_test_rect.rb +4 -0
  88. data/test/screen_test.rb +58 -0
  89. data/test/style_test.rb +18 -0
  90. data/test/termgui_test.rb +10 -0
  91. data/test/test_all.rb +30 -0
  92. data/test/util_hash_object_test.rb +93 -0
  93. data/test/util_test.rb +26 -0
  94. data/test/widget/checkbox_test.rb +99 -0
  95. data/test/widget/col_test.rb +87 -0
  96. data/test/widget/inline_test.rb +40 -0
  97. data/test/widget/label_test.rb +94 -0
  98. data/test/widget/row_test.rb +40 -0
  99. data/test/wrap_test.rb +11 -0
  100. data/test/xml_test.rb +77 -0
  101. metadata +148 -0
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
data/src/util.rb ADDED
@@ -0,0 +1,110 @@
1
+ require 'json'
2
+
3
+ def json_parse(str)
4
+ JSON.parse(str)
5
+ end
6
+
7
+ def json_stringify(obj)
8
+ JSON.generate(obj)
9
+ end
10
+
11
+ # TODO: why is it so hard in ruby to access an outer local variable from a method ? I needed to create a
12
+ # module and a wrapper for somethig very trivial
13
+
14
+ module Util
15
+ @uc = 0
16
+ def self.unique(s = '')
17
+ @uc += 1
18
+ s + @uc.to_s
19
+ end
20
+ end
21
+
22
+ def to_array(v)
23
+ v.is_a?(Array) ? v : [v]
24
+ end
25
+
26
+ def unique(s = '')
27
+ Util.unique(s)
28
+ end
29
+
30
+ # TODO: hack because \n chars are not printed with puts or print:
31
+ def print_string(str)
32
+ str.split('\n').map { |s| puts s || '' }
33
+ end
34
+
35
+ def print_ms(t0)
36
+ "#{((Time.now - t0) * 1000).to_i} ms"
37
+ end
38
+
39
+ def parse_integer(s, default = nil)
40
+ Integer(s)
41
+ rescue StandardError
42
+ default
43
+ end
44
+
45
+ def parse_float(s, default = nil)
46
+ Float(s)
47
+ rescue StandardError
48
+ default
49
+ end
50
+
51
+ # returns true if given value is between 0.0 and 1.0
52
+ def is_percent(val)
53
+ val && val > 0 && val < 1
54
+ end
55
+
56
+ def unquote(s)
57
+ s[1..s.length - 1]
58
+ end
59
+
60
+ def next_tick
61
+ sleep 0.0000001
62
+ end
63
+
64
+ def random_int(min = 0, max = 10)
65
+ (min..max).to_a.sample
66
+ end
67
+
68
+ def random_float(min = 0.0, max = 1.0)
69
+ rand * (max - min) + min
70
+ end
71
+
72
+ CHARS = ('a'..'z').to_a.concat(('A'..'Z').to_a).push('_', '-', '@', '!', '#', '$', '%', '^', '&', '*', '=', '+')
73
+
74
+ def random_char
75
+ CHARS.sample
76
+ end
77
+
78
+ def random_color
79
+ [random_int(0, 255), random_int(0, 255), random_int(0, 255)]
80
+ end
81
+
82
+ # similar to JS Array.prototype.some
83
+ def some(array, predicate)
84
+ i = 0
85
+ value = nil
86
+ while i < array.length
87
+ e = array[i]
88
+ value = predicate.call e
89
+ if value
90
+ value = array[i]
91
+ break
92
+ else
93
+ value = nil
94
+ end
95
+ i += 1
96
+ end
97
+ value
98
+ end
99
+
100
+ # TODO: this is the same as event.rb Event. Move Event classes to individual - non dependency file
101
+ module TermGui
102
+ class Event
103
+ # @return {String}
104
+ attr_reader :name
105
+ # @param {String} name
106
+ def initialize(name)
107
+ @name = name
108
+ end
109
+ end
110
+ 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
@@ -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
+
@@ -0,0 +1,33 @@
1
+ require_relative '../element'
2
+ module TermGui
3
+ module Widget
4
+ # A label widget. It's size, if not given, will be computed according to its text.
5
+ class Label < Element
6
+ def initialize(**args)
7
+ super
8
+ @name = 'label'
9
+ # sets width and height according to size rendering:
10
+ w = args[:width] || 0
11
+ update_width if !w || w.zero?
12
+ h = args[:height] || 0
13
+ update_height if !h || h.zero?
14
+ end
15
+
16
+ def text=(t)
17
+ super
18
+ update_width
19
+ update_height
20
+ end
21
+
22
+ def update_width(text = @text)
23
+ self.width = render_text_size(text)[:width]
24
+ end
25
+
26
+ def update_height(text = @text)
27
+ self.height = render_text_size(text)[:height]
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ Label = TermGui::Widget::Label