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,135 @@
1
+ require 'glimr/widgets/image'
2
+
3
+
4
+ module GlimR
5
+
6
+
7
+ class StretchableImage < Image
8
+
9
+ def set_dimensions(w,h)
10
+ @width = w if w >= @texture.width
11
+ @height = h if h >= @texture.height
12
+ update_geometry
13
+ end
14
+
15
+ def width=(w)
16
+ return if w < @texture.width
17
+ super
18
+ update_geometry
19
+ end
20
+
21
+ def height=(h)
22
+ return if h < @texture.height
23
+ super
24
+ update_geometry
25
+ end
26
+
27
+ def update_geometry
28
+ @min_width = @texture.width
29
+ @min_height = @texture.height
30
+ @width = [@width, @texture.width].max
31
+ @height = [@height, @texture.height].max
32
+ super
33
+ end
34
+
35
+
36
+ def create_texcoords
37
+ tc = super
38
+ tw = th = 0
39
+ tc.each_with_index{|e,i|
40
+ if i % 2 == 0
41
+ tw = e if e > tw
42
+ else
43
+ th = e if e > th
44
+ end
45
+ }
46
+ texcoords_3 = create_vertices(0.5*tw, 0.5*tw, tw, 0.5*th, 0.5*th, th)
47
+ xcoords = []
48
+ ycoords = []
49
+ texcoords_3.each_with_index{|e,i|
50
+ case i%3
51
+ when 0
52
+ xcoords << e
53
+ when 1
54
+ ycoords << e
55
+ end
56
+ }
57
+ xcoords.zip(ycoords)
58
+ end
59
+
60
+ def create_normals
61
+ super * 9
62
+ end
63
+
64
+ def create_vertices(
65
+ hw = @texture.width / 2.0,
66
+ fhw = @width - hw,
67
+ w = @width,
68
+ hh = @texture.height / 2.0,
69
+ fhh = @height - hh,
70
+ h = @height
71
+ )
72
+ top_left_quad = [
73
+ 0, hh, 0,
74
+ hw, hh, 0,
75
+ hw, 0, 0,
76
+ 0,0,0
77
+ ]
78
+ top_middle_quad = [
79
+ hw, hh, 0,
80
+ fhw, hh, 0,
81
+ fhw, 0, 0,
82
+ hw, 0, 0
83
+ ]
84
+ top_right_quad = [
85
+ fhw, hh, 0,
86
+ w, hh, 0,
87
+ w, 0, 0,
88
+ fhw, 0, 0
89
+ ]
90
+ middle_left_quad = [
91
+ 0, fhh, 0,
92
+ hw, fhh, 0,
93
+ hw, hh, 0,
94
+ 0, hh, 0
95
+ ]
96
+ middle_middle_quad = [
97
+ hw, fhh, 0,
98
+ fhw, fhh, 0,
99
+ fhw, hh, 0,
100
+ hw, hh, 0
101
+ ]
102
+ middle_right_quad = [
103
+ fhw, fhh, 0,
104
+ w, fhh, 0,
105
+ w, hh, 0,
106
+ fhw, hh, 0
107
+ ]
108
+ bottom_left_quad = [
109
+ 0, h, 0,
110
+ hw, h, 0,
111
+ hw, fhh, 0,
112
+ 0, fhh, 0
113
+ ]
114
+ bottom_middle_quad = [
115
+ hw, h, 0,
116
+ fhw, h, 0,
117
+ fhw, fhh, 0,
118
+ hw, fhh, 0
119
+ ]
120
+ bottom_right_quad = [
121
+ fhw, h, 0,
122
+ w, h, 0,
123
+ w, fhh, 0,
124
+ fhw, fhh, 0
125
+ ]
126
+ (
127
+ top_left_quad + top_middle_quad + top_right_quad +
128
+ middle_left_quad + middle_middle_quad + middle_right_quad +
129
+ bottom_left_quad + bottom_middle_quad + bottom_right_quad
130
+ )
131
+ end
132
+
133
+ end
134
+
135
+ end
@@ -0,0 +1,349 @@
1
+ require 'glimr/widgets/label'
2
+
3
+ module GlimR
4
+
5
+
6
+ class TextCursor < StretchableImage
7
+
8
+ def default_config
9
+ super.merge(
10
+ :normal => "text_cursor.png",
11
+ :insert => "text_cursor_insert.png"
12
+ )
13
+ end
14
+
15
+ def initialize(*a,&b)
16
+ super
17
+ load_theme(@normal)
18
+ @normal_texture = @texture
19
+ @insert_texture = Texture.load(GlimR.theme + @insert)
20
+ end
21
+
22
+ def use_normal
23
+ @texture = @normal_texture
24
+ touch!
25
+ end
26
+
27
+ def use_insert
28
+ @texture = @insert_texture
29
+ touch!
30
+ end
31
+
32
+ end
33
+
34
+
35
+ # TextInput is a single-line text input widget.
36
+ #
37
+ class TextInput < Label
38
+
39
+ if !WINDOWS and !MAC_OS_X # assume we're in an X environment
40
+ @@xsel = `which xsel`.strip
41
+ @@xsel = nil if @@xsel.empty?
42
+ else
43
+ @@xsel = false
44
+ end
45
+
46
+ attr_reader :scroll_x, :scroll_y
47
+
48
+ attr_accessor :cursor_position, :selection_start, :selection_end
49
+ attr_accessor :submit_handler
50
+
51
+ def default_config
52
+ super.merge(
53
+ :fit_children => false,
54
+ :align => :left,
55
+ :text => "",
56
+ :width => 150,
57
+ :height => 16,
58
+ :scroll_x => 0,
59
+ :scroll_y => 0,
60
+ :background => StretchableImage.load_theme("text_input_bg.png"),
61
+ :cursor => TextCursor.new,
62
+ :cursor_position => 0,
63
+ :selection_start => 0,
64
+ :selection_end => 0
65
+ )
66
+ end
67
+
68
+ def initialize(*a,&b)
69
+ if a[0]
70
+ txt = a[0][:text]
71
+ a[0].delete(:text)
72
+ end
73
+ super(*a, &nil)
74
+ @texture.pixels = "\000" * (@width*@height*4)
75
+ @texture.width = @width
76
+ @texture.height = @height
77
+ @bound = @texture.bound
78
+ update_geometry
79
+ if a[0]
80
+ self.text = txt if txt
81
+ end
82
+ @cursor.height = @height - 2
83
+ @cursor.y = 1
84
+ @cursor.z = -0.1
85
+ attach @cursor
86
+ @background.set_dimensions(@width, @height)
87
+ @background.z = -0.2
88
+ attach @background
89
+ if block_given?
90
+ self.submit_handler = b
91
+ end
92
+ end
93
+
94
+ def submit_handler= sh
95
+ remove_event_listener(:submit, @submit_handler) if @submit_handler
96
+ @submit_handler = sh
97
+ on_submit(@submit_handler)
98
+ end
99
+
100
+ def key_up(o,e)
101
+ return unless focused
102
+ case e.key
103
+ when :left
104
+ clear_selection if @text_selected
105
+ self.cursor_position -= 1
106
+ when :right
107
+ clear_selection if @text_selected
108
+ self.cursor_position += 1
109
+ when 8 # backspace
110
+ if @text_selected
111
+ replace_selection_with ""
112
+ elsif self.cursor_position > 0
113
+ @text[self.cursor_position-1] = ""
114
+ self.text = @text
115
+ @cursor_position -= 1
116
+ end
117
+ when 127 # delete
118
+ if @text_selected
119
+ replace_selection_with ""
120
+ elsif self.cursor_position < @text.size
121
+ @text[self.cursor_position] = ""
122
+ self.text = @text
123
+ end
124
+ when 13 # enter
125
+ submit
126
+ else
127
+ if @text_selected
128
+ replace_selection_with e.key.chr
129
+ cp = [@selection_start, @selection_end].min
130
+ else
131
+ cp = self.cursor_position
132
+ @text.insert(cp, e.key.chr)
133
+ self.text = @text
134
+ end
135
+ @cursor_position = cp + 1
136
+ end
137
+ end
138
+
139
+ def submit
140
+ dispatch_event(Event.new(:submit, :text => @text))
141
+ end
142
+
143
+ def replace_selection_with str
144
+ @text[selection_range] = str
145
+ clear_selection
146
+ self.text = @text
147
+ end
148
+
149
+ def selection_range
150
+ if @selection_start > @selection_end
151
+ (@selection_end...@selection_start) # up until selection start
152
+ else
153
+ (@selection_start..@selection_end)
154
+ end
155
+ end
156
+
157
+ def selection
158
+ @text[selection_range]
159
+ end
160
+
161
+ def click(o,e)
162
+ if e.button == :left
163
+ clear_selection if @text_selected
164
+ self.cursor_position = cursor_position_closest_to(e.rx, e.ry)
165
+ elsif e.button == :middle and @@xsel
166
+ x_sel = IO.popen("#@@xsel -o", "r"){|f| f.read }
167
+ replace_selection_with x_sel
168
+ @cursor_position += x_sel.size
169
+ end
170
+ end
171
+
172
+ def mouse_down(o,e)
173
+ if e.button == :left
174
+ @sx, @sy = e.rx, e.ry
175
+ @selection_start = @selection_end = cursor_position_closest_to(@sx, @sy)
176
+ end
177
+ super
178
+ end
179
+
180
+ def mouse_up(o,e)
181
+ super unless @selecting
182
+ if e.button == :left and @selecting
183
+ @selecting = false
184
+ @selection_end = cursor_position_closest_to(@sx, @sy)
185
+ update_selection
186
+ end
187
+ @sx = @sy = false
188
+ end
189
+
190
+ def mouse_move(o,e)
191
+ if @sx and @sy
192
+ @selecting = true
193
+ @sx += e.dx
194
+ @sy += e.dy
195
+ @selection_end = cursor_position_closest_to(@sx, @sy)
196
+ update_selection
197
+ end
198
+ end
199
+
200
+ def update_selection
201
+ if @@xsel
202
+ IO.popen("#@@xsel -i", "w"){|f| f.write selection }
203
+ end
204
+ ss = @text_offsets[@selection_start][0]
205
+ se = @text_offsets[@selection_end][0]
206
+ d = se - ss
207
+ self.cursor_position = @selection_end
208
+ ex = @cursor.x
209
+ ew = @cursor.width
210
+ if d > 0 # end is after beginning
211
+ @cursor.x = [0, ex - d].max # ex - d is first character x of selection
212
+ @cursor.width = ex + ew - @cursor.x # width is distance from right side of end char to current cursor.x
213
+ else # end is before beginning
214
+ # cursor.x is where it should be, width is -d
215
+ @cursor.x = ex
216
+ @cursor.width = [@width - ex, -d].min
217
+ end
218
+ @cursor.use_insert
219
+ @text_selected = true
220
+ dispatch_event(
221
+ Event.new(
222
+ :select, :selection => selection,
223
+ :start => @selection_start, :end => @selection_end))
224
+ end
225
+
226
+ def clear_selection(new_cursor_position=[@selection_start, @selection_end].min)
227
+ @selecting = false
228
+ @text_selected = false
229
+ @cursor.use_normal
230
+ self.cursor_position = new_cursor_position
231
+ end
232
+
233
+ def cursor_position_closest_to(cx,cy)
234
+ index = nil
235
+ @text_offsets.each_with_index{|(x,y), i|
236
+ if x + scroll_x > cx
237
+ index = [i-1, 0].max
238
+ break
239
+ end
240
+ }
241
+ index ||= @text.size
242
+ index
243
+ end
244
+
245
+ def text= text
246
+ @text = text
247
+ @text_changed = true
248
+ change!
249
+ end
250
+
251
+ def change!
252
+ dispatch_event(Event.new(:change, :text => @text))
253
+ update_on_frame
254
+ end
255
+
256
+ def update_on_frame
257
+ if not @updater
258
+ @updater = on_frame(:update_text)
259
+ end
260
+ end
261
+
262
+ def scroll_x= sx
263
+ @scroll_x = sx
264
+ update_on_frame
265
+ end
266
+
267
+ def cursor_position= index
268
+ @cursor_position = [[0,index].max, @text.size].min
269
+ @cursor.width = [((@text_offsets[@cursor_position + 1] || [0])[0] - @text_offsets[@cursor_position][0]), 1].max
270
+ x_offset, y_offset = @text_offsets[@cursor_position]
271
+ if @scroll_x + x_offset + @cursor.width > width
272
+ self.scroll_x = -x_offset + (width - 60)
273
+ elsif @scroll_x + x_offset < 0
274
+ self.scroll_x += 60
275
+ self.scroll_x = 0 if @scroll_x > 0
276
+ end
277
+ @cursor.x = @scroll_x + x_offset
278
+ end
279
+
280
+ def layout
281
+ end
282
+
283
+ def update_text(o,e)
284
+ if @text_changed
285
+ @text_offsets = false
286
+ @text_changed = false
287
+ end
288
+ update_text_offsets
289
+ self.cursor_position = self.cursor_position
290
+ update_selection if @selecting
291
+ draw_text
292
+ touch!
293
+ remove_event_listener(:frame, @updater)
294
+ @updater = false
295
+ end
296
+
297
+ def update_text_offsets
298
+ @sdl_font = SDL::TTF.open(font, size)
299
+ @text_offsets ||= (0..text.size).map{|i|
300
+ @sdl_font.text_size(text[0,i])
301
+ }
302
+ end
303
+
304
+ def draw_text x_offset=@scroll_x, y_offset=@scroll_y
305
+ GC.disable
306
+ w,h = @width, @height
307
+ rmask = 0xff000000
308
+ gmask = 0x00ff0000
309
+ bmask = 0x0000ff00
310
+ amask = 0x000000ff
311
+ mask = SDL::Surface.new(
312
+ SDL::SWSURFACE|SDL::SRCALPHA,
313
+ w, h, 32,
314
+ amask,bmask,gmask,rmask)
315
+ mask.fill_rect(0,0, w,h, [0,0,0,255])
316
+ @sdl_font.draw_blended_utf8(
317
+ mask, text,
318
+ x_offset, y_offset,
319
+ 255, 255, 255
320
+ )
321
+ image = SDL::Surface.new(
322
+ SDL::SWSURFACE|SDL::SRCALPHA,
323
+ w, h, 32,
324
+ amask,bmask,gmask,rmask)
325
+ image.fill_rect(0,0, w,h, color.map{|i|(i*255).to_i})
326
+ texture.pixels = as_alpha(image.pixels, mask.pixels)
327
+ GC.enable
328
+ end
329
+
330
+ def as_alpha(pixels, alpha)
331
+ i = 3
332
+ while i < pixels.size
333
+ pixels[i] = alpha[i-1]
334
+ i += 4
335
+ end
336
+ pixels
337
+ end
338
+
339
+ end
340
+
341
+
342
+ # TextEditor is multi-line text editor widget.
343
+ #
344
+ class TextEditor < Widget
345
+ end
346
+
347
+ self.widgets[String] = TextInput
348
+
349
+ end