glimmer-dsl-swt 4.18.2.3 → 4.18.3.2

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 +59 -0
  3. data/README.md +267 -39
  4. data/VERSION +1 -1
  5. data/glimmer-dsl-swt.gemspec +14 -6
  6. data/lib/ext/glimmer/config.rb +24 -7
  7. data/lib/glimmer/data_binding/widget_binding.rb +14 -4
  8. data/lib/glimmer/dsl/swt/color_expression.rb +4 -4
  9. data/lib/glimmer/dsl/swt/data_binding_expression.rb +3 -3
  10. data/lib/glimmer/dsl/swt/dsl.rb +1 -0
  11. data/lib/glimmer/dsl/swt/multiply_expression.rb +53 -0
  12. data/lib/glimmer/dsl/swt/property_expression.rb +4 -2
  13. data/lib/glimmer/dsl/swt/shape_expression.rb +2 -4
  14. data/lib/glimmer/dsl/swt/transform_expression.rb +55 -0
  15. data/lib/glimmer/dsl/swt/widget_expression.rb +2 -1
  16. data/lib/glimmer/swt/color_proxy.rb +28 -6
  17. data/lib/glimmer/swt/custom/drawable.rb +8 -0
  18. data/lib/glimmer/swt/custom/shape.rb +66 -26
  19. data/lib/glimmer/swt/directory_dialog_proxy.rb +3 -3
  20. data/lib/glimmer/swt/display_proxy.rb +25 -4
  21. data/lib/glimmer/swt/file_dialog_proxy.rb +3 -3
  22. data/lib/glimmer/swt/layout_data_proxy.rb +3 -3
  23. data/lib/glimmer/swt/shell_proxy.rb +20 -5
  24. data/lib/glimmer/swt/table_proxy.rb +19 -4
  25. data/lib/glimmer/swt/transform_proxy.rb +109 -0
  26. data/lib/glimmer/swt/widget_listener_proxy.rb +14 -5
  27. data/lib/glimmer/swt/widget_proxy.rb +31 -20
  28. data/lib/glimmer/ui/custom_shell.rb +13 -11
  29. data/lib/glimmer/ui/custom_widget.rb +68 -44
  30. data/samples/elaborate/meta_sample.rb +81 -24
  31. data/samples/elaborate/tetris.rb +102 -47
  32. data/samples/elaborate/tetris/model/block.rb +2 -2
  33. data/samples/elaborate/tetris/model/game.rb +236 -74
  34. data/samples/elaborate/tetris/model/past_game.rb +26 -0
  35. data/samples/elaborate/tetris/model/tetromino.rb +123 -35
  36. data/samples/elaborate/tetris/view/block.rb +34 -9
  37. data/samples/elaborate/tetris/view/high_score_dialog.rb +114 -0
  38. data/samples/elaborate/tetris/view/playfield.rb +12 -5
  39. data/samples/elaborate/tetris/view/score_lane.rb +87 -0
  40. data/samples/elaborate/tetris/view/tetris_menu_bar.rb +123 -0
  41. data/samples/elaborate/tic_tac_toe.rb +4 -4
  42. data/samples/hello/hello_canvas_transform.rb +40 -0
  43. data/samples/hello/hello_link.rb +1 -1
  44. metadata +12 -4
@@ -0,0 +1,26 @@
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
+ class Tetris
23
+ module Model
24
+ PastGame = Struct.new(:name, :score)
25
+ end
26
+ end
@@ -38,16 +38,32 @@ class Tetris
38
38
  Z: :red,
39
39
  }
40
40
 
41
- attr_reader :letter
41
+ attr_reader :game, :letter, :preview
42
+ alias preview? preview
42
43
  attr_accessor :orientation, :blocks, :row, :column
43
44
 
44
- def initialize
45
+ def initialize(game)
46
+ @game = game
45
47
  @letter = LETTER_COLORS.keys.sample
46
48
  @orientation = :north
47
49
  @blocks = default_blocks
50
+ @preview = true
51
+ new_row = 0
52
+ new_column = (Model::Game::PREVIEW_PLAYFIELD_WIDTH - width)/2
53
+ update_playfield(new_row, new_column)
54
+ end
55
+
56
+ def playfield
57
+ @preview ? game.preview_playfield : game.playfield
58
+ end
59
+
60
+ def launch!
61
+ remove_from_playfield
62
+ @preview = false
48
63
  new_row = 1 - height
49
- new_column = (PLAYFIELD_WIDTH - width)/2
64
+ new_column = (game.playfield_width - width)/2
50
65
  update_playfield(new_row, new_column)
66
+ game.tetrominoes << self
51
67
  end
52
68
 
53
69
  def update_playfield(new_row = nil, new_column = nil)
@@ -61,58 +77,103 @@ class Tetris
61
77
 
62
78
  def add_to_playfield
63
79
  update_playfield_block do |playfield_row, playfield_column, row_index, column_index|
64
- Game.playfield[playfield_row][playfield_column].color = blocks[row_index][column_index].color if playfield_row >= 0 && Game.playfield[playfield_row][playfield_column].clear? && !blocks[row_index][column_index].clear?
80
+ playfield[playfield_row][playfield_column].color = blocks[row_index][column_index].color if playfield_row >= 0 && playfield[playfield_row][playfield_column]&.clear? && !blocks[row_index][column_index].clear? && playfield[playfield_row][playfield_column].color != blocks[row_index][column_index].color
65
81
  end
66
82
  end
67
83
 
68
84
  def remove_from_playfield
69
85
  return if @row.nil? || @column.nil?
70
86
  update_playfield_block do |playfield_row, playfield_column, row_index, column_index|
71
- Game.playfield[playfield_row][playfield_column].clear if playfield_row >= 0 && !blocks[row_index][column_index].clear? && Game.playfield[playfield_row][playfield_column].color == color
87
+ playfield[playfield_row][playfield_column].clear if playfield_row >= 0 && !blocks[row_index][column_index].clear? && playfield[playfield_row][playfield_column]&.color == color
72
88
  end
73
89
  end
74
90
 
75
- def stopped?(blocks: nil)
76
- blocks ||= @blocks
77
- playfield_remaining_heights = Game.playfield_remaining_heights(self)
78
- result = bottom_blocks(blocks).any? do |bottom_block|
79
- playfield_column = @column + bottom_block[:column_index]
80
- !bottom_block[:block].clear? &&
81
- (@row + bottom_block[:row]) >= playfield_remaining_heights[playfield_column] - 1
91
+ def stopped?
92
+ return true if @stopped || @preview
93
+ playfield_remaining_heights = game.playfield_remaining_heights(self)
94
+ result = bottom_most_blocks.any? do |bottom_most_block|
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
98
+ end
99
+ if result && !game.hypothetical?
100
+ @stopped = result
101
+ game.consider_eliminating_lines
102
+ @game.consider_adding_tetromino
82
103
  end
83
- Game.consider_eliminating_lines if result
84
104
  result
85
105
  end
86
106
 
87
- # Returns blocks at the bottom of a tetromino, which could be from multiple rows depending on shape (e.g. T)
88
- def bottom_blocks(blocks = nil)
89
- blocks ||= @blocks
107
+ # Returns bottom-most blocks of a tetromino, which could be from multiple rows depending on shape (e.g. T)
108
+ def bottom_most_blocks
90
109
  width.times.map do |column_index|
91
110
  row_blocks_with_row_index = @blocks.each_with_index.to_a.reverse.detect do |row_blocks, row_index|
92
111
  !row_blocks[column_index].clear?
93
112
  end
94
- bottom_block = row_blocks_with_row_index[0][column_index]
95
- bottom_block_row = row_blocks_with_row_index[1]
113
+ bottom_most_block = row_blocks_with_row_index[0][column_index]
114
+ bottom_most_block_row = row_blocks_with_row_index[1]
96
115
  {
97
- block: bottom_block,
98
- row: bottom_block_row,
116
+ block: bottom_most_block,
117
+ row: bottom_most_block_row,
99
118
  column_index: column_index
100
119
  }
101
120
  end
102
121
  end
103
122
 
104
- def bottom_block_for_column(column)
105
- bottom_blocks.detect {|bottom_block| (@column + bottom_block[:column_index]) == column}
123
+ def bottom_most_block_for_column(column)
124
+ bottom_most_blocks.detect {|bottom_most_block| (@column + bottom_most_block[:column_index]) == column}
106
125
  end
107
126
 
108
127
  def right_blocked?
109
- (@column == PLAYFIELD_WIDTH - width) || Game.playfield[row][column + width].occupied?
128
+ (@column == game.playfield_width - width) ||
129
+ right_most_blocks.any? { |right_most_block|
130
+ (@row + right_most_block[:row_index]) >= 0 &&
131
+ playfield[@row + right_most_block[:row_index]][@column + right_most_block[:column_index] + 1].occupied?
132
+ }
110
133
  end
111
134
 
112
- def left_blocked?
113
- (@column == 0) || Game.playfield[row][column - 1].occupied?
135
+ # Returns right-most blocks of a tetromino, which could be from multiple columns depending on shape (e.g. T)
136
+ def right_most_blocks
137
+ @blocks.each_with_index.map do |row_blocks, row_index|
138
+ column_block_with_column_index = row_blocks.each_with_index.to_a.reverse.detect do |column_block, column_index|
139
+ !column_block.clear?
140
+ end
141
+ if column_block_with_column_index
142
+ right_most_block = column_block_with_column_index[0]
143
+ {
144
+ block: right_most_block,
145
+ row_index: row_index,
146
+ column_index: column_block_with_column_index[1]
147
+ }
148
+ end
149
+ end.compact
114
150
  end
115
151
 
152
+ def left_blocked?
153
+ (@column == 0) ||
154
+ left_most_blocks.any? { |left_most_block|
155
+ (@row + left_most_block[:row_index]) >= 0 &&
156
+ playfield[@row + left_most_block[:row_index]][@column + left_most_block[:column_index] - 1].occupied?
157
+ }
158
+ end
159
+
160
+ # Returns right-most blocks of a tetromino, which could be from multiple columns depending on shape (e.g. T)
161
+ def left_most_blocks
162
+ @blocks.each_with_index.map do |row_blocks, row_index|
163
+ column_block_with_column_index = row_blocks.each_with_index.to_a.detect do |column_block, column_index|
164
+ !column_block.clear?
165
+ end
166
+ if column_block_with_column_index
167
+ left_most_block = column_block_with_column_index[0]
168
+ {
169
+ block: left_most_block,
170
+ row_index: row_index,
171
+ column_index: column_block_with_column_index[1]
172
+ }
173
+ end
174
+ end.compact
175
+ end
176
+
116
177
  def width
117
178
  @blocks[0].size
118
179
  end
@@ -121,21 +182,22 @@ class Tetris
121
182
  @blocks.size
122
183
  end
123
184
 
124
- def down
185
+ def down!
186
+ launch! if preview?
125
187
  unless stopped?
126
188
  new_row = @row + 1
127
189
  update_playfield(new_row, @column)
128
190
  end
129
191
  end
130
192
 
131
- def left
193
+ def left!
132
194
  unless left_blocked?
133
195
  new_column = @column - 1
134
196
  update_playfield(@row, new_column)
135
197
  end
136
198
  end
137
199
 
138
- def right
200
+ def right!
139
201
  unless right_blocked?
140
202
  new_column = @column + 1
141
203
  update_playfield(@row, new_column)
@@ -143,21 +205,43 @@ class Tetris
143
205
  end
144
206
 
145
207
  # Rotate in specified direcation, which can be :right (clockwise) or :left (counterclockwise)
146
- def rotate(direction)
208
+ def rotate!(direction)
147
209
  return if stopped?
148
- array_rotation_value = direction == :right ? -1 : 1
149
- self.orientation = ORIENTATIONS[ORIENTATIONS.rotate(array_rotation_value).index(@orientation)]
210
+ can_rotate = nil
211
+ new_blocks = nil
212
+ game.hypothetical do
213
+ hypothetical_rotated_tetromino = hypothetical_tetromino
214
+ new_blocks = hypothetical_rotated_tetromino.rotate_blocks(direction)
215
+ can_rotate = !hypothetical_rotated_tetromino.stopped? && !hypothetical_rotated_tetromino.right_blocked? && !hypothetical_rotated_tetromino.left_blocked?
216
+ end
217
+ if can_rotate
218
+ remove_from_playfield
219
+ self.orientation = ORIENTATIONS[ORIENTATIONS.rotate(direction == :right ? -1 : 1).index(@orientation)]
220
+ self.blocks = new_blocks
221
+ update_playfield(@row, @column)
222
+ end
223
+ rescue => e
224
+ puts e.full_message
225
+ end
226
+
227
+ def rotate_blocks(direction)
150
228
  new_blocks = Matrix[*@blocks].transpose.to_a
151
229
  if direction == :right
152
230
  new_blocks = new_blocks.map(&:reverse)
153
231
  else
154
232
  new_blocks = new_blocks.reverse
155
233
  end
156
- new_blocks = Matrix[*new_blocks].to_a
157
- unless stopped?(blocks: new_blocks) || right_blocked? || left_blocked?
234
+ Matrix[*new_blocks].to_a
235
+ end
236
+
237
+ def hypothetical_tetromino
238
+ clone.tap do |hypo_clone|
158
239
  remove_from_playfield
159
- self.blocks = new_blocks
160
- update_playfield(@row, @column)
240
+ hypo_clone.blocks = @blocks.map do |row_blocks|
241
+ row_blocks.map do |column_block|
242
+ column_block.clone
243
+ end
244
+ end
161
245
  end
162
246
  end
163
247
 
@@ -204,6 +288,10 @@ class Tetris
204
288
  LETTER_COLORS[@letter]
205
289
  end
206
290
 
291
+ def include_block?(block)
292
+ @blocks.flatten.include?(block)
293
+ end
294
+
207
295
  private
208
296
 
209
297
  def block
@@ -24,19 +24,44 @@ class Tetris
24
24
  class Block
25
25
  include Glimmer::UI::CustomWidget
26
26
 
27
- options :block_size, :row, :column
27
+ options :game_playfield, :block_size, :row, :column
28
+
29
+ before_body {
30
+ @bevel_constant = 20
31
+ }
28
32
 
29
33
  body {
30
- composite {
34
+ canvas {
31
35
  layout nil
32
- layout_data {
33
- width_hint block_size
34
- height_hint block_size
36
+ background bind(game_playfield[row][column], :color)
37
+ polygon(0, 0, block_size, 0, block_size - 4, 4, 4, 4, fill: true) {
38
+ background bind(game_playfield[row][column], :color) { |color_value|
39
+ color = color(color_value)
40
+ rgb(color.red + 4*@bevel_constant, color.green + 4*@bevel_constant, color.blue + 4*@bevel_constant)
41
+ }
42
+ }
43
+ polygon(block_size, 0, block_size - 4, 4, block_size - 4, block_size - 4, block_size, block_size, fill: true) {
44
+ background bind(game_playfield[row][column], :color) { |color_value|
45
+ color = color(color_value)
46
+ rgb(color.red - @bevel_constant, color.green - @bevel_constant, color.blue - @bevel_constant)
47
+ }
48
+ }
49
+ polygon(block_size, block_size, 0, block_size, 4, block_size - 4, block_size - 4, block_size - 4, fill: true) {
50
+ background bind(game_playfield[row][column], :color) { |color_value|
51
+ color = color(color_value)
52
+ rgb(color.red - 2*@bevel_constant, color.green - 2*@bevel_constant, color.blue - 2*@bevel_constant)
53
+ }
54
+ }
55
+ polygon(0, 0, 0, block_size, 4, block_size - 4, 4, 4, fill: true) {
56
+ background bind(game_playfield[row][column], :color) { |color_value|
57
+ color = color(color_value)
58
+ rgb(color.red - @bevel_constant, color.green - @bevel_constant, color.blue - @bevel_constant)
59
+ }
35
60
  }
36
- background bind(Model::Game.playfield[row][column], :color)
37
- rectangle(0, 0, block_size, block_size)
38
- rectangle(3, 3, block_size - 6, block_size - 6) {
39
- foreground :gray
61
+ rectangle(0, 0, block_size, block_size) {
62
+ foreground bind(game_playfield[row][column], :color) { |color_value|
63
+ color_value == Model::Block::COLOR_CLEAR ? :gray : color_value
64
+ }
40
65
  }
41
66
  }
42
67
  }
@@ -0,0 +1,114 @@
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_relative 'tetris_menu_bar'
23
+
24
+ class Tetris
25
+ module View
26
+ class HighScoreDialog
27
+ include Glimmer::UI::CustomShell
28
+
29
+ options :parent_shell, :game
30
+
31
+ after_body {
32
+ @game_over_observer = observe(game, :game_over) do |game_over|
33
+ close if !game_over
34
+ end
35
+ }
36
+
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 bind(game, :game_over) {|game_over| game_over ? 'Game Over!' : 'High Scores'}
49
+ font name: FONT_NAME, height: FONT_TITLE_HEIGHT, style: FONT_TITLE_STYLE
50
+ }
51
+ @high_score_table = table {
52
+ layout_data {
53
+ height 100
54
+ }
55
+
56
+ table_column {
57
+ text 'Name'
58
+ }
59
+ table_column {
60
+ text 'Score'
61
+ }
62
+
63
+ items bind(game, :high_scores), column_properties(:name, :score)
64
+ }
65
+ composite {
66
+ row_layout :horizontal
67
+
68
+ button {
69
+ text 'Clear'
70
+
71
+ on_widget_selected {
72
+ game.clear_high_scores!
73
+ }
74
+ }
75
+ @play_close_button = button {
76
+ text bind(game, :game_over) {|game_over| game_over ? 'Play Again?' : 'Close'}
77
+ focus true # initial focus
78
+
79
+ on_widget_selected {
80
+ close
81
+ game.restart! if game.game_over?
82
+ }
83
+ }
84
+ }
85
+
86
+ on_swt_show {
87
+ if game.game_over? && game.added_high_score?
88
+ game.added_high_score = false
89
+ @high_score_table.edit_table_item(
90
+ @high_score_table.items.first, # row item
91
+ 0, # column
92
+ after_write: -> {
93
+ game.save_high_scores!
94
+ @play_close_button.set_focus
95
+ },
96
+ after_cancel: -> {
97
+ @play_close_button.set_focus
98
+ },
99
+ )
100
+ end
101
+ }
102
+
103
+ on_shell_closed {
104
+ @high_score_table.cancel_edit!
105
+ }
106
+
107
+ on_widget_disposed {
108
+ @game_over_observer.deregister
109
+ }
110
+ }
111
+ }
112
+ end
113
+ end
114
+ end
@@ -26,20 +26,27 @@ class Tetris
26
26
  class Playfield
27
27
  include Glimmer::UI::CustomWidget
28
28
 
29
- options :playfield_width, :playfield_height, :block_size
29
+ options :game_playfield, :playfield_width, :playfield_height, :block_size
30
30
 
31
31
  body {
32
- composite {
33
- grid_layout(playfield_width, true) {
32
+ canvas {
33
+ grid_layout {
34
+ num_columns playfield_width
35
+ make_columns_equal_width true
34
36
  margin_width block_size
35
37
  margin_height block_size
36
38
  horizontal_spacing 0
37
39
  vertical_spacing 0
38
40
  }
39
-
41
+
40
42
  playfield_height.times { |row|
41
43
  playfield_width.times { |column|
42
- block(block_size: block_size, row: row, column: column)
44
+ block(game_playfield: game_playfield, block_size: block_size, row: row, column: column) {
45
+ layout_data {
46
+ width_hint block_size
47
+ height_hint block_size
48
+ }
49
+ }
43
50
  }
44
51
  }
45
52
  }