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
@@ -19,167 +19,289 @@
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
27
34
  class Game
35
+ PLAYFIELD_WIDTH = 10
36
+ PLAYFIELD_HEIGHT = 20
37
+ PREVIEW_PLAYFIELD_WIDTH = 4
38
+ PREVIEW_PLAYFIELD_HEIGHT = 2
28
39
  SCORE_MULTIPLIER = {1 => 40, 2 => 100, 3 => 300, 4 => 1200}
29
40
 
30
- class << self
31
- attr_accessor :game_over, :preview_tetromino, :lines, :score, :level
32
- alias game_over? game_over
33
-
34
- def consider_adding_tetromino
35
- if tetrominoes.empty? || Game.current_tetromino.stopped?
36
- preview_tetromino.launch!
37
- preview_next_tetromino!
38
- end
39
- end
40
-
41
- def current_tetromino
42
- tetrominoes.last
43
- end
41
+ attr_reader :playfield_width, :playfield_height
42
+ attr_accessor :game_over, :paused, :preview_tetromino, :lines, :score, :level, :high_scores, :beeping, :added_high_score, :show_high_scores, :up_arrow_action
43
+ alias game_over? game_over
44
+ alias paused? paused
45
+ alias beeping? beeping
46
+ alias added_high_score? added_high_score
44
47
 
45
- def tetrominoes
46
- @tetrominoes ||= reset_tetrominoes
48
+ def initialize(playfield_width = PLAYFIELD_WIDTH, playfield_height = PLAYFIELD_HEIGHT)
49
+ @playfield_width = playfield_width
50
+ @playfield_height = playfield_height
51
+ @high_scores = []
52
+ @show_high_scores = false
53
+ @beeping = true
54
+ @up_arrow_action = :instant_down
55
+ load_high_scores!
56
+ end
57
+
58
+ def configure_beeper(&beeper)
59
+ @beeper = beeper
60
+ end
61
+
62
+ def game_in_progress?
63
+ !game_over? && !paused?
64
+ end
65
+
66
+ def start!
67
+ self.show_high_scores = false
68
+ self.paused = false
69
+ self.level = 1
70
+ self.score = 0
71
+ self.lines = 0
72
+ reset_playfield
73
+ reset_preview_playfield
74
+ reset_tetrominoes
75
+ preview_next_tetromino!
76
+ consider_adding_tetromino
77
+ self.game_over = false
78
+ end
79
+ alias restart! start!
80
+
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")) }
47
108
  end
48
-
49
- # Returns blocks in the playfield
50
- def playfield
51
- @playfield ||= @original_playfield = PLAYFIELD_HEIGHT.times.map {
52
- PLAYFIELD_WIDTH.times.map {
53
- Block.new
54
- }
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)
123
+ return unless game_in_progress?
124
+ current_tetromino.down!(instant: instant)
125
+ game_over! if current_tetromino.row <= 0 && current_tetromino.stopped?
126
+ end
127
+
128
+ def right!
129
+ return unless game_in_progress?
130
+ current_tetromino.right!
131
+ end
132
+
133
+ def left!
134
+ return unless game_in_progress?
135
+ current_tetromino.left!
136
+ end
137
+
138
+ def rotate!(direction)
139
+ return unless game_in_progress?
140
+ current_tetromino.rotate!(direction)
141
+ end
142
+
143
+ def current_tetromino
144
+ tetrominoes.last
145
+ end
146
+
147
+ def tetrominoes
148
+ @tetrominoes ||= reset_tetrominoes
149
+ end
150
+
151
+ # Returns blocks in the playfield
152
+ def playfield
153
+ @playfield ||= @original_playfield = @playfield_height.times.map {
154
+ @playfield_width.times.map {
155
+ Block.new
55
156
  }
56
- end
57
-
58
- def hypothetical(&block)
59
- @playfield = hypothetical_playfield
60
- block.call
61
- @playfield = @original_playfield
62
- end
63
-
64
- def hypothetical?
65
- @playfield != @original_playfield
66
- end
67
-
68
- def hypothetical_playfield
69
- PLAYFIELD_HEIGHT.times.map { |row|
70
- PLAYFIELD_WIDTH.times.map { |column|
71
- playfield[row][column].clone
72
- }
157
+ }
158
+ end
159
+
160
+ # Executes a hypothetical scenario without truly changing playfield permanently
161
+ def hypothetical(&block)
162
+ @playfield = hypothetical_playfield
163
+ block.call
164
+ @playfield = @original_playfield
165
+ end
166
+
167
+ # Returns whether currently executing a hypothetical scenario
168
+ def hypothetical?
169
+ @playfield != @original_playfield
170
+ end
171
+
172
+ def hypothetical_playfield
173
+ @playfield_height.times.map { |row|
174
+ @playfield_width.times.map { |column|
175
+ playfield[row][column].clone
73
176
  }
74
- end
75
-
76
- def preview_playfield
77
- @preview_playfield ||= PREVIEW_PLAYFIELD_HEIGHT.times.map {|row|
78
- PREVIEW_PLAYFIELD_WIDTH.times.map {|column|
79
- Block.new
80
- }
177
+ }
178
+ end
179
+
180
+ def preview_playfield
181
+ @preview_playfield ||= PREVIEW_PLAYFIELD_HEIGHT.times.map {|row|
182
+ PREVIEW_PLAYFIELD_WIDTH.times.map {|column|
183
+ Block.new
81
184
  }
82
- end
83
-
84
- def preview_next_tetromino!
85
- self.preview_tetromino = Tetromino.new
86
- end
87
-
88
- def calculate_score!(eliminated_lines)
89
- new_score = SCORE_MULTIPLIER[eliminated_lines] * (level + 1)
90
- self.score += new_score
91
- end
92
-
93
- def level_up!
94
- self.level += 1 if lines >= self.level*10
95
- end
96
-
97
- def delay
98
- [1.1 - (level.to_i * 0.1), 0.001].max
99
- end
100
-
101
- def consider_eliminating_lines
102
- eliminated_lines = 0
103
- playfield.each_with_index do |row, playfield_row|
104
- if row.all? {|block| !block.clear?}
105
- eliminated_lines += 1
106
- shift_blocks_down_above_row(playfield_row)
107
- end
108
- end
109
- if eliminated_lines > 0
110
- beep
111
- self.lines += eliminated_lines
112
- level_up!
113
- calculate_score!(eliminated_lines)
185
+ }
186
+ end
187
+
188
+ def preview_next_tetromino!
189
+ self.preview_tetromino = Tetromino.new(self)
190
+ end
191
+
192
+ def calculate_score!(eliminated_lines)
193
+ new_score = SCORE_MULTIPLIER[eliminated_lines] * (level + 1)
194
+ self.score += new_score
195
+ end
196
+
197
+ def level_up!
198
+ self.level += 1 if lines >= self.level*10
199
+ end
200
+
201
+ def delay
202
+ [1.1 - (level.to_i * 0.1), 0.001].max
203
+ end
204
+
205
+ def beep
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
231
+ end
232
+
233
+ def reset_tetrominoes
234
+ @tetrominoes = []
235
+ end
236
+
237
+ def reset_playfield
238
+ playfield.each do |row|
239
+ row.each do |block|
240
+ block.clear
114
241
  end
115
242
  end
116
-
117
- def beep
118
- @beeper&.call
119
- end
120
-
121
- def configure_beeper(&beeper)
122
- @beeper = beeper
123
- end
124
-
125
- def shift_blocks_down_above_row(row)
126
- row.downto(0) do |playfield_row|
127
- playfield[playfield_row].each_with_index do |block, playfield_column|
128
- previous_row = playfield[playfield_row - 1]
129
- previous_block = previous_row[playfield_column]
130
- block.color = previous_block.color unless block.color == previous_block.color
131
- end
243
+ end
244
+
245
+ def reset_preview_playfield
246
+ preview_playfield.each do |row|
247
+ row.each do |block|
248
+ block.clear
132
249
  end
133
- playfield[0].each(&:clear)
134
250
  end
135
-
136
- def start
137
- self.level = 1
138
- self.score = 0
139
- self.lines = @previoius_lines = 0
140
- reset_playfield
141
- reset_preview_playfield
142
- reset_tetrominoes
251
+ end
252
+
253
+ def consider_adding_tetromino
254
+ if tetrominoes.empty? || current_tetromino.stopped?
255
+ preview_tetromino.launch!
143
256
  preview_next_tetromino!
144
- consider_adding_tetromino
145
- self.game_over = false
146
- end
147
- alias restart start
148
-
149
- def reset_tetrominoes
150
- @tetrominoes = []
151
257
  end
152
-
153
- def reset_playfield
154
- playfield.each do |row|
155
- row.each do |block|
156
- block.clear
157
- end
258
+ end
259
+
260
+ def consider_eliminating_lines
261
+ eliminated_lines = 0
262
+ playfield.each_with_index do |row, playfield_row|
263
+ if row.all? {|block| !block.clear?}
264
+ eliminated_lines += 1
265
+ shift_blocks_down_above_row(playfield_row)
158
266
  end
159
267
  end
160
-
161
- def reset_preview_playfield
162
- preview_playfield.each do |row|
163
- row.each do |block|
164
- block.clear
165
- end
166
- end
268
+ if eliminated_lines > 0
269
+ beep
270
+ self.lines += eliminated_lines
271
+ level_up!
272
+ calculate_score!(eliminated_lines)
167
273
  end
168
-
169
- def playfield_remaining_heights(tetromino = nil)
170
- PLAYFIELD_WIDTH.times.map do |playfield_column|
171
- (playfield.each_with_index.detect do |row, playfield_row|
172
- !row[playfield_column].clear? &&
173
- (
174
- tetromino.nil? ||
175
- (bottom_most_block = tetromino.bottom_most_block_for_column(playfield_column)).nil? ||
176
- (playfield_row > tetromino.row + bottom_most_block[:row])
177
- )
178
- end || [nil, PLAYFIELD_HEIGHT])[1]
179
- end.to_a
274
+ end
275
+
276
+ def playfield_remaining_heights(tetromino = nil)
277
+ @playfield_width.times.map do |playfield_column|
278
+ bottom_most_block = tetromino.bottom_most_block_for_column(playfield_column)
279
+ (playfield.each_with_index.detect do |row, playfield_row|
280
+ !row[playfield_column].clear? &&
281
+ (
282
+ tetromino.nil? ||
283
+ bottom_most_block.nil? ||
284
+ (playfield_row > tetromino.row + bottom_most_block[:row_index])
285
+ )
286
+ end || [nil, @playfield_height])[1]
287
+ end.to_a
288
+ end
289
+
290
+ private
291
+
292
+ def shift_blocks_down_above_row(row)
293
+ row.downto(0) do |playfield_row|
294
+ playfield[playfield_row].each_with_index do |block, playfield_column|
295
+ previous_row = playfield[playfield_row - 1]
296
+ previous_block = previous_row[playfield_column]
297
+ block.color = previous_block.color unless block.color == previous_block.color
298
+ end
180
299
  end
300
+ playfield[0].each(&:clear)
181
301
  end
302
+
182
303
  end
304
+
183
305
  end
306
+
184
307
  end
185
-
@@ -20,52 +20,19 @@
20
20
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
21
 
22
22
  class Tetris
23
- module View
24
- class GameOverDialog
25
- include Glimmer::UI::CustomShell
26
-
27
- options :parent_shell
23
+ module Model
24
+ class PastGame
25
+ attr_accessor :name, :score, :lines, :level
28
26
 
29
- body {
30
- dialog(parent_shell) {
31
- row_layout {
32
- type :vertical
33
- center true
34
- }
35
- text 'Tetris'
36
-
37
- label(:center) {
38
- text 'Game Over!'
39
- font name: 'Menlo', height: 30, style: :bold
40
- }
41
- label # filler
42
- button {
43
- text 'Play Again?'
44
-
45
- on_widget_selected {
46
- Model::Game.restart
47
-
48
- body_root.close
49
- }
50
- }
51
-
52
- on_shell_activated {
53
- Model::Game.game_over = true
54
- display.beep
55
- }
56
-
57
- on_shell_closed {
58
- exit_game
59
- }
60
-
61
- on_key_pressed { |event|
62
- exit_game if event.keyCode == swt(:cr)
63
- }
64
- }
65
- }
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
66
33
 
67
- def exit_game
68
- display.dispose if Model::Game.game_over?
34
+ def to_a
35
+ [@name, @score, @lines, @level]
69
36
  end
70
37
  end
71
38
  end