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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +34 -0
- data/README.md +4 -4
- data/VERSION +1 -1
- data/docs/reference/GLIMMER_GUI_DSL_SYNTAX.md +64 -6
- data/docs/reference/GLIMMER_SAMPLES.md +71 -0
- data/glimmer-dsl-swt.gemspec +16 -6
- data/lib/glimmer/dsl/swt/animation_expression.rb +1 -1
- data/lib/glimmer/dsl/swt/custom_shape_expression.rb +61 -0
- data/lib/glimmer/dsl/swt/custom_widget_expression.rb +1 -1
- data/lib/glimmer/dsl/swt/dsl.rb +1 -0
- data/lib/glimmer/dsl/swt/expand_item_expression.rb +4 -4
- data/lib/glimmer/dsl/swt/image_expression.rb +1 -1
- data/lib/glimmer/dsl/swt/multiply_expression.rb +1 -1
- data/lib/glimmer/dsl/swt/shape_expression.rb +1 -1
- data/lib/glimmer/dsl/swt/transform_expression.rb +1 -1
- data/lib/glimmer/dsl/swt/widget_expression.rb +2 -1
- data/lib/glimmer/swt/custom/shape.rb +473 -180
- data/lib/glimmer/swt/custom/shape/image.rb +7 -9
- data/lib/glimmer/swt/custom/shape/path.rb +38 -29
- data/lib/glimmer/swt/custom/shape/path_segment.rb +21 -19
- data/lib/glimmer/swt/custom/shape/polygon.rb +24 -8
- data/lib/glimmer/swt/custom/shape/polyline.rb +5 -0
- data/lib/glimmer/swt/custom/shape/rectangle.rb +10 -19
- data/lib/glimmer/swt/display_proxy.rb +1 -1
- data/lib/glimmer/swt/message_box_proxy.rb +1 -1
- data/lib/glimmer/swt/shell_proxy.rb +1 -1
- data/lib/glimmer/swt/tab_folder_proxy.rb +52 -0
- data/lib/glimmer/swt/transform_proxy.rb +1 -1
- data/lib/glimmer/swt/widget_proxy.rb +1 -1
- data/lib/glimmer/ui/custom_shape.rb +281 -0
- data/samples/elaborate/meta_sample.rb +5 -5
- data/samples/elaborate/metronome.rb +177 -0
- data/samples/elaborate/stock_ticker.rb +0 -6
- data/samples/elaborate/tetris.rb +1 -12
- data/samples/elaborate/tetris/model/game.rb +3 -0
- data/samples/elaborate/tetris/view/bevel.rb +78 -0
- data/samples/elaborate/tetris/view/block.rb +6 -29
- data/samples/hello/hello_canvas.rb +3 -0
- data/samples/hello/hello_canvas_animation_data_binding.rb +66 -0
- data/samples/hello/hello_canvas_data_binding.rb +24 -3
- data/samples/hello/hello_canvas_path.rb +1 -1
- data/samples/hello/hello_custom_shape.rb +78 -0
- data/samples/hello/hello_shape.rb +71 -0
- data/samples/hello/hello_spinner.rb +7 -2
- data/sounds/metronome-down.wav +0 -0
- data/sounds/metronome-up.wav +0 -0
- metadata +14 -4
@@ -34,13 +34,9 @@ module Glimmer
|
|
34
34
|
class Shape
|
35
35
|
class Image < Shape
|
36
36
|
def parameter_names
|
37
|
-
|
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
|
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
|
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
|
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
|
-
|
56
|
-
|
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
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
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
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
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
|
107
|
-
|
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
|
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
|
-
|
114
|
-
|
115
|
-
|
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
|
-
|
121
|
-
|
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
|
-
|
82
|
-
|
83
|
-
|
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
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
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
|
-
|
123
|
-
|
124
|
-
|
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
|
-
|
130
|
-
|
131
|
-
|
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
|
-
|
148
|
-
|
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
|
-
|
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
|
72
|
-
|
73
|
-
if
|
74
|
-
|
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
|
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
|
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
|