glimmer-dsl-opal 0.25.1 → 0.26.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +23 -0
  3. data/README.md +48 -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/button_proxy.rb +5 -0
  8. data/lib/glimmer/swt/c_combo_proxy.rb +30 -1
  9. data/lib/glimmer/swt/combo_proxy.rb +10 -2
  10. data/lib/glimmer/swt/control_editor.rb +2 -2
  11. data/lib/glimmer/swt/dialog_proxy.rb +11 -4
  12. data/lib/glimmer/swt/display_proxy.rb +2 -0
  13. data/lib/glimmer/swt/fill_layout_proxy.rb +2 -2
  14. data/lib/glimmer/swt/shell_proxy.rb +28 -1
  15. data/lib/glimmer/swt/table_item_proxy.rb +0 -20
  16. data/lib/glimmer/swt/table_proxy.rb +4 -0
  17. data/lib/glimmer/swt/widget_proxy.rb +48 -7
  18. data/lib/glimmer/ui/custom_shell.rb +2 -0
  19. data/lib/glimmer/ui/custom_widget.rb +2 -0
  20. data/lib/glimmer-dsl-opal/samples/elaborate/login.rb +3 -5
  21. data/lib/glimmer-dsl-opal/samples/elaborate/tetris/model/block.rb +48 -0
  22. data/lib/glimmer-dsl-opal/samples/elaborate/tetris/model/game.rb +275 -0
  23. data/lib/glimmer-dsl-opal/samples/elaborate/tetris/model/past_game.rb +39 -0
  24. data/lib/glimmer-dsl-opal/samples/elaborate/tetris/model/tetromino.rb +329 -0
  25. data/lib/glimmer-dsl-opal/samples/elaborate/tetris/view/block.rb +41 -0
  26. data/lib/glimmer-dsl-opal/samples/elaborate/tetris/view/high_score_dialog.rb +122 -0
  27. data/lib/glimmer-dsl-opal/samples/elaborate/tetris/view/playfield.rb +56 -0
  28. data/lib/glimmer-dsl-opal/samples/elaborate/tetris/view/score_lane.rb +87 -0
  29. data/lib/glimmer-dsl-opal/samples/elaborate/tetris/view/tetris_menu_bar.rb +136 -0
  30. data/lib/glimmer-dsl-opal/samples/elaborate/tetris.rb +166 -0
  31. data/lib/glimmer-dsl-opal/samples/hello/hello_custom_widget.rb +1 -1
  32. data/lib/glimmer-dsl-opal.rb +1 -0
  33. metadata +14 -4
@@ -59,26 +59,6 @@ module Glimmer
59
59
  @text_array ||= []
60
60
  end
61
61
 
62
- def get_data(key = nil)
63
- if key.nil?
64
- @data
65
- else
66
- data_hash[key]
67
- end
68
- end
69
-
70
- def set_data(key = nil, data_value)
71
- if key.nil?
72
- @data = data_value
73
- else
74
- data_hash[key] = data_value
75
- end
76
- end
77
-
78
- def data_hash
79
- @data_hash ||= {}
80
- end
81
-
82
62
  def parent_path
83
63
  parent.items_path
84
64
  end
@@ -61,6 +61,10 @@ module Glimmer
61
61
  on_focus_lost {
62
62
  table_proxy.finish_edit!
63
63
  }
64
+ on_modify_text do |event|
65
+ # No Op, just record @text changes on key up
66
+ # TODO find a better solution than this in the future
67
+ end
64
68
  on_key_pressed { |key_event|
65
69
  if key_event.keyCode == swt(:cr)
66
70
  table_proxy.finish_edit!
@@ -31,6 +31,8 @@ module Glimmer
31
31
  include Glimmer
32
32
  include PropertyOwner
33
33
 
34
+ Event = Struct.new(:widget, keyword_init: true)
35
+
34
36
  SWT_CURSOR_TO_CSS_CURSOR_MAP = {
35
37
  wait: 'wait',
36
38
  sizenwse: 'nwse-resize',
@@ -140,7 +142,6 @@ module Glimmer
140
142
  # TODO consider changing children to an array (why is it a Set if order matters?)
141
143
  @children = Set.new # TODO consider moving to composite
142
144
  @enabled = true
143
- @data = {}
144
145
  DEFAULT_INITIALIZERS[self.class.underscored_widget_name(self).to_s.to_sym]&.call(self)
145
146
  @parent.post_initialize_child(self) # TODO rename to post_initialize_child to be closer to glimmer-dsl-swt terminology
146
147
  end
@@ -172,18 +173,30 @@ module Glimmer
172
173
  end
173
174
  end
174
175
 
175
- def set_data(key=nil, value)
176
- @data[key] = value
176
+ def set_data(key = nil, value)
177
+ if key.nil?
178
+ @data = value
179
+ else
180
+ swt_data[key] = value
181
+ end
177
182
  end
178
183
  alias setData set_data
179
184
  alias data= set_data
180
185
 
181
- def get_data(key=nil)
182
- @data[key]
186
+ def get_data(key = nil)
187
+ if key.nil?
188
+ @data
189
+ else
190
+ swt_data[key]
191
+ end
183
192
  end
184
193
  alias getData get_data
185
194
  alias data get_data
186
195
 
196
+ def swt_data
197
+ @swt_data ||= {}
198
+ end
199
+
187
200
  def css_classes
188
201
  dom_element.attr('class').to_s.split
189
202
  end
@@ -192,9 +205,9 @@ module Glimmer
192
205
  remove_all_listeners
193
206
  Document.find(path).remove
194
207
  parent&.post_dispose_child(self)
195
- # TODO fire on_widget_disposed listener
196
208
  # children.each(:dispose) # TODO enable this safely
197
209
  @disposed = true
210
+ listeners_for('widget_disposed').each {|listener| listener.call(Event.new(widget: self))}
198
211
  end
199
212
 
200
213
  def remove_all_listeners
@@ -733,6 +746,14 @@ module Glimmer
733
746
  @event_handling_suspended
734
747
  end
735
748
 
749
+ def listeners
750
+ @listeners ||= {}
751
+ end
752
+
753
+ def listeners_for(listener_event)
754
+ listeners[listener_event.to_s] ||= []
755
+ end
756
+
736
757
  def can_handle_observation_request?(observation_request)
737
758
  # TODO sort this out for Opal
738
759
  observation_request = observation_request.to_s
@@ -747,6 +768,15 @@ module Glimmer
747
768
  end
748
769
 
749
770
  def handle_observation_request(keyword, original_event_listener)
771
+ case keyword
772
+ when 'on_widget_disposed'
773
+ listeners_for(keyword.sub(/^on_/, '')) << original_event_listener.to_proc
774
+ else
775
+ handle_javascript_observation_request(keyword, original_event_listener)
776
+ end
777
+ end
778
+
779
+ def handle_javascript_observation_request(keyword, original_event_listener)
750
780
  return unless effective_observation_request_to_event_mapping.keys.include?(keyword)
751
781
  event = nil
752
782
  delegate = nil
@@ -886,6 +916,18 @@ module Glimmer
886
916
  # }
887
917
  # end,
888
918
  # },
919
+ MenuItemProxy => {
920
+ :selection => lambda do |observer|
921
+ on_widget_selected { |selection_event|
922
+ # TODO look into validity of this and perhaps move toggle logic to MenuItemProxy
923
+ if check?
924
+ observer.call(!selection)
925
+ else
926
+ observer.call(selection)
927
+ end
928
+ }
929
+ end
930
+ },
889
931
  ScaleProxy => {
890
932
  :selection => lambda do |observer|
891
933
  on_widget_selected { |selection_event|
@@ -1021,7 +1063,6 @@ require 'glimmer/swt/combo_proxy'
1021
1063
  require 'glimmer/swt/c_combo_proxy'
1022
1064
  require 'glimmer/swt/checkbox_proxy'
1023
1065
  require 'glimmer/swt/composite_proxy'
1024
- require 'glimmer/swt/dialog_proxy'
1025
1066
  require 'glimmer/swt/date_time_proxy'
1026
1067
  require 'glimmer/swt/group_proxy'
1027
1068
  require 'glimmer/swt/label_proxy'
@@ -67,6 +67,8 @@ module Glimmer
67
67
 
68
68
  def initialize(parent, args, options, &content)
69
69
  super(parent, args, options, &content)
70
+ body_root.set_data('custom_shell', self)
71
+ body_root.set_data('custom_window', self)
70
72
  raise Error, 'Invalid custom shell body root! Must be a shell or another custom shell.' unless body_root.is_a?(Glimmer::SWT::ShellProxy) || body_root.is_a?(Glimmer::UI::CustomShell)
71
73
  end
72
74
 
@@ -158,6 +158,7 @@ module Glimmer
158
158
 
159
159
 
160
160
  attr_reader :body_root, :parent, :options, :swt_style
161
+ alias parent_proxy parent
161
162
 
162
163
  def initialize(parent, args, options, &content)
163
164
  @parent = parent
@@ -177,6 +178,7 @@ module Glimmer
177
178
  body_block = self.class.instance_variable_get("@body_block")
178
179
  raise Glimmer::Error, 'Invalid custom widget for having no body! Please define body block!' if body_block.nil?
179
180
  @body_root = instance_exec(&body_block)
181
+ @parent ||= @body_root.parent
180
182
  raise Glimmer::Error, 'Invalid custom widget for having an empty body! Please fill body block!' if @body_root.nil?
181
183
  execute_hooks('after_body')
182
184
  end
@@ -19,8 +19,6 @@
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 'glimmer-dsl-swt'
23
-
24
22
  class LoginPresenter
25
23
 
26
24
  attr_accessor :user_name
@@ -64,10 +62,10 @@ end
64
62
 
65
63
  class Login
66
64
  include Glimmer::UI::CustomShell
67
-
68
- before_body {
65
+
66
+ before_body do
69
67
  @presenter = LoginPresenter.new
70
- }
68
+ end
71
69
 
72
70
  body {
73
71
  shell {
@@ -0,0 +1,48 @@
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 Block
25
+ COLOR_CLEAR = :white
26
+
27
+ attr_accessor :color
28
+
29
+ # Initializes with color. Default color (gray) signifies an empty block
30
+ def initialize(color = COLOR_CLEAR)
31
+ @color = color
32
+ end
33
+
34
+ # Clears block color. `quietly` option indicates if it should not notify observers by setting value quietly via variable not attribute writer.
35
+ def clear
36
+ self.color = COLOR_CLEAR unless self.color == COLOR_CLEAR
37
+ end
38
+
39
+ def clear?
40
+ self.color == COLOR_CLEAR
41
+ end
42
+
43
+ def occupied?
44
+ !clear?
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,275 @@
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 'fileutils'
23
+ require 'json'
24
+ require 'glimmer/data_binding/observer'
25
+ require 'glimmer/config'
26
+
27
+ require_relative 'block'
28
+ require_relative 'tetromino'
29
+ require_relative 'past_game'
30
+
31
+ class Tetris
32
+ module Model
33
+ class Game
34
+ PLAYFIELD_WIDTH = 10
35
+ PLAYFIELD_HEIGHT = 20
36
+ PREVIEW_PLAYFIELD_WIDTH = 4
37
+ PREVIEW_PLAYFIELD_HEIGHT = 2
38
+ SCORE_MULTIPLIER = {1 => 40, 2 => 100, 3 => 300, 4 => 1200}
39
+
40
+ attr_reader :playfield_width, :playfield_height
41
+ attr_accessor :game_over, :paused, :preview_tetromino, :lines, :score, :level, :high_scores, :added_high_score, :show_high_scores, :up_arrow_action
42
+ alias game_over? game_over
43
+ alias paused? paused
44
+ alias added_high_score? added_high_score
45
+
46
+ def initialize(playfield_width = PLAYFIELD_WIDTH, playfield_height = PLAYFIELD_HEIGHT)
47
+ @playfield_width = playfield_width
48
+ @playfield_height = playfield_height
49
+ @high_scores = []
50
+ @show_high_scores = false
51
+ @up_arrow_action = :rotate_left
52
+ end
53
+
54
+ def game_in_progress?
55
+ !game_over? && !paused?
56
+ end
57
+
58
+ def start!
59
+ self.show_high_scores = false
60
+ self.paused = false
61
+ self.level = 1
62
+ self.score = 0
63
+ self.lines = 0
64
+ reset_playfield
65
+ reset_preview_playfield
66
+ reset_tetrominoes
67
+ preview_next_tetromino!
68
+ consider_adding_tetromino
69
+ self.game_over = false
70
+ end
71
+ alias restart! start!
72
+
73
+ def game_over!
74
+ add_high_score!
75
+ self.game_over = true
76
+ end
77
+
78
+ def clear_high_scores!
79
+ high_scores.clear
80
+ end
81
+
82
+ def add_high_score!
83
+ self.added_high_score = true
84
+ high_scores.prepend(PastGame.new("Player #{high_scores.count + 1}", score, lines, level))
85
+ end
86
+
87
+ def tetris_dir
88
+ @tetris_dir ||= File.join(File.expand_path('~'), '.glimmer-tetris')
89
+ end
90
+
91
+ def tetris_high_score_file
92
+ File.join(tetris_dir, "high_scores.txt")
93
+ end
94
+
95
+ def down!(instant: false)
96
+ return unless game_in_progress?
97
+ current_tetromino.down!(instant: instant)
98
+ game_over! if current_tetromino.row <= 0 && current_tetromino.stopped?
99
+ end
100
+
101
+ def right!
102
+ return unless game_in_progress?
103
+ current_tetromino.right!
104
+ end
105
+
106
+ def left!
107
+ return unless game_in_progress?
108
+ current_tetromino.left!
109
+ end
110
+
111
+ def rotate!(direction)
112
+ return unless game_in_progress?
113
+ current_tetromino.rotate!(direction)
114
+ end
115
+
116
+ def current_tetromino
117
+ tetrominoes.last
118
+ end
119
+
120
+ def tetrominoes
121
+ @tetrominoes ||= reset_tetrominoes
122
+ end
123
+
124
+ # Returns blocks in the playfield
125
+ def playfield
126
+ @playfield ||= @original_playfield = @playfield_height.times.map {
127
+ @playfield_width.times.map {
128
+ Block.new
129
+ }
130
+ }
131
+ end
132
+
133
+ # Executes a hypothetical scenario without truly changing playfield permanently
134
+ def hypothetical(&block)
135
+ @playfield = hypothetical_playfield
136
+ block.call
137
+ @playfield = @original_playfield
138
+ end
139
+
140
+ # Returns whether currently executing a hypothetical scenario
141
+ def hypothetical?
142
+ @playfield != @original_playfield
143
+ end
144
+
145
+ def hypothetical_playfield
146
+ @playfield_height.times.map { |row|
147
+ @playfield_width.times.map { |column|
148
+ playfield[row][column].clone
149
+ }
150
+ }
151
+ end
152
+
153
+ def preview_playfield
154
+ @preview_playfield ||= PREVIEW_PLAYFIELD_HEIGHT.times.map {|row|
155
+ PREVIEW_PLAYFIELD_WIDTH.times.map {|column|
156
+ Block.new
157
+ }
158
+ }
159
+ end
160
+
161
+ def preview_next_tetromino!
162
+ self.preview_tetromino = Tetromino.new(self)
163
+ end
164
+
165
+ def calculate_score!(eliminated_lines)
166
+ new_score = SCORE_MULTIPLIER[eliminated_lines] * (level + 1)
167
+ self.score += new_score
168
+ end
169
+
170
+ def level_up!
171
+ self.level += 1 if lines >= self.level*10
172
+ end
173
+
174
+ def delay
175
+ [1.1 - (level.to_i * 0.1), 0.001].max
176
+ end
177
+
178
+ def instant_down_on_up=(value)
179
+ self.up_arrow_action = :instant_down if value
180
+ end
181
+
182
+ def instant_down_on_up
183
+ self.up_arrow_action == :instant_down
184
+ end
185
+
186
+ def rotate_right_on_up=(value)
187
+ self.up_arrow_action = :rotate_right if value
188
+ end
189
+
190
+ def rotate_right_on_up
191
+ self.up_arrow_action == :rotate_right
192
+ end
193
+
194
+ def rotate_left_on_up=(value)
195
+ self.up_arrow_action = :rotate_left if value
196
+ end
197
+
198
+ def rotate_left_on_up
199
+ self.up_arrow_action == :rotate_left
200
+ end
201
+
202
+ def reset_tetrominoes
203
+ @tetrominoes = []
204
+ end
205
+
206
+ def reset_playfield
207
+ playfield.each do |row|
208
+ row.each do |block|
209
+ block.clear
210
+ end
211
+ end
212
+ end
213
+
214
+ def reset_preview_playfield
215
+ preview_playfield.each do |row|
216
+ row.each do |block|
217
+ block.clear
218
+ end
219
+ end
220
+ end
221
+
222
+ def consider_adding_tetromino
223
+ if tetrominoes.empty? || current_tetromino.stopped?
224
+ preview_tetromino.launch!
225
+ preview_next_tetromino!
226
+ end
227
+ end
228
+
229
+ def consider_eliminating_lines
230
+ eliminated_lines = 0
231
+ playfield.each_with_index do |row, playfield_row|
232
+ if row.all? {|block| !block.clear?}
233
+ eliminated_lines += 1
234
+ shift_blocks_down_above_row(playfield_row)
235
+ end
236
+ end
237
+ if eliminated_lines > 0
238
+ self.lines += eliminated_lines
239
+ level_up!
240
+ calculate_score!(eliminated_lines)
241
+ end
242
+ end
243
+
244
+ def playfield_remaining_heights(tetromino = nil)
245
+ @playfield_width.times.map do |playfield_column|
246
+ bottom_most_block = tetromino.bottom_most_block_for_column(playfield_column)
247
+ (playfield.each_with_index.detect do |row, playfield_row|
248
+ !row[playfield_column].clear? &&
249
+ (
250
+ tetromino.nil? ||
251
+ bottom_most_block.nil? ||
252
+ (playfield_row > tetromino.row + bottom_most_block[:row_index])
253
+ )
254
+ end || [nil, @playfield_height])[1]
255
+ end.to_a
256
+ end
257
+
258
+ private
259
+
260
+ def shift_blocks_down_above_row(row)
261
+ row.downto(0) do |playfield_row|
262
+ playfield[playfield_row].each_with_index do |block, playfield_column|
263
+ previous_row = playfield[playfield_row - 1]
264
+ previous_block = previous_row[playfield_column]
265
+ block.color = previous_block.color unless block.color == previous_block.color
266
+ end
267
+ end
268
+ playfield[0].each(&:clear)
269
+ end
270
+
271
+ end
272
+
273
+ end
274
+
275
+ end