glimr 0.1.0 → 0.1.1
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.
- data/COPYING +504 -0
- data/README +222 -0
- data/glimr-0.1.1.gemspec +21 -0
- data/lib/glimr/renderer/glutwindow.rb +387 -387
- data/lib/glimr/renderer/viewport.rb +352 -349
- data/lib/glimr/widget.rb +1 -0
- data/lib/glimr/widgets/label.rb +120 -91
- data/lib/glimr/widgets/list.rb +67 -9
- data/lib/glimr/widgets/scrollbar.rb +4 -4
- data/setup.rb +1360 -0
- data/tests/integration_tests/test_list.rb +11 -0
- metadata +61 -55
- data/tests/assets/download_progress_meter.png +0 -0
- data/tests/assets/metalwing2.png +0 -0
data/README
ADDED
@@ -0,0 +1,222 @@
|
|
1
|
+
GlimR 0.1
|
2
|
+
Ruby OpenGL Widget Kit
|
3
|
+
2006-07-14
|
4
|
+
-----------
|
5
|
+
http://glimr.rubyforge.org
|
6
|
+
|
7
|
+
Under development. Watch your head.
|
8
|
+
|
9
|
+
Funded as one of Ruby Central Inc.'s
|
10
|
+
Google Summer of Code 2006 projects.
|
11
|
+
|
12
|
+
http://code.google.com/soc/
|
13
|
+
|
14
|
+
|
15
|
+
Installation:
|
16
|
+
-------------
|
17
|
+
|
18
|
+
Requirements:
|
19
|
+
* Ruby OpenGL bindings and GLUT
|
20
|
+
* Ruby/SDL with SDL::TTF support
|
21
|
+
|
22
|
+
To install:
|
23
|
+
(sudo) ruby setup.rb
|
24
|
+
|
25
|
+
Should install and run on Windows, Linux and OS X.
|
26
|
+
|
27
|
+
|
28
|
+
|
29
|
+
How to use:
|
30
|
+
-----------
|
31
|
+
GlimR is an event-driven GUI toolkit.
|
32
|
+
|
33
|
+
You write programs by creating widgets,
|
34
|
+
attaching them to a parent widget and setting
|
35
|
+
event listeners to respond to user input.
|
36
|
+
|
37
|
+
GlimR can be used as either a stand-alone
|
38
|
+
GUI toolkit or embedded as a part of an
|
39
|
+
OpenGL application.
|
40
|
+
|
41
|
+
Currently GlimR has simple low-level widgets
|
42
|
+
and you need to build dialogs and such from
|
43
|
+
smaller bits.
|
44
|
+
|
45
|
+
Here's a small GlimR program:
|
46
|
+
|
47
|
+
require 'glimr'
|
48
|
+
include GlimR
|
49
|
+
|
50
|
+
name_form = VLayout.new(
|
51
|
+
:expand_height => true, :expand_width => true,
|
52
|
+
:align => :center, :valign => :middle)
|
53
|
+
|
54
|
+
label = Label.new(:text => "Enter your name:")
|
55
|
+
name_input = TextInput.new(:text => "")
|
56
|
+
submit = Button.new(:label => "OK"){|o,e|
|
57
|
+
name_form.attach Label.new(:text => "Hi, #{name_input.text}!")
|
58
|
+
}
|
59
|
+
name_form.attach label, name_input, submit
|
60
|
+
|
61
|
+
window = GLUTWindow.new(400, 200, "My App")
|
62
|
+
window.attach name_form
|
63
|
+
window.run
|
64
|
+
|
65
|
+
|
66
|
+
See tests/integration_tests/ for more sample code.
|
67
|
+
|
68
|
+
|
69
|
+
|
70
|
+
Embedding:
|
71
|
+
----------
|
72
|
+
To embed GlimR in an OpenGL application, do this:
|
73
|
+
1. Create a GlimR::Viewport as the root of your GlimR scene.
|
74
|
+
2. Set the viewport's width and height to your window's width and height.
|
75
|
+
3. Send user input events, window reshape events and a frame event to
|
76
|
+
the viewport. See an example on how to do this in
|
77
|
+
lib/glimr/renderer/glutwindow.rb.
|
78
|
+
4. After drawing your OpenGL frame -- but before swapping buffers --
|
79
|
+
draw the viewport by calling viewport.render
|
80
|
+
|
81
|
+
To avoid loading GLUT, set $glimr_no_glut = true before loading GlimR.
|
82
|
+
|
83
|
+
To draw the GUI only when it has changed, render the viewport to a
|
84
|
+
texture and poll viewport.changed? to figure out if anything
|
85
|
+
redraw-worthy has taken place.
|
86
|
+
|
87
|
+
E.g. hypothetical frame loop:
|
88
|
+
|
89
|
+
input_events.each{|ev| viewport.multicast_event(ev) }
|
90
|
+
if reshape_event
|
91
|
+
viewport.multicast_event(reshape_event)
|
92
|
+
reshape_event = false
|
93
|
+
end
|
94
|
+
viewport.multicast_event(frame_event)
|
95
|
+
if viewport.changed?
|
96
|
+
gui_texture.render_into(w,h){ viewport.render }
|
97
|
+
end
|
98
|
+
|
99
|
+
draw_frame
|
100
|
+
|
101
|
+
gui_texture.draw_on_screen
|
102
|
+
|
103
|
+
Implementation of gui_texture left as an exercise for the reader.
|
104
|
+
|
105
|
+
|
106
|
+
|
107
|
+
Code structure:
|
108
|
+
---------------
|
109
|
+
|
110
|
+
--- EVENTS
|
111
|
+
|
112
|
+
+ lib/glimr/event.rb - GlimR::Event
|
113
|
+
- Events are glorified OpenStructs
|
114
|
+
- conforms to the DOM2 Event interface (but no subclasses)
|
115
|
+
|
116
|
+
+ lib/glimr/eventlistener.rb - GlimR::EventListener
|
117
|
+
- EventListener mixin
|
118
|
+
- conforms to the DOM2 EventListener interface
|
119
|
+
(plus event multicasting)
|
120
|
+
- if you're ever used JavaScript, you should feel comfy
|
121
|
+
|
122
|
+
--- UTILITIES
|
123
|
+
|
124
|
+
+ lib/glimr/configurable.rb - Configurable
|
125
|
+
- Configurable mixin for Hash-based initialization that works
|
126
|
+
with inheritance
|
127
|
+
|
128
|
+
+ lib/glimr/layoutable.rb - GlimR::Layoutable
|
129
|
+
- Layoutable mixin
|
130
|
+
- used to make objects get layouted
|
131
|
+
- Widgets and Layouts are Layoutable
|
132
|
+
|
133
|
+
+ lib/glimr/util.rb
|
134
|
+
- utility classes and core class extensions
|
135
|
+
(metaprogramming helpers mostly)
|
136
|
+
|
137
|
+
--- WIDGETS
|
138
|
+
|
139
|
+
+ lib/glimr/widget.rb - GlimR::Widget
|
140
|
+
- base GlimR widget class
|
141
|
+
- inherits from GlimR::Model (see below)
|
142
|
+
- is Layoutable and EventListener
|
143
|
+
|
144
|
+
+ lib/glimr/widgets.rb
|
145
|
+
- loads widgets from widgets/
|
146
|
+
|
147
|
+
+ lib/glimr/default_theme/
|
148
|
+
- the theme dir contains the images and fonts used by the widgets
|
149
|
+
|
150
|
+
+ lib/glimr/widgets/
|
151
|
+
- all different widgets
|
152
|
+
|
153
|
+
+ lib/glimr/layout.rb - GlimR::Layout, GlimR::VLayout, GlimR::HLayout
|
154
|
+
- layout classes, inherit from GlimR::Widget
|
155
|
+
|
156
|
+
+ button.rb - Button
|
157
|
+
+ checkbox.rb - CheckBox
|
158
|
+
+ radiogroup.rb - RadioGroup
|
159
|
+
|
160
|
+
+ container.rb - Container
|
161
|
+
- viewport wrapper
|
162
|
+
+ scrollable_container.rb - ScrollableContainer
|
163
|
+
+ resizer.rb - Resizer
|
164
|
+
- resizing knob
|
165
|
+
+ scrollbar.rb - VScrollBar, HScrollBar
|
166
|
+
|
167
|
+
+ image.rb - Image
|
168
|
+
+ stretchable_image.rb - StretchableImage
|
169
|
+
|
170
|
+
+ label.rb - Label
|
171
|
+
+ text_editor.rb - TextInput
|
172
|
+
|
173
|
+
--- RENDERER
|
174
|
+
|
175
|
+
+ lib/glimr/renderer.rb
|
176
|
+
- loads renderer core classes
|
177
|
+
- loads standalone renderer if $glimr_no_glut is nil or false
|
178
|
+
|
179
|
+
+ lib/glimr/renderer_core.rb
|
180
|
+
- loads renderer core classes
|
181
|
+
|
182
|
+
+ lib/glimr/renderer/
|
183
|
+
- rendering-related classes
|
184
|
+
|
185
|
+
+ glutwindow.rb - Glimr::GLUTWindow
|
186
|
+
- stand-alone GLUT window
|
187
|
+
- creates GL context
|
188
|
+
- passes input events to the viewport
|
189
|
+
- ctrl-q quits
|
190
|
+
|
191
|
+
+ sceneobject.rb - GlimR::SceneObject
|
192
|
+
- class that other rendering classes inherit from
|
193
|
+
- defines child-parent relationships and state collapsing defaults
|
194
|
+
|
195
|
+
+ viewport.rb - GlimR::Viewport
|
196
|
+
- the root of the GlimR drawing hierarchy
|
197
|
+
- captures input events, munges them to fit its view of the world
|
198
|
+
and passes them to the scene below
|
199
|
+
- handles clearing screen, setting up camera, clipping to viewport,
|
200
|
+
turning mouse_down & mouse_up into click
|
201
|
+
|
202
|
+
+ model.rb - GlimR::Model
|
203
|
+
- convenience class for managing a transform with attached
|
204
|
+
material, texture, shader and geometry
|
205
|
+
|
206
|
+
+ geometry.rb - GlimR::Geometry
|
207
|
+
- drawable geometry
|
208
|
+
- uses vertex arrays
|
209
|
+
|
210
|
+
+ transform.rb - GlimR::Transform, GlimR::AngleAxis, GlimR::Quaternion
|
211
|
+
- does OpenGL transforms: translation, scaling, rotation
|
212
|
+
(with angle-axis or quaternion), custom transform matrix
|
213
|
+
|
214
|
+
+ texture.rb - GlimR::Texture
|
215
|
+
- manages OpenGL textures
|
216
|
+
- handles loading image files to textures
|
217
|
+
|
218
|
+
|
219
|
+
|
220
|
+
Author:
|
221
|
+
-------
|
222
|
+
Ilmari Heikkinen <ilmari.heikkinen@gmail.com>
|
data/glimr-0.1.1.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'rake'
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'glimr'
|
5
|
+
s.version = '0.1.1'
|
6
|
+
s.date = '2006-07-22'
|
7
|
+
s.summary = 'Ruby OpenGL GUI Widget Kit'
|
8
|
+
s.email = 'ilmari.heikkinen@gmail.com'
|
9
|
+
s.homepage = 'glimr.rubyforge.org'
|
10
|
+
s.rubyforge_project = 'glimr'
|
11
|
+
s.authors = ['Ilmari Heikkinen']
|
12
|
+
s.files = FileList[
|
13
|
+
'*',
|
14
|
+
'lib/**/*.rb',
|
15
|
+
'lib/**/*.png',
|
16
|
+
'lib/**/*.ttf',
|
17
|
+
'tests/**/*'
|
18
|
+
].to_a
|
19
|
+
s.required_ruby_version = '>= 1.8.1'
|
20
|
+
s.requirements = ['Ruby-OpenGL >= 0.32g', 'Ruby/SDL >= 1.0.0']
|
21
|
+
end
|
@@ -1,387 +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
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
GLUT.
|
55
|
-
window.
|
56
|
-
|
57
|
-
window.window_id
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
@viewport
|
82
|
-
@
|
83
|
-
@
|
84
|
-
@
|
85
|
-
@
|
86
|
-
@
|
87
|
-
@
|
88
|
-
@
|
89
|
-
@
|
90
|
-
@
|
91
|
-
@
|
92
|
-
@
|
93
|
-
@
|
94
|
-
@
|
95
|
-
@
|
96
|
-
@
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
#
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
@
|
115
|
-
@
|
116
|
-
@reshape_event.
|
117
|
-
@reshape_event.
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
@
|
142
|
-
@frame_event.
|
143
|
-
@
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
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
|
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
|
+
GC.enable
|
41
|
+
if @window_index == 0
|
42
|
+
slp = 0.033 - (Time.now.to_f - @frame_time)
|
43
|
+
sleep(slp) if slp > 0
|
44
|
+
@frame_time = Time.now.to_f
|
45
|
+
end
|
46
|
+
@window_index = (window_index + 1)%windows.size
|
47
|
+
GC.disable
|
48
|
+
GLUT.SetWindow(current_window.window_id)
|
49
|
+
current_window.heartbeat
|
50
|
+
end
|
51
|
+
|
52
|
+
# Creates a GLUT window with the title and adds it to windows list.
|
53
|
+
def self.create_window(window)
|
54
|
+
GLUT.InitDisplayMode(window.display_mode)
|
55
|
+
GLUT.InitWindowSize(window.width, window.height)
|
56
|
+
GLUT.InitWindowPosition(window.x, window.y) if window.x and window.y
|
57
|
+
window.window_id = GLUT.CreateWindow window.title
|
58
|
+
windows << window
|
59
|
+
window.window_id
|
60
|
+
end
|
61
|
+
|
62
|
+
# Destroys the GLUT window w and deletes it from the windows list.
|
63
|
+
# Exits if the window in question was the last remaining window.
|
64
|
+
def self.destroy_window(w)
|
65
|
+
windows.delete w
|
66
|
+
GLUT.DestroyWindow w.window_id
|
67
|
+
exit if windows.empty?
|
68
|
+
end
|
69
|
+
|
70
|
+
# GLUT idle function is to show the next window.
|
71
|
+
GLUT.IdleFunc(lambda { next_window })
|
72
|
+
|
73
|
+
attr_accessor :viewport
|
74
|
+
attr_accessor :window_id
|
75
|
+
attr_reader :width, :height, :title, :frame, :frames_drawn, :x, :y, :display_mode
|
76
|
+
|
77
|
+
delegate :viewport, :<<, :attach, :detach, :camera=, :camera, :background, :background=
|
78
|
+
|
79
|
+
# Creates a window with width w and height h.
|
80
|
+
def initialize(w=400,h=300,title="GlimR - #{$0}",x=nil,y=nil)
|
81
|
+
@viewport = Viewport.new
|
82
|
+
@viewport.background = [1,1,1,1]
|
83
|
+
@viewport.clear_depth_buffer = true
|
84
|
+
@visible = true
|
85
|
+
@display_mode = GLUT::DOUBLE | GLUT::RGB | GLUT::DEPTH
|
86
|
+
@x = x
|
87
|
+
@y = y
|
88
|
+
@viewport.x = 0
|
89
|
+
@viewport.y = 0
|
90
|
+
@frame_event = Event.new(:frame)
|
91
|
+
@reshape_event = Event.new(:window_resize)
|
92
|
+
@frame_time = @last_frame_time = Time.now.to_f
|
93
|
+
@frame = @frames_drawn = 0
|
94
|
+
@title = title
|
95
|
+
@event_queue = {}
|
96
|
+
@mouse_buttons_down = {}
|
97
|
+
@mouse_properties = {}
|
98
|
+
@modifiers = {}
|
99
|
+
reshape(w,h)
|
100
|
+
init_glut
|
101
|
+
end
|
102
|
+
|
103
|
+
# Initialize GLUT state for the window, then ask GLUTWindow class to
|
104
|
+
# create the window.
|
105
|
+
def init_glut
|
106
|
+
self.class.create_window(self)
|
107
|
+
set_callback_funcs
|
108
|
+
end
|
109
|
+
|
110
|
+
# GLUT ReshapeFunc.
|
111
|
+
# Sets viewport width and height to w and h, respectively,
|
112
|
+
# and sends a window_resize event to the viewport.
|
113
|
+
def reshape(w,h)
|
114
|
+
@width = w
|
115
|
+
@height = h
|
116
|
+
@reshape_event.time = Time.now.to_f
|
117
|
+
@reshape_event.bubbles = false
|
118
|
+
@reshape_event.width = w
|
119
|
+
@reshape_event.height = h
|
120
|
+
viewport.multicast_event(@reshape_event)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Enters GLUT.MainLoop.
|
124
|
+
def run
|
125
|
+
GLUT.MainLoop
|
126
|
+
end
|
127
|
+
|
128
|
+
def need_redraw?
|
129
|
+
@visible and (changed? or damaged?)
|
130
|
+
end
|
131
|
+
|
132
|
+
def damaged?
|
133
|
+
GLUT.LayerGet(GLUT::NORMAL_DAMAGED) == GL::TRUE
|
134
|
+
end
|
135
|
+
|
136
|
+
def changed?
|
137
|
+
viewport.changed?
|
138
|
+
end
|
139
|
+
|
140
|
+
def heartbeat
|
141
|
+
@frame_time = Time.now.to_f
|
142
|
+
@frame_event.time = @frame_time
|
143
|
+
@frame_event.bubbles = false
|
144
|
+
@event_queue.values.each{|evt|
|
145
|
+
viewport.dispatch_event(evt)
|
146
|
+
}
|
147
|
+
@event_queue.clear
|
148
|
+
viewport.multicast_event(@frame_event)
|
149
|
+
GLUT.PostRedisplay if need_redraw?
|
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
|