glimmer-dsl-libui 0.4.12 → 0.4.16

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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -0
  3. data/README.md +111 -48
  4. data/VERSION +1 -1
  5. data/examples/basic_image.rb +5 -3
  6. data/examples/basic_image2.rb +1 -3
  7. data/examples/basic_image3.rb +3 -3
  8. data/examples/basic_image4.rb +0 -3
  9. data/examples/basic_image5.rb +0 -2
  10. data/examples/basic_table_button.rb +0 -1
  11. data/examples/cpu_percentage.rb +1 -1
  12. data/examples/editable_column_table.rb +5 -0
  13. data/examples/form_table.rb +10 -4
  14. data/examples/form_table2.rb +12 -6
  15. data/examples/form_table3.rb +9 -3
  16. data/examples/form_table4.rb +9 -3
  17. data/examples/form_table5.rb +9 -3
  18. data/examples/meta_example.rb +3 -1
  19. data/glimmer-dsl-libui.gemspec +0 -0
  20. data/icons/blank.png +0 -0
  21. data/lib/glimmer/libui/attributed_string.rb +17 -8
  22. data/lib/glimmer/libui/control_proxy/area_proxy.rb +17 -17
  23. data/lib/glimmer/libui/control_proxy/box.rb +1 -0
  24. data/lib/glimmer/libui/control_proxy/column/button_column_proxy.rb +1 -29
  25. data/lib/glimmer/libui/control_proxy/form_proxy.rb +1 -0
  26. data/lib/glimmer/libui/control_proxy/image_proxy.rb +92 -10
  27. data/lib/glimmer/libui/control_proxy/menu_item_proxy/quit_menu_item_proxy.rb +18 -8
  28. data/lib/glimmer/libui/control_proxy/open_type_features_proxy.rb +11 -2
  29. data/lib/glimmer/libui/control_proxy/open_type_tag_proxy.rb +2 -0
  30. data/lib/glimmer/libui/control_proxy/path_proxy.rb +2 -0
  31. data/lib/glimmer/libui/control_proxy/table_proxy.rb +13 -11
  32. data/lib/glimmer/libui/control_proxy/text_proxy.rb +2 -0
  33. data/lib/glimmer/libui/control_proxy/window_proxy.rb +34 -35
  34. data/lib/glimmer/libui/control_proxy.rb +45 -9
  35. data/lib/glimmer/libui/shape.rb +1 -0
  36. data/lib/glimmer/libui.rb +1 -0
  37. metadata +5 -4
@@ -18,7 +18,7 @@ class FormTable
18
18
  end
19
19
 
20
20
  def launch
21
- window('Contacts', 600, 600) { |w|
21
+ window('Contacts', 600, 600) {
22
22
  margined true
23
23
 
24
24
  vertical_box {
@@ -56,8 +56,8 @@ class FormTable
56
56
 
57
57
  on_clicked do
58
58
  new_row = [name, email, phone, city, state]
59
- if new_row.include?('')
60
- msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
59
+ if new_row.map(&:to_s).include?('')
60
+ msg_box_error('Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
61
61
  else
62
62
  @contacts << Contact.new(*new_row) # automatically inserts a row into the table due to explicit data-binding
63
63
  @unfiltered_contacts = @contacts.dup
@@ -98,10 +98,16 @@ class FormTable
98
98
  text_column('State')
99
99
 
100
100
  editable true
101
- cell_rows <=> [self, :contacts] # explicit data-binding to Model Array
101
+ cell_rows <=> [self, :contacts] # explicit data-binding to self.contacts Model Array, auto-inferring model attribute names from underscored table column names by convention
102
102
 
103
103
  on_changed do |row, type, row_data|
104
104
  puts "Row #{row} #{type}: #{row_data}"
105
+ $stdout.flush # for Windows
106
+ end
107
+
108
+ on_edited do |row, row_data| # only fires on direct table editing
109
+ puts "Row #{row} edited: #{row_data}"
110
+ $stdout.flush # for Windows
105
111
  end
106
112
  }
107
113
  }
@@ -1,7 +1,7 @@
1
1
  require 'glimmer-dsl-libui'
2
2
 
3
3
  class FormTable
4
- Contact = Struct.new(:name, :email, :phone, :city, :state)
4
+ Contact = Struct.new(:name, :email, :phone, :city, :state_province)
5
5
 
6
6
  include Glimmer
7
7
 
@@ -18,7 +18,7 @@ class FormTable
18
18
  end
19
19
 
20
20
  def launch
21
- window('Contacts', 600, 600) { |w|
21
+ window('Contacts', 600, 600) {
22
22
  margined true
23
23
 
24
24
  vertical_box {
@@ -56,8 +56,8 @@ class FormTable
56
56
 
57
57
  on_clicked do
58
58
  new_row = [name, email, phone, city, state]
59
- if new_row.include?('')
60
- msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
59
+ if new_row.map(&:to_s).include?('')
60
+ msg_box_error('Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
61
61
  else
62
62
  @contacts << Contact.new(*new_row) # automatically inserts a row into the table due to implicit data-binding
63
63
  @unfiltered_contacts = @contacts.dup
@@ -95,13 +95,19 @@ class FormTable
95
95
  text_column('Email')
96
96
  text_column('Phone')
97
97
  text_column('City')
98
- text_column('State/Province')
98
+ text_column('State')
99
99
 
100
100
  editable true
101
- cell_rows <=> [self, :contacts, column_attributes: {'State/Province' => :state}] # explicit data-binding to Model Array with column_attributes mapping for a specific column
101
+ cell_rows <=> [self, :contacts, column_attributes: {'State' => :state_province}] # explicit data-binding to Model Array with column_attributes mapping for a specific column
102
102
 
103
103
  on_changed do |row, type, row_data|
104
104
  puts "Row #{row} #{type}: #{row_data}"
105
+ $stdout.flush # for Windows
106
+ end
107
+
108
+ on_edited do |row, row_data| # only fires on direct table editing
109
+ puts "Row #{row} edited: #{row_data}"
110
+ $stdout.flush # for Windows
105
111
  end
106
112
  }
107
113
  }
@@ -19,7 +19,7 @@ class FormTable
19
19
  end
20
20
 
21
21
  def launch
22
- window('Contacts', 600, 600) { |w|
22
+ window('Contacts', 600, 600) {
23
23
  margined true
24
24
 
25
25
  vertical_box {
@@ -57,8 +57,8 @@ class FormTable
57
57
 
58
58
  on_clicked do
59
59
  new_row = [name, email, phone, city, state]
60
- if new_row.include?('')
61
- msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
60
+ if new_row.map(&:to_s).include?('')
61
+ msg_box_error('Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
62
62
  else
63
63
  @contacts << Contact.new(*new_row) # automatically inserts a row into the table due to implicit data-binding
64
64
  @unfiltered_contacts = @contacts.dup
@@ -103,6 +103,12 @@ class FormTable
103
103
 
104
104
  on_changed do |row, type, row_data|
105
105
  puts "Row #{row} #{type}: #{row_data}"
106
+ $stdout.flush # for Windows
107
+ end
108
+
109
+ on_edited do |row, row_data| # only fires on direct table editing
110
+ puts "Row #{row} edited: #{row_data}"
111
+ $stdout.flush # for Windows
106
112
  end
107
113
  }
108
114
  }
@@ -16,7 +16,7 @@ class FormTable
16
16
  end
17
17
 
18
18
  def launch
19
- window('Contacts', 600, 600) { |w|
19
+ window('Contacts', 600, 600) {
20
20
  margined true
21
21
 
22
22
  vertical_box {
@@ -54,8 +54,8 @@ class FormTable
54
54
 
55
55
  on_clicked do
56
56
  new_row = [name, email, phone, city, state]
57
- if new_row.include?('')
58
- msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
57
+ if new_row.map(&:to_s).include?('')
58
+ msg_box_error('Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
59
59
  else
60
60
  data << new_row # automatically inserts a row into the table due to implicit data-binding
61
61
  @unfiltered_data = data.dup
@@ -100,6 +100,12 @@ class FormTable
100
100
 
101
101
  on_changed do |row, type, row_data|
102
102
  puts "Row #{row} #{type}: #{row_data}"
103
+ $stdout.flush # for Windows
104
+ end
105
+
106
+ on_edited do |row, row_data| # only fires on direct table editing
107
+ puts "Row #{row} edited: #{row_data}"
108
+ $stdout.flush # for Windows
103
109
  end
104
110
  }
105
111
  }
@@ -10,7 +10,7 @@ data = [
10
10
  ['Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'],
11
11
  ]
12
12
 
13
- window('Contacts', 600, 600) { |w|
13
+ window('Contacts', 600, 600) {
14
14
  margined true
15
15
 
16
16
  vertical_box {
@@ -43,8 +43,8 @@ window('Contacts', 600, 600) { |w|
43
43
 
44
44
  on_clicked do
45
45
  new_row = [@name_entry.text, @email_entry.text, @phone_entry.text, @city_entry.text, @state_entry.text]
46
- if new_row.include?('')
47
- msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
46
+ if new_row.map(&:to_s).include?('')
47
+ msg_box_error('Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
48
48
  else
49
49
  data << new_row # automatically inserts a row into the table due to implicit data-binding
50
50
  @unfiltered_data = data.dup
@@ -88,6 +88,12 @@ window('Contacts', 600, 600) { |w|
88
88
 
89
89
  on_changed do |row, type, row_data|
90
90
  puts "Row #{row} #{type}: #{row_data}"
91
+ $stdout.flush # for Windows
92
+ end
93
+
94
+ on_edited do |row, row_data| # only fires on direct table editing
95
+ puts "Row #{row} edited: #{row_data}"
96
+ $stdout.flush # for Windows
91
97
  end
92
98
  }
93
99
  }
@@ -180,7 +180,9 @@ class MetaExample
180
180
  }
181
181
  button('Reset') {
182
182
  on_clicked do
183
- self.code_text = File.read(file_path_for(selected_example))
183
+ version_number = @version_spinbox.value == 1 ? '' : @version_spinbox.value
184
+ example = "#{selected_example}#{version_number}"
185
+ self.code_text = File.read(file_path_for(example))
184
186
  end
185
187
  }
186
188
  }
Binary file
data/icons/blank.png ADDED
Binary file
@@ -22,7 +22,6 @@
22
22
  require 'glimmer/libui/control_proxy'
23
23
  require 'glimmer/libui/control_proxy/area_proxy'
24
24
  require 'glimmer/libui/parent'
25
- require 'glimmer/libui/control_proxy/transformable'
26
25
  require 'glimmer/libui/data_bindable'
27
26
 
28
27
  module Glimmer
@@ -48,7 +47,7 @@ module Glimmer
48
47
  @string
49
48
  else
50
49
  @string = value
51
- redraw
50
+ request_auto_redraw
52
51
  end
53
52
  end
54
53
  alias string= string
@@ -59,7 +58,7 @@ module Glimmer
59
58
  @font
60
59
  else
61
60
  @font = value
62
- redraw
61
+ request_auto_redraw
63
62
  end
64
63
  end
65
64
  alias font= font
@@ -70,7 +69,7 @@ module Glimmer
70
69
  @color
71
70
  else
72
71
  @color = Glimmer::LibUI.interpret_color(value)
73
- redraw
72
+ request_auto_redraw
74
73
  end
75
74
  end
76
75
  alias color= color
@@ -81,7 +80,7 @@ module Glimmer
81
80
  @background
82
81
  else
83
82
  @background = Glimmer::LibUI.interpret_color(value)
84
- redraw
83
+ request_auto_redraw
85
84
  end
86
85
  end
87
86
  alias background= background
@@ -92,7 +91,7 @@ module Glimmer
92
91
  @underline
93
92
  else
94
93
  @underline = value
95
- redraw
94
+ request_auto_redraw
96
95
  end
97
96
  end
98
97
  alias underline= underline
@@ -103,7 +102,7 @@ module Glimmer
103
102
  @underline_color
104
103
  else
105
104
  @underline_color = value
106
- redraw
105
+ request_auto_redraw
107
106
  end
108
107
  end
109
108
  alias underline_color= underline_color
@@ -114,12 +113,21 @@ module Glimmer
114
113
  @open_type_features
115
114
  else
116
115
  @open_type_features = value
117
- redraw
116
+ request_auto_redraw
118
117
  end
119
118
  end
120
119
  alias open_type_features= open_type_features
121
120
  alias set_open_type_features open_type_features
122
121
 
122
+ def remove_open_type_features
123
+ return if @removing_open_type_features
124
+ @removing_open_type_features = true
125
+ @open_type_features&.destroy
126
+ @open_type_features = nil
127
+ request_auto_redraw
128
+ @removing_open_type_features = false
129
+ end
130
+
123
131
  def post_add_content(block = nil)
124
132
  block ||= @block
125
133
  block_result = block&.call
@@ -189,6 +197,7 @@ module Glimmer
189
197
  end
190
198
 
191
199
  def destroy
200
+ return if ControlProxy.main_window_proxy&.destroying?
192
201
  open_type_features.destroy unless open_type_features.nil?
193
202
  @parent_proxy&.children&.delete(self)
194
203
  end
@@ -36,9 +36,9 @@ module Glimmer
36
36
  attr_accessor :current_area_draw_params
37
37
  end
38
38
 
39
- LISTENERS = ['on_draw', 'on_mouse_event', 'on_mouse_move', 'on_mouse_down', 'on_mouse_up', 'on_mouse_drag_start', 'on_mouse_drag', 'on_mouse_drop', 'on_mouse_crossed', 'on_mouse_enter', 'on_mouse_exit', 'on_drag_broken', 'on_key_event', 'on_key_down', 'on_key_up']
39
+ CUSTOM_LISTENER_NAMES = ['on_draw', 'on_mouse_event', 'on_mouse_move', 'on_mouse_down', 'on_mouse_up', 'on_mouse_drag_start', 'on_mouse_drag', 'on_mouse_drop', 'on_mouse_crossed', 'on_mouse_enter', 'on_mouse_exit', 'on_drag_broken', 'on_key_event', 'on_key_down', 'on_key_up']
40
40
 
41
- LISTENER_ALIASES = {
41
+ CUSTOM_LISTENER_NAME_ALIASES = {
42
42
  on_drawn: 'on_draw',
43
43
  on_mouse_moved: 'on_mouse_move',
44
44
  on_mouse_drag_started: 'on_mouse_drag_start',
@@ -105,7 +105,7 @@ module Glimmer
105
105
 
106
106
  def draw(area_draw_params)
107
107
  children.dup.each {|child| child.draw(area_draw_params)}
108
- on_draw.each {|listener| listener.call(area_draw_params)}
108
+ notify_custom_listeners('on_draw', area_draw_params)
109
109
  end
110
110
 
111
111
  def redraw
@@ -156,37 +156,37 @@ module Glimmer
156
156
  @area_handler.MouseEvent = fiddle_closure_block_caller(0, [1, 1, 1]) do |_, _, area_mouse_event|
157
157
  area_mouse_event = ::LibUI::FFI::AreaMouseEvent.new(area_mouse_event)
158
158
  area_mouse_event = area_mouse_event_hash(area_mouse_event)
159
- on_mouse_event.each { |listener| listener.call(area_mouse_event)}
160
- on_mouse_move.each { |listener| listener.call(area_mouse_event)} if area_mouse_event[:x].between?(0, area_mouse_event[:area_width]) && area_mouse_event[:y].between?(0, area_mouse_event[:area_height])
159
+ notify_custom_listeners('on_mouse_event', area_mouse_event)
160
+ notify_custom_listeners('on_mouse_move', area_mouse_event) if area_mouse_event[:x].between?(0, area_mouse_event[:area_width]) && area_mouse_event[:y].between?(0, area_mouse_event[:area_height])
161
161
  unless @last_area_mouse_event.nil?
162
- on_mouse_down.each { |listener| listener.call(area_mouse_event)} if area_mouse_event[:down] > 0 && @last_area_mouse_event[:down] == 0
163
- on_mouse_up.each { |listener| listener.call(area_mouse_event)} if area_mouse_event[:up] > 0 && @last_area_mouse_event[:up] == 0
164
- on_mouse_drag_start.each { |listener| listener.call(area_mouse_event)} if area_mouse_event[:held] > 0 && @last_area_mouse_event[:held] == 0
165
- on_mouse_drag.each { |listener| listener.call(area_mouse_event)} if area_mouse_event[:held] > 0
166
- on_mouse_drop.each { |listener| listener.call(area_mouse_event)} if area_mouse_event[:held] == 0 && @last_area_mouse_event[:held] > 0
162
+ notify_custom_listeners('on_mouse_down', area_mouse_event) if area_mouse_event[:down] > 0 && @last_area_mouse_event[:down] == 0
163
+ notify_custom_listeners('on_mouse_up', area_mouse_event) if area_mouse_event[:up] > 0 && @last_area_mouse_event[:up] == 0
164
+ notify_custom_listeners('on_mouse_drag_start', area_mouse_event) if area_mouse_event[:held] > 0 && @last_area_mouse_event[:held] == 0
165
+ notify_custom_listeners('on_mouse_drag', area_mouse_event) if area_mouse_event[:held] > 0
166
+ notify_custom_listeners('on_mouse_drop', area_mouse_event) if area_mouse_event[:held] == 0 && @last_area_mouse_event[:held] > 0
167
167
  end
168
168
  @last_area_mouse_event = area_mouse_event
169
169
  end
170
170
  @area_handler.MouseCrossed = fiddle_closure_block_caller(0, [1, 1, 4]) do |_, _, left|
171
171
  left = Glimmer::LibUI.integer_to_boolean(left)
172
- on_mouse_crossed.each { |listener| listener.call(left) }
172
+ notify_custom_listeners('on_mouse_crossed', left)
173
173
  if left
174
- on_mouse_exit.each { |listener| listener.call(left) }
174
+ notify_custom_listeners('on_mouse_exit', left)
175
175
  else
176
- on_mouse_enter.each { |listener| listener.call(left) }
176
+ notify_custom_listeners('on_mouse_enter', left)
177
177
  end
178
178
  end
179
179
  @area_handler.DragBroken = fiddle_closure_block_caller(0, [1, 1]) do |_, _|
180
- on_drag_broken.each { |listener| listener.call }
180
+ notify_custom_listeners('on_drag_broken')
181
181
  end
182
182
  @area_handler.KeyEvent = fiddle_closure_block_caller(0, [1, 1, 1]) do |_, _, area_key_event|
183
183
  area_key_event = ::LibUI::FFI::AreaKeyEvent.new(area_key_event)
184
184
  area_key_event = area_key_event_hash(area_key_event)
185
- on_key_event.each { |listener| listener.call(area_key_event) }
185
+ notify_custom_listeners('on_key_event', area_key_event)
186
186
  if area_key_event[:up]
187
- on_key_up.each { |listener| listener.call(area_key_event) }
187
+ notify_custom_listeners('on_key_up', area_key_event)
188
188
  else
189
- on_key_down.each { |listener| listener.call(area_key_event) }
189
+ notify_custom_listeners('on_key_down', area_key_event)
190
190
  end
191
191
  end
192
192
  @listeners_installed = true
@@ -42,6 +42,7 @@ module Glimmer
42
42
  end
43
43
 
44
44
  def destroy_child(child)
45
+ child.deregister_all_custom_listeners
45
46
  ::LibUI.send("box_delete", @libui, children.index(child))
46
47
  ControlProxy.control_proxies.delete(child)
47
48
  end
@@ -34,36 +34,8 @@ module Glimmer
34
34
  include Column
35
35
  include EnableableColumn
36
36
 
37
- def on_clicked(&block)
38
- # TODO consider generalizing into custom listeners and moving to ControlProxy
39
- @on_clicked_procs ||= []
40
- if block.nil?
41
- @on_clicked_procs
42
- else
43
- @on_clicked_procs << block
44
- block
45
- end
46
- end
47
-
48
- def can_handle_listener?(listener_name)
49
- listener_name == 'on_clicked' || super
50
- end
37
+ CUSTOM_LISTENER_NAMES = ['on_clicked']
51
38
 
52
- def handle_listener(listener_name, &listener)
53
- case listener_name
54
- when 'on_clicked'
55
- on_clicked(&listener)
56
- else
57
- super
58
- end
59
- end
60
-
61
- def notify_listeners(listener_name, *args)
62
- @on_clicked_procs&.each do |on_clicked_proc|
63
- on_clicked_proc.call(*args)
64
- end
65
- end
66
-
67
39
  private
68
40
 
69
41
  def build_control
@@ -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
@@ -34,14 +34,31 @@ module Glimmer
34
34
  #
35
35
  # Follows the Proxy Design Pattern
36
36
  class ImageProxy < ControlProxy
37
+ class << self
38
+ # creates or returns existing instance for passed in arguments if parent is nil and block is nil
39
+ def create(keyword, parent, args, &block)
40
+ if parent.nil? && block.nil?
41
+ instances[args] ||= new(keyword, parent, args.dup, &block)
42
+ else
43
+ new(keyword, parent, args, &block)
44
+ end
45
+ end
46
+
47
+ def instances
48
+ @@instances = {} unless defined? @@instances
49
+ @@instances
50
+ end
51
+ end
52
+
37
53
  include Parent
38
54
  prepend Transformable
39
55
 
40
- attr_reader :data, :pixels, :shapes
56
+ attr_reader :data, :pixels, :shapes, :options
41
57
 
42
58
  def initialize(keyword, parent, args, &block)
43
59
  @keyword = keyword
44
60
  @parent_proxy = parent
61
+ @options = args.last.is_a?(Hash) ? args.pop : {}
45
62
  @args = args
46
63
  @block = block
47
64
  @enabled = true
@@ -76,11 +93,47 @@ module Glimmer
76
93
  alias file= file
77
94
  alias set_file file
78
95
 
96
+ def x(value = nil)
97
+ if value.nil?
98
+ @args.size > 3 ? @args[1] : (@options[:x] || 0)
99
+ else
100
+ if @args.size > 3
101
+ @args[1] = value
102
+ else
103
+ @options[:x] = value
104
+ end
105
+ if area_image? && @content_added
106
+ post_add_content
107
+ request_auto_redraw
108
+ end
109
+ end
110
+ end
111
+ alias x= x
112
+ alias set_x x
113
+
114
+ def y(value = nil)
115
+ if value.nil?
116
+ @args.size > 3 ? @args[2] : (@options[:y] || 0)
117
+ else
118
+ if @args.size > 3
119
+ @args[2] = value
120
+ else
121
+ @options[:y] = value
122
+ end
123
+ if area_image? && @content_added
124
+ post_add_content
125
+ request_auto_redraw
126
+ end
127
+ end
128
+ end
129
+ alias y= y
130
+ alias set_y y
131
+
79
132
  def width(value = nil)
80
133
  if value.nil?
81
- @args[1]
134
+ @args.size > 3 ? @args[3] : (@options[:width] || @args[1])
82
135
  else
83
- @args[1] = value
136
+ set_width_variable(value)
84
137
  if area_image? && @content_added
85
138
  post_add_content
86
139
  request_auto_redraw
@@ -92,9 +145,9 @@ module Glimmer
92
145
 
93
146
  def height(value = nil)
94
147
  if value.nil?
95
- @args[2]
148
+ @args.size > 3 ? @args[4] : (@options[:height] || @args[2])
96
149
  else
97
- @args[2] = value
150
+ set_height_variable(value)
98
151
  if area_image? && @content_added
99
152
  post_add_content
100
153
  request_auto_redraw
@@ -126,12 +179,34 @@ module Glimmer
126
179
  end
127
180
 
128
181
  def destroy
182
+ return if ControlProxy.main_window_proxy&.destroying?
183
+ deregister_all_custom_listeners
129
184
  @parent_proxy&.children&.delete(self)
130
185
  ControlProxy.control_proxies.delete(self)
131
186
  end
132
187
 
133
188
  private
134
189
 
190
+ def set_width_variable(value)
191
+ if @args.size > 3
192
+ @args[3] = value
193
+ elsif @options[:width]
194
+ @options[:width] = value
195
+ else
196
+ @args[1] = value
197
+ end
198
+ end
199
+
200
+ def set_height_variable(value)
201
+ if @args.size > 3
202
+ @args[4] = value
203
+ elsif @options[:height]
204
+ @options[:height] = value
205
+ else
206
+ @args[2] = value
207
+ end
208
+ end
209
+
135
210
  def build_control
136
211
  unless area_image? # image object
137
212
  if file
@@ -164,10 +239,15 @@ module Glimmer
164
239
  canvas = ChunkyPNG::Canvas.from_io(f)
165
240
  f.close
166
241
  end
167
- canvas.resample_nearest_neighbor!(width, height) if width && height
242
+ original_width = canvas.width
243
+ original_height = canvas.height
244
+ require 'bigdecimal'
245
+ calculated_width = ((BigDecimal(height)/BigDecimal(original_height))*original_width).to_i if height && !width
246
+ calculated_height = ((BigDecimal(width)/BigDecimal(original_width))*original_height).to_i if width && !height
247
+ canvas.resample_nearest_neighbor!(calculated_width || width, calculated_height || height) if width || height
168
248
  @data = canvas.to_rgba_stream
169
- @args[1] = canvas.width
170
- @args[2] = canvas.height
249
+ set_width_variable(canvas.width) unless width
250
+ set_height_variable(canvas.height) unless height
171
251
  [@data, width, height]
172
252
  end
173
253
 
@@ -187,6 +267,8 @@ module Glimmer
187
267
  @shapes = []
188
268
  original_pixels = @pixels.dup
189
269
  indexed_original_pixels = Hash[original_pixels.each_with_index.to_a]
270
+ x_offset = x
271
+ y_offset = y
190
272
  @pixels.each do |pixel|
191
273
  index = indexed_original_pixels[pixel]
192
274
  @rectangle_start_x ||= pixel[:x]
@@ -195,9 +277,9 @@ module Glimmer
195
277
  @rectangle_width += 1
196
278
  else
197
279
  if pixel[:x] > 0 && pixel[:color] == original_pixels[index - 1][:color]
198
- @shapes << {x: @rectangle_start_x, y: pixel[:y], width: @rectangle_width, height: 1, color: pixel[:color]}
280
+ @shapes << {x: x_offset + @rectangle_start_x, y: y_offset + pixel[:y], width: @rectangle_width, height: 1, color: pixel[:color]}
199
281
  else
200
- @shapes << {x: pixel[:x], y: pixel[:y], width: 1, height: 1, color: pixel[:color]}
282
+ @shapes << {x: x_offset + pixel[:x], y: y_offset + pixel[:y], width: 1, height: 1, color: pixel[:color]}
201
283
  end
202
284
  @rectangle_width = 1
203
285
  @rectangle_start_x = pixel[:x] == width - 1 ? 0 : pixel[:x] + 1
@@ -35,29 +35,39 @@ 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
49
  destroy
50
+ ControlProxy.main_window_proxy&.destroy
44
51
  ::LibUI.quit
45
52
  0
46
53
  end
54
+ end.tap do |default_behavior_listener|
55
+ ::LibUI.on_should_quit(&default_behavior_listener)
47
56
  end
48
- ::LibUI.on_should_quit(&@default_behavior_listener)
49
57
  end
50
58
  end
59
+
60
+ def destroy
61
+ @on_clicked_listeners&.clear
62
+ super
63
+ end
51
64
 
52
65
  private
53
66
 
54
67
  def build_control
55
68
  @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
69
+ # setup default on_clicked listener if no on_clicked listeners are setup
70
+ handle_listener('on_clicked') {} if @on_clicked_listeners.nil? || @on_clicked_listeners.empty?
61
71
  end
62
72
  end
63
73
  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