glimmer-dsl-swt 4.18.5.1 → 4.18.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +44 -0
  3. data/README.md +15 -12
  4. data/VERSION +1 -1
  5. data/docs/reference/GLIMMER_GUI_DSL_SYNTAX.md +205 -24
  6. data/docs/reference/GLIMMER_SAMPLES.md +8 -0
  7. data/glimmer-dsl-swt.gemspec +8 -3
  8. data/lib/glimmer/dsl/swt/shape_expression.rb +1 -1
  9. data/lib/glimmer/swt/custom/drawable.rb +10 -2
  10. data/lib/glimmer/swt/custom/shape.rb +449 -54
  11. data/lib/glimmer/swt/custom/shape/arc.rb +35 -0
  12. data/lib/glimmer/swt/custom/shape/cubic.rb +108 -0
  13. data/lib/glimmer/swt/custom/shape/focus.rb +2 -2
  14. data/lib/glimmer/swt/custom/shape/image.rb +35 -9
  15. data/lib/glimmer/swt/custom/shape/line.rb +121 -4
  16. data/lib/glimmer/swt/custom/shape/oval.rb +18 -0
  17. data/lib/glimmer/swt/custom/shape/path.rb +197 -0
  18. data/lib/glimmer/swt/custom/shape/path_segment.rb +86 -0
  19. data/lib/glimmer/swt/custom/shape/point.rb +42 -4
  20. data/lib/glimmer/swt/custom/shape/polygon.rb +105 -15
  21. data/lib/glimmer/swt/custom/shape/polyline.rb +88 -15
  22. data/lib/glimmer/swt/custom/shape/quad.rb +104 -0
  23. data/lib/glimmer/swt/custom/shape/rectangle.rb +19 -0
  24. data/lib/glimmer/swt/custom/shape/text.rb +13 -3
  25. data/lib/glimmer/swt/dialog_proxy.rb +4 -0
  26. data/lib/glimmer/swt/proxy_properties.rb +1 -1
  27. data/lib/glimmer/swt/transform_proxy.rb +20 -4
  28. data/lib/glimmer/swt/widget_proxy.rb +17 -1
  29. data/samples/elaborate/contact_manager.rb +2 -0
  30. data/samples/elaborate/login.rb +2 -0
  31. data/samples/elaborate/mandelbrot_fractal.rb +2 -1
  32. data/samples/elaborate/meta_sample.rb +1 -0
  33. data/samples/elaborate/tetris.rb +2 -1
  34. data/samples/elaborate/tic_tac_toe.rb +2 -0
  35. data/samples/elaborate/user_profile.rb +10 -8
  36. data/samples/hello/hello_browser.rb +2 -0
  37. data/samples/hello/hello_button.rb +2 -0
  38. data/samples/hello/hello_canvas.rb +40 -23
  39. data/samples/hello/hello_canvas_animation.rb +2 -0
  40. data/samples/hello/hello_canvas_path.rb +223 -0
  41. data/samples/hello/hello_canvas_transform.rb +2 -0
  42. data/samples/hello/hello_checkbox.rb +2 -0
  43. data/samples/hello/hello_checkbox_group.rb +2 -0
  44. data/samples/hello/hello_code_text.rb +2 -0
  45. data/samples/hello/hello_color_dialog.rb +2 -0
  46. data/samples/hello/hello_combo.rb +2 -0
  47. data/samples/hello/hello_computed.rb +2 -0
  48. data/samples/hello/hello_cursor.rb +2 -0
  49. data/samples/hello/hello_custom_shell.rb +1 -0
  50. data/samples/hello/hello_custom_widget.rb +2 -0
  51. data/samples/hello/hello_date_time.rb +2 -0
  52. data/samples/hello/hello_dialog.rb +2 -0
  53. data/samples/hello/hello_directory_dialog.rb +2 -0
  54. data/samples/hello/hello_drag_and_drop.rb +5 -3
  55. data/samples/hello/hello_expand_bar.rb +2 -0
  56. data/samples/hello/hello_file_dialog.rb +2 -0
  57. data/samples/hello/hello_font_dialog.rb +2 -0
  58. data/samples/hello/hello_group.rb +2 -0
  59. data/samples/hello/hello_link.rb +2 -0
  60. data/samples/hello/hello_list_multi_selection.rb +2 -0
  61. data/samples/hello/hello_list_single_selection.rb +2 -0
  62. data/samples/hello/hello_menu_bar.rb +2 -0
  63. data/samples/hello/hello_message_box.rb +2 -0
  64. data/samples/hello/hello_pop_up_context_menu.rb +2 -0
  65. data/samples/hello/hello_progress_bar.rb +2 -0
  66. data/samples/hello/hello_radio.rb +2 -0
  67. data/samples/hello/hello_radio_group.rb +2 -0
  68. data/samples/hello/hello_sash_form.rb +2 -0
  69. data/samples/hello/hello_spinner.rb +2 -0
  70. data/samples/hello/hello_styled_text.rb +19 -17
  71. data/samples/hello/hello_tab.rb +2 -0
  72. data/samples/hello/hello_table.rb +2 -0
  73. data/samples/hello/hello_world.rb +2 -0
  74. metadata +7 -2
@@ -564,6 +564,10 @@ Hello, Canvas!
564
564
 
565
565
  ![Hello Canvas](/images/glimmer-hello-canvas.png)
566
566
 
567
+ Hello, Canvas! Moving Shapes and Nested Shapes via Drag'n'Drop
568
+
569
+ ![Hello Canvas Moving Shapes](/images/glimmer-hello-canvas-moving-shapes.gif)
570
+
567
571
  Hello, Canvas! with Moved Shapes (via Drag'n'Drop)
568
572
 
569
573
  ![Hello Canvas Moved Shapes](/images/glimmer-hello-canvas-moved-shapes.png)
@@ -580,6 +584,10 @@ Hello, Canvas! Colors Changed
580
584
 
581
585
  ![Hello Canvas Colors Changed](/images/glimmer-hello-canvas-colors-changed.png)
582
586
 
587
+ Hello, Canvas! Data-Binding (changing a `text` shape `string` via data-binding changes from another thread)
588
+
589
+ ![Hello Canvas Data Binding](/images/glimmer-hello-canvas-data-binding.gif)
590
+
583
591
  #### Hello, Canvas Animation!
584
592
 
585
593
  This sample demonstrates the use of the `canvas` widget and [Animation DSL](#canvas-animation-dsl) in Glimmer.
@@ -2,16 +2,16 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Juwelier::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: glimmer-dsl-swt 4.18.5.1 ruby lib
5
+ # stub: glimmer-dsl-swt 4.18.6.0 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "glimmer-dsl-swt".freeze
9
- s.version = "4.18.5.1"
9
+ s.version = "4.18.6.0"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib".freeze]
13
13
  s.authors = ["AndyMaleh".freeze]
14
- s.date = "2021-02-23"
14
+ s.date = "2021-02-28"
15
15
  s.description = "Glimmer DSL for SWT (JRuby Desktop Development GUI Framework) is a native-GUI cross-platform desktop development library written in JRuby, an OS-threaded faster JVM version of Ruby. Glimmer's main innovation is a declarative Ruby DSL that enables productive and efficient authoring of desktop application user-interfaces by relying on the robust Eclipse SWT library. Glimmer additionally innovates by having built-in data-binding support, which greatly facilitates synchronizing the GUI with domain models, thus achieving true decoupling of object oriented components and enabling developers to solve business problems (test-first) without worrying about GUI concerns, or alternatively drive development GUI-first, and then write clean business models (test-first) afterwards. Not only does Glimmer provide a large set of GUI widgets, but it also supports drawing Canvas Graphics like Shapes and Animations. To get started quickly, Glimmer offers scaffolding options for Apps, Gems, and Custom Widgets. Glimmer also includes native-executable packaging support, sorely lacking in other libraries, thus enabling the delivery of desktop apps written in Ruby as truly native DMG/PKG/APP files on the Mac + App Store, MSI/EXE files on Windows, and Gem Packaged Shell Scripts on Linux.".freeze
16
16
  s.email = "andy.am@gmail.com".freeze
17
17
  s.executables = ["glimmer".freeze, "girb".freeze]
@@ -115,13 +115,17 @@ Gem::Specification.new do |s|
115
115
  "lib/glimmer/swt/custom/radio_group.rb",
116
116
  "lib/glimmer/swt/custom/shape.rb",
117
117
  "lib/glimmer/swt/custom/shape/arc.rb",
118
+ "lib/glimmer/swt/custom/shape/cubic.rb",
118
119
  "lib/glimmer/swt/custom/shape/focus.rb",
119
120
  "lib/glimmer/swt/custom/shape/image.rb",
120
121
  "lib/glimmer/swt/custom/shape/line.rb",
121
122
  "lib/glimmer/swt/custom/shape/oval.rb",
123
+ "lib/glimmer/swt/custom/shape/path.rb",
124
+ "lib/glimmer/swt/custom/shape/path_segment.rb",
122
125
  "lib/glimmer/swt/custom/shape/point.rb",
123
126
  "lib/glimmer/swt/custom/shape/polygon.rb",
124
127
  "lib/glimmer/swt/custom/shape/polyline.rb",
128
+ "lib/glimmer/swt/custom/shape/quad.rb",
125
129
  "lib/glimmer/swt/custom/shape/rectangle.rb",
126
130
  "lib/glimmer/swt/custom/shape/text.rb",
127
131
  "lib/glimmer/swt/date_time_proxy.rb",
@@ -180,6 +184,7 @@ Gem::Specification.new do |s|
180
184
  "samples/hello/hello_button.rb",
181
185
  "samples/hello/hello_canvas.rb",
182
186
  "samples/hello/hello_canvas_animation.rb",
187
+ "samples/hello/hello_canvas_path.rb",
183
188
  "samples/hello/hello_canvas_transform.rb",
184
189
  "samples/hello/hello_checkbox.rb",
185
190
  "samples/hello/hello_checkbox_group.rb",
@@ -32,7 +32,7 @@ module Glimmer
32
32
  include ParentExpression
33
33
 
34
34
  def can_interpret?(parent, keyword, *args, &block)
35
- parent.is_a?(Glimmer::SWT::Custom::Drawable) and
35
+ (parent.is_a?(Glimmer::SWT::Custom::Drawable) or parent.is_a?(Glimmer::SWT::Custom::Shape)) and
36
36
  Glimmer::SWT::Custom::Shape.valid?(parent, keyword, *args, &block)
37
37
  end
38
38
 
@@ -34,12 +34,20 @@ module Glimmer
34
34
  @shapes ||= []
35
35
  end
36
36
 
37
+ def expanded_shapes
38
+ @shapes.map do |shape|
39
+ [shape] + shape.expanded_shapes
40
+ end.flatten
41
+ end
42
+
37
43
  def image_buffered_shapes
38
44
  @image_buffered_shapes ||= []
39
45
  end
40
46
 
47
+ # TODO add a method like shapes that specifies drawable_properties to be able to adjust properties like transform in between shapes
48
+
41
49
  def shape_at_location(x, y)
42
- shapes.reverse.detect {|shape| shape.include?(x, y)}
50
+ expanded_shapes.reverse.detect {|shape| shape.include?(x, y)}
43
51
  end
44
52
 
45
53
  def add_shape(shape)
@@ -132,7 +140,7 @@ module Glimmer
132
140
  @paint_listener_proxy = on_swt_paint(&shape_painter)
133
141
  end
134
142
  else
135
- redraw if @finished_add_content && !is_disposed
143
+ redraw if respond_to?(:redraw) && @finished_add_content && !is_disposed
136
144
  end
137
145
  end
138
146
  alias resetup_shape_painting setup_shape_painting
@@ -26,6 +26,34 @@ require 'glimmer/swt/color_proxy'
26
26
  require 'glimmer/swt/font_proxy'
27
27
  require 'glimmer/swt/transform_proxy'
28
28
 
29
+ class Java::OrgEclipseSwtGraphics::GC
30
+ def setLineDashOffset(value)
31
+ lineMiterLimit = getLineAttributes&.miterLimit || 999_999
32
+ setLineAttributes(Java::OrgEclipseSwtGraphics::LineAttributes.new(getLineWidth, getLineCap, getLineJoin, getLineStyle, getLineDash.map(&:to_f).to_java(:float), value, lineMiterLimit))
33
+ end
34
+ alias set_line_dash_offset setLineDashOffset
35
+ alias line_dash_offset= setLineDashOffset
36
+
37
+ def getLineDashOffset
38
+ getLineAttributes&.dashOffset
39
+ end
40
+ alias get_line_dash_offset getLineDashOffset
41
+ alias line_dash_offset getLineDashOffset
42
+
43
+ def setLineMiterLimit(value)
44
+ lineDashOffset = getLineAttributes&.dashOffset || 0
45
+ setLineAttributes(Java::OrgEclipseSwtGraphics::LineAttributes.new(getLineWidth, getLineCap, getLineJoin, getLineStyle, getLineDash.map(&:to_f).to_java(:float), lineDashOffset, value))
46
+ end
47
+ alias set_line_miter_limit setLineMiterLimit
48
+ alias line_miter_limit= setLineMiterLimit
49
+
50
+ def getLineMiterLimit
51
+ getLineAttributes&.miterLimit
52
+ end
53
+ alias get_line_miter_limit getLineMiterLimit
54
+ alias line_miter_limit getLineMiterLimit
55
+ end
56
+
29
57
  module Glimmer
30
58
  module SWT
31
59
  module Custom
@@ -34,8 +62,6 @@ module Glimmer
34
62
  class Shape
35
63
  include Packages
36
64
  include Properties
37
- # TODO support textExtent sized shapes nested within text/string
38
- # TODO support a Pattern DSL for methods that take Pattern arguments
39
65
 
40
66
  class << self
41
67
  def create(parent, keyword, *args, &property_block)
@@ -48,7 +74,8 @@ module Glimmer
48
74
  end
49
75
 
50
76
  def valid?(parent, keyword, *args, &block)
51
- gc_instance_methods.include?(method_name(keyword, arg_options(args)))
77
+ gc_instance_methods.include?(method_name(keyword, arg_options(args))) ||
78
+ constants.include?(keyword.to_s.camelcase(:upper).to_sym)
52
79
  end
53
80
 
54
81
  def gc_instance_methods
@@ -100,15 +127,18 @@ module Glimmer
100
127
  end
101
128
  end
102
129
 
103
- attr_reader :parent, :name, :args, :options
130
+ attr_reader :drawable, :parent, :name, :args, :options, :shapes
131
+ attr_accessor :extent
104
132
 
105
133
  def initialize(parent, keyword, *args, &property_block)
106
134
  @parent = parent
135
+ @drawable = @parent.is_a?(Drawable) ? @parent : @parent.drawable
107
136
  @name = keyword
108
137
  @options = self.class.arg_options(args, extract: true)
109
138
  @method_name = self.class.method_name(keyword, @options)
110
139
  @args = args
111
140
  @properties = {}
141
+ @shapes = [] # nested shapes
112
142
  @options.reject {|key, value| %w[fill gradient round].include?(key.to_s)}.each do |property, property_args|
113
143
  @properties[property] = property_args
114
144
  end
@@ -116,13 +146,20 @@ module Glimmer
116
146
  post_add_content if property_block.nil?
117
147
  end
118
148
 
149
+ def add_shape(shape)
150
+ @shapes << shape
151
+ calculated_args_changed_for_defaults!
152
+ end
153
+
119
154
  def draw?
120
155
  !fill?
121
156
  end
157
+ alias drawn? draw?
122
158
 
123
159
  def fill?
124
160
  @options[:fill]
125
161
  end
162
+ alias filled? fill?
126
163
 
127
164
  def gradient?
128
165
  @options[:gradient]
@@ -132,26 +169,68 @@ module Glimmer
132
169
  @options[:round]
133
170
  end
134
171
 
135
- # subclasses (like polygon) may override to indicate if a point x,y coordinates fall inside the shape
136
- # has a default implementation for rectangle and oval
172
+ # The bounding box top-left x, y, width, height in absolute positioning
173
+ def bounds
174
+ org.eclipse.swt.graphics.Rectangle.new(absolute_x, absolute_y, calculated_width, calculated_height)
175
+ end
176
+
177
+ # The bounding box top-left x and y
178
+ def location
179
+ org.eclipse.swt.graphics.Point.new(bounds.x, bounds.y)
180
+ end
181
+
182
+ # The bounding box width and height (as a Point object with x being width and y being height)
183
+ def size
184
+ org.eclipse.swt.graphics.Point.new(calculated_width, calculated_height)
185
+ end
186
+
187
+ def extent
188
+ @extent || size
189
+ end
190
+
191
+ # Returns if shape contains a point
192
+ # Subclasses (like polygon) may override to indicate if a point x,y coordinates falls inside the shape
193
+ # some shapes may choose to provide a fuzz factor to make usage of this method for mouse clicking more user friendly
194
+ def contain?(x, y)
195
+ # assume a rectangular filled shape by default (works for several shapes like image, text, and focus)
196
+ x.between?(self.absolute_x, self.absolute_x + calculated_width) && y.between?(self.absolute_y, self.absolute_y + calculated_height)
197
+ end
198
+
199
+ # Returns if shape includes a point. When the shape is filled, this is the same as contain. When the shape is drawn, it only returns true if the point lies on the edge (boundary/border)
200
+ # Subclasses (like polygon) may override to indicate if a point x,y coordinates falls on the edge of a drawn shape or inside a filled shape
201
+ # some shapes may choose to provide a fuzz factor to make usage of this method for mouse clicking more user friendly
137
202
  def include?(x, y)
138
- case @name
139
- when 'rectangle', 'oval', 'arc'
140
- self_x = self.x
141
- self_y = self.y
142
- width = self.width
143
- height = self.height
144
- x.between?(self_x, self_x + width) && y.between?(self_y, self_y + height)
145
- else
146
- false
147
- end
203
+ # assume a rectangular shape by default
204
+ contain?(x, y)
205
+ end
206
+
207
+ # Indicates if a shape's x, y, width, height differ from its bounds calculation (e.g. arc / polygon)
208
+ def irregular?
209
+ false
148
210
  end
149
211
 
212
+ # moves by x delta and y delta. Subclasses must implement
213
+ # provdies a default implementation that assumes moving x and y is sufficient by default (not for polygons though, which must override)
150
214
  def move_by(x_delta, y_delta)
151
- case @name
152
- when 'rectangle', 'oval', 'arc'
153
- self.x += x_delta
154
- self.y += y_delta
215
+ if respond_to?(:x) && respond_to?(:y) && respond_to?(:x=) && respond_to?(:y=)
216
+ if default_x?
217
+ self.default_x_delta += x_delta
218
+ else
219
+ self.x += x_delta
220
+ end
221
+ if default_y?
222
+ self.default_y_delta += y_delta
223
+ else
224
+ self.y += y_delta
225
+ end
226
+ end
227
+ end
228
+
229
+ def content(&block)
230
+ Glimmer::SWT::DisplayProxy.instance.auto_exec do
231
+ Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::SWT::ShapeExpression.new, &block)
232
+ calculated_args_changed!(children: false)
233
+ drawable.redraw unless drawable.is_a?(ImageProxy)
155
234
  end
156
235
  end
157
236
 
@@ -164,23 +243,28 @@ module Glimmer
164
243
  end
165
244
 
166
245
  def post_add_content
167
- unless @content_added
246
+ # unless @content_added # TODO delete if no longer needed
168
247
  amend_method_name_options_based_on_properties!
169
- @parent.setup_shape_painting unless @parent.is_a?(ImageProxy)
248
+ @drawable.setup_shape_painting unless @drawable.is_a?(ImageProxy)
170
249
  @content_added = true
171
- end
250
+ # end
172
251
  end
173
252
 
174
253
  def apply_property_arg_conversions(method_name, property, args)
175
254
  args = args.dup
176
255
  the_java_method = org.eclipse.swt.graphics.GC.java_class.declared_instance_methods.detect {|m| m.name == method_name}
256
+ return args if the_java_method.nil?
177
257
  if the_java_method.parameter_types.first == Color.java_class && args.first.is_a?(RGB)
178
258
  args[0] = [args[0].red, args[0].green, args[0].blue]
179
259
  end
180
260
  if ['setBackground', 'setForeground'].include?(method_name.to_s) && args.first.is_a?(Array)
181
261
  args[0] = ColorProxy.new(args[0])
182
262
  end
183
- if args.first.is_a?(Symbol) || args.first.is_a?(String)
263
+ if method_name.to_s == 'setLineDash' && args.size > 1
264
+ args[0] = args.dup
265
+ args[1..-1] = []
266
+ end
267
+ if args.first.is_a?(Symbol) || args.first.is_a?(::String)
184
268
  if the_java_method.parameter_types.first == Color.java_class
185
269
  args[0] = ColorProxy.new(args[0])
186
270
  end
@@ -201,11 +285,11 @@ module Glimmer
201
285
  args[0] = args[0].swt_transform
202
286
  end
203
287
  if ['setBackgroundPattern', 'setForegroundPattern'].include?(method_name.to_s)
204
- @parent.requires_shape_disposal = true
288
+ @drawable.requires_shape_disposal = true
205
289
  args = args.first if args.first.is_a?(Array)
206
290
  args.each_with_index do |arg, i|
207
291
  arg = ColorProxy.new(arg.red, arg.green, arg.blue) if arg.is_a?(RGB)
208
- arg = ColorProxy.new(arg) if arg.is_a?(Symbol) || arg.is_a?(String)
292
+ arg = ColorProxy.new(arg) if arg.is_a?(Symbol) || arg.is_a?(::String)
209
293
  arg = arg.swt_color if arg.is_a?(ColorProxy)
210
294
  args[i] = arg
211
295
  end
@@ -229,7 +313,7 @@ module Glimmer
229
313
  @args[1..-1] = []
230
314
  end
231
315
  if @name == 'image'
232
- if @args.first.is_a?(String)
316
+ if @args.first.is_a?(::String)
233
317
  @args[0] = ImageProxy.new(@args[0])
234
318
  end
235
319
  if @args.first.is_a?(ImageProxy)
@@ -240,7 +324,7 @@ module Glimmer
240
324
  end
241
325
  end
242
326
  if @name == 'text'
243
- if @args[3].is_a?(Symbol) || @args[3].is_a?(String)
327
+ if @args[3].is_a?(Symbol) || @args[3].is_a?(::String)
244
328
  @args[3] = [@args[3]]
245
329
  end
246
330
  if @args[3].is_a?(Array)
@@ -253,19 +337,21 @@ module Glimmer
253
337
  end
254
338
 
255
339
  def apply_shape_arg_defaults!
340
+ self.x = :default if current_parameter_name?(:x) && x.nil?
341
+ self.y = :default if current_parameter_name?(:y) && y.nil?
342
+ self.dest_x = :default if current_parameter_name?(:dest_x) && dest_x.nil?
343
+ self.dest_y = :default if current_parameter_name?(:dest_y) && dest_y.nil?
344
+ self.width = :default if current_parameter_name?(:width) && width.nil?
345
+ self.height = :default if current_parameter_name?(:height) && height.nil?
256
346
  if @name.include?('rectangle') && round? && @args.size.between?(4, 5)
257
347
  (6 - @args.size).times {@args << 60}
258
348
  elsif @name.include?('rectangle') && gradient? && @args.size == 4
259
- @args << true
260
- elsif (@name.include?('text') || @name.include?('String')) && !@properties.keys.map(&:to_s).include?('background') && @args.size == 3
261
- @args << true
349
+ set_attribute('vertical', true, redraw: false)
350
+ elsif (@name.include?('text') || @name.include?('string')) && !@properties.keys.map(&:to_s).include?('background') && @args.size < 4
351
+ set_attribute('is_transparent', true, redraw: false)
262
352
  end
263
353
  if @name.include?('image')
264
- @parent.requires_shape_disposal = true
265
- if @args.size == 1
266
- @args[1] = 0
267
- @args[2] = 0
268
- end
354
+ @drawable.requires_shape_disposal = true
269
355
  end
270
356
  end
271
357
 
@@ -282,7 +368,7 @@ module Glimmer
282
368
 
283
369
  def amend_method_name_options_based_on_properties!
284
370
  return if @name == 'point'
285
- if @name != 'text' && has_some_background? && !has_some_foreground?
371
+ if @name != 'text' && @name != 'string' && has_some_background? && !has_some_foreground?
286
372
  @options[:fill] = true
287
373
  elsif !has_some_background? && has_some_foreground?
288
374
  @options[:fill] = false
@@ -301,6 +387,12 @@ module Glimmer
301
387
  []
302
388
  end
303
389
 
390
+ # subclasses may override to specify location parameter names if different from x and y (e.g. all polygon points are location parameters)
391
+ # used in calculating movement changes
392
+ def location_parameter_names
393
+ [:x, :y]
394
+ end
395
+
304
396
  def possible_parameter_names
305
397
  parameter_names
306
398
  end
@@ -309,8 +401,12 @@ module Glimmer
309
401
  possible_parameter_names.map(&:to_s).include?(ruby_attribute_getter(attribute_name))
310
402
  end
311
403
 
404
+ def current_parameter_name?(attribute_name)
405
+ parameter_names.include?(attribute_name.to_s.to_sym)
406
+ end
407
+
312
408
  def parameter_index(attribute_name)
313
- parameter_names.map(&:to_s).index(attribute_name.to_s)
409
+ parameter_names.index(attribute_name.to_s.to_sym)
314
410
  end
315
411
 
316
412
  def set_parameter_attribute(attribute_name, *args)
@@ -319,24 +415,44 @@ module Glimmer
319
415
 
320
416
  def has_attribute?(attribute_name, *args)
321
417
  self.class.gc_instance_methods.include?(attribute_setter(attribute_name)) or
322
- parameter_name?(attribute_name)
418
+ parameter_name?(attribute_name) or
419
+ (respond_to?(attribute_name, super: true) and respond_to?(ruby_attribute_setter(attribute_name), super: true))
323
420
  end
324
-
421
+
325
422
  def set_attribute(attribute_name, *args)
423
+ options = args.last if args.last.is_a?(Hash)
424
+ args.pop if !options.nil? && !options[:redraw].nil?
425
+ perform_redraw = @perform_redraw
426
+ perform_redraw = options[:redraw] if perform_redraw.nil? && !options.nil?
427
+ perform_redraw = true if perform_redraw.nil?
326
428
  if parameter_name?(attribute_name)
327
429
  set_parameter_attribute(attribute_name, *args)
430
+ elsif (respond_to?(attribute_name, super: true) and respond_to?(ruby_attribute_setter(attribute_name), super: true))
431
+ self.send(ruby_attribute_setter(attribute_name), *args)
328
432
  else
329
433
  @properties[ruby_attribute_getter(attribute_name)] = args
330
434
  end
331
- if @content_added && !@parent.is_disposed
435
+ if @content_added && perform_redraw && !drawable.is_disposed
332
436
  @calculated_paint_args = false
333
- @parent.redraw
437
+ attribute_name = ruby_attribute_getter(attribute_name)
438
+ if location_parameter_names.map(&:to_s).include?(attribute_name)
439
+ @calculated_args = nil
440
+ parent.calculated_args_changed_for_defaults! if parent.is_a?(Shape)
441
+ end
442
+ if ['width', 'height'].include?(attribute_name)
443
+ calculated_args_changed_for_defaults!
444
+ end
445
+ # TODO consider redrawing an image proxy's gc in the future
446
+ drawable.redraw unless drawable.is_a?(ImageProxy)
334
447
  end
335
448
  end
336
449
 
337
450
  def get_attribute(attribute_name)
338
451
  if parameter_name?(attribute_name)
339
- @args[parameter_index(attribute_name)]
452
+ arg_index = parameter_index(attribute_name)
453
+ @args[arg_index] if arg_index
454
+ elsif (respond_to?(attribute_name, super: true) and respond_to?(ruby_attribute_setter(attribute_name), super: true))
455
+ self.send(attribute_name)
340
456
  else
341
457
  @properties.symbolize_keys[attribute_name.to_s.to_sym]
342
458
  end
@@ -353,7 +469,9 @@ module Glimmer
353
469
  end
354
470
 
355
471
  def respond_to?(method_name, *args, &block)
356
- if has_attribute?(method_name)
472
+ options = args.last if args.last.is_a?(Hash)
473
+ super_invocation = options && options[:super]
474
+ if !super_invocation && has_attribute?(method_name)
357
475
  true
358
476
  else
359
477
  super
@@ -382,6 +500,7 @@ module Glimmer
382
500
  end
383
501
 
384
502
  def dispose(dispose_images: true, dispose_patterns: true)
503
+ shapes.each { |shape| shape.is_a?(Shape::Path) && shape.dispose }
385
504
  if dispose_patterns
386
505
  @background_pattern&.dispose
387
506
  @background_pattern = nil
@@ -394,21 +513,297 @@ module Glimmer
394
513
  end
395
514
  @parent.shapes.delete(self)
396
515
  end
397
-
516
+
398
517
  def paint(paint_event)
518
+ # pre-paint children an extra-time first when default width/height need to be calculated for defaults
519
+ paint_children(paint_event) if default_width? || default_height?
520
+ paint_self(paint_event)
521
+ # re-paint children from scratch in the special case of pre-calculating parent width/height to re-center within new parent dimensions
522
+ shapes.each(&:calculated_args_changed!) if default_width? || default_height?
523
+ paint_children(paint_event)
524
+ rescue => e
525
+ Glimmer::Config.logger.error {"Error encountered in painting shape (#{self.inspect}) with calculated args (#{@calculated_args}) and args (#{@args})"}
526
+ Glimmer::Config.logger.error {e.full_message}
527
+ end
528
+
529
+ def paint_self(paint_event)
530
+ @painting = true
399
531
  calculate_paint_args!
532
+ @original_properties_backup = {}
400
533
  @properties.each do |property, args|
401
534
  method_name = attribute_setter(property)
402
- # TODO consider optimization of not setting a background/foreground/font if it didn't change from last shape
535
+ @original_properties_backup[method_name] = paint_event.gc.send(method_name.sub('set', 'get')) rescue nil
403
536
  paint_event.gc.send(method_name, *args)
404
537
  if property == 'transform' && args.first.is_a?(TransformProxy)
405
538
  args.first.swt_transform.dispose
406
539
  end
407
540
  end
408
- paint_event.gc.send(@method_name, *@args)
541
+ ensure_extent(paint_event)
542
+ if !@calculated_args || parent_shape_absolute_location_changed?
543
+ @calculated_args = calculated_args
544
+ end
545
+ # paint unless parent's calculated args are not calculated yet, meaning it is about to get painted and trigger a paint on this child anyways
546
+ paint_event.gc.send(@method_name, *@calculated_args) unless parent.is_a?(Shape) && !parent.calculated_args?
547
+ @original_properties_backup.each do |method_name, value|
548
+ paint_event.gc.send(method_name, value)
549
+ end
550
+ @painting = false
409
551
  rescue => e
410
- Glimmer::Config.logger.error {"Error encountered in painting shape: #{self.inspect}"}
552
+ Glimmer::Config.logger.error {"Error encountered in painting shape (#{self.inspect}) with calculated args (#{@calculated_args}) and args (#{@args})"}
411
553
  Glimmer::Config.logger.error {e.full_message}
554
+ ensure
555
+ @painting = false
556
+ end
557
+
558
+ def paint_children(paint_event)
559
+ shapes.to_a.each do |shape|
560
+ shape.paint(paint_event)
561
+ end
562
+ end
563
+
564
+ def ensure_extent(paint_event)
565
+ old_extent = @extent
566
+ if ['text', 'string'].include?(@name)
567
+ extent_args = [string]
568
+ extent_flags = SWTProxy[:draw_transparent] if current_parameter_name?(:is_transparent) && is_transparent
569
+ extent_flags = flags if current_parameter_name?(:flags)
570
+ extent_args << extent_flags unless extent_flags.nil?
571
+ self.extent = paint_event.gc.send("#{@name}Extent", *extent_args)
572
+ end
573
+ if !@extent.nil? && (old_extent&.x != @extent&.x || old_extent&.y != @extent&.y)
574
+ calculated_args_changed!
575
+ parent.calculated_args_changed_for_defaults! if parent.is_a?(Shape)
576
+ end
577
+ end
578
+
579
+ def expanded_shapes
580
+ if shapes.to_a.any?
581
+ shapes.map do |shape|
582
+ [shape] + shape.expanded_shapes
583
+ end.flatten
584
+ else
585
+ []
586
+ end
587
+ end
588
+
589
+ def parent_shape_absolute_location_changed?
590
+ (parent.is_a?(Shape) && (parent.absolute_x != @parent_absolute_x || parent.absolute_y != @parent_absolute_y))
591
+ end
592
+
593
+ def calculated_args_changed!(children: true)
594
+ # TODO add a children: true option to enable setting to false to avoid recalculating children args
595
+ @calculated_args = nil
596
+ shapes.each(&:calculated_args_changed!) if children
597
+ end
598
+
599
+ def calculated_args_changed_for_defaults!
600
+ has_default_dimensions = default_width? || default_height?
601
+ parent_calculated_args_changed_for_defaults = has_default_dimensions
602
+ @calculated_args = nil if default_x? || default_y? || has_default_dimensions
603
+ if has_default_dimensions && parent.is_a?(Shape)
604
+ parent.calculated_args_changed_for_defaults!
605
+ elsif @content_added && !drawable.is_disposed
606
+ # TODO consider optimizing in the future if needed by ensuring one redraw for all parents in the hierarchy at the end instead of doing one per parent that needs it
607
+ drawable.redraw if !@painting && !drawable.is_a?(ImageProxy)
608
+ end
609
+ end
610
+
611
+ def calculated_args?
612
+ !!@calculated_args
613
+ end
614
+
615
+ # args translated to absolute coordinates
616
+ def calculated_args
617
+ return @args if !default_x? && !default_y? && !default_width? && !default_height? && parent.is_a?(Drawable)
618
+ # Note: Must set x and move_by because not all shapes have a real x and some must translate all their points with move_by
619
+ # TODO change that by setting a bounding box for all shapes with a calculated top-left x, y and
620
+ # a setter that does the moving inside them instead so that I could rely on absolute_x and absolute_y
621
+ # here to get the job done of calculating absolute args
622
+ @perform_redraw = false
623
+ original_x = nil
624
+ original_y = nil
625
+ original_width = nil
626
+ original_height = nil
627
+ if parent.is_a?(Shape)
628
+ @parent_absolute_x = parent.absolute_x
629
+ @parent_absolute_y = parent.absolute_y
630
+ end
631
+ if default_width?
632
+ original_width = width
633
+ self.width = default_width + default_width_delta
634
+ end
635
+ if default_height?
636
+ original_height = height
637
+ self.height = default_height + default_height_delta
638
+ end
639
+ if default_x?
640
+ original_x = x
641
+ self.x = default_x + default_x_delta
642
+ end
643
+ if default_y?
644
+ original_y = y
645
+ self.y = default_y + default_y_delta
646
+ end
647
+ if parent.is_a?(Shape)
648
+ move_by(@parent_absolute_x, @parent_absolute_y)
649
+ result_args = @args.clone
650
+ move_by(-1*@parent_absolute_x, -1*@parent_absolute_y)
651
+ else
652
+ result_args = @args.clone
653
+ end
654
+ if original_x
655
+ self.x = original_x
656
+ end
657
+ if original_y
658
+ self.y = original_y
659
+ end
660
+ if original_width
661
+ self.width = original_width
662
+ end
663
+ if original_height
664
+ self.height = original_height
665
+ end
666
+ @perform_redraw = true
667
+ result_args
668
+ end
669
+
670
+ def default_x?
671
+ current_parameter_name?(:x) and
672
+ (x.nil? || x.to_s == 'default' || (x.is_a?(Array) && x.first.to_s == 'default'))
673
+ end
674
+
675
+ def default_y?
676
+ current_parameter_name?(:y) and
677
+ (y.nil? || y.to_s == 'default' || (y.is_a?(Array) && y.first.to_s == 'default'))
678
+ end
679
+
680
+ def default_width?
681
+ return false unless current_parameter_name?(:width)
682
+ width = self.width
683
+ (width.nil? || width == :default || width == 'default' || (width.is_a?(Array) && (width.first.to_s == :default || width.first.to_s == 'default')))
684
+ end
685
+
686
+ def default_height?
687
+ return false unless current_parameter_name?(:height)
688
+ height = self.height
689
+ (height.nil? || height == :default || height == 'default' || (height.is_a?(Array) && (height.first.to_s == :default || height.first.to_s == 'default')))
690
+ end
691
+
692
+ def default_x
693
+ result = ((parent.size.x - size.x) / 2)
694
+ result += parent.bounds.x - parent.absolute_x if parent.is_a?(Shape) && parent.irregular?
695
+ result
696
+ end
697
+
698
+ def default_y
699
+ result = ((parent.size.y - size.y) / 2)
700
+ result += parent.bounds.y - parent.absolute_y if parent.is_a?(Shape) && parent.irregular?
701
+ result
702
+ end
703
+
704
+ def default_width
705
+ # TODO consider caching
706
+ x_ends = shapes.map do |shape|
707
+ shape_width = shape.calculated_width.to_f
708
+ shape_x = shape.default_x? ? 0 : shape.x.to_f
709
+ shape_x + shape_width
710
+ end
711
+ x_ends.max.to_f
712
+ end
713
+
714
+ def default_height
715
+ # TODO consider caching
716
+ y_ends = shapes.map do |shape|
717
+ shape_height = shape.calculated_height.to_f
718
+ shape_y = shape.default_y? ? 0 : shape.y.to_f
719
+ shape_y + shape_height
720
+ end
721
+ y_ends.max.to_f
722
+ end
723
+
724
+ def calculated_width
725
+ default_width? ? (default_width + default_width_delta) : width
726
+ end
727
+
728
+ def calculated_height
729
+ default_height? ? (default_height + default_height_delta) : height
730
+ end
731
+
732
+ def default_x_delta
733
+ return 0 unless default_x? && x.is_a?(Array)
734
+ x[1].to_f
735
+ end
736
+
737
+ def default_y_delta
738
+ return 0 unless default_y? && y.is_a?(Array)
739
+ y[1].to_f
740
+ end
741
+
742
+ def default_width_delta
743
+ return 0 unless default_width? && width.is_a?(Array)
744
+ width[1].to_f
745
+ end
746
+
747
+ def default_height_delta
748
+ return 0 unless default_height? && height.is_a?(Array)
749
+ height[1].to_f
750
+ end
751
+
752
+ def default_x_delta=(delta)
753
+ return unless default_x?
754
+ self.x = [:default, delta]
755
+ end
756
+
757
+ def default_y_delta=(delta)
758
+ return unless default_y?
759
+ self.y = [:default, delta]
760
+ end
761
+
762
+ def default_width_delta=(delta)
763
+ return unless default_width?
764
+ self.width = [:default, delta]
765
+ end
766
+
767
+ def default_height_delta=(delta)
768
+ return unless default_height?
769
+ self.height = [:default, delta]
770
+ end
771
+
772
+ def calculated_x
773
+ result = default_x? ? default_x : self.x
774
+ result += default_x_delta
775
+ result
776
+ end
777
+
778
+ def calculated_y
779
+ result = default_y? ? default_y : self.y
780
+ result += default_y_delta
781
+ result
782
+ end
783
+
784
+ def absolute_x
785
+ x = calculated_x
786
+ if parent.is_a?(Shape)
787
+ parent.absolute_x + x
788
+ else
789
+ x
790
+ end
791
+ end
792
+
793
+ def absolute_y
794
+ y = calculated_y
795
+ if parent.is_a?(Shape)
796
+ parent.absolute_y + y
797
+ else
798
+ y
799
+ end
800
+ end
801
+
802
+ # Overriding inspect to avoid printing very long shape hierarchies
803
+ def inspect
804
+ "#<#{self.class.name}:0x#{self.hash.to_s(16)} args=#{@args.inspect}, properties=#{@properties.inspect}}>"
805
+ rescue => e
806
+ "#<#{self.class.name}:0x#{self.hash.to_s(16)}"
412
807
  end
413
808
 
414
809
  def calculate_paint_args!
@@ -420,7 +815,7 @@ module Glimmer
420
815
  if @properties[:foreground].is_a?(Array)
421
816
  @properties[:foreground] = ColorProxy.new(@properties[:foreground], ensure_bounds: false)
422
817
  end
423
- if @properties[:foreground].is_a?(Symbol) || @properties[:foreground].is_a?(String)
818
+ if @properties[:foreground].is_a?(Symbol) || @properties[:foreground].is_a?(::String)
424
819
  @properties[:foreground] = ColorProxy.new(@properties[:foreground], ensure_bounds: false)
425
820
  end
426
821
  if @properties[:foreground].is_a?(ColorProxy)
@@ -428,14 +823,14 @@ module Glimmer
428
823
  end
429
824
  end
430
825
  else
431
- @properties['background'] = [@parent.background] if fill? && !has_some_background?
432
- @properties['foreground'] = [@parent.foreground] if @parent.respond_to?(:foreground) && draw? && !has_some_foreground?
826
+ @properties['background'] = [@drawable.background] if fill? && !has_some_background?
827
+ @properties['foreground'] = [@drawable.foreground] if @drawable.respond_to?(:foreground) && draw? && !has_some_foreground?
433
828
  # TODO regarding alpha, make sure to reset it to parent stored alpha once we allow setting shape properties on parents directly without shapes
434
829
  @properties['alpha'] ||= [255]
435
- @properties['font'] = [@parent.font] if @parent.respond_to?(:font) && draw? && !@properties.keys.map(&:to_s).include?('font')
436
- # TODO regarding transform, make sure to reset it to parent stored alpha once we allow setting shape properties on parents directly without shapes
830
+ @properties['font'] = [@drawable.font] if @drawable.respond_to?(:font) && draw? && !@properties.keys.map(&:to_s).include?('font')
831
+ # TODO regarding transform, make sure to reset it to parent stored transform once we allow setting shape properties on parents directly without shapes
437
832
  # Also do that with all future-added properties
438
- @properties['transform'] = [nil] if @parent.respond_to?(:transform) && !@properties.keys.map(&:to_s).include?('transform')
833
+ @properties['transform'] = [nil] if @drawable.respond_to?(:transform) && !@properties.keys.map(&:to_s).include?('transform')
439
834
  @properties.each do |property, args|
440
835
  method_name = attribute_setter(property)
441
836
  converted_args = apply_property_arg_conversions(method_name, property, args)