glimmer-dsl-swt 4.18.2.5 → 4.18.3.4

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +56 -0
  3. data/README.md +344 -35
  4. data/VERSION +1 -1
  5. data/glimmer-dsl-swt.gemspec +13 -7
  6. data/lib/ext/glimmer/config.rb +24 -7
  7. data/lib/glimmer/data_binding/table_items_binding.rb +8 -5
  8. data/lib/glimmer/data_binding/widget_binding.rb +22 -4
  9. data/lib/glimmer/dsl/swt/dsl.rb +1 -0
  10. data/lib/glimmer/dsl/swt/image_expression.rb +14 -6
  11. data/lib/glimmer/dsl/swt/layout_data_expression.rb +4 -4
  12. data/lib/glimmer/dsl/swt/layout_expression.rb +5 -3
  13. data/lib/glimmer/dsl/swt/multiply_expression.rb +53 -0
  14. data/lib/glimmer/dsl/swt/property_expression.rb +4 -2
  15. data/{samples/elaborate/tetris/view/game_over_dialog.rb → lib/glimmer/dsl/swt/transform_expression.rb} +29 -46
  16. data/lib/glimmer/swt/custom/drawable.rb +4 -3
  17. data/lib/glimmer/swt/custom/shape.rb +37 -14
  18. data/lib/glimmer/swt/directory_dialog_proxy.rb +3 -3
  19. data/lib/glimmer/swt/display_proxy.rb +26 -5
  20. data/lib/glimmer/swt/file_dialog_proxy.rb +3 -3
  21. data/lib/glimmer/swt/image_proxy.rb +68 -1
  22. data/lib/glimmer/swt/shell_proxy.rb +23 -3
  23. data/lib/glimmer/swt/table_proxy.rb +31 -7
  24. data/lib/glimmer/swt/transform_proxy.rb +109 -0
  25. data/lib/glimmer/swt/widget_listener_proxy.rb +14 -5
  26. data/lib/glimmer/swt/widget_proxy.rb +7 -3
  27. data/lib/glimmer/ui/custom_shell.rb +11 -9
  28. data/lib/glimmer/ui/custom_widget.rb +32 -17
  29. data/samples/elaborate/meta_sample.rb +81 -24
  30. data/samples/elaborate/tetris.rb +146 -42
  31. data/samples/elaborate/tetris/model/game.rb +258 -137
  32. data/samples/elaborate/tetris/model/past_game.rb +26 -0
  33. data/samples/elaborate/tetris/model/tetromino.rb +45 -29
  34. data/samples/elaborate/tetris/view/block.rb +8 -13
  35. data/samples/elaborate/tetris/view/high_score_dialog.rb +133 -0
  36. data/samples/elaborate/tetris/view/playfield.rb +1 -1
  37. data/samples/elaborate/tetris/view/score_lane.rb +11 -11
  38. data/samples/elaborate/tetris/view/tetris_menu_bar.rb +139 -0
  39. data/samples/elaborate/tic_tac_toe.rb +4 -4
  40. data/samples/hello/hello_canvas.rb +4 -4
  41. data/samples/hello/hello_canvas_animation.rb +3 -3
  42. data/samples/hello/hello_canvas_transform.rb +40 -0
  43. data/samples/hello/hello_link.rb +1 -1
  44. metadata +11 -5
@@ -0,0 +1,109 @@
1
+ # Copyright (c) 2007-2021 Andy Maleh
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ require 'glimmer/swt/display_proxy'
23
+ require 'glimmer/swt/properties'
24
+ require 'glimmer/swt/custom/shape'
25
+
26
+ module Glimmer
27
+ module SWT
28
+ # Proxy for org.eclipse.swt.graphics.Transform
29
+ #
30
+ # Follows the Proxy Design Pattern
31
+ class TransformProxy
32
+ include Properties
33
+
34
+ include_package 'org.eclipse.swt.graphics'
35
+ include_package 'org.eclipse.swt.widgets'
36
+
37
+ attr_reader :swt_transform, :parent
38
+
39
+ def initialize(parent, *args, swt_transform: nil, multiply: false)
40
+ @parent = parent
41
+ @multiply = multiply
42
+ if swt_transform.nil?
43
+ if !args.first.is_a?(Display) && !args.first.is_a?(DisplayProxy)
44
+ args.prepend DisplayProxy.instance.swt_display
45
+ end
46
+ if args.first.is_a?(DisplayProxy)
47
+ args[0] = args[0].swt_display
48
+ end
49
+ if args.last.is_a?(TransformProxy)
50
+ args[-1] = args[-1].swt_transform
51
+ end
52
+ if args.last.nil? || args.last.is_a?(Transform)
53
+ @swt_transform = args.last
54
+ @parent&.set_attribute('transform', self)
55
+ else
56
+ @swt_transform = Transform.new(*args)
57
+ end
58
+ else
59
+ @swt_transform = swt_transform
60
+ end
61
+ end
62
+
63
+ def post_add_content
64
+ if @multiply
65
+ @parent.multiply(@swt_transform)
66
+ else
67
+ @parent&.set_attribute('transform', self)
68
+ end
69
+ end
70
+
71
+ def content(&block)
72
+ Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::SWT::TransformExpression.new, &block)
73
+ end
74
+
75
+ def has_attribute?(attribute_name, *args)
76
+ @swt_transform.respond_to?(attribute_name) || @swt_transform.respond_to?(attribute_setter(attribute_name))
77
+ end
78
+
79
+ def set_attribute(attribute_name, *args)
80
+ if @swt_transform.respond_to?(attribute_name)
81
+ @swt_transform.send(attribute_name, *args)
82
+ elsif @swt_transform.respond_to?(attribute_setter(attribute_name))
83
+ @swt_transform.send(attribute_setter(attribute_name), *args)
84
+ end
85
+ end
86
+
87
+ def get_attribute(attribute_name)
88
+ if @swt_transform.respond_to?(attribute_getter(attribute_name))
89
+ @swt_transform.send(attribute_getter(attribute_name))
90
+ else
91
+ @swt_transform.send(attribute_name)
92
+ end
93
+ end
94
+
95
+ def method_missing(method_name, *args, &block)
96
+ result = @swt_transform.send(method_name, *args, &block)
97
+ result.nil? ? self : result
98
+ rescue => e
99
+ Glimmer::Config.logger.debug {"Neither MessageBoxProxy nor #{@swt_transform.class.name} can handle the method ##{method}"}
100
+ super
101
+ end
102
+
103
+ def respond_to?(method, *args, &block)
104
+ super ||
105
+ @swt_transform.respond_to?(method, *args, &block)
106
+ end
107
+ end
108
+ end
109
+ end
@@ -26,10 +26,12 @@ module Glimmer
26
26
  # Follows the Proxy Design Pattern
27
27
  class WidgetListenerProxy
28
28
 
29
- attr_reader :swt_widget, :swt_listener, :widget_add_listener_method, :swt_listener_class, :swt_listener_method, :event_type, :swt_constant
29
+ attr_reader :swt_widget, :swt_display, :swt_listener, :widget_add_listener_method, :swt_listener_class, :swt_listener_method, :event_type, :swt_constant
30
30
 
31
- def initialize(swt_widget:, swt_listener:, widget_add_listener_method: nil, swt_listener_class: nil, swt_listener_method: nil, event_type: nil, swt_constant: nil)
31
+ def initialize(swt_widget:nil, swt_display:nil, swt_listener:, widget_add_listener_method: nil, swt_listener_class: nil, swt_listener_method: nil, event_type: nil, swt_constant: nil, filter: false)
32
32
  @swt_widget = swt_widget
33
+ @swt_display = swt_display
34
+ @filter = filter
33
35
  @swt_listener = swt_listener
34
36
  @widget_add_listener_method = widget_add_listener_method
35
37
  @swt_listener_class = swt_listener_class
@@ -42,14 +44,21 @@ module Glimmer
42
44
  @widget_add_listener_method.sub('add', 'remove')
43
45
  end
44
46
 
45
- def unregister
46
- # TODO consider renaming to deregister (and in Observer too)
47
- if @event_type
47
+ def deregister
48
+ return if @swt_widget&.is_disposed || @swt_display&.is_disposed
49
+ if @swt_display
50
+ if @filter
51
+ @swt_display.removeFilter(@event_type, @swt_listener)
52
+ else
53
+ @swt_display.removeListener(@event_type, @swt_listener)
54
+ end
55
+ elsif @event_type
48
56
  @swt_widget.removeListener(@event_type, @swt_listener)
49
57
  else
50
58
  @swt_widget.send(widget_remove_listener_method, @swt_listener)
51
59
  end
52
60
  end
61
+ alias unregister deregister # TODO consider dropping unregister (and in Observer too)
53
62
  end
54
63
  end
55
64
  end
@@ -77,7 +77,7 @@ module Glimmer
77
77
  composite.layout = GridLayout.new if composite.get_layout.nil?
78
78
  end,
79
79
  canvas: lambda do |canvas|
80
- canvas.layout = nil unless canvas.get_layout.nil?
80
+ canvas.layout = nil if canvas.respond_to?('layout=') && !canvas.get_layout.nil?
81
81
  end,
82
82
  scrolled_composite: lambda do |scrolled_composite|
83
83
  scrolled_composite.expand_horizontal = true
@@ -547,6 +547,10 @@ module Glimmer
547
547
  @swt_widget.dispose
548
548
  end
549
549
 
550
+ def disposed?
551
+ @swt_widget.isDisposed
552
+ end
553
+
550
554
  # TODO Consider renaming these methods as they are mainly used for data-binding
551
555
 
552
556
  def can_add_observer?(property_name)
@@ -663,7 +667,7 @@ module Glimmer
663
667
  can_handle_observation_request?(method) ||
664
668
  swt_widget.respond_to?(method, *args, &block)
665
669
  end
666
-
670
+
667
671
  private
668
672
 
669
673
  def style(underscored_widget_name, styles)
@@ -696,7 +700,7 @@ module Glimmer
696
700
  safe_block = lambda { |*args| block.call(*args) unless @swt_widget.isDisposed }
697
701
  listener = listener_class.new(listener_method => safe_block)
698
702
  @swt_widget.send(widget_add_listener_method, listener)
699
- widget_listener_proxy = WidgetListenerProxy.new(swt_widget: @swt_widget, swt_listener: listener, widget_add_listener_method: widget_add_listener_method, swt_listener_class: listener_class, swt_listener_method: listener_method)
703
+ WidgetListenerProxy.new(swt_widget: @swt_widget, swt_listener: listener, widget_add_listener_method: widget_add_listener_method, swt_listener_class: listener_class, swt_listener_method: listener_method)
700
704
  end
701
705
 
702
706
  # Looks through SWT class add***Listener methods till it finds one for which
@@ -28,15 +28,12 @@ module Glimmer
28
28
  include Glimmer::UI::CustomWidget
29
29
 
30
30
  class << self
31
- attr_reader :custom_shell
31
+ attr_reader :launched_custom_shell
32
32
 
33
- def launch
34
- @custom_shell = send(self.name.underscore.gsub('::', '__'))
35
- @custom_shell.open
36
- end
37
-
38
- def shutdown
39
- @custom_shell.close
33
+ def launch(*args, &content)
34
+ @launched_custom_shell = send(keyword, *args, &content) if @launched_custom_shell.nil? || @launched_custom_shell.disposed?
35
+ @launched_custom_shell.swt_widget.set_data('launched', true)
36
+ @launched_custom_shell.open
40
37
  end
41
38
  end
42
39
 
@@ -45,7 +42,7 @@ module Glimmer
45
42
  @swt_widget.set_data('custom_shell', self)
46
43
  raise Error, 'Invalid custom shell body root! Must be a shell or another custom shell.' unless body_root.swt_widget.is_a?(org.eclipse.swt.widgets.Shell)
47
44
  end
48
-
45
+
49
46
  # Classes may override
50
47
  def open
51
48
  body_root.open
@@ -56,6 +53,7 @@ module Glimmer
56
53
  open
57
54
  end
58
55
 
56
+ # TODO consider using Forwardable instead
59
57
  def close
60
58
  body_root.close
61
59
  end
@@ -68,6 +66,10 @@ module Glimmer
68
66
  body_root.visible?
69
67
  end
70
68
 
69
+ def disposed?
70
+ swt_widget.is_disposed
71
+ end
72
+
71
73
  def center_within_display
72
74
  body_root.center_within_display
73
75
  end
@@ -77,7 +77,17 @@ module Glimmer
77
77
  def flyweight_custom_widget_classes
78
78
  @flyweight_custom_widget_classes ||= {}
79
79
  end
80
-
80
+
81
+ # Returns keyword to use for this custom widget
82
+ def keyword
83
+ self.name.underscore.gsub('::', '__')
84
+ end
85
+
86
+ # Returns shortcut keyword to use for this custom widget (keyword minus namespace)
87
+ def shortcut_keyword
88
+ self.name.underscore.gsub('::', '__').split('__').last
89
+ end
90
+
81
91
  def add_custom_widget_namespaces_for(klass)
82
92
  Glimmer::UI::CustomWidget.namespaces_for_class(klass).drop(1).each do |namespace|
83
93
  Glimmer::UI::CustomWidget.custom_widget_namespaces << namespace
@@ -138,8 +148,7 @@ module Glimmer
138
148
  end
139
149
 
140
150
  def before_body(&block)
141
- @before_body_blocks ||= []
142
- @before_body_blocks << block
151
+ @before_body_block = block
143
152
  end
144
153
 
145
154
  def body(&block)
@@ -147,29 +156,27 @@ module Glimmer
147
156
  end
148
157
 
149
158
  def after_body(&block)
150
- @after_body_blocks ||= []
151
- @after_body_blocks << block
159
+ @after_body_block = block
152
160
  end
153
161
  end
154
162
 
155
163
  attr_reader :body_root, :swt_widget, :parent, :parent_proxy, :swt_style, :options
156
164
 
157
165
  def initialize(parent, *swt_constants, options, &content)
166
+ @parent_proxy = @parent = parent
167
+ @parent_proxy = @parent&.get_data('proxy') if @parent.respond_to?(:get_data) && @parent.get_data('proxy')
158
168
  @swt_style = SWT::SWTProxy[*swt_constants]
159
169
  options ||= {}
160
170
  @options = self.class.options.merge(options)
161
171
  @content = Util::ProcTracker.new(content) if content
162
- execute_hooks('before_body')
172
+ execute_hook('before_body')
163
173
  body_block = self.class.instance_variable_get("@body_block")
164
174
  raise Glimmer::Error, 'Invalid custom widget for having no body! Please define body block!' if body_block.nil?
165
175
  @body_root = instance_exec(&body_block)
166
176
  raise Glimmer::Error, 'Invalid custom widget for having an empty body! Please fill body block!' if @body_root.nil?
167
177
  @swt_widget = @body_root.swt_widget
168
178
  @swt_widget.set_data('custom_widget', self)
169
- @parent = parent
170
- @parent ||= @swt_widget.parent
171
- @parent_proxy ||= @parent&.get_data('proxy')
172
- execute_hooks('after_body')
179
+ execute_hook('after_body')
173
180
  end
174
181
 
175
182
  # Subclasses may override to perform post initialization work on an added child
@@ -242,10 +249,18 @@ module Glimmer
242
249
  def attribute_setter(attribute_name)
243
250
  "#{attribute_name}="
244
251
  end
252
+
253
+ def disposed?
254
+ swt_widget.isDisposed
255
+ end
245
256
 
246
257
  def has_style?(style)
247
258
  (swt_style & SWT::SWTProxy[style]) == SWT::SWTProxy[style]
248
259
  end
260
+
261
+ def pack(*args)
262
+ body_root.pack(*args)
263
+ end
249
264
 
250
265
  # TODO see if it is worth it to eliminate duplication of async_exec/sync_exec
251
266
  # delegation to DisplayProxy, via a module
@@ -287,13 +302,13 @@ module Glimmer
287
302
 
288
303
  private
289
304
 
290
- def execute_hooks(hook_name)
291
- self.class.instance_variable_get("@#{hook_name}_blocks")&.each do |hook_block|
292
- temp_method_name = "#{hook_name}_block_#{hook_block.hash.abs}_#{(Time.now.to_f * 1_000_000).to_i}"
293
- singleton_class.define_method(temp_method_name, &hook_block)
294
- send(temp_method_name)
295
- singleton_class.send(:remove_method, temp_method_name)
296
- end
305
+ def execute_hook(hook_name)
306
+ hook_block = self.class.instance_variable_get("@#{hook_name}_block")
307
+ return if hook_block.nil?
308
+ temp_method_name = "#{hook_name}_block_#{hook_block.hash.abs}_#{(Time.now.to_f * 1_000_000).to_i}"
309
+ singleton_class.define_method(temp_method_name, &hook_block)
310
+ send(temp_method_name)
311
+ singleton_class.send(:remove_method, temp_method_name)
297
312
  end
298
313
  end
299
314
  end
@@ -25,6 +25,24 @@ require 'etc'
25
25
  class Sample
26
26
  include Glimmer::DataBinding::ObservableModel
27
27
 
28
+ class << self
29
+ def glimmer_directory
30
+ File.expand_path('../../..', __FILE__)
31
+ end
32
+
33
+ def user_glimmer_directory
34
+ File.join(Etc.getpwuid.dir, '.glimmer-dsl-swt')
35
+ end
36
+
37
+ def ensure_user_glimmer_directory
38
+ unless @ensured_glimmer_directory
39
+ FileUtils.rm_rf(user_glimmer_directory)
40
+ FileUtils.cp_r(glimmer_directory, user_glimmer_directory)
41
+ @ensured_glimmer_directory = true
42
+ end
43
+ end
44
+ end
45
+
28
46
  attr_accessor :sample_directory, :file, :selected
29
47
 
30
48
  def initialize(file, sample_directory: )
@@ -59,25 +77,35 @@ class Sample
59
77
  File.basename(file) != 'meta_sample.rb'
60
78
  end
61
79
  alias launchable editable
80
+
81
+ def file_relative_path
82
+ file.sub(self.class.glimmer_directory, '')
83
+ end
84
+
85
+ def user_file
86
+ File.join(self.class.user_glimmer_directory, file_relative_path)
87
+ end
88
+
89
+ def user_file_parent_directory
90
+ File.dirname(user_file)
91
+ end
62
92
 
93
+ def directory
94
+ file.sub(/\.rb/, '')
95
+ end
96
+
63
97
  def launch(modified_code)
64
- parent_directory = File.basename(File.dirname(file))
65
- modified_file_parent_directory = File.join(Etc.getpwuid.dir, '.glimmer', 'samples', parent_directory)
66
- launch_file = modified_file = File.join(modified_file_parent_directory, File.basename(file))
98
+ launch_file = user_file
67
99
  begin
68
- FileUtils.mkdir_p(modified_file_parent_directory)
69
- FileUtils.cp_r(file, modified_file_parent_directory)
70
- FileUtils.cp_r(file.sub(/\.rb/, ''), modified_file_parent_directory) if File.exist?(file.sub(/\.rb/, ''))
71
- File.write(modified_file, modified_code)
100
+ FileUtils.cp_r(file, user_file_parent_directory)
101
+ FileUtils.cp_r(directory, user_file_parent_directory) if File.exist?(directory)
102
+ File.write(user_file, modified_code)
72
103
  rescue => e
73
104
  puts 'Error writing sample modifications. Launching original sample.'
74
105
  puts e.full_message
75
106
  launch_file = file # load original file if failed to write changes
76
107
  end
77
108
  load(launch_file)
78
- ensure
79
- FileUtils.rm_rf(modified_file)
80
- FileUtils.rm_rf(modified_file.sub(/\.rb/, ''))
81
109
  end
82
110
  end
83
111
 
@@ -162,17 +190,18 @@ class SampleDirectory
162
190
  end
163
191
 
164
192
  class MetaSampleApplication
165
- include Glimmer
193
+ include Glimmer::UI::CustomShell
166
194
 
167
- def initialize
195
+ before_body {
196
+ Sample.ensure_user_glimmer_directory
168
197
  selected_sample_directory = SampleDirectory.sample_directories.first
169
198
  selected_sample = selected_sample_directory.samples.first
170
199
  selected_sample_directory.selected_sample_name = selected_sample.name
171
- end
172
-
173
- def launch
174
200
  Display.app_name = 'Glimmer Meta-Sample'
175
- shell {
201
+ }
202
+
203
+ body {
204
+ shell(:fill_screen) {
176
205
  minimum_size 1280, 768
177
206
  text 'Glimmer Meta-Sample (The Sample of Samples)'
178
207
  image File.expand_path('../../icons/scaffold_app.png', __dir__)
@@ -218,11 +247,8 @@ class MetaSampleApplication
218
247
  on_widget_selected {
219
248
  begin
220
249
  SampleDirectory.selected_sample.launch(@code_text.text)
221
- rescue StandardError, SyntaxError => launch_error
222
- message_box {
223
- text 'Error Launching'
224
- message launch_error.full_message
225
- }.open
250
+ rescue LoadError, StandardError, SyntaxError => launch_error
251
+ error_dialog(message: launch_error.full_message).open
226
252
  end
227
253
  }
228
254
  }
@@ -243,10 +269,41 @@ class MetaSampleApplication
243
269
  editable bind(SampleDirectory, 'selected_sample.editable')
244
270
  }
245
271
 
246
- weights 4, 9
272
+ weights 4, 11
273
+ }
274
+ }
275
+ }
276
+
277
+ # Method-based error_dialog custom widget
278
+ def error_dialog(message:)
279
+ return if message.nil?
280
+ dialog(body_root) { |dialog_proxy|
281
+ row_layout(:vertical) {
282
+ center true
283
+ }
284
+
285
+ text 'Error Launching'
286
+
287
+ styled_text(:border, :h_scroll, :v_scroll) {
288
+ layout_data {
289
+ width body_root.bounds.width*0.75
290
+ height body_root.bounds.height*0.75
291
+ }
292
+
293
+ text message
294
+ editable false
295
+ caret nil
296
+ }
297
+
298
+ button {
299
+ text 'Close'
300
+
301
+ on_widget_selected {
302
+ dialog_proxy.close
303
+ }
247
304
  }
248
- }.open
305
+ }
249
306
  end
250
307
  end
251
308
 
252
- MetaSampleApplication.new.launch
309
+ MetaSampleApplication.launch