rubygame 2.2.0-mswin32

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 (94) hide show
  1. data/CREDITS +60 -0
  2. data/LICENSE +504 -0
  3. data/NEWS +201 -0
  4. data/README +139 -0
  5. data/ROADMAP +43 -0
  6. data/Rakefile +409 -0
  7. data/doc/extended_readme.rdoc +49 -0
  8. data/doc/getting_started.rdoc +47 -0
  9. data/doc/macosx_install.rdoc +70 -0
  10. data/doc/windows_install.rdoc +127 -0
  11. data/ext/rubygame/MANIFEST +25 -0
  12. data/ext/rubygame/rubygame_core.so +0 -0
  13. data/ext/rubygame/rubygame_event.c +644 -0
  14. data/ext/rubygame/rubygame_event.h +48 -0
  15. data/ext/rubygame/rubygame_event.obj +0 -0
  16. data/ext/rubygame/rubygame_gfx.c +942 -0
  17. data/ext/rubygame/rubygame_gfx.h +101 -0
  18. data/ext/rubygame/rubygame_gfx.obj +0 -0
  19. data/ext/rubygame/rubygame_gfx.so +0 -0
  20. data/ext/rubygame/rubygame_gl.c +154 -0
  21. data/ext/rubygame/rubygame_gl.h +32 -0
  22. data/ext/rubygame/rubygame_gl.obj +0 -0
  23. data/ext/rubygame/rubygame_image.c +108 -0
  24. data/ext/rubygame/rubygame_image.h +41 -0
  25. data/ext/rubygame/rubygame_image.obj +0 -0
  26. data/ext/rubygame/rubygame_image.so +0 -0
  27. data/ext/rubygame/rubygame_joystick.c +247 -0
  28. data/ext/rubygame/rubygame_joystick.h +41 -0
  29. data/ext/rubygame/rubygame_joystick.obj +0 -0
  30. data/ext/rubygame/rubygame_main.c +155 -0
  31. data/ext/rubygame/rubygame_main.h +33 -0
  32. data/ext/rubygame/rubygame_main.obj +0 -0
  33. data/ext/rubygame/rubygame_mixer.c +764 -0
  34. data/ext/rubygame/rubygame_mixer.h +62 -0
  35. data/ext/rubygame/rubygame_mixer.obj +0 -0
  36. data/ext/rubygame/rubygame_mixer.so +0 -0
  37. data/ext/rubygame/rubygame_screen.c +448 -0
  38. data/ext/rubygame/rubygame_screen.h +43 -0
  39. data/ext/rubygame/rubygame_screen.obj +0 -0
  40. data/ext/rubygame/rubygame_shared.c +209 -0
  41. data/ext/rubygame/rubygame_shared.h +60 -0
  42. data/ext/rubygame/rubygame_shared.obj +0 -0
  43. data/ext/rubygame/rubygame_surface.c +1147 -0
  44. data/ext/rubygame/rubygame_surface.h +62 -0
  45. data/ext/rubygame/rubygame_surface.obj +0 -0
  46. data/ext/rubygame/rubygame_time.c +183 -0
  47. data/ext/rubygame/rubygame_time.h +32 -0
  48. data/ext/rubygame/rubygame_time.obj +0 -0
  49. data/ext/rubygame/rubygame_ttf.c +599 -0
  50. data/ext/rubygame/rubygame_ttf.h +69 -0
  51. data/ext/rubygame/rubygame_ttf.obj +0 -0
  52. data/ext/rubygame/rubygame_ttf.so +0 -0
  53. data/lib/rubygame.rb +41 -0
  54. data/lib/rubygame/MANIFEST +12 -0
  55. data/lib/rubygame/clock.rb +128 -0
  56. data/lib/rubygame/color.rb +79 -0
  57. data/lib/rubygame/color/models/base.rb +106 -0
  58. data/lib/rubygame/color/models/hsl.rb +153 -0
  59. data/lib/rubygame/color/models/hsv.rb +149 -0
  60. data/lib/rubygame/color/models/rgb.rb +78 -0
  61. data/lib/rubygame/color/palettes/css.rb +49 -0
  62. data/lib/rubygame/color/palettes/palette.rb +100 -0
  63. data/lib/rubygame/color/palettes/x11.rb +177 -0
  64. data/lib/rubygame/constants.rb +238 -0
  65. data/lib/rubygame/event.rb +313 -0
  66. data/lib/rubygame/ftor.rb +370 -0
  67. data/lib/rubygame/hotspot.rb +265 -0
  68. data/lib/rubygame/keyconstants.rb +237 -0
  69. data/lib/rubygame/mediabag.rb +94 -0
  70. data/lib/rubygame/queue.rb +288 -0
  71. data/lib/rubygame/rect.rb +612 -0
  72. data/lib/rubygame/sfont.rb +223 -0
  73. data/lib/rubygame/sprite.rb +511 -0
  74. data/samples/FreeSans.ttf +0 -0
  75. data/samples/GPL.txt +340 -0
  76. data/samples/README +40 -0
  77. data/samples/chimp.bmp +0 -0
  78. data/samples/chimp.rb +313 -0
  79. data/samples/demo_gl.rb +151 -0
  80. data/samples/demo_gl_tex.rb +197 -0
  81. data/samples/demo_music.rb +75 -0
  82. data/samples/demo_rubygame.rb +284 -0
  83. data/samples/demo_sfont.rb +52 -0
  84. data/samples/demo_ttf.rb +193 -0
  85. data/samples/demo_utf8.rb +53 -0
  86. data/samples/fist.bmp +0 -0
  87. data/samples/load_and_blit.rb +22 -0
  88. data/samples/panda.png +0 -0
  89. data/samples/punch.wav +0 -0
  90. data/samples/ruby.png +0 -0
  91. data/samples/song.ogg +0 -0
  92. data/samples/term16.png +0 -0
  93. data/samples/whiff.wav +0 -0
  94. metadata +152 -0
@@ -0,0 +1,370 @@
1
+ # Ftor ("Fake vecTOR"), a vector-like class for 2D position/movement.
2
+ #--
3
+ # Rubygame -- Ruby code and bindings to SDL to facilitate game creation
4
+ # Copyright (C) 2004-2007 John Croisant
5
+ #
6
+ # This library is free software; you can redistribute it and/or
7
+ # modify it under the terms of the GNU Lesser General Public
8
+ # License as published by the Free Software Foundation; either
9
+ # version 2.1 of the License, or (at your option) any later version.
10
+ #
11
+ # This library is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14
+ # Lesser General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Lesser General Public
17
+ # License along with this library; if not, write to the Free Software
18
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
+ #++
20
+
21
+ module Rubygame
22
+
23
+ # *NOTE*: you must require 'rubygame/ftor' manually to gain access to
24
+ # Rubygame::Ftor. It is not imported with Rubygame by default!
25
+ #
26
+ # Ftor ("Fake vecTOR"), a vector-like class for 2D position/movement.
27
+ #
28
+ # (NB: See #angle for an important note about why angles appear to be the
29
+ # opposite of what you may expect.)
30
+ #
31
+ # Ftor is useful for storing 2D coordinates (x,y) as well as
32
+ # vector quantities such as velocity and acceleration (representationally,
33
+ # points and vectors are equivalent.) Although Ftors are always represented
34
+ # internally as Cartesian coordinates (x, y), it is possible to deal with an
35
+ # Ftor as polar coordinates (#angle, #magnitude) instead.
36
+ # See #new_am and #set_am!, for example.
37
+ #
38
+ # Ftor is a "fake" vector because it has certain convenient properties which
39
+ # differ from "true" vectors (i.e. vectors in a strict mathematical sense).
40
+ #
41
+ # Unlike vectors, Ftors may be multiplied or divided to another Ftor. This is
42
+ # equivalent to multiplying or dividing each component by the corresponding
43
+ # component in the second Ftor. If you like, you can think of this feature as
44
+ # scaling each component of the Ftor by a separate factor:
45
+ #
46
+ # Ftor(a,b) * Ftor(c,d) = Ftor(a*c, b*d)
47
+ #
48
+ # Of course, Ftors also have the usual vector behavior for addition/subraction
49
+ # between two Ftors, and multiplication/division of an Ftor by a scalar:
50
+ #
51
+ # Ftor(a,b) + Ftor(c,d) = Ftor(a+c, b+d)
52
+ # Ftor(a,b) * n = Ftor(a*n, b*n)
53
+ #
54
+ # Additionally, Ftor contains functions for manipulating itself.
55
+ # You can both get and set such properties as #angle, #magnitude, #unit,
56
+ # and #normal, and the Ftor will change in-place as needed. For example,
57
+ # if you set #angle=, the vector will change to have the new angle,
58
+ # but keeps the same magnitude as before.
59
+ #
60
+ # Ftor attempts to save processing time (at the expense of memory) by
61
+ # storing secondary properties (angle, magnitude, etc.) whenever they are
62
+ # calculated,so that they need not be calculated repeatedly. If the vector
63
+ # changes, the properties will be calculated again the next time they
64
+ # are needed.
65
+ # (In future versions, it may be possible to disable this feature for
66
+ # certain Ftors, for example if they will change very often, to save memory.)
67
+ #
68
+ class Ftor
69
+ PI = Math::PI
70
+ HALF_PI = PI*0.5
71
+ THREE_HALF_PI = PI*1.5
72
+ TWO_PI = PI*2
73
+
74
+ # Create a new Ftor by specifying its x and y components. See also #new_am
75
+ # and #new_from_to.
76
+ def initialize(x,y)
77
+ @x, @y = x, y
78
+ end
79
+
80
+ # Create a new Ftor by specifying its #angle (in radians) and #magnitude.
81
+ # See also #new.
82
+ def self.new_am(a,m)
83
+ v = Ftor.new(1,0)
84
+ v.a, v.m = a, m
85
+ return v
86
+ end
87
+
88
+ # Returns a new Ftor which represents the difference in position of two
89
+ # points +p1+ and +p2+. (+p1+ and +p2+ can be Ftors, size-2 Arrays, or
90
+ # anything else which has two numerical components and responds to #[].)
91
+ #
92
+ # In other words, assuming +v+ is the Ftor returned by this function:
93
+ # p1 + v = p2
94
+ def self.new_from_to(p1,p2)
95
+ return self.class.new(p2[0]-p1[0],p2[1]-p1[1])
96
+ end
97
+
98
+ attr_reader :x # The x component of the Ftor.
99
+ # Set the x component of the Ftor.
100
+ def x=(value)
101
+ @x = value
102
+ _clear()
103
+ end
104
+
105
+ attr_reader :y # The y component of the Ftor.
106
+ # Set the y component of the Ftor.
107
+ def y=(value)
108
+ @y = value
109
+ _clear()
110
+ end
111
+
112
+ # Modify the x and y components of the Ftor in-place
113
+ def set!(x,y)
114
+ @x, @y = x,y
115
+ _clear()
116
+ end
117
+
118
+ # Modify the #angle (in radians) and #magnitude of the Ftor in-place
119
+ def set_am!(a,m)
120
+ self.angle, self.magnitude = a, m
121
+ end
122
+
123
+ # Same as #to_s, but this Ftor's #object_id is also displayed.
124
+ def inspect
125
+ "#<#{self.class}:#{object_id}: %f, %f>"%[@x,@y]
126
+ end
127
+
128
+ # Display this Ftor in the format: "#<Ftor: [x, y]>". x and y are displayed
129
+ # as floats at full precision. See also #pp.
130
+ def to_s
131
+ "#<#{self.class}: [%f, %f]>"%[@x,@y]
132
+ end
133
+
134
+ # "Pretty print". Same as #to_s, but components are displayed as rounded
135
+ # floats to 3 decimal places, for easy viewing.
136
+ def pretty
137
+ "#<#{self.class}: [%0.3f, %0.3f]>"%[@x,@y]
138
+ end
139
+
140
+ # Same as #to_s_am, but this Ftor's #object_id is also displayed.
141
+ def inspect_am
142
+ "#<#{self.class}:AM:#{object_id}: %f, %f>"%[angle(),magnitude()]
143
+ end
144
+
145
+ # Display this Ftor in the format: "#<Ftor:AM: [angle, magnitude]>".
146
+ # angle and magnitude are displayed as floats at full precision.
147
+ # See also #to_s and #pp_am.
148
+ def to_s_am
149
+ "#<#{self.class}:AM: [%f, %f]>"%[angle(),magnitude()]
150
+ end
151
+
152
+ # "Pretty print" with angle and magnitude.
153
+ # Same as #to_s_am, but components are displayed as rounded floats to 3
154
+ # decimal places, for easy viewing.
155
+ def pretty_am
156
+ "#<#{self.class}:AM: [%0.3f, %0.3f]>"%[angle(),magnitude()]
157
+ end
158
+
159
+ # Returns an Array of this Ftor's components, [x,y].
160
+ def to_a
161
+ [@x,@y]
162
+ end
163
+ alias :to_ary :to_a
164
+
165
+ # Return the +i+th component of this Ftor, as if it were the Array
166
+ # returned by #to_a.
167
+ def [](i)
168
+ [@x,@y][i]
169
+ end
170
+
171
+ # True if this Ftor is equal to +other+, when both have been converted to
172
+ # Arrays via #to_a. In other words, a component-by-component equality check.
173
+ def ==(other)
174
+ to_a() == other.to_a
175
+ end
176
+
177
+ # The reverse of this Ftor. I.e., all components are negated. See also
178
+ # #reverse!.
179
+ def -@
180
+ self.class.new(-@x,-@y)
181
+ end
182
+
183
+ # Like #-@, but reverses this Ftor in-place.
184
+ def reverse!
185
+ set!(-@x,-@y)
186
+ end
187
+
188
+ # Perform vector addition with this Ftor and +other+, adding them on a
189
+ # component-by-component basis, like so:
190
+ # Ftor(a,b) + Ftor(c,d) = Ftor(a+c, b+d)
191
+ def +(other)
192
+ return self.class.new(@x+other[0],@y+other[1])
193
+ end
194
+
195
+ # Like #+, but performs subtraction instead of addition.
196
+ def -(other)
197
+ return self.class.new(@x-other[0],@y-other[1])
198
+ end
199
+
200
+ # Perform multiplication of this Ftor by the scalar +other+, like so:
201
+ # Ftor(a,b) * n = Ftor(a*n, b*n)
202
+ #
203
+ # However, if this causes TypeError, attempt to extract indices 0 and 1
204
+ # with +other+'s #[] operator, and multiply them into the corresponding
205
+ # components of this Ftor, like so:
206
+ # Ftor(a,b) * Ftor(c,d) = Ftor(a*c, b*d)
207
+ # Ftor(a,b) * [c,d] = Ftor(a*c, b*d)
208
+ def *(other)
209
+ return self.class.new(@x*other,@y*other)
210
+ rescue TypeError
211
+ return self.class.new(@x*other[0],@y*other[1])
212
+ end
213
+
214
+ # Like #*, but performs division instead of multiplication.
215
+ def /(other)
216
+ x, y = @x.to_f, @y.to_f
217
+ return self.class.new(x/other,y/other)
218
+ rescue TypeError
219
+ return self.class.new(x/other[0],y/other[1])
220
+ end
221
+
222
+ # Return the angle (radians) this Ftor forms with the positive X axis.
223
+ # This is the same as the Ftor's angle in a polar coordinate system.
224
+ #
225
+ # *IMPORTANT*: Because the positive Y axis on the Rubygame::Screen points
226
+ # *downwards*, an angle in the range 0..PI will appear to point *downwards*,
227
+ # rather than upwards!
228
+ # This also means that positive rotation will appear *clockwise*, and
229
+ # negative rotation will appear *counterclockwise*!
230
+ # This is the opposite of what you would expect in geometry class!
231
+ def angle
232
+ @angle or @angle = Math.atan2(@y,@x)
233
+ end
234
+
235
+ # Set the angle (radians) of this Ftor from the positive X axis.
236
+ # Magnitude is preserved.
237
+ def angle=(a)
238
+ m = magnitude()
239
+ set!( Math.cos(a)*m, Math.sin(a)*m )
240
+ end
241
+
242
+ alias :a :angle
243
+ alias :a= :angle= ;
244
+
245
+ # Returns the magnitude of the Ftor, i.e. its length from tail to head.
246
+ # This is the same as the Ftor's magnitude in a polar coordinate system.
247
+ def magnitude
248
+ @magnitude or @magnitude = Math.hypot(@x,@y)
249
+ end
250
+
251
+ # Modifies the #magnitude of the Ftor, preserving its #angle.
252
+ #
253
+ # In other words, the Ftor will point in the same direction, but it will
254
+ # be a different length from tail to head.
255
+ def magnitude=(m)
256
+ new = unit() * m
257
+ set!(new.x, new.y)
258
+ end
259
+
260
+ alias :m :magnitude
261
+ alias :m= :magnitude= ;
262
+
263
+ # Return a new unit Ftor which is perpendicular to this Ftor (rotated by
264
+ # pi/2 radians, to be specific).
265
+ def normal
266
+ @normal or @normal = unit().rotate(HALF_PI)
267
+ end
268
+
269
+ # Rotate this Ftor in-place, so that it is perpendicular to +other+.
270
+ # This Ftor will be at an angle of -pi/2 to +other+.
271
+ def normal=(other)
272
+ set!( *(self.class.new(*other).unit().rotate(-HALF_PI) * magnitude()) )
273
+ end
274
+
275
+ alias :n :normal
276
+ alias :n= :normal= ;
277
+
278
+ # Return the unit vector of the Ftor, i.e. an Ftor with the same direction,
279
+ # but a #magnitude of 1. (This is sometimes called a "normalized" vector,
280
+ # not to be confused with a vector's #normal.)
281
+ def unit
282
+ m = magnitude().to_f
283
+ @unit or @unit = Ftor.new(@x/m, @y/m)
284
+ end
285
+
286
+ # Rotates this Ftor in-place, so that its #unit vector matches the unit
287
+ # vector of the given Ftor.
288
+ #
289
+ # In other words, changes the #angle of this Ftor to be the same as the angle
290
+ # of the given Ftor, but this Ftor's #magnitude does not change.
291
+ #--
292
+ # TODO: investigate efficiency of using `self.angle = other.angle` instead
293
+ #++
294
+ def unit=(other)
295
+ set!( *(self.class.new(*other).unit() * magnitude()) )
296
+ end
297
+
298
+ alias :u :unit
299
+ alias :u= :unit=
300
+ alias :align! :unit=;
301
+
302
+ # Return the dot product (aka inner product) of this Ftor and +other+.
303
+ # The dot product of two vectors +v1+ and +v2+ is:
304
+ # v1.x * v2.x + v1.y * v2.y
305
+ def dot(other)
306
+ @x*other[0] + @y*other[1]
307
+ end
308
+
309
+ # Return the #dot product of #unit vectors of this Ftor and +other+.
310
+ def udot(other)
311
+ unit().dot(self.class.new(*other).unit)
312
+ end
313
+
314
+ #Return the difference in angles (radians) between this Ftor and +other+.
315
+ def angle_with(other)
316
+ Math.acos( self.udot(other) )
317
+ end
318
+
319
+ # Rotate this Ftor in-place by +angle+ (radians). This is the same as
320
+ # adding +angle+ to this Ftor's #angle.
321
+ #
322
+ #--
323
+ # , with one important difference:
324
+ # This method will be much more efficient when rotating at a right angle,
325
+ # i.e.rotating by any multiple of PI/2 radians from -2*PI to 2*PI radians.
326
+ #
327
+ # For convenience, and to ensure exactitude, several numerical constants
328
+ # have been defined for multiples of PI/2:
329
+ # * Ftor::PI:: (same as Math::PI)
330
+ # * Ftor::HALF_PI:: PI * 0.5 (or PI/2)
331
+ # * Ftor::THREE_HALF_PI:: PI * 1.5 (or 3*PI/2)
332
+ # * Ftor::TWO_PI:: PI * 2
333
+ #++
334
+ #
335
+ # *IMPORTANT*: Positive rotation will appear *clockwise*, and negative
336
+ # rotation will appear *counterclockwise*! See #angle for the reason.
337
+ def rotate!(angle)
338
+ # case(angle)
339
+ # when HALF_PI, -THREE_HALF_PI
340
+ # self.set!(@y,-@x)
341
+ # when THREE_HALF_PI, -HALF_PI
342
+ # self.set!(-@y,@x)
343
+ # when PI, -PI
344
+ # self.set!(@y,-@x)
345
+ # when 0, TWO_PI, -TWO_PI
346
+ # self.set!(@y,-@x)
347
+ # else
348
+ self.a += angle
349
+ # end
350
+ return self
351
+ end
352
+
353
+ # Like #rotate!, but returns a duplicate instead of rotating this Ftor
354
+ # in-place.
355
+ def rotate(radians)
356
+ self.dup.rotate!(radians)
357
+ end
358
+
359
+ # Clears stored values for #angle, #magnitude, #normal, and #unit,
360
+ # so that they will be recalculated the next time they are needed.
361
+ # Intended for internal use, but might be useful in other situations.
362
+ def _clear
363
+ @angle = nil
364
+ @magnitude = nil
365
+ @normal = nil
366
+ @unit = nil
367
+ return self
368
+ end
369
+ end
370
+ end
@@ -0,0 +1,265 @@
1
+ # Hotspot, a mixin module to extend an object with custom, named, relative
2
+ # position offsets.
3
+ #--
4
+ # Rubygame -- Ruby code and bindings to SDL to facilitate game creation
5
+ # Copyright (C) 2004-2007 John Croisant
6
+ #
7
+ # This library is free software; you can redistribute it and/or
8
+ # modify it under the terms of the GNU Lesser General Public
9
+ # License as published by the Free Software Foundation; either
10
+ # version 2.1 of the License, or (at your option) any later version.
11
+ #
12
+ # This library is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15
+ # Lesser General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU Lesser General Public
18
+ # License along with this library; if not, write to the Free Software
19
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20
+ #++
21
+
22
+ module Rubygame
23
+
24
+ # *NOTE*: you must require 'rubygame/hotspot' manually to gain access to
25
+ # Rubygame::Hotspot. It is not imported with Rubygame by default!
26
+ #
27
+ # Hotspot is a mixin module to extend an object with "hotspots": custom,
28
+ # named, relative position offsets. Hotspots can be defined relative to the
29
+ # origin, to another hotspot, or to the results of a method (via a
30
+ # 'smart' hotspot).
31
+ #
32
+ # There are two types of hotspots, simple and 'smart'.
33
+ #
34
+ # Simple hotspots are an Array of three values, an x offset, a y offset,
35
+ # and the label of another hotspot relative to which this hotspot is defined.
36
+ # If the last argument is omitted or nil, the hotspot is defined relative
37
+ # to the true origin (i.e. (0,0), the top-left corner of the Screen).
38
+ # See #new_hotspot.
39
+ #
40
+ # Smart hotspots, or 'smartspots' for short, act as a proxy to the object's
41
+ # methods. Each time a smartspot is evaluated, it calls the object's method
42
+ # of the same name as the smartspot, and uses the results of the method as
43
+ # x and y offsets. Therefore, smartspots only work for methods which:
44
+ # 1. take no arguments
45
+ # 2. return an Array with 2 Numeric values (or something else that responds
46
+ # to #[]
47
+ #
48
+ # By adding a smartspot to a Rect, for example, you could define simple
49
+ # hotspots relative to its Rect#center; then, even if the Rect moves or
50
+ # changes size, the smartspot will always to evaluate to its true center.
51
+ # See #new_smartspot.
52
+ #
53
+ #--
54
+ # ((Old documentation/brainstorming))
55
+ # As an example, consider an object which represents a person's face: eyes,
56
+ # ears, nose, mouth, etc. You might make a face and define several hotspots
57
+ # like so:
58
+ #
59
+ # myface = Face.new() # Create a new face, with no hotspots.
60
+ # myface.extend(Hotspot) # Extend myface with hotspot ability.
61
+ # myface.new_hotspot \ # Define some new hotspots: ...
62
+ # :nose => [10,5, :center], # the nose, relative to face's center,
63
+ # :left_eye => [-5,-2, :nose], # the left eye, left and above the nose,
64
+ # :left_brow => [0,-5, :left_eye], # the left eye-brow, above the left eye.
65
+ #
66
+ # Please note that +:center+ is a "virtual" hotspot. When the coordinates of
67
+ # +:center+ are requested, +myface+'s #center method is called* and the
68
+ # results used as the coordinates. (* Technically, +myface+ is sent
69
+ # +:center+, which is not exactly the same as calling #center.)
70
+ #
71
+ # Now, suppose we want to find out where :left_brow is, in absolute
72
+ # coordinates (i.e. relative to the origin). We can do this, even if +myface+
73
+ # has moved, or the hotspots have been changed:
74
+ #
75
+ # myface.left_brow # => [5,-2]
76
+ # myface.move(20,-12) # moves the face right and up
77
+ # myface.left_brow # => [25,-14]
78
+ # myface.new_hotspot \
79
+ # :left_eye => [-10,3] # redefine the left_eye hotspot
80
+ # myface.left_brow # => [20,-9]
81
+ #
82
+ # Where do [5,-2], [25,-24], and [20,-9] come from? They are the vector sums
83
+ # of each hotspot in the chain: left_brow, left_eye, nose, and center. See
84
+ # #hotspot for more information.
85
+ #++
86
+ #
87
+ module Hotspot
88
+ # :call-seq: def_hotspot label => [x,y,parent]
89
+ #
90
+ # Define +label+ as a simple hotspot, a custom reference coordinate
91
+ # point +x+ pixels to the right and +y+ pixels below the hotspot whose
92
+ # label is +parent+.
93
+ # You may omit +parent+, in which case the hotspot will evaluate relative
94
+ # to the origin, i.e. the top-left corner of the Screen.
95
+ #
96
+ # See also #def_smartspot to create a 'smart hotspot'.
97
+ #
98
+ # +label+ must be usable as a key in a Hash table. Additionally, if you
99
+ # want <code>myobject.{label}</code> to work like
100
+ # <code>myobject.hotspot({label})</code>, +label+ must be a :symbol.
101
+ #
102
+ # *IMPORTANT*: Do NOT create circular hotspot chains (e.g. a -> b -> a).
103
+ # Doing so will raise SystemStackError when #hotspot is asked to evaluate
104
+ # any hotspot in that chain. Hotspots are not yet smart enough to detect
105
+ # circular chains.
106
+ #
107
+ # Hotspots can be defined in any order, as long as you define all the
108
+ # hotspots in a chain before that chain is evaluated with #hotspot.
109
+ #
110
+ # You may define multiple hotspots simultaneously by separating the
111
+ # definitions by commas. For example:
112
+ #
113
+ # def_hotspot label => [x,y,parent], label2 => [x,y,parent]
114
+ #
115
+ # Users of the Rake library will recognize this style of syntax.
116
+ # It is simply constructing a Hash object and passing it as the
117
+ # only argument to #new_hotspot. The above code is equivalent to:
118
+ #
119
+ # def_hotspot( { label => [x,y,parent], label2 => [x,y,parent] } )
120
+ def def_hotspot(dfn)
121
+ @hotspots.update(dfn)
122
+ rescue NoMethodError => e
123
+ unless defined? @hotspots
124
+ @hotspots = Hash.new
125
+ retry
126
+ else raise e
127
+ end
128
+ end
129
+
130
+ # Remove all simple hotspots whose label is included in +*labels+.
131
+ def undef_hotspot(*labels)
132
+ labels.flatten.each do |l|
133
+ @hotspots.delete(l)
134
+ end
135
+ end
136
+
137
+ # True if +label+ has been defined as a simple hotspot.
138
+ def defined_hotspot?(label)
139
+ @hotspots.include? label
140
+ rescue NoMethodError => e
141
+ unless defined? @hotspots
142
+ false
143
+ else raise e
144
+ end
145
+ end
146
+
147
+ # :call-seq: hotspot(label)
148
+ #
149
+ # Returns the absolute coordinates represented by the hotspot +label+.
150
+ # Will return nil if the hotspot (or one of its ancestors) does not exist.
151
+ #
152
+ # This method will recursively evaluate the hotspot, it's parent hotspot
153
+ # (if any), and so on, until a parent-less hotspot or a smartspot is found.
154
+ #
155
+ # (*NOTE*: this means that a circular chains (e.g. a -> b -> a)
156
+ # will keep going around and around until the ruby interpreter
157
+ # raises SystemStackError!)
158
+ #
159
+ # The final value returned by this method will be the vector component sum
160
+ # of all the hotspots in the chain. For example, if you have this chain:
161
+ #
162
+ # :a => [1, 2, :b]
163
+ # :b => [4, 8, :c]
164
+ # :c => [16,32]
165
+ #
166
+ # the value returned for +:a+ would be [21,42], i.e. [1+4+16, 2+8+32]
167
+ #--
168
+ # The x and y arguments are used for recursive accumulation, although
169
+ # I suppose you could use them to create a temporary offset by giving
170
+ # something besides zero when you look up a hotspot.
171
+ #++
172
+ def hotspot(label,x=0,y=0)
173
+ a = @hotspots[label]
174
+ if a[2].nil? # has no parent
175
+ [x+a[0],y+a[1]]
176
+ else # has a parent
177
+ hotspot(a[2],x+a[0],y+a[1])
178
+ end
179
+ rescue NoMethodError => e
180
+ if not(defined? @hotspots)
181
+ return nil
182
+ elsif a.nil?
183
+ smartspot(label,x,y)
184
+ else raise e
185
+ end
186
+ end
187
+
188
+ # Define each label in +*labels+ as a smartspot ('smart hotspot').
189
+ #
190
+ # To prevent outside objects from abusing hotspots to call arbitrary
191
+ # methods, a smartspot must be defined for each method before it can be
192
+ # used as a parent to a hotspot.
193
+ #
194
+ # The label must be a :symbol, and it must be identical to the name of the
195
+ # method to be called.
196
+ def def_smartspot(*labels)
197
+ @smartspots += labels.flatten
198
+ @smartspots.uniq!
199
+ rescue NoMethodError => e
200
+ unless defined? @smartspots
201
+ @smartspots = Array.new
202
+ retry
203
+ else raise e
204
+ end
205
+ end
206
+
207
+ # Remove all smartspots whose label is included in +*labels+.
208
+ def undef_smartspot(*labels)
209
+ @smartspots -= labels.flatten
210
+ end
211
+
212
+ # True if +label+ has been defined as a smartspot.
213
+ def defined_smartspot?(label)
214
+ @smartspots.include? label
215
+ rescue NoMethodError => e
216
+ unless defined? @smartspots
217
+ false
218
+ else raise e
219
+ end
220
+ end
221
+
222
+ # :call-seq: smartspot(label)
223
+ #
224
+ # Evaluate the smartspot +label+, calling the method of the same name as
225
+ # +label+. Will return nil if no such smartspot has been defined.
226
+ #--
227
+ # The x and y arguments are used for recursive accumulation.
228
+ #++
229
+ def smartspot(label,x=0,y=0)
230
+ if @smartspots.include? label
231
+ a = self.send(label)
232
+ [x+a[0],y+a[1]]
233
+ else
234
+ nil
235
+ end
236
+ rescue NoMethodError => e
237
+ unless defined? @smartspots
238
+ nil
239
+ else raise e
240
+ end
241
+ end
242
+
243
+ #--
244
+ #
245
+ # TODO:
246
+ # Methods for changing the position of the object indirectly,
247
+ # by saying where the hotspot should be.
248
+ # You could do this: my_object.foot = [5,3], and if foot was defined
249
+ # relative to, say, #center, it would set center = [5-x_offset, 3-y_offset]
250
+ #
251
+ # It should be recursive to work with chains of hotspots too.
252
+ #
253
+ #++
254
+
255
+ alias :old_method_missing :method_missing
256
+
257
+ def method_missing(symbol,*args)
258
+ if have_hotspot?(symbol)
259
+ hotspot(symbol)
260
+ else old_method_missing(symbol,*args)
261
+ end
262
+ end
263
+
264
+ end
265
+ end