glimmer-dsl-swt 4.18.3.2 → 4.18.4.1

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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +52 -0
  3. data/README.md +674 -323
  4. data/VERSION +1 -1
  5. data/glimmer-dsl-swt.gemspec +7 -3
  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 +196 -51
  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/layout_proxy.rb +4 -1
  21. data/lib/glimmer/swt/shell_proxy.rb +10 -1
  22. data/lib/glimmer/swt/table_proxy.rb +27 -14
  23. data/lib/glimmer/swt/widget_proxy.rb +12 -2
  24. data/lib/glimmer/ui/custom_widget.rb +6 -2
  25. data/samples/elaborate/meta_sample.rb +5 -2
  26. data/samples/elaborate/meta_sample/meta_sample_logo.png +0 -0
  27. data/samples/elaborate/tetris.rb +60 -7
  28. data/samples/elaborate/tetris/model/game.rb +36 -7
  29. data/samples/elaborate/tetris/model/past_game.rb +14 -1
  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 +23 -6
  33. data/samples/elaborate/tetris/view/playfield.rb +1 -1
  34. data/samples/elaborate/tetris/view/tetris_menu_bar.rb +22 -6
  35. data/samples/hello/hello_canvas.rb +10 -9
  36. data/samples/hello/hello_canvas_animation.rb +5 -5
  37. data/samples/hello/hello_code_text.rb +104 -0
  38. data/samples/hello/hello_table.rb +6 -4
  39. data/samples/hello/hello_table/baseball_park.png +0 -0
  40. metadata +6 -2
@@ -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,12 @@ 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
+ @keyword = underscored_widget_name.to_s
169
+ if respond_to?(:on_widget_disposed)
170
+ on_widget_disposed {
171
+ clear_shapes
172
+ }
173
+ end
168
174
  end
169
175
 
170
176
  # Subclasses may override to perform post initialization work on an added child
@@ -547,6 +553,10 @@ module Glimmer
547
553
  @swt_widget.dispose
548
554
  end
549
555
 
556
+ def disposed?
557
+ @swt_widget.isDisposed
558
+ end
559
+
550
560
  # TODO Consider renaming these methods as they are mainly used for data-binding
551
561
 
552
562
  def can_add_observer?(property_name)
@@ -663,7 +673,7 @@ module Glimmer
663
673
  can_handle_observation_request?(method) ||
664
674
  swt_widget.respond_to?(method, *args, &block)
665
675
  end
666
-
676
+
667
677
  private
668
678
 
669
679
  def style(underscored_widget_name, styles)
@@ -163,8 +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 = parent
167
- @parent_proxy = @parent&.get_data('proxy')
166
+ @parent_proxy = @parent = parent
167
+ @parent_proxy = @parent&.get_data('proxy') if @parent.respond_to?(:get_data) && @parent.get_data('proxy')
168
168
  @swt_style = SWT::SWTProxy[*swt_constants]
169
169
  options ||= {}
170
170
  @options = self.class.options.merge(options)
@@ -249,6 +249,10 @@ module Glimmer
249
249
  def attribute_setter(attribute_name)
250
250
  "#{attribute_name}="
251
251
  end
252
+
253
+ def disposed?
254
+ swt_widget.isDisposed
255
+ end
252
256
 
253
257
  def has_style?(style)
254
258
  (swt_style & SWT::SWTProxy[style]) == SWT::SWTProxy[style]
@@ -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 {
@@ -264,9 +264,12 @@ class MetaSampleApplication
264
264
  }
265
265
  }
266
266
 
267
- @code_text = code_text {
267
+ @code_text = code_text(lines: {width: 2}) {
268
268
  text bind(SampleDirectory, 'selected_sample.code', read_only: true)
269
269
  editable bind(SampleDirectory, 'selected_sample.editable')
270
+ line_numbers {
271
+ background :white
272
+ }
270
273
  }
271
274
 
272
275
  weights 4, 11
@@ -35,6 +35,7 @@ class Tetris
35
35
  FONT_NAME = 'Menlo'
36
36
  FONT_TITLE_HEIGHT = 32
37
37
  FONT_TITLE_STYLE = :bold
38
+ BEVEL_CONSTANT = 20
38
39
 
39
40
  option :playfield_width, default: Model::Game::PLAYFIELD_WIDTH
40
41
  option :playfield_height, default: Model::Game::PLAYFIELD_HEIGHT
@@ -55,6 +56,15 @@ class Tetris
55
56
  case key_event.keyCode
56
57
  when swt(:arrow_down), 's'.bytes.first
57
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
58
68
  when swt(:arrow_left), 'a'.bytes.first
59
69
  game.left!
60
70
  when swt(:arrow_right), 'd'.bytes.first
@@ -65,8 +75,6 @@ class Tetris
65
75
  elsif key_event.keyLocation == swt(:left) # left shift key
66
76
  game.rotate!(:left)
67
77
  end
68
- when swt(:arrow_up)
69
- game.rotate!(:right)
70
78
  when swt(:ctrl)
71
79
  game.rotate!(:left)
72
80
  end
@@ -76,6 +84,10 @@ class Tetris
76
84
  @about_observer = on_about {
77
85
  show_about_dialog
78
86
  }
87
+
88
+ @quit_observer = on_quit {
89
+ exit(0)
90
+ }
79
91
  }
80
92
  }
81
93
 
@@ -87,6 +99,13 @@ class Tetris
87
99
  start_moving_tetrominos_down
88
100
  end
89
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
90
109
  @game.start!
91
110
  }
92
111
 
@@ -99,10 +118,10 @@ class Tetris
99
118
  margin_height 0
100
119
  horizontal_spacing 0
101
120
  }
102
-
121
+
103
122
  text 'Glimmer Tetris'
104
123
  minimum_size 475, 500
105
- background :gray
124
+ image tetris_icon
106
125
 
107
126
  tetris_menu_bar(game: game)
108
127
 
@@ -118,6 +137,39 @@ class Tetris
118
137
  }
119
138
  }
120
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
+
121
173
  def start_moving_tetrominos_down
122
174
  Thread.new do
123
175
  @mutex.synchronize do
@@ -147,10 +199,11 @@ class Tetris
147
199
  end
148
200
 
149
201
  def deregister_observers
150
- @show_high_scores_observer.deregister
151
- @game_over_observer.deregister
152
- @keyboard_listener.deregister
202
+ @show_high_scores_observer&.deregister
203
+ @game_over_observer&.deregister
204
+ @keyboard_listener&.deregister
153
205
  @about_observer&.deregister
206
+ @quit_observer&.deregister
154
207
  end
155
208
  end
156
209
 
@@ -21,6 +21,7 @@
21
21
 
22
22
  require 'fileutils'
23
23
  require 'etc'
24
+ require 'json'
24
25
  require 'glimmer/data_binding/observer'
25
26
  require 'glimmer/config'
26
27
 
@@ -38,7 +39,7 @@ class Tetris
38
39
  SCORE_MULTIPLIER = {1 => 40, 2 => 100, 3 => 300, 4 => 1200}
39
40
 
40
41
  attr_reader :playfield_width, :playfield_height
41
- attr_accessor :game_over, :paused, :preview_tetromino, :lines, :score, :level, :high_scores, :beeping, :added_high_score
42
+ attr_accessor :game_over, :paused, :preview_tetromino, :lines, :score, :level, :high_scores, :beeping, :added_high_score, :show_high_scores, :up_arrow_action
42
43
  alias game_over? game_over
43
44
  alias paused? paused
44
45
  alias beeping? beeping
@@ -48,10 +49,12 @@ class Tetris
48
49
  @playfield_width = playfield_width
49
50
  @playfield_height = playfield_height
50
51
  @high_scores = []
52
+ @show_high_scores = false
51
53
  @beeping = true
54
+ @up_arrow_action = :rotate_left
52
55
  load_high_scores!
53
56
  end
54
-
57
+
55
58
  def configure_beeper(&beeper)
56
59
  @beeper = beeper
57
60
  end
@@ -61,6 +64,7 @@ class Tetris
61
64
  end
62
65
 
63
66
  def start!
67
+ self.show_high_scores = false
64
68
  self.paused = false
65
69
  self.level = 1
66
70
  self.score = 0
@@ -86,7 +90,7 @@ class Tetris
86
90
 
87
91
  def add_high_score!
88
92
  self.added_high_score = true
89
- high_scores.prepend(PastGame.new("Player #{high_scores.count + 1}", score))
93
+ high_scores.prepend(PastGame.new("Player #{high_scores.count + 1}", score, lines, level))
90
94
  end
91
95
 
92
96
  def save_high_scores!
@@ -115,9 +119,9 @@ class Tetris
115
119
  File.join(tetris_dir, "high_scores.txt")
116
120
  end
117
121
 
118
- def down!
122
+ def down!(instant: false)
119
123
  return unless game_in_progress?
120
- current_tetromino.down!
124
+ current_tetromino.down!(instant: instant)
121
125
  game_over! if current_tetromino.row <= 0 && current_tetromino.stopped?
122
126
  end
123
127
 
@@ -202,6 +206,30 @@ class Tetris
202
206
  @beeper&.call if beeping
203
207
  end
204
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
231
+ end
232
+
205
233
  def reset_tetrominoes
206
234
  @tetrominoes = []
207
235
  end
@@ -247,12 +275,13 @@ class Tetris
247
275
 
248
276
  def playfield_remaining_heights(tetromino = nil)
249
277
  @playfield_width.times.map do |playfield_column|
278
+ bottom_most_block = tetromino.bottom_most_block_for_column(playfield_column)
250
279
  (playfield.each_with_index.detect do |row, playfield_row|
251
280
  !row[playfield_column].clear? &&
252
281
  (
253
282
  tetromino.nil? ||
254
- (bottom_most_block = tetromino.bottom_most_block_for_column(playfield_column)).nil? ||
255
- (playfield_row > tetromino.row + bottom_most_block[:row])
283
+ bottom_most_block.nil? ||
284
+ (playfield_row > tetromino.row + bottom_most_block[:row_index])
256
285
  )
257
286
  end || [nil, @playfield_height])[1]
258
287
  end.to_a
@@ -21,6 +21,19 @@
21
21
 
22
22
  class Tetris
23
23
  module Model
24
- PastGame = Struct.new(:name, :score)
24
+ class PastGame
25
+ attr_accessor :name, :score, :lines, :level
26
+
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
33
+
34
+ def to_a
35
+ [@name, @score, @lines, @level]
36
+ end
37
+ end
25
38
  end
26
39
  end
@@ -93,8 +93,8 @@ class Tetris
93
93
  playfield_remaining_heights = game.playfield_remaining_heights(self)
94
94
  result = bottom_most_blocks.any? do |bottom_most_block|
95
95
  playfield_column = @column + bottom_most_block[:column_index]
96
- playfield_remaining_heights[playfield_column] &&
97
- @row + bottom_most_block[:row] >= playfield_remaining_heights[playfield_column] - 1
96
+ playfield_remaining_heights[playfield_column] &&
97
+ @row + bottom_most_block[:row_index] >= playfield_remaining_heights[playfield_column] - 1
98
98
  end
99
99
  if result && !game.hypothetical?
100
100
  @stopped = result
@@ -114,7 +114,7 @@ class Tetris
114
114
  bottom_most_block_row = row_blocks_with_row_index[1]
115
115
  {
116
116
  block: bottom_most_block,
117
- row: bottom_most_block_row,
117
+ row_index: bottom_most_block_row,
118
118
  column_index: column_index
119
119
  }
120
120
  end
@@ -182,10 +182,15 @@ class Tetris
182
182
  @blocks.size
183
183
  end
184
184
 
185
- def down!
185
+ def down!(instant: false)
186
186
  launch! if preview?
187
187
  unless stopped?
188
- new_row = @row + 1
188
+ block_count = 1
189
+ if instant
190
+ remaining_height, bottom_touching_block = remaining_height_and_bottom_touching_block
191
+ block_count = remaining_height - @row
192
+ end
193
+ new_row = @row + block_count
189
194
  update_playfield(new_row, @column)
190
195
  end
191
196
  end
@@ -245,6 +250,14 @@ class Tetris
245
250
  end
246
251
  end
247
252
 
253
+ def remaining_height_and_bottom_touching_block
254
+ playfield_remaining_heights = game.playfield_remaining_heights(self)
255
+ bottom_most_blocks.map do |bottom_most_block|
256
+ playfield_column = @column + bottom_most_block[:column_index]
257
+ [playfield_remaining_heights[playfield_column] - (bottom_most_block[:row_index] + 1), bottom_most_block]
258
+ end.min_by(&:first)
259
+ end
260
+
248
261
  def default_blocks
249
262
  case @letter
250
263
  when :I
@@ -26,36 +26,31 @@ class Tetris
26
26
 
27
27
  options :game_playfield, :block_size, :row, :column
28
28
 
29
- before_body {
30
- @bevel_constant = 20
31
- }
32
-
33
29
  body {
34
30
  canvas {
35
- layout nil
36
31
  background bind(game_playfield[row][column], :color)
37
- polygon(0, 0, block_size, 0, block_size - 4, 4, 4, 4, fill: true) {
32
+ polygon(0, 0, block_size, 0, block_size - 4, 4, 4, 4) {
38
33
  background bind(game_playfield[row][column], :color) { |color_value|
39
34
  color = color(color_value)
40
- rgb(color.red + 4*@bevel_constant, color.green + 4*@bevel_constant, color.blue + 4*@bevel_constant)
35
+ rgb(color.red + 4*BEVEL_CONSTANT, color.green + 4*BEVEL_CONSTANT, color.blue + 4*BEVEL_CONSTANT)
41
36
  }
42
37
  }
43
- polygon(block_size, 0, block_size - 4, 4, block_size - 4, block_size - 4, block_size, block_size, fill: true) {
38
+ polygon(block_size, 0, block_size - 4, 4, block_size - 4, block_size - 4, block_size, block_size) {
44
39
  background bind(game_playfield[row][column], :color) { |color_value|
45
40
  color = color(color_value)
46
- rgb(color.red - @bevel_constant, color.green - @bevel_constant, color.blue - @bevel_constant)
41
+ rgb(color.red - BEVEL_CONSTANT, color.green - BEVEL_CONSTANT, color.blue - BEVEL_CONSTANT)
47
42
  }
48
43
  }
49
- polygon(block_size, block_size, 0, block_size, 4, block_size - 4, block_size - 4, block_size - 4, fill: true) {
44
+ polygon(block_size, block_size, 0, block_size, 4, block_size - 4, block_size - 4, block_size - 4) {
50
45
  background bind(game_playfield[row][column], :color) { |color_value|
51
46
  color = color(color_value)
52
- rgb(color.red - 2*@bevel_constant, color.green - 2*@bevel_constant, color.blue - 2*@bevel_constant)
47
+ rgb(color.red - 2*BEVEL_CONSTANT, color.green - 2*BEVEL_CONSTANT, color.blue - 2*BEVEL_CONSTANT)
53
48
  }
54
49
  }
55
- polygon(0, 0, 0, block_size, 4, block_size - 4, 4, 4, fill: true) {
50
+ polygon(0, 0, 0, block_size, 4, block_size - 4, 4, 4) {
56
51
  background bind(game_playfield[row][column], :color) { |color_value|
57
52
  color = color(color_value)
58
- rgb(color.red - @bevel_constant, color.green - @bevel_constant, color.blue - @bevel_constant)
53
+ rgb(color.red - BEVEL_CONSTANT, color.green - BEVEL_CONSTANT, color.blue - BEVEL_CONSTANT)
59
54
  }
60
55
  }
61
56
  rectangle(0, 0, block_size, block_size) {
@@ -59,8 +59,14 @@ class Tetris
59
59
  table_column {
60
60
  text 'Score'
61
61
  }
62
+ table_column {
63
+ text 'Lines'
64
+ }
65
+ table_column {
66
+ text 'Level'
67
+ }
62
68
 
63
- items bind(game, :high_scores), column_properties(:name, :score)
69
+ items bind(game, :high_scores, read_only_sort: true), column_properties(:name, :score, :lines, :level)
64
70
  }
65
71
  composite {
66
72
  row_layout :horizontal
@@ -77,31 +83,42 @@ class Tetris
77
83
  focus true # initial focus
78
84
 
79
85
  on_widget_selected {
80
- close
86
+ async_exec { close }
87
+ game.paused = @game_paused
81
88
  game.restart! if game.game_over?
82
89
  }
83
90
  }
84
91
  }
85
92
 
86
93
  on_swt_show {
94
+ @game_paused = game.paused?
95
+ game.paused = true
87
96
  if game.game_over? && game.added_high_score?
88
97
  game.added_high_score = false
98
+ game.save_high_scores!
89
99
  @high_score_table.edit_table_item(
90
100
  @high_score_table.items.first, # row item
91
101
  0, # column
102
+ write_on_cancel: true,
92
103
  after_write: -> {
93
104
  game.save_high_scores!
94
105
  @play_close_button.set_focus
95
106
  },
96
- after_cancel: -> {
97
- @play_close_button.set_focus
98
- },
99
107
  )
100
108
  end
101
109
  }
102
110
 
103
111
  on_shell_closed {
104
- @high_score_table.cancel_edit!
112
+ # guard is needed because there is an observer in Tetris closing on
113
+ # game.show_high_scores change, which gets set below
114
+ unless @closing
115
+ @closing = true
116
+ @high_score_table.cancel_edit!
117
+ game.paused = @game_paused
118
+ game.show_high_scores = false
119
+ else
120
+ @closing = false
121
+ end
105
122
  }
106
123
 
107
124
  on_widget_disposed {