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.
- checksums.yaml +7 -0
- data/.gitignore +56 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +8 -0
- data/LICENSE +21 -0
- data/README.md +27 -0
- data/Rakefile +4 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/data/NASDAQ.csv +5285 -0
- data/data/Pick4_12_21_2020.txt +9878 -0
- data/lib/wads/app.rb +199 -0
- data/lib/wads/data_structures.rb +251 -0
- data/lib/wads/textinput.rb +87 -0
- data/lib/wads/version.rb +3 -0
- data/lib/wads/widgets.rb +827 -0
- data/lib/wads.rb +7 -0
- data/media/Banner.png +0 -0
- data/run-sample-app +3 -0
- data/sample_app.rb +52 -0
- data/wads.gemspec +37 -0
- metadata +107 -0
@@ -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
|
data/lib/wads/version.rb
ADDED
data/lib/wads/widgets.rb
ADDED
@@ -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
|