glimmer-dsl-swt 4.18.3.1 → 4.18.4.0

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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +55 -1
  3. data/README.md +739 -324
  4. data/VERSION +1 -1
  5. data/glimmer-dsl-swt.gemspec +9 -4
  6. data/lib/ext/rouge/themes/glimmer.rb +29 -0
  7. data/lib/glimmer-dsl-swt.rb +0 -1
  8. data/lib/glimmer/data_binding/table_items_binding.rb +8 -5
  9. data/lib/glimmer/data_binding/widget_binding.rb +9 -1
  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/swt/custom/code_text.rb +171 -53
  14. data/lib/glimmer/swt/custom/drawable.rb +4 -6
  15. data/lib/glimmer/swt/custom/shape.rb +69 -12
  16. data/lib/glimmer/swt/date_time_proxy.rb +1 -3
  17. data/lib/glimmer/swt/display_proxy.rb +12 -1
  18. data/lib/glimmer/swt/font_proxy.rb +1 -0
  19. data/lib/glimmer/swt/image_proxy.rb +79 -1
  20. data/lib/glimmer/swt/shell_proxy.rb +13 -1
  21. data/lib/glimmer/swt/table_proxy.rb +43 -15
  22. data/lib/glimmer/swt/widget_proxy.rb +11 -2
  23. data/lib/glimmer/ui/custom_shell.rb +1 -0
  24. data/lib/glimmer/ui/custom_widget.rb +10 -3
  25. data/samples/elaborate/meta_sample.rb +1 -1
  26. data/samples/elaborate/meta_sample/meta_sample_logo.png +0 -0
  27. data/samples/elaborate/tetris.rb +90 -17
  28. data/samples/elaborate/tetris/model/game.rb +89 -8
  29. data/samples/elaborate/tetris/{view/game_over_dialog.rb → model/past_game.rb} +12 -41
  30. data/samples/elaborate/tetris/model/tetromino.rb +18 -5
  31. data/samples/elaborate/tetris/view/block.rb +8 -13
  32. data/samples/elaborate/tetris/view/high_score_dialog.rb +131 -0
  33. data/samples/elaborate/tetris/view/playfield.rb +1 -1
  34. data/samples/elaborate/tetris/view/score_lane.rb +6 -6
  35. data/samples/elaborate/tetris/view/tetris_menu_bar.rb +70 -3
  36. data/samples/hello/hello_canvas.rb +10 -9
  37. data/samples/hello/hello_canvas_animation.rb +5 -5
  38. data/samples/hello/hello_code_text.rb +92 -0
  39. data/samples/hello/hello_table.rb +6 -4
  40. data/samples/hello/hello_table/baseball_park.png +0 -0
  41. metadata +8 -3
@@ -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
@@ -165,6 +165,11 @@ module Glimmer
165
165
  DEFAULT_INITIALIZERS[underscored_widget_name.to_s.to_sym]&.call(@swt_widget)
166
166
  @parent_proxy.post_initialize_child(self)
167
167
  end
168
+ if respond_to?(:on_widget_disposed)
169
+ on_widget_disposed {
170
+ clear_shapes
171
+ }
172
+ end
168
173
  end
169
174
 
170
175
  # Subclasses may override to perform post initialization work on an added child
@@ -547,6 +552,10 @@ module Glimmer
547
552
  @swt_widget.dispose
548
553
  end
549
554
 
555
+ def disposed?
556
+ @swt_widget.isDisposed
557
+ end
558
+
550
559
  # TODO Consider renaming these methods as they are mainly used for data-binding
551
560
 
552
561
  def can_add_observer?(property_name)
@@ -663,7 +672,7 @@ module Glimmer
663
672
  can_handle_observation_request?(method) ||
664
673
  swt_widget.respond_to?(method, *args, &block)
665
674
  end
666
-
675
+
667
676
  private
668
677
 
669
678
  def style(underscored_widget_name, styles)
@@ -32,6 +32,7 @@ module Glimmer
32
32
 
33
33
  def launch(*args, &content)
34
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)
35
36
  @launched_custom_shell.open
36
37
  end
37
38
  end
@@ -163,6 +163,8 @@ module Glimmer
163
163
  attr_reader :body_root, :swt_widget, :parent, :parent_proxy, :swt_style, :options
164
164
 
165
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')
166
168
  @swt_style = SWT::SWTProxy[*swt_constants]
167
169
  options ||= {}
168
170
  @options = self.class.options.merge(options)
@@ -174,9 +176,6 @@ module Glimmer
174
176
  raise Glimmer::Error, 'Invalid custom widget for having an empty body! Please fill body block!' if @body_root.nil?
175
177
  @swt_widget = @body_root.swt_widget
176
178
  @swt_widget.set_data('custom_widget', self)
177
- @parent = parent
178
- @parent ||= @swt_widget.parent
179
- @parent_proxy ||= @parent&.get_data('proxy')
180
179
  execute_hook('after_body')
181
180
  end
182
181
 
@@ -250,10 +249,18 @@ module Glimmer
250
249
  def attribute_setter(attribute_name)
251
250
  "#{attribute_name}="
252
251
  end
252
+
253
+ def disposed?
254
+ swt_widget.isDisposed
255
+ end
253
256
 
254
257
  def has_style?(style)
255
258
  (swt_style & SWT::SWTProxy[style]) == SWT::SWTProxy[style]
256
259
  end
260
+
261
+ def pack(*args)
262
+ body_root.pack(*args)
263
+ end
257
264
 
258
265
  # TODO see if it is worth it to eliminate duplication of async_exec/sync_exec
259
266
  # delegation to DisplayProxy, via a module
@@ -204,7 +204,7 @@ class MetaSampleApplication
204
204
  shell(:fill_screen) {
205
205
  minimum_size 1280, 768
206
206
  text 'Glimmer Meta-Sample (The Sample of Samples)'
207
- image File.expand_path('../../icons/scaffold_app.png', __dir__)
207
+ image File.expand_path('meta_sample/meta_sample_logo.png', __dir__)
208
208
 
209
209
  sash_form {
210
210
  composite {
@@ -25,13 +25,17 @@ 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
29
  require_relative 'tetris/view/tetris_menu_bar'
30
30
 
31
31
  class Tetris
32
32
  include Glimmer::UI::CustomShell
33
33
 
34
34
  BLOCK_SIZE = 25
35
+ FONT_NAME = 'Menlo'
36
+ FONT_TITLE_HEIGHT = 32
37
+ FONT_TITLE_STYLE = :bold
38
+ BEVEL_CONSTANT = 20
35
39
 
36
40
  option :playfield_width, default: Model::Game::PLAYFIELD_WIDTH
37
41
  option :playfield_height, default: Model::Game::PLAYFIELD_HEIGHT
@@ -50,36 +54,58 @@ class Tetris
50
54
  display {
51
55
  @keyboard_listener = on_swt_keydown { |key_event|
52
56
  case key_event.keyCode
53
- when swt(:arrow_down)
57
+ when swt(:arrow_down), 's'.bytes.first
54
58
  game.down!
55
- when swt(:arrow_left)
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
56
69
  game.left!
57
- when swt(:arrow_right)
70
+ when swt(:arrow_right), 'd'.bytes.first
58
71
  game.right!
59
- when swt(:shift)
72
+ when swt(:shift), swt(:alt)
60
73
  if key_event.keyLocation == swt(:right) # right shift key
61
74
  game.rotate!(:right)
62
75
  elsif key_event.keyLocation == swt(:left) # left shift key
63
76
  game.rotate!(:left)
64
77
  end
65
- when 'd'.bytes.first, swt(:arrow_up)
66
- game.rotate!(:right)
67
- when 'a'.bytes.first
78
+ when swt(:ctrl)
68
79
  game.rotate!(:left)
69
80
  end
70
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
+ }
71
91
  }
72
92
  }
73
93
 
74
94
  after_body {
75
95
  @game_over_observer = observe(@game, :game_over) do |game_over|
76
96
  if game_over
77
- @game_over_dialog = game_over_dialog(parent_shell: body_root, game: @game) if @game_over_dialog.nil? || @game_over_dialog.disposed?
78
- @game_over_dialog.show
97
+ show_high_score_dialog
79
98
  else
80
99
  start_moving_tetrominos_down
81
100
  end
82
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
83
109
  @game.start!
84
110
  }
85
111
 
@@ -95,22 +121,55 @@ class Tetris
95
121
 
96
122
  text 'Glimmer Tetris'
97
123
  minimum_size 475, 500
98
- background :gray
99
-
124
+ image tetris_icon
125
+
100
126
  tetris_menu_bar(game: game)
101
-
127
+
102
128
  playfield(game_playfield: game.playfield, playfield_width: playfield_width, playfield_height: playfield_height, block_size: BLOCK_SIZE)
103
-
129
+
104
130
  score_lane(game: game, block_size: BLOCK_SIZE) {
105
131
  layout_data(:fill, :fill, true, true)
106
132
  }
107
-
133
+
108
134
  on_widget_disposed {
109
135
  deregister_observers
110
136
  }
111
137
  }
112
138
  }
113
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
+
114
173
  def start_moving_tetrominos_down
115
174
  Thread.new do
116
175
  @mutex.synchronize do
@@ -126,11 +185,25 @@ class Tetris
126
185
  end
127
186
  end
128
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
+
129
201
  def deregister_observers
202
+ @show_high_scores_observer&.deregister
130
203
  @game_over_observer&.deregister
131
- @game_over_observer = nil
132
204
  @keyboard_listener&.deregister
133
- @keyboard_listener = nil
205
+ @about_observer&.deregister
206
+ @quit_observer&.deregister
134
207
  end
135
208
  end
136
209
 
@@ -19,8 +19,15 @@
19
19
  # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
20
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
21
 
22
+ require 'fileutils'
23
+ require 'etc'
24
+ require 'json'
25
+ require 'glimmer/data_binding/observer'
26
+ require 'glimmer/config'
27
+
22
28
  require_relative 'block'
23
29
  require_relative 'tetromino'
30
+ require_relative 'past_game'
24
31
 
25
32
  class Tetris
26
33
  module Model
@@ -32,15 +39,22 @@ class Tetris
32
39
  SCORE_MULTIPLIER = {1 => 40, 2 => 100, 3 => 300, 4 => 1200}
33
40
 
34
41
  attr_reader :playfield_width, :playfield_height
35
- attr_accessor :game_over, :paused, :preview_tetromino, :lines, :score, :level
42
+ attr_accessor :game_over, :paused, :preview_tetromino, :lines, :score, :level, :high_scores, :beeping, :added_high_score, :show_high_scores, :up_arrow_action
36
43
  alias game_over? game_over
37
44
  alias paused? paused
45
+ alias beeping? beeping
46
+ alias added_high_score? added_high_score
38
47
 
39
48
  def initialize(playfield_width = PLAYFIELD_WIDTH, playfield_height = PLAYFIELD_HEIGHT)
40
49
  @playfield_width = playfield_width
41
50
  @playfield_height = playfield_height
51
+ @high_scores = []
52
+ @show_high_scores = false
53
+ @beeping = true
54
+ @up_arrow_action = :rotate_left
55
+ load_high_scores!
42
56
  end
43
-
57
+
44
58
  def configure_beeper(&beeper)
45
59
  @beeper = beeper
46
60
  end
@@ -50,6 +64,7 @@ class Tetris
50
64
  end
51
65
 
52
66
  def start!
67
+ self.show_high_scores = false
53
68
  self.paused = false
54
69
  self.level = 1
55
70
  self.score = 0
@@ -63,10 +78,51 @@ class Tetris
63
78
  end
64
79
  alias restart! start!
65
80
 
66
- def down!
81
+ def game_over!
82
+ add_high_score!
83
+ beep
84
+ self.game_over = true
85
+ end
86
+
87
+ def clear_high_scores!
88
+ high_scores.clear
89
+ end
90
+
91
+ def add_high_score!
92
+ self.added_high_score = true
93
+ high_scores.prepend(PastGame.new("Player #{high_scores.count + 1}", score, lines, level))
94
+ end
95
+
96
+ def save_high_scores!
97
+ high_score_file_content = @high_scores.map {|past_game| past_game.to_a.join("\t") }.join("\n")
98
+ FileUtils.mkdir_p(tetris_dir)
99
+ File.write(tetris_high_score_file, high_score_file_content)
100
+ rescue => e
101
+ # Fail safely by keeping high scores in memory if unable to access disk
102
+ Glimmer::Config.logger.error {"Failed to save high scores in: #{tetris_high_score_file}\n#{e.full_message}"}
103
+ end
104
+
105
+ def load_high_scores!
106
+ if File.exist?(tetris_high_score_file)
107
+ self.high_scores = File.read(tetris_high_score_file).split("\n").map {|line| PastGame.new(*line.split("\t")) }
108
+ end
109
+ rescue => e
110
+ # Fail safely by keeping high scores in memory if unable to access disk
111
+ Glimmer::Config.logger.error {"Failed to load high scores from: #{tetris_high_score_file}\n#{e.full_message}"}
112
+ end
113
+
114
+ def tetris_dir
115
+ @tetris_dir ||= File.join(Etc.getpwuid.dir, '.glimmer-tetris')
116
+ end
117
+
118
+ def tetris_high_score_file
119
+ File.join(tetris_dir, "high_scores.txt")
120
+ end
121
+
122
+ def down!(instant: false)
67
123
  return unless game_in_progress?
68
- current_tetromino.down!
69
- self.game_over = true if current_tetromino.row <= 0 && current_tetromino.stopped?
124
+ current_tetromino.down!(instant: instant)
125
+ game_over! if current_tetromino.row <= 0 && current_tetromino.stopped?
70
126
  end
71
127
 
72
128
  def right!
@@ -147,7 +203,31 @@ class Tetris
147
203
  end
148
204
 
149
205
  def beep
150
- @beeper&.call
206
+ @beeper&.call if beeping
207
+ end
208
+
209
+ def instant_down_on_up=(value)
210
+ self.up_arrow_action = :instant_down if value
211
+ end
212
+
213
+ def instant_down_on_up
214
+ self.up_arrow_action == :instant_down
215
+ end
216
+
217
+ def rotate_right_on_up=(value)
218
+ self.up_arrow_action = :rotate_right if value
219
+ end
220
+
221
+ def rotate_right_on_up
222
+ self.up_arrow_action == :rotate_right
223
+ end
224
+
225
+ def rotate_left_on_up=(value)
226
+ self.up_arrow_action = :rotate_left if value
227
+ end
228
+
229
+ def rotate_left_on_up
230
+ self.up_arrow_action == :rotate_left
151
231
  end
152
232
 
153
233
  def reset_tetrominoes
@@ -195,12 +275,13 @@ class Tetris
195
275
 
196
276
  def playfield_remaining_heights(tetromino = nil)
197
277
  @playfield_width.times.map do |playfield_column|
278
+ bottom_most_block = tetromino.bottom_most_block_for_column(playfield_column)
198
279
  (playfield.each_with_index.detect do |row, playfield_row|
199
280
  !row[playfield_column].clear? &&
200
281
  (
201
282
  tetromino.nil? ||
202
- (bottom_most_block = tetromino.bottom_most_block_for_column(playfield_column)).nil? ||
203
- (playfield_row > tetromino.row + bottom_most_block[:row])
283
+ bottom_most_block.nil? ||
284
+ (playfield_row > tetromino.row + bottom_most_block[:row_index])
204
285
  )
205
286
  end || [nil, @playfield_height])[1]
206
287
  end.to_a
@@ -19,50 +19,21 @@
19
19
  # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
20
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
21
 
22
- require_relative 'tetris_menu_bar'
23
-
24
22
  class Tetris
25
- module View
26
- class GameOverDialog
27
- include Glimmer::UI::CustomShell
28
-
29
- options :parent_shell, :game
23
+ module Model
24
+ class PastGame
25
+ attr_accessor :name, :score, :lines, :level
30
26
 
31
- after_body {
32
- observe(game, :game_over) do |game_over|
33
- hide if !game_over
34
- end
35
- }
27
+ def initialize(name, score, lines, level)
28
+ @name = name
29
+ @score = score.to_i
30
+ @lines = lines.to_i
31
+ @level = level.to_i
32
+ end
36
33
 
37
- body {
38
- dialog(parent_shell) {
39
- row_layout {
40
- type :vertical
41
- center true
42
- }
43
- text 'Tetris'
44
-
45
- tetris_menu_bar(game: game)
46
-
47
- label(:center) {
48
- text 'Game Over!'
49
- font name: 'Menlo', height: 30, style: :bold
50
- }
51
- label # filler
52
- button {
53
- text 'Play Again?'
54
-
55
- on_widget_selected {
56
- hide
57
- game.restart!
58
- }
59
- }
60
-
61
- on_shell_activated {
62
- display.beep
63
- }
64
- }
65
- }
34
+ def to_a
35
+ [@name, @score, @lines, @level]
36
+ end
66
37
  end
67
38
  end
68
39
  end