glimmer-dsl-opal 0.25.2 → 0.26.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +25 -0
  3. data/README.md +46 -9
  4. data/VERSION +1 -1
  5. data/lib/glimmer/dsl/opal/combo_selection_data_binding_expression.rb +3 -3
  6. data/lib/glimmer/dsl/opal/menu_expression.rb +0 -3
  7. data/lib/glimmer/swt/c_combo_proxy.rb +30 -1
  8. data/lib/glimmer/swt/combo_proxy.rb +10 -2
  9. data/lib/glimmer/swt/control_editor.rb +2 -2
  10. data/lib/glimmer/swt/dialog_proxy.rb +11 -4
  11. data/lib/glimmer/swt/display_proxy.rb +18 -3
  12. data/lib/glimmer/swt/shell_proxy.rb +28 -1
  13. data/lib/glimmer/swt/table_item_proxy.rb +0 -20
  14. data/lib/glimmer/swt/table_proxy.rb +4 -0
  15. data/lib/glimmer/swt/widget_proxy.rb +48 -7
  16. data/lib/glimmer/ui/custom_shell.rb +2 -0
  17. data/lib/glimmer/ui/custom_widget.rb +2 -0
  18. data/lib/glimmer-dsl-opal/samples/elaborate/login.rb +3 -5
  19. data/lib/glimmer-dsl-opal/samples/elaborate/tetris/model/block.rb +48 -0
  20. data/lib/glimmer-dsl-opal/samples/elaborate/tetris/model/game.rb +275 -0
  21. data/lib/glimmer-dsl-opal/samples/elaborate/tetris/model/past_game.rb +39 -0
  22. data/lib/glimmer-dsl-opal/samples/elaborate/tetris/model/tetromino.rb +329 -0
  23. data/lib/glimmer-dsl-opal/samples/elaborate/tetris/view/block.rb +36 -0
  24. data/lib/glimmer-dsl-opal/samples/elaborate/tetris/view/high_score_dialog.rb +122 -0
  25. data/lib/glimmer-dsl-opal/samples/elaborate/tetris/view/playfield.rb +56 -0
  26. data/lib/glimmer-dsl-opal/samples/elaborate/tetris/view/score_lane.rb +83 -0
  27. data/lib/glimmer-dsl-opal/samples/elaborate/tetris/view/tetris_menu_bar.rb +136 -0
  28. data/lib/glimmer-dsl-opal/samples/elaborate/tetris.rb +150 -0
  29. data/lib/glimmer-dsl-opal/samples/hello/hello_custom_widget.rb +1 -1
  30. data/lib/glimmer-dsl-opal.rb +1 -0
  31. metadata +14 -4
@@ -0,0 +1,39 @@
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
+ 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
38
+ end
39
+ end
@@ -0,0 +1,329 @@
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 'block'
23
+
24
+ require 'matrix'
25
+
26
+ class Tetris
27
+ module Model
28
+ class Tetromino
29
+ ORIENTATIONS = [:north, :east, :south, :west]
30
+
31
+ LETTER_COLORS = {
32
+ I: :cyan,
33
+ J: :blue,
34
+ L: :dark_yellow,
35
+ O: :yellow,
36
+ S: :green,
37
+ T: :magenta,
38
+ Z: :red,
39
+ }
40
+
41
+ attr_reader :game, :letter, :preview
42
+ alias preview? preview
43
+ attr_accessor :orientation, :blocks, :row, :column
44
+
45
+ def initialize(game)
46
+ @game = game
47
+ @letter = LETTER_COLORS.keys.sample
48
+ @orientation = :north
49
+ @blocks = default_blocks
50
+ @preview = true
51
+ new_row = 0
52
+ new_column = ((Model::Game::PREVIEW_PLAYFIELD_WIDTH - width)/2).to_i
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
63
+ new_row = 1 - height
64
+ new_column = ((game.playfield_width - width)/2).to_i
65
+ update_playfield(new_row, new_column)
66
+ game.tetrominoes << self
67
+ end
68
+
69
+ def update_playfield(new_row = nil, new_column = nil)
70
+ remove_from_playfield
71
+ if !new_row.nil? && !new_column.nil?
72
+ @row = new_row
73
+ @column = new_column
74
+ add_to_playfield
75
+ end
76
+ end
77
+
78
+ def add_to_playfield
79
+ update_playfield_block do |playfield_row, playfield_column, row_index, column_index|
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
81
+ end
82
+ end
83
+
84
+ def remove_from_playfield
85
+ return if @row.nil? || @column.nil?
86
+ update_playfield_block do |playfield_row, playfield_column, row_index, column_index|
87
+ playfield[playfield_row][playfield_column].clear if playfield_row >= 0 && !blocks[row_index][column_index].clear? && playfield[playfield_row][playfield_column]&.color == color
88
+ end
89
+ end
90
+
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_index] >= 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
103
+ end
104
+ result
105
+ end
106
+
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
109
+ width.times.map do |column_index|
110
+ row_blocks_with_row_index = @blocks.each_with_index.to_a.reverse.detect do |row_blocks, row_index|
111
+ !row_blocks[column_index].clear?
112
+ end
113
+ bottom_most_block = row_blocks_with_row_index[0][column_index]
114
+ bottom_most_block_row = row_blocks_with_row_index[1]
115
+ {
116
+ block: bottom_most_block,
117
+ row_index: bottom_most_block_row,
118
+ column_index: column_index
119
+ }
120
+ end
121
+ end
122
+
123
+ def bottom_most_block_for_column(column)
124
+ bottom_most_blocks.detect {|bottom_most_block| (@column + bottom_most_block[:column_index]) == column}
125
+ end
126
+
127
+ def right_blocked?
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
+ }
133
+ end
134
+
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
150
+ end
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
+
177
+ def width
178
+ @blocks[0].size
179
+ end
180
+
181
+ def height
182
+ @blocks.size
183
+ end
184
+
185
+ def down!(instant: false)
186
+ launch! if preview?
187
+ unless stopped?
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
194
+ update_playfield(new_row, @column)
195
+ end
196
+ end
197
+
198
+ def left!
199
+ unless left_blocked?
200
+ new_column = @column - 1
201
+ update_playfield(@row, new_column)
202
+ end
203
+ end
204
+
205
+ def right!
206
+ unless right_blocked?
207
+ new_column = @column + 1
208
+ update_playfield(@row, new_column)
209
+ end
210
+ end
211
+
212
+ # Rotate in specified direcation, which can be :right (clockwise) or :left (counterclockwise)
213
+ def rotate!(direction)
214
+ return if stopped?
215
+ can_rotate = nil
216
+ new_blocks = nil
217
+ game.hypothetical do
218
+ hypothetical_rotated_tetromino = hypothetical_tetromino
219
+ new_blocks = hypothetical_rotated_tetromino.rotate_blocks(direction)
220
+ can_rotate = !hypothetical_rotated_tetromino.stopped? && !hypothetical_rotated_tetromino.right_blocked? && !hypothetical_rotated_tetromino.left_blocked?
221
+ end
222
+ if can_rotate
223
+ remove_from_playfield
224
+ self.orientation = ORIENTATIONS[ORIENTATIONS.rotate(direction == :right ? -1 : 1).index(@orientation)]
225
+ self.blocks = new_blocks
226
+ update_playfield(@row, @column)
227
+ end
228
+ rescue => e
229
+ puts e.full_message
230
+ end
231
+
232
+ def rotate_blocks(direction)
233
+ new_blocks = Matrix[*@blocks].transpose.to_a
234
+ if direction == :right
235
+ new_blocks = new_blocks.map(&:reverse)
236
+ else
237
+ new_blocks = new_blocks.reverse
238
+ end
239
+ Matrix[*new_blocks].to_a
240
+ end
241
+
242
+ def hypothetical_tetromino
243
+ clone.tap do |hypo_clone|
244
+ remove_from_playfield
245
+ hypo_clone.blocks = @blocks.map do |row_blocks|
246
+ row_blocks.map do |column_block|
247
+ column_block.clone
248
+ end
249
+ end
250
+ end
251
+ end
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
+
261
+ def default_blocks
262
+ case @letter
263
+ when :I
264
+ [
265
+ [block, block, block, block]
266
+ ]
267
+ when :J
268
+ [
269
+ [block, block, block],
270
+ [empty, empty, block],
271
+ ]
272
+ when :L
273
+ [
274
+ [block, block, block],
275
+ [block, empty, empty],
276
+ ]
277
+ when :O
278
+ [
279
+ [block, block],
280
+ [block, block],
281
+ ]
282
+ when :S
283
+ [
284
+ [empty, block, block],
285
+ [block, block, empty],
286
+ ]
287
+ when :T
288
+ [
289
+ [block, block, block],
290
+ [empty, block, empty],
291
+ ]
292
+ when :Z
293
+ [
294
+ [block, block, empty],
295
+ [empty, block, block],
296
+ ]
297
+ end
298
+ end
299
+
300
+ def color
301
+ LETTER_COLORS[@letter]
302
+ end
303
+
304
+ def include_block?(block)
305
+ @blocks.flatten.include?(block)
306
+ end
307
+
308
+ private
309
+
310
+ def block
311
+ Block.new(color)
312
+ end
313
+
314
+ def empty
315
+ Block.new
316
+ end
317
+
318
+ def update_playfield_block(&updater)
319
+ @row.upto(@row + height - 1) do |playfield_row|
320
+ @column.upto(@column + width - 1) do |playfield_column|
321
+ row_index = playfield_row - @row
322
+ column_index = playfield_column - @column
323
+ updater.call(playfield_row, playfield_column, row_index, column_index)
324
+ end
325
+ end
326
+ end
327
+ end
328
+ end
329
+ end
@@ -0,0 +1,36 @@
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 View
24
+ class Block
25
+ include Glimmer::UI::CustomWidget
26
+
27
+ options :game_playfield, :block_size, :row, :column
28
+
29
+ body {
30
+ canvas { |canvas_proxy|
31
+ background <= [game_playfield[row][column], :color]
32
+ }
33
+ }
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,122 @@
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 'Glimmer Tetris'
44
+
45
+ tetris_menu_bar(game: game)
46
+
47
+ label(:center) {
48
+ text <= [game, :game_over, on_read: ->(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
+ table_column {
63
+ text 'Lines'
64
+ }
65
+ table_column {
66
+ text 'Level'
67
+ }
68
+
69
+ items <=> [game, :high_scores, read_only_sort: true, column_properties: [:name, :score, :lines, :level]]
70
+ }
71
+ composite {
72
+ row_layout :horizontal
73
+
74
+ @play_close_button = button {
75
+ text <= [game, :game_over, on_read: ->(game_over) { game_over ? 'Play Again?' : 'Close'}]
76
+ focus true # initial focus
77
+
78
+ on_widget_selected {
79
+ close
80
+ game.paused = @game_paused
81
+ game.restart! if game.game_over?
82
+ }
83
+ }
84
+ }
85
+
86
+ on_swt_show {
87
+ @game_paused = game.paused?
88
+ game.paused = true
89
+ if game.game_over? && game.added_high_score?
90
+ game.added_high_score = false
91
+ @high_score_table.edit_table_item(
92
+ @high_score_table.items.first, # row item
93
+ 0, # column
94
+ write_on_cancel: true,
95
+ after_write: -> {
96
+ @play_close_button.set_focus
97
+ },
98
+ )
99
+ end
100
+ }
101
+
102
+ on_shell_closed {
103
+ # guard is needed because there is an observer in Tetris closing on
104
+ # game.show_high_scores change, which gets set below
105
+ unless @closing
106
+ @closing = true
107
+ @high_score_table.cancel_edit!
108
+ game.paused = @game_paused
109
+ game.show_high_scores = false
110
+ else
111
+ @closing = false
112
+ end
113
+ }
114
+
115
+ on_widget_disposed {
116
+ @game_over_observer.deregister
117
+ }
118
+ }
119
+ }
120
+ end
121
+ end
122
+ end