termkit 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.ackrc +7 -0
  3. data/.editorconfig +14 -0
  4. data/.gitignore +5 -1
  5. data/.gitlab-ci.yml +70 -0
  6. data/.rdoc_options +21 -0
  7. data/.travis.yml +5 -1
  8. data/Gemfile +8 -0
  9. data/Makefile +16 -1
  10. data/Makefile.common +5 -3
  11. data/README.md +2 -1
  12. data/bin/build_info.sh +22 -0
  13. data/bin/dev +10 -0
  14. data/lib/termkit.rb +32 -0
  15. data/lib/termkit/app/app.rb +61 -0
  16. data/lib/termkit/app/app_curses.rb +159 -0
  17. data/lib/termkit/app/app_ui.rb +153 -0
  18. data/lib/termkit/controller/controller.rb +58 -0
  19. data/lib/termkit/controller/controller_app.rb +22 -0
  20. data/lib/termkit/controller/controller_view.rb +28 -0
  21. data/lib/termkit/event/event.rb +14 -0
  22. data/lib/termkit/event/event_key.rb +33 -0
  23. data/lib/termkit/event/event_later.rb +16 -0
  24. data/lib/termkit/exception/exception_event_key_unhandled.rb +11 -0
  25. data/lib/termkit/exception/exception_event_unhandled.rb +18 -0
  26. data/lib/termkit/exception/exception_initialized_not_class_parent.rb +17 -0
  27. data/lib/termkit/exception/exception_std.rb +11 -0
  28. data/lib/termkit/misc/curses_color.rb +23 -0
  29. data/lib/termkit/misc/point.rb +114 -0
  30. data/lib/termkit/misc/rect.rb +116 -0
  31. data/lib/termkit/misc/size.rb +29 -0
  32. data/lib/termkit/model/model.rb +16 -0
  33. data/lib/termkit/version.rb +5 -4
  34. data/lib/termkit/view/content_view.rb +59 -0
  35. data/lib/termkit/view/content_view_clear.rb +20 -0
  36. data/lib/termkit/view/row_grid_view.rb +12 -0
  37. data/lib/termkit/view/view.rb +825 -0
  38. data/lib/termkit/view/view_grid.rb +12 -0
  39. data/lib/termkit/view/view_table.rb +296 -0
  40. data/lib/termkit/view/view_table_cell.rb +38 -0
  41. data/lib/termkit/view/view_text.rb +63 -0
  42. data/termkit.gemspec +8 -0
  43. data/termkit.sublime-project +2 -2
  44. metadata +120 -5
  45. 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
@@ -0,0 +1,16 @@
1
+
2
+ module TheFox
3
+ module TermKit
4
+
5
+ ##
6
+ # Basic class for models.
7
+ class Model
8
+
9
+ def initialize
10
+ #puts 'Model initialize'
11
+ end
12
+
13
+ end
14
+
15
+ end
16
+ end
@@ -1,9 +1,10 @@
1
1
 
2
2
  module TheFox
3
3
  module TermKit
4
- RELEASE_ID = 1
5
- VERSION = '0.0.0'
6
- DATE = '2016-07-24'
7
- HOMEPAGE = 'https://github.com/TheFox/termkit'
4
+
5
+ VERSION = '0.1.0'
6
+ DATE = '2016-10-27'
7
+ HOMEPAGE = 'https://termkit.fox21.at/'
8
+
8
9
  end
9
10
  end
@@ -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,12 @@
1
+
2
+ module TheFox
3
+ module TermKit
4
+
5
+ ##
6
+ # Holds a row of a View's Grid.
7
+ class ViewGridRow < Hash
8
+
9
+ end
10
+
11
+ end
12
+ 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