glimmer-dsl-libui 0.4.14 → 0.4.18

Sign up to get free protection for your applications and to get access to all the features.
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