wads 0.1.2 → 0.2.2
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 +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +42 -0
- data/data/sample_graph.csv +11 -0
- data/lib/wads/app.rb +53 -320
- data/lib/wads/data_structures.rb +248 -18
- data/lib/wads/textinput.rb +20 -14
- data/lib/wads/version.rb +1 -1
- data/lib/wads/widgets.rb +2165 -216
- data/lib/wads.rb +1 -1
- data/media/CircleAlpha.png +0 -0
- data/media/CircleAqua.png +0 -0
- data/media/CircleBlue.png +0 -0
- data/media/CircleGray.png +0 -0
- data/media/CircleGreen.png +0 -0
- data/media/CirclePurple.png +0 -0
- data/media/CircleRed.png +0 -0
- data/media/CircleWhite.png +0 -0
- data/media/CircleYellow.png +0 -0
- data/media/GreenLight.png +0 -0
- data/media/RedLight.png +0 -0
- data/media/SampleGraph.png +0 -0
- data/media/StocksSample.png +0 -0
- data/media/WadsScreenshot.png +0 -0
- data/media/YellowLight.png +0 -0
- data/run-graph +3 -0
- data/run-star-wars +3 -0
- data/run-stocks +3 -0
- data/run-theme-test +3 -0
- data/samples/basic_gosu_with_graph_widget.rb +66 -0
- data/samples/gosu_bouncing_ball.rb +34 -0
- data/samples/gosu_hello_world.rb +16 -0
- data/samples/gosu_reaction_time.rb +121 -0
- data/samples/graph.rb +72 -0
- data/samples/star_wars.rb +112 -0
- data/samples/stocks.rb +126 -0
- data/samples/theme_test.rb +256 -0
- data/samples/wads_reaction_time.rb +117 -0
- metadata +31 -5
- data/run-sample-app +0 -3
- data/sample_app.rb +0 -64
data/lib/wads/widgets.rb
CHANGED
@@ -1,10 +1,17 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'logger'
|
1
3
|
require_relative 'data_structures'
|
2
4
|
|
5
|
+
#
|
6
|
+
# All wads classes are contained within the wads module.
|
7
|
+
#
|
3
8
|
module Wads
|
4
9
|
COLOR_PEACH = Gosu::Color.argb(0xffe6b0aa)
|
5
10
|
COLOR_LIGHT_PURPLE = Gosu::Color.argb(0xffd7bde2)
|
6
11
|
COLOR_LIGHT_BLUE = Gosu::Color.argb(0xffa9cce3)
|
12
|
+
COLOR_VERY_LIGHT_BLUE = Gosu::Color.argb(0xffd0def5)
|
7
13
|
COLOR_LIGHT_GREEN = Gosu::Color.argb(0xffa3e4d7)
|
14
|
+
COLOR_GREEN = COLOR_LIGHT_GREEN
|
8
15
|
COLOR_LIGHT_YELLOW = Gosu::Color.argb(0xfff9e79f)
|
9
16
|
COLOR_LIGHT_ORANGE = Gosu::Color.argb(0xffedbb99)
|
10
17
|
COLOR_WHITE = Gosu::Color::WHITE
|
@@ -13,12 +20,16 @@ module Wads
|
|
13
20
|
COLOR_LIME = Gosu::Color.argb(0xffDAF7A6)
|
14
21
|
COLOR_YELLOW = Gosu::Color.argb(0xffFFC300)
|
15
22
|
COLOR_MAROON = Gosu::Color.argb(0xffC70039)
|
23
|
+
COLOR_PURPLE = COLOR_MAROON
|
16
24
|
COLOR_LIGHT_GRAY = Gosu::Color.argb(0xff2c3e50)
|
25
|
+
COLOR_LIGHTER_GRAY = Gosu::Color.argb(0xff364d63)
|
26
|
+
COLOR_LIGHTEST_GRAY = Gosu::Color.argb(0xff486684)
|
17
27
|
COLOR_GRAY = Gosu::Color::GRAY
|
18
28
|
COLOR_OFF_GRAY = Gosu::Color.argb(0xff566573)
|
19
29
|
COLOR_LIGHT_BLACK = Gosu::Color.argb(0xff111111)
|
20
30
|
COLOR_LIGHT_RED = Gosu::Color.argb(0xffe6b0aa)
|
21
31
|
COLOR_CYAN = Gosu::Color::CYAN
|
32
|
+
COLOR_AQUA = COLOR_CYAN
|
22
33
|
COLOR_HEADER_BLUE = Gosu::Color.argb(0xff089FCE)
|
23
34
|
COLOR_HEADER_BRIGHT_BLUE = Gosu::Color.argb(0xff0FAADD)
|
24
35
|
COLOR_BLUE = Gosu::Color::BLUE
|
@@ -27,6 +38,8 @@ module Wads
|
|
27
38
|
COLOR_BLACK = Gosu::Color::BLACK
|
28
39
|
COLOR_FORM_BUTTON = Gosu::Color.argb(0xcc2e4053)
|
29
40
|
COLOR_ERROR_CODE_RED = Gosu::Color.argb(0xffe6b0aa)
|
41
|
+
COLOR_BORDER_BLUE = Gosu::Color.argb(0xff004D80)
|
42
|
+
COLOR_ALPHA = "alpha"
|
30
43
|
|
31
44
|
Z_ORDER_BACKGROUND = 2
|
32
45
|
Z_ORDER_BORDER = 3
|
@@ -42,33 +55,1070 @@ module Wads
|
|
42
55
|
EVENT_TABLE_UNSELECT = "tableunselect"
|
43
56
|
EVENT_TABLE_ROW_DELETE = "tablerowdelete"
|
44
57
|
|
58
|
+
IMAGE_CIRCLE_SIZE = 104
|
59
|
+
|
60
|
+
ELEMENT_TEXT = "text"
|
61
|
+
ELEMENT_TEXT_INPUT = "text_input"
|
62
|
+
ELEMENT_BUTTON = "button"
|
63
|
+
ELEMENT_IMAGE = "image"
|
64
|
+
ELEMENT_TABLE = "table"
|
65
|
+
ELEMENT_HORIZONTAL_PANEL = "hpanel"
|
66
|
+
ELEMENT_VERTICAL_PANEL = "vpanel"
|
67
|
+
ELEMENT_MAX_PANEL = "maxpanel"
|
68
|
+
ELEMENT_DOCUMENT = "document"
|
69
|
+
ELEMENT_GRAPH = "graph"
|
70
|
+
ELEMENT_GENERIC = "generic"
|
71
|
+
ELEMENT_PLOT = "plot"
|
72
|
+
|
73
|
+
ARG_SECTION = "section"
|
74
|
+
ARG_COLOR = "color"
|
75
|
+
ARG_DESIRED_WIDTH = "desired_width"
|
76
|
+
ARG_DESIRED_HEIGHT = "desired_height"
|
77
|
+
ARG_PANEL_WIDTH = "panel_width"
|
78
|
+
ARG_LAYOUT = "layout"
|
79
|
+
ARG_TEXT_ALIGN = "text_align"
|
80
|
+
ARG_USE_LARGE_FONT = "large_font"
|
81
|
+
ARG_THEME = "theme"
|
82
|
+
|
83
|
+
TEXT_ALIGN_LEFT = "left"
|
84
|
+
TEXT_ALIGN_CENTER = "center"
|
85
|
+
TEXT_ALIGN_RIGHT = "right"
|
86
|
+
|
87
|
+
SECTION_TOP = "north"
|
88
|
+
SECTION_MIDDLE = "center"
|
89
|
+
SECTION_BOTTOM = "south"
|
90
|
+
SECTION_LEFT = "west"
|
91
|
+
SECTION_RIGHT = "east"
|
92
|
+
SECTION_NORTH = SECTION_TOP
|
93
|
+
SECTION_HEADER = SECTION_TOP
|
94
|
+
SECTION_SOUTH = SECTION_BOTTOM
|
95
|
+
SECTION_FOOTER = SECTION_BOTTOM
|
96
|
+
SECTION_WEST = "west"
|
97
|
+
SECTION_EAST = "east"
|
98
|
+
SECTION_CENTER = "center"
|
99
|
+
SECTION_CONTENT = SECTION_CENTER
|
100
|
+
|
101
|
+
LAYOUT_VERTICAL_COLUMN = "vcolumn"
|
102
|
+
LAYOUT_TOP_MIDDLE_BOTTOM = "top_middle_bottom"
|
103
|
+
LAYOUT_HEADER_CONTENT = "header_content"
|
104
|
+
LAYOUT_CONTENT_FOOTER = "content_footer"
|
105
|
+
LAYOUT_BORDER = "border"
|
106
|
+
LAYOUT_EAST_WEST = "east_west"
|
107
|
+
LAYOUT_LEFT_RIGHT = LAYOUT_EAST_WEST
|
108
|
+
|
109
|
+
FILL_VERTICAL_STACK = "fill_vertical"
|
110
|
+
FILL_HORIZONTAL_STACK = "fill_horizontal"
|
111
|
+
FILL_FULL_SIZE = "fill_full_size"
|
112
|
+
|
113
|
+
GRAPH_DISPLAY_ALL = "all"
|
114
|
+
GRAPH_DISPLAY_EXPLORER = "explorer"
|
115
|
+
GRAPH_DISPLAY_TREE = "tree"
|
116
|
+
|
117
|
+
#
|
118
|
+
# An instance of Coordinates references an x, y position on the screen
|
119
|
+
# as well as the width and height of the widget, thus providing the
|
120
|
+
# outer dimensions of a rectangular widget.
|
121
|
+
#
|
122
|
+
class Coordinates
|
123
|
+
attr_accessor :x
|
124
|
+
attr_accessor :y
|
125
|
+
attr_accessor :width
|
126
|
+
attr_accessor :height
|
127
|
+
def initialize(x, y, w, h)
|
128
|
+
@x = x
|
129
|
+
@y = y
|
130
|
+
@width = w
|
131
|
+
@height = h
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
#
|
136
|
+
# An instance of GuiTheme directs wads widgets as to what colors and fonts
|
137
|
+
# should be used. This accomplishes two goals: one, we don't need to constantly
|
138
|
+
# pass around these instances. They can be globally accessed using WadsConfig.
|
139
|
+
# It also makes it easy to change the look and feel of your application.
|
140
|
+
#
|
141
|
+
class GuiTheme
|
142
|
+
attr_accessor :text_color
|
143
|
+
attr_accessor :graphic_elements_color
|
144
|
+
attr_accessor :border_color
|
145
|
+
attr_accessor :background_color
|
146
|
+
attr_accessor :selection_color
|
147
|
+
attr_accessor :use_icons
|
148
|
+
attr_accessor :font
|
149
|
+
attr_accessor :font_large
|
150
|
+
|
151
|
+
def initialize(text, graphics, border, background, selection, use_icons, font, font_large)
|
152
|
+
@text_color = text
|
153
|
+
@graphic_elements_color = graphics
|
154
|
+
@border_color = border
|
155
|
+
@background_color = background
|
156
|
+
@selection_color = selection
|
157
|
+
@use_icons = use_icons
|
158
|
+
@font = font
|
159
|
+
@font_large = font_large
|
160
|
+
end
|
161
|
+
|
162
|
+
def pixel_width_for_string(str)
|
163
|
+
@font.text_width(str)
|
164
|
+
end
|
165
|
+
|
166
|
+
def pixel_width_for_large_font(str)
|
167
|
+
@font_large.text_width(str)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
#
|
172
|
+
# Theme with black text on a white background
|
173
|
+
#
|
174
|
+
class WadsBrightTheme < GuiTheme
|
175
|
+
def initialize
|
176
|
+
super(COLOR_BLACK, # text color
|
177
|
+
COLOR_HEADER_BRIGHT_BLUE, # graphic elements
|
178
|
+
COLOR_BORDER_BLUE, # border color
|
179
|
+
COLOR_WHITE, # background
|
180
|
+
COLOR_VERY_LIGHT_BLUE, # selected item
|
181
|
+
true, # use icons
|
182
|
+
Gosu::Font.new(22), # regular font
|
183
|
+
Gosu::Font.new(38)) # large font
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
class WadsDarkRedBrownTheme < GuiTheme
|
188
|
+
def initialize
|
189
|
+
super(COLOR_WHITE, # text color
|
190
|
+
Gosu::Color.argb(0xffD63D41), # graphic elements - dark red
|
191
|
+
Gosu::Color.argb(0xffEC5633), # border color - dark orange
|
192
|
+
Gosu::Color.argb(0xff52373B), # background - dark brown
|
193
|
+
Gosu::Color.argb(0xffEC5633), # selected item - dark orange
|
194
|
+
true, # use icons
|
195
|
+
Gosu::Font.new(22), # regular font
|
196
|
+
Gosu::Font.new(38)) # large font
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
class WadsEarthTonesTheme < GuiTheme
|
201
|
+
def initialize
|
202
|
+
super(COLOR_WHITE, # text color
|
203
|
+
Gosu::Color.argb(0xffD0605E), # graphic elements
|
204
|
+
Gosu::Color.argb(0xffFF994C), # border color
|
205
|
+
Gosu::Color.argb(0xff98506D), # background
|
206
|
+
Gosu::Color.argb(0xffFF994C), # selected item
|
207
|
+
true, # use icons
|
208
|
+
Gosu::Font.new(22), # regular font
|
209
|
+
Gosu::Font.new(38)) # large font
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
class WadsNatureTheme < GuiTheme
|
214
|
+
def initialize
|
215
|
+
super(COLOR_WHITE, # text color
|
216
|
+
Gosu::Color.argb(0xffA9B40B), # graphic elements
|
217
|
+
Gosu::Color.argb(0xffF38B01), # border color
|
218
|
+
Gosu::Color.argb(0xffFFC001), # background
|
219
|
+
Gosu::Color.argb(0xffF38B01), # selected item
|
220
|
+
true, # use icons
|
221
|
+
Gosu::Font.new(22, { :bold => true}), # regular font
|
222
|
+
Gosu::Font.new(38, { :bold => true})) # large font
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
class WadsPurpleTheme < GuiTheme
|
227
|
+
def initialize
|
228
|
+
super(COLOR_WHITE, # text color
|
229
|
+
Gosu::Color.argb(0xff5A23B4), # graphic elements
|
230
|
+
Gosu::Color.argb(0xffFE01EA), # border color
|
231
|
+
Gosu::Color.argb(0xffAA01FF), # background
|
232
|
+
Gosu::Color.argb(0xffFE01EA), # selected item
|
233
|
+
true, # use icons
|
234
|
+
Gosu::Font.new(22), # regular font
|
235
|
+
Gosu::Font.new(38, { :bold => true})) # large font
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
class WadsAquaTheme < GuiTheme
|
240
|
+
def initialize
|
241
|
+
super(COLOR_WHITE, # text color
|
242
|
+
Gosu::Color.argb(0xff387CA3), # graphic elements
|
243
|
+
Gosu::Color.argb(0xff387CA3), # border color
|
244
|
+
Gosu::Color.argb(0xff52ADC8), # background
|
245
|
+
Gosu::Color.argb(0xff55C39E), # selected item
|
246
|
+
true, # use icons
|
247
|
+
Gosu::Font.new(22), # regular font
|
248
|
+
Gosu::Font.new(38, { :bold => true})) # large font
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
#
|
253
|
+
# Theme with white text on a black background that also does not use icons.
|
254
|
+
# Currently, icons are primarily used in the Graph display widget.
|
255
|
+
#
|
256
|
+
class WadsNoIconTheme < GuiTheme
|
257
|
+
def initialize
|
258
|
+
super(COLOR_WHITE, # text color
|
259
|
+
COLOR_HEADER_BRIGHT_BLUE, # graphic elements
|
260
|
+
COLOR_BORDER_BLUE, # border color
|
261
|
+
COLOR_BLACK, # background
|
262
|
+
COLOR_LIGHT_GRAY, # selected item
|
263
|
+
false, # use icons
|
264
|
+
Gosu::Font.new(22), # regular font
|
265
|
+
Gosu::Font.new(38)) # large font
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
##
|
270
|
+
# WadsConfig is the one singleton that provides access to resources
|
271
|
+
# used throughput the application, including fonts, themes, and layouts.
|
272
|
+
#
|
273
|
+
class WadsConfig
|
274
|
+
include Singleton
|
275
|
+
|
276
|
+
attr_accessor :logger
|
277
|
+
attr_accessor :window
|
278
|
+
|
279
|
+
def get_logger
|
280
|
+
if @logger.nil?
|
281
|
+
@logger = Logger.new(STDOUT)
|
282
|
+
end
|
283
|
+
@logger
|
284
|
+
end
|
285
|
+
|
286
|
+
#
|
287
|
+
# Wads uses the Ruby logger, and you can conrol the log level using this method.
|
288
|
+
# Valid values are 'debug', 'info', 'warn', 'error'
|
289
|
+
def set_log_level(level)
|
290
|
+
get_logger.level = level
|
291
|
+
end
|
292
|
+
|
293
|
+
def set_window(w)
|
294
|
+
@window = w
|
295
|
+
end
|
296
|
+
|
297
|
+
def get_window
|
298
|
+
if @window.nil?
|
299
|
+
raise "The WadsConfig.instance.set_window(window) needs to be invoked first"
|
300
|
+
end
|
301
|
+
@window
|
302
|
+
end
|
303
|
+
|
304
|
+
#
|
305
|
+
# Get the default theme which is white text on a black background
|
306
|
+
# that uses icons (primarily used in the Graph display widget currently)
|
307
|
+
#
|
308
|
+
def get_default_theme
|
309
|
+
if @default_theme.nil?
|
310
|
+
@default_theme = GuiTheme.new(COLOR_WHITE, # text color
|
311
|
+
COLOR_HEADER_BRIGHT_BLUE, # graphic elements
|
312
|
+
COLOR_BORDER_BLUE, # border color
|
313
|
+
COLOR_BLACK, # background
|
314
|
+
COLOR_LIGHT_GRAY, # selected item
|
315
|
+
true, # use icons
|
316
|
+
Gosu::Font.new(22), # regular font
|
317
|
+
Gosu::Font.new(38)) # large font
|
318
|
+
end
|
319
|
+
@default_theme
|
320
|
+
end
|
321
|
+
|
322
|
+
#
|
323
|
+
# Get a reference to the current theme. If one has not been set using
|
324
|
+
# set_current_theme(theme), the default theme will be used.
|
325
|
+
#
|
326
|
+
def current_theme
|
327
|
+
if @current_theme.nil?
|
328
|
+
@current_theme = get_default_theme
|
329
|
+
end
|
330
|
+
@current_theme
|
331
|
+
end
|
332
|
+
|
333
|
+
#
|
334
|
+
# Set the theme to be used by wads widgets
|
335
|
+
#
|
336
|
+
def set_current_theme(theme)
|
337
|
+
@current_theme = theme
|
338
|
+
end
|
339
|
+
|
340
|
+
#
|
341
|
+
# This method returns the default dimensions for the given widget type
|
342
|
+
# as a two value array of the form [width, height].
|
343
|
+
# This helps the layout manager allocate space to widgets within a layout
|
344
|
+
# and container. The string value max tells the layout to use all available
|
345
|
+
# space in that dimension (either x or y)
|
346
|
+
#
|
347
|
+
def default_dimensions(widget_type)
|
348
|
+
if @default_dimensions.nil?
|
349
|
+
@default_dimensions = {}
|
350
|
+
@default_dimensions[ELEMENT_TEXT] = [100, 20]
|
351
|
+
@default_dimensions[ELEMENT_TEXT_INPUT] = [100, 20]
|
352
|
+
@default_dimensions[ELEMENT_IMAGE] = [100, 100]
|
353
|
+
@default_dimensions[ELEMENT_TABLE] = ["max", "max"]
|
354
|
+
@default_dimensions[ELEMENT_HORIZONTAL_PANEL] = ["max", 100]
|
355
|
+
@default_dimensions[ELEMENT_VERTICAL_PANEL] = [100, "max"]
|
356
|
+
@default_dimensions[ELEMENT_MAX_PANEL] = ["max", "max"]
|
357
|
+
@default_dimensions[ELEMENT_DOCUMENT] = ["max", "max"]
|
358
|
+
@default_dimensions[ELEMENT_GRAPH] = ["max", "max"]
|
359
|
+
@default_dimensions[ELEMENT_BUTTON] = [100, 26]
|
360
|
+
@default_dimensions[ELEMENT_GENERIC] = ["max", "max"]
|
361
|
+
@default_dimensions[ELEMENT_PLOT] = ["max", "max"]
|
362
|
+
end
|
363
|
+
@default_dimensions[widget_type]
|
364
|
+
end
|
365
|
+
|
366
|
+
def create_layout_for_widget(widget, layout_type = nil, args = {})
|
367
|
+
create_layout(widget.x, widget.y, widget.width, widget.height, widget, layout_type, args)
|
368
|
+
end
|
369
|
+
|
370
|
+
def create_layout(x, y, width, height, widget, layout_type = nil, args = {})
|
371
|
+
if layout_type.nil?
|
372
|
+
if @default_layout_type.nil?
|
373
|
+
layout_type = LAYOUT_VERTICAL_COLUMN
|
374
|
+
else
|
375
|
+
layout_type = @default_layout_type
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
if not @default_layout_args.nil?
|
380
|
+
if args.nil?
|
381
|
+
args = @default_layout_args
|
382
|
+
else
|
383
|
+
args.merge(@default_layout_args)
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
if layout_type == LAYOUT_VERTICAL_COLUMN
|
388
|
+
return VerticalColumnLayout.new(x, y, width, height, widget, args)
|
389
|
+
elsif layout_type == LAYOUT_TOP_MIDDLE_BOTTOM
|
390
|
+
return TopMiddleBottomLayout.new(x, y, width, height, widget, args)
|
391
|
+
elsif layout_type == LAYOUT_BORDER
|
392
|
+
return BorderLayout.new(x, y, width, height, widget, args)
|
393
|
+
elsif layout_type == LAYOUT_HEADER_CONTENT
|
394
|
+
return HeaderContentLayout.new(x, y, width, height, widget, args)
|
395
|
+
elsif layout_type == LAYOUT_CONTENT_FOOTER
|
396
|
+
return ContentFooterLayout.new(x, y, width, height, widget, args)
|
397
|
+
elsif layout_type == LAYOUT_EAST_WEST
|
398
|
+
return EastWestLayout.new(x, y, width, height, widget, args)
|
399
|
+
end
|
400
|
+
raise "#{layout_type} is an unsupported layout type"
|
401
|
+
end
|
402
|
+
|
403
|
+
def set_default_layout(layout_type, layout_args = {})
|
404
|
+
@default_layout_type = layout_type
|
405
|
+
@default_layout_args = layout_args
|
406
|
+
end
|
407
|
+
|
408
|
+
#
|
409
|
+
# Get a Gosu images instance for the specified color, i.e. COLOR_AQUA ir COLOR_BLUE
|
410
|
+
#
|
411
|
+
def circle(color)
|
412
|
+
create_circles
|
413
|
+
if color.nil?
|
414
|
+
return nil
|
415
|
+
end
|
416
|
+
img = @wads_image_circles[color]
|
417
|
+
if img.nil?
|
418
|
+
get_logger.error("ERROR: Did not find circle image with color #{color}")
|
419
|
+
end
|
420
|
+
img
|
421
|
+
end
|
422
|
+
|
423
|
+
def create_circles
|
424
|
+
return unless @wads_image_circles.nil?
|
425
|
+
@wads_image_circle_aqua = Gosu::Image.new("./media/CircleAqua.png")
|
426
|
+
@wads_image_circle_blue = Gosu::Image.new("./media/CircleBlue.png")
|
427
|
+
@wads_image_circle_green = Gosu::Image.new("./media/CircleGreen.png")
|
428
|
+
@wads_image_circle_purple = Gosu::Image.new("./media/CirclePurple.png")
|
429
|
+
@wads_image_circle_red = Gosu::Image.new("./media/CircleRed.png")
|
430
|
+
@wads_image_circle_yellow = Gosu::Image.new("./media/CircleYellow.png")
|
431
|
+
@wads_image_circle_gray = Gosu::Image.new("./media/CircleGray.png")
|
432
|
+
@wads_image_circle_white = Gosu::Image.new("./media/CircleWhite.png")
|
433
|
+
@wads_image_circle_alpha = Gosu::Image.new("./media/CircleAlpha.png")
|
434
|
+
@wads_image_circles = {}
|
435
|
+
@wads_image_circles[COLOR_AQUA] = @wads_image_circle_aqua
|
436
|
+
@wads_image_circles[COLOR_BLUE] = @wads_image_circle_blue
|
437
|
+
@wads_image_circles[COLOR_GREEN] = @wads_image_circle_green
|
438
|
+
@wads_image_circles[COLOR_PURPLE] = @wads_image_circle_purple
|
439
|
+
@wads_image_circles[COLOR_RED] = @wads_image_circle_red
|
440
|
+
@wads_image_circles[COLOR_YELLOW] = @wads_image_circle_yellow
|
441
|
+
@wads_image_circles[COLOR_GRAY] = @wads_image_circle_gray
|
442
|
+
@wads_image_circles[COLOR_WHITE] = @wads_image_circle_white
|
443
|
+
@wads_image_circles[COLOR_ALPHA] = @wads_image_circle_alpha
|
444
|
+
@wads_image_circles[4294956800] = @wads_image_circle_yellow
|
445
|
+
@wads_image_circles[4281893349] = @wads_image_circle_blue
|
446
|
+
@wads_image_circles[4294967295] = @wads_image_circle_gray
|
447
|
+
@wads_image_circles[4286611584] = @wads_image_circle_gray
|
448
|
+
@wads_image_circles[4282962380] = @wads_image_circle_aqua
|
449
|
+
@wads_image_circles[4294939648] = @wads_image_circle_red
|
450
|
+
@wads_image_circles[4292664540] = @wads_image_circle_white
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
#
|
455
|
+
# A Gui container is used to allocate space in the x, y two dimensional space to widgets
|
456
|
+
# and keep track of where the next widget in the container will be placed.
|
457
|
+
# The fill type is one of FILL_VERTICAL_STACK, FILL_HORIZONTAL_STACK, or FILL_FULL_SIZE.
|
458
|
+
# Layouts used containers to allocate space across the entire visible application.
|
459
|
+
#
|
460
|
+
class GuiContainer
|
461
|
+
attr_accessor :start_x
|
462
|
+
attr_accessor :start_y
|
463
|
+
attr_accessor :next_x
|
464
|
+
attr_accessor :next_y
|
465
|
+
attr_accessor :max_width
|
466
|
+
attr_accessor :max_height
|
467
|
+
attr_accessor :padding
|
468
|
+
attr_accessor :fill_type
|
469
|
+
attr_accessor :elements
|
470
|
+
|
471
|
+
def initialize(start_x, start_y, width, height, fill_type = FILL_HORIZONTAL_STACK, padding = 5)
|
472
|
+
@start_x = start_x
|
473
|
+
@start_y = start_y
|
474
|
+
@next_x = start_x
|
475
|
+
@next_y = start_y
|
476
|
+
@max_width = width
|
477
|
+
@max_height = height
|
478
|
+
@padding = padding
|
479
|
+
if [FILL_VERTICAL_STACK, FILL_HORIZONTAL_STACK, FILL_FULL_SIZE].include? fill_type
|
480
|
+
@fill_type = fill_type
|
481
|
+
else
|
482
|
+
raise "#{fill_type} is not a valid fill type"
|
483
|
+
end
|
484
|
+
@elements = []
|
485
|
+
end
|
486
|
+
|
487
|
+
def get_coordinates(element_type, args = {})
|
488
|
+
default_dim = WadsConfig.instance.default_dimensions(element_type)
|
489
|
+
if default_dim.nil?
|
490
|
+
raise "#{element_type} is an undefined element type"
|
491
|
+
end
|
492
|
+
default_width = default_dim[0]
|
493
|
+
default_height = default_dim[1]
|
494
|
+
specified_width = args[ARG_DESIRED_WIDTH]
|
495
|
+
if specified_width.nil?
|
496
|
+
if default_width == "max"
|
497
|
+
if fill_type == FILL_VERTICAL_STACK or fill_type == FILL_FULL_SIZE
|
498
|
+
the_width = max_width
|
499
|
+
else
|
500
|
+
the_width = (@start_x + @max_width) - @next_x
|
501
|
+
end
|
502
|
+
else
|
503
|
+
the_width = default_width
|
504
|
+
end
|
505
|
+
else
|
506
|
+
if specified_width > @max_width
|
507
|
+
the_width = @max_width
|
508
|
+
else
|
509
|
+
the_width = specified_width
|
510
|
+
end
|
511
|
+
end
|
512
|
+
|
513
|
+
specified_height = args[ARG_DESIRED_HEIGHT]
|
514
|
+
if specified_height.nil?
|
515
|
+
if default_height == "max"
|
516
|
+
if fill_type == FILL_VERTICAL_STACK
|
517
|
+
the_height = (@start_y + @max_height) - @next_y
|
518
|
+
else
|
519
|
+
the_height = max_height
|
520
|
+
end
|
521
|
+
else
|
522
|
+
the_height = default_height
|
523
|
+
end
|
524
|
+
else
|
525
|
+
if specified_height > @max_height
|
526
|
+
the_height = @max_height
|
527
|
+
else
|
528
|
+
the_height = specified_height
|
529
|
+
end
|
530
|
+
end
|
531
|
+
|
532
|
+
# Not all elements require padding
|
533
|
+
padding_exempt = [ELEMENT_IMAGE, ELEMENT_HORIZONTAL_PANEL, ELEMENT_PLOT,
|
534
|
+
ELEMENT_VERTICAL_PANEL, ELEMENT_GENERIC, ELEMENT_MAX_PANEL].include? element_type
|
535
|
+
if padding_exempt
|
536
|
+
# No padding
|
537
|
+
width_to_use = the_width
|
538
|
+
height_to_use = the_height
|
539
|
+
x_to_use = @next_x
|
540
|
+
y_to_use = @next_y
|
541
|
+
else
|
542
|
+
# Apply padding only if we are the max, i.e. the boundaries
|
543
|
+
x_to_use = @next_x + @padding
|
544
|
+
y_to_use = @next_y + @padding
|
545
|
+
if the_width == @max_width
|
546
|
+
width_to_use = the_width - (2 * @padding)
|
547
|
+
else
|
548
|
+
width_to_use = the_width
|
549
|
+
end
|
550
|
+
if the_height == @max_height
|
551
|
+
height_to_use = the_height - (2 * @padding)
|
552
|
+
else
|
553
|
+
height_to_use = the_height
|
554
|
+
end
|
555
|
+
end
|
556
|
+
|
557
|
+
# Text elements also honor ARG_TEXT_ALIGN
|
558
|
+
arg_text_align = args[ARG_TEXT_ALIGN]
|
559
|
+
if not arg_text_align.nil?
|
560
|
+
# left is the default, so check for center or right
|
561
|
+
if arg_text_align == TEXT_ALIGN_CENTER
|
562
|
+
x_to_use = @next_x + ((@max_width - specified_width) / 2)
|
563
|
+
elsif arg_text_align == TEXT_ALIGN_RIGHT
|
564
|
+
x_to_use = @next_x + @max_width - specified_width - @padding
|
565
|
+
end
|
566
|
+
end
|
567
|
+
|
568
|
+
coords = Coordinates.new(x_to_use, y_to_use,
|
569
|
+
width_to_use, height_to_use)
|
570
|
+
|
571
|
+
if fill_type == FILL_VERTICAL_STACK
|
572
|
+
@next_y = @next_y + the_height + (2 * @padding)
|
573
|
+
elsif fill_type == FILL_HORIZONTAL_STACK
|
574
|
+
@next_x = @next_x + the_width + (2 * @padding)
|
575
|
+
end
|
576
|
+
|
577
|
+
@elements << coords
|
578
|
+
coords
|
579
|
+
end
|
580
|
+
end
|
581
|
+
|
582
|
+
# The base class for all wads layouts. It has helper methods to add
|
583
|
+
# different types of widgets to the layout.
|
584
|
+
class WadsLayout
|
585
|
+
attr_accessor :border_coords
|
586
|
+
attr_accessor :parent_widget
|
587
|
+
attr_accessor :args
|
588
|
+
|
589
|
+
def initialize(x, y, width, height, parent_widget, args = {})
|
590
|
+
@border_coords = Coordinates.new(x, y, width, height)
|
591
|
+
@parent_widget = parent_widget
|
592
|
+
@args = args
|
593
|
+
end
|
594
|
+
|
595
|
+
def get_coordinates(element_type, args = {})
|
596
|
+
raise "You must use a subclass of WadsLayout"
|
597
|
+
end
|
598
|
+
|
599
|
+
def add_widget(widget, args = {})
|
600
|
+
# The widget already has an x, y position, so we need to move it
|
601
|
+
# based on the layout
|
602
|
+
coordinates = get_coordinates(ELEMENT_GENERIC, args)
|
603
|
+
widget.move_recursive_absolute(coordinates.x, coordinates.y)
|
604
|
+
widget.base_z = @parent_widget.base_z
|
605
|
+
@parent_widget.add_child(widget)
|
606
|
+
widget
|
607
|
+
end
|
608
|
+
|
609
|
+
def add_text(message, args = {})
|
610
|
+
default_dimensions = WadsConfig.instance.default_dimensions(ELEMENT_TEXT)
|
611
|
+
if args[ARG_USE_LARGE_FONT]
|
612
|
+
text_width = WadsConfig.instance.current_theme.pixel_width_for_large_font(message)
|
613
|
+
else
|
614
|
+
text_width = WadsConfig.instance.current_theme.pixel_width_for_string(message)
|
615
|
+
end
|
616
|
+
coordinates = get_coordinates(ELEMENT_TEXT,
|
617
|
+
{ ARG_DESIRED_WIDTH => text_width,
|
618
|
+
ARG_DESIRED_HEIGHT => default_dimensions[1]}.merge(args))
|
619
|
+
new_text = Text.new(coordinates.x, coordinates.y, message,
|
620
|
+
{ ARG_THEME => @parent_widget.gui_theme}.merge(args))
|
621
|
+
new_text.base_z = @parent_widget.base_z
|
622
|
+
@parent_widget.add_child(new_text)
|
623
|
+
new_text
|
624
|
+
end
|
625
|
+
|
626
|
+
def add_text_input(width, default_text = '', args = {})
|
627
|
+
coordinates = get_coordinates(ELEMENT_TEXT_INPUT,
|
628
|
+
{ ARG_DESIRED_WIDTH => width}.merge(args))
|
629
|
+
new_text_input = TextField.new(coordinates.x, coordinates.y, default_text, width)
|
630
|
+
new_text_input.base_z = @parent_widget.base_z
|
631
|
+
@parent_widget.add_child(new_text_input)
|
632
|
+
@parent_widget.text_input_fields << new_text_input
|
633
|
+
new_text_input
|
634
|
+
end
|
635
|
+
|
636
|
+
def add_image(filename, args = {})
|
637
|
+
img = Gosu::Image.new(filename)
|
638
|
+
coordinates = get_coordinates(ELEMENT_IMAGE,
|
639
|
+
{ ARG_DESIRED_WIDTH => img.width,
|
640
|
+
ARG_DESIRED_HEIGHT => img.height}.merge(args))
|
641
|
+
new_image = ImageWidget.new(coordinates.x, coordinates.y, img,
|
642
|
+
{ARG_THEME => @parent_widget.gui_theme}.merge(args))
|
643
|
+
new_image.base_z = @parent_widget.base_z
|
644
|
+
@parent_widget.add_child(new_image)
|
645
|
+
new_image
|
646
|
+
end
|
647
|
+
|
648
|
+
def add_button(label, args = {}, &block)
|
649
|
+
text_width = WadsConfig.instance.current_theme.pixel_width_for_string(label) + 20
|
650
|
+
coordinates = get_coordinates(ELEMENT_BUTTON,
|
651
|
+
{ ARG_DESIRED_WIDTH => text_width}.merge(args))
|
652
|
+
new_button = Button.new(coordinates.x, coordinates.y, label,
|
653
|
+
{ ARG_DESIRED_WIDTH => coordinates.width,
|
654
|
+
ARG_THEME => @parent_widget.gui_theme}.merge(args))
|
655
|
+
new_button.set_action(&block)
|
656
|
+
new_button.base_z = @parent_widget.base_z
|
657
|
+
@parent_widget.add_child(new_button)
|
658
|
+
new_button
|
659
|
+
end
|
660
|
+
|
661
|
+
def add_plot(args = {})
|
662
|
+
coordinates = get_coordinates(ELEMENT_PLOT, args)
|
663
|
+
new_plot = Plot.new(coordinates.x, coordinates.y,
|
664
|
+
coordinates.width, coordinates.height)
|
665
|
+
new_plot.base_z = @parent_widget.base_z
|
666
|
+
@parent_widget.add_child(new_plot)
|
667
|
+
new_plot
|
668
|
+
end
|
669
|
+
|
670
|
+
def add_document(content, args = {})
|
671
|
+
number_of_content_lines = content.lines.count
|
672
|
+
height = (number_of_content_lines * 26) + 4
|
673
|
+
coordinates = get_coordinates(ELEMENT_DOCUMENT,
|
674
|
+
{ ARG_DESIRED_HEIGHT => height}.merge(args))
|
675
|
+
new_doc = Document.new(coordinates.x, coordinates.y,
|
676
|
+
coordinates.width, coordinates.height,
|
677
|
+
content,
|
678
|
+
{ARG_THEME => @parent_widget.gui_theme}.merge(args))
|
679
|
+
new_doc.base_z = @parent_widget.base_z
|
680
|
+
@parent_widget.add_child(new_doc)
|
681
|
+
new_doc
|
682
|
+
end
|
683
|
+
|
684
|
+
def add_graph_display(graph, display_mode = GRAPH_DISPLAY_ALL, args = {})
|
685
|
+
coordinates = get_coordinates(ELEMENT_GRAPH, args)
|
686
|
+
new_graph = GraphWidget.new(coordinates.x, coordinates.y,
|
687
|
+
coordinates.width, coordinates.height,
|
688
|
+
graph, display_mode)
|
689
|
+
new_graph.base_z = @parent_widget.base_z
|
690
|
+
@parent_widget.add_child(new_graph)
|
691
|
+
new_graph
|
692
|
+
end
|
693
|
+
|
694
|
+
def add_single_select_table(column_headers, visible_rows, args = {})
|
695
|
+
calculated_height = 30 + (visible_rows * 30)
|
696
|
+
coordinates = get_coordinates(ELEMENT_TABLE,
|
697
|
+
{ ARG_DESIRED_HEIGHT => calculated_height}.merge(args))
|
698
|
+
new_table = SingleSelectTable.new(coordinates.x, coordinates.y,
|
699
|
+
coordinates.width, coordinates.height,
|
700
|
+
column_headers, visible_rows,
|
701
|
+
{ARG_THEME => @parent_widget.gui_theme}.merge(args))
|
702
|
+
new_table.base_z = @parent_widget.base_z
|
703
|
+
@parent_widget.add_child(new_table)
|
704
|
+
new_table
|
705
|
+
end
|
706
|
+
|
707
|
+
def add_multi_select_table(column_headers, visible_rows, args = {})
|
708
|
+
calculated_height = 30 + (visible_rows * 30)
|
709
|
+
coordinates = get_coordinates(ELEMENT_TABLE,
|
710
|
+
{ ARG_DESIRED_HEIGHT => calculated_height}.merge(args))
|
711
|
+
new_table = MultiSelectTable.new(coordinates.x, coordinates.y,
|
712
|
+
coordinates.width, coordinates.height,
|
713
|
+
column_headers, visible_rows,
|
714
|
+
{ARG_THEME => @parent_widget.gui_theme}.merge(args))
|
715
|
+
new_table.base_z = @parent_widget.base_z
|
716
|
+
@parent_widget.add_child(new_table)
|
717
|
+
new_table
|
718
|
+
end
|
719
|
+
|
720
|
+
def add_table(column_headers, visible_rows, args = {})
|
721
|
+
calculated_height = 30 + (visible_rows * 30)
|
722
|
+
coordinates = get_coordinates(ELEMENT_TABLE,
|
723
|
+
{ ARG_DESIRED_HEIGHT => calculated_height}.merge(args))
|
724
|
+
new_table = Table.new(coordinates.x, coordinates.y,
|
725
|
+
coordinates.width, coordinates.height,
|
726
|
+
column_headers, visible_rows,
|
727
|
+
{ARG_THEME => @parent_widget.gui_theme}.merge(args))
|
728
|
+
new_table.base_z = @parent_widget.base_z
|
729
|
+
@parent_widget.add_child(new_table)
|
730
|
+
new_table
|
731
|
+
end
|
732
|
+
|
733
|
+
def add_horizontal_panel(args = {})
|
734
|
+
internal_add_panel(ELEMENT_HORIZONTAL_PANEL, args)
|
735
|
+
end
|
736
|
+
|
737
|
+
def add_vertical_panel(args = {})
|
738
|
+
internal_add_panel(ELEMENT_VERTICAL_PANEL, args)
|
739
|
+
end
|
740
|
+
|
741
|
+
def add_max_panel(args = {})
|
742
|
+
internal_add_panel(ELEMENT_MAX_PANEL, args)
|
743
|
+
end
|
744
|
+
|
745
|
+
def internal_add_panel(orientation, args)
|
746
|
+
coordinates = get_coordinates(orientation, args)
|
747
|
+
new_panel = Panel.new(coordinates.x, coordinates.y,
|
748
|
+
coordinates.width, coordinates.height)
|
749
|
+
new_panel_layout = args[ARG_LAYOUT]
|
750
|
+
if new_panel_layout.nil?
|
751
|
+
new_panel_layout = LAYOUT_VERTICAL_COLUMN
|
752
|
+
end
|
753
|
+
new_panel.set_layout(new_panel_layout, args)
|
754
|
+
|
755
|
+
new_panel_theme = args[ARG_THEME]
|
756
|
+
new_panel.gui_theme = new_panel_theme unless new_panel_theme.nil?
|
757
|
+
|
758
|
+
new_panel.base_z = @parent_widget.base_z
|
759
|
+
@parent_widget.add_child(new_panel)
|
760
|
+
#new_panel.disable_border
|
761
|
+
new_panel
|
762
|
+
end
|
763
|
+
end
|
764
|
+
|
765
|
+
class VerticalColumnLayout < WadsLayout
|
766
|
+
attr_accessor :single_column_container
|
767
|
+
|
768
|
+
def initialize(x, y, width, height, parent_widget, args = {})
|
769
|
+
super
|
770
|
+
@single_column_container = GuiContainer.new(x, y, width, height, FILL_VERTICAL_STACK)
|
771
|
+
end
|
772
|
+
|
773
|
+
# This is the standard interface for layouts
|
774
|
+
def get_coordinates(element_type, args = {})
|
775
|
+
@single_column_container.get_coordinates(element_type, args)
|
776
|
+
end
|
777
|
+
end
|
778
|
+
|
779
|
+
# SectionLayout is an intermediate class in the layout class hierarchy
|
780
|
+
# that is used to divide the visible screen into different sections.
|
781
|
+
# The commonly used sections include SECTION_TOP or SECTION_NORTH,
|
782
|
+
# SECTION_MIDDLE or SECTION_CENTER, SECTION_BOTTOM or SECTION_SOUTH,
|
783
|
+
# SECTION_LEFT or SECTION_WEST, SECTION_RIGHT or SECTION_EAST.
|
784
|
+
class SectionLayout < WadsLayout
|
785
|
+
attr_accessor :container_map
|
786
|
+
|
787
|
+
def initialize(x, y, width, height, parent_widget, args = {})
|
788
|
+
super
|
789
|
+
@container_map = {}
|
790
|
+
end
|
791
|
+
|
792
|
+
#
|
793
|
+
# Get the coordinates for the given element type. A generic map of parameters
|
794
|
+
# is accepted, however the ARG_SECTION is required so the layout can determine
|
795
|
+
# which section or container is used.
|
796
|
+
#
|
797
|
+
def get_coordinates(element_type, args = {})
|
798
|
+
section = args[ARG_SECTION]
|
799
|
+
if section.nil?
|
800
|
+
raise "Layout addition requires the arg '#{ARG_SECTION}' with value #{@container_map.keys.join(', ')}"
|
801
|
+
end
|
802
|
+
container = @container_map[section]
|
803
|
+
if container.nil?
|
804
|
+
raise "Invalid section #{section}. Value values are #{@container_map.keys.join(', ')}"
|
805
|
+
end
|
806
|
+
container.get_coordinates(element_type, args)
|
807
|
+
end
|
808
|
+
end
|
809
|
+
|
810
|
+
# The layout sections are as follows:
|
811
|
+
#
|
812
|
+
# +-------------------------------------------------+
|
813
|
+
# + SECTION_NORTH +
|
814
|
+
# +-------------------------------------------------+
|
815
|
+
# + +
|
816
|
+
# + SECTION_CENTER +
|
817
|
+
# + +
|
818
|
+
# +-------------------------------------------------+
|
819
|
+
class HeaderContentLayout < SectionLayout
|
820
|
+
def initialize(x, y, width, height, parent_widget, args = {})
|
821
|
+
super
|
822
|
+
# Divide the height into 100, 100, and the middle gets everything else
|
823
|
+
# Right now we are using 100 pixels rather than a percentage for the borders
|
824
|
+
header_section_height = 100
|
825
|
+
if args[ARG_DESIRED_HEIGHT]
|
826
|
+
header_section_height = args[ARG_DESIRED_HEIGHT]
|
827
|
+
end
|
828
|
+
middle_section_y_start = y + header_section_height
|
829
|
+
height_middle_section = height - header_section_height
|
830
|
+
@container_map[SECTION_NORTH] = GuiContainer.new(x, y, width, header_section_height)
|
831
|
+
@container_map[SECTION_CENTER] = GuiContainer.new(x, middle_section_y_start, width, height_middle_section, FILL_VERTICAL_STACK)
|
832
|
+
end
|
833
|
+
end
|
834
|
+
|
835
|
+
# The layout sections are as follows:
|
836
|
+
#
|
837
|
+
# +-------------------------------------------------+
|
838
|
+
# + +
|
839
|
+
# + SECTION_CENTER +
|
840
|
+
# + +
|
841
|
+
# +-------------------------------------------------+
|
842
|
+
# + SECTION_SOUTH +
|
843
|
+
# +-------------------------------------------------+
|
844
|
+
class ContentFooterLayout < SectionLayout
|
845
|
+
def initialize(x, y, width, height, parent_widget, args = {})
|
846
|
+
super
|
847
|
+
# Divide the height into 100, 100, and the middle gets everything else
|
848
|
+
# Right now we are using 100 pixels rather than a percentage for the borders
|
849
|
+
bottom_section_height = 100
|
850
|
+
if args[ARG_DESIRED_HEIGHT]
|
851
|
+
bottom_section_height = args[ARG_DESIRED_HEIGHT]
|
852
|
+
end
|
853
|
+
bottom_section_y_start = y + height - bottom_section_height
|
854
|
+
middle_section_height = height - bottom_section_height
|
855
|
+
@container_map[SECTION_CENTER] = GuiContainer.new(x, y, width, middle_section_height, FILL_VERTICAL_STACK)
|
856
|
+
@container_map[SECTION_SOUTH] = GuiContainer.new(x, bottom_section_y_start,
|
857
|
+
width, bottom_section_height)
|
858
|
+
end
|
859
|
+
end
|
860
|
+
|
861
|
+
# The layout sections are as follows:
|
862
|
+
#
|
863
|
+
# +-------------------------------------------------+
|
864
|
+
# + | +
|
865
|
+
# + SECTION_WEST | SECTION_EAST +
|
866
|
+
# + | +
|
867
|
+
# +-------------------------------------------------+
|
868
|
+
#
|
869
|
+
class EastWestLayout < SectionLayout
|
870
|
+
def initialize(x, y, width, height, parent_widget, args = {})
|
871
|
+
super
|
872
|
+
west_section_width = width / 2
|
873
|
+
if args[ARG_PANEL_WIDTH]
|
874
|
+
west_section_width = args[ARG_PANEL_WIDTH]
|
875
|
+
end
|
876
|
+
east_section_width = width - west_section_width
|
877
|
+
@container_map[SECTION_WEST] = GuiContainer.new(x, y,
|
878
|
+
west_section_width, height,
|
879
|
+
FILL_FULL_SIZE)
|
880
|
+
@container_map[SECTION_EAST] = GuiContainer.new(x + west_section_width, y,
|
881
|
+
east_section_width, height,
|
882
|
+
FILL_FULL_SIZE)
|
883
|
+
end
|
884
|
+
end
|
885
|
+
|
886
|
+
# The layout sections are as follows:
|
887
|
+
#
|
888
|
+
# +-------------------------------------------------+
|
889
|
+
# + SECTION_NORTH +
|
890
|
+
# +-------------------------------------------------+
|
891
|
+
# + +
|
892
|
+
# + SECTION_CENTER +
|
893
|
+
# + +
|
894
|
+
# +-------------------------------------------------+
|
895
|
+
# + SECTION_SOUTH +
|
896
|
+
# +-------------------------------------------------+
|
897
|
+
class TopMiddleBottomLayout < SectionLayout
|
898
|
+
def initialize(x, y, width, height, parent_widget, args = {})
|
899
|
+
super
|
900
|
+
# Divide the height into 100, 100, and the middle gets everything else
|
901
|
+
# Right now we are using 100 pixels rather than a percentage for the borders
|
902
|
+
middle_section_y_start = y + 100
|
903
|
+
bottom_section_y_start = y + height - 100
|
904
|
+
height_middle_section = height - 200
|
905
|
+
@container_map[SECTION_NORTH] = GuiContainer.new(x, y, width, 100)
|
906
|
+
@container_map[SECTION_CENTER] = GuiContainer.new(x, middle_section_y_start,
|
907
|
+
width, height_middle_section, FILL_VERTICAL_STACK)
|
908
|
+
@container_map[SECTION_SOUTH] = GuiContainer.new(x, bottom_section_y_start, width, 100)
|
909
|
+
end
|
910
|
+
end
|
911
|
+
|
912
|
+
# The layout sections are as follows:
|
913
|
+
#
|
914
|
+
# +-------------------------------------------------+
|
915
|
+
# + SECTION_NORTH +
|
916
|
+
# +-------------------------------------------------+
|
917
|
+
# + | | +
|
918
|
+
# + SECTION_WEST | SECTION_CENTER | SECTION_EAST +
|
919
|
+
# + | | +
|
920
|
+
# +-------------------------------------------------+
|
921
|
+
# + SECTION_SOUTH +
|
922
|
+
# +-------------------------------------------------+
|
923
|
+
class BorderLayout < SectionLayout
|
924
|
+
def initialize(x, y, width, height, parent_widget, args = {})
|
925
|
+
super
|
926
|
+
# Divide the height into 100, 100, and the middle gets everything else
|
927
|
+
# Right now we are using 100 pixels rather than a percentage for the borders
|
928
|
+
middle_section_y_start = y + 100
|
929
|
+
bottom_section_y_start = y + height - 100
|
930
|
+
|
931
|
+
height_middle_section = bottom_section_y_start - middle_section_y_start
|
932
|
+
|
933
|
+
middle_section_x_start = x + 100
|
934
|
+
right_section_x_start = x + width - 100
|
935
|
+
width_middle_section = right_section_x_start - middle_section_x_start
|
936
|
+
|
937
|
+
@container_map[SECTION_NORTH] = GuiContainer.new(x, y, width, 100)
|
938
|
+
@container_map[SECTION_WEST] = GuiContainer.new(
|
939
|
+
x, middle_section_y_start, 100, height_middle_section, FILL_VERTICAL_STACK)
|
940
|
+
@container_map[SECTION_CENTER] = GuiContainer.new(
|
941
|
+
middle_section_x_start,
|
942
|
+
middle_section_y_start,
|
943
|
+
width_middle_section,
|
944
|
+
height_middle_section,
|
945
|
+
FILL_VERTICAL_STACK)
|
946
|
+
@container_map[SECTION_EAST] = GuiContainer.new(
|
947
|
+
right_section_x_start,
|
948
|
+
middle_section_y_start,
|
949
|
+
100,
|
950
|
+
height_middle_section,
|
951
|
+
FILL_VERTICAL_STACK)
|
952
|
+
@container_map[SECTION_SOUTH] = GuiContainer.new(x, bottom_section_y_start, width, 100)
|
953
|
+
end
|
954
|
+
end
|
955
|
+
|
956
|
+
# The base class for all widgets. This class provides basic functionality for
|
957
|
+
# all gui widgets including maintaining the coordinates and layout used.
|
958
|
+
# A widget has a border and background, whose colors are defined by the theme.
|
959
|
+
# These can be turned off using the disable_border and disable_background methods.
|
960
|
+
# Widgets support a hierarchy of visible elements on the screen. For example,
|
961
|
+
# a parent widget may be a form, and it may contain many child widgets such as
|
962
|
+
# text labels, input fields, and a submit button. You can add children to a
|
963
|
+
# widget using the add or add_child methods. Children are automatically rendered
|
964
|
+
# so any child does not need an explicit call to its draw or render method.
|
965
|
+
# Children can be placed with x, y positioning relative to their parent for convenience
|
966
|
+
# (see the relative_x and relative_y methods).
|
967
|
+
#
|
968
|
+
# The draw and update methods are used by their Gosu counterparts.
|
969
|
+
# Typically there is one parent Wads widget used by a Gosu app, and it controls
|
970
|
+
# drawing all of the child widgets, invoking update on all widgets, and delegating
|
971
|
+
# user events. Widgets can override a render method for any specific drawing logic.
|
972
|
+
# It is worth showing the draw method here to amplify the point. You do not need
|
973
|
+
# to specifically call draw or render on any children. If you want to manage GUI
|
974
|
+
# elements outside of the widget hierarchy, then render is the best place to do it.
|
975
|
+
#
|
976
|
+
# Likewise, the update method recursively calls the handle_update method on all
|
977
|
+
# children in this widget's hierarchy.
|
978
|
+
#
|
979
|
+
# A commonly used method is contains_click(mouse_x, mouse_y) which returns
|
980
|
+
# whether this widget contained the mouse event. For a example, a button widget
|
981
|
+
# uses this method to determine if it was clicked.
|
982
|
+
#
|
45
983
|
class Widget
|
46
984
|
attr_accessor :x
|
47
985
|
attr_accessor :y
|
48
986
|
attr_accessor :base_z
|
49
|
-
attr_accessor :
|
50
|
-
attr_accessor :
|
51
|
-
attr_accessor :border_color
|
987
|
+
attr_accessor :gui_theme
|
988
|
+
attr_accessor :layout
|
52
989
|
attr_accessor :width
|
53
990
|
attr_accessor :height
|
54
991
|
attr_accessor :visible
|
55
|
-
attr_accessor :font
|
56
992
|
attr_accessor :children
|
57
993
|
attr_accessor :overlay_widget
|
58
|
-
attr_accessor :
|
994
|
+
attr_accessor :override_color
|
995
|
+
attr_accessor :is_selected
|
996
|
+
attr_accessor :text_input_fields
|
59
997
|
|
60
|
-
def initialize(x, y,
|
61
|
-
|
62
|
-
|
63
|
-
@
|
64
|
-
|
65
|
-
|
998
|
+
def initialize(x, y, width = 10, height = 10, layout = nil, theme = nil)
|
999
|
+
set_absolute_position(x, y)
|
1000
|
+
set_dimensions(width, height)
|
1001
|
+
@base_z = 0
|
1002
|
+
if uses_layout
|
1003
|
+
if layout.nil?
|
1004
|
+
@layout = WadsConfig.instance.create_layout(x, y, width, height, self)
|
1005
|
+
else
|
1006
|
+
@layout = layout
|
1007
|
+
end
|
1008
|
+
end
|
1009
|
+
if theme.nil?
|
1010
|
+
@gui_theme = WadsConfig.instance.current_theme
|
1011
|
+
else
|
1012
|
+
@gui_theme = theme
|
1013
|
+
end
|
66
1014
|
@visible = true
|
67
1015
|
@children = []
|
68
|
-
@
|
69
|
-
@
|
1016
|
+
@show_background = true
|
1017
|
+
@show_border = true
|
1018
|
+
@is_selected = false
|
1019
|
+
@text_input_fields = []
|
1020
|
+
end
|
1021
|
+
|
1022
|
+
def pad(str, size, left_align = false)
|
1023
|
+
str = str.to_s
|
1024
|
+
if left_align
|
1025
|
+
str[0, size].ljust(size, ' ')
|
1026
|
+
else
|
1027
|
+
str[0, size].rjust(size, ' ')
|
1028
|
+
end
|
1029
|
+
end
|
1030
|
+
def debug(message)
|
1031
|
+
WadsConfig.instance.get_logger.debug message
|
1032
|
+
end
|
1033
|
+
def info(message)
|
1034
|
+
WadsConfig.instance.get_logger.info message
|
1035
|
+
end
|
1036
|
+
def warn(message)
|
1037
|
+
WadsConfig.instance.get_logger.warn message
|
1038
|
+
end
|
1039
|
+
def error(message)
|
1040
|
+
WadsConfig.instance.get_logger.error message
|
1041
|
+
end
|
1042
|
+
|
1043
|
+
def set_absolute_position(x, y)
|
1044
|
+
@x = x
|
1045
|
+
@y = y
|
1046
|
+
end
|
1047
|
+
|
1048
|
+
def set_dimensions(width, height)
|
1049
|
+
@width = width
|
1050
|
+
@height = height
|
1051
|
+
end
|
1052
|
+
|
1053
|
+
def uses_layout
|
1054
|
+
true
|
1055
|
+
end
|
1056
|
+
|
1057
|
+
def get_layout
|
1058
|
+
if not uses_layout
|
1059
|
+
raise "The widget #{self.class.name} does not support layouts"
|
1060
|
+
end
|
1061
|
+
if @layout.nil?
|
1062
|
+
raise "No layout was defined for #{self.class.name}"
|
1063
|
+
end
|
1064
|
+
@layout
|
1065
|
+
end
|
1066
|
+
|
1067
|
+
def set_layout(layout_type, args = {})
|
1068
|
+
@layout = WadsConfig.instance.create_layout_for_widget(self, layout_type, args)
|
1069
|
+
end
|
1070
|
+
|
1071
|
+
def add_panel(section, args = {})
|
1072
|
+
new_panel = get_layout.add_max_panel({ ARG_SECTION => section,
|
1073
|
+
ARG_THEME => @gui_theme}.merge(args))
|
1074
|
+
new_panel.disable_border
|
1075
|
+
new_panel
|
1076
|
+
end
|
1077
|
+
|
1078
|
+
def get_theme
|
1079
|
+
@gui_theme
|
1080
|
+
end
|
1081
|
+
|
1082
|
+
def set_theme(new_theme)
|
1083
|
+
@gui_theme = new_theme
|
70
1084
|
end
|
71
1085
|
|
1086
|
+
def set_selected
|
1087
|
+
@is_selected = true
|
1088
|
+
end
|
1089
|
+
|
1090
|
+
def unset_selected
|
1091
|
+
@is_selected = false
|
1092
|
+
end
|
1093
|
+
|
1094
|
+
def graphics_color
|
1095
|
+
if @override_color
|
1096
|
+
return @override_color
|
1097
|
+
end
|
1098
|
+
@gui_theme.graphic_elements_color
|
1099
|
+
end
|
1100
|
+
|
1101
|
+
def text_color
|
1102
|
+
if @override_color
|
1103
|
+
return @override_color
|
1104
|
+
end
|
1105
|
+
@gui_theme.text_color
|
1106
|
+
end
|
1107
|
+
|
1108
|
+
def selection_color
|
1109
|
+
@gui_theme.selection_color
|
1110
|
+
end
|
1111
|
+
|
1112
|
+
def border_color
|
1113
|
+
@gui_theme.border_color
|
1114
|
+
end
|
1115
|
+
|
1116
|
+
#
|
1117
|
+
# The z order is determined by taking the base_z and adding the widget specific value.
|
1118
|
+
# An overlay widget has a base_z that is +10 higher than the widget underneath it.
|
1119
|
+
# The widget_z method provides a relative ordering that is common for user interfaces.
|
1120
|
+
# For example, text is higher than graphic elements and backgrounds.
|
1121
|
+
#
|
72
1122
|
def z_order
|
73
1123
|
@base_z + widget_z
|
74
1124
|
end
|
@@ -77,63 +1127,180 @@ module Wads
|
|
77
1127
|
@base_z + relative_order
|
78
1128
|
end
|
79
1129
|
|
1130
|
+
#
|
1131
|
+
# Add a child widget that will automatically be drawn by this widget and will received
|
1132
|
+
# delegated events. This is an alias for add_child
|
1133
|
+
#
|
1134
|
+
def add(child)
|
1135
|
+
add_child(child)
|
1136
|
+
end
|
1137
|
+
|
1138
|
+
#
|
1139
|
+
# Add a child widget that will automatically be drawn by this widget and will received
|
1140
|
+
# delegated events.
|
1141
|
+
#
|
80
1142
|
def add_child(child)
|
81
1143
|
@children << child
|
82
1144
|
end
|
83
1145
|
|
1146
|
+
#
|
1147
|
+
# Remove the given child widget
|
1148
|
+
#
|
84
1149
|
def remove_child(child)
|
85
1150
|
@children.delete(child)
|
86
1151
|
end
|
87
1152
|
|
1153
|
+
#
|
1154
|
+
# Remove a list of child widgets
|
1155
|
+
#
|
1156
|
+
def remove_children(list)
|
1157
|
+
list.each do |child|
|
1158
|
+
@children.delete(child)
|
1159
|
+
end
|
1160
|
+
end
|
1161
|
+
|
1162
|
+
#
|
1163
|
+
# Remove all children whose class name includes the given token.
|
1164
|
+
# This method can be used if you do not have a saved list of the
|
1165
|
+
# widgets you want to remove.
|
1166
|
+
#
|
1167
|
+
def remove_children_by_type(class_name_token)
|
1168
|
+
children_to_remove = []
|
1169
|
+
@children.each do |child|
|
1170
|
+
if child.class.name.include? class_name_token
|
1171
|
+
children_to_remove << child
|
1172
|
+
end
|
1173
|
+
end
|
1174
|
+
children_to_remove.each do |child|
|
1175
|
+
@children.delete(child)
|
1176
|
+
end
|
1177
|
+
end
|
1178
|
+
|
1179
|
+
#
|
1180
|
+
# Remove all children from this widget
|
1181
|
+
#
|
88
1182
|
def clear_children
|
89
1183
|
@children = []
|
90
1184
|
end
|
91
1185
|
|
92
|
-
|
93
|
-
|
1186
|
+
#
|
1187
|
+
# Drawing the background is on by default. Use this method to prevent drawing a background.
|
1188
|
+
#
|
1189
|
+
def disable_background
|
1190
|
+
@show_background = false
|
94
1191
|
end
|
95
1192
|
|
96
|
-
|
97
|
-
|
1193
|
+
#
|
1194
|
+
# Drawing the border is on by default. Use this method to prevent drawing a border.
|
1195
|
+
#
|
1196
|
+
def disable_border
|
1197
|
+
@show_border = false
|
98
1198
|
end
|
99
1199
|
|
100
|
-
|
101
|
-
|
1200
|
+
#
|
1201
|
+
# Turn back on drawing of the border
|
1202
|
+
#
|
1203
|
+
def enable_border
|
1204
|
+
@show_border = true
|
102
1205
|
end
|
103
1206
|
|
104
|
-
|
105
|
-
|
1207
|
+
#
|
1208
|
+
# Turn back on drawing of the background
|
1209
|
+
#
|
1210
|
+
def enable_background
|
1211
|
+
@show_background = true
|
106
1212
|
end
|
107
1213
|
|
108
|
-
|
109
|
-
|
110
|
-
|
1214
|
+
#
|
1215
|
+
# A convenience method, or alias, to return the left x coordinate of this widget.
|
1216
|
+
#
|
1217
|
+
def left_edge
|
1218
|
+
@x
|
111
1219
|
end
|
112
1220
|
|
1221
|
+
#
|
1222
|
+
# A convenience method to return the right x coordinate of this widget.
|
1223
|
+
#
|
113
1224
|
def right_edge
|
114
1225
|
@x + @width - 1
|
115
1226
|
end
|
116
|
-
|
1227
|
+
|
1228
|
+
#
|
1229
|
+
# A convenience method, or alias, to return the top y coordinate of this widget.
|
1230
|
+
#
|
1231
|
+
def top_edge
|
1232
|
+
@y
|
1233
|
+
end
|
1234
|
+
|
1235
|
+
#
|
1236
|
+
# A convenience method to return the bottom y coordinate of this widget
|
1237
|
+
#
|
117
1238
|
def bottom_edge
|
118
1239
|
@y + @height - 1
|
119
1240
|
end
|
120
1241
|
|
1242
|
+
#
|
1243
|
+
# A convenience method to return the center x coordinate of this widget
|
1244
|
+
#
|
121
1245
|
def center_x
|
122
1246
|
@x + ((right_edge - @x) / 2)
|
123
1247
|
end
|
124
1248
|
|
1249
|
+
#
|
1250
|
+
# A convenience method to return the center y coordinate of this widget
|
1251
|
+
#
|
125
1252
|
def center_y
|
126
1253
|
@y + ((bottom_edge - @y) / 2)
|
127
1254
|
end
|
128
1255
|
|
1256
|
+
#
|
1257
|
+
# Move this widget to an absolute x, y position on the screen.
|
1258
|
+
# It will automatically move all child widgets, however be warned that
|
1259
|
+
# if you are manually rendering any elements within your own render
|
1260
|
+
# logic, you will need to deal with that seperately as the base class
|
1261
|
+
# does not have access to its coordinates.
|
1262
|
+
#
|
1263
|
+
def move_recursive_absolute(new_x, new_y)
|
1264
|
+
delta_x = new_x - @x
|
1265
|
+
delta_y = new_y - @y
|
1266
|
+
move_recursive_delta(delta_x, delta_y)
|
1267
|
+
end
|
1268
|
+
|
1269
|
+
#
|
1270
|
+
# Move this widget to a relative number of x, y pixels on the screen.
|
1271
|
+
# It will automatically move all child widgets, however be warned that
|
1272
|
+
# if you are manually rendering any elements within your own render
|
1273
|
+
# logic, you will need to deal with that seperately as the base class
|
1274
|
+
# does not have access to its coordinates.
|
1275
|
+
#
|
1276
|
+
def move_recursive_delta(delta_x, delta_y)
|
1277
|
+
@x = @x + delta_x
|
1278
|
+
@y = @y + delta_y
|
1279
|
+
@children.each do |child|
|
1280
|
+
child.move_recursive_delta(delta_x, delta_y)
|
1281
|
+
end
|
1282
|
+
end
|
1283
|
+
|
1284
|
+
#
|
1285
|
+
# The primary draw method, used by the main Gosu loop draw method.
|
1286
|
+
# A common usage pattern is to have a primary widget in your Gosu app
|
1287
|
+
# that calls this draw method. All children of this widget are then
|
1288
|
+
# automatically drawn by this method recursively.
|
1289
|
+
# Note that as a widget author, you should only implement/override the
|
1290
|
+
# render method. This is a framework implementation that will
|
1291
|
+
# handle child rendering and invoke render as a user-implemented
|
1292
|
+
# callback.
|
1293
|
+
#
|
129
1294
|
def draw
|
130
1295
|
if @visible
|
131
1296
|
render
|
132
|
-
if @
|
1297
|
+
if @is_selected
|
1298
|
+
draw_background(Z_ORDER_SELECTION_BACKGROUND, @gui_theme.selection_color)
|
1299
|
+
elsif @show_background
|
133
1300
|
draw_background
|
134
1301
|
end
|
135
|
-
if @
|
136
|
-
draw_border
|
1302
|
+
if @show_border
|
1303
|
+
draw_border
|
137
1304
|
end
|
138
1305
|
@children.each do |child|
|
139
1306
|
child.draw
|
@@ -141,38 +1308,61 @@ module Wads
|
|
141
1308
|
end
|
142
1309
|
end
|
143
1310
|
|
144
|
-
def draw_background(z_override = nil)
|
1311
|
+
def draw_background(z_override = nil, color_override = nil)
|
1312
|
+
if color_override.nil?
|
1313
|
+
bgcolor = @gui_theme.background_color
|
1314
|
+
else
|
1315
|
+
bgcolor = color_override
|
1316
|
+
end
|
145
1317
|
if z_override
|
146
1318
|
z = relative_z_order(z_override)
|
147
1319
|
else
|
148
1320
|
z = relative_z_order(Z_ORDER_BACKGROUND)
|
149
1321
|
end
|
150
|
-
Gosu::draw_rect(@x + 1, @y + 1, @width - 3, @height - 3,
|
1322
|
+
Gosu::draw_rect(@x + 1, @y + 1, @width - 3, @height - 3, bgcolor, z)
|
151
1323
|
end
|
152
1324
|
|
153
|
-
def
|
154
|
-
Gosu::draw_line @x
|
155
|
-
Gosu::draw_line @x
|
156
|
-
|
157
|
-
|
158
|
-
def draw_border(color = nil)
|
159
|
-
if color.nil?
|
160
|
-
if @border_color
|
161
|
-
color = @border_color
|
162
|
-
else
|
163
|
-
color = @color
|
164
|
-
end
|
165
|
-
end
|
166
|
-
Gosu::draw_line @x, @y, color, right_edge, @y, color, relative_z_order(Z_ORDER_BORDER)
|
167
|
-
Gosu::draw_line @x, @y, color, @x, bottom_edge, color, relative_z_order(Z_ORDER_BORDER)
|
168
|
-
Gosu::draw_line @x,bottom_edge, color, right_edge, bottom_edge, color, relative_z_order(Z_ORDER_BORDER)
|
169
|
-
Gosu::draw_line right_edge, @y, color, right_edge, bottom_edge, color, relative_z_order(Z_ORDER_BORDER)
|
1325
|
+
def draw_border
|
1326
|
+
Gosu::draw_line @x, @y, @gui_theme.border_color, right_edge, @y, @gui_theme.border_color, relative_z_order(Z_ORDER_BORDER)
|
1327
|
+
Gosu::draw_line @x, @y, @gui_theme.border_color, @x, bottom_edge, @gui_theme.border_color, relative_z_order(Z_ORDER_BORDER)
|
1328
|
+
Gosu::draw_line @x,bottom_edge, @gui_theme.border_color, right_edge, bottom_edge, @gui_theme.border_color, relative_z_order(Z_ORDER_BORDER)
|
1329
|
+
Gosu::draw_line right_edge, @y, @gui_theme.border_color, right_edge, bottom_edge, @gui_theme.border_color, relative_z_order(Z_ORDER_BORDER)
|
170
1330
|
end
|
171
1331
|
|
172
1332
|
def contains_click(mouse_x, mouse_y)
|
173
1333
|
mouse_x >= @x and mouse_x <= right_edge and mouse_y >= @y and mouse_y <= bottom_edge
|
174
1334
|
end
|
175
1335
|
|
1336
|
+
#
|
1337
|
+
# Return true if any part of the given widget overlaps on the screen with this widget
|
1338
|
+
# as defined by the rectangle from the upper left corner to the bottom right.
|
1339
|
+
# Note that your widget may not necessariliy draw pixels in this entire space.
|
1340
|
+
#
|
1341
|
+
def overlaps_with(other_widget)
|
1342
|
+
if other_widget.contains_click(@x, @y)
|
1343
|
+
return true
|
1344
|
+
end
|
1345
|
+
if other_widget.contains_click(right_edge, @y)
|
1346
|
+
return true
|
1347
|
+
end
|
1348
|
+
if other_widget.contains_click(right_edge, bottom_edge - 1)
|
1349
|
+
return true
|
1350
|
+
end
|
1351
|
+
if other_widget.contains_click(@x, bottom_edge - 1)
|
1352
|
+
return true
|
1353
|
+
end
|
1354
|
+
if other_widget.contains_click(center_x, center_y)
|
1355
|
+
return true
|
1356
|
+
end
|
1357
|
+
return false
|
1358
|
+
end
|
1359
|
+
|
1360
|
+
#
|
1361
|
+
# The framework implementation of the main Gosu update loop. This method
|
1362
|
+
# propagates the event to all child widgets as well.
|
1363
|
+
# As a widget author, do not override this method.
|
1364
|
+
# Your callback to implement is the handle_update(update_count, mouse_x, mouse_y) method.
|
1365
|
+
#
|
176
1366
|
def update(update_count, mouse_x, mouse_y)
|
177
1367
|
if @overlay_widget
|
178
1368
|
@overlay_widget.update(update_count, mouse_x, mouse_y)
|
@@ -183,8 +1373,16 @@ module Wads
|
|
183
1373
|
end
|
184
1374
|
end
|
185
1375
|
|
1376
|
+
#
|
1377
|
+
# The framework implementation of the main Gosu button down method.
|
1378
|
+
# This method separates out mouse events from keyboard events, and calls the appropriate
|
1379
|
+
# callback. As a widget author, do not override this method.
|
1380
|
+
# Your callbacks to implement are:
|
1381
|
+
# handle_mouse_down(mouse_x, mouse_y)
|
1382
|
+
# handle_right_mouse(mouse_x, mouse_y)
|
1383
|
+
# handle_key_press(id, mouse_x, mouse_y)
|
1384
|
+
#
|
186
1385
|
def button_down(id, mouse_x, mouse_y)
|
187
|
-
puts "Base widget #{self.class.name} button down #{id}." unless @debug_off
|
188
1386
|
if @overlay_widget
|
189
1387
|
result = @overlay_widget.button_down(id, mouse_x, mouse_y)
|
190
1388
|
if not result.nil? and result.is_a? WidgetResult
|
@@ -200,6 +1398,14 @@ module Wads
|
|
200
1398
|
end
|
201
1399
|
|
202
1400
|
if id == Gosu::MsLeft
|
1401
|
+
# Special handling for text input fields
|
1402
|
+
# Mouse click: Select text field based on mouse position.
|
1403
|
+
if not @text_input_fields.empty?
|
1404
|
+
WadsConfig.instance.get_window.text_input = @text_input_fields.find { |tf| tf.under_point?(mouse_x, mouse_y) }
|
1405
|
+
# Advanced: Move caret to clicked position
|
1406
|
+
WadsConfig.instance.get_window.text_input.move_caret(mouse_x) unless WadsConfig.instance.get_window.text_input.nil?
|
1407
|
+
end
|
1408
|
+
|
203
1409
|
result = handle_mouse_down mouse_x, mouse_y
|
204
1410
|
elsif id == Gosu::MsRight
|
205
1411
|
result = handle_right_mouse mouse_x, mouse_y
|
@@ -208,7 +1414,6 @@ module Wads
|
|
208
1414
|
end
|
209
1415
|
|
210
1416
|
if not result.nil? and result.is_a? WidgetResult
|
211
|
-
puts "Base widget #{self.class.name} returning own #{result}" unless @debug_off
|
212
1417
|
return result
|
213
1418
|
end
|
214
1419
|
|
@@ -218,7 +1423,6 @@ module Wads
|
|
218
1423
|
result = child.button_down id, mouse_x, mouse_y
|
219
1424
|
if not result.nil? and result.is_a? WidgetResult
|
220
1425
|
intercept_widget_event(result)
|
221
|
-
puts "Base widget #{self.class.name} returning child #{result}" unless @debug_off
|
222
1426
|
return result
|
223
1427
|
end
|
224
1428
|
end
|
@@ -226,13 +1430,20 @@ module Wads
|
|
226
1430
|
result = child.button_down id, mouse_x, mouse_y
|
227
1431
|
if not result.nil? and result.is_a? WidgetResult
|
228
1432
|
intercept_widget_event(result)
|
229
|
-
puts "Base widget #{self.class.name} returning child #{result}" unless @debug_off
|
230
1433
|
return result
|
231
1434
|
end
|
232
1435
|
end
|
233
1436
|
end
|
234
1437
|
end
|
235
1438
|
|
1439
|
+
#
|
1440
|
+
# The framework implementation of the main Gosu button up method.
|
1441
|
+
# This method separates out mouse events from keyboard events.
|
1442
|
+
# Only the mouse up event is propagated through the child hierarchy.
|
1443
|
+
# As a widget author, do not override this method.
|
1444
|
+
# Your callback to implement is:
|
1445
|
+
# handle_mouse_up(mouse_x, mouse_y)
|
1446
|
+
#
|
236
1447
|
def button_up(id, mouse_x, mouse_y)
|
237
1448
|
if @overlay_widget
|
238
1449
|
return @overlay_widget.button_up(id, mouse_x, mouse_y)
|
@@ -257,133 +1468,269 @@ module Wads
|
|
257
1468
|
end
|
258
1469
|
end
|
259
1470
|
|
1471
|
+
#
|
1472
|
+
# Return the absolute x coordinate given the relative x pixel to this widget
|
1473
|
+
#
|
1474
|
+
def relative_x(x)
|
1475
|
+
x_pixel_to_screen(x)
|
1476
|
+
end
|
1477
|
+
|
1478
|
+
# An alias for relative_x
|
260
1479
|
def x_pixel_to_screen(x)
|
261
1480
|
@x + x
|
262
1481
|
end
|
263
1482
|
|
1483
|
+
#
|
1484
|
+
# Return the absolute y coordinate given the relative y pixel to this widget
|
1485
|
+
#
|
1486
|
+
def relative_y(y)
|
1487
|
+
y_pixel_to_screen(y)
|
1488
|
+
end
|
1489
|
+
|
1490
|
+
# An alias for relative_y
|
264
1491
|
def y_pixel_to_screen(y)
|
265
1492
|
@y + y
|
266
1493
|
end
|
267
1494
|
|
268
|
-
|
269
|
-
|
1495
|
+
#
|
1496
|
+
# Add a child text widget using x, y positioning relative to this widget
|
1497
|
+
#
|
1498
|
+
def add_text(message, rel_x, rel_y, color = nil, use_large_font = false)
|
1499
|
+
new_text = Text.new(x_pixel_to_screen(rel_x), y_pixel_to_screen(rel_y), message,
|
1500
|
+
{ ARG_COLOR => color, ARG_USE_LARGE_FONT => use_large_font})
|
270
1501
|
new_text.base_z = @base_z
|
1502
|
+
new_text.gui_theme = @gui_theme
|
271
1503
|
add_child(new_text)
|
272
1504
|
new_text
|
273
1505
|
end
|
274
1506
|
|
1507
|
+
#
|
1508
|
+
# Add a child document widget using x, y positioning relative to this widget
|
1509
|
+
#
|
275
1510
|
def add_document(content, rel_x, rel_y, width, height)
|
276
|
-
new_doc = Document.new(
|
1511
|
+
new_doc = Document.new(x_pixel_to_screen(rel_x), y_pixel_to_screen(rel_y),
|
1512
|
+
width, height,
|
1513
|
+
content)
|
277
1514
|
new_doc.base_z = @base_z
|
1515
|
+
new_doc.gui_theme = @gui_theme
|
278
1516
|
add_child(new_doc)
|
279
1517
|
new_doc
|
280
1518
|
end
|
281
1519
|
|
1520
|
+
#
|
1521
|
+
# Add a child button widget using x, y positioning relative to this widget.
|
1522
|
+
# The width of the button will be determined based on the label text unless
|
1523
|
+
# specified in the optional parameter. The code to execute is provided as a
|
1524
|
+
# block, as shown in the example below.
|
1525
|
+
# add_button("Test Button", 10, 10) do
|
1526
|
+
# puts "User hit the test button"
|
1527
|
+
# end
|
282
1528
|
def add_button(label, rel_x, rel_y, width = nil, &block)
|
283
|
-
new_button = Button.new(label, x_pixel_to_screen(rel_x), y_pixel_to_screen(rel_y), @font)
|
284
1529
|
if width.nil?
|
285
|
-
|
1530
|
+
args = {}
|
1531
|
+
else
|
1532
|
+
args = { ARG_DESIRED_WIDTH => width }
|
286
1533
|
end
|
287
|
-
new_button
|
1534
|
+
new_button = Button.new(x_pixel_to_screen(rel_x), y_pixel_to_screen(rel_y), label, args)
|
288
1535
|
new_button.set_action(&block)
|
289
1536
|
new_button.base_z = @base_z
|
1537
|
+
new_button.gui_theme = @gui_theme
|
290
1538
|
add_child(new_button)
|
291
1539
|
new_button
|
292
1540
|
end
|
293
1541
|
|
1542
|
+
#
|
1543
|
+
# Add a child delete button widget using x, y positioning relative to this widget.
|
1544
|
+
# A delete button is a regular button that is rendered as a red X, instead of a text label.
|
1545
|
+
#
|
294
1546
|
def add_delete_button(rel_x, rel_y, &block)
|
295
1547
|
new_delete_button = DeleteButton.new(x_pixel_to_screen(rel_x), y_pixel_to_screen(rel_y))
|
296
1548
|
new_delete_button.set_action(&block)
|
297
1549
|
new_delete_button.base_z = @base_z
|
1550
|
+
new_delete_button.gui_theme = @gui_theme
|
298
1551
|
add_child(new_delete_button)
|
299
1552
|
new_delete_button
|
300
1553
|
end
|
301
1554
|
|
302
|
-
|
1555
|
+
#
|
1556
|
+
# Add a child table widget using x, y positioning relative to this widget.
|
1557
|
+
#
|
1558
|
+
def add_table(rel_x, rel_y, width, height, column_headers, max_visible_rows = 10)
|
303
1559
|
new_table = Table.new(x_pixel_to_screen(rel_x), y_pixel_to_screen(rel_y),
|
304
|
-
width, height, column_headers,
|
1560
|
+
width, height, column_headers, max_visible_rows)
|
305
1561
|
new_table.base_z = @base_z
|
1562
|
+
new_table.gui_theme = @gui_theme
|
306
1563
|
add_child(new_table)
|
307
1564
|
new_table
|
308
1565
|
end
|
309
1566
|
|
310
|
-
|
1567
|
+
#
|
1568
|
+
# Add a child table widget using x, y positioning relative to this widget.
|
1569
|
+
# The user can select up to one and only one item in the table.
|
1570
|
+
#
|
1571
|
+
def add_single_select_table(rel_x, rel_y, width, height, column_headers, max_visible_rows = 10)
|
311
1572
|
new_table = SingleSelectTable.new(x_pixel_to_screen(rel_x), y_pixel_to_screen(rel_y),
|
312
|
-
width, height, column_headers,
|
1573
|
+
width, height, column_headers, max_visible_rows)
|
313
1574
|
new_table.base_z = @base_z
|
1575
|
+
new_table.gui_theme = @gui_theme
|
314
1576
|
add_child(new_table)
|
315
1577
|
new_table
|
316
1578
|
end
|
317
1579
|
|
318
|
-
|
1580
|
+
#
|
1581
|
+
# Add a child table widget using x, y positioning relative to this widget.
|
1582
|
+
# The user can zero to many items in the table.
|
1583
|
+
#
|
1584
|
+
def add_multi_select_table(rel_x, rel_y, width, height, column_headers, max_visible_rows = 10)
|
319
1585
|
new_table = MultiSelectTable.new(x_pixel_to_screen(rel_x), y_pixel_to_screen(rel_y),
|
320
|
-
width, height, column_headers,
|
1586
|
+
width, height, column_headers, max_visible_rows)
|
321
1587
|
new_table.base_z = @base_z
|
1588
|
+
new_table.gui_theme = @gui_theme
|
322
1589
|
add_child(new_table)
|
323
1590
|
new_table
|
324
1591
|
end
|
325
1592
|
|
1593
|
+
#
|
1594
|
+
# Add a child graph display widget using x, y positioning relative to this widget.
|
1595
|
+
#
|
326
1596
|
def add_graph_display(rel_x, rel_y, width, height, graph)
|
327
|
-
new_graph = GraphWidget.new(x_pixel_to_screen(rel_x), y_pixel_to_screen(rel_y), width, height,
|
1597
|
+
new_graph = GraphWidget.new(x_pixel_to_screen(rel_x), y_pixel_to_screen(rel_y), width, height, graph)
|
328
1598
|
new_graph.base_z = @base_z
|
329
1599
|
add_child(new_graph)
|
330
1600
|
new_graph
|
331
1601
|
end
|
332
1602
|
|
1603
|
+
#
|
1604
|
+
# Add a child plot display widget using x, y positioning relative to this widget.
|
1605
|
+
#
|
333
1606
|
def add_plot(rel_x, rel_y, width, height)
|
334
|
-
new_plot = Plot.new(x_pixel_to_screen(rel_x), y_pixel_to_screen(rel_y), width, height
|
1607
|
+
new_plot = Plot.new(x_pixel_to_screen(rel_x), y_pixel_to_screen(rel_y), width, height)
|
335
1608
|
new_plot.base_z = @base_z
|
1609
|
+
new_plot.gui_theme = @gui_theme
|
336
1610
|
add_child(new_plot)
|
337
1611
|
new_plot
|
338
1612
|
end
|
339
1613
|
|
340
|
-
|
341
|
-
|
1614
|
+
#
|
1615
|
+
# Add child axis lines widget using x, y positioning relative to this widget.
|
1616
|
+
#
|
1617
|
+
def add_axis_lines(rel_x, rel_y, width, height)
|
1618
|
+
new_axis_lines = AxisLines.new(x_pixel_to_screen(rel_x), y_pixel_to_screen(rel_y), width, height)
|
342
1619
|
new_axis_lines.base_z = @base_z
|
1620
|
+
new_axis_lines.gui_theme = @gui_theme
|
343
1621
|
add_child(new_axis_lines)
|
344
1622
|
new_axis_lines
|
345
1623
|
end
|
346
1624
|
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
1625
|
+
#
|
1626
|
+
# Add a child image widget using x, y positioning relative to this widget.
|
1627
|
+
#
|
1628
|
+
def add_image(filename, rel_x, rel_y)
|
1629
|
+
new_image = ImageWidget.new(x_pixel_to_screen(rel_x), y_pixel_to_screen(rel_y), img)
|
1630
|
+
new_image.base_z = @base_z
|
1631
|
+
new_image.gui_theme = @gui_theme
|
1632
|
+
add_child(new_image)
|
1633
|
+
new_image
|
1634
|
+
end
|
1635
|
+
|
1636
|
+
#
|
1637
|
+
# Add an overlay widget that is drawn on top of (at a higher z level) this widget
|
1638
|
+
#
|
1639
|
+
def add_overlay(overlay)
|
1640
|
+
overlay.base_z = @base_z + 10
|
1641
|
+
add_child(overlay)
|
1642
|
+
@overlay_widget = overlay
|
1643
|
+
end
|
1644
|
+
|
1645
|
+
# For all child widgets, adjust the x coordinate
|
1646
|
+
# so that they are centered.
|
1647
|
+
def center_children
|
1648
|
+
if @children.empty?
|
1649
|
+
return
|
1650
|
+
end
|
1651
|
+
number_of_children = @children.size
|
1652
|
+
total_width_of_children = 0
|
1653
|
+
@children.each do |child|
|
1654
|
+
total_width_of_children = total_width_of_children + child.width + 5
|
1655
|
+
end
|
1656
|
+
total_width_of_children = total_width_of_children - 5
|
1657
|
+
|
1658
|
+
start_x = (@width - total_width_of_children) / 2
|
1659
|
+
@children.each do |child|
|
1660
|
+
child.x = start_x
|
1661
|
+
start_x = start_x + child.width + 5
|
1662
|
+
end
|
351
1663
|
end
|
352
1664
|
|
353
1665
|
#
|
354
|
-
#
|
1666
|
+
# Override this method in your subclass to process mouse down events.
|
1667
|
+
# The base implementation is empty
|
355
1668
|
#
|
356
1669
|
def handle_mouse_down mouse_x, mouse_y
|
357
1670
|
# empty base implementation
|
358
1671
|
end
|
359
1672
|
|
1673
|
+
#
|
1674
|
+
# Override this method in your subclass to process mouse up events.
|
1675
|
+
# The base implementation is empty
|
1676
|
+
#
|
360
1677
|
def handle_mouse_up mouse_x, mouse_y
|
361
1678
|
# empty base implementation
|
362
1679
|
end
|
363
1680
|
|
1681
|
+
#
|
1682
|
+
# Override this method in your subclass to process the right mouse click event.
|
1683
|
+
# Note we do not differentiate between up and down for the right mouse button.
|
1684
|
+
# The base implementation is empty
|
1685
|
+
#
|
364
1686
|
def handle_right_mouse mouse_x, mouse_y
|
365
1687
|
# empty base implementation
|
366
1688
|
end
|
367
1689
|
|
1690
|
+
#
|
1691
|
+
# Override this method in your subclass to process keyboard events.
|
1692
|
+
# The base implementation is empty.
|
1693
|
+
# Note that the mouse was not necessarily positioned over this widget.
|
1694
|
+
# You can check this using the contains_click(mouse_x, mouse_y) method
|
1695
|
+
# and decide if you want to process the event based on that, if desired.
|
1696
|
+
#
|
368
1697
|
def handle_key_press id, mouse_x, mouse_y
|
369
1698
|
# empty base implementation
|
370
1699
|
end
|
371
1700
|
|
1701
|
+
#
|
1702
|
+
# This callback is invoked for any key registered by the
|
1703
|
+
# register_hold_down_key(id) method.
|
1704
|
+
#
|
1705
|
+
def handle_key_held_down id, mouse_x, mouse_y
|
1706
|
+
# empty base implementation
|
1707
|
+
end
|
1708
|
+
|
1709
|
+
#
|
1710
|
+
# Override this method in your subclass to perform any logic needed
|
1711
|
+
# as part of the main Gosu update loop. In most cases, this method is
|
1712
|
+
# invoked 60 times per second.
|
1713
|
+
#
|
372
1714
|
def handle_update update_count, mouse_x, mouse_y
|
373
1715
|
# empty base implementation
|
374
1716
|
end
|
375
1717
|
|
1718
|
+
#
|
1719
|
+
# Override this method in your subclass to perform any custom rendering logic.
|
1720
|
+
# Note that child widgets are automatically drawn and you do not need to do
|
1721
|
+
# that yourself.
|
1722
|
+
#
|
376
1723
|
def render
|
377
1724
|
# Base implementation is empty
|
378
|
-
# Note that the draw method invoked by clients stills renders any added children
|
379
|
-
# render is for specific drawing done by the widget
|
380
1725
|
end
|
381
1726
|
|
1727
|
+
#
|
1728
|
+
# Return the relative z order compared to other widgets.
|
1729
|
+
# The absolute z order is the base plus this value.
|
1730
|
+
# Its calculated relative so that overlay widgets can be
|
1731
|
+
# on top of base displays.
|
1732
|
+
#
|
382
1733
|
def widget_z
|
383
|
-
# The relative z order compared to other widgets
|
384
|
-
# The absolute z order is the base plus this value.
|
385
|
-
# Its calculated relative so that overlay widgets can be
|
386
|
-
# on top of base displays.
|
387
1734
|
0
|
388
1735
|
end
|
389
1736
|
|
@@ -393,36 +1740,132 @@ module Wads
|
|
393
1740
|
end
|
394
1741
|
end
|
395
1742
|
|
1743
|
+
#
|
1744
|
+
# A panel is simply an alias for a widget, although you can optionally
|
1745
|
+
# treat them differently if you wish. Generally a panel is used to
|
1746
|
+
# apply a specific layout to a sub-section of the screen.
|
1747
|
+
#
|
1748
|
+
class Panel < Widget
|
1749
|
+
def initialize(x, y, w, h, layout = nil, theme = nil)
|
1750
|
+
super(x, y, w, h, layout, theme)
|
1751
|
+
end
|
1752
|
+
end
|
1753
|
+
|
1754
|
+
#
|
1755
|
+
# Displays an image on the screen at the specific x, y location. The image
|
1756
|
+
# can be scaled by setting the scale attribute. The image attribute to the
|
1757
|
+
# construcor can be the string file location or a Gosu::Image instance
|
1758
|
+
#
|
1759
|
+
class ImageWidget < Widget
|
1760
|
+
attr_accessor :img
|
1761
|
+
attr_accessor :scale
|
1762
|
+
|
1763
|
+
def initialize(x, y, image, args = {})
|
1764
|
+
super(x, y)
|
1765
|
+
if image.is_a? String
|
1766
|
+
@img = Gosu::Image.new(image)
|
1767
|
+
elsif image.is_a? Gosu::Image
|
1768
|
+
@img = image
|
1769
|
+
elsif image.is_a? Gosu::Color
|
1770
|
+
@img = nil
|
1771
|
+
@override_color = image
|
1772
|
+
else
|
1773
|
+
raise "ImageWidget requires either a filename or a Gosu::Image object"
|
1774
|
+
end
|
1775
|
+
if args[ARG_THEME]
|
1776
|
+
@gui_theme = args[ARG_THEME]
|
1777
|
+
end
|
1778
|
+
@scale = 1
|
1779
|
+
disable_border
|
1780
|
+
disable_background
|
1781
|
+
set_dimensions(@img.width, @img.height) if @img
|
1782
|
+
end
|
1783
|
+
|
1784
|
+
def render
|
1785
|
+
if @img.nil?
|
1786
|
+
# TODO draw a box
|
1787
|
+
Gosu::draw_rect(@x, @y, @width - 1, @height - 1, @override_color, relative_z_order(Z_ORDER_GRAPHIC_ELEMENTS))
|
1788
|
+
else
|
1789
|
+
@img.draw @x, @y, z_order, @scale, @scale
|
1790
|
+
end
|
1791
|
+
end
|
1792
|
+
|
1793
|
+
def widget_z
|
1794
|
+
Z_ORDER_FOCAL_ELEMENTS
|
1795
|
+
end
|
1796
|
+
end
|
1797
|
+
|
1798
|
+
#
|
1799
|
+
# Displays a text label on the screen at the specific x, y location.
|
1800
|
+
# The font specified by the current theme is used.
|
1801
|
+
# The theme text color is used, unless the color parameter specifies an override.
|
1802
|
+
# The small font is used by default, unless the use_large_font parameter is true.
|
1803
|
+
#
|
396
1804
|
class Text < Widget
|
397
|
-
attr_accessor :
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
@
|
1805
|
+
attr_accessor :label
|
1806
|
+
|
1807
|
+
def initialize(x, y, label, args = {})
|
1808
|
+
super(x, y)
|
1809
|
+
@label = label
|
1810
|
+
if args[ARG_THEME]
|
1811
|
+
@gui_theme = args[ARG_THEME]
|
1812
|
+
end
|
1813
|
+
if args[ARG_USE_LARGE_FONT]
|
1814
|
+
@use_large_font = args[ARG_USE_LARGE_FONT]
|
1815
|
+
end
|
1816
|
+
if args[ARG_COLOR]
|
1817
|
+
@override_color = args[ARG_COLOR]
|
1818
|
+
end
|
1819
|
+
disable_border
|
1820
|
+
if @use_large_font
|
1821
|
+
set_dimensions(@gui_theme.font_large.text_width(@label) + 10, 20)
|
1822
|
+
else
|
1823
|
+
set_dimensions(@gui_theme.font.text_width(@label) + 10, 20)
|
1824
|
+
end
|
1825
|
+
end
|
1826
|
+
|
1827
|
+
def set_text(new_text)
|
1828
|
+
@label = new_text
|
1829
|
+
end
|
1830
|
+
|
1831
|
+
def change_text(new_text)
|
1832
|
+
set_text(new_text)
|
402
1833
|
end
|
1834
|
+
|
403
1835
|
def render
|
404
|
-
|
1836
|
+
if @use_large_font
|
1837
|
+
get_theme.font_large.draw_text(@label, @x, @y, z_order, 1, 1, text_color)
|
1838
|
+
else
|
1839
|
+
get_theme.font.draw_text(@label, @x, @y, z_order, 1, 1, text_color)
|
1840
|
+
end
|
405
1841
|
end
|
1842
|
+
|
406
1843
|
def widget_z
|
407
1844
|
Z_ORDER_TEXT
|
408
1845
|
end
|
409
1846
|
end
|
410
1847
|
|
1848
|
+
#
|
1849
|
+
# An ErrorMessage is a subclass of text that uses a red color
|
1850
|
+
#
|
411
1851
|
class ErrorMessage < Text
|
412
|
-
|
413
|
-
|
414
|
-
super("ERROR: #{str}", x, y, font, COLOR_ERROR_CODE_RED)
|
415
|
-
set_dimensions(@font.text_width(@str) + 4, 36)
|
1852
|
+
def initialize(x, y, message)
|
1853
|
+
super(x, y, "ERROR: #{message}", COLOR_ERROR_CODE_RED)
|
416
1854
|
end
|
417
1855
|
end
|
418
1856
|
|
1857
|
+
#
|
1858
|
+
# A data point to be used in a Plot widget. This object holds
|
1859
|
+
# the x, y screen location as well as the data values for x, y.
|
1860
|
+
#
|
419
1861
|
class PlotPoint < Widget
|
420
1862
|
attr_accessor :data_x
|
421
1863
|
attr_accessor :data_y
|
422
1864
|
attr_accessor :data_point_size
|
423
1865
|
|
424
1866
|
def initialize(x, y, data_x, data_y, color = COLOR_MAROON, size = 4)
|
425
|
-
super(x, y
|
1867
|
+
super(x, y)
|
1868
|
+
@override_color = color
|
426
1869
|
@data_x = data_x
|
427
1870
|
@data_y = data_y
|
428
1871
|
@data_point_size = size
|
@@ -436,7 +1879,7 @@ module Wads
|
|
436
1879
|
half_size = size_to_draw / 2
|
437
1880
|
Gosu::draw_rect(@x - half_size, @y - half_size,
|
438
1881
|
size_to_draw, size_to_draw,
|
439
|
-
|
1882
|
+
graphics_color, z_order)
|
440
1883
|
end
|
441
1884
|
|
442
1885
|
def widget_z
|
@@ -458,32 +1901,41 @@ module Wads
|
|
458
1901
|
end
|
459
1902
|
end
|
460
1903
|
|
1904
|
+
#
|
1905
|
+
# Displays a button at the specified x, y location.
|
1906
|
+
# The button width is based on the label text unless specified
|
1907
|
+
# using the optional parameter. The code to executeon a button
|
1908
|
+
# click is specified using the set_action method, however typical
|
1909
|
+
# using involves the widget or layout form of add_button. For example:
|
1910
|
+
# add_button("Test Button", 10, 10) do
|
1911
|
+
# puts "User hit the test button"
|
1912
|
+
# end
|
1913
|
+
|
461
1914
|
class Button < Widget
|
462
1915
|
attr_accessor :label
|
463
|
-
attr_accessor :text_color
|
464
1916
|
attr_accessor :is_pressed
|
465
1917
|
attr_accessor :action_code
|
466
1918
|
|
467
|
-
def initialize(
|
468
|
-
super(x, y
|
469
|
-
set_font(font)
|
1919
|
+
def initialize(x, y, label, args = {})
|
1920
|
+
super(x, y)
|
470
1921
|
@label = label
|
471
|
-
|
472
|
-
|
473
|
-
|
1922
|
+
if args[ARG_THEME]
|
1923
|
+
@gui_theme = args[ARG_THEME]
|
1924
|
+
end
|
1925
|
+
@text_pixel_width = @gui_theme.font.text_width(@label)
|
1926
|
+
if args[ARG_DESIRED_WIDTH]
|
1927
|
+
@width = args[ARG_DESIRED_WIDTH]
|
474
1928
|
else
|
475
|
-
@width =
|
1929
|
+
@width = @text_pixel_width + 10
|
476
1930
|
end
|
477
1931
|
@height = 26
|
478
|
-
@text_color = text_color
|
479
1932
|
@is_pressed = false
|
480
1933
|
@is_pressed_update_count = -100
|
481
1934
|
end
|
482
1935
|
|
483
1936
|
def render
|
484
|
-
draw_border(@color)
|
485
1937
|
text_x = center_x - (@text_pixel_width / 2)
|
486
|
-
@font.draw_text(@label, text_x, @y, z_order, 1, 1,
|
1938
|
+
@gui_theme.font.draw_text(@label, text_x, @y, z_order, 1, 1, text_color)
|
487
1939
|
end
|
488
1940
|
|
489
1941
|
def widget_z
|
@@ -508,22 +1960,24 @@ module Wads
|
|
508
1960
|
end
|
509
1961
|
|
510
1962
|
if update_count < @is_pressed_update_count + 15
|
511
|
-
|
1963
|
+
unset_selected
|
512
1964
|
elsif contains_click(mouse_x, mouse_y)
|
513
|
-
|
1965
|
+
set_selected
|
514
1966
|
else
|
515
|
-
|
1967
|
+
unset_selected
|
516
1968
|
end
|
517
1969
|
end
|
518
1970
|
end
|
519
1971
|
|
1972
|
+
#
|
1973
|
+
# A subclass of button that renders a red X instead of label text
|
1974
|
+
#
|
520
1975
|
class DeleteButton < Button
|
521
|
-
def initialize(x, y)
|
522
|
-
super("ignore",
|
1976
|
+
def initialize(x, y, args = {})
|
1977
|
+
super(x, y, "ignore", {ARG_DESIRED_WIDTH => 50}.merge(args))
|
523
1978
|
set_dimensions(14, 14)
|
524
1979
|
add_child(Line.new(@x, @y, right_edge, bottom_edge, COLOR_ERROR_CODE_RED))
|
525
1980
|
add_child(Line.new(@x, bottom_edge, right_edge, @y, COLOR_ERROR_CODE_RED))
|
526
|
-
set_border(@color)
|
527
1981
|
end
|
528
1982
|
|
529
1983
|
def render
|
@@ -531,20 +1985,26 @@ module Wads
|
|
531
1985
|
end
|
532
1986
|
end
|
533
1987
|
|
1988
|
+
#
|
1989
|
+
# Displays multiple lines of text content at the specified coordinates
|
1990
|
+
#
|
534
1991
|
class Document < Widget
|
535
1992
|
attr_accessor :lines
|
536
1993
|
|
537
|
-
def initialize(
|
538
|
-
super(x, y
|
539
|
-
set_font(font)
|
1994
|
+
def initialize(x, y, width, height, content, args = {})
|
1995
|
+
super(x, y)
|
540
1996
|
set_dimensions(width, height)
|
541
1997
|
@lines = content.split("\n")
|
1998
|
+
disable_border
|
1999
|
+
if args[ARG_THEME]
|
2000
|
+
@gui_theme = args[ARG_THEME]
|
2001
|
+
end
|
542
2002
|
end
|
543
2003
|
|
544
2004
|
def render
|
545
2005
|
y = @y + 4
|
546
2006
|
@lines.each do |line|
|
547
|
-
@font.draw_text(line, @x + 5, y, z_order, 1, 1,
|
2007
|
+
@gui_theme.font.draw_text(line, @x + 5, y, z_order, 1, 1, text_color)
|
548
2008
|
y = y + 26
|
549
2009
|
end
|
550
2010
|
end
|
@@ -555,19 +2015,18 @@ module Wads
|
|
555
2015
|
end
|
556
2016
|
|
557
2017
|
class InfoBox < Widget
|
558
|
-
def initialize(
|
2018
|
+
def initialize(x, y, width, height, title, content, args = {})
|
559
2019
|
super(x, y)
|
560
|
-
set_font(font)
|
561
2020
|
set_dimensions(width, height)
|
562
|
-
set_border(COLOR_WHITE)
|
563
|
-
set_background(COLOR_OFF_GRAY)
|
564
2021
|
@base_z = 10
|
2022
|
+
if args[ARG_THEME]
|
2023
|
+
@gui_theme = args[ARG_THEME]
|
2024
|
+
end
|
565
2025
|
add_text(title, 5, 5)
|
566
2026
|
add_document(content, 5, 52, width, height - 52)
|
567
2027
|
ok_button = add_button("OK", (@width / 2) - 50, height - 26) do
|
568
2028
|
WidgetResult.new(true)
|
569
2029
|
end
|
570
|
-
ok_button.text_color = COLOR_WHITE
|
571
2030
|
ok_button.width = 100
|
572
2031
|
end
|
573
2032
|
|
@@ -581,13 +2040,8 @@ module Wads
|
|
581
2040
|
class Dialog < Widget
|
582
2041
|
attr_accessor :textinput
|
583
2042
|
|
584
|
-
def initialize(
|
585
|
-
super(x, y)
|
586
|
-
@window = window
|
587
|
-
set_font(font)
|
588
|
-
set_dimensions(width, height)
|
589
|
-
set_background(COLOR_OFF_GRAY)
|
590
|
-
set_border(COLOR_WHITE)
|
2043
|
+
def initialize(x, y, width, height, title, text_input_default)
|
2044
|
+
super(x, y, width, height)
|
591
2045
|
@base_z = 10
|
592
2046
|
@error_message = nil
|
593
2047
|
|
@@ -596,7 +2050,7 @@ module Wads
|
|
596
2050
|
add_document(content, 0, 56, width, height)
|
597
2051
|
|
598
2052
|
# Forms automatically get a text input widget
|
599
|
-
@textinput = TextField.new(
|
2053
|
+
@textinput = TextField.new(x + 10, bottom_edge - 80, text_input_default, 600)
|
600
2054
|
@textinput.base_z = 10
|
601
2055
|
add_child(@textinput)
|
602
2056
|
|
@@ -604,15 +2058,11 @@ module Wads
|
|
604
2058
|
ok_button = add_button("OK", (@width / 2) - 100, height - 32) do
|
605
2059
|
handle_ok
|
606
2060
|
end
|
607
|
-
ok_button.color = COLOR_FORM_BUTTON
|
608
|
-
ok_button.text_color = COLOR_WHITE
|
609
2061
|
ok_button.width = 100
|
610
2062
|
|
611
2063
|
cancel_button = add_button("Cancel", (@width / 2) + 50, height - 32) do
|
612
2064
|
WidgetResult.new(true)
|
613
2065
|
end
|
614
|
-
cancel_button.color = COLOR_FORM_BUTTON
|
615
|
-
cancel_button.text_color = COLOR_WHITE
|
616
2066
|
cancel_button.width = 100
|
617
2067
|
end
|
618
2068
|
|
@@ -624,7 +2074,7 @@ module Wads
|
|
624
2074
|
end
|
625
2075
|
|
626
2076
|
def add_error_message(msg)
|
627
|
-
@error_message = ErrorMessage.new(
|
2077
|
+
@error_message = ErrorMessage.new(x + 10, bottom_edge - 120, msg)
|
628
2078
|
@error_message.base_z = @base_z
|
629
2079
|
end
|
630
2080
|
|
@@ -647,9 +2097,9 @@ module Wads
|
|
647
2097
|
|
648
2098
|
def handle_mouse_down mouse_x, mouse_y
|
649
2099
|
# Mouse click: Select text field based on mouse position.
|
650
|
-
|
2100
|
+
WadsConfig.instance.get_window.text_input = [@textinput].find { |tf| tf.under_point?(mouse_x, mouse_y) }
|
651
2101
|
# Advanced: Move caret to clicked position
|
652
|
-
|
2102
|
+
WadsConfig.instance.get_window.text_input.move_caret(mouse_x) unless WadsConfig.instance.get_window.text_input.nil?
|
653
2103
|
|
654
2104
|
handle_mouse_click(mouse_x, mouse_y)
|
655
2105
|
end
|
@@ -661,6 +2111,14 @@ module Wads
|
|
661
2111
|
end
|
662
2112
|
end
|
663
2113
|
|
2114
|
+
#
|
2115
|
+
# A result object returned from handle methods that instructs the parent widget
|
2116
|
+
# what to do. A close_widget value of true instructs the recipient to close
|
2117
|
+
# either the overlay window or the entire app, based on the context of the receiver.
|
2118
|
+
# In the case of a form being submitted, the action may be "OK" and the form_data
|
2119
|
+
# contains the information supplied by the user.
|
2120
|
+
# WidgetResult is intentionally generic so it can support a wide variety of use cases.
|
2121
|
+
#
|
664
2122
|
class WidgetResult
|
665
2123
|
attr_accessor :close_widget
|
666
2124
|
attr_accessor :action
|
@@ -673,80 +2131,124 @@ module Wads
|
|
673
2131
|
end
|
674
2132
|
end
|
675
2133
|
|
2134
|
+
#
|
2135
|
+
# Renders a line from x, y to x2, y2. The theme graphics elements color
|
2136
|
+
# is used by default, unless specified using the optional parameter.
|
2137
|
+
#
|
676
2138
|
class Line < Widget
|
677
2139
|
attr_accessor :x2
|
678
2140
|
attr_accessor :y2
|
679
2141
|
|
680
|
-
def initialize(x, y, x2, y2, color =
|
681
|
-
super
|
2142
|
+
def initialize(x, y, x2, y2, color = nil)
|
2143
|
+
super(x, y)
|
2144
|
+
@override_color = color
|
682
2145
|
@x2 = x2
|
683
2146
|
@y2 = y2
|
2147
|
+
disable_border
|
2148
|
+
disable_background
|
684
2149
|
end
|
685
2150
|
|
686
2151
|
def render
|
687
|
-
Gosu::draw_line x, y,
|
2152
|
+
Gosu::draw_line x, y, graphics_color, x2, y2, graphics_color, z_order
|
688
2153
|
end
|
689
2154
|
|
690
2155
|
def widget_z
|
691
2156
|
Z_ORDER_GRAPHIC_ELEMENTS
|
692
2157
|
end
|
2158
|
+
|
2159
|
+
def uses_layout
|
2160
|
+
false
|
2161
|
+
end
|
693
2162
|
end
|
694
2163
|
|
2164
|
+
#
|
2165
|
+
# A very specific widget used along with a Plot to draw the x and y axis lines.
|
2166
|
+
# Note that the labels are drawn using separate widgets.
|
2167
|
+
#
|
695
2168
|
class AxisLines < Widget
|
696
|
-
def initialize(x, y, width, height, color =
|
697
|
-
super
|
698
|
-
|
699
|
-
|
2169
|
+
def initialize(x, y, width, height, color = nil)
|
2170
|
+
super(x, y)
|
2171
|
+
set_dimensions(width, height)
|
2172
|
+
disable_border
|
2173
|
+
disable_background
|
700
2174
|
end
|
701
2175
|
|
702
2176
|
def render
|
703
|
-
add_child(Line.new(@x, @y, @x, bottom_edge,
|
704
|
-
add_child(Line.new(@x, bottom_edge, right_edge, bottom_edge,
|
2177
|
+
add_child(Line.new(@x, @y, @x, bottom_edge, graphics_color))
|
2178
|
+
add_child(Line.new(@x, bottom_edge, right_edge, bottom_edge, graphics_color))
|
2179
|
+
end
|
2180
|
+
|
2181
|
+
def uses_layout
|
2182
|
+
false
|
705
2183
|
end
|
706
2184
|
end
|
707
2185
|
|
2186
|
+
#
|
2187
|
+
# Labels and tic marks for the vertical axis on a plot
|
2188
|
+
#
|
708
2189
|
class VerticalAxisLabel < Widget
|
709
2190
|
attr_accessor :label
|
710
2191
|
|
711
|
-
def initialize(x, y, label,
|
712
|
-
super
|
713
|
-
set_font(font)
|
2192
|
+
def initialize(x, y, label, color = nil)
|
2193
|
+
super(x, y)
|
714
2194
|
@label = label
|
2195
|
+
@override_color = color
|
2196
|
+
text_pixel_width = @gui_theme.font.text_width(@label)
|
2197
|
+
add_text(@label, -text_pixel_width - 28, -12)
|
2198
|
+
disable_border
|
2199
|
+
disable_background
|
715
2200
|
end
|
716
2201
|
|
717
2202
|
def render
|
718
|
-
|
719
|
-
|
720
|
-
@x, @y, @color, z_order
|
721
|
-
|
722
|
-
@font.draw_text(@label, @x - text_pixel_width - 28, @y - 12, 1, 1, 1, @color)
|
2203
|
+
Gosu::draw_line @x - 20, @y, graphics_color,
|
2204
|
+
@x, @y, graphics_color, z_order
|
723
2205
|
end
|
724
2206
|
|
725
2207
|
def widget_z
|
726
2208
|
Z_ORDER_GRAPHIC_ELEMENTS
|
727
2209
|
end
|
2210
|
+
|
2211
|
+
def uses_layout
|
2212
|
+
false
|
2213
|
+
end
|
728
2214
|
end
|
729
2215
|
|
2216
|
+
#
|
2217
|
+
# Labels and tic marks for the horizontal axis on a plot
|
2218
|
+
#
|
730
2219
|
class HorizontalAxisLabel < Widget
|
731
2220
|
attr_accessor :label
|
732
2221
|
|
733
|
-
def initialize(x, y, label,
|
734
|
-
super
|
735
|
-
set_font(font)
|
2222
|
+
def initialize(x, y, label, color = nil)
|
2223
|
+
super(x, y)
|
736
2224
|
@label = label
|
2225
|
+
@override_color = color
|
2226
|
+
text_pixel_width = @gui_theme.font.text_width(@label)
|
2227
|
+
add_text(@label, -(text_pixel_width / 2), 26)
|
2228
|
+
disable_border
|
2229
|
+
disable_background
|
737
2230
|
end
|
738
2231
|
|
739
2232
|
def render
|
740
|
-
|
741
|
-
Gosu::draw_line @x, @y, @color, @x, @y + 20, @color
|
742
|
-
@font.draw_text(@label, @x - (text_pixel_width / 2), @y + 26, z_order, 1, 1, @color)
|
2233
|
+
Gosu::draw_line @x, @y, graphics_color, @x, @y + 20, graphics_color, z_order
|
743
2234
|
end
|
744
2235
|
|
745
2236
|
def widget_z
|
746
2237
|
Z_ORDER_TEXT
|
747
2238
|
end
|
2239
|
+
|
2240
|
+
def uses_layout
|
2241
|
+
false
|
2242
|
+
end
|
748
2243
|
end
|
749
2244
|
|
2245
|
+
#
|
2246
|
+
# Displays a table of information at the given coordinates.
|
2247
|
+
# The headers are an array of text labels to display at the top of each column.
|
2248
|
+
# The max_visible_rows specifies how many rows are visible at once.
|
2249
|
+
# If there are more data rows than the max, the arrow keys can be used to
|
2250
|
+
# page up or down through the rows in the table.
|
2251
|
+
#
|
750
2252
|
class Table < Widget
|
751
2253
|
attr_accessor :data_rows
|
752
2254
|
attr_accessor :row_colors
|
@@ -755,10 +2257,12 @@ module Wads
|
|
755
2257
|
attr_accessor :current_row
|
756
2258
|
attr_accessor :can_delete_rows
|
757
2259
|
|
758
|
-
def initialize(x, y, width, height, headers,
|
759
|
-
super(x, y
|
760
|
-
set_font(font)
|
2260
|
+
def initialize(x, y, width, height, headers, max_visible_rows = 10, args = {})
|
2261
|
+
super(x, y)
|
761
2262
|
set_dimensions(width, height)
|
2263
|
+
if args[ARG_THEME]
|
2264
|
+
@gui_theme = args[ARG_THEME]
|
2265
|
+
end
|
762
2266
|
@headers = headers
|
763
2267
|
@current_row = 0
|
764
2268
|
@max_visible_rows = max_visible_rows
|
@@ -785,7 +2289,7 @@ module Wads
|
|
785
2289
|
@row_colors = []
|
786
2290
|
end
|
787
2291
|
|
788
|
-
def add_row(data_row, color =
|
2292
|
+
def add_row(data_row, color = text_color )
|
789
2293
|
@data_rows << data_row
|
790
2294
|
@row_colors << color
|
791
2295
|
end
|
@@ -840,9 +2344,9 @@ module Wads
|
|
840
2344
|
column_widths = []
|
841
2345
|
number_of_columns = @data_rows[0].size
|
842
2346
|
(0..number_of_columns-1).each do |c|
|
843
|
-
max_length = @font.text_width(headers[c])
|
2347
|
+
max_length = @gui_theme.font.text_width(headers[c])
|
844
2348
|
(0..number_of_rows-1).each do |r|
|
845
|
-
text_pixel_width = @font.text_width(@data_rows[r][c])
|
2349
|
+
text_pixel_width = @gui_theme.font.text_width(@data_rows[r][c])
|
846
2350
|
if text_pixel_width > max_length
|
847
2351
|
max_length = text_pixel_width
|
848
2352
|
end
|
@@ -850,18 +2354,22 @@ module Wads
|
|
850
2354
|
column_widths[c] = max_length
|
851
2355
|
end
|
852
2356
|
|
2357
|
+
# Draw a horizontal line between header and data rows
|
853
2358
|
x = @x + 10
|
854
2359
|
if number_of_columns > 1
|
855
2360
|
(0..number_of_columns-2).each do |c|
|
856
2361
|
x = x + column_widths[c] + 20
|
857
|
-
Gosu::draw_line x, @y,
|
2362
|
+
Gosu::draw_line x, @y, graphics_color, x, @y + @height, graphics_color, z_order
|
858
2363
|
end
|
859
2364
|
end
|
860
2365
|
|
861
|
-
|
2366
|
+
# Draw the header row
|
2367
|
+
y = @y
|
2368
|
+
Gosu::draw_rect(@x + 1, y, @width - 3, 28, graphics_color, relative_z_order(Z_ORDER_SELECTION_BACKGROUND))
|
2369
|
+
|
862
2370
|
x = @x + 20
|
863
2371
|
(0..number_of_columns-1).each do |c|
|
864
|
-
@font.draw_text(@headers[c], x, y, z_order, 1, 1,
|
2372
|
+
@gui_theme.font.draw_text(@headers[c], x, y + 3, z_order, 1, 1, text_color)
|
865
2373
|
x = x + column_widths[c] + 20
|
866
2374
|
end
|
867
2375
|
y = y + 30
|
@@ -873,7 +2381,7 @@ module Wads
|
|
873
2381
|
elsif count < @current_row + @max_visible_rows
|
874
2382
|
x = @x + 20
|
875
2383
|
(0..number_of_columns-1).each do |c|
|
876
|
-
@font.draw_text(row[c], x, y + 2, z_order, 1, 1, @row_colors[count])
|
2384
|
+
@gui_theme.font.draw_text(row[c], x, y + 2, z_order, 1, 1, @row_colors[count])
|
877
2385
|
x = x + column_widths[c] + 20
|
878
2386
|
end
|
879
2387
|
y = y + 30
|
@@ -894,15 +2402,31 @@ module Wads
|
|
894
2402
|
def widget_z
|
895
2403
|
Z_ORDER_TEXT
|
896
2404
|
end
|
2405
|
+
|
2406
|
+
def uses_layout
|
2407
|
+
false
|
2408
|
+
end
|
897
2409
|
end
|
898
2410
|
|
2411
|
+
#
|
2412
|
+
# A table where the user can select one row at a time.
|
2413
|
+
# The selected row has a background color specified by the selection color of the
|
2414
|
+
# current theme.
|
2415
|
+
#
|
899
2416
|
class SingleSelectTable < Table
|
900
2417
|
attr_accessor :selected_row
|
901
|
-
attr_accessor :selected_color
|
902
2418
|
|
903
|
-
def initialize(x, y, width, height, headers,
|
904
|
-
super(x, y, width, height, headers,
|
905
|
-
|
2419
|
+
def initialize(x, y, width, height, headers, max_visible_rows = 10, args = {})
|
2420
|
+
super(x, y, width, height, headers, max_visible_rows, args)
|
2421
|
+
end
|
2422
|
+
|
2423
|
+
def is_row_selected(mouse_y)
|
2424
|
+
row_number = determine_row_number(mouse_y)
|
2425
|
+
if row_number.nil?
|
2426
|
+
return false
|
2427
|
+
end
|
2428
|
+
selected_row = @current_row + row_number
|
2429
|
+
@selected_row == selected_row
|
906
2430
|
end
|
907
2431
|
|
908
2432
|
def set_selected_row(mouse_y, column_number)
|
@@ -919,12 +2443,22 @@ module Wads
|
|
919
2443
|
end
|
920
2444
|
end
|
921
2445
|
|
2446
|
+
def unset_selected_row(mouse_y, column_number)
|
2447
|
+
row_number = determine_row_number(mouse_y)
|
2448
|
+
if not row_number.nil?
|
2449
|
+
this_selected_row = @current_row + row_number
|
2450
|
+
@selected_row = this_selected_row
|
2451
|
+
return @data_rows[this_selected_row][column_number]
|
2452
|
+
end
|
2453
|
+
nil
|
2454
|
+
end
|
2455
|
+
|
922
2456
|
def render
|
923
2457
|
super
|
924
2458
|
if @selected_row
|
925
2459
|
if @selected_row >= @current_row and @selected_row < @current_row + @max_visible_rows
|
926
2460
|
y = @y + 30 + ((@selected_row - @current_row) * 30)
|
927
|
-
Gosu::draw_rect(@x + 20, y, @width - 30, 28, @
|
2461
|
+
Gosu::draw_rect(@x + 20, y, @width - 30, 28, @gui_theme.selection_color, relative_z_order(Z_ORDER_SELECTION_BACKGROUND))
|
928
2462
|
end
|
929
2463
|
end
|
930
2464
|
end
|
@@ -932,20 +2466,58 @@ module Wads
|
|
932
2466
|
def widget_z
|
933
2467
|
Z_ORDER_TEXT
|
934
2468
|
end
|
2469
|
+
|
2470
|
+
def handle_mouse_down mouse_x, mouse_y
|
2471
|
+
if contains_click(mouse_x, mouse_y)
|
2472
|
+
row_number = determine_row_number(mouse_y)
|
2473
|
+
if row_number.nil?
|
2474
|
+
return WidgetResult.new(false)
|
2475
|
+
end
|
2476
|
+
# First check if its the delete button that got this
|
2477
|
+
delete_this_row = false
|
2478
|
+
@delete_buttons.each do |db|
|
2479
|
+
if db.contains_click(mouse_x, mouse_y)
|
2480
|
+
delete_this_row = true
|
2481
|
+
end
|
2482
|
+
end
|
2483
|
+
if delete_this_row
|
2484
|
+
if not row_number.nil?
|
2485
|
+
data_set_row_to_delete = @current_row + row_number
|
2486
|
+
data_set_name_to_delete = @data_rows[data_set_row_to_delete][1]
|
2487
|
+
@data_rows.delete_at(data_set_row_to_delete)
|
2488
|
+
return WidgetResult.new(false, EVENT_TABLE_ROW_DELETE, [data_set_name_to_delete])
|
2489
|
+
end
|
2490
|
+
else
|
2491
|
+
if is_row_selected(mouse_y)
|
2492
|
+
unset_selected_row(mouse_y, 0)
|
2493
|
+
return WidgetResult.new(false, EVENT_TABLE_UNSELECT, @data_rows[row_number])
|
2494
|
+
else
|
2495
|
+
set_selected_row(mouse_y, 0)
|
2496
|
+
return WidgetResult.new(false, EVENT_TABLE_SELECT, @data_rows[row_number])
|
2497
|
+
end
|
2498
|
+
end
|
2499
|
+
end
|
2500
|
+
end
|
935
2501
|
end
|
936
2502
|
|
2503
|
+
#
|
2504
|
+
# A table where the user can select multiple rows at a time.
|
2505
|
+
# Selected rows have a background color specified by the selection color of the
|
2506
|
+
# current theme.
|
2507
|
+
#
|
937
2508
|
class MultiSelectTable < Table
|
938
2509
|
attr_accessor :selected_rows
|
939
|
-
attr_accessor :selection_color
|
940
2510
|
|
941
|
-
def initialize(x, y, width, height, headers,
|
942
|
-
super(x, y, width, height, headers,
|
2511
|
+
def initialize(x, y, width, height, headers, max_visible_rows = 10, args = {})
|
2512
|
+
super(x, y, width, height, headers, max_visible_rows, args)
|
943
2513
|
@selected_rows = []
|
944
|
-
|
945
|
-
|
946
|
-
|
2514
|
+
end
|
2515
|
+
|
947
2516
|
def is_row_selected(mouse_y)
|
948
2517
|
row_number = determine_row_number(mouse_y)
|
2518
|
+
if row_number.nil?
|
2519
|
+
return false
|
2520
|
+
end
|
949
2521
|
@selected_rows.include?(@current_row + row_number)
|
950
2522
|
end
|
951
2523
|
|
@@ -980,7 +2552,7 @@ module Wads
|
|
980
2552
|
width_of_selection_background = width_of_selection_background - 20
|
981
2553
|
end
|
982
2554
|
Gosu::draw_rect(@x + 20, y, width_of_selection_background, 28,
|
983
|
-
@selection_color,
|
2555
|
+
@gui_theme.selection_color,
|
984
2556
|
relative_z_order(Z_ORDER_SELECTION_BACKGROUND))
|
985
2557
|
end
|
986
2558
|
y = y + 30
|
@@ -995,6 +2567,9 @@ module Wads
|
|
995
2567
|
def handle_mouse_down mouse_x, mouse_y
|
996
2568
|
if contains_click(mouse_x, mouse_y)
|
997
2569
|
row_number = determine_row_number(mouse_y)
|
2570
|
+
if row_number.nil?
|
2571
|
+
return WidgetResult.new(false)
|
2572
|
+
end
|
998
2573
|
# First check if its the delete button that got this
|
999
2574
|
delete_this_row = false
|
1000
2575
|
@delete_buttons.each do |db|
|
@@ -1022,6 +2597,11 @@ module Wads
|
|
1022
2597
|
end
|
1023
2598
|
end
|
1024
2599
|
|
2600
|
+
#
|
2601
|
+
# A two-dimensional graph display which plots a number of PlotPoint objects.
|
2602
|
+
# Options include grid lines that can be displayed, as well as whether lines
|
2603
|
+
# should be drawn connecting each point in a data set.
|
2604
|
+
#
|
1025
2605
|
class Plot < Widget
|
1026
2606
|
attr_accessor :points
|
1027
2607
|
attr_accessor :visible_range
|
@@ -1030,9 +2610,8 @@ module Wads
|
|
1030
2610
|
attr_accessor :zoom_level
|
1031
2611
|
attr_accessor :visibility_map
|
1032
2612
|
|
1033
|
-
def initialize(x, y, width, height
|
1034
|
-
super
|
1035
|
-
set_font(font)
|
2613
|
+
def initialize(x, y, width, height)
|
2614
|
+
super(x, y)
|
1036
2615
|
set_dimensions(width, height)
|
1037
2616
|
@display_grid = false
|
1038
2617
|
@display_lines = true
|
@@ -1044,6 +2623,7 @@ module Wads
|
|
1044
2623
|
# Hash of rendered points keyed by data set name, so we can toggle visibility
|
1045
2624
|
@points_by_data_set_name = {}
|
1046
2625
|
@visibility_map = {}
|
2626
|
+
disable_border
|
1047
2627
|
end
|
1048
2628
|
|
1049
2629
|
def toggle_visibility(data_set_name)
|
@@ -1105,6 +2685,24 @@ module Wads
|
|
1105
2685
|
point.data_x >= @visible_range.left_x and point.data_x <= @visible_range.right_x and point.data_y >= @visible_range.bottom_y and point.data_y <= @visible_range.top_y
|
1106
2686
|
end
|
1107
2687
|
|
2688
|
+
def add_data_point(data_set_name, data_x, data_y, color = COLOR_MAROON)
|
2689
|
+
if range_set?
|
2690
|
+
rendered_points = @points_by_data_set_name[data_set_name]
|
2691
|
+
if rendered_points.nil?
|
2692
|
+
rendered_points = []
|
2693
|
+
@points_by_data_set_name[data_set_name] = rendered_points
|
2694
|
+
end
|
2695
|
+
rendered_points << PlotPoint.new(draw_x(data_x), draw_y(data_y),
|
2696
|
+
data_x, data_y,
|
2697
|
+
color)
|
2698
|
+
if @visibility_map[data_set_name].nil?
|
2699
|
+
@visibility_map[data_set_name] = true
|
2700
|
+
end
|
2701
|
+
else
|
2702
|
+
error("ERROR: range not set, cannot add data")
|
2703
|
+
end
|
2704
|
+
end
|
2705
|
+
|
1108
2706
|
def add_data_set(data_set_name, rendered_points)
|
1109
2707
|
if range_set?
|
1110
2708
|
@points_by_data_set_name[data_set_name] = rendered_points
|
@@ -1112,7 +2710,7 @@ module Wads
|
|
1112
2710
|
@visibility_map[data_set_name] = true
|
1113
2711
|
end
|
1114
2712
|
else
|
1115
|
-
|
2713
|
+
error("ERROR: range not set, cannot add data")
|
1116
2714
|
end
|
1117
2715
|
end
|
1118
2716
|
|
@@ -1162,8 +2760,8 @@ module Wads
|
|
1162
2760
|
if points.length > 1
|
1163
2761
|
points.inject(points[0]) do |last, the_next|
|
1164
2762
|
if last.x < the_next.x
|
1165
|
-
Gosu::draw_line last.x, last.y, last.
|
1166
|
-
the_next.x, the_next.y, last.
|
2763
|
+
Gosu::draw_line last.x, last.y, last.graphics_color,
|
2764
|
+
the_next.x, the_next.y, last.graphics_color, relative_z_order(Z_ORDER_GRAPHIC_ELEMENTS)
|
1167
2765
|
end
|
1168
2766
|
the_next
|
1169
2767
|
end
|
@@ -1226,19 +2824,47 @@ module Wads
|
|
1226
2824
|
end
|
1227
2825
|
end
|
1228
2826
|
|
2827
|
+
#
|
2828
|
+
# A graphical representation of a node in a graph using a button-style, i.e
|
2829
|
+
# a rectangular border with a text label.
|
2830
|
+
# The choice to use this display class is dictated by the use_icons attribute
|
2831
|
+
# of the current theme.
|
2832
|
+
# Like images, the size of node widgets can be scaled.
|
2833
|
+
#
|
1229
2834
|
class NodeWidget < Button
|
1230
2835
|
attr_accessor :data_node
|
1231
2836
|
|
1232
|
-
def initialize(
|
1233
|
-
super(
|
1234
|
-
|
2837
|
+
def initialize(x, y, node, color = nil, initial_scale = 1, is_explorer = false)
|
2838
|
+
super(x, y, node.name)
|
2839
|
+
@orig_width = @width
|
2840
|
+
@orig_height = @height
|
1235
2841
|
@data_node = node
|
2842
|
+
@override_color = color
|
2843
|
+
set_scale(initial_scale, @is_explorer)
|
2844
|
+
end
|
2845
|
+
|
2846
|
+
def is_background
|
2847
|
+
@scale <= 1 and @is_explorer
|
2848
|
+
end
|
2849
|
+
|
2850
|
+
def set_scale(value, is_explorer = false)
|
2851
|
+
@scale = value
|
2852
|
+
@is_explorer = is_explorer
|
2853
|
+
if value < 1
|
2854
|
+
value = 1
|
2855
|
+
end
|
2856
|
+
@width = @orig_width * @scale.to_f
|
2857
|
+
debug("In regular node widget Setting scale of #{@label} to #{@scale}")
|
2858
|
+
end
|
2859
|
+
|
2860
|
+
def get_text_widget
|
2861
|
+
nil
|
1236
2862
|
end
|
1237
2863
|
|
1238
2864
|
def render
|
1239
2865
|
super
|
1240
2866
|
draw_background(Z_ORDER_FOCAL_ELEMENTS)
|
1241
|
-
draw_shadow(COLOR_GRAY)
|
2867
|
+
#draw_shadow(COLOR_GRAY)
|
1242
2868
|
end
|
1243
2869
|
|
1244
2870
|
def widget_z
|
@@ -1246,26 +2872,127 @@ module Wads
|
|
1246
2872
|
end
|
1247
2873
|
end
|
1248
2874
|
|
2875
|
+
#
|
2876
|
+
# A graphical representation of a node in a graph using circular icons
|
2877
|
+
# and adjacent text labels.
|
2878
|
+
# The choice to use this display class is dictated by the use_icons attribute
|
2879
|
+
# of the current theme.
|
2880
|
+
# Like images, the size of node widgets can be scaled.
|
2881
|
+
#
|
2882
|
+
class NodeIconWidget < Widget
|
2883
|
+
attr_accessor :data_node
|
2884
|
+
attr_accessor :image
|
2885
|
+
attr_accessor :scale
|
2886
|
+
attr_accessor :label
|
2887
|
+
attr_accessor :is_explorer
|
2888
|
+
|
2889
|
+
def initialize(x, y, node, color = nil, initial_scale = 1, is_explorer = false)
|
2890
|
+
super(x, y)
|
2891
|
+
@override_color = color
|
2892
|
+
@data_node = node
|
2893
|
+
@label = node.name
|
2894
|
+
circle_image = WadsConfig.instance.circle(color)
|
2895
|
+
if circle_image.nil?
|
2896
|
+
@image = WadsConfig.instance.circle(COLOR_BLUE)
|
2897
|
+
else
|
2898
|
+
@image = circle_image
|
2899
|
+
end
|
2900
|
+
@is_explorer = is_explorer
|
2901
|
+
set_scale(initial_scale, @is_explorer)
|
2902
|
+
disable_border
|
2903
|
+
end
|
2904
|
+
|
2905
|
+
def name
|
2906
|
+
@data_node.name
|
2907
|
+
end
|
2908
|
+
|
2909
|
+
def is_background
|
2910
|
+
@scale <= 0.1 and @is_explorer
|
2911
|
+
end
|
2912
|
+
|
2913
|
+
def set_scale(value, is_explorer = false)
|
2914
|
+
@is_explorer = is_explorer
|
2915
|
+
if value < 0.5
|
2916
|
+
value = 0.5
|
2917
|
+
end
|
2918
|
+
@scale = value / 10.to_f
|
2919
|
+
#debug("In node widget Setting scale of #{@label} to #{value} = #{@scale}")
|
2920
|
+
@width = IMAGE_CIRCLE_SIZE * scale.to_f
|
2921
|
+
@height = IMAGE_CIRCLE_SIZE * scale.to_f
|
2922
|
+
# Only in explorer mode do we dull out nodes on the outer edge
|
2923
|
+
if is_background
|
2924
|
+
@image = WadsConfig.instance.circle(COLOR_ALPHA)
|
2925
|
+
else
|
2926
|
+
text_pixel_width = @gui_theme.font.text_width(@label)
|
2927
|
+
clear_children # the text widget is the only child, so we can remove all
|
2928
|
+
add_text(@label, (@width / 2) - (text_pixel_width / 2), -20)
|
2929
|
+
end
|
2930
|
+
end
|
2931
|
+
|
2932
|
+
def get_text_widget
|
2933
|
+
if @children.size > 0
|
2934
|
+
return @children[0]
|
2935
|
+
end
|
2936
|
+
#raise "No text widget for NodeIconWidget"
|
2937
|
+
nil
|
2938
|
+
end
|
2939
|
+
|
2940
|
+
def render
|
2941
|
+
@image.draw @x, @y, relative_z_order(Z_ORDER_FOCAL_ELEMENTS), @scale, @scale
|
2942
|
+
end
|
2943
|
+
|
2944
|
+
def widget_z
|
2945
|
+
Z_ORDER_TEXT
|
2946
|
+
end
|
2947
|
+
end
|
2948
|
+
|
2949
|
+
#
|
2950
|
+
# Given a single node or a graph data structure, this widget displays
|
2951
|
+
# a visualization of the graph using one of the available node widget classes.
|
2952
|
+
# There are different display modes that control what nodes within the graph
|
2953
|
+
# are shown. The default display mode, GRAPH_DISPLAY_ALL, shows all nodes
|
2954
|
+
# as the name implies. GRAPH_DISPLAY_TREE assumes an acyclic graph and renders
|
2955
|
+
# the graph in a tree-like structure. GRAPH_DISPLAY_EXPLORER has a chosen
|
2956
|
+
# center focus node with connected nodes circled around it based on the depth
|
2957
|
+
# or distance from that node. This mode also allows the user to click on
|
2958
|
+
# different nodes to navigate the graph and change focus nodes.
|
2959
|
+
#
|
1249
2960
|
class GraphWidget < Widget
|
1250
2961
|
attr_accessor :graph
|
1251
|
-
attr_accessor :center_node
|
1252
2962
|
attr_accessor :selected_node
|
1253
2963
|
attr_accessor :selected_node_x_offset
|
1254
2964
|
attr_accessor :selected_node_y_offset
|
2965
|
+
attr_accessor :size_by_connections
|
2966
|
+
attr_accessor :is_explorer
|
1255
2967
|
|
1256
|
-
def initialize(x, y, width, height,
|
1257
|
-
super
|
1258
|
-
set_font(font)
|
2968
|
+
def initialize(x, y, width, height, graph, display_mode = GRAPH_DISPLAY_ALL)
|
2969
|
+
super(x, y)
|
1259
2970
|
set_dimensions(width, height)
|
1260
|
-
|
1261
|
-
|
1262
|
-
|
2971
|
+
if graph.is_a? Node
|
2972
|
+
@graph = Graph.new(graph)
|
2973
|
+
else
|
2974
|
+
@graph = graph
|
2975
|
+
end
|
2976
|
+
@size_by_connections = false
|
2977
|
+
@is_explorer = false
|
2978
|
+
if [GRAPH_DISPLAY_ALL, GRAPH_DISPLAY_TREE, GRAPH_DISPLAY_EXPLORER].include? display_mode
|
2979
|
+
debug("Displaying graph in #{display_mode} mode")
|
2980
|
+
else
|
2981
|
+
raise "#{display_mode} is not a valid display mode for Graph Widget"
|
2982
|
+
end
|
2983
|
+
if display_mode == GRAPH_DISPLAY_ALL
|
2984
|
+
set_all_nodes_for_display
|
2985
|
+
elsif display_mode == GRAPH_DISPLAY_TREE
|
2986
|
+
set_tree_display
|
2987
|
+
else
|
2988
|
+
set_explorer_display
|
2989
|
+
end
|
1263
2990
|
end
|
1264
2991
|
|
1265
2992
|
def handle_update update_count, mouse_x, mouse_y
|
1266
2993
|
if contains_click(mouse_x, mouse_y) and @selected_node
|
1267
|
-
@selected_node.
|
1268
|
-
|
2994
|
+
@selected_node.move_recursive_absolute(mouse_x - @selected_node_x_offset,
|
2995
|
+
mouse_y - @selected_node_y_offset)
|
1269
2996
|
end
|
1270
2997
|
end
|
1271
2998
|
|
@@ -1277,6 +3004,7 @@ module Wads
|
|
1277
3004
|
@selected_node = rn
|
1278
3005
|
@selected_node_x_offset = mouse_x - rn.x
|
1279
3006
|
@selected_node_y_offset = mouse_y - rn.y
|
3007
|
+
@click_timestamp = Time.now
|
1280
3008
|
end
|
1281
3009
|
end
|
1282
3010
|
end
|
@@ -1285,11 +3013,40 @@ module Wads
|
|
1285
3013
|
|
1286
3014
|
def handle_mouse_up mouse_x, mouse_y
|
1287
3015
|
if @selected_node
|
3016
|
+
if @is_explorer
|
3017
|
+
time_between_mouse_up_down = Time.now - @click_timestamp
|
3018
|
+
if time_between_mouse_up_down < 0.2
|
3019
|
+
# Treat this as a single click and make the selected
|
3020
|
+
# node the new center node of the graph
|
3021
|
+
set_explorer_display(@selected_node.data_node)
|
3022
|
+
end
|
3023
|
+
end
|
1288
3024
|
@selected_node = nil
|
1289
3025
|
end
|
1290
3026
|
end
|
1291
3027
|
|
1292
|
-
def
|
3028
|
+
def set_explorer_display(center_node = nil)
|
3029
|
+
if center_node.nil?
|
3030
|
+
# If not specified, pick a center node as the one with the most connections
|
3031
|
+
center_node = @graph.node_with_most_connections
|
3032
|
+
end
|
3033
|
+
|
3034
|
+
@graph.reset_visited
|
3035
|
+
@visible_data_nodes = {}
|
3036
|
+
center_node.bfs(4) do |n|
|
3037
|
+
@visible_data_nodes[n.name] = n
|
3038
|
+
end
|
3039
|
+
|
3040
|
+
@size_by_connections = false
|
3041
|
+
@is_explorer = true
|
3042
|
+
|
3043
|
+
@rendered_nodes = {}
|
3044
|
+
populate_rendered_nodes
|
3045
|
+
|
3046
|
+
prevent_text_overlap
|
3047
|
+
end
|
3048
|
+
|
3049
|
+
def set_tree_display
|
1293
3050
|
@graph.reset_visited
|
1294
3051
|
@visible_data_nodes = @graph.node_map
|
1295
3052
|
@rendered_nodes = {}
|
@@ -1299,7 +3056,7 @@ module Wads
|
|
1299
3056
|
width_for_each_root_tree = @width / number_of_root_nodes
|
1300
3057
|
|
1301
3058
|
start_x = 0
|
1302
|
-
y_level =
|
3059
|
+
y_level = 20
|
1303
3060
|
root_nodes.each do |root|
|
1304
3061
|
set_tree_recursive(root, start_x, start_x + width_for_each_root_tree - 1, y_level)
|
1305
3062
|
start_x = start_x + width_for_each_root_tree
|
@@ -1309,8 +3066,108 @@ module Wads
|
|
1309
3066
|
@rendered_nodes.values.each do |rn|
|
1310
3067
|
rn.base_z = @base_z
|
1311
3068
|
end
|
3069
|
+
|
3070
|
+
if @size_by_connections
|
3071
|
+
scale_node_size
|
3072
|
+
end
|
3073
|
+
|
3074
|
+
prevent_text_overlap
|
3075
|
+
end
|
3076
|
+
|
3077
|
+
def scale_node_size
|
3078
|
+
range = @graph.get_number_of_connections_range
|
3079
|
+
# There are six colors. Any number of scale sizes
|
3080
|
+
# Lets try 4 first as a max size.
|
3081
|
+
bins = range.bin_max_values(4)
|
3082
|
+
|
3083
|
+
# Set the scale for each node
|
3084
|
+
@visible_data_nodes.values.each do |node|
|
3085
|
+
num_links = node.number_of_links
|
3086
|
+
index = 0
|
3087
|
+
while index < bins.size
|
3088
|
+
if num_links <= bins[index]
|
3089
|
+
@rendered_nodes[node.name].set_scale(index + 1, @is_explorer)
|
3090
|
+
index = bins.size
|
3091
|
+
end
|
3092
|
+
index = index + 1
|
3093
|
+
end
|
3094
|
+
end
|
3095
|
+
end
|
3096
|
+
|
3097
|
+
def prevent_text_overlap
|
3098
|
+
@rendered_nodes.values.each do |rn|
|
3099
|
+
text = rn.get_text_widget
|
3100
|
+
if text
|
3101
|
+
if overlaps_with_a_node(text)
|
3102
|
+
move_text_for_node(rn)
|
3103
|
+
else
|
3104
|
+
move_in_bounds = false
|
3105
|
+
# We also check to see if the text is outside the edges of this widget
|
3106
|
+
if text.x < @x or text.right_edge > right_edge
|
3107
|
+
move_in_bounds = true
|
3108
|
+
elsif text.y < @y or text.bottom_edge > bottom_edge
|
3109
|
+
move_in_bounds = true
|
3110
|
+
end
|
3111
|
+
if move_in_bounds
|
3112
|
+
debug("#{text.label} was out of bounds")
|
3113
|
+
move_text_for_node(rn)
|
3114
|
+
end
|
3115
|
+
end
|
3116
|
+
end
|
3117
|
+
end
|
3118
|
+
end
|
3119
|
+
|
3120
|
+
def move_text_for_node(rendered_node)
|
3121
|
+
text = rendered_node.get_text_widget
|
3122
|
+
if text.nil?
|
3123
|
+
return
|
3124
|
+
end
|
3125
|
+
radians_between_attempts = DEG_360 / 24
|
3126
|
+
current_radians = 0.05
|
3127
|
+
done = false
|
3128
|
+
while not done
|
3129
|
+
# Use radians to spread the other nodes around the center node
|
3130
|
+
# TODO base the distance off of scale
|
3131
|
+
text_x = rendered_node.center_x + ((rendered_node.width / 2) * Math.cos(current_radians))
|
3132
|
+
text_y = rendered_node.center_y - ((rendered_node.height / 2) * Math.sin(current_radians))
|
3133
|
+
if text_x < @x
|
3134
|
+
text_x = @x + 1
|
3135
|
+
elsif text_x > right_edge - 20
|
3136
|
+
text_x = right_edge - 20
|
3137
|
+
end
|
3138
|
+
if text_y < @y
|
3139
|
+
text_y = @y + 1
|
3140
|
+
elsif text_y > bottom_edge - 26
|
3141
|
+
text_y = bottom_edge - 26
|
3142
|
+
end
|
3143
|
+
text.x = text_x
|
3144
|
+
text.y = text_y
|
3145
|
+
current_radians = current_radians + radians_between_attempts
|
3146
|
+
if overlaps_with_a_node(text)
|
3147
|
+
# check for done
|
3148
|
+
if current_radians > DEG_360
|
3149
|
+
done = true
|
3150
|
+
error("ERROR: could not find a spot to put the text")
|
3151
|
+
end
|
3152
|
+
else
|
3153
|
+
done = true
|
3154
|
+
end
|
3155
|
+
end
|
1312
3156
|
end
|
1313
3157
|
|
3158
|
+
def overlaps_with_a_node(text)
|
3159
|
+
@rendered_nodes.values.each do |rn|
|
3160
|
+
if text.label == rn.label
|
3161
|
+
# don't compare to yourself
|
3162
|
+
else
|
3163
|
+
if rn.overlaps_with(text)
|
3164
|
+
return true
|
3165
|
+
end
|
3166
|
+
end
|
3167
|
+
end
|
3168
|
+
false
|
3169
|
+
end
|
3170
|
+
|
1314
3171
|
def set_tree_recursive(current_node, start_x, end_x, y_level)
|
1315
3172
|
# Draw the current node, and then recursively divide up
|
1316
3173
|
# and call again for each of the children
|
@@ -1319,13 +3176,19 @@ module Wads
|
|
1319
3176
|
end
|
1320
3177
|
current_node.visited = true
|
1321
3178
|
|
1322
|
-
@
|
3179
|
+
if @gui_theme.use_icons
|
3180
|
+
@rendered_nodes[current_node.name] = NodeIconWidget.new(
|
3181
|
+
x_pixel_to_screen(start_x + ((end_x - start_x) / 2)),
|
3182
|
+
y_pixel_to_screen(y_level),
|
1323
3183
|
current_node,
|
3184
|
+
get_node_color(current_node))
|
3185
|
+
else
|
3186
|
+
@rendered_nodes[current_node.name] = NodeWidget.new(
|
1324
3187
|
x_pixel_to_screen(start_x + ((end_x - start_x) / 2)),
|
1325
3188
|
y_pixel_to_screen(y_level),
|
1326
|
-
|
1327
|
-
get_node_color(current_node),
|
3189
|
+
current_node,
|
1328
3190
|
get_node_color(current_node))
|
3191
|
+
end
|
1329
3192
|
|
1330
3193
|
number_of_child_nodes = current_node.outputs.size
|
1331
3194
|
if number_of_child_nodes == 0
|
@@ -1347,10 +3210,14 @@ module Wads
|
|
1347
3210
|
@visible_data_nodes = @graph.node_map
|
1348
3211
|
@rendered_nodes = {}
|
1349
3212
|
populate_rendered_nodes
|
3213
|
+
if @size_by_connections
|
3214
|
+
scale_node_size
|
3215
|
+
end
|
3216
|
+
prevent_text_overlap
|
1350
3217
|
end
|
1351
3218
|
|
1352
3219
|
def get_node_color(node)
|
1353
|
-
color_tag = node.get_tag(
|
3220
|
+
color_tag = node.get_tag(COLOR_TAG)
|
1354
3221
|
if color_tag.nil?
|
1355
3222
|
return @color
|
1356
3223
|
end
|
@@ -1365,18 +3232,60 @@ module Wads
|
|
1365
3232
|
# Convert the data nodes to rendered nodes
|
1366
3233
|
# Start by putting the center node in the center, then draw others around it
|
1367
3234
|
@rendered_nodes = {}
|
1368
|
-
@
|
1369
|
-
|
3235
|
+
if @gui_theme.use_icons
|
3236
|
+
@rendered_nodes[center_node.name] = NodeIconWidget.new(
|
3237
|
+
center_x, center_y, center_node, get_node_color(center_node))
|
3238
|
+
else
|
3239
|
+
@rendered_nodes[center_node.name] = NodeWidget.new(center_x, center_y,
|
3240
|
+
center_node, get_node_color(center_node), get_node_color(center_node))
|
3241
|
+
end
|
1370
3242
|
|
1371
3243
|
populate_rendered_nodes(center_node)
|
3244
|
+
|
3245
|
+
if @size_by_connections
|
3246
|
+
scale_node_size
|
3247
|
+
end
|
3248
|
+
prevent_text_overlap
|
1372
3249
|
end
|
1373
3250
|
|
1374
3251
|
def populate_rendered_nodes(center_node = nil)
|
1375
3252
|
# Spread out the other nodes around the center node
|
1376
|
-
# going in a circle
|
1377
|
-
|
1378
|
-
|
1379
|
-
|
3253
|
+
# going in a circle at each depth level
|
3254
|
+
stats = Stats.new("NodesPerDepth")
|
3255
|
+
@visible_data_nodes.values.each do |n|
|
3256
|
+
stats.increment(n.depth)
|
3257
|
+
end
|
3258
|
+
current_radians = []
|
3259
|
+
radians_increment = []
|
3260
|
+
(1..4).each do |n|
|
3261
|
+
number_of_nodes_at_depth = stats.count(n)
|
3262
|
+
radians_increment[n] = DEG_360 / number_of_nodes_at_depth.to_f
|
3263
|
+
current_radians[n] = 0.05
|
3264
|
+
end
|
3265
|
+
|
3266
|
+
padding = 100
|
3267
|
+
size_of_x_band = (@width - padding) / 6
|
3268
|
+
size_of_y_band = (@height - padding) / 6
|
3269
|
+
random_x = size_of_x_band / 8
|
3270
|
+
random_y = size_of_y_band / 8
|
3271
|
+
half_random_x = random_x / 2
|
3272
|
+
half_random_y = random_y / 2
|
3273
|
+
|
3274
|
+
# Precompute the band center points
|
3275
|
+
# then reference by the scale or depth values below
|
3276
|
+
band_center_x = padding + (size_of_x_band / 2)
|
3277
|
+
band_center_y = padding + (size_of_y_band / 2)
|
3278
|
+
# depth 1 [0] - center node, distance should be zero. Should be only one
|
3279
|
+
# depth 2 [1] - band one
|
3280
|
+
# depth 3 [2] - band two
|
3281
|
+
# depth 4 [3] - band three
|
3282
|
+
bands_x = [0, band_center_x]
|
3283
|
+
bands_x << band_center_x + size_of_x_band
|
3284
|
+
bands_x << band_center_x + size_of_x_band + size_of_x_band
|
3285
|
+
|
3286
|
+
bands_y = [0, band_center_y]
|
3287
|
+
bands_y << band_center_y + size_of_y_band
|
3288
|
+
bands_y << band_center_y + size_of_y_band + size_of_y_band
|
1380
3289
|
|
1381
3290
|
@visible_data_nodes.each do |node_name, data_node|
|
1382
3291
|
process_this_node = true
|
@@ -1386,10 +3295,33 @@ module Wads
|
|
1386
3295
|
end
|
1387
3296
|
end
|
1388
3297
|
if process_this_node
|
3298
|
+
scale_to_use = 1
|
3299
|
+
if stats.count(1) > 0 and stats.count(2) == 0
|
3300
|
+
# if all nodes are depth 1, then size everything
|
3301
|
+
# as a small node
|
3302
|
+
elsif data_node.depth < 4
|
3303
|
+
scale_to_use = 5 - data_node.depth
|
3304
|
+
end
|
3305
|
+
if @is_explorer
|
3306
|
+
# TODO Layer the nodes around the center
|
3307
|
+
# We need a better multiplier based on the height and width
|
3308
|
+
# max distance x would be (@width / 2) - padding
|
3309
|
+
# divide that into three regions, layer 2, 3, and 4
|
3310
|
+
# get the center point for each of these regions, and do a random from there
|
3311
|
+
# scale to use determines which of the regions
|
3312
|
+
band_index = 4 - scale_to_use
|
3313
|
+
distance_from_center_x = bands_x[band_index] + rand(random_x) - half_random_x
|
3314
|
+
distance_from_center_y = bands_y[band_index] + rand(random_y) - half_random_y
|
3315
|
+
else
|
3316
|
+
distance_from_center_x = 80 + rand(200)
|
3317
|
+
distance_from_center_y = 40 + rand(100)
|
3318
|
+
end
|
1389
3319
|
# Use radians to spread the other nodes around the center node
|
1390
|
-
|
1391
|
-
|
1392
|
-
|
3320
|
+
radians_to_use = current_radians[data_node.depth]
|
3321
|
+
radians_to_use = radians_to_use + (rand(radians_increment[data_node.depth]) / 2)
|
3322
|
+
current_radians[data_node.depth] = current_radians[data_node.depth] + radians_increment[data_node.depth]
|
3323
|
+
node_x = center_x + (distance_from_center_x * Math.cos(radians_to_use))
|
3324
|
+
node_y = center_y - (distance_from_center_y * Math.sin(radians_to_use))
|
1393
3325
|
if node_x < @x
|
1394
3326
|
node_x = @x + 1
|
1395
3327
|
elsif node_x > right_edge - 20
|
@@ -1400,17 +3332,26 @@ module Wads
|
|
1400
3332
|
elsif node_y > bottom_edge - 26
|
1401
3333
|
node_y = bottom_edge - 26
|
1402
3334
|
end
|
1403
|
-
current_radians = current_radians + radians_between_nodes
|
1404
3335
|
|
1405
3336
|
# Note we can link between data nodes and rendered nodes using the node name
|
1406
3337
|
# We have a map of each
|
1407
|
-
@
|
3338
|
+
if @gui_theme.use_icons
|
3339
|
+
@rendered_nodes[data_node.name] = NodeIconWidget.new(
|
3340
|
+
node_x,
|
3341
|
+
node_y,
|
1408
3342
|
data_node,
|
3343
|
+
get_node_color(data_node),
|
3344
|
+
scale_to_use,
|
3345
|
+
@is_explorer)
|
3346
|
+
else
|
3347
|
+
@rendered_nodes[data_node.name] = NodeWidget.new(
|
1409
3348
|
node_x,
|
1410
3349
|
node_y,
|
1411
|
-
|
3350
|
+
data_node,
|
1412
3351
|
get_node_color(data_node),
|
1413
|
-
|
3352
|
+
scale_to_use,
|
3353
|
+
@is_explorer)
|
3354
|
+
end
|
1414
3355
|
end
|
1415
3356
|
end
|
1416
3357
|
@rendered_nodes.values.each do |rn|
|
@@ -1423,6 +3364,7 @@ module Wads
|
|
1423
3364
|
@rendered_nodes.values.each do |vn|
|
1424
3365
|
vn.draw
|
1425
3366
|
end
|
3367
|
+
|
1426
3368
|
# Draw the connections between nodes
|
1427
3369
|
@visible_data_nodes.values.each do |data_node|
|
1428
3370
|
data_node.outputs.each do |connected_data_node|
|
@@ -1434,9 +3376,16 @@ module Wads
|
|
1434
3376
|
if connected_rendered_node.nil?
|
1435
3377
|
# Don't draw if it is not currently visible
|
1436
3378
|
else
|
1437
|
-
|
1438
|
-
|
3379
|
+
if @is_explorer and (rendered_node.is_background or connected_rendered_node.is_background)
|
3380
|
+
# Use a dull gray color for the line
|
3381
|
+
Gosu::draw_line rendered_node.center_x, rendered_node.center_y, COLOR_LIGHT_GRAY,
|
3382
|
+
connected_rendered_node.center_x, connected_rendered_node.center_y, COLOR_LIGHT_GRAY,
|
3383
|
+
relative_z_order(Z_ORDER_GRAPHIC_ELEMENTS)
|
3384
|
+
else
|
3385
|
+
Gosu::draw_line rendered_node.center_x, rendered_node.center_y, rendered_node.graphics_color,
|
3386
|
+
connected_rendered_node.center_x, connected_rendered_node.center_y, connected_rendered_node.graphics_color,
|
1439
3387
|
relative_z_order(Z_ORDER_GRAPHIC_ELEMENTS)
|
3388
|
+
end
|
1440
3389
|
end
|
1441
3390
|
end
|
1442
3391
|
end
|