glimmer-dsl-swt 4.18.4.1 → 4.18.4.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +44 -0
  3. data/README.md +84 -5072
  4. data/VERSION +1 -1
  5. data/docs/reference/GLIMMER_COMMAND.md +591 -0
  6. data/docs/reference/GLIMMER_CONFIGURATION.md +183 -0
  7. data/docs/reference/GLIMMER_GIRB.md +30 -0
  8. data/docs/reference/GLIMMER_GUI_DSL_SYNTAX.md +3251 -0
  9. data/docs/reference/GLIMMER_PACKAGING_AND_DISTRIBUTION.md +202 -0
  10. data/docs/reference/GLIMMER_SAMPLES.md +676 -0
  11. data/docs/reference/GLIMMER_STYLE_GUIDE.md +14 -0
  12. data/glimmer-dsl-swt.gemspec +16 -7
  13. data/lib/glimmer/dsl/swt/custom_widget_expression.rb +3 -3
  14. data/lib/glimmer/dsl/swt/exec_expression.rb +1 -1
  15. data/lib/glimmer/dsl/swt/observe_expression.rb +8 -5
  16. data/lib/glimmer/dsl/swt/pixel_expression.rb +38 -0
  17. data/lib/glimmer/dsl/swt/timer_exec_expression.rb +35 -0
  18. data/lib/glimmer/dsl/swt/widget_expression.rb +1 -0
  19. data/lib/glimmer/swt/custom/animation.rb +9 -8
  20. data/lib/glimmer/swt/custom/code_text.rb +26 -18
  21. data/lib/glimmer/swt/custom/drawable.rb +21 -5
  22. data/lib/glimmer/swt/custom/shape.rb +42 -37
  23. data/lib/glimmer/swt/display_proxy.rb +11 -10
  24. data/lib/glimmer/swt/properties.rb +35 -10
  25. data/lib/glimmer/swt/shell_proxy.rb +1 -1
  26. data/lib/glimmer/swt/table_proxy.rb +2 -2
  27. data/lib/glimmer/swt/widget_proxy.rb +21 -6
  28. data/lib/glimmer/ui/custom_widget.rb +17 -0
  29. data/samples/elaborate/mandelbrot_fractal.rb +103 -0
  30. data/samples/elaborate/meta_sample.rb +14 -6
  31. data/samples/elaborate/tetris.rb +14 -17
  32. data/samples/elaborate/tetris/model/game.rb +1 -2
  33. data/samples/elaborate/tetris/view/high_score_dialog.rb +0 -7
  34. data/samples/elaborate/tetris/view/playfield.rb +1 -1
  35. data/samples/elaborate/tetris/view/tetris_menu_bar.rb +13 -11
  36. data/samples/hello/hello_canvas_transform.rb +1 -1
  37. data/samples/hello/hello_table.rb +1 -0
  38. metadata +14 -5
  39. data/samples/elaborate/meta_sample/meta_sample_logo.png +0 -0
@@ -81,15 +81,15 @@ module Glimmer
81
81
  end
82
82
 
83
83
  def async_exec(&block)
84
- @swt_display.async_exec(&block)
84
+ @swt_display.asyncExec(&block)
85
85
  end
86
86
 
87
87
  def sync_exec(&block)
88
- @swt_display.sync_exec(&block)
88
+ @swt_display.syncExec(&block)
89
89
  end
90
90
 
91
- def timer_exec(&block)
92
- @swt_display.timer_exec(&block)
91
+ def timer_exec(delay_in_millis, &block)
92
+ @swt_display.timerExec(delay_in_millis, &block)
93
93
  end
94
94
 
95
95
  def on_widget_disposed(&block)
@@ -137,12 +137,13 @@ module Glimmer
137
137
  add_swt_event_filter(constant_name, &block)
138
138
  elsif observation_request.start_with?('on_')
139
139
  event_name = observation_request.sub(/^on_/, '')
140
- if OBSERVED_MENU_ITEMS.include?(event_name)
141
- if OS.mac?
142
- system_menu = swt_display.getSystemMenu
143
- menu_item = system_menu.getItems.find {|menu_item| menu_item.getID == SWTProxy["ID_#{event_name.upcase}"]}
144
- menu_item.addListener(SWTProxy[:Selection], &block)
145
- end
140
+ if OBSERVED_MENU_ITEMS.include?(event_name) && OS.mac?
141
+ system_menu = swt_display.getSystemMenu
142
+ menu_item = system_menu.getItems.find {|menu_item| menu_item.getID == SWTProxy["ID_#{event_name.upcase}"]}
143
+ display_mac_event_registration = menu_item.addListener(SWTProxy[:Selection], &block)
144
+ # TODO enable this code and test on the Mac to ensure automatic cleanup of mac event registrations in custom widgets
145
+ # Glimmer::UI::CustomWidget.current_custom_widgets.last&.observer_registrations&.push(display_mac_event_registration)
146
+ display_mac_event_registration
146
147
  end
147
148
  end
148
149
  end
@@ -22,28 +22,53 @@
22
22
  module Glimmer
23
23
  module SWT
24
24
  module Properties
25
+ class << self
26
+ def ruby_attribute_setter(attribute_name)
27
+ @ruby_attribute_setters ||= {}
28
+ @ruby_attribute_setters[attribute_name] ||= "#{normalized_attribute(attribute_name)}="
29
+ end
30
+
31
+ def attribute_setter(attribute_name)
32
+ @attribute_setters ||= {}
33
+ @attribute_setters[attribute_name] ||= "set#{normalized_attribute(attribute_name).camelcase(:upper)}"
34
+ end
35
+
36
+ def attribute_getter(attribute_name)
37
+ @attribute_getters ||= {}
38
+ @attribute_getters[attribute_name] ||= "get#{normalized_attribute(attribute_name).camelcase(:upper)}"
39
+ end
40
+
41
+ def normalized_attribute(attribute_name)
42
+ @normalized_attributes ||= {}
43
+ if @normalized_attributes[attribute_name].nil?
44
+ attribute_name = attribute_name.to_s if attribute_name.is_a?(Symbol)
45
+ attribute_name = attribute_name.underscore unless attribute_name.downcase?
46
+ attribute_name = attribute_name.sub(/^get_/, '') if attribute_name.start_with?('get_')
47
+ attribute_name = attribute_name.sub(/^set_/, '') if attribute_name.start_with?('set_')
48
+ attribute_name = attribute_name.sub(/=$/, '') if attribute_name.end_with?('=')
49
+ @normalized_attributes[attribute_name] = attribute_name
50
+ end
51
+ @normalized_attributes[attribute_name]
52
+ end
53
+ alias ruby_attribute_getter normalized_attribute
54
+ end
55
+
25
56
  def ruby_attribute_setter(attribute_name)
26
- "#{normalized_attribute(attribute_name)}="
57
+ Glimmer::SWT::Properties.ruby_attribute_setter(attribute_name)
27
58
  end
28
59
 
29
60
  def attribute_setter(attribute_name)
30
- "set#{normalized_attribute(attribute_name).camelcase(:upper)}"
61
+ Glimmer::SWT::Properties.attribute_setter(attribute_name)
31
62
  end
32
63
 
33
64
  def attribute_getter(attribute_name)
34
- "get#{normalized_attribute(attribute_name).camelcase(:upper)}"
65
+ Glimmer::SWT::Properties.attribute_getter(attribute_name)
35
66
  end
36
67
 
37
68
  def normalized_attribute(attribute_name)
38
- attribute_name = attribute_name.to_s if attribute_name.is_a?(Symbol)
39
- attribute_name = attribute_name.underscore unless attribute_name.downcase?
40
- attribute_name = attribute_name.sub(/^get_/, '') if attribute_name.start_with?('get_')
41
- attribute_name = attribute_name.sub(/^set_/, '') if attribute_name.start_with?('set_')
42
- attribute_name = attribute_name.sub(/=$/, '') if attribute_name.end_with?('=')
43
- attribute_name
69
+ Glimmer::SWT::Properties.normalized_attribute(attribute_name)
44
70
  end
45
71
  alias ruby_attribute_getter normalized_attribute
46
-
47
72
  end
48
73
  end
49
74
  end
@@ -164,7 +164,7 @@ module Glimmer
164
164
  minimum_size = @swt_widget.getMinimumSize
165
165
  @swt_widget.setMinimumSize(bounds.width, bounds.height)
166
166
  listener = on_control_resized { @swt_widget.setBounds(bounds) }
167
- @swt_widget.pack
167
+ @swt_widget.layout(true, true)
168
168
  @swt_widget.removeControlListener(listener.swt_listener)
169
169
  @swt_widget.setMinimumSize(minimum_size)
170
170
  elsif OS.linux?
@@ -497,12 +497,12 @@ module Glimmer
497
497
  @finish_edit = lambda do |event=nil|
498
498
  new_value = @table_editor_widget_proxy&.send(widget_value_property)
499
499
  if table_item.isDisposed
500
- @cancel_edit.call
500
+ @cancel_edit.call unless write_on_cancel
501
501
  elsif !new_value.nil? && !action_taken && !@edit_in_progress && !@cancel_in_progress
502
502
  action_taken = true
503
503
  @edit_in_progress = true
504
504
  if new_value == model.send(model_editing_property)
505
- @cancel_edit.call
505
+ @cancel_edit.call unless write_on_cancel
506
506
  else
507
507
  if before_write&.arity == 0
508
508
  before_write&.call
@@ -50,6 +50,7 @@ module Glimmer
50
50
  DEFAULT_STYLES = {
51
51
  'arrow' => [:arrow],
52
52
  'button' => [:push],
53
+ 'canvas' => ([:double_buffered] unless OS.mac?),
53
54
  'checkbox' => [:check],
54
55
  'check' => [:check],
55
56
  'drag_source' => [:drop_copy],
@@ -136,7 +137,8 @@ module Glimmer
136
137
  end
137
138
  end
138
139
 
139
- attr_reader :parent_proxy, :swt_widget, :drag_source_proxy, :drop_target_proxy, :drag_source_style, :drag_source_transfer, :drop_target_transfer
140
+ attr_reader :parent_proxy, :swt_widget, :drag_source_proxy, :drop_target_proxy, :drag_source_style, :drag_source_transfer, :drop_target_transfer, :finished_add_content
141
+ alias finished_add_content? finished_add_content
140
142
 
141
143
  # Initializes a new SWT Widget
142
144
  #
@@ -169,6 +171,7 @@ module Glimmer
169
171
  if respond_to?(:on_widget_disposed)
170
172
  on_widget_disposed {
171
173
  clear_shapes
174
+ deregister_shape_painting
172
175
  }
173
176
  end
174
177
  end
@@ -178,11 +181,17 @@ module Glimmer
178
181
  # No Op by default
179
182
  end
180
183
 
181
- # Subclasses may override to perform post add_content work
184
+ # Subclasses may override to perform post add_content work.
185
+ # Make sure its logic detects if it ran before since it could run multiple times
186
+ # when adding content multiple times post creation.
182
187
  def post_add_content
183
188
  # No Op by default
184
189
  end
185
-
190
+
191
+ def finish_add_content!
192
+ @finished_add_content = true
193
+ end
194
+
186
195
  def extract_args(underscored_widget_name, args)
187
196
  @arg_extractor_mapping ||= {
188
197
  'menu_item' => lambda do |args|
@@ -544,6 +553,10 @@ module Glimmer
544
553
  DisplayProxy.instance.sync_exec(&block)
545
554
  end
546
555
 
556
+ def timer_exec(delay_in_millis, &block)
557
+ DisplayProxy.instance.timer_exec(delay_in_millis, &block)
558
+ end
559
+
547
560
  def has_style?(style)
548
561
  comparison = interpret_style(style)
549
562
  (@swt_widget.style & comparison) == comparison
@@ -565,9 +578,11 @@ module Glimmer
565
578
 
566
579
  # Used for data-binding only. Consider renaming or improving to avoid the confusion it causes
567
580
  def add_observer(observer, property_name)
568
- property_listener_installers = @swt_widget.class.ancestors.map {|ancestor| widget_property_listener_installers[ancestor]}.compact
569
- widget_listener_installers = property_listener_installers.map{|installer| installer[property_name.to_s.to_sym]}.compact if !property_listener_installers.empty?
570
- widget_listener_installers.to_a.first&.call(observer)
581
+ if !observer.respond_to?(:binding_options) || !observer.binding_options[:read_only]
582
+ property_listener_installers = @swt_widget.class.ancestors.map {|ancestor| widget_property_listener_installers[ancestor]}.compact
583
+ widget_listener_installers = property_listener_installers.map{|installer| installer[property_name.to_s.to_sym]}.compact if !property_listener_installers.empty?
584
+ widget_listener_installers.to_a.first&.call(observer)
585
+ end
571
586
  end
572
587
 
573
588
  def remove_observer(observer, property_name)
@@ -158,11 +158,17 @@ module Glimmer
158
158
  def after_body(&block)
159
159
  @after_body_block = block
160
160
  end
161
+
162
+ # Current custom widgets being rendered. Useful to yoke all observers evaluated during rendering of their custom widgets for automatical disposal on_widget_disposed
163
+ def current_custom_widgets
164
+ @current_custom_widgets ||= []
165
+ end
161
166
  end
162
167
 
163
168
  attr_reader :body_root, :swt_widget, :parent, :parent_proxy, :swt_style, :options
164
169
 
165
170
  def initialize(parent, *swt_constants, options, &content)
171
+ Glimmer::UI::CustomWidget.current_custom_widgets << self
166
172
  @parent_proxy = @parent = parent
167
173
  @parent_proxy = @parent&.get_data('proxy') if @parent.respond_to?(:get_data) && @parent.get_data('proxy')
168
174
  @swt_style = SWT::SWTProxy[*swt_constants]
@@ -177,6 +183,9 @@ module Glimmer
177
183
  @swt_widget = @body_root.swt_widget
178
184
  @swt_widget.set_data('custom_widget', self)
179
185
  execute_hook('after_body')
186
+ @dispose_listener_registration = @body_root.on_widget_disposed do
187
+ observer_registrations.each(&:deregister)
188
+ end
180
189
  end
181
190
 
182
191
  # Subclasses may override to perform post initialization work on an added child
@@ -184,6 +193,10 @@ module Glimmer
184
193
  # No Op by default
185
194
  end
186
195
 
196
+ def observer_registrations
197
+ @observer_registrations ||= []
198
+ end
199
+
187
200
  def can_handle_observation_request?(observation_request)
188
201
  observation_request = observation_request.to_s
189
202
  result = false
@@ -273,6 +286,10 @@ module Glimmer
273
286
  SWT::DisplayProxy.instance.sync_exec(&block)
274
287
  end
275
288
 
289
+ def timer_exec(delay_in_millis, &block)
290
+ SWT::DisplayProxy.instance.timer_exec(delay_in_millis, &block)
291
+ end
292
+
276
293
  # Returns content block if used as an attribute reader (no args)
277
294
  # Otherwise, if a block is passed, it adds it as content to this custom widget
278
295
  def content(&block)
@@ -0,0 +1,103 @@
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 'complex'
23
+ require 'bigdecimal'
24
+ require 'concurrent-ruby'
25
+
26
+ # Mandelbrot implementation
27
+ # Courtesy of open-source code at:
28
+ # https://github.com/gotbadger/ruby-mandelbrot
29
+ class Mandelbrot
30
+
31
+ attr_accessor :max_iterations
32
+
33
+ def initialize(max_iterations)
34
+ @max_iterations = max_iterations
35
+ end
36
+
37
+ def calculate_all(x_array, y_array)
38
+ thread_pool = Concurrent::FixedThreadPool.new(Concurrent.processor_count)
39
+ width = x_array.size
40
+ height = y_array.size
41
+ pixel_rows_array = Concurrent::Array.new(height)
42
+ height.times do |y|
43
+ pixel_rows_array[y] ||= Concurrent::Array.new(width)
44
+ width.times do |x|
45
+ thread_pool.post do
46
+ pixel_rows_array[y][x] = calculate(x_array[x], y_array[y]).last
47
+ end
48
+ end
49
+ end
50
+ thread_pool.shutdown
51
+ thread_pool.wait_for_termination
52
+ pixel_rows_array
53
+ end
54
+
55
+ def calculate(x,y)
56
+ base_case = [Complex(x,y), 0]
57
+ Array.new(max_iterations, base_case).inject(base_case) do |prev ,base|
58
+ z, itr = prev
59
+ c, _ = base
60
+ val = z*z + c
61
+ itr += 1 unless val.abs < 2
62
+ [val, itr]
63
+ end
64
+ end
65
+ end
66
+
67
+ class MandelbrotFractal
68
+ include Glimmer::UI::CustomShell
69
+
70
+ before_body {
71
+ @colors = [[0, 0, 0]] + 40.times.map { |i| [255 - i*5, 255 - i*5, 55 + i*5] }
72
+ @colors = @colors.map {|color_data| rgb(*color_data).swt_color}
73
+ mandelbrot = Mandelbrot.new(@colors.size - 1)
74
+ @y_array = (1.0).step(-1,-0.0030).to_a
75
+ @x_array = (-2.0).step(0.5,0.0030).to_a
76
+ @height = @y_array.size
77
+ @width = @x_array.size
78
+ @pixel_rows_array = mandelbrot.calculate_all(@x_array, @y_array)
79
+ @image = Image.new(display.swt_display, @width, @height)
80
+ image_gc = org.eclipse.swt.graphics.GC.new(@image)
81
+ @height.times { |y|
82
+ @width.times { |x|
83
+ new_foreground = @colors[@pixel_rows_array[y][x]]
84
+ image_gc.foreground = @current_foreground = new_foreground unless new_foreground == @current_foreground
85
+ image_gc.draw_point x, y
86
+ }
87
+ }
88
+ }
89
+
90
+ body {
91
+ shell {
92
+ text 'Mandelbrot Fractal'
93
+ minimum_size @width, @height + 12
94
+ image @image
95
+
96
+ canvas {
97
+ image(@image, 0, 0)
98
+ }
99
+ }
100
+ }
101
+ end
102
+
103
+ MandelbrotFractal.launch
@@ -20,7 +20,6 @@
20
20
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
21
 
22
22
  require 'fileutils'
23
- require 'etc'
24
23
 
25
24
  class Sample
26
25
  include Glimmer::DataBinding::ObservableModel
@@ -31,7 +30,7 @@ class Sample
31
30
  end
32
31
 
33
32
  def user_glimmer_directory
34
- File.join(Etc.getpwuid.dir, '.glimmer-dsl-swt')
33
+ File.join(File.expand_path('~'), '.glimmer-dsl-swt')
35
34
  end
36
35
 
37
36
  def ensure_user_glimmer_directory
@@ -204,7 +203,7 @@ class MetaSampleApplication
204
203
  shell(:fill_screen) {
205
204
  minimum_size 1280, 768
206
205
  text 'Glimmer Meta-Sample (The Sample of Samples)'
207
- image File.expand_path('meta_sample/meta_sample_logo.png', __dir__)
206
+ image File.expand_path('../../icons/scaffold_app.png', __dir__)
208
207
 
209
208
  sash_form {
210
209
  composite {
@@ -264,12 +263,21 @@ class MetaSampleApplication
264
263
  }
265
264
  }
266
265
 
267
- @code_text = code_text(lines: {width: 2}) {
268
- text bind(SampleDirectory, 'selected_sample.code', read_only: true)
269
- editable bind(SampleDirectory, 'selected_sample.editable')
266
+ @code_text = code_text(lines: {width: 3}) {
267
+ root {
268
+ grid_layout(2, false) {
269
+ horizontal_spacing 0
270
+ margin_width 0
271
+ margin_height 0
272
+ }
273
+ }
270
274
  line_numbers {
271
275
  background :white
272
276
  }
277
+ text bind(SampleDirectory, 'selected_sample.code', read_only: true)
278
+ editable bind(SampleDirectory, 'selected_sample.editable')
279
+ left_margin 7
280
+ right_margin 7
273
281
  }
274
282
 
275
283
  weights 4, 11
@@ -51,11 +51,12 @@ class Tetris
51
51
  end
52
52
 
53
53
  Display.app_name = 'Glimmer Tetris'
54
+
54
55
  display {
55
- @keyboard_listener = on_swt_keydown { |key_event|
56
+ @keyboard_down_listener = on_swt_keydown { |key_event|
56
57
  case key_event.keyCode
57
58
  when swt(:arrow_down), 's'.bytes.first
58
- game.down!
59
+ game.down! if OS.mac?
59
60
  when swt(:arrow_up)
60
61
  case game.up_arrow_action
61
62
  when :instant_down
@@ -75,10 +76,18 @@ class Tetris
75
76
  elsif key_event.keyLocation == swt(:left) # left shift key
76
77
  game.rotate!(:left)
77
78
  end
78
- when swt(:ctrl)
79
- game.rotate!(:left)
80
79
  end
81
80
  }
81
+
82
+ # invoke game.down! on keyup with Windows/Linux since they seem to group-render similar events, preventing intermediate renders (causing invisiblity while holding keys)
83
+ if !OS.mac?
84
+ @keyboard_up_listener = on_swt_keyup { |key_event|
85
+ case key_event.keyCode
86
+ when swt(:arrow_down), 's'.bytes.first
87
+ game.down!
88
+ end
89
+ }
90
+ end
82
91
 
83
92
  # if running in app mode, set the Mac app about dialog (ignored in platforms)
84
93
  @about_observer = on_about {
@@ -130,10 +139,6 @@ class Tetris
130
139
  score_lane(game: game, block_size: BLOCK_SIZE) {
131
140
  layout_data(:fill, :fill, true, true)
132
141
  }
133
-
134
- on_widget_disposed {
135
- deregister_observers
136
- }
137
142
  }
138
143
  }
139
144
 
@@ -169,7 +174,7 @@ class Tetris
169
174
  }
170
175
  }
171
176
  end
172
-
177
+
173
178
  def start_moving_tetrominos_down
174
179
  Thread.new do
175
180
  @mutex.synchronize do
@@ -197,14 +202,6 @@ class Tetris
197
202
  message "Glimmer Tetris\n\nGlimmer DSL for SWT Sample\n\nCopyright (c) 2007-2021 Andy Maleh"
198
203
  }.open
199
204
  end
200
-
201
- def deregister_observers
202
- @show_high_scores_observer&.deregister
203
- @game_over_observer&.deregister
204
- @keyboard_listener&.deregister
205
- @about_observer&.deregister
206
- @quit_observer&.deregister
207
- end
208
205
  end
209
206
 
210
207
  Tetris.launch