wads 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.
@@ -0,0 +1,87 @@
1
+ require 'gosu'
2
+
3
+ class TextField < Gosu::TextInput
4
+ # Some constants that define our appearance.
5
+ INACTIVE_COLOR = 0xcc666666
6
+ ACTIVE_COLOR = 0xcc85929e
7
+ SELECTION_COLOR = 0xcc0000ff
8
+ CARET_COLOR = 0xffffffff
9
+ PADDING = 5
10
+
11
+ attr_reader :x, :y
12
+
13
+ def initialize(window, font, x, y, original_text, default_width)
14
+ super()
15
+
16
+ @window, @font, @x, @y = window, font, x, y
17
+ @default_width = default_width
18
+ self.text = original_text
19
+ end
20
+
21
+ def draw
22
+ # Depending on whether this is the currently selected input or not, change the
23
+ # background's color.
24
+ if @window.text_input == self then
25
+ background_color = ACTIVE_COLOR
26
+ else
27
+ background_color = INACTIVE_COLOR
28
+ end
29
+ @window.draw_quad(x - PADDING, y - PADDING, background_color,
30
+ x + width + PADDING, y - PADDING, background_color,
31
+ x - PADDING, y + height + PADDING, background_color,
32
+ x + width + PADDING, y + height + PADDING, background_color, 9)
33
+
34
+ # Calculate the position of the caret and the selection start.
35
+ pos_x = x + @font.text_width(self.text[0...self.caret_pos])
36
+ sel_x = x + @font.text_width(self.text[0...self.selection_start])
37
+
38
+ # Draw the selection background, if any; if not, sel_x and pos_x will be
39
+ # the same value, making this quad empty.
40
+ @window.draw_quad(sel_x, y, SELECTION_COLOR,
41
+ pos_x, y, SELECTION_COLOR,
42
+ sel_x, y + height, SELECTION_COLOR,
43
+ pos_x, y + height, SELECTION_COLOR, 50)
44
+
45
+ # Draw the caret; again, only if this is the currently selected field.
46
+ if @window.text_input == self then
47
+ @window.draw_line(pos_x, y, CARET_COLOR,
48
+ pos_x, y + height, CARET_COLOR, 50)
49
+ end
50
+
51
+ # Finally, draw the text itself!
52
+ @font.draw_text(self.text, x, y, 50)
53
+ end
54
+
55
+ # This text field grows with the text that's being entered.
56
+ # (Usually one would use clip_to and scroll around on the text field.)
57
+ def width
58
+ text_width = @font.text_width(self.text)
59
+ if text_width > @default_width
60
+ return text_width
61
+ end
62
+ @default_width
63
+ end
64
+
65
+ def height
66
+ @font.height
67
+ end
68
+
69
+ # Hit-test for selecting a text field with the mouse.
70
+ def under_point?(mouse_x, mouse_y)
71
+ mouse_x > x - PADDING and mouse_x < x + width + PADDING and
72
+ mouse_y > y - PADDING and mouse_y < y + height + PADDING
73
+ end
74
+
75
+ # Tries to move the caret to the position specifies by mouse_x
76
+ def move_caret(mouse_x)
77
+ # Test character by character
78
+ 1.upto(self.text.length) do |i|
79
+ if mouse_x < x + @font.text_width(text[0...i]) then
80
+ self.caret_pos = self.selection_start = i - 1;
81
+ return
82
+ end
83
+ end
84
+ # Default case: user must have clicked the right edge
85
+ self.caret_pos = self.selection_start = self.text.length
86
+ end
87
+ end
@@ -0,0 +1,3 @@
1
+ module Wads
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,827 @@
1
+ module Wads
2
+ COLOR_PEACH = Gosu::Color.argb(0xffe6b0aa)
3
+ COLOR_LIGHT_PURPLE = Gosu::Color.argb(0xffd7bde2)
4
+ COLOR_LIGHT_BLUE = Gosu::Color.argb(0xffa9cce3)
5
+ COLOR_LIGHT_GREEN = Gosu::Color.argb(0xffa3e4d7)
6
+ COLOR_LIGHT_YELLOW = Gosu::Color.argb(0xfff9e79f)
7
+ COLOR_LIGHT_ORANGE = Gosu::Color.argb(0xffedbb99)
8
+ COLOR_WHITE = Gosu::Color::WHITE
9
+ COLOR_OFF_WHITE = Gosu::Color.argb(0xfff8f9f9)
10
+ COLOR_PINK = Gosu::Color.argb(0xffe6b0aa)
11
+ COLOR_LIME = Gosu::Color.argb(0xffDAF7A6)
12
+ COLOR_YELLOW = Gosu::Color.argb(0xffFFC300)
13
+ COLOR_MAROON = Gosu::Color.argb(0xffC70039)
14
+ COLOR_LIGHT_GRAY = Gosu::Color.argb(0xff2c3e50)
15
+ COLOR_GRAY = Gosu::Color::GRAY
16
+ COLOR_OFF_GRAY = Gosu::Color.argb(0xff566573)
17
+ COLOR_LIGHT_BLACK = Gosu::Color.argb(0xff111111)
18
+ COLOR_LIGHT_RED = Gosu::Color.argb(0xffe6b0aa)
19
+ COLOR_CYAN = Gosu::Color::CYAN
20
+ COLOR_HEADER_BLUE = Gosu::Color.argb(0xff089FCE)
21
+ COLOR_HEADER_BRIGHT_BLUE = Gosu::Color.argb(0xff0FAADD)
22
+ COLOR_BLUE = Gosu::Color::BLUE
23
+ COLOR_DARK_GRAY = Gosu::Color.argb(0xccf0f3f4)
24
+ COLOR_RED = Gosu::Color::RED
25
+ COLOR_BLACK = Gosu::Color::BLACK
26
+ COLOR_FORM_BUTTON = Gosu::Color.argb(0xcc2e4053)
27
+ COLOR_ERROR_CODE_RED = Gosu::Color.argb(0xffe6b0aa)
28
+
29
+ Z_ORDER_BACKGROUND = 2
30
+ Z_ORDER_WIDGET_BORDER = 3
31
+ Z_ORDER_GRAPHIC_ELEMENTS = 4
32
+ Z_ORDER_SELECTION_BACKGROUND = 5
33
+ Z_ORDER_PLOT_POINTS = 6
34
+ Z_ORDER_OVERLAY_BACKGROUND = 7
35
+ Z_ORDER_OVERLAY_ELEMENTS = 8
36
+ Z_ORDER_TEXT = 10
37
+
38
+ class Widget
39
+ attr_accessor :x
40
+ attr_accessor :y
41
+ attr_accessor :color
42
+ attr_accessor :width
43
+ attr_accessor :height
44
+ attr_accessor :visible
45
+ attr_accessor :children
46
+ attr_accessor :background_color
47
+ attr_accessor :border_color
48
+ attr_accessor :font
49
+
50
+ def initialize(x, y, color = COLOR_CYAN)
51
+ @x = x
52
+ @y = y
53
+ @color = color
54
+ @width = 1
55
+ @height = 1
56
+ @visible = true
57
+ @children = []
58
+ end
59
+
60
+ def add_child(child)
61
+ @children << child
62
+ end
63
+
64
+ def clear_children
65
+ @children = []
66
+ end
67
+
68
+ def set_background(bgcolor)
69
+ @background_color = bgcolor
70
+ end
71
+
72
+ def set_border(bcolor)
73
+ @border_color = bcolor
74
+ end
75
+
76
+ def set_font(font)
77
+ @font = font
78
+ end
79
+
80
+ def set_dimensions(width, height)
81
+ @width = width
82
+ @height = height
83
+ end
84
+
85
+ def right_edge
86
+ @x + @width - 1
87
+ end
88
+
89
+ def bottom_edge
90
+ @y + @height - 1
91
+ end
92
+
93
+ def center_x
94
+ @x + ((right_edge - @x) / 2)
95
+ end
96
+
97
+ def draw
98
+ if @visible
99
+ render
100
+ if @background_color
101
+ draw_background
102
+ end
103
+ if @border_color
104
+ draw_border(@border_color)
105
+ end
106
+ @children.each do |child|
107
+ child.draw
108
+ end
109
+ end
110
+ end
111
+
112
+ def draw_background
113
+ Gosu::draw_rect(@x + 1, @y + 1, @width - 1, @height - 1, @background_color, Z_ORDER_BACKGROUND)
114
+ end
115
+
116
+ def render
117
+ # Base implementation is empty
118
+ # Note that the draw method invoked by clients stills renders any added children
119
+ # render is for specific drawing done by the widget
120
+ end
121
+
122
+ def draw_border(color = nil)
123
+ if color.nil?
124
+ color = @color
125
+ end
126
+ Gosu::draw_line @x, @y, color, right_edge, @y, color, Z_ORDER_WIDGET_BORDER
127
+ Gosu::draw_line @x, @y, color, @x, bottom_edge, color, Z_ORDER_WIDGET_BORDER
128
+ Gosu::draw_line @x,bottom_edge, color, right_edge, bottom_edge, color, Z_ORDER_WIDGET_BORDER
129
+ Gosu::draw_line right_edge, @y, color, right_edge, bottom_edge, color, Z_ORDER_WIDGET_BORDER
130
+ end
131
+
132
+ def contains_click(mouse_x, mouse_y)
133
+ mouse_x >= @x and mouse_x <= right_edge and mouse_y >= @y and mouse_y <= bottom_edge
134
+ end
135
+ end
136
+
137
+ class Text < Widget
138
+ attr_accessor :str
139
+ def initialize(str, x, y, font, color = COLOR_WHITE)
140
+ super(x, y, color)
141
+ set_font(font)
142
+ @str = str
143
+ end
144
+ def render
145
+ @font.draw_text(@str, @x, @y, Z_ORDER_TEXT, 1, 1, @color)
146
+ end
147
+ end
148
+
149
+ class ErrorMessage < Text
150
+ attr_accessor :str
151
+ def initialize(str, x, y, font)
152
+ super("ERROR: #{str}", x, y, font, COLOR_ERROR_CODE_RED)
153
+ set_dimensions(@font.text_width(@str) + 4, 36)
154
+ end
155
+ end
156
+
157
+ class PlotPoint < Widget
158
+ attr_accessor :data_point_size
159
+
160
+ def initialize(x, y, color = COLOR_MAROON, size = 4)
161
+ super(x, y, color)
162
+ @data_point_size = size
163
+ end
164
+
165
+ def render
166
+ @half_size = @data_point_size / 2
167
+ Gosu::draw_rect(@x - @half_size, @y - @half_size,
168
+ @data_point_size, @data_point_size,
169
+ @color, Z_ORDER_PLOT_POINTS)
170
+ end
171
+
172
+ def to_display
173
+ "#{@x}, #{@y}"
174
+ end
175
+
176
+ def increase_size
177
+ @data_point_size = @data_point_size + 2
178
+ end
179
+
180
+ def decrease_size
181
+ if @data_point_size > 2
182
+ @data_point_size = @data_point_size - 2
183
+ end
184
+ end
185
+ end
186
+
187
+ class Button < Widget
188
+ attr_accessor :label
189
+ attr_accessor :is_pressed
190
+
191
+ def initialize(label, x, y, font, width = nil, color = COLOR_DARK_GRAY, text_color = COLOR_HEADER_BRIGHT_BLUE)
192
+ super(x, y, color)
193
+ set_font(font)
194
+ @label = label
195
+ @text_pixel_width = @font.text_width(@label)
196
+ if width.nil?
197
+ @width = @text_pixel_width + 10
198
+ else
199
+ @width = width
200
+ end
201
+ @height = 26
202
+ @is_pressed = false
203
+ @text_color = text_color
204
+ end
205
+
206
+ def render
207
+ draw_border(COLOR_WHITE)
208
+ text_x = center_x - (@text_pixel_width / 2)
209
+ @font.draw_text(@label, text_x, @y, Z_ORDER_TEXT, 1, 1, @text_color)
210
+ end
211
+ end
212
+
213
+ class Document < Widget
214
+ attr_accessor :lines
215
+
216
+ def initialize(content, x, y, width, height, font)
217
+ super(x, y, COLOR_GRAY)
218
+ set_font(font)
219
+ set_dimensions(width, height)
220
+ @lines = content.split("\n")
221
+ end
222
+
223
+ def render
224
+ y = @y + 4
225
+ @lines.each do |line|
226
+ @font.draw_text(line, @x + 5, y, Z_ORDER_TEXT, 1, 1, COLOR_WHITE)
227
+ y = y + 26
228
+ end
229
+ end
230
+ end
231
+
232
+ class InfoBox < Widget
233
+ def initialize(title, content, x, y, font, width, height)
234
+ super(x, y)
235
+ set_font(font)
236
+ set_dimensions(width, height)
237
+ set_border(COLOR_WHITE)
238
+ @title = title
239
+ add_child(Text.new(title, x + 5, y + 5, Gosu::Font.new(32)))
240
+ add_child(Document.new(content, x + 5, y + 52, width, height, font))
241
+ @ok_button = Button.new("OK", center_x - 50, bottom_edge - 26, @font, 100, COLOR_FORM_BUTTON)
242
+ add_child(@ok_button)
243
+ set_background(COLOR_GRAY)
244
+ end
245
+
246
+ def button_down id, mouse_x, mouse_y
247
+ if id == Gosu::KbEscape
248
+ return WidgetResult.new(true)
249
+ elsif id == Gosu::MsLeft
250
+ if @ok_button.contains_click(mouse_x, mouse_y)
251
+ return WidgetResult.new(true)
252
+ end
253
+ end
254
+ WidgetResult.new(false)
255
+ end
256
+ end
257
+
258
+ class Dialog < Widget
259
+ attr_accessor :textinput
260
+
261
+ def initialize(window, font, x, y, width, height, title, text_input_default)
262
+ super(x, y)
263
+ @window = window
264
+ set_font(font)
265
+ set_dimensions(width, height)
266
+ set_background(0xff566573 )
267
+ set_border(COLOR_WHITE)
268
+ @error_message = nil
269
+
270
+ add_child(Text.new(title, x + 5, y + 5, @font))
271
+ # Forms automatically have some explanatory content
272
+ add_child(Document.new(content, x, y + 56, width, height, font))
273
+
274
+ # Forms automatically get a text input widget
275
+ @textinput = TextField.new(@window, @font, x + 10, bottom_edge - 80, text_input_default, 600)
276
+ add_child(@textinput)
277
+
278
+ # Forms automatically get OK and Cancel buttons
279
+ @ok_button = Button.new("OK", center_x - 100, bottom_edge - 26, @font, 100, COLOR_FORM_BUTTON, COLOR_WHITE)
280
+ @cancel_button = Button.new("Cancel", center_x + 50, bottom_edge - 26, @font, 100, COLOR_FORM_BUTTON, COLOR_WHITE)
281
+ add_child(@ok_button)
282
+ add_child(@cancel_button)
283
+ end
284
+
285
+ def content
286
+ <<~HEREDOC
287
+ Override the content method to
288
+ put your info here.
289
+ HEREDOC
290
+ end
291
+
292
+ def add_error_message(msg)
293
+ @error_message = ErrorMessage.new(msg, x + 10, bottom_edge - 120, @font)
294
+ end
295
+
296
+ def render
297
+ if @error_message
298
+ @error_message.draw
299
+ end
300
+ end
301
+
302
+ def handle_ok
303
+ # Default behavior is to do nothing except tell the caller to
304
+ # close the dialog
305
+ return WidgetResult.new(true)
306
+ end
307
+
308
+ def handle_cancel
309
+ # Default behavior is to do nothing except tell the caller to
310
+ # close the dialog
311
+ return WidgetResult.new(true)
312
+ end
313
+
314
+ def handle_up(mouse_x, mouse_y)
315
+ # empty implementation of up arrow
316
+ end
317
+
318
+ def handle_down(mouse_x, mouse_y)
319
+ # empty implementation of down arrow
320
+ end
321
+
322
+ def handle_mouse_click(mouse_x, mouse_y)
323
+ # empty implementation of mouse click outside
324
+ # of standard form elements in this dialog
325
+ end
326
+
327
+ def text_input_updated(text)
328
+ # empty implementation of text being updated
329
+ # in text widget
330
+ end
331
+
332
+ def button_down id, mouse_x, mouse_y
333
+ if id == Gosu::KbEscape
334
+ return WidgetResult.new(true)
335
+ elsif id == Gosu::KbUp
336
+ handle_up(mouse_x, mouse_y)
337
+ elsif id == Gosu::KbDown
338
+ handle_down(mouse_x, mouse_y)
339
+ elsif id == Gosu::MsLeft
340
+ if @ok_button.contains_click(mouse_x, mouse_y)
341
+ return handle_ok
342
+ elsif @cancel_button.contains_click(mouse_x, mouse_y)
343
+ return handle_cancel
344
+ else
345
+ # Mouse click: Select text field based on mouse position.
346
+ @window.text_input = [@textinput].find { |tf| tf.under_point?(mouse_x, mouse_y) }
347
+ # Advanced: Move caret to clicked position
348
+ @window.text_input.move_caret(mouse_x) unless @window.text_input.nil?
349
+
350
+ handle_mouse_click(mouse_x, mouse_y)
351
+ end
352
+ else
353
+ if @window.text_input
354
+ text_input_updated(@textinput.text)
355
+ end
356
+ end
357
+ WidgetResult.new(false)
358
+ end
359
+ end
360
+
361
+ class WidgetResult
362
+ attr_accessor :close_widget
363
+ attr_accessor :action
364
+ attr_accessor :form_data
365
+
366
+ def initialize(close_widget = false, action = "none", form_data = nil)
367
+ @close_widget = close_widget
368
+ @action = action
369
+ @form_data = form_data
370
+ end
371
+ end
372
+
373
+ class Line < Widget
374
+ attr_accessor :x2
375
+ attr_accessor :y2
376
+
377
+ def initialize(x, y, x2, y2, color = COLOR_CYAN)
378
+ super x, y, color
379
+ @x2 = x2
380
+ @y2 = y2
381
+ end
382
+
383
+ def render
384
+ Gosu::draw_line x, y, @color, x2, y2, @color, Z_ORDER_GRAPHIC_ELEMENTS
385
+ end
386
+ end
387
+
388
+ class AxisLines < Widget
389
+ def initialize(x, y, width, height, color = COLOR_CYAN)
390
+ super x, y, color
391
+ @width = width
392
+ @height = height
393
+ end
394
+
395
+ def render
396
+ add_child(Line.new(@x, @y, @x, bottom_edge, @color))
397
+ add_child(Line.new(@x, bottom_edge, right_edge, bottom_edge, @color))
398
+ end
399
+ end
400
+
401
+ class VerticalAxisLabel < Widget
402
+ attr_accessor :label
403
+
404
+ def initialize(x, y, label, font, color = COLOR_CYAN)
405
+ super x, y, color
406
+ set_font(font)
407
+ @label = label
408
+ end
409
+
410
+ def render
411
+ text_pixel_width = @font.text_width(@label)
412
+ Gosu::draw_line @x - 20, @y, @color,
413
+ @x, @y, @color, Z_ORDER_GRAPHIC_ELEMENTS
414
+
415
+ @font.draw_text(@label, @x - text_pixel_width - 28, @y - 12, 1, 1, 1, @color)
416
+ end
417
+ end
418
+
419
+ class HorizontalAxisLabel < Widget
420
+ attr_accessor :label
421
+
422
+ def initialize(x, y, label, font, color = COLOR_CYAN)
423
+ super x, y, color
424
+ set_font(font)
425
+ @label = label
426
+ end
427
+
428
+ def render
429
+ text_pixel_width = @font.text_width(@label)
430
+ Gosu::draw_line @x, @y, @color, @x, @y + 20, @color
431
+ @font.draw_text(@label, @x - (text_pixel_width / 2), @y + 26, Z_ORDER_TEXT, 1, 1, @color)
432
+ end
433
+ end
434
+
435
+ class Table < Widget
436
+ attr_accessor :data_rows
437
+ attr_accessor :row_colors
438
+ attr_accessor :headers
439
+ attr_accessor :max_visible_rows
440
+ attr_accessor :current_row
441
+
442
+ def initialize(x, y, width, height, headers, font, color = COLOR_GRAY, max_visible_rows = 10)
443
+ super(x, y, color)
444
+ set_font(font)
445
+ set_dimensions(width, height)
446
+ @headers = headers
447
+ @current_row = 0
448
+ @max_visible_rows = max_visible_rows
449
+ clear_rows
450
+ end
451
+
452
+ def scroll_up
453
+ if @current_row > 0
454
+ @current_row = @current_row - @max_visible_rows
455
+ end
456
+ end
457
+
458
+ def scroll_down
459
+ if @current_row < @data_rows.size - 1
460
+ @current_row = @current_row + @max_visible_rows
461
+ end
462
+ end
463
+
464
+ def clear_rows
465
+ @data_rows = []
466
+ @row_colors = []
467
+ end
468
+
469
+ def add_row(data_row, color = @color)
470
+ @data_rows << data_row
471
+ @row_colors << color
472
+ end
473
+
474
+ def number_of_rows
475
+ @data_rows.size
476
+ end
477
+
478
+ def render
479
+ draw_border
480
+ return unless number_of_rows > 0
481
+
482
+ column_widths = []
483
+ number_of_columns = @data_rows[0].size
484
+ (0..number_of_columns-1).each do |c|
485
+ max_length = @font.text_width(headers[c])
486
+ (0..number_of_rows-1).each do |r|
487
+ text_pixel_width = @font.text_width(@data_rows[r][c])
488
+ if text_pixel_width > max_length
489
+ max_length = text_pixel_width
490
+ end
491
+ end
492
+ column_widths[c] = max_length
493
+ end
494
+
495
+ x = @x + 10
496
+ if number_of_columns > 1
497
+ (0..number_of_columns-2).each do |c|
498
+ x = x + column_widths[c] + 20
499
+ Gosu::draw_line x, @y, @color, x, @y + @height, @color, Z_ORDER_GRAPHIC_ELEMENTS
500
+ end
501
+ end
502
+
503
+ y = @y
504
+ x = @x + 20
505
+ (0..number_of_columns-1).each do |c|
506
+ @font.draw_text(@headers[c], x, y, Z_ORDER_TEXT, 1, 1, @color)
507
+ x = x + column_widths[c] + 20
508
+ end
509
+ y = y + 30
510
+
511
+ count = 0
512
+ @data_rows.each do |row|
513
+ if count < @current_row
514
+ # skip
515
+ elsif count < @current_row + @max_visible_rows
516
+ x = @x + 20
517
+ (0..number_of_columns-1).each do |c|
518
+ @font.draw_text(row[c], x, y + 2, Z_ORDER_TEXT, 1, 1, @row_colors[count])
519
+ x = x + column_widths[c] + 20
520
+ end
521
+ y = y + 30
522
+ end
523
+ count = count + 1
524
+ end
525
+ end
526
+
527
+ def determine_row_number(mouse_y)
528
+ relative_y = mouse_y - @y
529
+ row_number = (relative_y / 30).floor - 1
530
+ if row_number < 0 or row_number > data_rows.size - 1
531
+ return nil
532
+ end
533
+ row_number
534
+ end
535
+ end
536
+
537
+ class SingleSelectTable < Table
538
+ attr_accessor :selected_row
539
+ attr_accessor :selected_color
540
+
541
+ def initialize(x, y, width, height, headers, font, color = COLOR_GRAY, max_visible_rows = 10)
542
+ super(x, y, width, height, headers, font, color, max_visible_rows)
543
+ @selected_color = COLOR_BLACK
544
+ end
545
+
546
+ def set_selected_row(mouse_y, column_number)
547
+ row_number = determine_row_number(mouse_y)
548
+ if not row_number.nil?
549
+ @selected_row = @current_row + row_number
550
+ @data_rows[@selected_row][column_number]
551
+ end
552
+ end
553
+
554
+ def render
555
+ super
556
+ if @selected_row
557
+ if @selected_row >= @current_row and @selected_row < @current_row + @max_visible_rows
558
+ y = @y + 30 + ((@selected_row - @current_row) * 30)
559
+ Gosu::draw_rect(@x + 20, y, @width - 30, 28, @selected_color, Z_ORDER_SELECTION_BACKGROUND)
560
+ end
561
+ end
562
+ end
563
+ end
564
+
565
+ class MultiSelectTable < Table
566
+ attr_accessor :selected_rows
567
+ attr_accessor :selection_color
568
+
569
+ def initialize(x, y, width, height, headers, font, color = COLOR_GRAY, max_visible_rows = 10)
570
+ super(x, y, width, height, headers, font, color, max_visible_rows)
571
+ @selected_rows = []
572
+ @selection_color = COLOR_LIGHT_GRAY
573
+ end
574
+
575
+ def is_row_selected(mouse_y)
576
+ row_number = determine_row_number(mouse_y)
577
+ @selected_rows.include?(@current_row + row_number)
578
+ end
579
+
580
+ def set_selected_row(mouse_y, column_number)
581
+ row_number = determine_row_number(mouse_y)
582
+ if not row_number.nil?
583
+ this_selected_row = @current_row + row_number
584
+ @selected_rows << this_selected_row
585
+ return @data_rows[this_selected_row][column_number]
586
+ end
587
+ nil
588
+ end
589
+
590
+ def unset_selected_row(mouse_y, column_number)
591
+ row_number = determine_row_number(mouse_y)
592
+ if not row_number.nil?
593
+ this_selected_row = @current_row + row_number
594
+ @selected_rows.delete(this_selected_row)
595
+ return @data_rows[this_selected_row][column_number]
596
+ end
597
+ nil
598
+ end
599
+
600
+ def render
601
+ super
602
+ y = @y + 30
603
+ row_count = @current_row
604
+ while row_count < @data_rows.size
605
+ if @selected_rows.include? row_count
606
+ Gosu::draw_rect(@x + 20, y, @width - 3, 28, @selection_color, Z_ORDER_SELECTION_BACKGROUND)
607
+ end
608
+ y = y + 30
609
+ row_count = row_count + 1
610
+ end
611
+ end
612
+ end
613
+
614
+ class Plot < Widget
615
+ attr_accessor :points
616
+ attr_accessor :visible_range
617
+ attr_accessor :display_grid
618
+ attr_accessor :display_lines
619
+ attr_accessor :zoom_level
620
+
621
+ def initialize(x, y, width, height, font)
622
+ super x, y, color
623
+ set_font(font)
624
+ set_dimensions(width, height)
625
+ @display_grid = false
626
+ @display_lines = true
627
+ @data_set_hash = {}
628
+ @grid_line_color = COLOR_CYAN
629
+ @cursor_line_color = COLOR_DARK_GRAY
630
+ @zero_line_color = COLOR_BLUE
631
+ @zoom_level = 1
632
+ end
633
+
634
+ def increase_data_point_size
635
+ @data_set_hash.keys.each do |key|
636
+ data_set = @data_set_hash[key]
637
+ data_set.rendered_points.each do |point|
638
+ point.increase_size
639
+ end
640
+ end
641
+ end
642
+
643
+ def decrease_data_point_size
644
+ @data_set_hash.keys.each do |key|
645
+ data_set = @data_set_hash[key]
646
+ data_set.rendered_points.each do |point|
647
+ point.decrease_size
648
+ end
649
+ end
650
+ end
651
+
652
+ def zoom_out
653
+ @zoom_level = @zoom_level + 0.1
654
+ visible_range.scale(@zoom_level)
655
+ end
656
+
657
+ def zoom_in
658
+ if @zoom_level > 0.11
659
+ @zoom_level = @zoom_level - 0.1
660
+ end
661
+ visible_range.scale(@zoom_level)
662
+ end
663
+
664
+ def scroll_up
665
+ visible_range.scroll_up
666
+ end
667
+
668
+ def scroll_down
669
+ visible_range.scroll_down
670
+ end
671
+
672
+ def scroll_right
673
+ visible_range.scroll_right
674
+ end
675
+
676
+ def scroll_left
677
+ visible_range.scroll_left
678
+ end
679
+
680
+ def define_range(range)
681
+ @visible_range = range
682
+ @zoom_level = 1
683
+ @data_set_hash.keys.each do |key|
684
+ data_set = @data_set_hash[key]
685
+ puts "Calling derive values on #{key}"
686
+ data_set.derive_values(range, @data_set_hash)
687
+ end
688
+ end
689
+
690
+ def range_set?
691
+ not @visible_range.nil?
692
+ end
693
+
694
+ def is_on_screen(point)
695
+ point.x >= @visible_range.left_x and point.x <= @visible_range.right_x and point.y >= @visible_range.bottom_y and point.y <= @visible_range.top_y
696
+ end
697
+
698
+ def add_data_set(data_set)
699
+ if range_set?
700
+ @data_set_hash[data_set.name] = data_set
701
+ data_set.clear_rendered_points
702
+ data_set.derive_values(@visible_range, @data_set_hash)
703
+ data_set.data_points.each do |point|
704
+ if is_on_screen(point)
705
+ #puts "Adding render point at x #{point.x}, #{Time.at(point.x)}"
706
+ #puts "Visible range: #{Time.at(@visible_range.left_x)} #{Time.at(@visible_range.right_x)}"
707
+ data_set.add_rendered_point PlotPoint.new(draw_x(point.x), draw_y(point.y), data_set.color, data_set.data_point_size)
708
+ end
709
+ end
710
+ else
711
+ puts "ERROR: range not set, cannot add data"
712
+ end
713
+ end
714
+
715
+ def x_val_to_pixel(val)
716
+ x_pct = (@visible_range.right_x - val).to_f / @visible_range.x_range
717
+ @width - (@width.to_f * x_pct).round
718
+ end
719
+
720
+ def y_val_to_pixel(val)
721
+ y_pct = (@visible_range.top_y - val).to_f / @visible_range.y_range
722
+ (@height.to_f * y_pct).round
723
+ end
724
+
725
+ def x_pixel_to_screen(x)
726
+ @x + x
727
+ end
728
+
729
+ def y_pixel_to_screen(y)
730
+ @y + y
731
+ end
732
+
733
+ def draw_x(x)
734
+ x_pixel_to_screen(x_val_to_pixel(x))
735
+ end
736
+
737
+ def draw_y(y)
738
+ y_pixel_to_screen(y_val_to_pixel(y))
739
+ end
740
+
741
+ def render
742
+ @data_set_hash.keys.each do |key|
743
+ data_set = @data_set_hash[key]
744
+ if data_set.visible
745
+ data_set.rendered_points.each do |point|
746
+ point.draw
747
+ end
748
+ if @display_lines
749
+ display_lines_for_point_set(data_set.rendered_points)
750
+ end
751
+ end
752
+ end
753
+ if @display_grid and range_set?
754
+ display_grid_lines
755
+ end
756
+ end
757
+
758
+ def display_lines_for_point_set(points)
759
+ if points.length > 1
760
+ points.inject(points[0]) do |last, the_next|
761
+ Gosu::draw_line last.x, last.y, last.color,
762
+ the_next.x, the_next.y, last.color, Z_ORDER_GRAPHIC_ELEMENTS
763
+ the_next
764
+ end
765
+ end
766
+ end
767
+
768
+ def display_grid_lines
769
+ # TODO this is bnot working well for large ranges with the given increment of 1
770
+ # We don't want to draw hundreds of grid lines
771
+ grid_widgets = []
772
+
773
+ grid_x = @visible_range.left_x
774
+ grid_y = @visible_range.bottom_y + 1
775
+ while grid_y < @visible_range.top_y
776
+ dx = draw_x(grid_x)
777
+ dy = draw_y(grid_y)
778
+ last_x = draw_x(@visible_range.right_x)
779
+ color = @grid_line_color
780
+ if grid_y == 0 and grid_y != @visible_range.bottom_y.to_i
781
+ color = @zero_line_color
782
+ end
783
+ grid_widgets << Line.new(dx, dy, last_x, dy, color)
784
+ grid_y = grid_y + 1
785
+ end
786
+ grid_x = @visible_range.left_x + 1
787
+ grid_y = @visible_range.bottom_y
788
+ while grid_x < @visible_range.right_x
789
+ dx = draw_x(grid_x)
790
+ dy = draw_y(grid_y)
791
+ last_y = draw_y(@visible_range.top_y)
792
+ color = @grid_line_color
793
+ if grid_x == 0 and grid_x != @visible_range.left_x.to_i
794
+ color = @zero_line_color
795
+ end
796
+ grid_widgets << Line.new(dx, dy, dx, last_y, color)
797
+ grid_x = grid_x + 1
798
+ end
799
+
800
+ grid_widgets.each do |gw|
801
+ gw.draw
802
+ end
803
+ end
804
+
805
+ def get_x_data_val(mouse_x)
806
+ graph_x = mouse_x - @x
807
+ x_pct = (@width - graph_x).to_f / @width.to_f
808
+ x_val = @visible_range.right_x - (x_pct * @visible_range.x_range)
809
+ x_val
810
+ end
811
+
812
+ def get_y_data_val(mouse_y)
813
+ graph_y = mouse_y - @y
814
+ y_pct = graph_y.to_f / @height.to_f
815
+ y_val = @visible_range.top_y - (y_pct * @visible_range.y_range)
816
+ y_val
817
+ end
818
+
819
+ def draw_cursor_lines(mouse_x, mouse_y)
820
+ Gosu::draw_line mouse_x, y_pixel_to_screen(0), @cursor_line_color, mouse_x, y_pixel_to_screen(@height), @cursor_line_color, Z_ORDER_GRAPHIC_ELEMENTS
821
+ Gosu::draw_line x_pixel_to_screen(0), mouse_y, @cursor_line_color, x_pixel_to_screen(@width), mouse_y, @cursor_line_color, Z_ORDER_GRAPHIC_ELEMENTS
822
+
823
+ # Return the data values at this point, so the plotter can display them
824
+ [get_x_data_val(mouse_x), get_y_data_val(mouse_y)]
825
+ end
826
+ end
827
+ end