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/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
|
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
|
+
|
data/src/widget/label.rb
ADDED
@@ -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
|