rubygame 2.2.0-mswin32

Sign up to get free protection for your applications and to get access to all the features.
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