glimmer-dsl-swt 4.18.6.3 → 4.18.7.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +35 -0
  3. data/README.md +4 -4
  4. data/VERSION +1 -1
  5. data/docs/reference/GLIMMER_COMMAND.md +46 -3
  6. data/docs/reference/GLIMMER_GUI_DSL_SYNTAX.md +26 -3
  7. data/docs/reference/GLIMMER_SAMPLES.md +56 -0
  8. data/glimmer-dsl-swt.gemspec +15 -6
  9. data/lib/glimmer/dsl/swt/animation_expression.rb +1 -1
  10. data/lib/glimmer/dsl/swt/custom_shape_expression.rb +61 -0
  11. data/lib/glimmer/dsl/swt/custom_widget_expression.rb +1 -1
  12. data/lib/glimmer/dsl/swt/dsl.rb +1 -0
  13. data/lib/glimmer/dsl/swt/expand_item_expression.rb +4 -4
  14. data/lib/glimmer/dsl/swt/image_expression.rb +1 -1
  15. data/lib/glimmer/dsl/swt/multiply_expression.rb +1 -1
  16. data/lib/glimmer/dsl/swt/shape_expression.rb +1 -1
  17. data/lib/glimmer/dsl/swt/transform_expression.rb +1 -1
  18. data/lib/glimmer/dsl/swt/widget_expression.rb +1 -1
  19. data/lib/glimmer/rake_task.rb +36 -6
  20. data/lib/glimmer/rake_task/list.rb +8 -0
  21. data/lib/glimmer/rake_task/scaffold.rb +103 -0
  22. data/lib/glimmer/swt/custom/shape.rb +464 -181
  23. data/lib/glimmer/swt/custom/shape/image.rb +7 -9
  24. data/lib/glimmer/swt/custom/shape/path.rb +1 -5
  25. data/lib/glimmer/swt/custom/shape/polygon.rb +24 -8
  26. data/lib/glimmer/swt/custom/shape/polyline.rb +5 -0
  27. data/lib/glimmer/swt/custom/shape/rectangle.rb +10 -19
  28. data/lib/glimmer/swt/display_proxy.rb +1 -1
  29. data/lib/glimmer/swt/message_box_proxy.rb +1 -1
  30. data/lib/glimmer/swt/shell_proxy.rb +1 -1
  31. data/lib/glimmer/swt/transform_proxy.rb +1 -1
  32. data/lib/glimmer/swt/widget_proxy.rb +1 -1
  33. data/lib/glimmer/ui/custom_shape.rb +281 -0
  34. data/samples/elaborate/meta_sample.rb +5 -5
  35. data/samples/elaborate/metronome.rb +177 -0
  36. data/samples/elaborate/tetris.rb +1 -15
  37. data/samples/elaborate/tetris/model/game.rb +3 -0
  38. data/samples/elaborate/tetris/view/bevel.rb +81 -0
  39. data/samples/elaborate/tetris/view/block.rb +5 -30
  40. data/samples/hello/hello_canvas.rb +3 -0
  41. data/samples/hello/hello_canvas_animation_data_binding.rb +66 -0
  42. data/samples/hello/hello_canvas_data_binding.rb +1 -1
  43. data/samples/hello/hello_custom_shape.rb +78 -0
  44. data/samples/hello/hello_shape.rb +71 -0
  45. data/samples/hello/hello_spinner.rb +7 -2
  46. data/sounds/metronome-down.wav +0 -0
  47. data/sounds/metronome-up.wav +0 -0
  48. metadata +13 -4
@@ -33,7 +33,7 @@ module Glimmer
33
33
  Glimmer::SWT::Custom::Animation.new(parent)
34
34
  end
35
35
 
36
- def add_content(parent, &block)
36
+ def add_content(parent, keyword, *args, &block)
37
37
  super
38
38
  parent.post_add_content
39
39
  end
@@ -0,0 +1,61 @@
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 'glimmer'
23
+ require 'glimmer/dsl/expression'
24
+ require 'glimmer/dsl/parent_expression'
25
+ require 'glimmer/dsl/top_level_expression'
26
+ require 'glimmer/ui/custom_shape'
27
+ require 'glimmer/swt/custom/code_text'
28
+ require 'glimmer/swt/custom/checkbox_group'
29
+
30
+ module Glimmer
31
+ module DSL
32
+ module SWT
33
+ class CustomShapeExpression < Expression
34
+ # TODO Make custom shapes automatically generate static expressions
35
+ include ParentExpression
36
+ include TopLevelExpression
37
+
38
+ def can_interpret?(parent, keyword, *args, &block)
39
+ !!UI::CustomShape.for(keyword)
40
+ end
41
+
42
+ def interpret(parent, keyword, *args, &block)
43
+ options = args.last.is_a?(Hash) ? args.pop : {}
44
+ UI::CustomShape.for(keyword).new(parent, *args, options, &block).tap do |new_custom_shape|
45
+ new_custom_shape.body_root.paint_pixel_by_pixel(&block) if block&.parameters&.count == 2
46
+ end
47
+ end
48
+
49
+ def add_content(parent, keyword, *args, &block)
50
+ # TODO consider avoiding source_location
51
+ return if block&.parameters&.count == 2
52
+ if block.source_location == parent.content&.__getobj__.source_location
53
+ parent.content.call(parent) unless parent.content.called?
54
+ else
55
+ super
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -51,7 +51,7 @@ module Glimmer
51
51
  end
52
52
  end
53
53
 
54
- def add_content(parent, &block)
54
+ def add_content(parent, keyword, *args, &block)
55
55
  # TODO consider avoiding source_location
56
56
  return if block&.parameters&.count == 2
57
57
  if block.source_location == parent.content&.__getobj__.source_location
@@ -57,6 +57,7 @@ module Glimmer
57
57
  widget
58
58
  custom_widget
59
59
  shape
60
+ custom_shape
60
61
  ]
61
62
  )
62
63
  end
@@ -1,5 +1,5 @@
1
1
  # Copyright (c) 2007-2021 Andy Maleh
2
- #
2
+ #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the
5
5
  # "Software"), to deal in the Software without restriction, including
@@ -7,10 +7,10 @@
7
7
  # distribute, sublicense, and/or sell copies of the Software, and to
8
8
  # permit persons to whom the Software is furnished to do so, subject to
9
9
  # the following conditions:
10
- #
10
+ #
11
11
  # The above copyright notice and this permission notice shall be
12
12
  # included in all copies or substantial portions of the Software.
13
- #
13
+ #
14
14
  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
15
  # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
16
  # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
@@ -49,7 +49,7 @@ module Glimmer
49
49
  Glimmer::SWT::ExpandItemProxy.new(parent, args)
50
50
  end
51
51
 
52
- def add_content(parent, &block)
52
+ def add_content(parent, keyword, *args, &block)
53
53
  super
54
54
  parent.post_add_content
55
55
  end
@@ -57,7 +57,7 @@ module Glimmer
57
57
  end
58
58
  end
59
59
 
60
- def add_content(parent, &block)
60
+ def add_content(parent, keyword, *args, &block)
61
61
  return if @create_pixel_by_pixel || block&.parameters&.count == 2
62
62
  super
63
63
  parent.post_add_content
@@ -39,7 +39,7 @@ module Glimmer
39
39
  Glimmer::SWT::TransformProxy.new(parent, *args, multiply: true)
40
40
  end
41
41
 
42
- def add_content(parent, &block)
42
+ def add_content(parent, keyword, *args, &block)
43
43
  super
44
44
  parent.post_add_content
45
45
  end
@@ -40,7 +40,7 @@ module Glimmer
40
40
  Glimmer::SWT::Custom::Shape.create(parent, keyword, *args, &block)
41
41
  end
42
42
 
43
- def add_content(parent, &block)
43
+ def add_content(parent, keyword, *args, &block)
44
44
  super
45
45
  parent.post_add_content
46
46
  end
@@ -41,7 +41,7 @@ module Glimmer
41
41
  Glimmer::SWT::TransformProxy.new(parent, *args)
42
42
  end
43
43
 
44
- def add_content(parent, &block)
44
+ def add_content(parent, keyword, *args, &block)
45
45
  super
46
46
  parent.post_add_content
47
47
  end
@@ -46,7 +46,7 @@ module Glimmer
46
46
  end
47
47
  end
48
48
 
49
- def add_content(parent, &block)
49
+ def add_content(parent, keyword, *args, &block)
50
50
  return if block&.parameters&.count == 2
51
51
  super
52
52
  parent.post_add_content
@@ -1,5 +1,5 @@
1
1
  # Copyright (c) 2007-2021 Andy Maleh
2
- #
2
+ #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the
5
5
  # "Software"), to deal in the Software without restriction, including
@@ -7,10 +7,10 @@
7
7
  # distribute, sublicense, and/or sell copies of the Software, and to
8
8
  # permit persons to whom the Software is furnished to do so, subject to
9
9
  # the following conditions:
10
- #
10
+ #
11
11
  # The above copyright notice and this permission notice shall be
12
12
  # included in all copies or substantial portions of the Software.
13
- #
13
+ #
14
14
  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
15
  # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
16
  # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
@@ -54,7 +54,7 @@ namespace :glimmer do
54
54
  if args[:app_path].nil?
55
55
  require 'fileutils'
56
56
  current_directory_name = File.basename(FileUtils.pwd)
57
- assumed_shell_script = File.join('.', 'bin', current_directory_name)
57
+ assumed_shell_script = File.join('.', 'bin', current_directory_name)
58
58
  assumed_shell_script = Dir.glob('./bin/*').detect {|f| File.file?(f)} if !File.exist?(assumed_shell_script)
59
59
  Glimmer::Launcher.new([assumed_shell_script]).launch
60
60
  else
@@ -145,6 +145,16 @@ namespace :glimmer do
145
145
  task :custom_widget, [:name, :namespace] => :customwidget
146
146
  task :"custom-widget", [:name, :namespace] => :customwidget
147
147
 
148
+ desc 'Scaffold Glimmer::UI::CustomShape subclass (part of a view) under app/views (namespace is optional) [alt: scaffold:cp]'
149
+ task :customshape, [:name, :namespace] do |t, args|
150
+ require_relative 'rake_task/scaffold'
151
+ Glimmer::RakeTask::Scaffold.custom_shape(args[:name], args[:namespace])
152
+ end
153
+
154
+ task :cp, [:name, :namespace] => :customshape
155
+ task :custom_shape, [:name, :namespace] => :customshape
156
+ task :"custom-shape", [:name, :namespace] => :customshape
157
+
148
158
  desc 'Desktopify a web app'
149
159
  task :desktopify, [:app_name, :website] do |t, args|
150
160
  require_relative 'rake_task/scaffold'
@@ -170,13 +180,24 @@ namespace :glimmer do
170
180
 
171
181
  task :cw, [:name, :namespace] => :customwidget
172
182
  task :custom_widget, [:name, :namespace] => :customwidget
173
- task :"custom-widget", [:name, :namespace] => :customwidget
183
+ task :"custom-widget", [:name, :namespace] => :customwidget
184
+
185
+ desc 'Scaffold Glimmer::UI::CustomShape subclass (part of a view) under its own Ruby gem project (namespace is required) [alt: scaffold:gem:cp]'
186
+ task :customshape, [:name, :namespace] do |t, args|
187
+ require_relative 'rake_task/scaffold'
188
+ Glimmer::RakeTask::Scaffold.custom_shape_gem(args[:name], args[:namespace])
189
+ end
190
+
191
+ task :cp, [:name, :namespace] => :customshape
192
+ task :custom_shape, [:name, :namespace] => :customshape
193
+ task :"custom-shape", [:name, :namespace] => :customshape
174
194
  end
175
195
 
176
196
  # legacy support
177
197
 
178
198
  task :custom_shell_gem, [:name, :namespace] => 'gem:customshell'
179
199
  task :custom_widget_gem, [:name, :namespace] => 'gem:customwidget'
200
+ task :custom_shape_gem, [:name, :namespace] => 'gem:customshape'
180
201
 
181
202
  end
182
203
 
@@ -204,6 +225,15 @@ namespace :glimmer do
204
225
  task :custom_shell, [:query] => :customshell
205
226
  task :"custom-shell", [:query] => :customshell
206
227
 
228
+ desc 'List Glimmer custom shape gems available at rubygems.org (query is optional) [alt: list:gems:cp]'
229
+ task :customshape, [:query] => :list_require do |t, args|
230
+ Glimmer::RakeTask::List.custom_shape_gems(args[:query])
231
+ end
232
+
233
+ task :cp, [:query] => :customshape
234
+ task :custom_shape, [:query] => :customshape
235
+ task :"custom-shape", [:query] => :customshape
236
+
207
237
  desc 'List Glimmer DSL gems available at rubygems.org (query is optional)'
208
238
  task :dsl, [:query] => :list_require do |t, args|
209
239
  Glimmer::RakeTask::List.dsl_gems(args[:query])
@@ -214,7 +244,7 @@ namespace :glimmer do
214
244
  # legacy support
215
245
 
216
246
  task :custom_shell_gems, [:name, :namespace] => 'gems:customshell'
217
- task :custom_widget_gems, [:name, :namespace] => 'gems:customwidget'
247
+ task :custom_widget_gems, [:name, :namespace] => 'gems:customwidget'
218
248
 
219
249
  end
220
250
  end
@@ -45,6 +45,14 @@ module Glimmer
45
45
  end
46
46
  end
47
47
 
48
+ def custom_shape_gems(query=nil)
49
+ list_gems('glimmer-cp-', query) do |result|
50
+ puts
51
+ puts " Glimmer Custom Shape Gems#{" matching [#{query}]" if query} at rubygems.org:"
52
+ puts result
53
+ end
54
+ end
55
+
48
56
  def dsl_gems(query=nil)
49
57
  list_gems('glimmer-dsl-', query) do |result|
50
58
  puts
@@ -215,6 +215,15 @@ module Glimmer
215
215
  write "#{parent_dir}/#{file_name(custom_widget_name)}.rb", custom_widget_file(custom_widget_name, namespace)
216
216
  end
217
217
 
218
+ def custom_shape(custom_shape_name, namespace)
219
+ namespace ||= current_dir_name
220
+ root_dir = File.exists?('app') ? 'app' : 'lib'
221
+ parent_dir = "#{root_dir}/views/#{file_name(namespace)}"
222
+ return puts("The file '#{parent_dir}/#{file_name(custom_shape_name)}.rb' already exists. Please either remove or pick a different name.") if File.exist?("#{parent_dir}/#{file_name(custom_shape_name)}.rb")
223
+ mkdir_p parent_dir unless File.exists?(parent_dir)
224
+ write "#{parent_dir}/#{file_name(custom_shape_name)}.rb", custom_shape_file(custom_shape_name, namespace)
225
+ end
226
+
218
227
  def custom_shell_gem(custom_shell_name, namespace)
219
228
  gem_name = "glimmer-cs-#{compact_name(custom_shell_name)}"
220
229
  gem_summary = "#{human_name(custom_shell_name)} - Glimmer Custom Shell"
@@ -334,6 +343,45 @@ module Glimmer
334
343
  puts 'Run `rake release` to release into rubygems.org once ready.'
335
344
  end
336
345
 
346
+ def custom_shape_gem(custom_shape_name, namespace)
347
+ return puts('Namespace is required! Usage: glimmer scaffold:custom_shape_gem[custom_shape_name,namespace]') unless `git config --get github.user`.to_s.strip == 'AndyObtiva'
348
+ gem_name = "glimmer-cp-#{compact_name(custom_shape_name)}"
349
+ gem_summary = "#{human_name(custom_shape_name)} - Glimmer Custom Shape"
350
+ if namespace
351
+ gem_name += "-#{compact_name(namespace)}"
352
+ gem_summary += " (#{human_name(namespace)})"
353
+ else
354
+ namespace = 'glimmer'
355
+ end
356
+
357
+ return puts("The directory '#{gem_name}' already exists. Please either remove or pick a different name.") if Dir.exist?(gem_name)
358
+ system "jruby -S gem install juwelier -v2.4.9 --no-document" unless juwelier_exists?
359
+ system "jruby -S juwelier --markdown --rspec --summary '#{gem_summary}' --description '#{gem_summary}' #{gem_name}"
360
+ return puts('Your Git user.name and/or github.user are missing! Please add in for Juwelier to help Glimmer with Scaffolding.') if `git config --get github.user`.strip.empty? && `git config --get user.name`.strip.empty?
361
+ cd gem_name
362
+ write '.gitignore', GITIGNORE
363
+ write '.ruby-version', RUBY_VERSION
364
+ write '.ruby-gemset', gem_name
365
+ write 'VERSION', '1.0.0'
366
+ write 'Gemfile', GEMFILE
367
+ write 'Rakefile', gem_rakefile
368
+ append "lib/#{gem_name}.rb", gem_main_file(custom_shape_name, namespace)
369
+ mkdir 'lib/views'
370
+ custom_shape(custom_shape_name, namespace)
371
+ if OS.windows?
372
+ system "bundle"
373
+ system "rspec --init"
374
+ else
375
+ system "bash -c '#{RVM_FUNCTION}\n cd .\n bundle\n rspec --init\n'"
376
+ end
377
+ write 'spec/spec_helper.rb', spec_helper_file
378
+ puts "Finished creating #{gem_name} Ruby gem."
379
+ puts 'Edit Rakefile to configure gem details.'
380
+ puts 'Run `rake` to execute specs.'
381
+ puts 'Run `rake build` to build gem.'
382
+ puts 'Run `rake release` to release into rubygems.org once ready.'
383
+ end
384
+
337
385
  private
338
386
 
339
387
  def juwelier_exists?
@@ -762,7 +810,62 @@ module Glimmer
762
810
  end
763
811
  MULTI_LINE_STRING
764
812
  end
813
+
814
+ def custom_shape_file(custom_shape_name, namespace)
815
+ namespace_type = class_name(namespace) == class_name(current_dir_name) ? 'class' : 'module'
816
+
817
+ <<~MULTI_LINE_STRING
818
+ #{namespace_type} #{class_name(namespace)}
819
+ class #{class_name(custom_shape_name)}
820
+ include Glimmer::UI::CustomShape
821
+
822
+ ## Add options like the following to configure CustomShape by outside consumers
823
+ #
824
+ # options :option1, option2, option3
825
+ option :background_color, default: :red
826
+ option :size_width, default: 100
827
+ option :size_height, default: 100
828
+ option :location_x, default: 0
829
+ option :location_y, default: 0
830
+
831
+ ## Use before_body block to pre-initialize variables to use in body
832
+ #
833
+ #
834
+ # before_body {
835
+ #
836
+ # }
837
+
838
+ ## Use after_body block to setup observers for shapes in body
839
+ #
840
+ # after_body {
841
+ #
842
+ # }
843
+
844
+ ## Add shape content under custom shape body
845
+ #
846
+ body {
847
+ # Replace example content below with custom shape content
848
+ shape(location_x, location_y) {
849
+ path {
850
+ background background_color
851
+ cubic size_width - size_width*0.66, size_height/2 - size_height*0.33, size_width*0.65 - size_width*0.66, 0 - size_height*0.33, size_width/2 - size_width*0.66, size_height*0.75 - size_height*0.33, size_width - size_width*0.66, size_height - size_height*0.33
852
+ }
853
+ path {
854
+ background background_color
855
+ cubic size_width - size_width*0.66, size_height/2 - size_height*0.33, size_width*1.35 - size_width*0.66, 0 - size_height*0.33, size_width*1.5 - size_width*0.66, size_height*0.75 - size_height*0.33, size_width - size_width*0.66, size_height - size_height*0.33
856
+ }
857
+ }
858
+ }
859
+
860
+ end
861
+ end
862
+ MULTI_LINE_STRING
863
+ end
864
+
765
865
  end
866
+
766
867
  end
868
+
767
869
  end
870
+
768
871
  end
@@ -73,6 +73,7 @@ module Glimmer
73
73
  end
74
74
 
75
75
  def valid?(parent, keyword, *args, &block)
76
+ return true if keyword.to_s == 'shape'
76
77
  gc_instance_methods.include?(method_name(keyword, arg_options(args))) ||
77
78
  constants.include?(keyword.to_s.camelcase(:upper).to_sym)
78
79
  end
@@ -93,8 +94,10 @@ module Glimmer
93
94
 
94
95
  def arg_options(args, extract: false)
95
96
  arg_options_method = extract ? :pop : :last
96
- options = args.send(arg_options_method) if args.last.is_a?(Hash)
97
- options.nil? ? {} : options.symbolize_keys
97
+ options = args.send(arg_options_method).symbolize_keys if args.last.is_a?(Hash)
98
+ # normalize :filled option as an alias to :fill
99
+ # options[:fill] = options.delete(:filled) if options&.keys&.include?(:filled)
100
+ options.nil? ? {} : options
98
101
  end
99
102
 
100
103
  def method_name(keyword, method_arg_options)
@@ -126,7 +129,7 @@ module Glimmer
126
129
  end
127
130
  end
128
131
 
129
- attr_reader :drawable, :parent, :name, :args, :options, :shapes
132
+ attr_reader :drawable, :parent, :name, :args, :options, :shapes, :properties
130
133
  attr_accessor :extent
131
134
 
132
135
  def initialize(parent, keyword, *args, &property_block)
@@ -134,7 +137,7 @@ module Glimmer
134
137
  @drawable = @parent.is_a?(Drawable) ? @parent : @parent.drawable
135
138
  @name = keyword
136
139
  @options = self.class.arg_options(args, extract: true)
137
- @method_name = self.class.method_name(keyword, @options)
140
+ @method_name = self.class.method_name(keyword, @options) unless keyword.to_s == 'shape'
138
141
  @args = args
139
142
  @properties = {}
140
143
  @shapes = [] # nested shapes
@@ -170,7 +173,13 @@ module Glimmer
170
173
 
171
174
  # The bounding box top-left x, y, width, height in absolute positioning
172
175
  def bounds
173
- org.eclipse.swt.graphics.Rectangle.new(absolute_x, absolute_y, calculated_width, calculated_height)
176
+ bounds_dependencies = [absolute_x, absolute_y, calculated_width, calculated_height]
177
+ if bounds_dependencies != @bounds_dependencies
178
+ # avoid repeating calculations
179
+ absolute_x, absolute_y, calculated_width, calculated_height = @bounds_dependencies = bounds_dependencies
180
+ @bounds = org.eclipse.swt.graphics.Rectangle.new(absolute_x, absolute_y, calculated_width, calculated_height)
181
+ end
182
+ @bounds
174
183
  end
175
184
 
176
185
  # The bounding box top-left x and y
@@ -180,7 +189,13 @@ module Glimmer
180
189
 
181
190
  # The bounding box width and height (as a Point object with x being width and y being height)
182
191
  def size
183
- org.eclipse.swt.graphics.Point.new(calculated_width, calculated_height)
192
+ size_dependencies = [calculated_width, calculated_height]
193
+ if size_dependencies != @size_dependencies
194
+ # avoid repeating calculations
195
+ calculated_width, calculated_height = @size_dependencies = size_dependencies
196
+ @size = org.eclipse.swt.graphics.Point.new(calculated_width, calculated_height)
197
+ end
198
+ @size
184
199
  end
185
200
 
186
201
  def extent
@@ -213,12 +228,12 @@ module Glimmer
213
228
  def move_by(x_delta, y_delta)
214
229
  if respond_to?(:x) && respond_to?(:y) && respond_to?(:x=) && respond_to?(:y=)
215
230
  if default_x?
216
- self.default_x_delta += x_delta
231
+ self.x_delta += x_delta
217
232
  else
218
233
  self.x += x_delta
219
234
  end
220
235
  if default_y?
221
- self.default_y_delta += y_delta
236
+ self.y_delta += y_delta
222
237
  else
223
238
  self.y += y_delta
224
239
  end
@@ -227,7 +242,7 @@ module Glimmer
227
242
 
228
243
  def content(&block)
229
244
  Glimmer::SWT::DisplayProxy.instance.auto_exec do
230
- Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::SWT::ShapeExpression.new, &block)
245
+ Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::SWT::ShapeExpression.new, @name, &block)
231
246
  calculated_args_changed!(children: false)
232
247
  end
233
248
  end
@@ -248,7 +263,8 @@ module Glimmer
248
263
  end
249
264
  end
250
265
 
251
- def apply_property_arg_conversions(method_name, property, args)
266
+ def apply_property_arg_conversions(property, args)
267
+ method_name = attribute_setter(property)
252
268
  args = args.dup
253
269
  the_java_method = org.eclipse.swt.graphics.GC.java_class.declared_instance_methods.detect {|m| m.name == method_name}
254
270
  return args if the_java_method.nil?
@@ -306,10 +322,10 @@ module Glimmer
306
322
  end
307
323
  @pattern_args ||= {}
308
324
  pattern_type = method_name.to_s.match(/set(.+)Pattern/)[1]
309
- if args.first.is_a?(Pattern)
325
+ if args.first.is_a?(org.eclipse.swt.graphics.Pattern)
310
326
  new_args = @pattern_args[pattern_type]
311
327
  else
312
- new_args = args.first.is_a?(Display) ? args : ([DisplayProxy.instance.swt_display] + args)
328
+ new_args = args.first.is_a?(org.eclipse.swt.widgets.Display) ? args : ([DisplayProxy.instance.swt_display] + args)
313
329
  @pattern_args[pattern_type] = new_args.dup
314
330
  end
315
331
  args[0] = pattern(*new_args, type: pattern_type)
@@ -348,10 +364,16 @@ module Glimmer
348
364
  end
349
365
 
350
366
  def apply_shape_arg_defaults!
351
- self.x = :default if current_parameter_name?(:x) && x.nil?
352
- self.y = :default if current_parameter_name?(:y) && y.nil?
353
- self.dest_x = :default if current_parameter_name?(:dest_x) && dest_x.nil?
354
- self.dest_y = :default if current_parameter_name?(:dest_y) && dest_y.nil?
367
+ if current_parameter_name?(:dest_x) && dest_x.nil?
368
+ self.dest_x = :default
369
+ elsif parameter_name?(:x) && x.nil?
370
+ self.x = :default
371
+ end
372
+ if current_parameter_name?(:dest_y) && dest_y.nil?
373
+ self.dest_y = :default
374
+ elsif parameter_name?(:y) && y.nil?
375
+ self.y = :default
376
+ end
355
377
  self.width = :default if current_parameter_name?(:width) && width.nil?
356
378
  self.height = :default if current_parameter_name?(:height) && height.nil?
357
379
  if @name.include?('rectangle') && round? && @args.size.between?(4, 5)
@@ -380,7 +402,7 @@ module Glimmer
380
402
  def amend_method_name_options_based_on_properties!
381
403
  @original_method_name = @method_name
382
404
  return if @name == 'point'
383
- if @name != 'text' && @name != 'string' && has_some_background? && !has_some_foreground?
405
+ if (@name != 'text' && @name != 'string' && has_some_background? && !has_some_foreground?) || (@name == 'path' && has_some_background?)
384
406
  @options[:fill] = true
385
407
  elsif !has_some_background? && has_some_foreground?
386
408
  @options[:fill] = false
@@ -396,7 +418,7 @@ module Glimmer
396
418
 
397
419
  # parameter names for arguments to pass to SWT GC.xyz method for rendering shape (e.g. draw_image(image, x, y) yields :image, :x, :y parameter names)
398
420
  def parameter_names
399
- []
421
+ [:x, :y, :width, :height]
400
422
  end
401
423
 
402
424
  # subclasses may override to specify location parameter names if different from x and y (e.g. all polygon points are location parameters)
@@ -421,6 +443,10 @@ module Glimmer
421
443
  parameter_names.index(attribute_name.to_s.to_sym)
422
444
  end
423
445
 
446
+ def get_parameter_attribute(attribute_name)
447
+ @args[parameter_index(ruby_attribute_getter(attribute_name))]
448
+ end
449
+
424
450
  def set_parameter_attribute(attribute_name, *args)
425
451
  @args[parameter_index(ruby_attribute_getter(attribute_name))] = args.size == 1 ? args.first : args
426
452
  end
@@ -436,31 +462,45 @@ module Glimmer
436
462
  args.pop if !options.nil? && !options[:redraw].nil?
437
463
  perform_redraw = @perform_redraw
438
464
  perform_redraw = options[:redraw] if perform_redraw.nil? && !options.nil?
439
- perform_redraw = true if perform_redraw.nil?
465
+ perform_redraw ||= true
466
+ property_change = nil
467
+ ruby_attribute_getter_name = ruby_attribute_getter(attribute_name)
468
+ ruby_attribute_setter_name = ruby_attribute_setter(attribute_name)
440
469
  if parameter_name?(attribute_name)
441
- set_parameter_attribute(attribute_name, *args)
442
- elsif (respond_to?(attribute_name, super: true) and respond_to?(ruby_attribute_setter(attribute_name), super: true))
443
- self.send(ruby_attribute_setter(attribute_name), *args)
470
+ return if ruby_attribute_getter_name == (args.size == 1 ? args.first : args)
471
+ set_parameter_attribute(ruby_attribute_getter_name, *args)
472
+ elsif (respond_to?(attribute_name, super: true) and respond_to?(ruby_attribute_setter_name, super: true))
473
+ return if self.send(ruby_attribute_getter_name) == (args.size == 1 ? args.first : args)
474
+ self.send(ruby_attribute_setter_name, *args)
444
475
  else
445
- @properties[ruby_attribute_getter(attribute_name)] = args
476
+ # TODO consider this optimization of preconverting args (removing conversion from other methods) to reject equal args
477
+ args = apply_property_arg_conversions(ruby_attribute_getter_name, args)
478
+ return if @properties[ruby_attribute_getter_name] == args
479
+ new_property = !@properties.keys.include?(ruby_attribute_getter_name)
480
+ @properties[ruby_attribute_getter_name] = args
481
+ amend_method_name_options_based_on_properties! if @content_added && new_property
482
+ property_change = true
446
483
  end
447
484
  if @content_added && perform_redraw && !drawable.is_disposed
448
- @calculated_paint_args = false
449
- if is_a?(PathSegment)
450
- root_path&.calculated_path_args = @calculated_path_args = false
451
- calculated_args_changed!
452
- root_path&.calculated_args_changed!
453
- end
454
- attribute_name = ruby_attribute_getter(attribute_name)
455
- if location_parameter_names.map(&:to_s).include?(attribute_name)
456
- @calculated_args = nil
457
- parent.calculated_args_changed_for_defaults! if parent.is_a?(Shape)
458
- end
459
- if ['width', 'height'].include?(attribute_name)
460
- calculated_args_changed_for_defaults!
485
+ redrawn = false
486
+ unless property_change
487
+ @calculated_paint_args = false
488
+ if is_a?(PathSegment)
489
+ root_path&.calculated_path_args = @calculated_path_args = false
490
+ calculated_args_changed!
491
+ root_path&.calculated_args_changed!
492
+ end
493
+ if location_parameter_names.map(&:to_s).include?(ruby_attribute_getter_name)
494
+ calculated_args_changed!(children: true)
495
+ redrawn = parent.calculated_args_changed_for_defaults! if parent.is_a?(Shape)
496
+ end
497
+ if ['width', 'height'].include?(ruby_attribute_getter_name)
498
+ redrawn = calculated_args_changed_for_defaults!
499
+ end
461
500
  end
462
501
  # TODO consider redrawing an image proxy's gc in the future
463
- drawable.redraw unless drawable.is_a?(ImageProxy)
502
+ # TODO consider ensuring only a single redraw happens for a hierarchy of nested shapes
503
+ drawable.redraw unless redrawn || drawable.is_a?(ImageProxy)
464
504
  end
465
505
  end
466
506
 
@@ -471,10 +511,25 @@ module Glimmer
471
511
  elsif (respond_to?(attribute_name, super: true) and respond_to?(ruby_attribute_setter(attribute_name), super: true))
472
512
  self.send(attribute_name)
473
513
  else
474
- @properties.symbolize_keys[attribute_name.to_s.to_sym]
514
+ @properties[attribute_name.to_s]
475
515
  end
476
516
  end
477
517
 
518
+ # Sets data just like SWT widgets
519
+ def set_data(key=nil, value)
520
+ @data ||= {}
521
+ @data[key] = value
522
+ end
523
+ alias setData set_data # for compatibility with SWT APIs
524
+
525
+ # Gets data just like SWT widgets
526
+ def get_data(key=nil)
527
+ @data ||= {}
528
+ @data[key]
529
+ end
530
+ alias getData get_data # for compatibility with SWT APIs
531
+ alias data get_data # for compatibility with SWT APIs
532
+
478
533
  def method_missing(method_name, *args, &block)
479
534
  if method_name.to_s.end_with?('=')
480
535
  set_attribute(method_name, *args)
@@ -532,8 +587,77 @@ module Glimmer
532
587
  drawable.redraw if redraw && !drawable.is_a?(ImageProxy)
533
588
  end
534
589
 
590
+ # Indicate if this is a container shape (meaning a shape bag that is just there to contain nested shapes, but doesn't render anything of its own)
591
+ def container?
592
+ @name == 'shape'
593
+ end
594
+
595
+ # Indicate if this is a composite shape (meaning a shape that contains nested shapes like a rectangle with ovals inside it)
596
+ def composite?
597
+ !shapes.empty?
598
+ end
599
+
600
+ # ordered from closest to farthest parent
601
+ def parent_shapes
602
+ if @parent_shapes.nil?
603
+ if parent.is_a?(Drawable)
604
+ @parent_shapes = []
605
+ else
606
+ @parent_shapes = parent.parent_shapes + [parent]
607
+ end
608
+ end
609
+ @parent_shapes
610
+ end
611
+
612
+ # ordered from closest to farthest parent
613
+ def parent_shape_containers
614
+ if @parent_shape_containers.nil?
615
+ if parent.is_a?(Drawable)
616
+ @parent_shape_containers = []
617
+ elsif !parent.container?
618
+ @parent_shape_containers = parent.parent_shape_containers
619
+ else
620
+ @parent_shape_containers = parent.parent_shape_containers + [parent]
621
+ end
622
+ end
623
+ @parent_shape_containers
624
+ end
625
+
626
+ # ordered from closest to farthest parent
627
+ def parent_shape_composites
628
+ if @parent_shape_composites.nil?
629
+ if parent.is_a?(Drawable)
630
+ @parent_shape_composites = []
631
+ elsif !parent.container?
632
+ @parent_shape_composites = parent.parent_shape_composites
633
+ else
634
+ @parent_shape_composites = parent.parent_shape_composites + [parent]
635
+ end
636
+ end
637
+ @parent_shape_composites
638
+ end
639
+
640
+ def convert_properties!
641
+ if @properties != @converted_properties
642
+ @properties.each do |property, args|
643
+ @properties[property] = apply_property_arg_conversions(property, args)
644
+ end
645
+ @converted_properties = @properties.dup
646
+ end
647
+ end
648
+
649
+ def converted_properties
650
+ convert_properties!
651
+ @properties
652
+ end
653
+
654
+ def all_parent_properties
655
+ @all_parent_properties ||= parent_shape_containers.reverse.reduce({}) do |all_properties, parent_shape|
656
+ all_properties.merge(parent_shape.converted_properties)
657
+ end
658
+ end
659
+
535
660
  def paint(paint_event)
536
- # pre-paint children an extra-time first when default width/height need to be calculated for defaults
537
661
  paint_children(paint_event) if default_width? || default_height?
538
662
  paint_self(paint_event)
539
663
  # re-paint children from scratch in the special case of pre-calculating parent width/height to re-center within new parent dimensions
@@ -546,24 +670,26 @@ module Glimmer
546
670
 
547
671
  def paint_self(paint_event)
548
672
  @painting = true
549
- calculate_paint_args!
550
- @original_properties_backup = {}
551
- @properties.each do |property, args|
552
- method_name = attribute_setter(property)
553
- @original_properties_backup[method_name] = paint_event.gc.send(method_name.sub('set', 'get')) rescue nil
554
- paint_event.gc.send(method_name, *args)
555
- if property == 'transform' && args.first.is_a?(TransformProxy)
556
- args.first.swt_transform.dispose
673
+ unless container?
674
+ calculate_paint_args!
675
+ @original_gc_properties = {} # this stores GC properties before making calls to updates TODO avoid using in pixel graphics
676
+ @properties.each do |property, args|
677
+ method_name = attribute_setter(property)
678
+ @original_gc_properties[method_name] = paint_event.gc.send(method_name.sub('set', 'get')) rescue nil
679
+ paint_event.gc.send(method_name, *args)
680
+ if property == 'transform' && args.first.is_a?(TransformProxy)
681
+ args.first.swt_transform.dispose
682
+ end
557
683
  end
684
+ ensure_extent(paint_event)
558
685
  end
559
- ensure_extent(paint_event)
560
- if !@calculated_args || parent_shape_absolute_location_changed?
561
- @calculated_args = calculated_args
562
- end
563
- # 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
564
- paint_event.gc.send(@method_name, *@calculated_args) unless parent.is_a?(Shape) && !parent.calculated_args?
565
- @original_properties_backup.each do |method_name, value|
566
- paint_event.gc.send(method_name, value)
686
+ @calculated_args ||= calculate_args!
687
+ unless container?
688
+ # 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
689
+ paint_event.gc.send(@method_name, *@calculated_args) unless (parent.is_a?(Shape) && !parent.calculated_args?)
690
+ @original_gc_properties.each do |method_name, value|
691
+ paint_event.gc.send(method_name, value)
692
+ end
567
693
  end
568
694
  @painting = false
569
695
  rescue => e
@@ -604,26 +730,27 @@ module Glimmer
604
730
  end
605
731
  end
606
732
 
607
- def parent_shape_absolute_location_changed?
608
- (parent.is_a?(Shape) && (parent.absolute_x != @parent_absolute_x || parent.absolute_y != @parent_absolute_y))
609
- end
610
-
611
733
  def calculated_args_changed!(children: true)
612
734
  # TODO add a children: true option to enable setting to false to avoid recalculating children args
613
735
  @calculated_args = nil
614
736
  shapes.each(&:calculated_args_changed!) if children
615
737
  end
616
738
 
739
+ # Notifies object that calculated args changed for defaults. Returns true if redrawing and false otherwise.
617
740
  def calculated_args_changed_for_defaults!
618
741
  has_default_dimensions = default_width? || default_height?
619
742
  parent_calculated_args_changed_for_defaults = has_default_dimensions
620
- @calculated_args = nil if default_x? || default_y? || has_default_dimensions
743
+ calculated_args_changed!(children: false) if default_x? || default_y? || has_default_dimensions
621
744
  if has_default_dimensions && parent.is_a?(Shape)
622
745
  parent.calculated_args_changed_for_defaults!
623
746
  elsif @content_added && !drawable.is_disposed
624
747
  # 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
625
- drawable.redraw if !@painting && !drawable.is_a?(ImageProxy)
748
+ if !@painting && !drawable.is_a?(ImageProxy)
749
+ drawable.redraw
750
+ return true
751
+ end
626
752
  end
753
+ false
627
754
  end
628
755
 
629
756
  def calculated_args?
@@ -631,68 +758,101 @@ module Glimmer
631
758
  end
632
759
 
633
760
  # args translated to absolute coordinates
634
- def calculated_args
635
- return @args if !default_x? && !default_y? && !default_width? && !default_height? && parent.is_a?(Drawable)
636
- # 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
637
- # TODO change that by setting a bounding box for all shapes with a calculated top-left x, y and
638
- # a setter that does the moving inside them instead so that I could rely on absolute_x and absolute_y
639
- # here to get the job done of calculating absolute args
640
- @perform_redraw = false
641
- original_x = nil
642
- original_y = nil
643
- original_width = nil
644
- original_height = nil
645
- if parent.is_a?(Shape)
646
- @parent_absolute_x = parent.absolute_x
647
- @parent_absolute_y = parent.absolute_y
648
- end
649
- if default_width?
650
- original_width = width
651
- self.width = default_width + default_width_delta
652
- end
653
- if default_height?
654
- original_height = height
655
- self.height = default_height + default_height_delta
656
- end
657
- if default_x?
658
- original_x = x
659
- self.x = default_x + default_x_delta
660
- end
661
- if default_y?
662
- original_y = y
663
- self.y = default_y + default_y_delta
664
- end
665
- if parent.is_a?(Shape)
666
- move_by(@parent_absolute_x, @parent_absolute_y)
667
- result_args = @args.clone
668
- move_by(-1*@parent_absolute_x, -1*@parent_absolute_y)
669
- else
670
- result_args = @args.clone
671
- end
672
- if original_x
673
- self.x = original_x
674
- end
675
- if original_y
676
- self.y = original_y
677
- end
678
- if original_width
679
- self.width = original_width
680
- end
681
- if original_height
682
- self.height = original_height
761
+ def calculate_args!
762
+ # TODO add conditions for parent having default width/height too
763
+ return @args if parent.is_a?(Drawable) && !default_x? && !default_y? && !default_width? && !default_height? && !max_width? && !max_height?
764
+ calculated_args_dependencies = [
765
+ x,
766
+ y,
767
+ parent.is_a?(Shape) && parent.absolute_x,
768
+ parent.is_a?(Shape) && parent.absolute_y,
769
+ default_width? && default_width,
770
+ default_width? && width_delta,
771
+ default_height? && default_height,
772
+ default_height? && height_delta,
773
+ max_width? && max_width,
774
+ max_width? && width_delta,
775
+ max_height? && max_height,
776
+ max_height? && height_delta,
777
+ default_x? && default_x,
778
+ default_x? && x_delta,
779
+ default_y? && default_y,
780
+ default_y? && y_delta,
781
+ ]
782
+ if calculated_args_dependencies != @calculated_args_dependencies
783
+ # avoid recalculating values again
784
+ x, y, parent_absolute_x, parent_absolute_y, default_width, default_width_delta, default_height, default_height_delta, max_width, max_width_delta, max_height, max_height_delta, default_x, default_x_delta, default_y, default_y_delta = @calculated_args_dependencies = calculated_args_dependencies
785
+ # 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
786
+ # TODO change that by setting a bounding box for all shapes with a calculated top-left x, y and
787
+ # a setter that does the moving inside them instead so that I could rely on absolute_x and absolute_y
788
+ # here to get the job done of calculating absolute args
789
+ @perform_redraw = false
790
+ original_x = nil
791
+ original_y = nil
792
+ original_width = nil
793
+ original_height = nil
794
+ if parent.is_a?(Shape)
795
+ @parent_absolute_x = parent_absolute_x
796
+ @parent_absolute_y = parent_absolute_y
797
+ end
798
+ if default_width?
799
+ original_width = width
800
+ self.width = default_width + default_width_delta
801
+ end
802
+ if default_height?
803
+ original_height = height
804
+ self.height = default_height + default_height_delta
805
+ end
806
+ if max_width?
807
+ original_width = width
808
+ self.width = max_width + max_width_delta
809
+ end
810
+ if max_height?
811
+ original_height = height
812
+ self.height = max_height + max_height_delta
813
+ end
814
+ if default_x?
815
+ original_x = x
816
+ self.x = default_x + default_x_delta
817
+ end
818
+ if default_y?
819
+ original_y = y
820
+ self.y = default_y + default_y_delta
821
+ end
822
+ if parent.is_a?(Shape)
823
+ move_by(@parent_absolute_x, @parent_absolute_y)
824
+ @result_calculated_args = @args.clone
825
+ move_by(-1*@parent_absolute_x, -1*@parent_absolute_y)
826
+ else
827
+ @result_calculated_args = @args.clone
828
+ end
829
+ if original_x
830
+ self.x = original_x
831
+ end
832
+ if original_y
833
+ self.y = original_y
834
+ end
835
+ if original_width
836
+ self.width = original_width
837
+ end
838
+ if original_height
839
+ self.height = original_height
840
+ end
841
+ @perform_redraw = true
683
842
  end
684
- @perform_redraw = true
685
- result_args
843
+ @result_calculated_args
686
844
  end
687
845
 
688
846
  def default_x?
689
- current_parameter_name?(:x) and
690
- (x.nil? || x.to_s == 'default' || (x.is_a?(Array) && x.first.to_s == 'default'))
847
+ return false unless current_parameter_name?(:x)
848
+ x = self.x
849
+ x.nil? || x.to_s == 'default' || (x.is_a?(Array) && x.first.to_s == 'default')
691
850
  end
692
851
 
693
852
  def default_y?
694
- current_parameter_name?(:y) and
695
- (y.nil? || y.to_s == 'default' || (y.is_a?(Array) && y.first.to_s == 'default'))
853
+ return false unless current_parameter_name?(:y)
854
+ y = self.y
855
+ y.nil? || y.to_s == 'default' || (y.is_a?(Array) && y.first.to_s == 'default')
696
856
  end
697
857
 
698
858
  def default_width?
@@ -707,120 +867,248 @@ module Glimmer
707
867
  (height.nil? || height == :default || height == 'default' || (height.is_a?(Array) && (height.first.to_s == :default || height.first.to_s == 'default')))
708
868
  end
709
869
 
870
+ def max_width?
871
+ return false unless current_parameter_name?(:width)
872
+ width = self.width
873
+ (width.nil? || width.to_s == 'max' || (width.is_a?(Array) && width.first.to_s == 'max'))
874
+ end
875
+
876
+ def max_height?
877
+ return false unless current_parameter_name?(:height)
878
+ height = self.height
879
+ (height.nil? || height.to_s == 'max' || (height.is_a?(Array) && height.first.to_s == 'max'))
880
+ end
881
+
710
882
  def default_x
711
- result = ((parent.size.x - size.x) / 2)
712
- result += parent.bounds.x - parent.absolute_x if parent.is_a?(Shape) && parent.irregular?
713
- result
883
+ default_x_dependencies = [parent.size.x, size.x, parent.is_a?(Shape) && parent.irregular? && parent.bounds.x, parent.is_a?(Shape) && parent.irregular? && parent.absolute_x]
884
+ if default_x_dependencies != @default_x_dependencies
885
+ @default_x_dependencies = default_x_dependencies
886
+ result = ((parent.size.x - size.x) / 2)
887
+ result += parent.bounds.x - parent.absolute_x if parent.is_a?(Shape) && parent.irregular?
888
+ @default_x = result
889
+ end
890
+ @default_x
714
891
  end
715
892
 
716
893
  def default_y
717
- result = ((parent.size.y - size.y) / 2)
718
- result += parent.bounds.y - parent.absolute_y if parent.is_a?(Shape) && parent.irregular?
719
- result
894
+ default_y_dependencies = [parent.size.y, size.y, parent.is_a?(Shape) && parent.irregular? && parent.bounds.y, parent.is_a?(Shape) && parent.irregular? && parent.absolute_y]
895
+ if default_y_dependencies != @default_y_dependencies
896
+ result = ((parent.size.y - size.y) / 2)
897
+ result += parent.bounds.y - parent.absolute_y if parent.is_a?(Shape) && parent.irregular?
898
+ @default_y = result
899
+ end
900
+ @default_y
901
+ end
902
+
903
+ # right-most x coordinate in this shape (adding up its width and location)
904
+ def x_end
905
+ x_end_dependencies = [calculated_width, default_x?, !default_x? && x]
906
+ if x_end_dependencies != @x_end_dependencies
907
+ # avoid recalculation of dependencies
908
+ calculated_width, is_default_x, x = @x_end_dependencies = x_end_dependencies
909
+ shape_width = calculated_width.to_f
910
+ shape_x = is_default_x ? 0 : x.to_f
911
+ @x_end = shape_x + shape_width
912
+ end
913
+ @x_end
914
+ end
915
+
916
+ # right-most y coordinate in this shape (adding up its height and location)
917
+ def y_end
918
+ y_end_dependencies = [calculated_height, default_y?, !default_y? && y]
919
+ if y_end_dependencies != @y_end_dependencies
920
+ # avoid recalculation of dependencies
921
+ calculated_height, is_default_y, y = @y_end_dependencies = y_end_dependencies
922
+ shape_height = calculated_height.to_f
923
+ shape_y = is_default_y ? 0 : y.to_f
924
+ @y_end = shape_y + shape_height
925
+ end
926
+ @y_end
720
927
  end
721
928
 
722
929
  def default_width
723
- # TODO consider caching
724
- x_ends = shapes.map do |shape|
725
- shape_width = shape.calculated_width.to_f
726
- shape_x = shape.default_x? ? 0 : shape.x.to_f
727
- shape_x + shape_width
930
+ default_width_dependencies = [shapes.empty? && max_width, shapes.size == 1 && shapes.first.max_width? && parent.size.x, shapes.size >= 1 && !shapes.first.max_width? && shapes.map {|s| s.max_width? ? 0 : s.x_end}]
931
+ if default_width_dependencies != @default_width_dependencies
932
+ # Do not repeat calculations
933
+ max_width, parent_size_x, x_ends = @default_width_dependencies = default_width_dependencies
934
+ @default_width = if shapes.empty?
935
+ max_width
936
+ elsif shapes.size == 1 && shapes.first.max_width?
937
+ parent_size_x
938
+ else
939
+ x_ends.max.to_f
940
+ end
728
941
  end
729
- x_ends.max.to_f
942
+ @default_width
730
943
  end
731
944
 
732
945
  def default_height
733
- # TODO consider caching
734
- y_ends = shapes.map do |shape|
735
- shape_height = shape.calculated_height.to_f
736
- shape_y = shape.default_y? ? 0 : shape.y.to_f
737
- shape_y + shape_height
946
+ default_height_dependencies = [shapes.empty? && max_height, shapes.size == 1 && shapes.first.max_height? && parent.size.y, shapes.size >= 1 && !shapes.first.max_height? && shapes.map {|s| s.max_height? ? 0 : s.y_end}]
947
+ if default_height_dependencies != @default_height_dependencies
948
+ # Do not repeat calculations
949
+ max_height, parent_size_y, y_ends = @default_height_dependencies = default_height_dependencies
950
+ @default_height = if shapes.empty?
951
+ max_height
952
+ elsif shapes.size == 1 && shapes.first.max_height?
953
+ parent_size_y
954
+ else
955
+ y_ends.max.to_f
956
+ end
957
+ end
958
+ @default_height
959
+ end
960
+
961
+ def max_width
962
+ max_width_dependencies = [parent.is_a?(Drawable) && parent.size.x, !parent.is_a?(Drawable) && parent.calculated_width]
963
+ if max_width_dependencies != @max_width_dependencies
964
+ # do not repeat calculations
965
+ parent_size_x, parent_calculated_width = @max_width_dependencies = max_width_dependencies
966
+ @max_width = parent.is_a?(Drawable) ? parent_size_x : parent_calculated_width
967
+ end
968
+ @max_width
969
+ end
970
+
971
+ def max_height
972
+ max_height_dependencies = [parent.is_a?(Drawable) && parent.size.y, !parent.is_a?(Drawable) && parent.calculated_height]
973
+ if max_height_dependencies != @max_height_dependencies
974
+ # do not repeat calculations
975
+ parent_size_y, parent_calculated_height = @max_height_dependencies = max_height_dependencies
976
+ @max_height = parent.is_a?(Drawable) ? parent_size_y : parent_calculated_height
738
977
  end
739
- y_ends.max.to_f
978
+ @max_height
740
979
  end
741
980
 
742
981
  def calculated_width
743
- default_width? ? (default_width + default_width_delta) : width
982
+ calculated_width_dependencies = [width, default_width? && (default_width + width_delta), max_width? && (max_width + width_delta)]
983
+ if calculated_width_dependencies != @calculated_width_dependencies
984
+ @calculated_width_dependencies = calculated_width_dependencies
985
+ result_width = width
986
+ result_width = (default_width + width_delta) if default_width?
987
+ result_width = (max_width + width_delta) if max_width?
988
+ @calculated_width = result_width
989
+ end
990
+ @calculated_width
744
991
  end
745
992
 
746
993
  def calculated_height
747
- default_height? ? (default_height + default_height_delta) : height
994
+ calculated_height_dependencies = [height, default_height? && (default_height + height_delta), max_height? && (max_height + height_delta)]
995
+ if calculated_height_dependencies != @calculated_height_dependencies
996
+ @calculated_height_dependencies = calculated_height_dependencies
997
+ result_height = height
998
+ result_height = (default_height + height_delta) if default_height?
999
+ result_height = (max_height + height_delta) if max_height?
1000
+ @calculated_height = result_height
1001
+ end
1002
+ @calculated_height
748
1003
  end
749
1004
 
750
- def default_x_delta
751
- return 0 unless default_x? && x.is_a?(Array)
1005
+ def x_delta
1006
+ return 0 unless x.is_a?(Array) && default_x?
752
1007
  x[1].to_f
753
1008
  end
754
1009
 
755
- def default_y_delta
756
- return 0 unless default_y? && y.is_a?(Array)
1010
+ def y_delta
1011
+ return 0 unless y.is_a?(Array) && default_y?
757
1012
  y[1].to_f
758
1013
  end
759
1014
 
760
- def default_width_delta
761
- return 0 unless default_width? && width.is_a?(Array)
1015
+ def width_delta
1016
+ return 0 unless width.is_a?(Array) && (default_width? || max_width?)
762
1017
  width[1].to_f
763
1018
  end
764
1019
 
765
- def default_height_delta
766
- return 0 unless default_height? && height.is_a?(Array)
1020
+ def height_delta
1021
+ return 0 unless height.is_a?(Array) && (default_height? || max_height?)
767
1022
  height[1].to_f
768
1023
  end
769
1024
 
770
- def default_x_delta=(delta)
1025
+ def x_delta=(delta)
771
1026
  return unless default_x?
772
- self.x = [:default, delta]
1027
+ symbol = x.is_a?(Array) ? x.first : x
1028
+ self.x = [symbol, delta]
773
1029
  end
774
1030
 
775
- def default_y_delta=(delta)
1031
+ def y_delta=(delta)
776
1032
  return unless default_y?
777
- self.y = [:default, delta]
1033
+ symbol = y.is_a?(Array) ? y.first : y
1034
+ self.y = [symbol, delta]
778
1035
  end
779
1036
 
780
- def default_width_delta=(delta)
1037
+ def width_delta=(delta)
781
1038
  return unless default_width?
782
- self.width = [:default, delta]
1039
+ symbol = width.is_a?(Array) ? width.first : width
1040
+ self.width = [symbol, delta]
783
1041
  end
784
1042
 
785
- def default_height_delta=(delta)
1043
+ def height_delta=(delta)
786
1044
  return unless default_height?
787
- self.height = [:default, delta]
1045
+ symbol = height.is_a?(Array) ? height.first : height
1046
+ self.height = [symbol, delta]
788
1047
  end
789
1048
 
790
1049
  def calculated_x
791
- result = default_x? ? default_x : self.x
792
- result += default_x_delta
793
- result
1050
+ calculated_x_dependencies = [default_x? && default_x, !default_x? && self.x, self.x_delta]
1051
+ if calculated_x_dependencies != @calculated_x_dependencies
1052
+ default_x, x, x_delta = @calculated_x_dependencies = calculated_x_dependencies
1053
+ result = default_x? ? default_x : x
1054
+ result += x_delta
1055
+ @calculated_x = result
1056
+ end
1057
+ @calculated_x
794
1058
  end
795
1059
 
796
1060
  def calculated_y
797
- result = default_y? ? default_y : self.y
798
- result += default_y_delta
799
- result
1061
+ calculated_y_dependencies = [default_y? && default_y, !default_y? && self.y, self.y_delta]
1062
+ if calculated_y_dependencies != @calculated_y_dependencies
1063
+ default_y, y, y_delta = @calculated_y_dependencies = calculated_y_dependencies
1064
+ result = default_y? ? default_y : y
1065
+ result += y_delta
1066
+ @calculated_y = result
1067
+ end
1068
+ @calculated_y
800
1069
  end
801
1070
 
802
1071
  def absolute_x
803
- x = calculated_x
804
- if parent.is_a?(Shape)
805
- parent.absolute_x + x
806
- else
807
- x
1072
+ absolute_x_dependencies = [calculated_x, parent.is_a?(Shape) && parent.absolute_x]
1073
+ if absolute_x_dependencies != @absolute_x_dependencies
1074
+ # do not repeat calculations
1075
+ calculated_x, parent_absolute_x = @absolute_x_dependencies = absolute_x_dependencies
1076
+ x = calculated_x
1077
+ @absolute_x = if parent.is_a?(Shape)
1078
+ parent_absolute_x + x
1079
+ else
1080
+ x
1081
+ end
808
1082
  end
1083
+ @absolute_x
809
1084
  end
810
1085
 
811
1086
  def absolute_y
812
- y = calculated_y
813
- if parent.is_a?(Shape)
814
- parent.absolute_y + y
815
- else
816
- y
1087
+ absolute_y_dependencies = [calculated_y, parent.is_a?(Shape) && parent.absolute_y]
1088
+ if absolute_y_dependencies != @absolute_y_dependencies
1089
+ calculated_y, parent_absolute_y = @absolute_y_dependencies = absolute_y_dependencies
1090
+ y = calculated_y
1091
+ @absolute_y = if parent.is_a?(Shape)
1092
+ parent_absolute_y + y
1093
+ else
1094
+ y
1095
+ end
817
1096
  end
818
- end
819
-
820
- # Overriding inspect to avoid printing very long shape hierarchies
821
- def inspect
822
- "#<#{self.class.name}:0x#{self.hash.to_s(16)} args=#{@args.inspect}, properties=#{@properties.inspect}}>"
1097
+ @absolute_y
1098
+ end
1099
+
1100
+ # Overriding inspect to avoid printing very long nested shape hierarchies (recurses onces only)
1101
+ def inspect(recursive: 1, calculated: false, args: true, properties: true, calculated_args: false)
1102
+ recurse = recursive == true || recursive.is_a?(Integer) && recursive.to_i > 0
1103
+ recursive = [recursive -= 1, 0].max if recursive.is_a?(Integer)
1104
+ args_string = " args=#{@args.inspect}" if args
1105
+ properties_string = " properties=#{@properties.inspect}}" if properties
1106
+ calculated_args_string = " calculated_args=#{@calculated_args.inspect}" if calculated_args
1107
+ calculated_string = " absolute_x=#{absolute_x} absolute_y=#{absolute_y} calculated_width=#{calculated_width} calculated_height=#{calculated_height}" if calculated
1108
+ recursive_string = " shapes=#{@shapes.map {|s| s.inspect(recursive: recursive, calculated: calculated, args: args, properties: properties)}}" if recurse
1109
+ "#<#{self.class.name}:0x#{self.hash.to_s(16)}#{args_string}#{properties_string}#{calculated_args_string}#{calculated_string}#{recursive_string}>"
823
1110
  rescue => e
1111
+ Glimmer::Config.logger.error { e.full_message }
824
1112
  "#<#{self.class.name}:0x#{self.hash.to_s(16)}"
825
1113
  end
826
1114
 
@@ -841,19 +1129,14 @@ module Glimmer
841
1129
  end
842
1130
  end
843
1131
  else
1132
+ @properties = all_parent_properties.merge(@properties)
844
1133
  @properties['background'] = [@drawable.background] if fill? && !has_some_background?
845
1134
  @properties['foreground'] = [@drawable.foreground] if @drawable.respond_to?(:foreground) && draw? && !has_some_foreground?
846
1135
  # TODO regarding alpha, make sure to reset it to parent stored alpha once we allow setting shape properties on parents directly without shapes
847
- @properties['alpha'] ||= [255]
848
- @properties['font'] = [@drawable.font] if @drawable.respond_to?(:font) && draw? && !@properties.keys.map(&:to_s).include?('font')
1136
+ @properties['font'] = [@drawable.font] if @drawable.respond_to?(:font) && @name == 'text' && draw? && !@properties.keys.map(&:to_s).include?('font')
849
1137
  # TODO regarding transform, make sure to reset it to parent stored transform once we allow setting shape properties on parents directly without shapes
850
1138
  # Also do that with all future-added properties
851
- @properties['transform'] = [nil] if @drawable.respond_to?(:transform) && !@properties.keys.map(&:to_s).include?('transform')
852
- @properties.each do |property, args|
853
- method_name = attribute_setter(property)
854
- converted_args = apply_property_arg_conversions(method_name, property, args)
855
- @properties[property] = converted_args
856
- end
1139
+ convert_properties!
857
1140
  apply_shape_arg_conversions!
858
1141
  apply_shape_arg_defaults!
859
1142
  tolerate_shape_extra_args!