glimmer-dsl-swt 4.18.6.2 → 4.18.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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