glimmer-dsl-swt 4.18.3.0 → 4.18.3.5

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +59 -0
  3. data/README.md +440 -18
  4. data/VERSION +1 -1
  5. data/glimmer-dsl-swt.gemspec +11 -7
  6. data/lib/ext/glimmer/config.rb +24 -7
  7. data/lib/ext/rouge/themes/glimmer.rb +29 -0
  8. data/lib/glimmer-dsl-swt.rb +0 -1
  9. data/lib/glimmer/data_binding/table_items_binding.rb +8 -5
  10. data/lib/glimmer/data_binding/widget_binding.rb +22 -4
  11. data/lib/glimmer/dsl/swt/image_expression.rb +14 -6
  12. data/lib/glimmer/dsl/swt/layout_data_expression.rb +4 -4
  13. data/lib/glimmer/dsl/swt/layout_expression.rb +5 -3
  14. data/lib/glimmer/swt/custom/code_text.rb +132 -37
  15. data/lib/glimmer/swt/custom/drawable.rb +3 -2
  16. data/lib/glimmer/swt/custom/shape.rb +25 -11
  17. data/lib/glimmer/swt/date_time_proxy.rb +1 -3
  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/font_proxy.rb +1 -0
  22. data/lib/glimmer/swt/image_proxy.rb +68 -1
  23. data/lib/glimmer/swt/shell_proxy.rb +23 -3
  24. data/lib/glimmer/swt/table_proxy.rb +43 -15
  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 +259 -137
  32. data/samples/elaborate/tetris/{view/game_over_dialog.rb → model/past_game.rb} +11 -44
  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 +131 -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 +1 -1
  43. data/samples/hello/hello_code_text.rb +84 -0
  44. data/samples/hello/hello_link.rb +1 -1
  45. metadata +9 -5
@@ -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
@@ -25,60 +25,88 @@ require_relative 'tetris/model/game'
25
25
 
26
26
  require_relative 'tetris/view/playfield'
27
27
  require_relative 'tetris/view/score_lane'
28
- require_relative 'tetris/view/game_over_dialog'
28
+ require_relative 'tetris/view/high_score_dialog'
29
+ require_relative 'tetris/view/tetris_menu_bar'
29
30
 
30
31
  class Tetris
31
32
  include Glimmer::UI::CustomShell
32
33
 
33
34
  BLOCK_SIZE = 25
34
- PLAYFIELD_WIDTH = 10
35
- PLAYFIELD_HEIGHT = 20
36
- PREVIEW_PLAYFIELD_WIDTH = 4
37
- PREVIEW_PLAYFIELD_HEIGHT = 2
35
+ FONT_NAME = 'Menlo'
36
+ FONT_TITLE_HEIGHT = 32
37
+ FONT_TITLE_STYLE = :bold
38
+ BEVEL_CONSTANT = 20
39
+
40
+ option :playfield_width, default: Model::Game::PLAYFIELD_WIDTH
41
+ option :playfield_height, default: Model::Game::PLAYFIELD_HEIGHT
42
+
43
+ attr_reader :game
38
44
 
39
45
  before_body {
46
+ @mutex = Mutex.new
47
+ @game = Model::Game.new(playfield_width, playfield_height)
48
+
49
+ @game.configure_beeper do
50
+ display.beep
51
+ end
52
+
53
+ Display.app_name = 'Glimmer Tetris'
40
54
  display {
41
- on_swt_keydown { |key_event|
55
+ @keyboard_listener = on_swt_keydown { |key_event|
42
56
  case key_event.keyCode
43
- when swt(:arrow_down)
44
- Model::Game.current_tetromino.down
45
- when swt(:arrow_left)
46
- Model::Game.current_tetromino.left
47
- when swt(:arrow_right)
48
- Model::Game.current_tetromino.right
49
- when swt(:shift)
57
+ when swt(:arrow_down), 's'.bytes.first
58
+ game.down!
59
+ when swt(:arrow_up)
60
+ case game.up_arrow_action
61
+ when :instant_down
62
+ game.down!(instant: true)
63
+ when :rotate_right
64
+ game.rotate!(:right)
65
+ when :rotate_left
66
+ game.rotate!(:left)
67
+ end
68
+ when swt(:arrow_left), 'a'.bytes.first
69
+ game.left!
70
+ when swt(:arrow_right), 'd'.bytes.first
71
+ game.right!
72
+ when swt(:shift), swt(:alt)
50
73
  if key_event.keyLocation == swt(:right) # right shift key
51
- Model::Game.current_tetromino.rotate(:right)
74
+ game.rotate!(:right)
52
75
  elsif key_event.keyLocation == swt(:left) # left shift key
53
- Model::Game.current_tetromino.rotate(:left)
76
+ game.rotate!(:left)
54
77
  end
55
- when 'd'.bytes.first, swt(:arrow_up)
56
- Model::Game.current_tetromino.rotate(:right)
57
- when 'a'.bytes.first
58
- Model::Game.current_tetromino.rotate(:left)
78
+ when swt(:ctrl)
79
+ game.rotate!(:left)
59
80
  end
60
81
  }
82
+
83
+ # if running in app mode, set the Mac app about dialog (ignored in platforms)
84
+ @about_observer = on_about {
85
+ show_about_dialog
86
+ }
87
+
88
+ @quit_observer = on_quit {
89
+ exit(0)
90
+ }
61
91
  }
62
-
63
- Model::Game.configure_beeper do
64
- display.beep
65
- end
66
92
  }
67
93
 
68
94
  after_body {
69
- Model::Game.start
70
-
71
- Thread.new {
72
- loop {
73
- sleep(Model::Game.delay)
74
- sync_exec {
75
- unless Model::Game.game_over?
76
- Model::Game.current_tetromino.down
77
- game_over_dialog(parent_shell: body_root).open if Model::Game.current_tetromino.row <= 0 && Model::Game.current_tetromino.stopped?
78
- end
79
- }
80
- }
81
- }
95
+ @game_over_observer = observe(@game, :game_over) do |game_over|
96
+ if game_over
97
+ show_high_score_dialog
98
+ else
99
+ start_moving_tetrominos_down
100
+ end
101
+ end
102
+ @show_high_scores_observer = observe(@game, :show_high_scores) do |show_high_scores|
103
+ if show_high_scores
104
+ show_high_score_dialog
105
+ else
106
+ @high_score_dialog.close unless @high_score_dialog.nil? || @high_score_dialog.disposed? || !@high_score_dialog.visible?
107
+ end
108
+ end
109
+ @game.start!
82
110
  }
83
111
 
84
112
  body {
@@ -92,15 +120,91 @@ class Tetris
92
120
  }
93
121
 
94
122
  text 'Glimmer Tetris'
95
- background :gray
96
-
97
- playfield(game_playfield: Model::Game.playfield, playfield_width: PLAYFIELD_WIDTH, playfield_height: PLAYFIELD_HEIGHT, block_size: BLOCK_SIZE)
98
-
99
- score_lane(block_size: BLOCK_SIZE) {
100
- layout_data(:fill, :fill, false, true)
123
+ minimum_size 475, 500
124
+ image tetris_icon
125
+
126
+ tetris_menu_bar(game: game)
127
+
128
+ playfield(game_playfield: game.playfield, playfield_width: playfield_width, playfield_height: playfield_height, block_size: BLOCK_SIZE)
129
+
130
+ score_lane(game: game, block_size: BLOCK_SIZE) {
131
+ layout_data(:fill, :fill, true, true)
132
+ }
133
+
134
+ on_widget_disposed {
135
+ deregister_observers
101
136
  }
102
137
  }
103
138
  }
139
+
140
+ def tetris_icon
141
+ icon_block_size = 64
142
+ icon_bevel_size = icon_block_size.to_f / 25.to_f
143
+ icon_bevel_pixel_size = 0.16*icon_block_size.to_f
144
+ icon_size = 8
145
+ icon_pixel_size = icon_block_size * icon_size
146
+ image(icon_pixel_size, icon_pixel_size) {
147
+ icon_size.times { |row|
148
+ icon_size.times { |column|
149
+ colored = row >= 1 && column.between?(1, 6)
150
+ color = colored ? color(([:white] + Model::Tetromino::LETTER_COLORS.values).sample) : color(:white)
151
+ x = column * icon_block_size
152
+ y = row * icon_block_size
153
+ rectangle(x, y, icon_block_size, icon_block_size, fill: true) {
154
+ background color
155
+ }
156
+ polygon(x, y, x + icon_block_size, y, x + icon_block_size - icon_bevel_pixel_size, y + icon_bevel_pixel_size, x + icon_bevel_pixel_size, y + icon_bevel_pixel_size, fill: true) {
157
+ background rgb(color.red + 4*BEVEL_CONSTANT, color.green + 4*BEVEL_CONSTANT, color.blue + 4*BEVEL_CONSTANT)
158
+ }
159
+ polygon(x + icon_block_size, y, x + icon_block_size - icon_bevel_pixel_size, y + icon_bevel_pixel_size, x + icon_block_size - icon_bevel_pixel_size, y + icon_block_size - icon_bevel_pixel_size, x + icon_block_size, y + icon_block_size, fill: true) {
160
+ background rgb(color.red - BEVEL_CONSTANT, color.green - BEVEL_CONSTANT, color.blue - BEVEL_CONSTANT)
161
+ }
162
+ polygon(x + icon_block_size, y + icon_block_size, x, y + icon_block_size, x + icon_bevel_pixel_size, y + icon_block_size - icon_bevel_pixel_size, x + icon_block_size - icon_bevel_pixel_size, y + icon_block_size - icon_bevel_pixel_size, fill: true) {
163
+ background rgb(color.red - 2*BEVEL_CONSTANT, color.green - 2*BEVEL_CONSTANT, color.blue - 2*BEVEL_CONSTANT)
164
+ }
165
+ polygon(x, y, x, y + icon_block_size, x + icon_bevel_pixel_size, y + icon_block_size - icon_bevel_pixel_size, x + icon_bevel_pixel_size, y + icon_bevel_pixel_size, fill: true) {
166
+ background rgb(color.red - BEVEL_CONSTANT, color.green - BEVEL_CONSTANT, color.blue - BEVEL_CONSTANT)
167
+ }
168
+ }
169
+ }
170
+ }
171
+ end
172
+
173
+ def start_moving_tetrominos_down
174
+ Thread.new do
175
+ @mutex.synchronize do
176
+ loop do
177
+ time = Time.now
178
+ sleep @game.delay
179
+ break if @game.game_over? || body_root.disposed?
180
+ sync_exec {
181
+ @game.down! unless @game.paused?
182
+ }
183
+ end
184
+ end
185
+ end
186
+ end
187
+
188
+ def show_high_score_dialog
189
+ return if @high_score_dialog&.visible?
190
+ @high_score_dialog = high_score_dialog(parent_shell: body_root, game: @game) if @high_score_dialog.nil? || @high_score_dialog.disposed?
191
+ @high_score_dialog.show
192
+ end
193
+
194
+ def show_about_dialog
195
+ message_box {
196
+ text 'Glimmer Tetris'
197
+ message "Glimmer Tetris\n\nGlimmer DSL for SWT Sample\n\nCopyright (c) 2007-2021 Andy Maleh"
198
+ }.open
199
+ end
200
+
201
+ def deregister_observers
202
+ @show_high_scores_observer&.deregister
203
+ @game_over_observer&.deregister
204
+ @keyboard_listener&.deregister
205
+ @about_observer&.deregister
206
+ @quit_observer&.deregister
207
+ end
104
208
  end
105
209
 
106
210
  Tetris.launch