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.
- data/lib/glimr.rb +2 -0
- data/lib/glimr/configurable.rb +37 -0
- data/lib/glimr/default_theme/button_bg.png +0 -0
- data/lib/glimr/default_theme/button_bg_down.png +0 -0
- data/lib/glimr/default_theme/button_cover.png +0 -0
- data/lib/glimr/default_theme/button_cover_down.png +0 -0
- data/lib/glimr/default_theme/button_focus.png +0 -0
- data/lib/glimr/default_theme/checkbox_bg.png +0 -0
- data/lib/glimr/default_theme/checkbox_checked_bg.png +0 -0
- data/lib/glimr/default_theme/font.ttf +0 -0
- data/lib/glimr/default_theme/hscroller_bg.png +0 -0
- data/lib/glimr/default_theme/radiobutton_bg.png +0 -0
- data/lib/glimr/default_theme/radiobutton_checked_bg.png +0 -0
- data/lib/glimr/default_theme/resizer_down.png +0 -0
- data/lib/glimr/default_theme/resizer_up.png +0 -0
- data/lib/glimr/default_theme/scroll_down_down.png +0 -0
- data/lib/glimr/default_theme/scroll_down_up.png +0 -0
- data/lib/glimr/default_theme/scroll_hknob_down.png +0 -0
- data/lib/glimr/default_theme/scroll_hknob_up.png +0 -0
- data/lib/glimr/default_theme/scroll_left_down.png +0 -0
- data/lib/glimr/default_theme/scroll_left_up.png +0 -0
- data/lib/glimr/default_theme/scroll_right_down.png +0 -0
- data/lib/glimr/default_theme/scroll_right_up.png +0 -0
- data/lib/glimr/default_theme/scroll_up_down.png +0 -0
- data/lib/glimr/default_theme/scroll_up_up.png +0 -0
- data/lib/glimr/default_theme/scroll_vknob_down.png +0 -0
- data/lib/glimr/default_theme/scroll_vknob_up.png +0 -0
- data/lib/glimr/default_theme/text_cursor.png +0 -0
- data/lib/glimr/default_theme/text_cursor_insert.png +0 -0
- data/lib/glimr/default_theme/text_input_bg.png +0 -0
- data/lib/glimr/default_theme/vscroller_bg.png +0 -0
- data/lib/glimr/event.rb +41 -0
- data/lib/glimr/eventlistener.rb +209 -0
- data/lib/glimr/layoutable.rb +520 -0
- data/lib/glimr/renderer.rb +2 -0
- data/lib/glimr/renderer/camera.rb +63 -0
- data/lib/glimr/renderer/geometry.rb +194 -0
- data/lib/glimr/renderer/glutwindow.rb +387 -0
- data/lib/glimr/renderer/light.rb +43 -0
- data/lib/glimr/renderer/material.rb +66 -0
- data/lib/glimr/renderer/model.rb +103 -0
- data/lib/glimr/renderer/orthoprojection.rb +21 -0
- data/lib/glimr/renderer/raw.rb +34 -0
- data/lib/glimr/renderer/sceneobject.rb +279 -0
- data/lib/glimr/renderer/shader.rb +14 -0
- data/lib/glimr/renderer/texture.rb +280 -0
- data/lib/glimr/renderer/transform.rb +322 -0
- data/lib/glimr/renderer/viewport.rb +349 -0
- data/lib/glimr/renderer_core.rb +10 -0
- data/lib/glimr/util.rb +247 -0
- data/lib/glimr/widget.rb +87 -0
- data/lib/glimr/widgets.rb +37 -0
- data/lib/glimr/widgets/button.rb +277 -0
- data/lib/glimr/widgets/checkbox.rb +82 -0
- data/lib/glimr/widgets/container.rb +84 -0
- data/lib/glimr/widgets/image.rb +82 -0
- data/lib/glimr/widgets/label.rb +91 -0
- data/lib/glimr/widgets/layout.rb +227 -0
- data/lib/glimr/widgets/list.rb +28 -0
- data/lib/glimr/widgets/radiogroup.rb +118 -0
- data/lib/glimr/widgets/resizer.rb +31 -0
- data/lib/glimr/widgets/scrollable_container.rb +67 -0
- data/lib/glimr/widgets/scrollbar.rb +496 -0
- data/lib/glimr/widgets/stretchable_image.rb +135 -0
- data/lib/glimr/widgets/text_editor.rb +349 -0
- data/tests/assets/datatowers_crop.jpg +0 -0
- data/tests/assets/download_progress_meter.png +0 -0
- data/tests/assets/metalwing2.png +0 -0
- data/tests/assets/redhairgreeneyes3.jpg +0 -0
- data/tests/demo_apps/spinning_ruby.rb +37 -0
- data/tests/integration_tests/run_all.rb +8 -0
- data/tests/integration_tests/test_button.rb +22 -0
- data/tests/integration_tests/test_checkbox.rb +21 -0
- data/tests/integration_tests/test_container.rb +22 -0
- data/tests/integration_tests/test_label.rb +12 -0
- data/tests/integration_tests/test_layout.rb +43 -0
- data/tests/integration_tests/test_radiogroup.rb +16 -0
- data/tests/integration_tests/test_renderer.rb +44 -0
- data/tests/integration_tests/test_renderer2.rb +36 -0
- data/tests/integration_tests/test_scrollable_container.rb +34 -0
- data/tests/integration_tests/test_scrollbar.rb +20 -0
- data/tests/integration_tests/test_stretchable_image.rb +34 -0
- data/tests/integration_tests/test_text_input.rb +20 -0
- data/tests/integration_tests/test_zsort.rb +18 -0
- data/tests/unit_tests/test_button.rb +93 -0
- data/tests/unit_tests/test_checkbox.rb +35 -0
- data/tests/unit_tests/test_label.rb +36 -0
- data/tests/unit_tests/test_layout.rb +229 -0
- data/tests/unit_tests/test_widget.rb +3 -0
- metadata +139 -0
|
@@ -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
|