termkit 0.0.0 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.ackrc +7 -0
- data/.editorconfig +14 -0
- data/.gitignore +5 -1
- data/.gitlab-ci.yml +70 -0
- data/.rdoc_options +21 -0
- data/.travis.yml +5 -1
- data/Gemfile +8 -0
- data/Makefile +16 -1
- data/Makefile.common +5 -3
- data/README.md +2 -1
- data/bin/build_info.sh +22 -0
- data/bin/dev +10 -0
- data/lib/termkit.rb +32 -0
- data/lib/termkit/app/app.rb +61 -0
- data/lib/termkit/app/app_curses.rb +159 -0
- data/lib/termkit/app/app_ui.rb +153 -0
- data/lib/termkit/controller/controller.rb +58 -0
- data/lib/termkit/controller/controller_app.rb +22 -0
- data/lib/termkit/controller/controller_view.rb +28 -0
- data/lib/termkit/event/event.rb +14 -0
- data/lib/termkit/event/event_key.rb +33 -0
- data/lib/termkit/event/event_later.rb +16 -0
- data/lib/termkit/exception/exception_event_key_unhandled.rb +11 -0
- data/lib/termkit/exception/exception_event_unhandled.rb +18 -0
- data/lib/termkit/exception/exception_initialized_not_class_parent.rb +17 -0
- data/lib/termkit/exception/exception_std.rb +11 -0
- data/lib/termkit/misc/curses_color.rb +23 -0
- data/lib/termkit/misc/point.rb +114 -0
- data/lib/termkit/misc/rect.rb +116 -0
- data/lib/termkit/misc/size.rb +29 -0
- data/lib/termkit/model/model.rb +16 -0
- data/lib/termkit/version.rb +5 -4
- data/lib/termkit/view/content_view.rb +59 -0
- data/lib/termkit/view/content_view_clear.rb +20 -0
- data/lib/termkit/view/row_grid_view.rb +12 -0
- data/lib/termkit/view/view.rb +825 -0
- data/lib/termkit/view/view_grid.rb +12 -0
- data/lib/termkit/view/view_table.rb +296 -0
- data/lib/termkit/view/view_table_cell.rb +38 -0
- data/lib/termkit/view/view_text.rb +63 -0
- data/termkit.gemspec +8 -0
- data/termkit.sublime-project +2 -2
- metadata +120 -5
- data/tests/ts_all.rb +0 -1
@@ -0,0 +1,116 @@
|
|
1
|
+
|
2
|
+
module TheFox
|
3
|
+
module TermKit
|
4
|
+
|
5
|
+
##
|
6
|
+
# A composition of the Point class (`@origin` attribute) and the Size class (`@size` attribute).
|
7
|
+
class Rect
|
8
|
+
|
9
|
+
# Point instance.
|
10
|
+
attr_reader :origin
|
11
|
+
|
12
|
+
# Size instance.
|
13
|
+
attr_reader :size
|
14
|
+
|
15
|
+
attr_reader :x_range
|
16
|
+
attr_reader :y_range
|
17
|
+
|
18
|
+
def initialize(x = nil, y = nil, width = nil, height = nil)
|
19
|
+
@origin = Point.new(x, y)
|
20
|
+
@size = Size.new(width, height)
|
21
|
+
set_x_range
|
22
|
+
set_y_range
|
23
|
+
end
|
24
|
+
|
25
|
+
def origin=(origin)
|
26
|
+
@origin = origin
|
27
|
+
set_x_range
|
28
|
+
set_y_range
|
29
|
+
end
|
30
|
+
|
31
|
+
def size=(size)
|
32
|
+
@size = size
|
33
|
+
set_x_range
|
34
|
+
set_y_range
|
35
|
+
end
|
36
|
+
|
37
|
+
def x
|
38
|
+
@origin.x
|
39
|
+
end
|
40
|
+
|
41
|
+
def x_max
|
42
|
+
if !@origin.x.nil? && !@size.width.nil?
|
43
|
+
@origin.x + @size.width - 1
|
44
|
+
else
|
45
|
+
-1
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def y
|
50
|
+
@origin.y
|
51
|
+
end
|
52
|
+
|
53
|
+
def y_max
|
54
|
+
if !@origin.y.nil? && !@size.height.nil?
|
55
|
+
@origin.y + @size.height - 1
|
56
|
+
else
|
57
|
+
-1
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def width
|
62
|
+
@size.width
|
63
|
+
end
|
64
|
+
|
65
|
+
def height
|
66
|
+
@size.height
|
67
|
+
end
|
68
|
+
|
69
|
+
def has_default_values?
|
70
|
+
@origin.x.nil? && @origin.y.nil? && @size.width.nil? && @size.height.nil?
|
71
|
+
end
|
72
|
+
|
73
|
+
def to_points
|
74
|
+
points = []
|
75
|
+
@x_range.each do |x_pos|
|
76
|
+
@y_range.each do |y_pos|
|
77
|
+
points << Point.new(x_pos, y_pos)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
points
|
81
|
+
end
|
82
|
+
|
83
|
+
def to_s
|
84
|
+
x_s = x.nil? ? 'NIL' : x
|
85
|
+
y_s = y.nil? ? 'NIL' : y
|
86
|
+
|
87
|
+
w_s = width.nil? ? 'NIL' : width
|
88
|
+
h_s = height.nil? ? 'NIL' : height
|
89
|
+
|
90
|
+
"#{x_s}:#{y_s}[#{w_s}:#{h_s}]"
|
91
|
+
end
|
92
|
+
|
93
|
+
def inspect
|
94
|
+
x_s = x.nil? ? 'NIL' : x
|
95
|
+
y_s = y.nil? ? 'NIL' : y
|
96
|
+
|
97
|
+
w_s = width.nil? ? 'NIL' : width
|
98
|
+
h_s = height.nil? ? 'NIL' : height
|
99
|
+
|
100
|
+
"#<Rect x=#{x_s} y=#{y_s} w=#{w_s} h=#{h_s}>"
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def set_x_range
|
106
|
+
@x_range = Range.new(@origin.x.nil? ? 0: @origin.x, x_max)
|
107
|
+
end
|
108
|
+
|
109
|
+
def set_y_range
|
110
|
+
@y_range = Range.new(@origin.y.nil? ? 0: @origin.y, y_max)
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
|
2
|
+
module TheFox
|
3
|
+
module TermKit
|
4
|
+
|
5
|
+
class Size
|
6
|
+
|
7
|
+
attr_accessor :width
|
8
|
+
attr_accessor :height
|
9
|
+
|
10
|
+
def initialize(width = nil, height = nil)
|
11
|
+
@width = width
|
12
|
+
@height = height
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
"#{@width}:#{@height}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def inspect
|
20
|
+
w_s = @width.nil? ? 'NIL' : @width.to_s
|
21
|
+
h_s = @height.nil? ? 'NIL' : @height.to_s
|
22
|
+
|
23
|
+
"#<Size w=#{w_s} h=#{h_s}>"
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
data/lib/termkit/version.rb
CHANGED
@@ -0,0 +1,59 @@
|
|
1
|
+
|
2
|
+
module TheFox
|
3
|
+
module TermKit
|
4
|
+
|
5
|
+
##
|
6
|
+
# Holds the character for a single Point of a View.
|
7
|
+
class ViewContent
|
8
|
+
|
9
|
+
attr_accessor :char
|
10
|
+
attr_accessor :view
|
11
|
+
|
12
|
+
# This variable is used to detect which of the points has already been rendered by the View.
|
13
|
+
#
|
14
|
+
# - If `true` View `render()` will return this instance.
|
15
|
+
# - If `false` the content of the View didn't change since the last call of `render()` and the content has already been used in `render()`.
|
16
|
+
attr_accessor :needs_rendering
|
17
|
+
|
18
|
+
attr_accessor :origin
|
19
|
+
|
20
|
+
attr_reader :foreground_color
|
21
|
+
attr_reader :background_color
|
22
|
+
|
23
|
+
def initialize(char, view = nil, origin = nil)
|
24
|
+
@char = char[0]
|
25
|
+
@view = view
|
26
|
+
@needs_rendering = true
|
27
|
+
@origin = origin
|
28
|
+
@foreground_color = nil
|
29
|
+
@background_color = nil
|
30
|
+
end
|
31
|
+
|
32
|
+
def foreground_color=(foreground_color)
|
33
|
+
if @foreground_color != foreground_color
|
34
|
+
@foreground_color = foreground_color
|
35
|
+
|
36
|
+
@needs_rendering = true
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def background_color=(background_color)
|
41
|
+
if @background_color != background_color
|
42
|
+
@background_color = background_color
|
43
|
+
|
44
|
+
@needs_rendering = true
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_s
|
49
|
+
@char
|
50
|
+
end
|
51
|
+
|
52
|
+
def inspect
|
53
|
+
"#<#{self.class.name.split('::').last} c='#{@char}' r?=#{@needs_rendering ? 'Y' : 'N'} v=#{@view} o=#{@origin}>"
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
|
2
|
+
module TheFox
|
3
|
+
module TermKit
|
4
|
+
|
5
|
+
##
|
6
|
+
# Use to clear a single point of a View.
|
7
|
+
#
|
8
|
+
# If a View disappears the screen needs to be cleaned or redrawn. An instance of this class should only be used temporary.
|
9
|
+
class ClearViewContent < ViewContent
|
10
|
+
|
11
|
+
def initialize(char = nil, view = nil, origin = nil)
|
12
|
+
char ||= ' '
|
13
|
+
|
14
|
+
super(char, view, origin)
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,825 @@
|
|
1
|
+
|
2
|
+
require 'thefox-ext'
|
3
|
+
require 'pp'
|
4
|
+
|
5
|
+
module TheFox
|
6
|
+
module TermKit
|
7
|
+
|
8
|
+
##
|
9
|
+
# Base View class.
|
10
|
+
#
|
11
|
+
# A View is an abstraction of any view object.
|
12
|
+
class View
|
13
|
+
|
14
|
+
# The `name` variable is **FOR DEBUGGING ONLY**.
|
15
|
+
attr_accessor :name
|
16
|
+
attr_accessor :parent_view
|
17
|
+
attr_accessor :subviews
|
18
|
+
|
19
|
+
# Holds the content points for this View. A single point content is an instance of ViewContent class.
|
20
|
+
attr_accessor :grid
|
21
|
+
|
22
|
+
# Will be used for the actual rendering.
|
23
|
+
# The `@grid_cache` variable can hold *foreign* content points (see ViewContent) as well as own content points.
|
24
|
+
# Foreign content points are owned by subviews that are shown on this View as well. If a View has subviews but no own content on the `@grid` the `@grid_cache` variable holds only content points from its subviews. The View not just holds the content points of the subviews but also the content points of the subviews of subviews and so on. Through the deepest level of subviews. If you draw a point on a View calling `draw_point()` the point will also be drawn on the parent view through the top view. `@grid` holds only ViewContents of its own View. Not so the `@grid_cache` variable that also holds foreign content points.
|
25
|
+
attr_accessor :grid_cache
|
26
|
+
|
27
|
+
attr_reader :position
|
28
|
+
attr_reader :is_init_position
|
29
|
+
|
30
|
+
# Defines a maximum `width` and `height` (see Size) for a View to be rendered.
|
31
|
+
attr_reader :size
|
32
|
+
|
33
|
+
# Defines the stack order. This variable will only be used when the View has a parent view. The subview on the parent view with the highest zindex will be shown on the parent view. See `redraw_point_zindex()` method for details.
|
34
|
+
attr_reader :zindex
|
35
|
+
|
36
|
+
def initialize(name = nil)
|
37
|
+
#puts 'View->initialize'
|
38
|
+
|
39
|
+
@name = name # FOR DEBUG ONLY
|
40
|
+
@parent_view = nil
|
41
|
+
@subviews = Set.new
|
42
|
+
|
43
|
+
# @grid = Hash.new
|
44
|
+
@grid = ViewGrid.new
|
45
|
+
# @grid_cache = Hash.new
|
46
|
+
@grid_cache = ViewGrid.new
|
47
|
+
|
48
|
+
@is_visible = false
|
49
|
+
@position = Point.new(0, 0)
|
50
|
+
@is_init_position = true
|
51
|
+
@size = nil
|
52
|
+
@zindex = 1
|
53
|
+
end
|
54
|
+
|
55
|
+
##
|
56
|
+
# FOR DEBUG ONLY
|
57
|
+
# :nocov:
|
58
|
+
def pp_grid
|
59
|
+
@grid.map{ |y_pos, row|
|
60
|
+
[y_pos, row.map{ |x_pos, content| [x_pos, content.char] }.to_h]
|
61
|
+
}.to_h
|
62
|
+
end
|
63
|
+
|
64
|
+
##
|
65
|
+
# FOR DEBUG ONLY
|
66
|
+
def pp_grid_cache
|
67
|
+
@grid_cache.map{ |y_pos, row|
|
68
|
+
[y_pos,
|
69
|
+
row.map{ |x_pos, content|
|
70
|
+
# [x_pos, {'c' => content.char, 'v' => content.view.name}]
|
71
|
+
[x_pos, content.char]
|
72
|
+
}.to_h,
|
73
|
+
]
|
74
|
+
}.to_h
|
75
|
+
end
|
76
|
+
# :nocov:
|
77
|
+
|
78
|
+
def is_visible=(is_visible)
|
79
|
+
# puts "#{@name} -- is_visible= #{is_visible}"
|
80
|
+
|
81
|
+
trend = 0
|
82
|
+
|
83
|
+
if @is_visible && !is_visible
|
84
|
+
trend = -1
|
85
|
+
elsif !@is_visible && is_visible
|
86
|
+
trend = 1
|
87
|
+
end
|
88
|
+
|
89
|
+
@is_visible = is_visible
|
90
|
+
|
91
|
+
redraw_parent(trend)
|
92
|
+
end
|
93
|
+
|
94
|
+
def is_visible?
|
95
|
+
@is_visible
|
96
|
+
end
|
97
|
+
|
98
|
+
def position=(new_position)
|
99
|
+
if !new_position.is_a?(Point)
|
100
|
+
raise ArgumentError, "Argument is not a Point -- #{new_position.class} given"
|
101
|
+
end
|
102
|
+
|
103
|
+
# puts "#{@name} -- position= old=#{@position} new=#{new_position}"
|
104
|
+
|
105
|
+
if @position != new_position
|
106
|
+
# puts "#{@name} -- position= diff"
|
107
|
+
|
108
|
+
if @parent_view.nil?
|
109
|
+
@position = new_position
|
110
|
+
else
|
111
|
+
# Keep old position.
|
112
|
+
old_position = @position
|
113
|
+
|
114
|
+
# Move it.
|
115
|
+
@position = new_position
|
116
|
+
|
117
|
+
x_max_i = x_max.to_i + 1
|
118
|
+
y_max_i = y_max.to_i + 1
|
119
|
+
|
120
|
+
# puts "x_max '#{x_max_i}'"
|
121
|
+
# puts "y_max '#{y_max_i}'"
|
122
|
+
|
123
|
+
new_area = Rect.new(nil, nil, x_max_i, y_max_i)
|
124
|
+
new_area.origin = new_position
|
125
|
+
new_area_points = new_area.to_points
|
126
|
+
|
127
|
+
old_area = Rect.new(nil, nil, x_max_i, y_max_i)
|
128
|
+
old_area.origin = old_position
|
129
|
+
|
130
|
+
# puts "#{@name} -- plain area a=#{area.inspect}"
|
131
|
+
|
132
|
+
# Redraw new position.
|
133
|
+
# puts "#{@name} -- new area #{new_area.inspect}"
|
134
|
+
# changes_new = {}
|
135
|
+
# changes_new = @parent_view.redraw_area_zindex(new_area)
|
136
|
+
@parent_view.redraw_area_zindex(new_area)
|
137
|
+
# puts "#{@name} -- redraw_area_zindex OK #{new_area.inspect}"
|
138
|
+
# STDIN.gets
|
139
|
+
|
140
|
+
# changes_new.each do |y_pos, row|
|
141
|
+
# row.each do |x_pos, content|
|
142
|
+
# new_point = Point.new(x_pos, y_pos)
|
143
|
+
# puts "#{@name} -- new content #{new_point} c=#{content.inspect}"
|
144
|
+
# end
|
145
|
+
# end
|
146
|
+
|
147
|
+
# puts
|
148
|
+
# puts "#{@name} -- @is_init_position = #{@is_init_position}"
|
149
|
+
# puts
|
150
|
+
|
151
|
+
# Redraw old position.
|
152
|
+
if !@is_init_position
|
153
|
+
parent_view = @parent_view
|
154
|
+
parent_level = 0
|
155
|
+
point_offset = Point.new(0, 0)
|
156
|
+
while parent_view
|
157
|
+
# puts "#{@name} -- l=#{parent_level} '#{parent_view}' -- old area #{old_area.inspect} #{point_offset}"
|
158
|
+
old_points = old_area.to_points
|
159
|
+
old_points_s = old_points.map{ |point| point.to_s }
|
160
|
+
|
161
|
+
new_points = new_area_points.map{ |point| (point + point_offset) }
|
162
|
+
new_points_s = new_points.map{ |point| point.to_s }
|
163
|
+
#bottom_points = old_points.map{ |point| (point - point_offset).to_s }
|
164
|
+
# rest_points = bottom_points - new_area_points
|
165
|
+
rest_points_s = old_points_s - new_points_s
|
166
|
+
rest_points = rest_points_s.map{ |point| Point.from_s(point) }
|
167
|
+
#rest_points = old_points - new_points
|
168
|
+
|
169
|
+
# puts "#{@name} -- old points #{old_points_s}"
|
170
|
+
# puts "#{@name} -- new points #{new_points_s}"
|
171
|
+
# puts "#{@name} -- bottom points #{bottom_points}"
|
172
|
+
# puts "#{@name} -- new points #{new_area_points}"
|
173
|
+
# puts "#{@name} -- rest points #{rest_points_s}"
|
174
|
+
|
175
|
+
rest_points.each do |point|
|
176
|
+
# puts "#{@name} -- #{parent_view} -- old content #{point}"
|
177
|
+
# changed = false
|
178
|
+
# changed = parent_view.grid_cache_erase_point(point)
|
179
|
+
parent_view.grid_cache_erase_point(point)
|
180
|
+
# puts "#{@name} -- #{parent_view} -- old content #{point} c=#{changed.inspect}"
|
181
|
+
end
|
182
|
+
|
183
|
+
old_area.origin += parent_view.position
|
184
|
+
point_offset += parent_view.position
|
185
|
+
# puts "#{@name} -- #{parent_view} -- pos parent #{parent_view.position} -> #{old_area.inspect} #{point_offset.inspect}"
|
186
|
+
# puts
|
187
|
+
|
188
|
+
parent_view = parent_view.parent_view
|
189
|
+
parent_level += 1
|
190
|
+
|
191
|
+
# puts
|
192
|
+
|
193
|
+
end
|
194
|
+
|
195
|
+
end
|
196
|
+
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
@is_init_position = false
|
201
|
+
end
|
202
|
+
|
203
|
+
def top_position
|
204
|
+
if @parent_view.nil?
|
205
|
+
@position
|
206
|
+
else
|
207
|
+
@parent_view.top_position
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def size=(size)
|
212
|
+
if !size.is_a?(Size)
|
213
|
+
raise ArgumentError, "Argument is not a Size -- #{size.class} given"
|
214
|
+
end
|
215
|
+
|
216
|
+
@size = size
|
217
|
+
end
|
218
|
+
|
219
|
+
def zindex=(zindex)
|
220
|
+
@zindex = zindex
|
221
|
+
|
222
|
+
# puts "#{@name} -- set zindex #{zindex} p=#{@parent_view.nil? ? 'N' : 'Y'}"
|
223
|
+
|
224
|
+
if !@parent_view.nil?
|
225
|
+
@grid_cache.each do |y_pos, row|
|
226
|
+
row.each do |x_pos, content|
|
227
|
+
point = Point.new(x_pos + @position.x, y_pos + @position.y)
|
228
|
+
|
229
|
+
# puts "#{@name} -- set zindex #{zindex}, #{point.x}:#{point.y}"
|
230
|
+
|
231
|
+
@parent_view.redraw_point_zindex(point)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
def width
|
238
|
+
keys = @grid_cache.map{ |y_pos, row| row.keys }.flatten
|
239
|
+
# pp keys
|
240
|
+
min = keys.min.to_i
|
241
|
+
max = keys.max.to_i
|
242
|
+
|
243
|
+
# puts "min '#{min}'"
|
244
|
+
# puts "max '#{max}'"
|
245
|
+
# puts
|
246
|
+
|
247
|
+
if keys.count > 0
|
248
|
+
max - min + 1
|
249
|
+
else
|
250
|
+
0
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
def height
|
255
|
+
keys = @grid_cache.keys
|
256
|
+
# pp keys
|
257
|
+
|
258
|
+
min = keys.min.to_i
|
259
|
+
max = keys.max.to_i
|
260
|
+
|
261
|
+
# puts "min '#{min}'"
|
262
|
+
# puts "max '#{max}'"
|
263
|
+
# puts
|
264
|
+
|
265
|
+
if keys.count > 0
|
266
|
+
max - min + 1
|
267
|
+
else
|
268
|
+
0
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
def x_max
|
273
|
+
# puts 'x_max'
|
274
|
+
# pp @grid_cache.map{ |y_pos, row| row.keys.max }.flatten.max
|
275
|
+
@grid_cache.map{ |y_pos, row| row.keys.max }.flatten.max
|
276
|
+
end
|
277
|
+
|
278
|
+
def y_max
|
279
|
+
@grid_cache.keys.max
|
280
|
+
end
|
281
|
+
|
282
|
+
def add_subview(subview)
|
283
|
+
if subview == self
|
284
|
+
raise ArgumentError, 'self given'
|
285
|
+
end
|
286
|
+
if !subview.is_a?(View)
|
287
|
+
raise ArgumentError, "Argument is not a View -- #{subview.class} given"
|
288
|
+
end
|
289
|
+
|
290
|
+
return unless @subviews.add?(subview)
|
291
|
+
|
292
|
+
subview.parent_view = self
|
293
|
+
@subviews.add(subview)
|
294
|
+
|
295
|
+
subview.grid_cache.each do |y_pos, row|
|
296
|
+
row.each do |x_pos, content|
|
297
|
+
point = Point.new(x_pos + subview.position.x, y_pos + subview.position.y)
|
298
|
+
|
299
|
+
# puts "#{@name} -- add_subview, redraw_point_zindex #{point.x}:#{point.y}"
|
300
|
+
|
301
|
+
redraw_point_zindex(point)
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
subview
|
306
|
+
end
|
307
|
+
|
308
|
+
def is_subview?(subview)
|
309
|
+
@subviews.include?(subview)
|
310
|
+
end
|
311
|
+
|
312
|
+
def remove_subview(subview)
|
313
|
+
if subview == self
|
314
|
+
raise ArgumentError, 'self given'
|
315
|
+
end
|
316
|
+
if !subview.is_a?(View)
|
317
|
+
raise ArgumentError, "Argument is not a View -- #{subview.class} given"
|
318
|
+
end
|
319
|
+
|
320
|
+
return unless @subviews.delete?(subview)
|
321
|
+
|
322
|
+
@subviews.delete(subview)
|
323
|
+
|
324
|
+
subview.grid_cache.each do |y_pos, row|
|
325
|
+
row.each do |x_pos, content|
|
326
|
+
point = Point.new(x_pos + subview.position.x, y_pos + subview.position.y)
|
327
|
+
|
328
|
+
# puts "#{@name} -- remove_subview, grid cache erase point #{point.x}:#{point.y}"
|
329
|
+
|
330
|
+
grid_cache_erase_point(point)
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
subview
|
335
|
+
end
|
336
|
+
|
337
|
+
def remove_subviews
|
338
|
+
@subviews.each do |subview|
|
339
|
+
remove_subview(subview)
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
##
|
344
|
+
# Draw a single Point to the current view.
|
345
|
+
def draw_point(point, content)
|
346
|
+
case point
|
347
|
+
when Array, Hash
|
348
|
+
point = Point.new(point)
|
349
|
+
when Point
|
350
|
+
else
|
351
|
+
raise NotImplementedError, "#{content.class} class not implemented"
|
352
|
+
end
|
353
|
+
|
354
|
+
case content
|
355
|
+
when String
|
356
|
+
content = ViewContent.new(content, self)
|
357
|
+
when ViewContent
|
358
|
+
else
|
359
|
+
raise NotImplementedError, "#{content.class} class not implemented"
|
360
|
+
end
|
361
|
+
|
362
|
+
is_foreign_point = content.view != self
|
363
|
+
|
364
|
+
x_pos = point.x
|
365
|
+
y_pos = point.y
|
366
|
+
|
367
|
+
|
368
|
+
if is_foreign_point
|
369
|
+
else
|
370
|
+
if !@grid[y_pos]
|
371
|
+
@grid[y_pos] = {}
|
372
|
+
end
|
373
|
+
|
374
|
+
@grid[y_pos][x_pos] = content
|
375
|
+
content.origin = point
|
376
|
+
end
|
377
|
+
|
378
|
+
# puts "#{@name} -- draw #{point} #{content.inspect}"
|
379
|
+
|
380
|
+
new_point = Point.new(x_pos, y_pos)
|
381
|
+
|
382
|
+
# puts "#{@name} -- draw '#{content}' #{x_pos}:#{y_pos} foreign=#{is_foreign_point ? 'Y' : 'N'} from=#{content.view}"
|
383
|
+
# puts "#{@name} -- subviews: #{@subviews.count}"
|
384
|
+
|
385
|
+
changed = nil
|
386
|
+
|
387
|
+
if @subviews.count == 0
|
388
|
+
changed = set_grid_cache(new_point, content)
|
389
|
+
else
|
390
|
+
# puts "#{@name} -- has subviews"
|
391
|
+
|
392
|
+
if @grid_cache[y_pos] && @grid_cache[y_pos][x_pos]
|
393
|
+
# puts "#{@name} -- found something on cached grid"
|
394
|
+
|
395
|
+
redraw_point_zindex(new_point)
|
396
|
+
else
|
397
|
+
# puts "#{@name} -- draw free point"
|
398
|
+
changed = set_grid_cache(new_point, content)
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
if changed
|
403
|
+
parent_draw_point(new_point, content)
|
404
|
+
end
|
405
|
+
|
406
|
+
changed
|
407
|
+
end
|
408
|
+
|
409
|
+
# Draw a point on the parent View (`@parent_view`).
|
410
|
+
def parent_draw_point(point, content)
|
411
|
+
if !@parent_view.nil? && is_visible?
|
412
|
+
|
413
|
+
new_point = Point.new(point.x + @position.x, point.y + @position.y)
|
414
|
+
|
415
|
+
# puts "#{@name} -- draw parent: #{@parent_view} #{new_point.x}:#{new_point.y} (#{point.x}:#{point.y})"
|
416
|
+
@parent_view.draw_point(new_point, content)
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
##
|
421
|
+
# Redraw to Parent View based on the visibility trend.
|
422
|
+
# The visibility trend is `0` for unchanged, `-1` will hide, `1` will appear.
|
423
|
+
#
|
424
|
+
# - `-1` means `is_visible` was set from `true` to `false`.
|
425
|
+
# - `1` means `is_visible` was set from `false` to `true`.
|
426
|
+
def redraw_parent(visibility_trend)
|
427
|
+
# puts "#{@name} -- redraw parent, t=#{visibility_trend}"
|
428
|
+
|
429
|
+
unless @parent_view.nil?
|
430
|
+
if visibility_trend == 1
|
431
|
+
|
432
|
+
@grid_cache.each do |y_pos, row|
|
433
|
+
row.each do |x_pos, content|
|
434
|
+
point = Point.new(x_pos, y_pos)
|
435
|
+
|
436
|
+
# puts "#{@name} -- redraw parent, draw, #{point}"
|
437
|
+
|
438
|
+
parent_draw_point(point, content)
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|
442
|
+
elsif visibility_trend == -1
|
443
|
+
|
444
|
+
@grid_cache.each do |y_pos, row|
|
445
|
+
row.each do |x_pos, content|
|
446
|
+
# puts "#{@name} -- redraw parent, hide (#{@position.x}:#{@position.y}) #{x_pos}:#{y_pos}"
|
447
|
+
|
448
|
+
view = @parent_view
|
449
|
+
view_x_pos = x_pos + @position.x
|
450
|
+
view_y_pos = y_pos + @position.y
|
451
|
+
|
452
|
+
# Erase the content on all parent views.
|
453
|
+
while !view.nil?
|
454
|
+
# puts "#{@name} -- redraw parent, hide #{x_pos}:#{y_pos}, #{view} #{view_x_pos}:#{view_y_pos}"
|
455
|
+
|
456
|
+
view_content = view.grid_cache[view_y_pos] && view.grid_cache[view_y_pos][view_x_pos] ? view.grid_cache[view_y_pos][view_x_pos] : nil
|
457
|
+
# view_content = view.grid[view_y_pos] && view.grid[view_y_pos][view_x_pos] ? view.grid[view_y_pos][view_x_pos] : nil
|
458
|
+
|
459
|
+
if view_content
|
460
|
+
|
461
|
+
# puts "#{@name} -- redraw parent, hide #{x_pos}:#{y_pos}, #{view} #{view_x_pos}:#{view_y_pos}, content '#{view_content}'"
|
462
|
+
|
463
|
+
# Erase the content on the parent view only when the content is viewable on the parent view.
|
464
|
+
if view_content == content
|
465
|
+
# puts "#{@name} -- redraw parent, hide #{x_pos}:#{y_pos}, #{view} #{view_x_pos}:#{view_y_pos}, same"
|
466
|
+
|
467
|
+
view.grid_cache_erase_point(Point.new(view_x_pos, view_y_pos))
|
468
|
+
else
|
469
|
+
# puts "#{@name} -- redraw parent, hide #{x_pos}:#{y_pos}, #{view} #{view_x_pos}:#{view_y_pos}, not same"
|
470
|
+
|
471
|
+
# Break when reaching a foreign layer (view). This can happen when this view
|
472
|
+
# has a lower zindex and is concealed by another view.
|
473
|
+
break
|
474
|
+
end
|
475
|
+
|
476
|
+
else
|
477
|
+
# puts "#{@name} -- redraw parent, hide #{x_pos}:#{y_pos}, #{view} #{view_x_pos}:#{view_y_pos}, empty"
|
478
|
+
end
|
479
|
+
|
480
|
+
|
481
|
+
view_x_pos += view.position.x
|
482
|
+
view_y_pos += view.position.y
|
483
|
+
view = view.parent_view
|
484
|
+
end
|
485
|
+
|
486
|
+
end
|
487
|
+
end
|
488
|
+
|
489
|
+
end
|
490
|
+
end
|
491
|
+
end
|
492
|
+
|
493
|
+
def grid_erase
|
494
|
+
@grid.each do |y_pos, row|
|
495
|
+
row.each do |x_pos, content|
|
496
|
+
#puts "clean #{x_pos}:#{y_pos} '#{content}'"
|
497
|
+
point = Point.new(x_pos, y_pos)
|
498
|
+
|
499
|
+
grid_erase_point(point)
|
500
|
+
end
|
501
|
+
end
|
502
|
+
end
|
503
|
+
|
504
|
+
def grid_erase_point(point)
|
505
|
+
x_pos, y_pos = point.to_a
|
506
|
+
|
507
|
+
@grid[y_pos][x_pos] = ClearViewContent.new(nil, self, point)
|
508
|
+
grid_cache_erase_point(point)
|
509
|
+
end
|
510
|
+
|
511
|
+
##
|
512
|
+
# Erase a single Point of the cached Grid (`@grid_cache`).
|
513
|
+
#
|
514
|
+
# First call `redraw_point_zindex(point)` to redraw the `point`. If the `point` didn't change use a new ClearViewContent instance and set it only on `@grid_cache`. Not on `@grid` because this clearing point instance will be removed by `render()`.
|
515
|
+
def grid_cache_erase_point(point)
|
516
|
+
x_pos, y_pos = point.to_a
|
517
|
+
|
518
|
+
# puts "#{@name} -- erase point #{point}"
|
519
|
+
|
520
|
+
changed = nil
|
521
|
+
if @grid_cache[y_pos] && @grid_cache[y_pos][x_pos] && !@grid_cache[y_pos][x_pos].is_a?(ClearViewContent)
|
522
|
+
# puts "#{@name} -- erase point #{point}, ok found & delete"
|
523
|
+
@grid_cache[y_pos].delete(x_pos)
|
524
|
+
if @grid_cache[y_pos].count == 0
|
525
|
+
@grid_cache.delete(y_pos)
|
526
|
+
end
|
527
|
+
|
528
|
+
# puts "#{@name} -- erase point #{point}, redraw point zindex"
|
529
|
+
changed = redraw_point_zindex(point)
|
530
|
+
|
531
|
+
# puts "#{@name} -- erase point #{point}, changed=#{changed ? 'Y' : 'N'} #{changed.inspect}"
|
532
|
+
|
533
|
+
# When nothing has changed.
|
534
|
+
unless changed
|
535
|
+
# puts "#{@name} -- erase point #{point}, nothing changed"
|
536
|
+
|
537
|
+
content = ClearViewContent.new(nil, self, point)
|
538
|
+
|
539
|
+
# puts "#{@name} -- erase point #{point}, set ClearViewContent"
|
540
|
+
changed = set_grid_cache(point, content)
|
541
|
+
# puts "#{@name} -- erase point #{point}, set ClearViewContent: #{changed.inspect}"
|
542
|
+
|
543
|
+
#changed = content
|
544
|
+
else
|
545
|
+
# puts "#{@name} -- erase point #{point}, CHANGED #{changed.inspect}"
|
546
|
+
#set_grid_cache(point, changed)
|
547
|
+
end
|
548
|
+
else
|
549
|
+
# puts "#{@name} -- erase point #{point}, not found"
|
550
|
+
end
|
551
|
+
changed
|
552
|
+
end
|
553
|
+
|
554
|
+
def grid_cache_remove_point(point, cls = nil)
|
555
|
+
# puts "#{@name} -- remove point #{point}"
|
556
|
+
|
557
|
+
x_pos, y_pos = point.to_a
|
558
|
+
if @grid_cache[y_pos] && @grid_cache[y_pos][x_pos]
|
559
|
+
# puts "#{@name} -- cls=#{cls.inspect} #{@grid_cache[y_pos][x_pos].class}"
|
560
|
+
if cls.nil? || @grid_cache[y_pos][x_pos].is_a?(cls)
|
561
|
+
# puts "#{@name} -- remove point #{point}, ok"
|
562
|
+
@grid_cache[y_pos].delete(x_pos)
|
563
|
+
|
564
|
+
if @grid_cache[y_pos].count == 0
|
565
|
+
@grid_cache.delete(y_pos)
|
566
|
+
end
|
567
|
+
end
|
568
|
+
else
|
569
|
+
# puts "#{@name} -- remove point #{point}, not found"
|
570
|
+
end
|
571
|
+
end
|
572
|
+
|
573
|
+
##
|
574
|
+
# Redraw a single Point based on the `zindexes` of the subviews.
|
575
|
+
# Happens when a subview added, removed, hides, `zindex` changes, or draws.
|
576
|
+
#
|
577
|
+
# The subview with the highest `zindex` will be selected to set the content for this `point`. When no subview exists or all subviews are hidden look-up the Point on the `@grid` variable to set the Point on `@grid_cache`.
|
578
|
+
def redraw_point_zindex(point)
|
579
|
+
x_pos = point.x
|
580
|
+
y_pos = point.y
|
581
|
+
|
582
|
+
# puts "#{@name} -- redraw point zindex #{point}"
|
583
|
+
|
584
|
+
views = @subviews
|
585
|
+
.select{ |subview| subview.is_visible? && subview.zindex >= 1 }
|
586
|
+
.select{ |subview|
|
587
|
+
|
588
|
+
subview_x_pos = x_pos - subview.position.x
|
589
|
+
subview_y_pos = y_pos - subview.position.y
|
590
|
+
|
591
|
+
content = subview.grid_cache[subview_y_pos] && subview.grid_cache[subview_y_pos][subview_x_pos]
|
592
|
+
|
593
|
+
# puts "#{@name} -- find '#{subview}' #{subview_x_pos}:#{subview_y_pos}, #{content.inspect}"
|
594
|
+
|
595
|
+
!content.nil?
|
596
|
+
}
|
597
|
+
.sort{ |subview1, subview2| subview1.zindex <=> subview2.zindex }
|
598
|
+
|
599
|
+
# pp views.map{ |subview| subview.name }
|
600
|
+
|
601
|
+
view = views.last
|
602
|
+
|
603
|
+
content = nil
|
604
|
+
|
605
|
+
if view.nil?
|
606
|
+
# When no subview was found, draw the current view
|
607
|
+
# if a point on the current view's grid exist.
|
608
|
+
|
609
|
+
# puts "#{@name} -- redraw point zindex #{point}, no view found"
|
610
|
+
|
611
|
+
if @grid[y_pos] && @grid[y_pos][x_pos]
|
612
|
+
# puts "#{@name} -- redraw point zindex #{point}, found something on the grid: '#{@grid[y_pos][x_pos]}'"
|
613
|
+
content = @grid[y_pos][x_pos]
|
614
|
+
else
|
615
|
+
# puts "#{@name} -- redraw point zindex #{point}, nothing on grid @ #{x_pos}:#{y_pos}"
|
616
|
+
|
617
|
+
if @grid_cache[y_pos] && @grid_cache[y_pos][x_pos]
|
618
|
+
content = @grid_cache[y_pos][x_pos]
|
619
|
+
|
620
|
+
unless content.is_a?(ClearViewContent)
|
621
|
+
# puts "#{@name} -- redraw point zindex #{point}, found something on the grid_cache: '#{@grid_cache[y_pos][x_pos]}', DELETE"
|
622
|
+
content = ClearViewContent.new(nil, self, point)
|
623
|
+
end
|
624
|
+
else
|
625
|
+
# puts "#{@name} -- redraw point zindex #{point}, nothing on grid_cache @ #{x_pos}:#{y_pos}"
|
626
|
+
end
|
627
|
+
end
|
628
|
+
else
|
629
|
+
subview_x_pos = x_pos - view.position.x
|
630
|
+
subview_y_pos = y_pos - view.position.y
|
631
|
+
|
632
|
+
content = view.grid_cache[subview_y_pos][subview_x_pos]
|
633
|
+
|
634
|
+
# puts "#{@name} -- redraw point zindex #{point}, last view: '#{view}' #{subview_x_pos}:#{subview_y_pos} #{content.inspect}"
|
635
|
+
end
|
636
|
+
|
637
|
+
changed = nil
|
638
|
+
unless content.nil?
|
639
|
+
# puts "#{@name} -- redraw point zindex #{point}, set grid cache"
|
640
|
+
changed = set_grid_cache(point, content)
|
641
|
+
end
|
642
|
+
|
643
|
+
if changed
|
644
|
+
# puts "#{@name} -- redraw point zindex #{point}, changed #{content.inspect}"
|
645
|
+
parent_draw_point(point, content)
|
646
|
+
else
|
647
|
+
# puts "#{@name} -- redraw point zindex #{point}, NOT changed"
|
648
|
+
end
|
649
|
+
|
650
|
+
changed
|
651
|
+
end
|
652
|
+
|
653
|
+
def redraw_area_zindex(area)
|
654
|
+
if !area.is_a?(Rect)
|
655
|
+
raise ArgumentError, "Argument is not a Rect -- #{area.class} given"
|
656
|
+
end
|
657
|
+
|
658
|
+
# puts "#{@name} -- redraw area zindex #{area}"
|
659
|
+
|
660
|
+
changes = {}
|
661
|
+
area.y_range.each do |y_pos|
|
662
|
+
area.x_range.each do |x_pos|
|
663
|
+
point = Point.new(x_pos, y_pos)
|
664
|
+
|
665
|
+
unless changes[y_pos]
|
666
|
+
changes[y_pos] = {}
|
667
|
+
end
|
668
|
+
|
669
|
+
changes[y_pos][x_pos] = redraw_point_zindex(point)
|
670
|
+
end
|
671
|
+
end
|
672
|
+
changes
|
673
|
+
end
|
674
|
+
|
675
|
+
##
|
676
|
+
# Set a single Point on the cached Grid (`@grid_cache`).
|
677
|
+
# This method returns `true` only if the content of the `point` has changed.
|
678
|
+
def set_grid_cache(point, new_content)
|
679
|
+
x_pos, y_pos = point.to_a
|
680
|
+
|
681
|
+
if !@grid_cache[y_pos]
|
682
|
+
@grid_cache[y_pos] = {}
|
683
|
+
end
|
684
|
+
|
685
|
+
changed =
|
686
|
+
if @grid_cache[y_pos][x_pos]
|
687
|
+
# puts "#{@name} -- set grid #{point}, x + y OK"
|
688
|
+
|
689
|
+
old_content = @grid_cache[y_pos][x_pos]
|
690
|
+
if old_content == new_content # && old_content.class == new_content.class
|
691
|
+
# puts "#{@name} -- set grid #{point}, equals, #{old_content.inspect} == #{new_content.inspect}"
|
692
|
+
false
|
693
|
+
else
|
694
|
+
# puts "#{@name} -- set grid #{point}, diff A #{@grid_cache[y_pos][x_pos].inspect}"
|
695
|
+
|
696
|
+
true
|
697
|
+
end
|
698
|
+
else
|
699
|
+
# puts "#{@name} -- set grid #{point}, x + y N/A"
|
700
|
+
|
701
|
+
true
|
702
|
+
end
|
703
|
+
|
704
|
+
# puts "#{@name} -- set grid #{point} '#{new_content}' changed=#{changed ? 'Y' : 'N'}"
|
705
|
+
|
706
|
+
if changed
|
707
|
+
new_content.needs_rendering = true
|
708
|
+
@grid_cache[y_pos][x_pos] = new_content
|
709
|
+
end
|
710
|
+
end
|
711
|
+
|
712
|
+
##
|
713
|
+
# Renders a View.
|
714
|
+
#
|
715
|
+
# Only ViewContents that needs a rendering (see ViewContent, `needs_rendering` attribute) will be returned. `needs_rendering` attribute is set to `false` by `render()`.
|
716
|
+
def render(area = nil)
|
717
|
+
# puts "#{@name} -- render area=#{area ? 'Y' : 'N'}"
|
718
|
+
|
719
|
+
if !@size.nil?
|
720
|
+
if area.nil?
|
721
|
+
area = Rect.new(0, 0)
|
722
|
+
area.size = @size
|
723
|
+
end
|
724
|
+
end
|
725
|
+
|
726
|
+
grid_filtered = @grid_cache
|
727
|
+
|
728
|
+
grid_filtered = grid_filtered
|
729
|
+
.map{ |y_pos, row|
|
730
|
+
[y_pos, row.select{ |x_pos, content| content.needs_rendering }]
|
731
|
+
}
|
732
|
+
.to_h
|
733
|
+
|
734
|
+
|
735
|
+
if area.nil? || area.has_default_values?
|
736
|
+
|
737
|
+
else
|
738
|
+
|
739
|
+
grid_filtered = grid_filtered
|
740
|
+
.select{ |y_pos, row|
|
741
|
+
y_pos >= area.y
|
742
|
+
}
|
743
|
+
.map{ |y_pos, row|
|
744
|
+
[y_pos, row.select{ |x_pos, content| x_pos >= area.x }]
|
745
|
+
}
|
746
|
+
.to_h
|
747
|
+
|
748
|
+
if area.height
|
749
|
+
grid_filtered = grid_filtered
|
750
|
+
.select{ |y_pos, row|
|
751
|
+
y_pos <= area.y_max
|
752
|
+
}
|
753
|
+
end
|
754
|
+
|
755
|
+
if area.width
|
756
|
+
grid_filtered = grid_filtered
|
757
|
+
.map{ |y_pos, row|
|
758
|
+
[y_pos, row.select{ |x_pos, content| x_pos <= area.x_max }]
|
759
|
+
}
|
760
|
+
.to_h
|
761
|
+
end
|
762
|
+
|
763
|
+
end
|
764
|
+
|
765
|
+
grid_filtered = grid_filtered.select{ |y_pos, row| row.count > 0 }
|
766
|
+
|
767
|
+
grid_filtered.each do |y_pos, row|
|
768
|
+
row.each do |x_pos, content|
|
769
|
+
#point = Point.new(x_pos, y_pos)
|
770
|
+
|
771
|
+
# puts "#{@name} -- render #{point} #{content.inspect}"
|
772
|
+
content.needs_rendering = false
|
773
|
+
|
774
|
+
if content.is_a?(ClearViewContent)
|
775
|
+
if @grid[y_pos] && @grid[y_pos][x_pos] && @grid[y_pos][x_pos].is_a?(ClearViewContent)
|
776
|
+
# puts "#{@name} -- render remove grid ClearViewContent"
|
777
|
+
@grid[y_pos].delete(x_pos)
|
778
|
+
end
|
779
|
+
|
780
|
+
parent_view = content.view
|
781
|
+
parent_point = content.origin
|
782
|
+
|
783
|
+
# puts "#{@name} -- render PARENT START '#{parent_point}'"
|
784
|
+
while parent_view
|
785
|
+
# puts "#{@name} -- render PARENT '#{parent_view}' '#{parent_point}' (#{parent_view.position})"
|
786
|
+
|
787
|
+
# puts "#{@name} -- render remove grid_cache ClearViewContent"
|
788
|
+
parent_view.grid_cache_remove_point(parent_point, ClearViewContent)
|
789
|
+
|
790
|
+
parent_point += parent_view.position
|
791
|
+
parent_view = parent_view.parent_view
|
792
|
+
|
793
|
+
# sleep 0.1
|
794
|
+
end
|
795
|
+
end
|
796
|
+
end
|
797
|
+
end
|
798
|
+
|
799
|
+
# @grid.values.map{ |row| row.values }.flatten.select{ |content| content.needs_rendering }.each do |content|
|
800
|
+
# puts "render '#{content}'"
|
801
|
+
# content.needs_rendering = false
|
802
|
+
# end
|
803
|
+
|
804
|
+
grid_filtered
|
805
|
+
end
|
806
|
+
|
807
|
+
def needs_rendering?
|
808
|
+
@grid_cache
|
809
|
+
.map{ |y_pos, row| row.values.map{ |content| content.needs_rendering ? 1 : 0 } }
|
810
|
+
.flatten
|
811
|
+
.inject(:+) > 0
|
812
|
+
end
|
813
|
+
|
814
|
+
def to_s
|
815
|
+
@name
|
816
|
+
end
|
817
|
+
|
818
|
+
def inspect
|
819
|
+
"#<View name=#{@name} w=#{width}>"
|
820
|
+
end
|
821
|
+
|
822
|
+
end
|
823
|
+
|
824
|
+
end
|
825
|
+
end
|