glimmer-dsl-swt 4.18.1.1 → 4.18.2.4
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 +39 -0
- data/README.md +141 -51
- data/VERSION +1 -1
- data/glimmer-dsl-swt.gemspec +12 -4
- data/lib/glimmer/dsl/swt/display_expression.rb +3 -3
- data/lib/glimmer/dsl/swt/property_expression.rb +2 -1
- data/lib/glimmer/rake_task/package.rb +9 -9
- data/lib/glimmer/rake_task/scaffold.rb +4 -3
- data/lib/glimmer/swt/custom/animation.rb +138 -15
- data/lib/glimmer/swt/custom/code_text.rb +2 -1
- data/lib/glimmer/swt/custom/shape.rb +58 -12
- data/lib/glimmer/swt/display_proxy.rb +1 -1
- data/lib/glimmer/swt/layout_data_proxy.rb +3 -3
- data/lib/glimmer/swt/shell_proxy.rb +3 -2
- data/lib/glimmer/swt/widget_proxy.rb +5 -7
- data/lib/glimmer/ui/custom_shell.rb +15 -2
- data/lib/glimmer/ui/custom_widget.rb +11 -9
- data/samples/elaborate/meta_sample.rb +21 -0
- data/samples/elaborate/tetris.rb +108 -0
- data/samples/elaborate/tetris/model/block.rb +48 -0
- data/samples/elaborate/tetris/model/game.rb +185 -0
- data/samples/elaborate/tetris/model/tetromino.rb +313 -0
- data/samples/elaborate/tetris/view/block.rb +42 -0
- data/samples/elaborate/tetris/view/game_over_dialog.rb +72 -0
- data/samples/elaborate/tetris/view/playfield.rb +56 -0
- data/samples/elaborate/tetris/view/score_lane.rb +87 -0
- data/samples/hello/hello_canvas.rb +28 -8
- metadata +24 -14
@@ -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
|
@@ -30,7 +30,8 @@ module Glimmer
|
|
30
30
|
args.size > 0 and
|
31
31
|
parent.respond_to?(:set_attribute) and
|
32
32
|
parent.respond_to?(:has_attribute?) and
|
33
|
-
parent.has_attribute?(keyword, *args)
|
33
|
+
parent.has_attribute?(keyword, *args) and
|
34
|
+
!(parent.respond_to?(:swt_widget) && parent.swt_widget.class == org.eclipse.swt.widgets.Canvas && keyword == 'image')
|
34
35
|
end
|
35
36
|
|
36
37
|
def interpret(parent, keyword, *args, &block)
|
@@ -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
|
@@ -27,7 +27,7 @@ module Glimmer
|
|
27
27
|
module Package
|
28
28
|
class << self
|
29
29
|
attr_accessor :javapackager_extra_args
|
30
|
-
alias jpackage_extra_args
|
30
|
+
alias jpackage_extra_args javapackager_extra_args
|
31
31
|
|
32
32
|
def clean
|
33
33
|
require 'fileutils'
|
@@ -48,7 +48,7 @@ module Glimmer
|
|
48
48
|
FileUtils.mkdir_p('vendor/jars')
|
49
49
|
command = "lock_jars --vendor-dir vendor/jars"
|
50
50
|
puts command
|
51
|
-
system command
|
51
|
+
system command
|
52
52
|
end
|
53
53
|
|
54
54
|
def config
|
@@ -78,18 +78,18 @@ module Glimmer
|
|
78
78
|
else
|
79
79
|
puts 'Warbler executable "warble" is missing!'
|
80
80
|
end
|
81
|
-
end
|
81
|
+
end
|
82
82
|
end
|
83
83
|
|
84
84
|
def jar
|
85
85
|
FileUtils.mkdir_p('dist')
|
86
86
|
puts "Generating JAR with Warbler..."
|
87
87
|
system "jruby -S gem install warbler -v2.0.5 --no-document" unless warbler_exists?
|
88
|
-
system('warble')
|
88
|
+
system('warble')
|
89
89
|
end
|
90
90
|
|
91
91
|
def native(native_type=nil, native_extra_args)
|
92
|
-
puts "Generating native executable with javapackager/jpackage..."
|
92
|
+
puts "Generating native executable with javapackager/jpackage..."
|
93
93
|
java_version = `java -version`
|
94
94
|
puts "WARNING! Glimmer Packaging Pre-Requisite Java Version 1.8.0_241 Is Not Found!" unless java_version.include?('1.8.0_241')
|
95
95
|
require 'facets/string/titlecase'
|
@@ -125,7 +125,7 @@ module Glimmer
|
|
125
125
|
command += " #{ENV['JAVAPACKAGER_EXTRA_ARGS']} " if ENV['JAVAPACKAGER_EXTRA_ARGS']
|
126
126
|
command += " #{native_extra_args} " if native_extra_args
|
127
127
|
puts command
|
128
|
-
system command
|
128
|
+
system command
|
129
129
|
end
|
130
130
|
|
131
131
|
private
|
@@ -42,6 +42,7 @@ module Glimmer
|
|
42
42
|
*.gem
|
43
43
|
*.rbc
|
44
44
|
/.config
|
45
|
+
/.mvn/
|
45
46
|
/coverage/
|
46
47
|
/InstalledFiles
|
47
48
|
/pkg/
|
@@ -97,9 +98,9 @@ module Glimmer
|
|
97
98
|
.gladiator
|
98
99
|
|
99
100
|
# Glimmer
|
100
|
-
dist
|
101
|
-
packages
|
102
|
-
vendor/jars
|
101
|
+
/dist/
|
102
|
+
/packages/
|
103
|
+
/vendor/jars/
|
103
104
|
MULTI_LINE_STRING
|
104
105
|
|
105
106
|
GEMFILE = <<~MULTI_LINE_STRING
|
@@ -28,42 +28,126 @@ module Glimmer
|
|
28
28
|
class Animation
|
29
29
|
include Properties # TODO rename to Properties
|
30
30
|
|
31
|
+
class << self
|
32
|
+
def schedule_frame_animation(animation, &frame_animation_block)
|
33
|
+
frame_animation_queue(animation).prepend(frame_animation_block)
|
34
|
+
swt_display.async_exec do
|
35
|
+
frame_animation_queue(next_animation)&.pop&.call
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def next_animation
|
40
|
+
animation = nil
|
41
|
+
while frame_animation_queues.values.reduce(:+)&.any? && (animation.nil? || frame_animation_queue(animation).last.nil?)
|
42
|
+
animation = frame_animation_queues.keys[next_animation_index]
|
43
|
+
frame_animation_queues.delete(animation) if frame_animation_queues.values.reduce(:+)&.any? && !animation.nil? && frame_animation_queue(animation).empty?
|
44
|
+
end
|
45
|
+
animation
|
46
|
+
end
|
47
|
+
|
48
|
+
def next_animation_index
|
49
|
+
next_schedule_index % frame_animation_queues.keys.size
|
50
|
+
end
|
51
|
+
|
52
|
+
def next_schedule_index
|
53
|
+
unless defined? @@next_schedule_index
|
54
|
+
@@next_schedule_index = 0
|
55
|
+
else
|
56
|
+
@@next_schedule_index += 1
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def frame_animation_queues
|
61
|
+
unless defined? @@frame_animation_queues
|
62
|
+
@@frame_animation_queues = {}
|
63
|
+
end
|
64
|
+
@@frame_animation_queues
|
65
|
+
end
|
66
|
+
|
67
|
+
def frame_animation_queue(animation)
|
68
|
+
frame_animation_queues[animation] ||= []
|
69
|
+
end
|
70
|
+
|
71
|
+
def swt_display
|
72
|
+
unless defined? @@swt_display
|
73
|
+
@@swt_display = DisplayProxy.instance.swt_display
|
74
|
+
end
|
75
|
+
@@swt_display
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
31
79
|
attr_reader :parent, :options, :frame_index, :cycle
|
32
80
|
alias current_frame_index frame_index
|
33
|
-
attr_accessor :frame_block, :every, :cycle_count, :frame_count, :started
|
81
|
+
attr_accessor :frame_block, :every, :cycle_count, :frame_count, :started, :duration_limit
|
82
|
+
alias started? started
|
34
83
|
# TODO consider supporting an async: false option
|
35
84
|
|
36
85
|
def initialize(parent)
|
37
86
|
@parent = parent
|
38
87
|
@started = true
|
39
88
|
@frame_index = 0
|
89
|
+
@cycle_count_index = 0
|
90
|
+
@start_number = 0 # denotes the number of starts (increments on every start)
|
91
|
+
self.class.swt_display # ensures initializing variable to set from GUI thread
|
40
92
|
end
|
41
93
|
|
42
94
|
def post_add_content
|
43
95
|
@parent.on_widget_disposed { stop }
|
44
|
-
start if
|
96
|
+
start if started?
|
45
97
|
end
|
46
98
|
|
99
|
+
# Starts an animation that is indefinite or has never been started before (i.e. having `started: false` option).
|
100
|
+
# Otherwise, resumes a stopped animation that has not been completed.
|
47
101
|
def start
|
48
|
-
|
102
|
+
return if @start_number > 0 && started?
|
103
|
+
@start_number += 1
|
104
|
+
@started = true
|
105
|
+
@start_time = Time.now
|
106
|
+
@original_start_time = @start_time if @duration.nil?
|
107
|
+
# TODO track when finished in a variable for finite animations (whether by frame count, cycle count, or duration limit)
|
49
108
|
Thread.new do
|
109
|
+
start_number = @start_number
|
50
110
|
if cycle_count.is_a?(Integer) && cycle.is_a?(Array)
|
51
111
|
(cycle_count * cycle.length).times do
|
52
|
-
break unless draw_frame(
|
112
|
+
break unless draw_frame(start_number)
|
53
113
|
end
|
54
114
|
else
|
55
115
|
loop do
|
56
116
|
# this code has to be duplicated to break from a loop (break keyword only works when literally in a loop block)
|
57
|
-
break unless draw_frame(
|
117
|
+
break unless draw_frame(start_number)
|
58
118
|
end
|
59
119
|
end
|
60
|
-
@started = false
|
61
120
|
end
|
62
121
|
end
|
63
122
|
|
64
123
|
def stop
|
124
|
+
return if stopped?
|
65
125
|
@started = false
|
126
|
+
@duration = (Time.now - @start_time) + @duration.to_f if duration_limited? && !@start_time.nil?
|
127
|
+
end
|
128
|
+
|
129
|
+
# Restarts an animation (whether indefinite or not and whether stopped or not)
|
130
|
+
def restart
|
131
|
+
@original_start_time = @start_time = nil
|
132
|
+
@duration = nil
|
133
|
+
@frame_index = 0
|
134
|
+
@cycle_count_index = 0
|
135
|
+
stop
|
136
|
+
start
|
137
|
+
end
|
138
|
+
|
139
|
+
def stopped?
|
140
|
+
!started?
|
141
|
+
end
|
142
|
+
|
143
|
+
def finite?
|
144
|
+
frame_count_limited? || cycle_limited? || duration_limited?
|
145
|
+
end
|
146
|
+
|
147
|
+
def infinite?
|
148
|
+
!finite?
|
66
149
|
end
|
150
|
+
alias indefinite? infinite?
|
67
151
|
|
68
152
|
def has_attribute?(attribute_name, *args)
|
69
153
|
respond_to?(ruby_attribute_setter(attribute_name)) && respond_to?(ruby_attribute_getter(attribute_name))
|
@@ -89,21 +173,60 @@ module Glimmer
|
|
89
173
|
end
|
90
174
|
end
|
91
175
|
|
176
|
+
def cycle_enabled?
|
177
|
+
@cycle.is_a?(Array)
|
178
|
+
end
|
179
|
+
|
180
|
+
def cycle_limited?
|
181
|
+
cycle_enabled? && @cycle_count.is_a?(Integer)
|
182
|
+
end
|
183
|
+
|
184
|
+
def duration_limited?
|
185
|
+
@duration_limit.is_a?(Integer)
|
186
|
+
end
|
187
|
+
|
188
|
+
def frame_count_limited?
|
189
|
+
@frame_count.is_a?(Integer)
|
190
|
+
end
|
191
|
+
|
192
|
+
def surpassed_duration_limit?
|
193
|
+
duration_limited? && ((Time.now - @start_time) > (@duration_limit - @duration.to_f))
|
194
|
+
end
|
195
|
+
|
196
|
+
def within_duration_limit?
|
197
|
+
!surpassed_duration_limit?
|
198
|
+
end
|
199
|
+
|
92
200
|
private
|
93
201
|
|
94
202
|
# Returns true on success of painting a frame and false otherwise
|
95
|
-
def draw_frame(
|
96
|
-
return false if
|
203
|
+
def draw_frame(start_number)
|
204
|
+
return false if stopped? ||
|
205
|
+
start_number != @start_number ||
|
206
|
+
(frame_count_limited? && @frame_index == @frame_count) ||
|
207
|
+
(cycle_limited? && @cycle_count_index == @cycle_count) ||
|
208
|
+
surpassed_duration_limit?
|
97
209
|
block_args = [@frame_index]
|
98
|
-
block_args << @cycle[@frame_index % @cycle.length] if
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
210
|
+
block_args << @cycle[@frame_index % @cycle.length] if cycle_enabled?
|
211
|
+
current_frame_index = @frame_index
|
212
|
+
current_cycle_count_index = @cycle_count_index
|
213
|
+
self.class.schedule_frame_animation(self) do
|
214
|
+
if started? && start_number == @start_number && within_duration_limit?
|
215
|
+
@parent.clear_shapes
|
216
|
+
@parent.content {
|
217
|
+
frame_block.call(*block_args)
|
218
|
+
}
|
219
|
+
@parent.redraw
|
220
|
+
else
|
221
|
+
if stopped? && @frame_index > current_frame_index
|
222
|
+
@started = false
|
223
|
+
@frame_index = current_frame_index
|
224
|
+
@cycle_count_index = current_cycle_count_index
|
225
|
+
end
|
226
|
+
end
|
105
227
|
end
|
106
228
|
@frame_index += 1
|
229
|
+
@cycle_count_index += 1 if cycle_limited? && (@frame_index % @cycle&.length&.to_i) == 0
|
107
230
|
sleep(every) if every.is_a?(Numeric)
|
108
231
|
true
|
109
232
|
rescue => e
|
@@ -61,11 +61,12 @@ module Glimmer
|
|
61
61
|
|
62
62
|
before_body {
|
63
63
|
@swt_style = swt_style == 0 ? [:border, :multi, :v_scroll, :h_scroll] : swt_style
|
64
|
+
@font_name = display.get_font_list(nil, true).map(&:name).include?('Consolas') ? 'Consolas' : 'Courier'
|
64
65
|
}
|
65
66
|
|
66
67
|
body {
|
67
68
|
styled_text(swt_style) {
|
68
|
-
font name:
|
69
|
+
font name: @font_name, height: 15
|
69
70
|
foreground rgb(75, 75, 75)
|
70
71
|
left_margin 5
|
71
72
|
top_margin 5
|
@@ -20,6 +20,10 @@
|
|
20
20
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
21
|
|
22
22
|
require 'glimmer/swt/properties'
|
23
|
+
require 'glimmer/swt/swt_proxy'
|
24
|
+
require 'glimmer/swt/display_proxy'
|
25
|
+
require 'glimmer/swt/color_proxy'
|
26
|
+
require 'glimmer/swt/font_proxy'
|
23
27
|
|
24
28
|
module Glimmer
|
25
29
|
module SWT
|
@@ -28,8 +32,9 @@ module Glimmer
|
|
28
32
|
# swt_widget returns the parent (e.g. a `canvas` WidgetProxy), equivalent to `parent.swt_widget`
|
29
33
|
# That is because Shape is drawn on a parent as graphics and doesn't have an SWT widget for itself
|
30
34
|
class Shape
|
35
|
+
include Packages
|
31
36
|
include Properties
|
32
|
-
# TODO support textExtent
|
37
|
+
# TODO support textExtent sized shapes nested within text/string
|
33
38
|
# TODO support a Pattern DSL for methods that take Pattern arguments
|
34
39
|
|
35
40
|
class << self
|
@@ -48,8 +53,11 @@ module Glimmer
|
|
48
53
|
end
|
49
54
|
|
50
55
|
def method_name(keyword, args)
|
51
|
-
|
52
|
-
|
56
|
+
keyword = keyword.to_s
|
57
|
+
gradient = 'gradient_' if arg_options(args)[:gradient]
|
58
|
+
round = 'round_' if arg_options(args)[:round]
|
59
|
+
gc_instance_method_name_prefix = !['polyline', 'point', 'image', 'focus'].include?(keyword) && (arg_options(args)[:fill] || arg_options(args)[:gradient]) ? 'fill_' : 'draw_'
|
60
|
+
"#{gc_instance_method_name_prefix}#{gradient}#{round}#{keyword}"
|
53
61
|
end
|
54
62
|
end
|
55
63
|
|
@@ -67,18 +75,26 @@ module Glimmer
|
|
67
75
|
post_add_content if property_block.nil?
|
68
76
|
end
|
69
77
|
|
78
|
+
def draw?
|
79
|
+
!fill?
|
80
|
+
end
|
81
|
+
|
70
82
|
def fill?
|
71
|
-
|
83
|
+
@options[:fill]
|
72
84
|
end
|
73
85
|
|
74
|
-
def
|
75
|
-
|
86
|
+
def gradient?
|
87
|
+
@options[:gradient]
|
88
|
+
end
|
89
|
+
|
90
|
+
def round?
|
91
|
+
@options[:round]
|
76
92
|
end
|
77
93
|
|
78
94
|
def post_add_content
|
79
95
|
event_handler = lambda do |event|
|
80
|
-
@properties['background'] = [
|
81
|
-
@properties['foreground'] = [
|
96
|
+
@properties['background'] = [@parent.background] if fill? && !@properties.keys.map(&:to_s).include?('background')
|
97
|
+
@properties['foreground'] = [@parent.foreground] if draw? && !@properties.keys.map(&:to_s).include?('foreground')
|
82
98
|
@properties.each do |property, args|
|
83
99
|
method_name = attribute_setter(property)
|
84
100
|
apply_property_arg_conversions(method_name, args)
|
@@ -86,6 +102,7 @@ module Glimmer
|
|
86
102
|
end
|
87
103
|
apply_shape_arg_conversions(@method_name, @args)
|
88
104
|
apply_shape_arg_defaults(@method_name, @args)
|
105
|
+
tolerate_shape_extra_args(@method_name, @args)
|
89
106
|
event.gc.send(@method_name, *@args)
|
90
107
|
end
|
91
108
|
if parent.respond_to?(:swt_display)
|
@@ -102,7 +119,7 @@ module Glimmer
|
|
102
119
|
args[0] = ColorProxy.new(args[0])
|
103
120
|
end
|
104
121
|
if the_java_method.parameter_types.first == Java::int.java_class
|
105
|
-
args[0] = SWTProxy
|
122
|
+
args[0] = SWTProxy.constant(args[0])
|
106
123
|
end
|
107
124
|
end
|
108
125
|
if args.first.is_a?(ColorProxy)
|
@@ -114,6 +131,18 @@ module Glimmer
|
|
114
131
|
if args.first.is_a?(FontProxy)
|
115
132
|
args[0] = args[0].swt_font
|
116
133
|
end
|
134
|
+
if ['setBackgroundPattern', 'setForegroundPattern'].include?(method_name.to_s)
|
135
|
+
args.each_with_index do |arg, i|
|
136
|
+
if arg.is_a?(Symbol) || arg.is_a?(String)
|
137
|
+
args[i] = ColorProxy.new(arg).swt_color
|
138
|
+
elsif arg.is_a?(ColorProxy)
|
139
|
+
args[i] = arg.swt_color
|
140
|
+
end
|
141
|
+
end
|
142
|
+
new_args = [DisplayProxy.instance.swt_display] + args
|
143
|
+
args[0] = org.eclipse.swt.graphics.Pattern.new(*new_args)
|
144
|
+
args[1..-1] = []
|
145
|
+
end
|
117
146
|
end
|
118
147
|
|
119
148
|
def apply_shape_arg_conversions(method_name, args)
|
@@ -126,18 +155,35 @@ module Glimmer
|
|
126
155
|
def apply_shape_arg_defaults(method_name, args)
|
127
156
|
if method_name.include?('round_rectangle') && args.size.between?(4, 5)
|
128
157
|
(6 - args.size).times {args << 60}
|
129
|
-
elsif method_name.include?('
|
158
|
+
elsif method_name.include?('rectangle') && gradient? && args.size == 4
|
159
|
+
args << true
|
160
|
+
elsif (method_name.include?('text') || method_name.include?('string')) && !@properties.keys.map(&:to_s).include?('background') && args.size == 3
|
130
161
|
args << true
|
131
162
|
end
|
163
|
+
if method_name.include?('image') && args.first.is_a?(String)
|
164
|
+
args[0] = ImageProxy.new(args[0])
|
165
|
+
end
|
166
|
+
if method_name.include?('image') && args.first.is_a?(ImageProxy)
|
167
|
+
args[0] = args[0].swt_image
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
# Tolerates shape extra args added by user by mistake
|
172
|
+
# (e.g. happens when switching from round rectangle to a standard one without removing all extra args)
|
173
|
+
def tolerate_shape_extra_args(method_name, args)
|
174
|
+
the_java_method_arg_count = org.eclipse.swt.graphics.GC.java_class.declared_instance_methods.select do |m|
|
175
|
+
m.name == method_name.camelcase(:lower)
|
176
|
+
end.map(&:parameter_types).map(&:size).max
|
177
|
+
if args.size > the_java_method_arg_count
|
178
|
+
args[the_java_method_arg_count..-1] = []
|
179
|
+
end
|
132
180
|
end
|
133
181
|
|
134
182
|
def has_attribute?(attribute_name, *args)
|
135
|
-
# TODO test that attribute getter responds too
|
136
183
|
self.class.gc_instance_methods.include?(attribute_setter(attribute_name))
|
137
184
|
end
|
138
185
|
|
139
186
|
def set_attribute(attribute_name, *args)
|
140
|
-
# TODO special treatment for color symbols
|
141
187
|
@properties[attribute_name] = args
|
142
188
|
end
|
143
189
|
|