glimr 0.1.0

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 (90) hide show
  1. data/lib/glimr.rb +2 -0
  2. data/lib/glimr/configurable.rb +37 -0
  3. data/lib/glimr/default_theme/button_bg.png +0 -0
  4. data/lib/glimr/default_theme/button_bg_down.png +0 -0
  5. data/lib/glimr/default_theme/button_cover.png +0 -0
  6. data/lib/glimr/default_theme/button_cover_down.png +0 -0
  7. data/lib/glimr/default_theme/button_focus.png +0 -0
  8. data/lib/glimr/default_theme/checkbox_bg.png +0 -0
  9. data/lib/glimr/default_theme/checkbox_checked_bg.png +0 -0
  10. data/lib/glimr/default_theme/font.ttf +0 -0
  11. data/lib/glimr/default_theme/hscroller_bg.png +0 -0
  12. data/lib/glimr/default_theme/radiobutton_bg.png +0 -0
  13. data/lib/glimr/default_theme/radiobutton_checked_bg.png +0 -0
  14. data/lib/glimr/default_theme/resizer_down.png +0 -0
  15. data/lib/glimr/default_theme/resizer_up.png +0 -0
  16. data/lib/glimr/default_theme/scroll_down_down.png +0 -0
  17. data/lib/glimr/default_theme/scroll_down_up.png +0 -0
  18. data/lib/glimr/default_theme/scroll_hknob_down.png +0 -0
  19. data/lib/glimr/default_theme/scroll_hknob_up.png +0 -0
  20. data/lib/glimr/default_theme/scroll_left_down.png +0 -0
  21. data/lib/glimr/default_theme/scroll_left_up.png +0 -0
  22. data/lib/glimr/default_theme/scroll_right_down.png +0 -0
  23. data/lib/glimr/default_theme/scroll_right_up.png +0 -0
  24. data/lib/glimr/default_theme/scroll_up_down.png +0 -0
  25. data/lib/glimr/default_theme/scroll_up_up.png +0 -0
  26. data/lib/glimr/default_theme/scroll_vknob_down.png +0 -0
  27. data/lib/glimr/default_theme/scroll_vknob_up.png +0 -0
  28. data/lib/glimr/default_theme/text_cursor.png +0 -0
  29. data/lib/glimr/default_theme/text_cursor_insert.png +0 -0
  30. data/lib/glimr/default_theme/text_input_bg.png +0 -0
  31. data/lib/glimr/default_theme/vscroller_bg.png +0 -0
  32. data/lib/glimr/event.rb +41 -0
  33. data/lib/glimr/eventlistener.rb +209 -0
  34. data/lib/glimr/layoutable.rb +520 -0
  35. data/lib/glimr/renderer.rb +2 -0
  36. data/lib/glimr/renderer/camera.rb +63 -0
  37. data/lib/glimr/renderer/geometry.rb +194 -0
  38. data/lib/glimr/renderer/glutwindow.rb +387 -0
  39. data/lib/glimr/renderer/light.rb +43 -0
  40. data/lib/glimr/renderer/material.rb +66 -0
  41. data/lib/glimr/renderer/model.rb +103 -0
  42. data/lib/glimr/renderer/orthoprojection.rb +21 -0
  43. data/lib/glimr/renderer/raw.rb +34 -0
  44. data/lib/glimr/renderer/sceneobject.rb +279 -0
  45. data/lib/glimr/renderer/shader.rb +14 -0
  46. data/lib/glimr/renderer/texture.rb +280 -0
  47. data/lib/glimr/renderer/transform.rb +322 -0
  48. data/lib/glimr/renderer/viewport.rb +349 -0
  49. data/lib/glimr/renderer_core.rb +10 -0
  50. data/lib/glimr/util.rb +247 -0
  51. data/lib/glimr/widget.rb +87 -0
  52. data/lib/glimr/widgets.rb +37 -0
  53. data/lib/glimr/widgets/button.rb +277 -0
  54. data/lib/glimr/widgets/checkbox.rb +82 -0
  55. data/lib/glimr/widgets/container.rb +84 -0
  56. data/lib/glimr/widgets/image.rb +82 -0
  57. data/lib/glimr/widgets/label.rb +91 -0
  58. data/lib/glimr/widgets/layout.rb +227 -0
  59. data/lib/glimr/widgets/list.rb +28 -0
  60. data/lib/glimr/widgets/radiogroup.rb +118 -0
  61. data/lib/glimr/widgets/resizer.rb +31 -0
  62. data/lib/glimr/widgets/scrollable_container.rb +67 -0
  63. data/lib/glimr/widgets/scrollbar.rb +496 -0
  64. data/lib/glimr/widgets/stretchable_image.rb +135 -0
  65. data/lib/glimr/widgets/text_editor.rb +349 -0
  66. data/tests/assets/datatowers_crop.jpg +0 -0
  67. data/tests/assets/download_progress_meter.png +0 -0
  68. data/tests/assets/metalwing2.png +0 -0
  69. data/tests/assets/redhairgreeneyes3.jpg +0 -0
  70. data/tests/demo_apps/spinning_ruby.rb +37 -0
  71. data/tests/integration_tests/run_all.rb +8 -0
  72. data/tests/integration_tests/test_button.rb +22 -0
  73. data/tests/integration_tests/test_checkbox.rb +21 -0
  74. data/tests/integration_tests/test_container.rb +22 -0
  75. data/tests/integration_tests/test_label.rb +12 -0
  76. data/tests/integration_tests/test_layout.rb +43 -0
  77. data/tests/integration_tests/test_radiogroup.rb +16 -0
  78. data/tests/integration_tests/test_renderer.rb +44 -0
  79. data/tests/integration_tests/test_renderer2.rb +36 -0
  80. data/tests/integration_tests/test_scrollable_container.rb +34 -0
  81. data/tests/integration_tests/test_scrollbar.rb +20 -0
  82. data/tests/integration_tests/test_stretchable_image.rb +34 -0
  83. data/tests/integration_tests/test_text_input.rb +20 -0
  84. data/tests/integration_tests/test_zsort.rb +18 -0
  85. data/tests/unit_tests/test_button.rb +93 -0
  86. data/tests/unit_tests/test_checkbox.rb +35 -0
  87. data/tests/unit_tests/test_label.rb +36 -0
  88. data/tests/unit_tests/test_layout.rb +229 -0
  89. data/tests/unit_tests/test_widget.rb +3 -0
  90. metadata +139 -0
@@ -0,0 +1,2 @@
1
+ require 'glimr/renderer_core'
2
+ require 'glimr/renderer/glutwindow' unless $glimr_no_glut
@@ -0,0 +1,63 @@
1
+ require 'glimr/renderer/transform'
2
+
3
+ module GlimR
4
+
5
+ # Camera is a special Transform that applies to both the
6
+ # OpenGL projection matrix and modelview matrix.
7
+ #
8
+ class Camera < Transform
9
+
10
+ touching_accessor :looking_at, :up
11
+ attr_accessor :perspective, :fov, :near, :far
12
+
13
+ def default_config
14
+ super.merge(
15
+ :position => [0,0,5000],
16
+ :looking_at => [0,0,0],
17
+ :up => [0,1,0],
18
+ :near => 1,
19
+ :far => 10000,
20
+ :fov => 60,
21
+ :perspective => false
22
+ )
23
+ end
24
+
25
+ def initialize(*a, &b)
26
+ super
27
+ @changed = true
28
+ end
29
+
30
+ # Sets up the camera projection to the projection matrix
31
+ def apply
32
+ MatrixMode(PROJECTION)
33
+ LoadIdentity()
34
+ if perspective
35
+ top = Math.tan(fov*Math::PI/360) * near
36
+ bottom = -top
37
+ left = viewport.aspect * bottom
38
+ right = viewport.aspect * top
39
+ Frustum(left,right, bottom,top, near,far)
40
+ else
41
+ Ortho(viewport.x,viewport.width, viewport.y,viewport.height, near,far)
42
+ Scale(1,-1,1)
43
+ Translate(0,-viewport.height,0)
44
+ end
45
+ MatrixMode(MODELVIEW)
46
+ LoadIdentity()
47
+ super
48
+ end
49
+
50
+ # Uses GLU.LookAt to position the camera.
51
+ def gl_transform
52
+ GLU.LookAt(*position+looking_at+up)
53
+ end
54
+
55
+ def absolute_transform
56
+ collapse! unless @collapsed_matrix
57
+ @collapsed_matrix
58
+ end
59
+
60
+ end
61
+
62
+ end
63
+
@@ -0,0 +1,194 @@
1
+ require 'glimr/renderer/sceneobject'
2
+
3
+
4
+ module GlimR
5
+
6
+
7
+ # A Geometry object abstracts vertex arrays.
8
+ # It may contain a normal array, texcoord array, color array and vertex array.
9
+ #
10
+ # The type of a Geometry is the corresponding GL VertexPointer type.
11
+ #
12
+ # A Geometry is required to actually draw something to the screen.
13
+ #
14
+ class Geometry < SceneObject
15
+
16
+ attr_accessor :normals, :texcoords, :colors, :vertices, :type, :vertice_count
17
+
18
+ def default_config
19
+ super.merge(
20
+ :type => GL::QUADS
21
+ )
22
+ end
23
+
24
+ # Returns the smallest x coordinate in the vertices.
25
+ def min_x
26
+ x_coords.min
27
+ end
28
+
29
+ # Returns the largest x coordinate in the vertices.
30
+ def max_x
31
+ x_coords.max
32
+ end
33
+
34
+ # Returns the smallest y coordinate in the vertices.
35
+ def min_y
36
+ y_coords.min
37
+ end
38
+
39
+ # Returns the largest y coordinate in the vertices.
40
+ def max_y
41
+ y_coords.max
42
+ end
43
+
44
+ # Returns the smallest z coordinate in the vertices.
45
+ def min_z
46
+ z_coords.min
47
+ end
48
+
49
+ # Returns the largest z coordinate in the vertices.
50
+ def max_z
51
+ z_coords.max
52
+ end
53
+
54
+ # Returns an array of the x coordinates in the vertices.
55
+ def x_coords
56
+ coords 0
57
+ end
58
+
59
+ # Returns an array of the y coordinates in the vertices.
60
+ def y_coords
61
+ coords 1
62
+ end
63
+
64
+ # Returns an array of the z coordinates in the vertices.
65
+ def z_coords
66
+ coords 2
67
+ end
68
+
69
+ # Returns the coordinates that have coord index % 3 == d.
70
+ def coords(d)
71
+ if vertices
72
+ v = vertices.unpack("f*")
73
+ i = -1
74
+ v.find_all{|c|
75
+ i += 1
76
+ i%3 == d
77
+ }
78
+ else
79
+ [0]
80
+ end
81
+ end
82
+
83
+ # Draws the vertex arrays.
84
+ def draw
85
+ DrawArrays type, 0, vertice_count
86
+ end
87
+
88
+ # Parses the given vertex array to a valid OpenGL vertex array
89
+ def parse_array(v)
90
+ case v
91
+ when Array
92
+ v.flatten.pack("f*")
93
+ when String,nil,false
94
+ v
95
+ else
96
+ raise ArgumentError, "Provide either an Array of coords or a String of packed floats"
97
+ end
98
+ end
99
+
100
+ # A geometry is a drawable.
101
+ def drawables
102
+ @drawables + [self]
103
+ end
104
+
105
+ def collapse(list=[])
106
+ if vertices # drawable geometry
107
+ list << self
108
+ end
109
+ super
110
+ end
111
+
112
+ # The collapsed geometry of a Geometry is the Geometry itself.
113
+ def absolute_geometry
114
+ self
115
+ end
116
+
117
+ # Merges self with the other.
118
+ def merge!(other)
119
+ @type = other.type if other.type
120
+ @normals = other.normals if other.normals
121
+ @texcoords = other.texcoords if other.texcoords
122
+ @colors = other.colors if other.colors
123
+ @vertices = other.vertices if other.vertices
124
+ @vertice_count = other.vertice_count if other.vertice_count
125
+ self
126
+ end
127
+
128
+ # Replaces ivars with the other's.
129
+ def replace!(other)
130
+ @type = other.type
131
+ @normals = other.normals
132
+ @texcoords = other.texcoords
133
+ @colors = other.colors
134
+ @vertices = other.vertices
135
+ @vertice_count = other.vertice_count
136
+ self
137
+ end
138
+
139
+ def texcoords=(v)
140
+ v = parse_array(v)
141
+ @texcoords = v
142
+ end
143
+
144
+ def normals=(v)
145
+ v = parse_array(v)
146
+ @normals = v
147
+ end
148
+
149
+ def colors=(v)
150
+ v = parse_array(v)
151
+ @colors = v
152
+ end
153
+
154
+ def vertices=(v)
155
+ v = parse_array(v)
156
+ @vertices = v
157
+ @vertice_count = v.size / 12
158
+ end
159
+
160
+ touching :touch!, :texcoords=, :normals=, :colors=, :vertices=, :vertice_count=, :replace!
161
+
162
+ # Applies the different arrays and draws if vertices set.
163
+ def apply
164
+ NormalPointer FLOAT, 0, normals if normals
165
+ TexCoordPointer 2, FLOAT, 0, texcoords if texcoords
166
+ ColorPointer 4, FLOAT, 0, colors if colors
167
+ VertexPointer 3, FLOAT, 0, vertices if vertices
168
+ draw if vertices
169
+ end
170
+
171
+ # Enables vertex arrays.
172
+ def push_state
173
+ EnableClientState NORMAL_ARRAY if normals
174
+ EnableClientState TEXTURE_COORD_ARRAY if texcoords
175
+ EnableClientState COLOR_ARRAY if colors
176
+ EnableClientState VERTEX_ARRAY if vertices
177
+ end
178
+
179
+ # Disables vertex arrays.
180
+ def pop_state
181
+ DisableClientState NORMAL_ARRAY if normals
182
+ DisableClientState TEXTURE_COORD_ARRAY if texcoords
183
+ DisableClientState COLOR_ARRAY if colors
184
+ DisableClientState VERTEX_ARRAY if vertices
185
+ end
186
+
187
+ def inspect
188
+ "#<#{self.class.name}:#{hash}:TYPE#@type:#@vertice_count vertices>"
189
+ end
190
+
191
+ end
192
+
193
+
194
+ end
@@ -0,0 +1,387 @@
1
+ require 'glimr/renderer/viewport'
2
+ require 'glimr/renderer/camera'
3
+ require 'glimr/util'
4
+ GC.disable
5
+ require 'glut'
6
+ GLUT.Init
7
+
8
+
9
+ module GlimR
10
+
11
+ # The GLUTWindow provides a stand-alone OpenGL context for GlimR.
12
+ #
13
+ # When rendering, a GLUTWindow first sends a frame event to its viewport,
14
+ # then clears the buffer and tells its viewport to draw itself.
15
+ #
16
+ # The GLUTWindow is also responsible for mapping GLUT events into GlimR
17
+ # events and sending them to the viewport.
18
+ #
19
+ class GLUTWindow
20
+
21
+ @frame_time = 0
22
+
23
+ # Index of the current window.
24
+ def self.window_index
25
+ @window_index ||= 0
26
+ end
27
+
28
+ # Array of windows created.
29
+ def self.windows
30
+ @windows ||= []
31
+ end
32
+
33
+ # Id of current GLUT window.
34
+ def self.current_window
35
+ windows[window_index]
36
+ end
37
+
38
+ # Sets the active GLUT window to the next window in order.
39
+ def self.next_window
40
+ if @window_index == 0
41
+ slp = 0.033 - (Time.now.to_f - @frame_time)
42
+ sleep(slp) if slp > 0
43
+ @frame_time = Time.now.to_f
44
+ end
45
+ @window_index = (window_index + 1)%windows.size
46
+ GLUT.SetWindow(current_window.window_id)
47
+ current_window.heartbeat
48
+ end
49
+
50
+ # Creates a GLUT window with the title and adds it to windows list.
51
+ def self.create_window(window)
52
+ GLUT.InitDisplayMode(window.display_mode)
53
+ GLUT.InitWindowSize(window.width, window.height)
54
+ GLUT.InitWindowPosition(window.x, window.y) if window.x and window.y
55
+ window.window_id = GLUT.CreateWindow window.title
56
+ windows << window
57
+ window.window_id
58
+ end
59
+
60
+ # Destroys the GLUT window w and deletes it from the windows list.
61
+ # Exits if the window in question was the last remaining window.
62
+ def self.destroy_window(w)
63
+ windows.delete w
64
+ GLUT.DestroyWindow w.window_id
65
+ exit if windows.empty?
66
+ end
67
+
68
+ # GLUT idle function is to show the next window.
69
+ GLUT.IdleFunc(lambda { next_window })
70
+
71
+ attr_accessor :viewport
72
+ attr_accessor :window_id
73
+ attr_reader :width, :height, :title, :frame, :frames_drawn, :x, :y, :display_mode
74
+
75
+ delegate :viewport, :<<, :attach, :detach, :camera=, :camera, :background, :background=
76
+
77
+ # Creates a window with width w and height h.
78
+ def initialize(w=400,h=300,title="GlimR - #{$0}",x=nil,y=nil)
79
+ @viewport = Viewport.new
80
+ @viewport.background = [1,1,1,1]
81
+ @viewport.clear_depth_buffer = true
82
+ @visible = true
83
+ @display_mode = GLUT::DOUBLE | GLUT::RGB | GLUT::DEPTH
84
+ @x = x
85
+ @y = y
86
+ @viewport.x = 0
87
+ @viewport.y = 0
88
+ @frame_event = Event.new(:frame)
89
+ @reshape_event = Event.new(:resize)
90
+ @frame_time = @last_frame_time = Time.now.to_f
91
+ @frame = @frames_drawn = 0
92
+ @title = title
93
+ @event_queue = {}
94
+ @mouse_buttons_down = {}
95
+ @mouse_properties = {}
96
+ @modifiers = {}
97
+ reshape(w,h)
98
+ init_glut
99
+ end
100
+
101
+ # Initialize GLUT state for the window, then ask GLUTWindow class to
102
+ # create the window.
103
+ def init_glut
104
+ self.class.create_window(self)
105
+ set_callback_funcs
106
+ end
107
+
108
+ # GLUT ReshapeFunc.
109
+ # Sets viewport width and height to w and h, respectively,
110
+ # and sends a resize event to the viewport.
111
+ def reshape(w,h)
112
+ @width = w
113
+ @height = h
114
+ @reshape_event.time = Time.now.to_f
115
+ @reshape_event.bubbles = false
116
+ @reshape_event.width = w
117
+ @reshape_event.height = h
118
+ viewport.multicast_event(@reshape_event)
119
+ end
120
+
121
+ # Enters GLUT.MainLoop.
122
+ def run
123
+ GLUT.MainLoop
124
+ end
125
+
126
+ def need_redraw?
127
+ @visible and (changed? or damaged?)
128
+ end
129
+
130
+ def damaged?
131
+ GLUT.LayerGet(GLUT::NORMAL_DAMAGED) == GL::TRUE
132
+ end
133
+
134
+ def changed?
135
+ viewport.changed?
136
+ end
137
+
138
+ def heartbeat
139
+ GC.disable
140
+ @frame_time = Time.now.to_f
141
+ @frame_event.time = @frame_time
142
+ @frame_event.bubbles = false
143
+ @event_queue.values.each{|evt|
144
+ viewport.dispatch_event(evt)
145
+ }
146
+ @event_queue.clear
147
+ viewport.multicast_event(@frame_event)
148
+ GLUT.PostRedisplay if need_redraw?
149
+ GC.enable
150
+ @frame += 1
151
+ end
152
+
153
+ def queue_event evt
154
+ if evt.type == :mouse_move and @event_queue[:mouse_move]
155
+ evt.dx += @event_queue[:mouse_move].dx
156
+ evt.dy += @event_queue[:mouse_move].dy
157
+ evt.params[:dx] = evt.dx
158
+ evt.params[:dy] = evt.dy
159
+ evt.params[:time] = evt.time = Time.now.to_f
160
+ end
161
+ @event_queue[evt.type] = evt
162
+ end
163
+
164
+ # GLUT DisplayFunc.
165
+ #
166
+ # Displays the GLUTWindow. Sleeps to maintain a maximum
167
+ # framerate of 33fps, then displays previously rendered frame.
168
+ # Then sends a frame event to the viewport.
169
+ #
170
+ # If the GLUTWindow is visible, clears the buffer and tells viewport to draw itself.
171
+ # Finally increments the frame counter.
172
+ #
173
+ def display
174
+ GC.disable
175
+ viewport.render
176
+ GLUT.SwapBuffers
177
+ @frames_drawn += 1
178
+ end
179
+
180
+ # Asks GLUTWindow class to destroy the window.
181
+ def close(*a)
182
+ self.class.destroy_window(self)
183
+ end
184
+
185
+ # Toggles fullscreen mode.
186
+ def fullscreen(*a)
187
+ if not @fullscreen
188
+ @fullscreen = %w(X Y WIDTH HEIGHT).map{|cn|
189
+ GLUT.Get(GLUT.const_get("WINDOW_#{cn}"))
190
+ }
191
+ GLUT.FullScreen
192
+ else
193
+ x,y,w,h = *@fullscreen
194
+ GLUT.ReshapeWindow(w,h)
195
+ GLUT.PositionWindow(x,y)
196
+ @fullscreen = nil
197
+ end
198
+ end
199
+
200
+ # Takes screenshot (not implemented.)
201
+ def take_screenshot(*a)
202
+ #screenshot
203
+ end
204
+
205
+ # Enters the profiler, lets run for 5 seconds, then
206
+ # exits.
207
+ def profiler(*a)
208
+ Thread.new{
209
+ puts "Entering profiler"
210
+ require 'profile'
211
+ sleep 5
212
+ exit
213
+ }
214
+ end
215
+
216
+ # Set GLUT callback functions to the object's methods that have
217
+ # the rubyized name of the corresponding callback function.
218
+ # That's not very clear, is it. Maybe this example sheds some light on it.
219
+ #
220
+ # GLUT::ReshapeFunc is set to "reshape"-method
221
+ # GLUT::SpaceballMotionFunc is set to "spaceball_motion"-method
222
+ # GLUT::MouseFunc is set to "mouse"-method
223
+ # ... and so on for every GLUT method that ends with Func.
224
+ #
225
+ # If there is no corresponding method (e.g. there's no "idle"-method), the
226
+ # callback won't be set.
227
+ #
228
+ # List of callback names (avoid these in non-callback method names):
229
+ #
230
+ # ButtonBoxFunc => button_box
231
+ # DialsFunc => dials
232
+ # DisplayFunc => display
233
+ # EntryFunc => entry
234
+ # IdleFunc => idle
235
+ # KeyboardFunc => keyboard
236
+ # MotionFunc => motion
237
+ # MouseFunc => mouse
238
+ # OverlayDisplayFunc => overlay_display
239
+ # PassiveMotionFunc => passive_motion
240
+ # ReshapeFunc => reshape
241
+ # SpaceballButtonFunc => spaceball_button
242
+ # SpaceballMotionFunc => spaceball_motion
243
+ # SpaceballRotateFunc => spaceball_rotate
244
+ # SpecialFunc => special
245
+ # TabletButtonFunc => tablet_button
246
+ # TabletMotionFunc => tablet_motion
247
+ # TimerFunc => timer
248
+ # VisibilityFunc => visibility
249
+ # WindowStatusFunc => window_status
250
+ #
251
+ def set_callback_funcs
252
+ GLUT.methods.grep(/Func$/).sort.each{|callback_setter_name|
253
+ # Change FooBarFunc into foo_bar
254
+ callback_method_name = callback_setter_name[0,1].downcase +
255
+ callback_setter_name[1..-5].
256
+ gsub(/[A-Z]/){|cap| "_" + cap.downcase}
257
+ if respond_to?(callback_method_name.to_sym)
258
+ callback_method = method(callback_method_name).to_proc
259
+ GLUT.__send__(callback_setter_name, callback_method)
260
+ end
261
+ }
262
+ @mouse_over_window = true
263
+ end
264
+
265
+ MOUSE_BUTTONS = [:left, :middle, :right, :wheel_up, :wheel_down]
266
+
267
+ # GLUT EntryFunc.
268
+ def entry(enter)
269
+ @mouse_over_window = (enter == 1)
270
+ if @mouse_over_window
271
+ @mouse_x, @mouse_y = @prev_mouse_x, @prev_mouse_y
272
+ else
273
+ @mouse_x = @mouse_y = nil
274
+ end
275
+ end
276
+
277
+ # Updates current mouse coordinates.
278
+ def update_coords(x,y,update_modifier_keys=true)
279
+ props = {
280
+ :target => @target,
281
+ :x => x, :y => y,
282
+ :dx => (@prev_mouse_x ? x-@prev_mouse_x : 0),
283
+ :dy => (@prev_mouse_y ? y-@prev_mouse_y : 0),
284
+ :buttons => @mouse_buttons_down
285
+ }
286
+ @prev_mouse_x, @prev_mouse_y = x, y
287
+ @mouse_x, @mouse_y = x, y if @mouse_over_window
288
+ update_modifiers if update_modifier_keys
289
+ @mouse_properties = @mouse_properties.merge(props).merge(@modifiers)
290
+ end
291
+
292
+ # GLUT PassiveMotionFunc. Moving mouse without buttons depressed
293
+ def passive_motion(x,y)
294
+ event_properties = update_coords(x,y,false)
295
+ queue_event Event.new(:mouse_move, event_properties)
296
+ end
297
+
298
+ # GLUT MotionFunc. Moving mouse with mouse button depressed.
299
+ def motion(x,y)
300
+ event_properties = update_coords(x,y,false)
301
+ queue_event Event.new(:mouse_move, event_properties)
302
+ end
303
+
304
+ # GLUT ButtonBoxFunc.
305
+ def button_box(*a)
306
+ end
307
+
308
+ # GLUT VisibilityFunc. Toggles window visibility variable based on the argument.
309
+ def visibility(visible)
310
+ @visible = (visible == GLUT::VISIBLE)
311
+ end
312
+
313
+ # GLUT WindowStatusFunc.
314
+ def window_status(*a)
315
+ end
316
+
317
+ # GLUT MouseFunc. Fired when mouse button pressed or released.
318
+ def mouse(button, down, x, y)
319
+ down = (down == 0)
320
+ button = MOUSE_BUTTONS[button]
321
+ @mouse_buttons_down[button] = down
322
+ event_type = (down ? :mouse_down : :mouse_up)
323
+ event_properties = update_coords(x,y)
324
+ event_properties[:button] = button
325
+ queue_event Event.new(event_type, event_properties)
326
+ end
327
+
328
+ # Gets modifier key states and assigns them to
329
+ # @modifiers-Hash and returns the Hash.
330
+ def update_modifiers
331
+ modifier_bitfield = GLUT.GetModifiers
332
+ GLUT.constants.grep(/^ACTIVE_/).each{|cn|
333
+ @modifiers[cn[7..-1].downcase.to_sym] = (modifier_bitfield & GLUT.const_get(cn) > 0)
334
+ }
335
+ @modifiers
336
+ end
337
+
338
+ # GLUT KeyboardFunc. Fired when key pressed.
339
+ # Multicasts key_down and key_up.
340
+ #
341
+ # Note: there is no "Hold button down"-functionality in GLUT.
342
+ #
343
+ def keyboard(key, x, y)
344
+ event_properties = update_coords(x,y).merge(:key => key)
345
+ ev = Event.new(:key_down, event_properties)
346
+ @viewport.multicast_event(ev)
347
+ ev = Event.new(:key_up, event_properties)
348
+ @viewport.multicast_event(ev)
349
+ handler = keyhandlers[key] if @modifiers[:ctrl] and not (@modifiers[:shift] or @modifiers[:alt])
350
+ if handler and not (ev.stopped or ev.cancelled)
351
+ if handler.respond_to? :call
352
+ handler.call(key, x, y)
353
+ else
354
+ __send__(handler, key, x, y)
355
+ end
356
+ end
357
+ end
358
+
359
+ # Handles special key presses.
360
+ #
361
+ # Converts the special key to a symbol by stripping KEY_
362
+ # off the front and downcasing the remains, e.g. :f1, :left, :right.
363
+ #
364
+ # Delegates the symbol to #keyboard.
365
+ #
366
+ def special(key, x, y)
367
+ cn = GLUT.constants.grep(/^KEY_/).find{|k| GLUT.const_get(k) == key}
368
+ key = cn.sub(/^KEY_/, '').downcase.to_sym
369
+ keyboard(key, x, y)
370
+ end
371
+
372
+ # Default key handlers.
373
+ #
374
+ # ESC closes window, f toggles fullscreen, p enters profiler, s takes screenshot.
375
+ #
376
+ def keyhandlers
377
+ @keyhandlers ||= {
378
+ 17 => :close,
379
+ 6 => :fullscreen,
380
+ 16 => :profiler,
381
+ 20 => :take_screenshot,
382
+ }
383
+ end
384
+
385
+ end
386
+
387
+ end