glimmer-dsl-libui 0.4.14 → 0.4.18

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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +30 -0
  3. data/README.md +170 -60
  4. data/VERSION +1 -1
  5. data/bin/girb +0 -0
  6. data/examples/basic_table.rb +1 -1
  7. data/examples/basic_table_button.rb +0 -1
  8. data/examples/cpu_percentage.rb +1 -1
  9. data/examples/custom_draw_text.rb +14 -7
  10. data/examples/custom_draw_text2.rb +15 -8
  11. data/examples/editable_column_table.rb +5 -0
  12. data/examples/form_table.rb +9 -3
  13. data/examples/form_table2.rb +12 -6
  14. data/examples/form_table3.rb +9 -3
  15. data/examples/form_table4.rb +9 -3
  16. data/examples/form_table5.rb +9 -3
  17. data/examples/meta_example.rb +3 -1
  18. data/glimmer-dsl-libui.gemspec +0 -0
  19. data/icons/blank.png +0 -0
  20. data/lib/glimmer/dsl/libui/dsl.rb +1 -0
  21. data/lib/glimmer/dsl/libui/operation_expression.rb +47 -0
  22. data/lib/glimmer/dsl/libui/property_expression.rb +2 -2
  23. data/lib/glimmer/libui/attributed_string.rb +17 -8
  24. data/lib/glimmer/libui/control_proxy/area_proxy.rb +17 -17
  25. data/lib/glimmer/libui/control_proxy/box.rb +1 -0
  26. data/lib/glimmer/libui/control_proxy/column/background_color_column_proxy.rb +6 -0
  27. data/lib/glimmer/libui/control_proxy/column/button_column_proxy.rb +6 -28
  28. data/lib/glimmer/libui/control_proxy/column/checkbox_column_proxy.rb +6 -0
  29. data/lib/glimmer/libui/control_proxy/column/checkbox_text_color_column_proxy.rb +6 -0
  30. data/lib/glimmer/libui/control_proxy/column/checkbox_text_column_proxy.rb +6 -0
  31. data/lib/glimmer/libui/control_proxy/column/image_column_proxy.rb +6 -0
  32. data/lib/glimmer/libui/control_proxy/column/image_text_color_column_proxy.rb +6 -0
  33. data/lib/glimmer/libui/control_proxy/column/image_text_column_proxy.rb +6 -0
  34. data/lib/glimmer/libui/control_proxy/column/progress_bar_column_proxy.rb +6 -0
  35. data/lib/glimmer/libui/control_proxy/column/text_color_column_proxy.rb +6 -0
  36. data/lib/glimmer/libui/control_proxy/column/text_column_proxy.rb +6 -0
  37. data/lib/glimmer/libui/control_proxy/column.rb +7 -0
  38. data/lib/glimmer/libui/control_proxy/form_proxy.rb +1 -0
  39. data/lib/glimmer/libui/control_proxy/image_proxy.rb +3 -0
  40. data/lib/glimmer/libui/control_proxy/menu_item_proxy/quit_menu_item_proxy.rb +18 -9
  41. data/lib/glimmer/libui/control_proxy/open_type_features_proxy.rb +11 -2
  42. data/lib/glimmer/libui/control_proxy/open_type_tag_proxy.rb +2 -0
  43. data/lib/glimmer/libui/control_proxy/path_proxy.rb +8 -4
  44. data/lib/glimmer/libui/control_proxy/table_proxy.rb +59 -34
  45. data/lib/glimmer/libui/control_proxy/text_proxy.rb +2 -0
  46. data/lib/glimmer/libui/control_proxy/window_proxy.rb +34 -35
  47. data/lib/glimmer/libui/control_proxy.rb +45 -9
  48. data/lib/glimmer/libui/data_bindable.rb +1 -1
  49. data/lib/glimmer/libui/shape.rb +1 -0
  50. data/lib/glimmer/libui.rb +1 -0
  51. data/lib/glimmer-dsl-libui.rb +6 -0
  52. metadata +21 -5
@@ -32,6 +32,12 @@ module Glimmer
32
32
  #
33
33
  # Follows the Proxy Design Pattern
34
34
  class TextColorColumnProxy < ControlProxy
35
+ class << self
36
+ def default_value
37
+ ['', :black]
38
+ end
39
+ end
40
+
35
41
  include Column
36
42
  include DualColumn
37
43
  include EditableColumn
@@ -31,6 +31,12 @@ module Glimmer
31
31
  #
32
32
  # Follows the Proxy Design Pattern
33
33
  class TextColumnProxy < ControlProxy
34
+ class << self
35
+ def default_value
36
+ ''
37
+ end
38
+ end
39
+
34
40
  include Column
35
41
  include EditableColumn
36
42
 
@@ -24,6 +24,13 @@ module Glimmer
24
24
  class ControlProxy
25
25
  # Common logic for all column proxy objects
26
26
  module Column
27
+ class << self
28
+ # subclasses may override to provide a valid default value like a blank image for image columns and false for checkbox
29
+ def default_value
30
+ nil
31
+ end
32
+ end
33
+
27
34
  def initialize(keyword, parent, args, &block)
28
35
  @keyword = keyword
29
36
  @parent_proxy = parent
@@ -39,6 +39,7 @@ module Glimmer
39
39
  end
40
40
 
41
41
  def destroy_child(child)
42
+ child.deregister_all_custom_listeners
42
43
  ::LibUI.send("form_delete", @libui, children.index(child))
43
44
  ControlProxy.control_proxies.delete(child)
44
45
  end
@@ -52,6 +52,7 @@ module Glimmer
52
52
 
53
53
  include Parent
54
54
  prepend Transformable
55
+ include Equalizer.new(:options, :data)
55
56
 
56
57
  attr_reader :data, :pixels, :shapes, :options
57
58
 
@@ -179,6 +180,8 @@ module Glimmer
179
180
  end
180
181
 
181
182
  def destroy
183
+ return if ControlProxy.main_window_proxy&.destroying?
184
+ deregister_all_custom_listeners
182
185
  @parent_proxy&.children&.delete(self)
183
186
  ControlProxy.control_proxies.delete(self)
184
187
  end
@@ -35,29 +35,38 @@ module Glimmer
35
35
 
36
36
  def handle_listener(listener_name, &listener)
37
37
  if listener_name == 'on_clicked'
38
- @default_behavior_listener = Proc.new do
39
- return_value = listener.call(self)
38
+ @on_clicked_listeners ||= []
39
+ @on_clicked_listeners << listener
40
+ @default_behavior_listener ||= Proc.new do
41
+ return_value = nil
42
+ @on_clicked_listeners.each do |l|
43
+ return_value = l.call(self)
44
+ break if return_value.is_a?(Numeric)
45
+ end
40
46
  if return_value.is_a?(Numeric)
41
47
  return_value
42
48
  else
43
- destroy
49
+ ControlProxy.main_window_proxy&.destroy
44
50
  ::LibUI.quit
45
51
  0
46
52
  end
53
+ end.tap do |default_behavior_listener|
54
+ ::LibUI.on_should_quit(&default_behavior_listener)
47
55
  end
48
- ::LibUI.on_should_quit(&@default_behavior_listener)
49
56
  end
50
57
  end
58
+
59
+ def destroy
60
+ @on_clicked_listeners&.clear
61
+ super
62
+ end
51
63
 
52
64
  private
53
65
 
54
66
  def build_control
55
67
  @libui = @parent_proxy.append_quit_item(*@args)
56
- handle_listener('on_clicked') do
57
- ControlProxy.main_window_proxy&.destroy
58
- ::LibUI.quit
59
- 0
60
- end
68
+ # setup default on_clicked listener if no on_clicked listeners are setup
69
+ handle_listener('on_clicked') {} if @on_clicked_listeners.nil? || @on_clicked_listeners.empty?
61
70
  end
62
71
  end
63
72
  end
@@ -33,15 +33,24 @@ module Glimmer
33
33
  include Parent
34
34
 
35
35
  def destroy
36
+ return if ControlProxy.main_window_proxy&.destroying?
37
+ return if @destroying
38
+ @destroying = true
39
+ deregister_all_custom_listeners
36
40
  ::LibUI.free_open_type_features(@libui)
37
- @parent_proxy&.children&.delete(self)
41
+ @parent_proxy&.remove_open_type_features
38
42
  ControlProxy.control_proxies.delete(self)
43
+ @destroying = false
39
44
  end
40
45
 
41
46
  def redraw
42
- @parent_proxy.redraw
47
+ @parent_proxy&.redraw
43
48
  end
44
49
 
50
+ def request_auto_redraw
51
+ @parent_proxy&.request_auto_redraw
52
+ end
53
+
45
54
  private
46
55
 
47
56
  def build_control
@@ -31,6 +31,8 @@ module Glimmer
31
31
  # Follows the Proxy Design Pattern
32
32
  class OpenTypeTagProxy < ControlProxy
33
33
  def destroy
34
+ return if ControlProxy.main_window_proxy&.destroying?
35
+ deregister_all_custom_listeners
34
36
  @parent_proxy&.children&.delete(self)
35
37
  ControlProxy.control_proxies.delete(self)
36
38
  end
@@ -70,7 +70,8 @@ module Glimmer
70
70
  else
71
71
  new_color = Glimmer::LibUI.interpret_color(args)
72
72
  if new_color != @fill
73
- @fill_observer&.unobserve(@fill) if @fill
73
+ # TODO consider replacing unobserve with observer_registration.deregister
74
+ @fill_observer&.unobserve(@fill, attribute_writer_type: [:attribute=, :set_attribute]) if @fill
74
75
  @fill = new_color
75
76
  request_auto_redraw
76
77
  end
@@ -79,7 +80,7 @@ module Glimmer
79
80
  @fill_observer ||= Glimmer::DataBinding::Observer.proc do
80
81
  request_auto_redraw
81
82
  end
82
- @fill_observer.observe(@fill)
83
+ @fill_observer.observe(@fill, attribute_writer_type: [:attribute=, :set_attribute])
83
84
  end
84
85
  end
85
86
  alias fill= fill
@@ -98,7 +99,8 @@ module Glimmer
98
99
  else
99
100
  new_color = Glimmer::LibUI.interpret_color(args)
100
101
  if new_color != @stroke
101
- @stroke_observer&.unobserve(@stroke) if @stroke
102
+ # TODO consider replacing unobserve with observer_registration.deregister
103
+ @stroke_observer&.unobserve(@stroke, attribute_writer_type: [:attribute=, :set_attribute]) if @stroke
102
104
  @stroke = Glimmer::LibUI.interpret_color(args)
103
105
  request_auto_redraw
104
106
  end
@@ -107,7 +109,7 @@ module Glimmer
107
109
  @stroke_observer ||= Glimmer::DataBinding::Observer.proc do
108
110
  request_auto_redraw
109
111
  end
110
- @stroke_observer.observe(@stroke)
112
+ @stroke_observer.observe(@stroke, attribute_writer_type: [:attribute=, :set_attribute])
111
113
  end
112
114
  end
113
115
  alias stroke= stroke
@@ -140,6 +142,8 @@ module Glimmer
140
142
  end
141
143
 
142
144
  def destroy
145
+ return if ControlProxy.main_window_proxy&.destroying?
146
+ deregister_all_custom_listeners
143
147
  @parent_proxy&.children&.delete(self)
144
148
  ControlProxy.control_proxies.delete(self)
145
149
  end
@@ -36,7 +36,7 @@ module Glimmer
36
36
  class TableProxy < ControlProxy
37
37
  include Glimmer::FiddleConsumer
38
38
 
39
- LISTENERS = ['on_changed', 'on_edited']
39
+ CUSTOM_LISTENER_NAMES = ['on_changed', 'on_edited']
40
40
 
41
41
  attr_reader :model_handler, :model, :table_params, :columns, :column_attributes
42
42
 
@@ -48,6 +48,8 @@ module Glimmer
48
48
  @enabled = true
49
49
  @columns = []
50
50
  @cell_rows = []
51
+ @last_cell_rows = []
52
+ register_cell_rows_observer
51
53
  window_proxy.on_destroy do
52
54
  # the following unless condition is an exceptional condition stumbled upon that fails freeing the table model
53
55
  ::LibUI.free_table_model(@model) unless @destroyed && parent_proxy.is_a?(Box)
@@ -75,7 +77,8 @@ module Glimmer
75
77
 
76
78
  def destroy
77
79
  super
78
- @cell_rows_observer&.unobserve(self, :cell_rows, recursive: true)
80
+ # TODO consider replacing unobserve with observer_registration.deregister
81
+ @cell_rows_observer&.unobserve(self, :cell_rows, recursive: true, ignore_frozen: true, attribute_writer_type: [:attribute=, :set_attribute])
79
82
  @destroyed = true
80
83
  end
81
84
 
@@ -86,31 +89,6 @@ module Glimmer
86
89
  if rows != @cell_rows
87
90
  @cell_rows = rows
88
91
  @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|
91
- if @cell_rows.size < @last_cell_rows.size && @last_cell_rows.include_all?(*@cell_rows)
92
- @last_cell_rows.array_diff_indexes(@cell_rows).reverse.each do |row|
93
- ::LibUI.table_model_row_deleted(model, row)
94
- on_changed.each {|listener| listener.call(row, :deleted, @last_cell_rows[row])}
95
- end
96
- elsif @cell_rows.size > @last_cell_rows.size && @cell_rows.include_all?(*@last_cell_rows)
97
- @cell_rows.array_diff_indexes(@last_cell_rows).each do |row|
98
- ::LibUI.table_model_row_inserted(model, row)
99
- on_changed.each {|listener| listener.call(row, :inserted, @cell_rows[row])}
100
- end
101
- else
102
- @cell_rows.each_with_index do |new_row_data, row|
103
- if new_row_data != @last_cell_rows[row]
104
- ::LibUI.table_model_row_changed(model, row)
105
- on_changed.each {|listener| listener.call(row, :changed, @cell_rows[row])}
106
- end
107
- end
108
- end
109
- @last_last_cell_rows = array_deep_clone(@last_cell_rows)
110
- @last_cell_rows = array_deep_clone(@cell_rows)
111
- end.tap do |cell_rows_observer|
112
- cell_rows_observer.observe(self, :cell_rows, recursive: true)
113
- end
114
92
  end
115
93
  @cell_rows
116
94
  end
@@ -153,13 +131,13 @@ module Glimmer
153
131
  new_value = new_value.to_a if new_value.is_a?(Enumerator)
154
132
  if model_binding.binding_options[:column_attributes] || (!new_value.empty? && !new_value.first.is_a?(Array))
155
133
  @model_attribute_array_observer_registration&.deregister
156
- @model_attribute_array_observer_registration = model_attribute_observer.observe(new_value, @column_attributes)
134
+ @model_attribute_array_observer_registration = model_attribute_observer.observe(new_value, @column_attributes, ignore_frozen: true, attribute_writer_type: [:attribute=, :set_attribute])
157
135
  model_attribute_observer.add_dependent(model_attribute_observer_registration => @model_attribute_array_observer_registration)
158
136
  end
159
137
  # TODO look if multiple notifications are happening as a result of observing array and observing model binding
160
138
  send("#{property}=", new_value) unless @last_cell_rows == new_value
161
139
  end
162
- model_attribute_observer_registration = model_attribute_observer.observe(model_binding)
140
+ model_attribute_observer_registration = model_attribute_observer.observe(model_binding, attribute_writer_type: [:attribute=, :set_attribute])
163
141
  model_attribute_observer.call # initial update
164
142
  data_binding_model_attribute_observer_registrations << model_attribute_observer_registration
165
143
  model_attribute_observer
@@ -208,9 +186,11 @@ module Glimmer
208
186
  when Column::TextColumnProxy, Column::ButtonColumnProxy, Column::TextColorColumnProxy, :text
209
187
  ::LibUI.new_table_value_string((expanded_cell_rows[row] && expanded_cell_rows[row][column]).to_s)
210
188
  when Column::ImageColumnProxy, Column::ImageTextColumnProxy, Column::ImageTextColorColumnProxy
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]
189
+ if OS.windows? && row == cell_rows.count
190
+ img = Glimmer::LibUI::ICON
191
+ else
192
+ img = expanded_cell_rows[row][column]
193
+ end
214
194
  img = ControlProxy::ImageProxy.create('image', nil, img) if img.is_a?(Array)
215
195
  img = ControlProxy::ImageProxy.create('image', nil, [img]) if img.is_a?(String)
216
196
  img = img.respond_to?(:libui) ? img.libui : img
@@ -270,7 +250,7 @@ module Glimmer
270
250
  @cell_rows[row].send(attribute)[1] = ::LibUI.table_value_string(val).to_s
271
251
  end
272
252
  when Column::ButtonColumnProxy
273
- @columns[column].notify_listeners(:on_clicked, row)
253
+ @columns[column].notify_custom_listeners('on_clicked', row)
274
254
  when Column::CheckboxColumnProxy
275
255
  column = @columns[column].index
276
256
  @cell_rows[row] ||= []
@@ -292,7 +272,7 @@ module Glimmer
292
272
  @cell_rows[row].send(attribute)[0] = ::LibUI.table_value_int(val).to_i == 1
293
273
  end
294
274
  end
295
- on_edited.each {|listener| listener.call(row, @cell_rows[row])}
275
+ notify_custom_listeners('on_edited', row, @cell_rows[row])
296
276
  end
297
277
 
298
278
  @model = ::LibUI.new_table_model(@model_handler)
@@ -308,12 +288,57 @@ module Glimmer
308
288
  @libui.tap do
309
289
  @columns.each {|column| column.respond_to?(:build_control, true) && column.send(:build_control) }
310
290
  end
291
+
292
+ if !@applied_windows_fix && OS.windows?
293
+ @applied_windows_fix = true
294
+ apply_windows_fix
295
+ end
311
296
  end
312
297
 
313
298
  def next_column_index
314
299
  @next_column_index ||= -1
315
300
  @next_column_index += 1
316
301
  end
302
+
303
+ def register_cell_rows_observer
304
+ @cell_rows_observer = Glimmer::DataBinding::Observer.proc do |new_cell_rows|
305
+ if @cell_rows.size < @last_cell_rows.size && @last_cell_rows.include_all?(*@cell_rows)
306
+ @last_cell_rows.array_diff_indexes(@cell_rows).reverse.each do |row|
307
+ ::LibUI.table_model_row_deleted(model, row) if model && row
308
+ notify_custom_listeners('on_changed', row, :deleted, @last_cell_rows[row])
309
+ end
310
+ elsif @cell_rows.size > @last_cell_rows.size && @cell_rows.include_all?(*@last_cell_rows)
311
+ @cell_rows.array_diff_indexes(@last_cell_rows).each do |row|
312
+ ::LibUI.table_model_row_inserted(model, row) if model && row
313
+ notify_custom_listeners('on_changed', row, :inserted, @cell_rows[row])
314
+ end
315
+ else
316
+ @cell_rows.each_with_index do |new_row_data, row|
317
+ if new_row_data != @last_cell_rows[row]
318
+ ::LibUI.table_model_row_changed(model, row) if model && row
319
+ notify_custom_listeners('on_changed', row, :changed, @cell_rows[row])
320
+ end
321
+ end
322
+ end
323
+ @last_last_cell_rows = array_deep_clone(@last_cell_rows)
324
+ @last_cell_rows = array_deep_clone(@cell_rows)
325
+ if !@applied_windows_fix_on_first_cell_rows_update && OS.windows?
326
+ @applied_windows_fix_on_first_cell_rows_update = true
327
+ apply_windows_fix
328
+ end
329
+ end
330
+ @cell_rows_observer_registration = @cell_rows_observer.observe(self, :cell_rows, recursive: true, ignore_frozen: true, attribute_writer_type: [:attribute=, :set_attribute])
331
+ end
332
+
333
+ def apply_windows_fix
334
+ Glimmer::LibUI.queue_main do
335
+ new_row = @columns&.select {|column| column.is_a?(Column)}&.map {|column| column.class.default_value}
336
+ if new_row
337
+ @cell_rows << new_row
338
+ @cell_rows.pop
339
+ end
340
+ end
341
+ end
317
342
  end
318
343
  end
319
344
  end
@@ -60,6 +60,8 @@ module Glimmer
60
60
  end
61
61
 
62
62
  def destroy
63
+ return if ControlProxy.main_window_proxy&.destroying?
64
+ deregister_all_custom_listeners
63
65
  @parent_proxy&.children&.delete(self)
64
66
  ControlProxy.control_proxies.delete(self)
65
67
  end
@@ -29,6 +29,10 @@ module Glimmer
29
29
  #
30
30
  # Follows the Proxy Design Pattern
31
31
  class WindowProxy < ControlProxy
32
+ CUSTOM_LISTENER_NAMES = ['on_destroy']
33
+ CUSTOM_LISTENER_NAME_ALIASES = {
34
+ on_destroyed: 'on_destroy',
35
+ }
32
36
  DEFAULT_TITLE = ''
33
37
  DEFAULT_WIDTH = 190
34
38
  DEFAULT_HEIGHT = 150
@@ -44,22 +48,20 @@ module Glimmer
44
48
  end
45
49
 
46
50
  def destroy
51
+ return if @destroying
52
+ @destroying = true
53
+ @on_closing_listeners&.clear
47
54
  super
48
55
  ControlProxy.image_proxies.each { |image_proxy| ::LibUI.free_image(image_proxy.libui) unless image_proxy.area_image? }
49
- @on_destroy_procs&.each { |on_destroy_proc| on_destroy_proc.call(self)}
56
+ notify_custom_listeners('on_destroy', self)
57
+ deregister_custom_listeners('on_destroy')
58
+ @destroying = false
50
59
  end
51
60
 
52
- def on_destroy(&block)
53
- # TODO look into a way to generalize this logic for multiple listeners
54
- @on_destroy_procs ||= []
55
- if block.nil?
56
- @on_destroy_procs
57
- else
58
- @on_destroy_procs << block
59
- block
60
- end
61
+ def destroying?
62
+ @destroying
61
63
  end
62
-
64
+
63
65
  def show
64
66
  super
65
67
  unless @shown_at_least_once
@@ -69,29 +71,29 @@ module Glimmer
69
71
  end
70
72
  end
71
73
 
72
- def can_handle_listener?(listener_name)
73
- listener_name == 'on_destroy' || super
74
- end
75
-
76
74
  def handle_listener(listener_name, &listener)
77
75
  case listener_name
78
- when 'on_destroy'
79
- on_destroy(&listener)
80
- else
81
- default_behavior_listener = nil
82
- if listener_name == 'on_closing'
83
- default_behavior_listener = Proc.new do
84
- return_value = listener.call(self)
85
- if return_value.is_a?(Numeric)
86
- return_value
87
- else
88
- destroy
89
- ::LibUI.quit
90
- 0
91
- end
76
+ when 'on_closing'
77
+ @on_closing_listeners ||= []
78
+ @on_closing_listeners << listener
79
+ @default_behavior_listener ||= Proc.new do
80
+ return_value = nil
81
+ @on_closing_listeners.each do |l|
82
+ return_value = l.call(self)
83
+ break if return_value.is_a?(Numeric)
84
+ end
85
+ if return_value.is_a?(Numeric)
86
+ return_value
87
+ else
88
+ destroy
89
+ ::LibUI.quit
90
+ 0
92
91
  end
92
+ end.tap do |default_behavior_listener|
93
+ super(listener_name, &default_behavior_listener)
93
94
  end
94
- super(listener_name, &(default_behavior_listener || listener))
95
+ else
96
+ super
95
97
  end
96
98
  end
97
99
 
@@ -168,11 +170,8 @@ module Glimmer
168
170
  @height = construction_args[2]
169
171
  @libui = ControlProxy.new_control(@keyword, construction_args)
170
172
  @libui.tap do
171
- handle_listener('on_closing') do
172
- destroy
173
- ::LibUI.quit
174
- 0
175
- end
173
+ # setup default on_closing listener if no on_closing listeners are setup
174
+ handle_listener('on_closing') {} if @on_closing_listeners.nil? || @on_closing_listeners.empty?
176
175
  end
177
176
  end
178
177
  end
@@ -169,31 +169,49 @@ module Glimmer
169
169
  def handle_listener(listener_name, &listener)
170
170
  safe_listener = Proc.new { listener.call(self) }
171
171
  if ::LibUI.respond_to?("#{libui_api_keyword}_#{listener_name}")
172
- ::LibUI.send("#{libui_api_keyword}_#{listener_name}", @libui, &safe_listener)
172
+ if listeners[listener_name].nil?
173
+ ::LibUI.send("#{libui_api_keyword}_#{listener_name}", @libui) do
174
+ listeners_for(listener_name).map { |listener| listener.call }.last
175
+ end
176
+ end
177
+ listeners_for(listener_name) << safe_listener
173
178
  elsif ::LibUI.respond_to?("control_#{listener_name}")
174
- ::LibUI.send("control_#{listener_name}", @libui, &safe_listener)
179
+ if listeners[listener_name].nil?
180
+ ::LibUI.send("control_#{listener_name}", @libui) do
181
+ listeners_for(listener_name).map { |listener| listener.call }.last
182
+ end
183
+ end
184
+ listeners_for(listener_name) << safe_listener
175
185
  elsif has_custom_listener?(listener_name)
176
186
  handle_custom_listener(listener_name, &listener)
177
187
  end
178
188
  end
179
189
 
190
+ def listeners
191
+ @listeners ||= {}
192
+ end
193
+
194
+ def listeners_for(listener_name)
195
+ listeners[listener_name] ||= []
196
+ end
197
+
180
198
  def has_custom_listener?(listener_name)
181
199
  listener_name = listener_name.to_s
182
- custom_listeners.include?(listener_name) || custom_listener_aliases.stringify_keys.keys.include?(listener_name)
200
+ custom_listener_names.include?(listener_name) || custom_listener_name_aliases.stringify_keys.keys.include?(listener_name)
183
201
  end
184
202
 
185
- def custom_listeners
186
- self.class.constants.include?(:LISTENERS) ? self.class::LISTENERS : []
203
+ def custom_listener_names
204
+ self.class.constants.include?(:CUSTOM_LISTENER_NAMES) ? self.class::CUSTOM_LISTENER_NAMES : []
187
205
  end
188
206
 
189
- def custom_listener_aliases
190
- self.class.constants.include?(:LISTENER_ALIASES) ? self.class::LISTENER_ALIASES : {}
207
+ def custom_listener_name_aliases
208
+ self.class.constants.include?(:CUSTOM_LISTENER_NAME_ALIASES) ? self.class::CUSTOM_LISTENER_NAME_ALIASES : {}
191
209
  end
192
210
 
193
211
  def handle_custom_listener(listener_name, &listener)
194
212
  listener_name = listener_name.to_s
195
- listener_name = custom_listener_aliases.stringify_keys[listener_name] || listener_name
196
- instance_variable_name = "@#{listener_name}_procs"
213
+ listener_name = custom_listener_name_aliases.stringify_keys[listener_name] || listener_name
214
+ instance_variable_name = "@#{listener_name}_procs" # TODO ensure clearing custom listeners on destroy of a control
197
215
  instance_variable_set(instance_variable_name, []) if instance_variable_get(instance_variable_name).nil?
198
216
  if listener.nil?
199
217
  instance_variable_get(instance_variable_name)
@@ -203,6 +221,21 @@ module Glimmer
203
221
  end
204
222
  end
205
223
 
224
+ def notify_custom_listeners(listener_name, *args)
225
+ handle_custom_listener(listener_name).each do |listener|
226
+ listener.call(*args)
227
+ end
228
+ end
229
+
230
+ def deregister_custom_listeners(listener_name)
231
+ handle_custom_listener(listener_name).clear
232
+ end
233
+
234
+ # deregisters all custom listeners except on_destroy, which can only be deregistered after destruction of a control, using deregister_custom_listeners
235
+ def deregister_all_custom_listeners
236
+ (custom_listener_names - ['on_destroy']).each { |listener_name| deregister_custom_listeners(listener_name) }
237
+ end
238
+
206
239
  def respond_to?(method_name, *args, &block)
207
240
  respond_to_libui?(method_name, *args, &block) ||
208
241
  (
@@ -289,6 +322,8 @@ module Glimmer
289
322
  end
290
323
 
291
324
  def destroy
325
+ # TODO exclude menus from this initial return
326
+ return if !is_a?(ControlProxy::WindowProxy) && ControlProxy.main_window_proxy&.destroying?
292
327
  data_binding_model_attribute_observer_registrations.each(&:deregister)
293
328
  if parent_proxy.nil?
294
329
  default_destroy
@@ -302,6 +337,7 @@ module Glimmer
302
337
  end
303
338
 
304
339
  def default_destroy
340
+ deregister_all_custom_listeners
305
341
  send_to_libui('destroy')
306
342
  ControlProxy.control_proxies.delete(self)
307
343
  end
@@ -46,7 +46,7 @@ module Glimmer
46
46
  new_value = model_binding.evaluate_property
47
47
  send("#{property}=", new_value) unless send(property) == new_value
48
48
  end
49
- observer_registration = model_attribute_observer.observe(model_binding)
49
+ observer_registration = model_attribute_observer.observe(model_binding, attribute_writer_type: [:attribute=, :set_attribute])
50
50
  model_attribute_observer.call # initial update
51
51
  data_binding_model_attribute_observer_registrations << observer_registration
52
52
  observer_registration
@@ -116,6 +116,7 @@ module Glimmer
116
116
  end
117
117
 
118
118
  def destroy
119
+ return if ControlProxy.main_window_proxy&.destroying?
119
120
  @parent.children.delete(self)
120
121
  end
121
122
 
data/lib/glimmer/libui.rb CHANGED
@@ -23,6 +23,7 @@ require 'glimmer/fiddle_consumer'
23
23
 
24
24
  module Glimmer
25
25
  module LibUI
26
+ ICON = File.expand_path('../../icons/blank.png', __dir__)
26
27
  class << self
27
28
  include Glimmer::FiddleConsumer
28
29
 
@@ -28,6 +28,7 @@ require 'glimmer'
28
28
  # require 'super_module'
29
29
  require 'color'
30
30
  require 'os'
31
+ require 'equalizer'
31
32
  require 'array_include_methods'
32
33
  require 'facets/hash/stringify_keys'
33
34
  require 'facets/string/underscore'
@@ -46,3 +47,8 @@ Glimmer::Config.excluded_keyword_checkers << lambda do |method_symbol, *args|
46
47
  end
47
48
 
48
49
  ::LibUI.init
50
+ # begin
51
+ # PutsDebuggerer.printer = lambda { |m| puts m; $stdout.flush}
52
+ # rescue
53
+ ##### No Op if puts_debuggerer is not loaded
54
+ # end