glimmer-dsl-swt 4.18.6.2 → 4.18.7.3

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 +34 -0
  3. data/README.md +4 -4
  4. data/VERSION +1 -1
  5. data/docs/reference/GLIMMER_GUI_DSL_SYNTAX.md +64 -6
  6. data/docs/reference/GLIMMER_SAMPLES.md +71 -0
  7. data/glimmer-dsl-swt.gemspec +16 -6
  8. data/lib/glimmer/dsl/swt/animation_expression.rb +1 -1
  9. data/lib/glimmer/dsl/swt/custom_shape_expression.rb +61 -0
  10. data/lib/glimmer/dsl/swt/custom_widget_expression.rb +1 -1
  11. data/lib/glimmer/dsl/swt/dsl.rb +1 -0
  12. data/lib/glimmer/dsl/swt/expand_item_expression.rb +4 -4
  13. data/lib/glimmer/dsl/swt/image_expression.rb +1 -1
  14. data/lib/glimmer/dsl/swt/multiply_expression.rb +1 -1
  15. data/lib/glimmer/dsl/swt/shape_expression.rb +1 -1
  16. data/lib/glimmer/dsl/swt/transform_expression.rb +1 -1
  17. data/lib/glimmer/dsl/swt/widget_expression.rb +2 -1
  18. data/lib/glimmer/swt/custom/shape.rb +473 -180
  19. data/lib/glimmer/swt/custom/shape/image.rb +7 -9
  20. data/lib/glimmer/swt/custom/shape/path.rb +38 -29
  21. data/lib/glimmer/swt/custom/shape/path_segment.rb +21 -19
  22. data/lib/glimmer/swt/custom/shape/polygon.rb +24 -8
  23. data/lib/glimmer/swt/custom/shape/polyline.rb +5 -0
  24. data/lib/glimmer/swt/custom/shape/rectangle.rb +10 -19
  25. data/lib/glimmer/swt/display_proxy.rb +1 -1
  26. data/lib/glimmer/swt/message_box_proxy.rb +1 -1
  27. data/lib/glimmer/swt/shell_proxy.rb +1 -1
  28. data/lib/glimmer/swt/tab_folder_proxy.rb +52 -0
  29. data/lib/glimmer/swt/transform_proxy.rb +1 -1
  30. data/lib/glimmer/swt/widget_proxy.rb +1 -1
  31. data/lib/glimmer/ui/custom_shape.rb +281 -0
  32. data/samples/elaborate/meta_sample.rb +5 -5
  33. data/samples/elaborate/metronome.rb +177 -0
  34. data/samples/elaborate/stock_ticker.rb +0 -6
  35. data/samples/elaborate/tetris.rb +1 -12
  36. data/samples/elaborate/tetris/model/game.rb +3 -0
  37. data/samples/elaborate/tetris/view/bevel.rb +78 -0
  38. data/samples/elaborate/tetris/view/block.rb +6 -29
  39. data/samples/hello/hello_canvas.rb +3 -0
  40. data/samples/hello/hello_canvas_animation_data_binding.rb +66 -0
  41. data/samples/hello/hello_canvas_data_binding.rb +24 -3
  42. data/samples/hello/hello_canvas_path.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 +14 -4
@@ -34,13 +34,9 @@ module Glimmer
34
34
  class Shape
35
35
  class Image < Shape
36
36
  def parameter_names
37
- if @args.to_a.size > 3
38
- image_part_parameter_names
39
- else
40
- image_whole_parameter_names
41
- end
37
+ @parameter_names || image_whole_parameter_names
42
38
  end
43
-
39
+
44
40
  def possible_parameter_names
45
41
  (image_part_parameter_names + image_whole_parameter_names).uniq
46
42
  end
@@ -53,13 +49,15 @@ module Glimmer
53
49
  [:image, :x, :y]
54
50
  end
55
51
 
56
- def parameter_index(attribute_name)
52
+ def set_parameter_attribute(attribute_name, *args)
53
+ return super if @parameter_names.to_a.map(&:to_s).include?(attribute_name.to_s)
57
54
  ####TODO refactor and improve this method through meta-programming (and share across other shapes)
58
55
  if image_part_parameter_names.map(&:to_s).include?(attribute_name.to_s)
59
- image_part_parameter_names.map(&:to_s).index(attribute_name.to_s)
56
+ @parameter_names = image_part_parameter_names
60
57
  elsif image_whole_parameter_names.map(&:to_s).include?(attribute_name.to_s)
61
- image_whole_parameter_names.map(&:to_s).index(attribute_name.to_s)
58
+ @parameter_names = image_whole_parameter_names
62
59
  end
60
+ super
63
61
  end
64
62
 
65
63
  def x
@@ -52,8 +52,10 @@ module Glimmer
52
52
 
53
53
  def add_shape(shape)
54
54
  if shape.is_a?(PathSegment)
55
- @path_segments << shape
56
- @uncalculated_path_segments << shape
55
+ Glimmer::SWT::DisplayProxy.instance.auto_exec do
56
+ @path_segments << shape
57
+ @uncalculated_path_segments << shape
58
+ end
57
59
  else
58
60
  super
59
61
  end
@@ -80,45 +82,52 @@ module Glimmer
80
82
  end
81
83
 
82
84
  def post_dispose_content(path_segment)
83
- @path_segments.delete(path_segment)
84
- @uncalculated_path_segments = @path_segments.dup
85
- @swt_path&.dispose
86
- @swt_path = nil
87
- @args = []
88
- calculated_args_changed!(children: false)
85
+ Glimmer::SWT::DisplayProxy.instance.auto_exec do
86
+ @path_segments.delete(path_segment)
87
+ @uncalculated_path_segments = @path_segments.dup
88
+ @swt_path&.dispose
89
+ @swt_path = nil
90
+ @args = []
91
+ calculated_args_changed!(children: false)
92
+ end
89
93
  end
90
94
 
91
95
  def clear
92
- @path_segments.each { |path_segments| path_segments.class == Path && path_segments.dispose }
93
- @path_segments.clear
94
- @uncalculated_path_segments = @path_segments.dup
95
- @swt_path&.dispose
96
- @swt_path = nil
97
- @args = []
98
- calculated_args_changed!(children: false)
99
- end
100
-
101
- def dispose
102
- clear if self.class == Path
103
- super if parent.is_a?(Drawable)
96
+ Glimmer::SWT::DisplayProxy.instance.auto_exec do
97
+ @path_segments.each { |path_segments| path_segments.class == Path && path_segments.dispose }
98
+ @path_segments.clear
99
+ @uncalculated_path_segments = @path_segments.dup
100
+ @swt_path&.dispose
101
+ @swt_path = nil
102
+ @args = []
103
+ calculated_args_changed!(children: false)
104
+ drawable.redraw unless drawable.is_a?(ImageProxy)
105
+ end
104
106
  end
105
107
 
106
- def calculated_args_changed!(children: true)
107
- super
108
+ def dispose(redraw: true)
109
+ Glimmer::SWT::DisplayProxy.instance.auto_exec do
110
+ clear if self.class == Path
111
+ super(redraw: redraw) if (parent.is_a?(Shape) && (!parent.is_a?(PathSegment) || !parent.part_of_path?)) || parent.is_a?(Drawable)
112
+ end
108
113
  end
109
114
 
110
- def calculated_args
115
+ def calculate_args!
111
116
  new_swt_path = @swt_path.nil? || !@calculated_paint_args || !@calculated_path_args
112
117
  if new_swt_path
113
- @swt_path&.dispose
114
- @swt_path = org.eclipse.swt.graphics.Path.new(Glimmer::SWT::DisplayProxy.instance.swt_display)
115
- @uncalculated_path_segments = @path_segments.dup
118
+ Glimmer::SWT::DisplayProxy.instance.auto_exec do
119
+ @swt_path&.dispose
120
+ @swt_path = org.eclipse.swt.graphics.Path.new(Glimmer::SWT::DisplayProxy.instance.swt_display)
121
+ @uncalculated_path_segments = @path_segments.dup
122
+ end
116
123
  end
117
124
  # TODO recreate @swt_path only if one of the children get disposed (must notify parent on dispose)
118
125
  @args = [@swt_path]
119
- @uncalculated_path_segments.each do |path_segment|
120
- path_segment.add_to_swt_path(@swt_path)
121
- @uncalculated_path_segments.delete(path_segment)
126
+ @uncalculated_path_segments.dup.each do |path_segment|
127
+ Glimmer::SWT::DisplayProxy.instance.auto_exec do
128
+ path_segment.add_to_swt_path(@swt_path)
129
+ @uncalculated_path_segments.delete(path_segment)
130
+ end
122
131
  end
123
132
  @calculated_path_args = true
124
133
  if new_swt_path
@@ -24,8 +24,6 @@ require 'glimmer/swt/custom/shape'
24
24
  module Glimmer
25
25
  module SWT
26
26
  module Custom
27
- # Represents a path to be drawn on a control/widget/canvas/display
28
- # That is because Shape is drawn on a parent as graphics and doesn't have an SWT widget for itself
29
27
  class Shape
30
28
  # Represents path segments like point, line, quad, and cubic curves
31
29
  # Shapes could mix in
@@ -77,10 +75,17 @@ module Glimmer
77
75
  true
78
76
  end
79
77
 
80
- def dispose
81
- parent.post_dispose_content(self) if parent.is_a?(Path)
82
- super if !part_of_path?
83
- drawable.redraw unless drawable.is_a?(ImageProxy)
78
+ def dispose(redraw: true)
79
+ Glimmer::SWT::DisplayProxy.instance.auto_exec do
80
+ # including classes could override to dispose of resources first
81
+ # afterwards, parent removes from its path segments with post_dispose_content
82
+ parent.post_dispose_content(self) if parent.is_a?(Path)
83
+ if part_of_path?
84
+ drawable.redraw if redraw && !drawable.is_a?(ImageProxy)
85
+ else
86
+ super(redraw: redraw)
87
+ end
88
+ end
84
89
  end
85
90
 
86
91
  def first_path_segment?
@@ -92,22 +97,19 @@ module Glimmer
92
97
  end
93
98
 
94
99
  def add_to_swt_path(swt_path)
95
- if @swt_path != swt_path
96
- @swt_path = swt_path
97
- the_path_segment_args = path_segment_args.dup
98
- if !is_a?(Point) && self.class != Path
99
- if !previous_point_connected?
100
- if the_path_segment_args.count == default_path_segment_arg_count
101
- point = the_path_segment_args.shift, the_path_segment_args.shift
102
- @swt_path.moveTo(*point)
103
- elsif first_path_segment?
104
- point = the_path_segment_args[0..1]
105
- @swt_path.moveTo(*point)
106
- end
100
+ the_path_segment_args = path_segment_args.dup
101
+ if !is_a?(Point) && self.class != Path
102
+ if !previous_point_connected?
103
+ if the_path_segment_args.count == default_path_segment_arg_count
104
+ point = the_path_segment_args.shift, the_path_segment_args.shift
105
+ swt_path.moveTo(*point)
106
+ elsif first_path_segment?
107
+ point = the_path_segment_args[0..1]
108
+ swt_path.moveTo(*point)
107
109
  end
108
110
  end
109
- @swt_path.send(path_segment_method_name, *the_path_segment_args)
110
111
  end
112
+ swt_path.send(path_segment_method_name, *the_path_segment_args)
111
113
  end
112
114
 
113
115
  def add_to_geometry(geometry)
@@ -119,16 +119,28 @@ module Glimmer
119
119
 
120
120
  # Logical x coordinate relative to parent
121
121
  def x
122
- x_value = bounds.x
123
- x_value -= parent.absolute_x if parent.is_a?(Shape)
124
- x_value
122
+ x_dependencies = [bounds.x, parent.is_a?(Shape) && parent.absolute_x]
123
+ if x_dependencies != @x_dependencies
124
+ # avoid recalculating values
125
+ bounds_x, parent_absolute_x = @x_dependencies = x_dependencies
126
+ x_value = bounds_x
127
+ x_value -= parent_absolute_x if parent.is_a?(Shape)
128
+ @x = x_value
129
+ end
130
+ @x
125
131
  end
126
132
 
127
133
  # Logical y coordinate relative to parent
128
134
  def y
129
- y_value = bounds.y
130
- y_value -= parent.absolute_y if parent.is_a?(Shape)
131
- y_value
135
+ y_dependencies = [bounds.y, parent.is_a?(Shape) && parent.absolute_y]
136
+ if y_dependencies != @y_dependencies
137
+ # avoid recalculating values
138
+ bounds_y, parent_absolute_y = @y_dependencies = y_dependencies
139
+ y_value = bounds_y
140
+ y_value -= parent_absolute_y if parent.is_a?(Shape)
141
+ @y = y_value
142
+ end
143
+ @y
132
144
  end
133
145
 
134
146
  def width
@@ -144,8 +156,12 @@ module Glimmer
144
156
  end
145
157
 
146
158
  def include?(x, y)
147
- comparison_lines = absolute_point_xy_array.zip(absolute_point_xy_array.rotate(1))
148
- comparison_lines.any? {|line| Line.include?(line.first.first, line.first.last, line.last.first, line.last.last, x, y)}
159
+ if filled?
160
+ contain?(x, y)
161
+ else
162
+ comparison_lines = absolute_point_xy_array.zip(absolute_point_xy_array.rotate(1))
163
+ comparison_lines.any? {|line| Line.include?(line.first.first, line.first.last, line.last.first, line.last.last, x, y)}
164
+ end
149
165
  end
150
166
 
151
167
  def move_by(x_delta, y_delta)
@@ -42,19 +42,23 @@ module Glimmer
42
42
  end
43
43
 
44
44
  def point_count
45
+ point_array = args.size > 1 ? args : self.point_array
45
46
  point_array.count / 2
46
47
  end
47
48
 
48
49
  def [](index)
50
+ point_array = args.size > 1 ? args : self.point_array
49
51
  index = 0 if index == point_count
50
52
  org.eclipse.swt.graphics.Point.new(point_array[index * 2], point_array[index * 2 + 1])
51
53
  end
52
54
 
53
55
  def x_array
56
+ point_array = args.size > 1 ? args : self.point_array
54
57
  point_array.each_with_index.select {|pair| pair.last.even?}.map(&:first)
55
58
  end
56
59
 
57
60
  def y_array
61
+ point_array = args.size > 1 ? args : self.point_array
58
62
  point_array.each_with_index.select {|pair| pair.last.odd?}.map(&:first)
59
63
  end
60
64
 
@@ -63,6 +67,7 @@ module Glimmer
63
67
  end
64
68
 
65
69
  def absolute_point_array
70
+ point_array = args.size > 1 ? args : self.point_array
66
71
  if parent.is_a?(Shape)
67
72
  point_array.each_with_index.map do |coordinate, i|
68
73
  if i.even?
@@ -23,7 +23,6 @@ require 'glimmer/swt/custom/shape'
23
23
  require 'glimmer/swt/swt_proxy'
24
24
  require 'glimmer/swt/display_proxy'
25
25
  require 'glimmer/swt/color_proxy'
26
- require 'glimmer/swt/font_proxy'
27
26
  require 'glimmer/swt/transform_proxy'
28
27
 
29
28
  module Glimmer
@@ -34,16 +33,7 @@ module Glimmer
34
33
  class Shape
35
34
  class Rectangle < Shape
36
35
  def parameter_names
37
- # TODO consider optimizing just like text where it is set upon updating attribute and here you just return a variable
38
- if @args.to_a.size >= 6
39
- rectangle_round_parameter_names
40
- elsif @args.to_a.size == 5
41
- rectangle_gradient_parameter_names
42
- elsif @args.to_a.size == 1
43
- rectangle_rectangle_parameter_names
44
- else
45
- rectangle_parameter_names
46
- end
36
+ @parameter_names || rectangle_parameter_names
47
37
  end
48
38
 
49
39
  def possible_parameter_names
@@ -68,17 +58,18 @@ module Glimmer
68
58
  [:rectangle]
69
59
  end
70
60
 
71
- def parameter_index(attribute_name)
72
- ####TODO refactor and improve this method through meta-programming (and share across other shapes)
73
- if rectangle_round_parameter_names.map(&:to_s).include?(attribute_name.to_s)
74
- rectangle_round_parameter_names.map(&:to_s).index(attribute_name.to_s)
61
+ def set_parameter_attribute(attribute_name, *args)
62
+ return super if @parameter_names.to_a.map(&:to_s).include?(attribute_name.to_s)
63
+ if rectangle_parameter_names.map(&:to_s).include?(attribute_name.to_s)
64
+ @parameter_names = rectangle_parameter_names
65
+ elsif rectangle_round_parameter_names.map(&:to_s).include?(attribute_name.to_s)
66
+ @parameter_names = rectangle_round_parameter_names
75
67
  elsif rectangle_gradient_parameter_names.map(&:to_s).include?(attribute_name.to_s)
76
- rectangle_gradient_parameter_names.map(&:to_s).index(attribute_name.to_s)
77
- elsif rectangle_parameter_names.map(&:to_s).include?(attribute_name.to_s)
78
- rectangle_parameter_names.map(&:to_s).index(attribute_name.to_s)
68
+ @parameter_names = rectangle_gradient_parameter_names
79
69
  elsif rectangle_rectangle_parameter_names.map(&:to_s).include?(attribute_name.to_s)
80
- rectangle_rectangle_parameter_names.map(&:to_s).index(attribute_name.to_s)
70
+ @parameter_names = rectangle_rectangle_parameter_names
81
71
  end
72
+ super
82
73
  end
83
74
 
84
75
  def point_xy_array
@@ -84,7 +84,7 @@ module Glimmer
84
84
  end
85
85
 
86
86
  def content(&block)
87
- Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::SWT::DisplayExpression.new, &block)
87
+ Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::SWT::DisplayExpression.new, 'display', &block)
88
88
  end
89
89
 
90
90
  # asynchronously executes the block (required from threads other than first GUI thread)
@@ -51,7 +51,7 @@ module Glimmer
51
51
  end
52
52
 
53
53
  def content(&block)
54
- Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::SWT::MessageBoxExpression.new, &block)
54
+ Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::SWT::MessageBoxExpression.new, 'message_box', &block)
55
55
  end
56
56
 
57
57
  # TODO refactor the following methods to put in a JavaBean mixin or somethin (perhaps contribute to OSS project too)
@@ -186,7 +186,7 @@ module Glimmer
186
186
  end
187
187
 
188
188
  def content(&block)
189
- Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::SWT::ShellExpression.new, &block)
189
+ Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::SWT::ShellExpression.new, 'shell', &block)
190
190
  end
191
191
 
192
192
  # (happens as part of `#open`)
@@ -0,0 +1,52 @@
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/swt/widget_proxy'
23
+
24
+ module Glimmer
25
+ module SWT
26
+ # Proxy for org.eclipse.swt.widgets.TabProxy
27
+ #
28
+ # Follows the Proxy Design Pattern
29
+ class TabFolderProxy < WidgetProxy
30
+ def initialize(underscored_widget_name, parent, args, swt_widget: nil)
31
+ @initialize_tabs_on_select = args.delete(:initialize_tabs_on_select)
32
+ super
33
+ end
34
+
35
+ def post_add_content
36
+ shown = false
37
+ unless @initialize_tabs_on_select
38
+ @show_listener = parent_proxy.on_swt_show do
39
+ unless shown
40
+ shown = true
41
+ self.items.to_a.each do |item|
42
+ self.selection = item
43
+ end
44
+ self.selection = self.items.first unless self.items.first.nil?
45
+ @show_listener.deregister
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -73,7 +73,7 @@ module Glimmer
73
73
  end
74
74
 
75
75
  def content(&block)
76
- Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::SWT::TransformExpression.new, &block)
76
+ Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::SWT::TransformExpression.new, 'transform', &block)
77
77
  end
78
78
 
79
79
  def proxy_source_object
@@ -712,7 +712,7 @@ module Glimmer
712
712
 
713
713
  def content(&block)
714
714
  auto_exec do
715
- Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::SWT::WidgetExpression.new, &block)
715
+ Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::SWT::WidgetExpression.new, @keyword, &block)
716
716
  end
717
717
  end
718
718
 
@@ -0,0 +1,281 @@
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/ui'
24
+ require 'glimmer/error'
25
+ require 'glimmer/swt/swt_proxy'
26
+ require 'glimmer/swt/display_proxy'
27
+ require 'glimmer/util/proc_tracker'
28
+ require 'glimmer/data_binding/observer'
29
+ require 'glimmer/data_binding/observable_model'
30
+
31
+ module Glimmer
32
+ module UI
33
+ module CustomShape
34
+ include SuperModule
35
+ include DataBinding::ObservableModel
36
+
37
+ super_module_included do |klass|
38
+ klass.include(Glimmer)
39
+ Glimmer::UI::CustomShape.add_custom_shape_namespaces_for(klass)
40
+ end
41
+
42
+ class << self
43
+ def for(underscored_custom_shape_name)
44
+ unless flyweight_custom_shape_classes.keys.include?(underscored_custom_shape_name)
45
+ begin
46
+ extracted_namespaces = underscored_custom_shape_name.
47
+ to_s.
48
+ split(/__/).map do |namespace|
49
+ namespace.camelcase(:upper)
50
+ end
51
+ custom_shape_namespaces.each do |base|
52
+ extracted_namespaces.reduce(base) do |result, namespace|
53
+ if !result.constants.include?(namespace)
54
+ namespace = result.constants.detect {|c| c.to_s.upcase == namespace.to_s.upcase } || namespace
55
+ end
56
+ begin
57
+ flyweight_custom_shape_classes[underscored_custom_shape_name] = constant = result.const_get(namespace)
58
+ return constant if constant.ancestors.include?(Glimmer::UI::CustomShape)
59
+ flyweight_custom_shape_classes[underscored_custom_shape_name] = constant
60
+ rescue => e
61
+ # Glimmer::Config.logger.debug {"#{e.message}\n#{e.backtrace.join("\n")}"}
62
+ flyweight_custom_shape_classes[underscored_custom_shape_name] = result
63
+ end
64
+ end
65
+ end
66
+ raise "#{underscored_custom_shape_name} has no custom shape class!"
67
+ rescue => e
68
+ Glimmer::Config.logger.debug {e.message}
69
+ Glimmer::Config.logger.debug {"#{e.message}\n#{e.backtrace.join("\n")}"}
70
+ flyweight_custom_shape_classes[underscored_custom_shape_name] = nil
71
+ end
72
+ end
73
+ flyweight_custom_shape_classes[underscored_custom_shape_name]
74
+ end
75
+
76
+ # Flyweight Design Pattern memoization cache. Can be cleared if memory is needed.
77
+ def flyweight_custom_shape_classes
78
+ @flyweight_custom_shape_classes ||= {}
79
+ end
80
+
81
+ # Returns keyword to use for this custom shape
82
+ def keyword
83
+ self.name.underscore.gsub('::', '__')
84
+ end
85
+
86
+ # Returns shortcut keyword to use for this custom shape (keyword minus namespace)
87
+ def shortcut_keyword
88
+ self.name.underscore.gsub('::', '__').split('__').last
89
+ end
90
+
91
+ def add_custom_shape_namespaces_for(klass)
92
+ Glimmer::UI::CustomShape.namespaces_for_class(klass).drop(1).each do |namespace|
93
+ Glimmer::UI::CustomShape.custom_shape_namespaces << namespace
94
+ end
95
+ end
96
+
97
+ def namespaces_for_class(m)
98
+ return [m] if m.name.nil?
99
+ namespace_constants = m.name.split(/::/).map(&:to_sym)
100
+ namespace_constants.reduce([Object]) do |output, namespace_constant|
101
+ output += [output.last.const_get(namespace_constant)]
102
+ end[1..-1].uniq.reverse
103
+ end
104
+
105
+ def custom_shape_namespaces
106
+ @custom_shape_namespaces ||= reset_custom_shape_namespaces
107
+ end
108
+
109
+ def reset_custom_shape_namespaces
110
+ @custom_shape_namespaces = Set[Object, Glimmer::UI]
111
+ end
112
+
113
+ # Allows defining convenience option accessors for an array of option names
114
+ # Example: `options :color1, :color2` defines `#color1` and `#color2`
115
+ # where they return the instance values `options[:color1]` and `options[:color2]`
116
+ # respectively.
117
+ # Can be called multiple times to set more options additively.
118
+ # When passed no arguments, it returns list of all option names captured so far
119
+ def options(*new_options)
120
+ new_options = new_options.compact.map(&:to_s).map(&:to_sym)
121
+ if new_options.empty?
122
+ @options ||= {} # maps options to defaults
123
+ else
124
+ new_options = new_options.reduce({}) {|new_options_hash, new_option| new_options_hash.merge(new_option => nil)}
125
+ @options = options.merge(new_options)
126
+ def_option_attr_accessors(new_options)
127
+ end
128
+ end
129
+
130
+ def option(new_option, default: nil)
131
+ new_option = new_option.to_s.to_sym
132
+ new_options = {new_option => default}
133
+ @options = options.merge(new_options)
134
+ def_option_attr_accessors(new_options)
135
+ end
136
+
137
+ def def_option_attr_accessors(new_options)
138
+ new_options.each do |option, default|
139
+ class_eval <<-end_eval, __FILE__, __LINE__
140
+ def #{option}
141
+ options[:#{option}]
142
+ end
143
+ def #{option}=(option_value)
144
+ self.options[:#{option}] = option_value
145
+ end
146
+ end_eval
147
+ end
148
+ end
149
+
150
+ def before_body(&block)
151
+ @before_body_block = block
152
+ end
153
+
154
+ def body(&block)
155
+ @body_block = block
156
+ end
157
+
158
+ def after_body(&block)
159
+ @after_body_block = block
160
+ end
161
+
162
+ # Current custom shapes being rendered. Useful to yoke all observers evaluated during rendering of their custom shapes for automatical disposal on_shape_disposed
163
+ def current_custom_shapes
164
+ @current_custom_shapes ||= []
165
+ end
166
+ end
167
+
168
+ attr_reader :body_root, :args, :parent, :parent_proxy, :options
169
+
170
+ def initialize(parent, *args, options, &content)
171
+ Glimmer::UI::CustomShape.current_custom_shapes << self
172
+ @parent_proxy = @parent = parent
173
+ @parent_proxy = @parent&.get_data('proxy') if @parent.respond_to?(:get_data) && @parent.get_data('proxy')
174
+ @args = args
175
+ options ||= {}
176
+ @options = self.class.options.merge(options)
177
+ @content = Util::ProcTracker.new(content) if content
178
+ execute_hook('before_body')
179
+ body_block = self.class.instance_variable_get("@body_block")
180
+ raise Glimmer::Error, 'Invalid custom shape for having no body! Please define body block!' if body_block.nil?
181
+ @body_root = instance_exec(&body_block)
182
+ raise Glimmer::Error, 'Invalid custom shape for having an empty body! Please fill body block!' if @body_root.nil?
183
+ auto_exec do
184
+ @body_root.set_data('custom_shape', self)
185
+ end
186
+ execute_hook('after_body')
187
+ end
188
+
189
+ # Subclasses may override to perform post initialization work on an added child
190
+ def post_initialize_child(child)
191
+ # No Op by default
192
+ end
193
+
194
+ def observer_registrations
195
+ @observer_registrations ||= []
196
+ end
197
+
198
+ def has_attribute?(attribute_name, *args)
199
+ has_instance_method?(attribute_setter(attribute_name)) ||
200
+ @body_root.has_attribute?(attribute_name, *args)
201
+ end
202
+
203
+ def set_attribute(attribute_name, *args)
204
+ if has_instance_method?(attribute_setter(attribute_name))
205
+ send(attribute_setter(attribute_name), *args)
206
+ else
207
+ @body_root.set_attribute(attribute_name, *args)
208
+ end
209
+ end
210
+
211
+ # This method ensures it has an instance method not coming from Glimmer DSL
212
+ def has_instance_method?(method_name)
213
+ respond_to?(method_name) and
214
+ !body_root&.respond_to?(method_name) and
215
+ (method(method_name) rescue nil) and
216
+ !method(method_name)&.source_location&.first&.include?('glimmer/dsl/engine.rb') and
217
+ !method(method_name)&.source_location&.first&.include?('glimmer/swt/custom/shape.rb')
218
+ end
219
+
220
+ def get_attribute(attribute_name)
221
+ if has_instance_method?(attribute_name)
222
+ send(attribute_name)
223
+ else
224
+ @body_root.get_attribute(attribute_name)
225
+ end
226
+ end
227
+
228
+ def attribute_setter(attribute_name)
229
+ "#{attribute_name}="
230
+ end
231
+
232
+ # TODO see if it is worth it to eliminate duplication of async_exec/sync_exec
233
+ # delegation to DisplayProxy, via a module
234
+
235
+ def async_exec(&block)
236
+ SWT::DisplayProxy.instance.async_exec(&block)
237
+ end
238
+
239
+ def sync_exec(&block)
240
+ SWT::DisplayProxy.instance.sync_exec(&block)
241
+ end
242
+
243
+ def timer_exec(delay_in_millis, &block)
244
+ SWT::DisplayProxy.instance.timer_exec(delay_in_millis, &block)
245
+ end
246
+
247
+ # Returns content block if used as an attribute reader (no args)
248
+ # Otherwise, if a block is passed, it adds it as content to this custom shape
249
+ def content(&block)
250
+ if block_given?
251
+ body_root.content(&block)
252
+ else
253
+ @content
254
+ end
255
+ end
256
+
257
+ def method_missing(method, *args, &block)
258
+ # TODO Consider supporting a glimmer error silencing option for methods defined here
259
+ # but fail the glimmer DSL for the right reason to avoid seeing noise in the log output
260
+ body_root.send(method, *args, &block)
261
+ end
262
+
263
+ alias local_respond_to? respond_to?
264
+ def respond_to?(method, *args, &block)
265
+ super or
266
+ body_root.respond_to?(method, *args, &block)
267
+ end
268
+
269
+ private
270
+
271
+ def execute_hook(hook_name)
272
+ hook_block = self.class.instance_variable_get("@#{hook_name}_block")
273
+ return if hook_block.nil?
274
+ temp_method_name = "#{hook_name}_block_#{hook_block.hash.abs}_#{(Time.now.to_f * 1_000_000).to_i}"
275
+ singleton_class.define_method(temp_method_name, &hook_block)
276
+ send(temp_method_name)
277
+ singleton_class.send(:remove_method, temp_method_name)
278
+ end
279
+ end
280
+ end
281
+ end