glimmer-dsl-libui 0.6.0.pre.3 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (122) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +21 -0
  3. data/LICENSE.txt +1 -1
  4. data/README.md +3 -5
  5. data/VERSION +1 -1
  6. data/bin/girb_runner.rb +1 -1
  7. data/docs/examples/GLIMMER-DSL-LIBUI-ADVANCED-EXAMPLES.md +1 -1
  8. data/docs/examples/GLIMMER-DSL-LIBUI-BASIC-EXAMPLES.md +0 -56
  9. data/examples/basic_table_checkbox.rb +13 -1
  10. data/examples/basic_table_checkbox_text.rb +13 -1
  11. data/examples/tetris/model/block.rb +1 -1
  12. data/examples/tetris/model/game.rb +1 -1
  13. data/examples/tetris/model/past_game.rb +1 -1
  14. data/examples/tetris/model/tetromino.rb +1 -1
  15. data/examples/tetris.rb +1 -1
  16. data/examples/tic_tac_toe/board.rb +1 -1
  17. data/examples/tic_tac_toe/cell.rb +1 -1
  18. data/glimmer-dsl-libui.gemspec +0 -0
  19. data/lib/glimmer/dsl/libui/bind_expression.rb +1 -1
  20. data/lib/glimmer/dsl/libui/control_expression.rb +1 -1
  21. data/lib/glimmer/dsl/libui/custom_control_expression.rb +1 -1
  22. data/lib/glimmer/dsl/libui/data_binding_expression.rb +1 -1
  23. data/lib/glimmer/dsl/libui/dsl.rb +1 -1
  24. data/lib/glimmer/dsl/libui/file_expression.rb +1 -1
  25. data/lib/glimmer/dsl/libui/listener_expression.rb +1 -1
  26. data/lib/glimmer/dsl/libui/observe_expression.rb +1 -1
  27. data/lib/glimmer/dsl/libui/open_file_expression.rb +1 -1
  28. data/lib/glimmer/dsl/libui/open_folder_expression.rb +1 -1
  29. data/lib/glimmer/dsl/libui/operation_expression.rb +1 -1
  30. data/lib/glimmer/dsl/libui/property_expression.rb +1 -1
  31. data/lib/glimmer/dsl/libui/save_file_expression.rb +1 -1
  32. data/lib/glimmer/dsl/libui/shape_expression.rb +1 -1
  33. data/lib/glimmer/dsl/libui/shine_data_binding_expression.rb +1 -1
  34. data/lib/glimmer/dsl/libui/string_expression.rb +1 -1
  35. data/lib/glimmer/dsl/libui/tab_item_expression.rb +1 -1
  36. data/lib/glimmer/fiddle_consumer.rb +1 -1
  37. data/lib/glimmer/libui/attributed_string.rb +1 -1
  38. data/lib/glimmer/libui/control_proxy/area_proxy/scrolling_area_proxy.rb +1 -1
  39. data/lib/glimmer/libui/control_proxy/area_proxy.rb +1 -1
  40. data/lib/glimmer/libui/control_proxy/box/horizontal_box_proxy.rb +1 -1
  41. data/lib/glimmer/libui/control_proxy/box/vertical_box_proxy.rb +1 -1
  42. data/lib/glimmer/libui/control_proxy/box.rb +1 -1
  43. data/lib/glimmer/libui/control_proxy/button_proxy.rb +1 -1
  44. data/lib/glimmer/libui/control_proxy/checkbox_proxy.rb +1 -1
  45. data/lib/glimmer/libui/control_proxy/color_button_proxy.rb +1 -1
  46. data/lib/glimmer/libui/control_proxy/column/background_color_column_proxy.rb +1 -1
  47. data/lib/glimmer/libui/control_proxy/column/button_column_proxy.rb +1 -1
  48. data/lib/glimmer/libui/control_proxy/column/checkbox_column_proxy.rb +1 -1
  49. data/lib/glimmer/libui/control_proxy/column/checkbox_text_color_column_proxy.rb +1 -1
  50. data/lib/glimmer/libui/control_proxy/column/checkbox_text_column_proxy.rb +1 -1
  51. data/lib/glimmer/libui/control_proxy/column/image_column_proxy.rb +1 -1
  52. data/lib/glimmer/libui/control_proxy/column/image_text_color_column_proxy.rb +1 -1
  53. data/lib/glimmer/libui/control_proxy/column/image_text_column_proxy.rb +1 -1
  54. data/lib/glimmer/libui/control_proxy/column/progress_bar_column_proxy.rb +1 -1
  55. data/lib/glimmer/libui/control_proxy/column/text_color_column_proxy.rb +1 -1
  56. data/lib/glimmer/libui/control_proxy/column/text_column_proxy.rb +1 -1
  57. data/lib/glimmer/libui/control_proxy/column.rb +1 -1
  58. data/lib/glimmer/libui/control_proxy/combobox_proxy.rb +1 -1
  59. data/lib/glimmer/libui/control_proxy/date_time_picker_proxy/date_picker_proxy.rb +1 -1
  60. data/lib/glimmer/libui/control_proxy/date_time_picker_proxy/time_picker_proxy.rb +1 -1
  61. data/lib/glimmer/libui/control_proxy/date_time_picker_proxy.rb +1 -1
  62. data/lib/glimmer/libui/control_proxy/dual_column.rb +1 -1
  63. data/lib/glimmer/libui/control_proxy/editable_column.rb +1 -1
  64. data/lib/glimmer/libui/control_proxy/editable_combobox_proxy.rb +1 -1
  65. data/lib/glimmer/libui/control_proxy/enableable_column.rb +1 -1
  66. data/lib/glimmer/libui/control_proxy/entry_proxy/password_entry_proxy.rb +1 -1
  67. data/lib/glimmer/libui/control_proxy/entry_proxy/search_entry_proxy.rb +1 -1
  68. data/lib/glimmer/libui/control_proxy/entry_proxy.rb +1 -1
  69. data/lib/glimmer/libui/control_proxy/font_button_proxy.rb +1 -1
  70. data/lib/glimmer/libui/control_proxy/form_proxy.rb +1 -1
  71. data/lib/glimmer/libui/control_proxy/grid_proxy.rb +1 -1
  72. data/lib/glimmer/libui/control_proxy/group_proxy.rb +1 -1
  73. data/lib/glimmer/libui/control_proxy/image_part_proxy.rb +1 -1
  74. data/lib/glimmer/libui/control_proxy/image_proxy.rb +1 -1
  75. data/lib/glimmer/libui/control_proxy/label_proxy.rb +1 -1
  76. data/lib/glimmer/libui/control_proxy/matrix_proxy.rb +1 -1
  77. data/lib/glimmer/libui/control_proxy/menu_item_proxy/about_menu_item_proxy.rb +1 -1
  78. data/lib/glimmer/libui/control_proxy/menu_item_proxy/check_menu_item_proxy.rb +1 -1
  79. data/lib/glimmer/libui/control_proxy/menu_item_proxy/preferences_menu_item_proxy.rb +1 -1
  80. data/lib/glimmer/libui/control_proxy/menu_item_proxy/quit_menu_item_proxy.rb +1 -1
  81. data/lib/glimmer/libui/control_proxy/menu_item_proxy/radio_menu_item_proxy.rb +1 -1
  82. data/lib/glimmer/libui/control_proxy/menu_item_proxy/separator_menu_item_proxy.rb +1 -1
  83. data/lib/glimmer/libui/control_proxy/menu_item_proxy.rb +1 -1
  84. data/lib/glimmer/libui/control_proxy/menu_proxy.rb +1 -1
  85. data/lib/glimmer/libui/control_proxy/message_box/msg_box_error_proxy.rb +1 -1
  86. data/lib/glimmer/libui/control_proxy/message_box/msg_box_proxy.rb +1 -1
  87. data/lib/glimmer/libui/control_proxy/message_box.rb +1 -1
  88. data/lib/glimmer/libui/control_proxy/multiline_entry_proxy/non_wrapping_multiline_entry_proxy.rb +1 -1
  89. data/lib/glimmer/libui/control_proxy/multiline_entry_proxy.rb +1 -1
  90. data/lib/glimmer/libui/control_proxy/open_type_features_proxy.rb +1 -1
  91. data/lib/glimmer/libui/control_proxy/open_type_tag_proxy.rb +1 -1
  92. data/lib/glimmer/libui/control_proxy/path_proxy.rb +1 -1
  93. data/lib/glimmer/libui/control_proxy/radio_buttons_proxy.rb +1 -1
  94. data/lib/glimmer/libui/control_proxy/slider_proxy.rb +1 -1
  95. data/lib/glimmer/libui/control_proxy/spinbox_proxy.rb +1 -1
  96. data/lib/glimmer/libui/control_proxy/tab_item_proxy.rb +1 -1
  97. data/lib/glimmer/libui/control_proxy/table_proxy.rb +243 -98
  98. data/lib/glimmer/libui/control_proxy/text_proxy.rb +1 -1
  99. data/lib/glimmer/libui/control_proxy/transformable.rb +1 -1
  100. data/lib/glimmer/libui/control_proxy/triple_column.rb +1 -1
  101. data/lib/glimmer/libui/control_proxy/window_proxy.rb +1 -1
  102. data/lib/glimmer/libui/control_proxy.rb +1 -1
  103. data/lib/glimmer/libui/custom_control/code_area.rb +1 -1
  104. data/lib/glimmer/libui/custom_control/refined_table.rb +2 -2
  105. data/lib/glimmer/libui/custom_control.rb +1 -1
  106. data/lib/glimmer/libui/custom_window.rb +1 -1
  107. data/lib/glimmer/libui/data_bindable.rb +1 -1
  108. data/lib/glimmer/libui/parent.rb +1 -1
  109. data/lib/glimmer/libui/shape/arc.rb +1 -1
  110. data/lib/glimmer/libui/shape/bezier.rb +1 -1
  111. data/lib/glimmer/libui/shape/circle.rb +1 -1
  112. data/lib/glimmer/libui/shape/figure.rb +1 -1
  113. data/lib/glimmer/libui/shape/line.rb +1 -1
  114. data/lib/glimmer/libui/shape/polybezier.rb +1 -1
  115. data/lib/glimmer/libui/shape/polygon.rb +1 -1
  116. data/lib/glimmer/libui/shape/polyline.rb +1 -1
  117. data/lib/glimmer/libui/shape/rectangle.rb +1 -1
  118. data/lib/glimmer/libui/shape/square.rb +1 -1
  119. data/lib/glimmer/libui/shape.rb +1 -1
  120. data/lib/glimmer/libui.rb +1 -1
  121. data/lib/glimmer-dsl-libui.rb +1 -1
  122. metadata +5 -5
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2021-2022 Andy Maleh
1
+ # Copyright (c) 2021-2023 Andy Maleh
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the
@@ -48,7 +48,7 @@ module Glimmer
48
48
  @enabled = true
49
49
  @columns = []
50
50
  @cell_rows = []
51
- @last_cell_rows = []
51
+ @last_cell_rows = nil
52
52
  register_cell_rows_observer
53
53
  window_proxy.on_destroy do
54
54
  # the following unless condition is an exceptional condition stumbled upon that fails freeing the table model
@@ -88,7 +88,12 @@ module Glimmer
88
88
  else
89
89
  if !rows.equal?(@cell_rows)
90
90
  @cell_rows = rows
91
- @cell_rows = @cell_rows.to_a if @cell_rows.is_a?(Enumerator)
91
+ # TODO instead of clearing cached cell rows, consider amending cached cell rows for better performance (to avoid regenerating them)
92
+ clear_cached_cell_rows
93
+ if @cell_rows.is_a?(Enumerator)
94
+ @cell_rows.rewind
95
+ @future_last_cell_rows = array_deep_dup(@cell_rows) # must be done after rewinding
96
+ end
92
97
  end
93
98
  @cell_rows
94
99
  end
@@ -96,20 +101,21 @@ module Glimmer
96
101
  alias cell_rows= cell_rows
97
102
  alias set_cell_rows cell_rows
98
103
 
99
- def expanded_cell_rows
100
- expanded_cell_rows_dependency_cell_rows = cell_rows
101
- if expanded_cell_rows_dependency_cell_rows != @expanded_cell_rows_dependency_cell_rows
102
- @expanded_cell_rows_dependency_cell_rows = expanded_cell_rows_dependency_cell_rows
103
- @expanded_cell_rows = expand(@expanded_cell_rows_dependency_cell_rows)
104
- end
105
- @expanded_cell_rows
104
+ def expand_cell_rows(cell_rows = nil)
105
+ cell_rows ||= self.cell_rows
106
+ cell_rows ||= []
107
+ cell_rows.map { |cell_row| expand_cell_row(cell_row) }
106
108
  end
109
+ alias expanded_cell_rows expand_cell_rows
107
110
 
108
- def expand(cell_rows)
109
- cell_rows.to_a.map do |row|
110
- row = @column_attributes.map {|attribute| row.send(attribute) } if @column_attributes&.any? && !row.is_a?(Array)
111
- row.flatten(1)
112
- end
111
+ def expand_cell_row(cell_row)
112
+ return cell_row if cell_row.nil?
113
+ cell_row = expand_cell_row_from_model(cell_row) if !cell_row.is_a?(Array) && @column_attributes&.any?
114
+ cell_row.flatten(1)
115
+ end
116
+
117
+ def expand_cell_row_from_model(cell_row)
118
+ @column_attributes.to_a.map {|attribute| cell_row.send(attribute) }
113
119
  end
114
120
 
115
121
  def editable(value = nil)
@@ -133,14 +139,14 @@ module Glimmer
133
139
  model_attribute_observer = model_attribute_observer_registration = nil
134
140
  model_attribute_observer = Glimmer::DataBinding::Observer.proc do
135
141
  new_value = model_binding.evaluate_property
136
- new_value = new_value.to_a if new_value.is_a?(Enumerator)
137
142
  if model_binding.binding_options[:column_attributes] || (!new_value.nil? && (!new_value.is_a?(String) || !new_value.empty?) && (!new_value.is_a?(Array) || !new_value.first.is_a?(Array)))
138
143
  @model_attribute_array_observer_registration&.deregister
139
144
  @model_attribute_array_observer_registration = model_attribute_observer.observe(new_value, @column_attributes, ignore_frozen: true, attribute_writer_type: [:attribute=, :set_attribute])
140
145
  model_attribute_observer.add_dependent(model_attribute_observer_registration => @model_attribute_array_observer_registration)
141
146
  end
142
147
  # TODO look if multiple notifications are happening as a result of observing array and observing model binding
143
- send("#{property}=", new_value) unless @last_cell_rows == new_value
148
+ last_cell_rows = @last_cell_rows || [] # initialize with empty array of first time loading table (only time when @last_cell_rows is nil)
149
+ send("#{property}=", new_value) unless last_cell_rows == new_value
144
150
  end
145
151
  model_attribute_observer_registration = model_attribute_observer.observe(model_binding, attribute_writer_type: [:attribute=, :set_attribute])
146
152
  model_attribute_observer.call # initial update
@@ -149,22 +155,127 @@ module Glimmer
149
155
  end
150
156
 
151
157
  def data_bind_write(property, model_binding)
152
- # TODO ensure writing is happening to models if rows are not arrays
153
158
  handle_listener('on_edited') { model_binding.call(cell_rows) } if property == 'cell_rows'
154
159
  end
155
160
 
156
- def array_deep_clone(array_or_object)
161
+ def array_deep_dup(array_or_object)
157
162
  if array_or_object.is_a?(Array)
158
163
  array_or_object.map do |element|
159
- array_deep_clone(element)
164
+ array_deep_dup(element)
160
165
  end
161
166
  else
162
- array_or_object.clone
167
+ array_or_object.dup
163
168
  end
164
169
  end
165
170
 
171
+ def compound_column_index_for(expanded_column_index)
172
+ compound_column = @columns.find { |compound_column| compound_column.respond_to?(:column_index) && compound_column.column_index == expanded_column_index }
173
+ compound_columns = @columns.select { |compound_column| compound_column.is_a?(Column) }
174
+ compound_columns.index(compound_column)
175
+ end
176
+
166
177
  private
167
178
 
179
+ # returns table cell for row and column (expanded)
180
+ def expanded_cell_for(row, column)
181
+ cell_row = expanded_cell_row_for(row)
182
+ cell = cell_row && cell_row[column]
183
+ end
184
+
185
+ def expanded_cell_row_for(row)
186
+ expand_cell_row(cell_row_for(row))
187
+ end
188
+
189
+ def cell_row_for(row)
190
+ @cached_cell_rows ||= []
191
+ if @cached_cell_rows[row].nil?
192
+ cell_rows = self.cell_rows || []
193
+ if cell_rows.is_a?(Enumerator)
194
+ @cached_cell_rows_size ||= @cached_cell_rows.size
195
+ @cached_cell_rows_enumerator_index ||= 0
196
+ # TODO consider handling size being nil and needing to force Enumerator count instead
197
+ while @cached_cell_rows_enumerator_index <= row
198
+ begin
199
+ @cached_cell_rows << cell_rows.next
200
+ @cached_cell_rows_enumerator_index += 1
201
+ rescue StopIteration => e
202
+ break
203
+ end
204
+ end
205
+ else
206
+ @cached_cell_rows = cell_rows
207
+ end
208
+ end
209
+ @cached_cell_rows[row]
210
+ end
211
+
212
+ def last_cell_row_for(row)
213
+ # TODO refactor to share code with cell_row_for
214
+ @cached_last_cell_rows ||= []
215
+ if @cached_last_cell_rows[row].nil?
216
+ last_cell_rows = @last_cell_rows || []
217
+ if last_cell_rows.is_a?(Enumerator)
218
+ @cached_last_cell_rows_size ||= @cached_last_cell_rows.size
219
+ @cached_last_cell_rows_enumerator_index ||= 0
220
+ # TODO consider handling size being nil and needing to force Enumerator count instead
221
+ while @cached_last_cell_rows_enumerator_index <= row
222
+ begin
223
+ @cached_last_cell_rows << last_cell_rows.next
224
+ @cached_last_cell_rows_enumerator_index += 1
225
+ rescue StopIteration => e
226
+ break
227
+ end
228
+ end
229
+ else
230
+ @cached_last_cell_rows = last_cell_rows
231
+ end
232
+ end
233
+ @cached_last_cell_rows[row]
234
+ end
235
+
236
+ def last_last_cell_row_for(row)
237
+ # TODO refactor to share code with cell_row_for
238
+ @cached_last_last_cell_rows ||= []
239
+ if @cached_last_last_cell_rows[row].nil?
240
+ last_last_cell_rows = @last_last_cell_rows || []
241
+ if last_last_cell_rows.is_a?(Enumerator)
242
+ @cached_last_last_cell_rows_size ||= @cached_last_last_cell_rows.size
243
+ @cached_last_last_cell_rows_enumerator_index ||= 0
244
+ # TODO consider handling size being nil and needing to force Enumerator count instead
245
+ while @cached_last_last_cell_rows_enumerator_index <= row
246
+ begin
247
+ @cached_last_last_cell_rows << last_last_cell_rows.next
248
+ @cached_last_last_cell_rows_enumerator_index += 1
249
+ rescue StopIteration => e
250
+ break
251
+ end
252
+ end
253
+ else
254
+ @cached_last_last_cell_rows = last_last_cell_rows
255
+ end
256
+ end
257
+ @cached_last_last_cell_rows[row]
258
+ end
259
+
260
+ def dup_last_cell_rows
261
+ return (@last_cell_rows = nil) if @cell_rows.nil?
262
+ # using future last cell rows guarantees that Enumerator is rewound
263
+ @last_cell_rows = @future_last_cell_rows || array_deep_dup(@cell_rows)
264
+ @future_last_last_cell_rows = array_deep_dup(@last_cell_rows)
265
+ @future_last_cell_rows = nil
266
+ clear_cached_last_cell_rows
267
+ @last_cell_rows
268
+ end
269
+
270
+ def dup_last_last_cell_rows
271
+ return (@last_last_cell_rows = nil) if @last_cell_rows.nil?
272
+ # using future last cell rows guarantees that Enumerator is rewound
273
+ @last_last_cell_rows = @future_last_last_cell_rows || array_deep_dup(@last_cell_rows)
274
+ @future_last_last_cell_rows = nil
275
+ clear_cached_last_last_cell_rows
276
+ @last_last_cell_rows
277
+ end
278
+
168
279
  def build_control
169
280
  @model_handler = ::LibUI::FFI::TableModelHandler.malloc
170
281
  @model_handler.NumColumns = fiddle_closure_block_caller(4) { @columns.map {|c| c.is_a?(DualColumn) ? 2 : (c.is_a?(TripleColumn) ? 3 : 1)}.sum }
@@ -183,102 +294,106 @@ module Glimmer
183
294
  end
184
295
  @model_handler.NumRows = fiddle_closure_block_caller(4) do
185
296
  # Note: there is a double-delete bug in Windows when performing table_model_row_deleted, which requires pre-adding and extra empty row
186
- cell_rows.count + (OS.windows? ? 1 : 0)
297
+ cell_rows.size + (OS.windows? ? 1 : 0)
298
+ # TODO consider handling case of Enumerator not having size, but having count that needs to be forced instead
187
299
  end
188
300
  @model_handler.CellValue = fiddle_closure_block_caller(1, [1, 1, 4, 4]) do |_, _, row, column|
189
- the_cell_rows = expanded_cell_rows
301
+ cell_rows = self.cell_rows || []
302
+ the_cell = expanded_cell_for(row, column)
190
303
  case @columns[column]
191
304
  when Column::TextColumnProxy, Column::ButtonColumnProxy, Column::TextColorColumnProxy, :text
192
- ::LibUI.new_table_value_string((expanded_cell_rows[row] && expanded_cell_rows[row][column]).to_s)
305
+ ::LibUI.new_table_value_string((the_cell).to_s)
193
306
  when Column::ImageColumnProxy, Column::ImageTextColumnProxy, Column::ImageTextColorColumnProxy
194
- if OS.windows? && row == cell_rows.count
307
+ if OS.windows? && row == cell_rows.size
195
308
  img = Glimmer::LibUI::ICON
196
309
  else
197
- img = expanded_cell_rows[row][column]
310
+ img = the_cell
198
311
  end
199
312
  img = ControlProxy::ImageProxy.create('image', nil, img) if img.is_a?(Array)
200
313
  img = ControlProxy::ImageProxy.create('image', nil, [img]) if img.is_a?(String)
201
314
  img = img.respond_to?(:libui) ? img.libui : img
202
315
  ::LibUI.new_table_value_image(img)
203
316
  when Column::CheckboxColumnProxy, Column::CheckboxTextColumnProxy, Column::CheckboxTextColorColumnProxy
204
- ::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)
317
+ ::LibUI.new_table_value_int(((the_cell == 1 || the_cell.to_s.strip.downcase == 'true' ? 1 : 0)) || 0)
205
318
  when Column::ProgressBarColumnProxy
206
- value = (expanded_cell_rows[row] && expanded_cell_rows[row][column]).to_i
207
- expanded_last_last_cell_rows = expand(@last_last_cell_rows)
208
- old_value = (expanded_last_last_cell_rows[row] && expanded_last_last_cell_rows[row][column]).to_i
319
+ value = (the_cell).to_i
320
+ expanded_last_last_cell_row = expand_cell_row(last_last_cell_row_for(row))
321
+ # TODO consider only caching old value when displayed in CellValue before, but otherwise not worrying about it (if row was hiding, this fix shouldn't be needed on Windows)
322
+ old_value = (expanded_last_last_cell_row && expanded_last_last_cell_row[column]).to_i
209
323
  if OS.windows? && old_value == -1 && value >= 0
210
324
  Glimmer::Config.logger.error('Switching a progress bar value from -1 to a positive value is not supported on Windows')
211
- cell_rows[row][column] = -1
325
+ cell_row = cell_row_for(row)
326
+ if cell_row.is_a?(Array)
327
+ cell_row[compound_column_index_for(column)] = -1
328
+ else
329
+ attribute = @column_attributes[column]
330
+ cell_row.send("#{attribute}=", -1)
331
+ end
212
332
  ::LibUI.new_table_value_int(old_value)
213
333
  else
214
- ::LibUI.new_table_value_int((expanded_cell_rows[row] && expanded_cell_rows[row][column]).to_i)
334
+ ::LibUI.new_table_value_int((the_cell).to_i)
215
335
  end
216
336
  when Column::BackgroundColorColumnProxy
217
- background_color = Glimmer::LibUI.interpret_color(expanded_cell_rows[row] && expanded_cell_rows[row][column]) || {r: 255, g: 255, b: 255}
337
+ background_color = Glimmer::LibUI.interpret_color(the_cell) || {r: 255, g: 255, b: 255}
218
338
  ::LibUI.new_table_value_color(background_color[:r] / 255.0, background_color[:g] / 255.0, background_color[:b] / 255.0, background_color[:a] || 1.0)
219
339
  when :color
220
- color = Glimmer::LibUI.interpret_color(expanded_cell_rows[row] && expanded_cell_rows[row][column]) || {r: 0, g: 0, b: 0}
340
+ color = Glimmer::LibUI.interpret_color(the_cell) || {r: 0, g: 0, b: 0}
221
341
  ::LibUI.new_table_value_color(color[:r] / 255.0, color[:g] / 255.0, color[:b] / 255.0, color[:a] || 1.0)
222
342
  end
223
343
  end
224
344
  @model_handler.SetCellValue = fiddle_closure_block_caller(0, [1, 1, 4, 4, 1]) do |_, _, row, column, val|
345
+ table_cell_row = cell_row_for(row)
225
346
  case @columns[column]
226
347
  when Column::TextColumnProxy
227
348
  column = @columns[column].index
228
- @cell_rows[row] ||= []
229
- if @cell_rows[row].is_a?(Array)
230
- @cell_rows[row][column] = ::LibUI.table_value_string(val).to_s
349
+ if table_cell_row.is_a?(Array)
350
+ table_cell_row[column] = ::LibUI.table_value_string(val).to_s
231
351
  else
232
352
  attribute = @column_attributes[column]
233
- @cell_rows[row].send("#{attribute}=", ::LibUI.table_value_string(val).to_s)
353
+ table_cell_row.send("#{attribute}=", ::LibUI.table_value_string(val).to_s)
234
354
  end
235
355
  when Column::TextColorColumnProxy
236
356
  column = @columns[column].index
237
- @cell_rows[row] ||= []
238
- if @cell_rows[row].is_a?(Array)
239
- @cell_rows[row][column] ||= []
240
- @cell_rows[row][column][0] = ::LibUI.table_value_string(val).to_s
357
+ if table_cell_row.is_a?(Array)
358
+ table_cell_row[column] ||= []
359
+ table_cell_row[column][0] = ::LibUI.table_value_string(val).to_s
241
360
  else
242
361
  attribute = @column_attributes[column]
243
- @cell_rows[row].send("#{attribute}=", []) unless @cell_rows[row].send(attribute)
244
- @cell_rows[row].send(attribute)[0] = ::LibUI.table_value_string(val).to_s
362
+ table_cell_row.send("#{attribute}=", []) unless table_cell_row.send(attribute)
363
+ table_cell_row.send(attribute)[0] = ::LibUI.table_value_string(val).to_s
245
364
  end
246
365
  when :text
247
366
  column = @columns[column - 1].index
248
- @cell_rows[row] ||= []
249
- if @cell_rows[row].is_a?(Array)
250
- @cell_rows[row][column] ||= []
251
- @cell_rows[row][column][1] = ::LibUI.table_value_string(val).to_s
367
+ if table_cell_row.is_a?(Array)
368
+ table_cell_row[column] ||= []
369
+ table_cell_row[column][1] = ::LibUI.table_value_string(val).to_s
252
370
  else
253
371
  attribute = @column_attributes[column]
254
- @cell_rows[row].send("#{attribute}=", []) unless @cell_rows[row].send(attribute)
255
- @cell_rows[row].send(attribute)[1] = ::LibUI.table_value_string(val).to_s
372
+ table_cell_row.send("#{attribute}=", []) unless table_cell_row.send(attribute)
373
+ table_cell_row.send(attribute)[1] = ::LibUI.table_value_string(val).to_s
256
374
  end
257
375
  when Column::ButtonColumnProxy
258
376
  @columns[column].notify_custom_listeners('on_clicked', row)
259
377
  when Column::CheckboxColumnProxy
260
378
  column = @columns[column].index
261
- @cell_rows[row] ||= []
262
- if @cell_rows[row].is_a?(Array)
263
- @cell_rows[row][column] = ::LibUI.table_value_int(val).to_i == 1
379
+ if table_cell_row.is_a?(Array)
380
+ table_cell_row[column] = ::LibUI.table_value_int(val).to_i == 1
264
381
  else
265
382
  attribute = @column_attributes[column]
266
- @cell_rows[row].send("#{attribute}=", ::LibUI.table_value_int(val).to_i == 1)
383
+ table_cell_row.send("#{attribute}=", ::LibUI.table_value_int(val).to_i == 1)
267
384
  end
268
385
  when Column::CheckboxTextColumnProxy
269
386
  column = @columns[column].index
270
- @cell_rows[row] ||= []
271
- if @cell_rows[row].is_a?(Array)
272
- @cell_rows[row][column] ||= []
273
- @cell_rows[row][column][0] = ::LibUI.table_value_int(val).to_i == 1
387
+ if table_cell_row.is_a?(Array)
388
+ table_cell_row[column] ||= []
389
+ table_cell_row[column][0] = ::LibUI.table_value_int(val).to_i == 1
274
390
  else
275
391
  attribute = @column_attributes[column]
276
- @cell_rows[row].send("#{attribute}=", []) unless @cell_rows[row].send(attribute)
277
- @cell_rows[row].send(attribute)[0] = ::LibUI.table_value_int(val).to_i == 1
392
+ table_cell_row.send("#{attribute}=", []) unless table_cell_row.send(attribute)
393
+ table_cell_row.send(attribute)[0] = ::LibUI.table_value_int(val).to_i == 1
278
394
  end
279
395
  end
280
- @expanded_cell_rows_dependency_cell_rows = nil
281
- notify_custom_listeners('on_edited', row, @cell_rows[row])
396
+ notify_custom_listeners('on_edited', row, table_cell_row)
282
397
  end
283
398
 
284
399
  @model = ::LibUI.new_table_model(@model_handler)
@@ -301,6 +416,24 @@ module Glimmer
301
416
  end
302
417
  end
303
418
 
419
+ def clear_cached_cell_rows
420
+ @cached_cell_rows = nil
421
+ @cached_cell_rows_size = nil
422
+ @cached_cell_rows_enumerator_index = nil
423
+ end
424
+
425
+ def clear_cached_last_cell_rows
426
+ @cached_last_cell_rows = nil
427
+ @cached_last_cell_rows_size = nil
428
+ @cached_last_cell_rows_enumerator_index = nil
429
+ end
430
+
431
+ def clear_cached_last_last_cell_rows
432
+ @cached_last_last_cell_rows = nil
433
+ @cached_last_last_cell_rows_size = nil
434
+ @cached_last_last_cell_rows_enumerator_index = nil
435
+ end
436
+
304
437
  def next_column_index
305
438
  @next_column_index ||= -1
306
439
  @next_column_index += 1
@@ -308,49 +441,60 @@ module Glimmer
308
441
 
309
442
  def register_cell_rows_observer
310
443
  @cell_rows_observer = Glimmer::DataBinding::Observer.proc do |new_cell_rows|
311
- if @cell_rows.size < @last_cell_rows.size
312
- @last_cell_rows.each_with_index do |old_row_data, row|
313
- if old_row_data != @cell_rows[row] && model
314
- ::LibUI.table_model_row_changed(model, row)
315
- @expanded_cell_rows_dependency_cell_rows = nil
316
- notify_custom_listeners('on_changed', row, :changed, @cell_rows[row])
444
+ if !@last_cell_rows.nil? # not initial load of table
445
+ if @cell_rows.size < @last_cell_rows.size
446
+ # TODO avoid inefficient delete/change notifications by only updating cells that need updates (only cells that have been displayed in CellValue)
447
+ # instead of updating everything
448
+ @last_cell_rows.each_with_index do |old_row_data, row|
449
+ new_row_data = cell_row_for(row)
450
+ if old_row_data != new_row_data && model
451
+ ::LibUI.table_model_row_changed(model, row)
452
+ notify_custom_listeners('on_changed', row, :changed, new_row_data)
453
+ end
317
454
  end
318
- end
319
- (@last_cell_rows.size - @cell_rows.size).times do |n|
320
- row = @last_cell_rows.size - n - 1
321
- if model
322
- ::LibUI.table_model_row_deleted(model, row)
323
- @expanded_cell_rows_dependency_cell_rows = nil
324
- notify_custom_listeners('on_changed', row, :deleted, @last_cell_rows[row])
455
+ count_of_cells_to_delete = @last_cell_rows.size - @cell_rows.size
456
+ count_of_cells_to_delete.times do |n|
457
+ row = @last_cell_rows.size - n - 1
458
+ if model
459
+ ::LibUI.table_model_row_deleted(model, row)
460
+ notify_custom_listeners('on_changed', row, :deleted, last_cell_row_for(row))
461
+ end
325
462
  end
326
- end
327
- elsif @cell_rows.size > @last_cell_rows.size
328
- (@cell_rows.size - @last_cell_rows.size).times do |n|
329
- row = @last_cell_rows.size + n
330
- if model
331
- ::LibUI.table_model_row_inserted(model, row)
332
- @expanded_cell_rows_dependency_cell_rows = nil
333
- notify_custom_listeners('on_changed', row, :inserted, @cell_rows[row])
463
+ elsif @cell_rows.size > @last_cell_rows.size
464
+ (@cell_rows.size - @last_cell_rows.size).times do |n|
465
+ row = @last_cell_rows.size + n
466
+ if model
467
+ ::LibUI.table_model_row_inserted(model, row)
468
+ notify_custom_listeners('on_changed', row, :inserted, cell_row_for(row))
469
+ end
334
470
  end
335
- end
336
- @cell_rows.each_with_index do |new_row_data, row|
337
- if new_row_data != @last_cell_rows[row] && model
338
- ::LibUI.table_model_row_changed(model, row)
339
- @expanded_cell_rows_dependency_cell_rows = nil
340
- notify_custom_listeners('on_changed', row, :changed, @cell_rows[row])
471
+ # TODO avoid inefficient insert/change notifications by only updating cells that need updates (only cells that have been displayed in CellValue)
472
+ # instead of updating everything
473
+ @cell_rows.each_with_index do |new_row_data, row|
474
+ if new_row_data != last_cell_row_for(row) && model
475
+ ::LibUI.table_model_row_changed(model, row)
476
+ notify_custom_listeners('on_changed', row, :changed, new_row_data)
477
+ end
341
478
  end
342
- end
343
- elsif @cell_rows != @last_cell_rows
344
- @cell_rows.each_with_index do |new_row_data, row|
345
- if new_row_data != @last_cell_rows[row] && model
346
- ::LibUI.table_model_row_changed(model, row)
347
- @expanded_cell_rows_dependency_cell_rows = nil
348
- notify_custom_listeners('on_changed', row, :changed, @cell_rows[row])
479
+ elsif @cell_rows != @last_cell_rows
480
+ @cell_rows.each_with_index do |new_row_data, row|
481
+ if new_row_data != last_cell_row_for(row) && model
482
+ ::LibUI.table_model_row_changed(model, row)
483
+ notify_custom_listeners('on_changed', row, :changed, new_row_data)
484
+ end
349
485
  end
350
486
  end
351
487
  end
352
- @last_last_cell_rows = array_deep_clone(@last_cell_rows)
353
- @last_cell_rows = array_deep_clone(@cell_rows)
488
+ # TODO look into performance implications of deep cloning an entire array,
489
+ # which wastes time looping through all elements even if we are displaying about
490
+ # 20 of them only at the moment, thus preventing lazy loading from happening
491
+ # Consider alternatively caching rows per index when needed instead
492
+ # This seems to add about a full second to certain apps, especially when using enumerators
493
+ # to avoid loading everything at once
494
+
495
+ dup_last_last_cell_rows if OS.windows?
496
+ dup_last_cell_rows
497
+
354
498
  if !@applied_windows_fix_on_first_cell_rows_update && OS.windows?
355
499
  @applied_windows_fix_on_first_cell_rows_update = true
356
500
  apply_windows_fix
@@ -360,6 +504,7 @@ module Glimmer
360
504
  end
361
505
 
362
506
  def apply_windows_fix
507
+ # TODO will this require that @cell_rows_observer is called with queue_main too to avoid issue with multiple immediate model updates breaking this?
363
508
  Glimmer::LibUI.queue_main do
364
509
  new_row = @columns&.select {|column| column.is_a?(Column)}&.map {|column| column.class.default_value}
365
510
  if new_row
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2021-2022 Andy Maleh
1
+ # Copyright (c) 2021-2023 Andy Maleh
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2021-2022 Andy Maleh
1
+ # Copyright (c) 2021-2023 Andy Maleh
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2021-2022 Andy Maleh
1
+ # Copyright (c) 2021-2023 Andy Maleh
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2021-2022 Andy Maleh
1
+ # Copyright (c) 2021-2023 Andy Maleh
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2021-2022 Andy Maleh
1
+ # Copyright (c) 2021-2023 Andy Maleh
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2021-2022 Andy Maleh
1
+ # Copyright (c) 2021-2023 Andy Maleh
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2021-2022 Andy Maleh
1
+ # Copyright (c) 2021-2023 Andy Maleh
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the
@@ -216,7 +216,7 @@ module Glimmer
216
216
  if !@filtered_model_array_stack.key?(filter_query)
217
217
  table_column_names = @table_proxy.columns.map(&:name)
218
218
  @filtered_model_array_stack[filter_query] = model_array.dup.filter do |model|
219
- row_values = @table_proxy.expand([model])[0].map(&:to_s)
219
+ row_values = @table_proxy.expand_cell_row(model).map(&:to_s)
220
220
  row_hash = Hash[table_column_names.zip(row_values)]
221
221
  filter.call(row_hash, filter_query)
222
222
  end
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2021-2022 Andy Maleh
1
+ # Copyright (c) 2021-2023 Andy Maleh
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2021-2022 Andy Maleh
1
+ # Copyright (c) 2021-2023 Andy Maleh
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2021-2022 Andy Maleh
1
+ # Copyright (c) 2021-2023 Andy Maleh
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2021-2022 Andy Maleh
1
+ # Copyright (c) 2021-2023 Andy Maleh
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2021-2022 Andy Maleh
1
+ # Copyright (c) 2021-2023 Andy Maleh
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2021-2022 Andy Maleh
1
+ # Copyright (c) 2021-2023 Andy Maleh
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2021-2022 Andy Maleh
1
+ # Copyright (c) 2021-2023 Andy Maleh
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2021-2022 Andy Maleh
1
+ # Copyright (c) 2021-2023 Andy Maleh
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2021-2022 Andy Maleh
1
+ # Copyright (c) 2021-2023 Andy Maleh
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2021-2022 Andy Maleh
1
+ # Copyright (c) 2021-2023 Andy Maleh
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2021-2022 Andy Maleh
1
+ # Copyright (c) 2021-2023 Andy Maleh
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2021-2022 Andy Maleh
1
+ # Copyright (c) 2021-2023 Andy Maleh
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2021-2022 Andy Maleh
1
+ # Copyright (c) 2021-2023 Andy Maleh
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the