glimmer-dsl-opal 0.25.2 → 0.26.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 (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
@@ -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