glimmer-dsl-libui 0.4.8 → 0.4.12

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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +32 -0
  3. data/README.md +1361 -461
  4. data/VERSION +1 -1
  5. data/examples/basic_table_button.rb +54 -30
  6. data/examples/basic_table_button2.rb +34 -0
  7. data/examples/basic_table_color.rb +104 -26
  8. data/examples/basic_table_color2.rb +2 -14
  9. data/examples/basic_table_color3.rb +37 -0
  10. data/examples/basic_table_image.rb +1 -1
  11. data/examples/basic_table_image2.rb +2 -14
  12. data/examples/basic_table_image3.rb +44 -0
  13. data/examples/basic_table_image_text.rb +1 -2
  14. data/examples/basic_table_image_text2.rb +2 -13
  15. data/examples/basic_table_image_text3.rb +44 -0
  16. data/examples/cpu_percentage.rb +36 -0
  17. data/examples/editable_table.rb +1 -1
  18. data/examples/form_table.rb +21 -17
  19. data/examples/form_table2.rb +104 -85
  20. data/examples/form_table3.rb +113 -0
  21. data/examples/form_table4.rb +110 -0
  22. data/examples/form_table5.rb +94 -0
  23. data/examples/meta_example.rb +6 -4
  24. data/examples/snake.rb +19 -10
  25. data/examples/snake2.rb +97 -0
  26. data/examples/tic_tac_toe.rb +1 -0
  27. data/examples/tic_tac_toe2.rb +84 -0
  28. data/glimmer-dsl-libui.gemspec +0 -0
  29. data/lib/glimmer/dsl/libui/control_expression.rb +2 -1
  30. data/lib/glimmer/dsl/libui/shape_expression.rb +2 -2
  31. data/lib/glimmer/dsl/libui/string_expression.rb +2 -1
  32. data/lib/glimmer/libui/attributed_string.rb +3 -2
  33. data/lib/glimmer/libui/control_proxy/checkbox_proxy.rb +1 -2
  34. data/lib/glimmer/libui/control_proxy/color_button_proxy.rb +1 -2
  35. data/lib/glimmer/libui/control_proxy/column/background_color_column_proxy.rb +4 -0
  36. data/lib/glimmer/libui/control_proxy/combobox_proxy.rb +1 -2
  37. data/lib/glimmer/libui/control_proxy/date_time_picker_proxy.rb +1 -2
  38. data/lib/glimmer/libui/control_proxy/editable_combobox_proxy.rb +1 -2
  39. data/lib/glimmer/libui/control_proxy/entry_proxy.rb +1 -2
  40. data/lib/glimmer/libui/control_proxy/font_button_proxy.rb +5 -1
  41. data/lib/glimmer/libui/control_proxy/menu_item_proxy/check_menu_item_proxy.rb +1 -2
  42. data/lib/glimmer/libui/control_proxy/menu_item_proxy/radio_menu_item_proxy.rb +1 -2
  43. data/lib/glimmer/libui/control_proxy/multiline_entry_proxy.rb +1 -2
  44. data/lib/glimmer/libui/control_proxy/radio_buttons_proxy.rb +1 -2
  45. data/lib/glimmer/libui/control_proxy/slider_proxy.rb +1 -2
  46. data/lib/glimmer/libui/control_proxy/spinbox_proxy.rb +1 -2
  47. data/lib/glimmer/libui/control_proxy/table_proxy.rb +95 -29
  48. data/lib/glimmer/libui/control_proxy.rb +4 -2
  49. data/lib/glimmer/libui/data_bindable.rb +34 -4
  50. data/lib/glimmer/libui/shape.rb +3 -2
  51. data/lib/glimmer/libui.rb +2 -2
  52. data/lib/glimmer-dsl-libui.rb +1 -0
  53. metadata +12 -2
@@ -0,0 +1,97 @@
1
+ require 'glimmer-dsl-libui'
2
+
3
+ require_relative 'snake/presenter/grid'
4
+
5
+ class Snake
6
+ include Glimmer
7
+
8
+ CELL_SIZE = 15
9
+ SNAKE_MOVE_DELAY = 0.1
10
+
11
+ def initialize
12
+ @game = Model::Game.new
13
+ @grid = Presenter::Grid.new(@game)
14
+ @game.start
15
+ @keypress_queue = []
16
+ create_gui
17
+ register_observers
18
+ end
19
+
20
+ def launch
21
+ @main_window.show
22
+ end
23
+
24
+ def register_observers
25
+ @game.height.times do |row|
26
+ @game.width.times do |column|
27
+ observe(@grid.cells[row][column], :color) do |new_color|
28
+ @cell_grid[row][column].fill = new_color
29
+ end
30
+ end
31
+ end
32
+
33
+ observe(@game, :over) do |game_over|
34
+ Glimmer::LibUI.queue_main do
35
+ if game_over
36
+ msg_box('Game Over!', "Score: #{@game.score} | High Score: #{@game.high_score}")
37
+ @game.start
38
+ end
39
+ end
40
+ end
41
+
42
+ Glimmer::LibUI.timer(SNAKE_MOVE_DELAY) do
43
+ unless @game.over?
44
+ process_queued_keypress
45
+ @game.snake.move
46
+ end
47
+ end
48
+ end
49
+
50
+ def process_queued_keypress
51
+ # key press queue ensures one turn per snake move to avoid a double-turn resulting in instant death (due to snake illogically going back against itself)
52
+ key = @keypress_queue.shift
53
+ case [@game.snake.head.orientation, key]
54
+ in [:north, :right] | [:east, :down] | [:south, :left] | [:west, :up]
55
+ @game.snake.turn_right
56
+ in [:north, :left] | [:west, :down] | [:south, :right] | [:east, :up]
57
+ @game.snake.turn_left
58
+ else
59
+ # No Op
60
+ end
61
+ end
62
+
63
+ def create_gui
64
+ @cell_grid = []
65
+ @main_window = window {
66
+ # data-bind window title to game score, converting it to a title string on read from the model
67
+ title <= [@game, :score, on_read: -> (score) {"Snake (Score: #{@game.score})"}]
68
+ content_size @game.width * CELL_SIZE, @game.height * CELL_SIZE
69
+ resizable false
70
+
71
+ vertical_box {
72
+ padded false
73
+
74
+ @game.height.times do |row|
75
+ @cell_grid << []
76
+ horizontal_box {
77
+ padded false
78
+
79
+ @game.width.times do |column|
80
+ area {
81
+ @cell_grid.last << square(0, 0, CELL_SIZE) {
82
+ fill Presenter::Cell::COLOR_CLEAR
83
+ }
84
+
85
+ on_key_up do |area_key_event|
86
+ @keypress_queue << area_key_event[:ext_key]
87
+ end
88
+ }
89
+ end
90
+ }
91
+ end
92
+ }
93
+ }
94
+ end
95
+ end
96
+
97
+ Snake.new.launch
@@ -41,6 +41,7 @@ class TicTacToe
41
41
  text(23, 19) {
42
42
  string {
43
43
  font family: 'Arial', size: OS.mac? ? 20 : 16
44
+ # data-bind string property of area text attributed string to tic tac toe board cell sign
44
45
  string <= [@tic_tac_toe_board[row + 1, column + 1], :sign] # board model is 1-based
45
46
  }
46
47
  }
@@ -0,0 +1,84 @@
1
+
2
+ require 'glimmer-dsl-libui'
3
+
4
+ require_relative "tic_tac_toe/board"
5
+
6
+ class TicTacToe
7
+ include Glimmer
8
+
9
+ def initialize
10
+ @tic_tac_toe_board = Board.new
11
+ end
12
+
13
+ def launch
14
+ create_gui
15
+ register_observers
16
+ @main_window.show
17
+ end
18
+
19
+ def register_observers
20
+ observe(@tic_tac_toe_board, :game_status) do |game_status|
21
+ display_win_message if game_status == Board::WIN
22
+ display_draw_message if game_status == Board::DRAW
23
+ end
24
+
25
+ 3.times.map do |row|
26
+ 3.times.map do |column|
27
+ observe(@tic_tac_toe_board[row + 1, column + 1], :sign) do |sign| # board model is 1-based
28
+ @cells[row][column].string = sign
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ def create_gui
35
+ @main_window = window('Tic-Tac-Toe', 180, 180) {
36
+ resizable false
37
+
38
+ @cells = []
39
+ vertical_box {
40
+ padded false
41
+
42
+ 3.times.map do |row|
43
+ @cells << []
44
+ horizontal_box {
45
+ padded false
46
+
47
+ 3.times.map do |column|
48
+ area {
49
+ square(0, 0, 60) {
50
+ stroke :black, thickness: 2
51
+ }
52
+ text(23, 19) {
53
+ @cells[row] << string('') {
54
+ font family: 'Arial', size: OS.mac? ? 20 : 16
55
+ }
56
+ }
57
+ on_mouse_up do
58
+ @tic_tac_toe_board.mark(row + 1, column + 1) # board model is 1-based
59
+ end
60
+ }
61
+ end
62
+ }
63
+ end
64
+ }
65
+ }
66
+ end
67
+
68
+ def display_win_message
69
+ display_game_over_message("Player #{@tic_tac_toe_board.winning_sign} has won!")
70
+ end
71
+
72
+ def display_draw_message
73
+ display_game_over_message("Draw!")
74
+ end
75
+
76
+ def display_game_over_message(message_text)
77
+ Glimmer::LibUI.queue_main do
78
+ msg_box('Game Over', message_text)
79
+ @tic_tac_toe_board.reset!
80
+ end
81
+ end
82
+ end
83
+
84
+ TicTacToe.new.launch
Binary file
@@ -39,8 +39,9 @@ module Glimmer
39
39
  end
40
40
 
41
41
  def add_content(parent, keyword, *args, &block)
42
+ options = args.last.is_a?(Hash) ? args.last : {post_add_content: true}
42
43
  super
43
- parent&.post_add_content
44
+ parent&.post_add_content if options[:post_add_content]
44
45
  end
45
46
  end
46
47
  end
@@ -47,10 +47,10 @@ module Glimmer
47
47
  end
48
48
 
49
49
  def add_content(parent, keyword, *args, &block)
50
+ options = args.last.is_a?(Hash) ? args.last : {post_add_content: true}
50
51
  super
51
- parent.post_add_content
52
+ parent&.post_add_content if options[:post_add_content]
52
53
  end
53
-
54
54
  end
55
55
  end
56
56
  end
@@ -46,7 +46,8 @@ module Glimmer
46
46
  end
47
47
 
48
48
  def add_content(parent, keyword, *args, &block)
49
- parent.post_add_content(block)
49
+ options = args.last.is_a?(Hash) ? args.last : {post_add_content: true}
50
+ parent&.post_add_content(block) if options[:post_add_content]
50
51
  end
51
52
  end
52
53
  end
@@ -30,8 +30,9 @@ module Glimmer
30
30
  class AttributedString
31
31
  include DataBindable
32
32
 
33
- attr_reader :keyword, :parent_proxy, :args
33
+ attr_reader :keyword, :parent_proxy, :args, :content_added
34
34
  attr_accessor :block
35
+ alias content_added? content_added
35
36
 
36
37
  def initialize(keyword, parent_proxy, args, &block)
37
38
  @keyword = keyword
@@ -205,7 +206,7 @@ module Glimmer
205
206
  end
206
207
 
207
208
  def content(&block)
208
- Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::Libui::StringExpression.new, @keyword, &block)
209
+ Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::Libui::StringExpression.new, @keyword, {post_add_content: true}, &block)
209
210
  end
210
211
  end
211
212
  end
@@ -30,8 +30,7 @@ module Glimmer
30
30
  class CheckboxProxy < ControlProxy
31
31
  DEFAULT_TEXT = ''
32
32
 
33
- def data_bind(property, model_binding)
34
- super
33
+ def data_bind_write(property, model_binding)
35
34
  handle_listener('on_toggled') { model_binding.call(checked) } if property == 'checked'
36
35
  end
37
36
 
@@ -114,8 +114,7 @@ module Glimmer
114
114
  super
115
115
  end
116
116
 
117
- def data_bind(property, model_binding)
118
- super
117
+ def data_bind_write(property, model_binding)
119
118
  handle_listener('on_changed') { model_binding.call(color) } if property == 'color'
120
119
  end
121
120
  end
@@ -31,6 +31,10 @@ module Glimmer
31
31
  # Follows the Proxy Design Pattern
32
32
  class BackgroundColorColumnProxy < ControlProxy
33
33
  include Column
34
+
35
+ def name
36
+ 'Background Color'
37
+ end
34
38
  end
35
39
  end
36
40
  end
@@ -64,8 +64,7 @@ module Glimmer
64
64
  alias set_selected_item selected_item
65
65
  alias selected_item= selected_item
66
66
 
67
- def data_bind(property, model_binding)
68
- super # model to view data-binding
67
+ def data_bind_write(property, model_binding)
69
68
  case property
70
69
  when 'selected'
71
70
  handle_listener('on_selected') { model_binding.call(selected) }
@@ -65,8 +65,7 @@ module Glimmer
65
65
  super
66
66
  end
67
67
 
68
- def data_bind(property, model_binding)
69
- super
68
+ def data_bind_write(property, model_binding)
70
69
  handle_listener('on_changed') { model_binding.call(time) } if property == 'time'
71
70
  end
72
71
  end
@@ -40,8 +40,7 @@ module Glimmer
40
40
  alias set_items items
41
41
  alias items= items
42
42
 
43
- def data_bind(property, model_binding)
44
- super
43
+ def data_bind_write(property, model_binding)
45
44
  handle_listener('on_changed') { model_binding.call(text) } if property == 'text'
46
45
  end
47
46
  end
@@ -28,8 +28,7 @@ module Glimmer
28
28
  #
29
29
  # Follows the Proxy Design Pattern
30
30
  class EntryProxy < ControlProxy
31
- def data_bind(property, model_binding)
32
- super
31
+ def data_bind_write(property, model_binding)
33
32
  handle_listener('on_changed') { model_binding.call(text) } if property == 'text'
34
33
  end
35
34
 
@@ -65,7 +65,11 @@ module Glimmer
65
65
  super
66
66
  end
67
67
 
68
- def data_bind(property, model_binding)
68
+ def data_bind_read(property, model_binding)
69
+ # No Op
70
+ end
71
+
72
+ def data_bind_write(property, model_binding)
69
73
  handle_listener('on_changed') { model_binding.call(font) } if property == 'font'
70
74
  end
71
75
  end
@@ -29,8 +29,7 @@ module Glimmer
29
29
  #
30
30
  # Follows the Proxy Design Pattern
31
31
  class CheckMenuItemProxy < MenuItemProxy
32
- def data_bind(property, model_binding)
33
- super
32
+ def data_bind_write(property, model_binding)
34
33
  handle_listener('on_clicked') { model_binding.call(checked) } if property == 'checked'
35
34
  end
36
35
 
@@ -65,8 +65,7 @@ module Glimmer
65
65
  end
66
66
  end
67
67
 
68
- def data_bind(property, model_binding)
69
- super
68
+ def data_bind_write(property, model_binding)
70
69
  handle_listener('on_clicked') { model_binding.call(checked) } if property == 'checked'
71
70
  end
72
71
 
@@ -28,8 +28,7 @@ module Glimmer
28
28
  #
29
29
  # Follows the Proxy Design Pattern
30
30
  class MultilineEntryProxy < ControlProxy
31
- def data_bind(property, model_binding)
32
- super
31
+ def data_bind_write(property, model_binding)
33
32
  handle_listener('on_changed') { model_binding.call(text) } if property == 'text'
34
33
  end
35
34
 
@@ -50,8 +50,7 @@ module Glimmer
50
50
  alias set_selected_item selected_item
51
51
  alias selected_item= selected_item
52
52
 
53
- def data_bind(property, model_binding)
54
- super # model to view data-binding
53
+ def data_bind_write(property, model_binding)
55
54
  case property
56
55
  when 'selected'
57
56
  handle_listener('on_selected') { model_binding.call(selected) }
@@ -28,8 +28,7 @@ module Glimmer
28
28
  #
29
29
  # Follows the Proxy Design Pattern
30
30
  class SliderProxy < ControlProxy
31
- def data_bind(property, model_binding)
32
- super
31
+ def data_bind_write(property, model_binding)
33
32
  handle_listener('on_changed') { model_binding.call(value) } if property == 'value'
34
33
  end
35
34
  end
@@ -28,8 +28,7 @@ module Glimmer
28
28
  #
29
29
  # Follows the Proxy Design Pattern
30
30
  class SpinboxProxy < ControlProxy
31
- def data_bind(property, model_binding)
32
- super
31
+ def data_bind_write(property, model_binding)
33
32
  handle_listener('on_changed') { model_binding.call(value) } if property == 'value'
34
33
  end
35
34
  end
@@ -38,7 +38,7 @@ module Glimmer
38
38
 
39
39
  LISTENERS = ['on_changed', 'on_edited']
40
40
 
41
- attr_reader :model_handler, :model, :table_params, :columns
41
+ attr_reader :model_handler, :model, :table_params, :columns, :column_attributes
42
42
 
43
43
  def initialize(keyword, parent, args, &block)
44
44
  @keyword = keyword
@@ -75,6 +75,7 @@ module Glimmer
75
75
 
76
76
  def destroy
77
77
  super
78
+ @cell_rows_observer&.unobserve(self, :cell_rows, recursive: true)
78
79
  @destroyed = true
79
80
  end
80
81
 
@@ -82,10 +83,11 @@ module Glimmer
82
83
  if rows.nil?
83
84
  @cell_rows
84
85
  else
85
- @cell_rows = rows
86
- @cell_rows.tap do
87
- @last_cell_rows = array_deep_clone(@cell_rows)
88
- Glimmer::DataBinding::Observer.proc do |new_cell_rows|
86
+ if rows != @cell_rows
87
+ @cell_rows = rows
88
+ @cell_rows = @cell_rows.to_a if @cell_rows.is_a?(Enumerator)
89
+ @last_cell_rows ||= array_deep_clone(@cell_rows)
90
+ @cell_rows_observer ||= Glimmer::DataBinding::Observer.proc do |new_cell_rows|
89
91
  if @cell_rows.size < @last_cell_rows.size && @last_cell_rows.include_all?(*@cell_rows)
90
92
  @last_cell_rows.array_diff_indexes(@cell_rows).reverse.each do |row|
91
93
  ::LibUI.table_model_row_deleted(model, row)
@@ -106,8 +108,11 @@ module Glimmer
106
108
  end
107
109
  @last_last_cell_rows = array_deep_clone(@last_cell_rows)
108
110
  @last_cell_rows = array_deep_clone(@cell_rows)
109
- end.observe(self, :cell_rows, recursive: true)
111
+ end.tap do |cell_rows_observer|
112
+ cell_rows_observer.observe(self, :cell_rows, recursive: true)
113
+ end
110
114
  end
115
+ @cell_rows
111
116
  end
112
117
  end
113
118
  alias cell_rows= cell_rows
@@ -119,6 +124,7 @@ module Glimmer
119
124
 
120
125
  def expand(cell_rows)
121
126
  cell_rows.to_a.map do |row|
127
+ row = @column_attributes.map {|attribute| row.send(attribute) } if @column_attributes&.any? && !row.is_a?(Array)
122
128
  row.flatten(1)
123
129
  end
124
130
  end
@@ -134,6 +140,46 @@ module Glimmer
134
140
  alias set_editable editable
135
141
  alias editable? editable
136
142
 
143
+ def data_bind_read(property, model_binding)
144
+ if model_binding.binding_options[:column_attributes].is_a?(Array)
145
+ @column_attributes = model_binding.binding_options[:column_attributes]
146
+ else
147
+ column_attribute_mapping = model_binding.binding_options[:column_attributes].is_a?(Hash) ? model_binding.binding_options[:column_attributes] : {}
148
+ @column_attributes = columns.select {|column| column.is_a?(Column)}.map(&:name).map {|column_name| column_attribute_mapping[column_name] || column_name.underscore}
149
+ end
150
+ model_attribute_observer = model_attribute_observer_registration = nil
151
+ model_attribute_observer = Glimmer::DataBinding::Observer.proc do
152
+ new_value = model_binding.evaluate_property
153
+ new_value = new_value.to_a if new_value.is_a?(Enumerator)
154
+ if model_binding.binding_options[:column_attributes] || (!new_value.empty? && !new_value.first.is_a?(Array))
155
+ @model_attribute_array_observer_registration&.deregister
156
+ @model_attribute_array_observer_registration = model_attribute_observer.observe(new_value, @column_attributes)
157
+ model_attribute_observer.add_dependent(model_attribute_observer_registration => @model_attribute_array_observer_registration)
158
+ end
159
+ # TODO look if multiple notifications are happening as a result of observing array and observing model binding
160
+ send("#{property}=", new_value) unless @last_cell_rows == new_value
161
+ end
162
+ model_attribute_observer_registration = model_attribute_observer.observe(model_binding)
163
+ model_attribute_observer.call # initial update
164
+ data_binding_model_attribute_observer_registrations << model_attribute_observer_registration
165
+ model_attribute_observer
166
+ end
167
+
168
+ def data_bind_write(property, model_binding)
169
+ # TODO ensure writing is happening to models if rows are not arrays
170
+ handle_listener('on_edited') { model_binding.call(cell_rows) } if property == 'cell_rows'
171
+ end
172
+
173
+ def array_deep_clone(array_or_object)
174
+ if array_or_object.is_a?(Array)
175
+ array_or_object.map do |element|
176
+ array_deep_clone(element)
177
+ end
178
+ else
179
+ array_or_object.clone
180
+ end
181
+ end
182
+
137
183
  private
138
184
 
139
185
  def build_control
@@ -162,11 +208,13 @@ module Glimmer
162
208
  when Column::TextColumnProxy, Column::ButtonColumnProxy, Column::TextColorColumnProxy, :text
163
209
  ::LibUI.new_table_value_string((expanded_cell_rows[row] && expanded_cell_rows[row][column]).to_s)
164
210
  when Column::ImageColumnProxy, Column::ImageTextColumnProxy, Column::ImageTextColorColumnProxy
165
- if OS.windows? && row == cell_rows.count
166
- ::LibUI.new_table_value_image((expanded_cell_rows[row - 1] && (expanded_cell_rows[row - 1][column].respond_to?(:libui) ? expanded_cell_rows[row - 1][column].libui : expanded_cell_rows[row - 1][column])))
167
- else
168
- ::LibUI.new_table_value_image((expanded_cell_rows[row] && (expanded_cell_rows[row][column].respond_to?(:libui) ? expanded_cell_rows[row][column].libui : expanded_cell_rows[row][column])))
169
- end
211
+ # TODO refactor to eliminate redundancy and share similar code
212
+ row = row - 1 if OS.windows? && row == cell_rows.count
213
+ img = expanded_cell_rows[row][column]
214
+ img = ControlProxy::ImageProxy.new('image', nil, img) if img.is_a?(Array)
215
+ img = ControlProxy::ImageProxy.new('image', nil, [img]) if img.is_a?(String)
216
+ img = img.respond_to?(:libui) ? img.libui : img
217
+ ::LibUI.new_table_value_image(img)
170
218
  when Column::CheckboxColumnProxy, Column::CheckboxTextColumnProxy, Column::CheckboxTextColorColumnProxy
171
219
  ::LibUI.new_table_value_int(((expanded_cell_rows[row] && (expanded_cell_rows[row][column] == 1 || expanded_cell_rows[row][column].to_s.strip.downcase == 'true' ? 1 : 0))) || 0)
172
220
  when Column::ProgressBarColumnProxy
@@ -193,28 +241,56 @@ module Glimmer
193
241
  when Column::TextColumnProxy
194
242
  column = @columns[column].index
195
243
  @cell_rows[row] ||= []
196
- @cell_rows[row][column] = ::LibUI.table_value_string(val).to_s
244
+ if @cell_rows[row].is_a?(Array)
245
+ @cell_rows[row][column] = ::LibUI.table_value_string(val).to_s
246
+ else
247
+ attribute = @column_attributes[column]
248
+ @cell_rows[row].send("#{attribute}=", ::LibUI.table_value_string(val).to_s)
249
+ end
197
250
  when Column::TextColorColumnProxy
198
251
  column = @columns[column].index
199
252
  @cell_rows[row] ||= []
200
- @cell_rows[row][column] ||= []
201
- @cell_rows[row][column][0] = ::LibUI.table_value_string(val).to_s
253
+ if @cell_rows[row].is_a?(Array)
254
+ @cell_rows[row][column] ||= []
255
+ @cell_rows[row][column][0] = ::LibUI.table_value_string(val).to_s
256
+ else
257
+ attribute = @column_attributes[column]
258
+ @cell_rows[row].send("#{attribute}=", []) unless @cell_rows[row].send(attribute)
259
+ @cell_rows[row].send(attribute)[0] = ::LibUI.table_value_string(val).to_s
260
+ end
202
261
  when :text
203
262
  column = @columns[column - 1].index
204
263
  @cell_rows[row] ||= []
205
- @cell_rows[row][column] ||= []
206
- @cell_rows[row][column][1] = ::LibUI.table_value_string(val).to_s
264
+ if @cell_rows[row].is_a?(Array)
265
+ @cell_rows[row][column] ||= []
266
+ @cell_rows[row][column][1] = ::LibUI.table_value_string(val).to_s
267
+ else
268
+ attribute = @column_attributes[column]
269
+ @cell_rows[row].send("#{attribute}=", []) unless @cell_rows[row].send(attribute)
270
+ @cell_rows[row].send(attribute)[1] = ::LibUI.table_value_string(val).to_s
271
+ end
207
272
  when Column::ButtonColumnProxy
208
273
  @columns[column].notify_listeners(:on_clicked, row)
209
274
  when Column::CheckboxColumnProxy
210
275
  column = @columns[column].index
211
276
  @cell_rows[row] ||= []
212
- @cell_rows[row][column] = ::LibUI.table_value_int(val).to_i == 1
277
+ if @cell_rows[row].is_a?(Array)
278
+ @cell_rows[row][column] = ::LibUI.table_value_int(val).to_i == 1
279
+ else
280
+ attribute = @column_attributes[column]
281
+ @cell_rows[row].send("#{attribute}=", ::LibUI.table_value_int(val).to_i == 1)
282
+ end
213
283
  when Column::CheckboxTextColumnProxy
214
284
  column = @columns[column].index
215
285
  @cell_rows[row] ||= []
216
- @cell_rows[row][column] ||= []
217
- @cell_rows[row][column][0] = ::LibUI.table_value_int(val).to_i == 1
286
+ if @cell_rows[row].is_a?(Array)
287
+ @cell_rows[row][column] ||= []
288
+ @cell_rows[row][column][0] = ::LibUI.table_value_int(val).to_i == 1
289
+ else
290
+ attribute = @column_attributes[column]
291
+ @cell_rows[row].send("#{attribute}=", []) unless @cell_rows[row].send(attribute)
292
+ @cell_rows[row].send(attribute)[0] = ::LibUI.table_value_int(val).to_i == 1
293
+ end
218
294
  end
219
295
  on_edited.each {|listener| listener.call(row, @cell_rows[row])}
220
296
  end
@@ -238,16 +314,6 @@ module Glimmer
238
314
  @next_column_index ||= -1
239
315
  @next_column_index += 1
240
316
  end
241
-
242
- def array_deep_clone(array_or_object)
243
- if array_or_object.is_a?(Array)
244
- array_or_object.map do |element|
245
- array_deep_clone(element)
246
- end
247
- else
248
- array_or_object.clone
249
- end
250
- end
251
317
  end
252
318
  end
253
319
  end
@@ -126,7 +126,8 @@ module Glimmer
126
126
  ]
127
127
 
128
128
  # libui returns the contained LibUI object
129
- attr_reader :parent_proxy, :libui, :args, :keyword, :block
129
+ attr_reader :parent_proxy, :libui, :args, :keyword, :block, :content_added
130
+ alias content_added? content_added
130
131
 
131
132
  def initialize(keyword, parent, args, &block)
132
133
  @keyword = keyword
@@ -288,6 +289,7 @@ module Glimmer
288
289
  end
289
290
 
290
291
  def destroy
292
+ data_binding_model_attribute_observer_registrations.each(&:deregister)
291
293
  if parent_proxy.nil?
292
294
  default_destroy
293
295
  else
@@ -337,7 +339,7 @@ module Glimmer
337
339
  alias visible= visible
338
340
 
339
341
  def content(&block)
340
- Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::Libui::ControlExpression.new, @keyword, &block)
342
+ Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::Libui::ControlExpression.new, @keyword, {post_add_content: @content_added}, &block)
341
343
  end
342
344
 
343
345
  private