glimmer-dsl-swt 4.18.7.2 → 4.18.7.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -53,7 +53,7 @@ module Glimmer
53
53
  return
54
54
  end
55
55
  # need the rescue false for a scenario with tree items not being equal to model objects raising an exception
56
- unless ((value == evaluate_property) rescue false) # need the rescue false for a scenario with tree items not being equal to model objects raising an exception
56
+ if @async_exec || !((value == evaluate_property) rescue false) # need the rescue false for a scenario with tree items not being equal to model objects raising an exception
57
57
  @widget.set_attribute(@property, value)
58
58
  end
59
59
  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
@@ -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
@@ -64,14 +64,30 @@ module Glimmer
64
64
  respond_to?(method_name)
65
65
  end
66
66
 
67
+ def can_add_observer?(attribute_name)
68
+ @styled_text_proxy&.can_add_observer?(attribute_name) || super
69
+ end
70
+
71
+ def add_observer(observer, attribute_name)
72
+ if @styled_text_proxy&.can_add_observer?(attribute_name)
73
+ @styled_text_proxy.add_observer(observer, attribute_name)
74
+ else
75
+ super
76
+ end
77
+ end
78
+
67
79
  def can_handle_observation_request?(observation_request)
68
- @styled_text_proxy.can_handle_observation_request?(observation_request)
80
+ @styled_text_proxy&.can_handle_observation_request?(observation_request) || super
69
81
  rescue
70
82
  super
71
83
  end
72
-
84
+
73
85
  def handle_observation_request(observation_request, &block)
74
- @styled_text_proxy.handle_observation_request(observation_request, &block)
86
+ if @styled_text_proxy&.can_handle_observation_request?(observation_request)
87
+ @styled_text_proxy.handle_observation_request(observation_request, &block)
88
+ else
89
+ super
90
+ end
75
91
  rescue
76
92
  super
77
93
  end
@@ -94,8 +94,10 @@ module Glimmer
94
94
 
95
95
  def arg_options(args, extract: false)
96
96
  arg_options_method = extract ? :pop : :last
97
- options = args.send(arg_options_method) if args.last.is_a?(Hash)
98
- 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
99
101
  end
100
102
 
101
103
  def method_name(keyword, method_arg_options)
@@ -171,7 +173,13 @@ module Glimmer
171
173
 
172
174
  # The bounding box top-left x, y, width, height in absolute positioning
173
175
  def bounds
174
- 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
175
183
  end
176
184
 
177
185
  # The bounding box top-left x and y
@@ -181,7 +189,13 @@ module Glimmer
181
189
 
182
190
  # The bounding box width and height (as a Point object with x being width and y being height)
183
191
  def size
184
- 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
185
199
  end
186
200
 
187
201
  def extent
@@ -308,10 +322,10 @@ module Glimmer
308
322
  end
309
323
  @pattern_args ||= {}
310
324
  pattern_type = method_name.to_s.match(/set(.+)Pattern/)[1]
311
- if args.first.is_a?(Pattern)
325
+ if args.first.is_a?(org.eclipse.swt.graphics.Pattern)
312
326
  new_args = @pattern_args[pattern_type]
313
327
  else
314
- 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)
315
329
  @pattern_args[pattern_type] = new_args.dup
316
330
  end
317
331
  args[0] = pattern(*new_args, type: pattern_type)
@@ -388,7 +402,7 @@ module Glimmer
388
402
  def amend_method_name_options_based_on_properties!
389
403
  @original_method_name = @method_name
390
404
  return if @name == 'point'
391
- 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?)
392
406
  @options[:fill] = true
393
407
  elsif !has_some_background? && has_some_foreground?
394
408
  @options[:fill] = false
@@ -429,6 +443,10 @@ module Glimmer
429
443
  parameter_names.index(attribute_name.to_s.to_sym)
430
444
  end
431
445
 
446
+ def get_parameter_attribute(attribute_name)
447
+ @args[parameter_index(ruby_attribute_getter(attribute_name))]
448
+ end
449
+
432
450
  def set_parameter_attribute(attribute_name, *args)
433
451
  @args[parameter_index(ruby_attribute_getter(attribute_name))] = args.size == 1 ? args.first : args
434
452
  end
@@ -444,31 +462,45 @@ module Glimmer
444
462
  args.pop if !options.nil? && !options[:redraw].nil?
445
463
  perform_redraw = @perform_redraw
446
464
  perform_redraw = options[:redraw] if perform_redraw.nil? && !options.nil?
447
- 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)
448
469
  if parameter_name?(attribute_name)
449
- set_parameter_attribute(attribute_name, *args)
450
- elsif (respond_to?(attribute_name, super: true) and respond_to?(ruby_attribute_setter(attribute_name), super: true))
451
- 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)
452
475
  else
453
- @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
454
483
  end
455
484
  if @content_added && perform_redraw && !drawable.is_disposed
456
- @calculated_paint_args = false
457
- if is_a?(PathSegment)
458
- root_path&.calculated_path_args = @calculated_path_args = false
459
- calculated_args_changed!
460
- root_path&.calculated_args_changed!
461
- end
462
- attribute_name = ruby_attribute_getter(attribute_name)
463
- if location_parameter_names.map(&:to_s).include?(attribute_name)
464
- @calculated_args = nil
465
- parent.calculated_args_changed_for_defaults! if parent.is_a?(Shape)
466
- end
467
- if ['width', 'height'].include?(attribute_name)
468
- 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
469
500
  end
470
501
  # TODO consider redrawing an image proxy's gc in the future
471
- 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)
472
504
  end
473
505
  end
474
506
 
@@ -479,7 +511,7 @@ module Glimmer
479
511
  elsif (respond_to?(attribute_name, super: true) and respond_to?(ruby_attribute_setter(attribute_name), super: true))
480
512
  self.send(attribute_name)
481
513
  else
482
- @properties.symbolize_keys[attribute_name.to_s.to_sym]
514
+ @properties[attribute_name.to_s]
483
515
  end
484
516
  end
485
517
 
@@ -555,35 +587,73 @@ module Glimmer
555
587
  drawable.redraw if redraw && !drawable.is_a?(ImageProxy)
556
588
  end
557
589
 
558
- # Indicate if this is a shape composite (meaning a shape bag that just contains nested shapes, but doesn't render anything of its own)
559
- def shape_composite?
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?
560
592
  @name == 'shape'
561
593
  end
562
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
+
563
600
  # ordered from closest to farthest parent
564
601
  def parent_shapes
565
- current_parent = parent
566
- the_parent_shapes = []
567
- until current_parent.is_a?(Drawable)
568
- the_parent_shapes << current_parent
569
- current_parent = current_parent.parent
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
570
622
  end
571
- the_parent_shapes
623
+ @parent_shape_containers
572
624
  end
573
625
 
574
626
  # ordered from closest to farthest parent
575
627
  def parent_shape_composites
576
- parent_shapes.select(&:shape_composite?)
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
577
638
  end
578
639
 
579
- def all_parent_properties
580
- # TODO consider providing a converted property version of this ready for consumption
581
- @all_parent_properties ||= parent_shape_composites.reverse.reduce({}) do |all_properties, parent_shape|
582
- parent_properties = parent_shape.properties
583
- parent_properties.each do |property, args|
584
- parent_properties[property] = apply_property_arg_conversions(property, args)
640
+ def convert_properties!
641
+ if @properties != @converted_properties
642
+ @properties.each do |property, args|
643
+ @properties[property] = apply_property_arg_conversions(property, args)
585
644
  end
586
- all_properties.merge(parent_properties)
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)
587
657
  end
588
658
  end
589
659
 
@@ -600,11 +670,10 @@ module Glimmer
600
670
 
601
671
  def paint_self(paint_event)
602
672
  @painting = true
603
- unless shape_composite?
673
+ unless container?
604
674
  calculate_paint_args!
605
675
  @original_gc_properties = {} # this stores GC properties before making calls to updates TODO avoid using in pixel graphics
606
- @original_properties = @properties # this stores original shape attributes like background/foreground/font
607
- @properties.merge(all_parent_properties).each do |property, args|
676
+ @properties.each do |property, args|
608
677
  method_name = attribute_setter(property)
609
678
  @original_gc_properties[method_name] = paint_event.gc.send(method_name.sub('set', 'get')) rescue nil
610
679
  paint_event.gc.send(method_name, *args)
@@ -614,10 +683,8 @@ module Glimmer
614
683
  end
615
684
  ensure_extent(paint_event)
616
685
  end
617
- if !@calculated_args || parent_shape_absolute_location_changed?
618
- @calculated_args = calculated_args
619
- end
620
- unless shape_composite?
686
+ @calculated_args ||= calculate_args!
687
+ unless container?
621
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
622
689
  paint_event.gc.send(@method_name, *@calculated_args) unless (parent.is_a?(Shape) && !parent.calculated_args?)
623
690
  @original_gc_properties.each do |method_name, value|
@@ -663,26 +730,27 @@ module Glimmer
663
730
  end
664
731
  end
665
732
 
666
- def parent_shape_absolute_location_changed?
667
- (parent.is_a?(Shape) && (parent.absolute_x != @parent_absolute_x || parent.absolute_y != @parent_absolute_y))
668
- end
669
-
670
733
  def calculated_args_changed!(children: true)
671
734
  # TODO add a children: true option to enable setting to false to avoid recalculating children args
672
735
  @calculated_args = nil
673
736
  shapes.each(&:calculated_args_changed!) if children
674
737
  end
675
738
 
739
+ # Notifies object that calculated args changed for defaults. Returns true if redrawing and false otherwise.
676
740
  def calculated_args_changed_for_defaults!
677
741
  has_default_dimensions = default_width? || default_height?
678
742
  parent_calculated_args_changed_for_defaults = has_default_dimensions
679
- @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
680
744
  if has_default_dimensions && parent.is_a?(Shape)
681
745
  parent.calculated_args_changed_for_defaults!
682
746
  elsif @content_added && !drawable.is_disposed
683
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
684
- drawable.redraw if !@painting && !drawable.is_a?(ImageProxy)
748
+ if !@painting && !drawable.is_a?(ImageProxy)
749
+ drawable.redraw
750
+ return true
751
+ end
685
752
  end
753
+ false
686
754
  end
687
755
 
688
756
  def calculated_args?
@@ -690,76 +758,101 @@ module Glimmer
690
758
  end
691
759
 
692
760
  # args translated to absolute coordinates
693
- def calculated_args
694
- return @args if !default_x? && !default_y? && !default_width? && !default_height? && !max_width? && !max_height? && parent.is_a?(Drawable)
695
- # 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
696
- # TODO change that by setting a bounding box for all shapes with a calculated top-left x, y and
697
- # a setter that does the moving inside them instead so that I could rely on absolute_x and absolute_y
698
- # here to get the job done of calculating absolute args
699
- @perform_redraw = false
700
- original_x = nil
701
- original_y = nil
702
- original_width = nil
703
- original_height = nil
704
- if parent.is_a?(Shape)
705
- @parent_absolute_x = parent.absolute_x
706
- @parent_absolute_y = parent.absolute_y
707
- end
708
- if default_width?
709
- original_width = width
710
- self.width = default_width + width_delta
711
- end
712
- if default_height?
713
- original_height = height
714
- self.height = default_height + height_delta
715
- end
716
- if max_width?
717
- original_width = width
718
- self.width = max_width + width_delta
719
- end
720
- if max_height?
721
- original_height = height
722
- self.height = max_height + height_delta
723
- end
724
- if default_x?
725
- original_x = x
726
- self.x = default_x + self.x_delta
727
- end
728
- if default_y?
729
- original_y = y
730
- self.y = default_y + self.y_delta
731
- end
732
- if parent.is_a?(Shape)
733
- move_by(@parent_absolute_x, @parent_absolute_y)
734
- result_args = @args.clone
735
- move_by(-1*@parent_absolute_x, -1*@parent_absolute_y)
736
- else
737
- result_args = @args.clone
738
- end
739
- if original_x
740
- self.x = original_x
741
- end
742
- if original_y
743
- self.y = original_y
744
- end
745
- if original_width
746
- self.width = original_width
747
- end
748
- if original_height
749
- 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
750
842
  end
751
- @perform_redraw = true
752
- result_args
843
+ @result_calculated_args
753
844
  end
754
845
 
755
846
  def default_x?
756
- current_parameter_name?(:x) and
757
- (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')
758
850
  end
759
851
 
760
852
  def default_y?
761
- current_parameter_name?(:y) and
762
- (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')
763
856
  end
764
857
 
765
858
  def default_width?
@@ -775,108 +868,157 @@ module Glimmer
775
868
  end
776
869
 
777
870
  def max_width?
778
- current_parameter_name?(:width) and
779
- (width.nil? || width.to_s == 'max' || (width.is_a?(Array) && width.first.to_s == 'max'))
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'))
780
874
  end
781
875
 
782
876
  def max_height?
783
- current_parameter_name?(:height) and
784
- (height.nil? || height.to_s == 'max' || (height.is_a?(Array) && height.first.to_s == 'max'))
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'))
785
880
  end
786
881
 
787
882
  def default_x
788
- result = ((parent.size.x - size.x) / 2)
789
- result += parent.bounds.x - parent.absolute_x if parent.is_a?(Shape) && parent.irregular?
790
- 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
791
891
  end
792
892
 
793
893
  def default_y
794
- result = ((parent.size.y - size.y) / 2)
795
- result += parent.bounds.y - parent.absolute_y if parent.is_a?(Shape) && parent.irregular?
796
- 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
797
927
  end
798
928
 
799
929
  def default_width
800
- # TODO consider caching
801
- x_ends = shapes.map do |shape|
802
- if shape.max_width?
803
- 0
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
804
938
  else
805
- shape_width = shape.calculated_width.to_f
806
- shape_x = shape.default_x? ? 0 : shape.x.to_f
807
- shape_x + shape_width
939
+ x_ends.max.to_f
808
940
  end
809
941
  end
810
- if shapes.empty?
811
- max_width
812
- elsif shapes.size == 1 && shapes.first.max_width?
813
- self.parent.size.x
814
- else
815
- x_ends.max.to_f
816
- end
942
+ @default_width
817
943
  end
818
944
 
819
945
  def default_height
820
- # TODO consider caching
821
- y_ends = shapes.map do |shape|
822
- if shape.max_height?
823
- 0
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
824
954
  else
825
- shape_height = shape.calculated_height.to_f
826
- shape_y = shape.default_y? ? 0 : shape.y.to_f
827
- shape_y + shape_height
955
+ y_ends.max.to_f
828
956
  end
829
957
  end
830
- if shapes.empty?
831
- max_height
832
- elsif shapes.size == 1 && shapes.first.max_height?
833
- self.parent.size.y
834
- else
835
- y_ends.max.to_f
836
- end
958
+ @default_height
837
959
  end
838
960
 
839
961
  def max_width
840
- # consider caching
841
- parent.is_a?(Drawable) ? parent.size.x : parent.calculated_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
842
969
  end
843
970
 
844
971
  def max_height
845
- # consider caching
846
- parent.is_a?(Drawable) ? parent.size.y : parent.calculated_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
977
+ end
978
+ @max_height
847
979
  end
848
980
 
849
981
  def calculated_width
850
- result_width = width
851
- result_width = (default_width + width_delta) if default_width?
852
- result_width = (max_width + width_delta) if max_width?
853
- result_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
854
991
  end
855
992
 
856
993
  def calculated_height
857
- result_height = height
858
- result_height = (default_height + height_delta) if default_height?
859
- result_height = (max_height + height_delta) if max_height?
860
- result_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
861
1003
  end
862
1004
 
863
1005
  def x_delta
864
- return 0 unless default_x? && x.is_a?(Array)
1006
+ return 0 unless x.is_a?(Array) && default_x?
865
1007
  x[1].to_f
866
1008
  end
867
1009
 
868
1010
  def y_delta
869
- return 0 unless default_y? && y.is_a?(Array)
1011
+ return 0 unless y.is_a?(Array) && default_y?
870
1012
  y[1].to_f
871
1013
  end
872
1014
 
873
1015
  def width_delta
874
- return 0 unless (default_width? || max_width?) && width.is_a?(Array)
1016
+ return 0 unless width.is_a?(Array) && (default_width? || max_width?)
875
1017
  width[1].to_f
876
1018
  end
877
1019
 
878
1020
  def height_delta
879
- return 0 unless (default_height? || max_height?) && height.is_a?(Array)
1021
+ return 0 unless height.is_a?(Array) && (default_height? || max_height?)
880
1022
  height[1].to_f
881
1023
  end
882
1024
 
@@ -905,39 +1047,68 @@ module Glimmer
905
1047
  end
906
1048
 
907
1049
  def calculated_x
908
- result = default_x? ? default_x : self.x
909
- result += self.x_delta
910
- 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
911
1058
  end
912
1059
 
913
1060
  def calculated_y
914
- result = default_y? ? default_y : self.y
915
- result += self.y_delta
916
- 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
917
1069
  end
918
1070
 
919
1071
  def absolute_x
920
- x = calculated_x
921
- if parent.is_a?(Shape)
922
- parent.absolute_x + x
923
- else
924
- 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
925
1082
  end
1083
+ @absolute_x
926
1084
  end
927
1085
 
928
1086
  def absolute_y
929
- y = calculated_y
930
- if parent.is_a?(Shape)
931
- parent.absolute_y + y
932
- else
933
- 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
934
1096
  end
1097
+ @absolute_y
935
1098
  end
936
1099
 
937
- # Overriding inspect to avoid printing very long shape hierarchies
938
- def inspect
939
- "#<#{self.class.name}:0x#{self.hash.to_s(16)} args=#{@args.inspect}, properties=#{@properties.inspect}}>"
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}>"
940
1110
  rescue => e
1111
+ Glimmer::Config.logger.error { e.full_message }
941
1112
  "#<#{self.class.name}:0x#{self.hash.to_s(16)}"
942
1113
  end
943
1114
 
@@ -958,17 +1129,14 @@ module Glimmer
958
1129
  end
959
1130
  end
960
1131
  else
1132
+ @properties = all_parent_properties.merge(@properties)
961
1133
  @properties['background'] = [@drawable.background] if fill? && !has_some_background?
962
1134
  @properties['foreground'] = [@drawable.foreground] if @drawable.respond_to?(:foreground) && draw? && !has_some_foreground?
963
1135
  # TODO regarding alpha, make sure to reset it to parent stored alpha once we allow setting shape properties on parents directly without shapes
964
- @properties['alpha'] ||= [255]
965
- @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')
966
1137
  # TODO regarding transform, make sure to reset it to parent stored transform once we allow setting shape properties on parents directly without shapes
967
1138
  # Also do that with all future-added properties
968
- @properties['transform'] = [nil] if @drawable.respond_to?(:transform) && !@properties.keys.map(&:to_s).include?('transform')
969
- @properties.each do |property, args|
970
- @properties[property] = apply_property_arg_conversions(property, args)
971
- end
1139
+ convert_properties!
972
1140
  apply_shape_arg_conversions!
973
1141
  apply_shape_arg_defaults!
974
1142
  tolerate_shape_extra_args!