wads 0.1.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +11 -0
- data/data/sample_graph.csv +11 -0
- data/lib/wads/app.rb +40 -332
- data/lib/wads/data_structures.rb +236 -19
- data/lib/wads/textinput.rb +20 -14
- data/lib/wads/version.rb +1 -1
- data/lib/wads/widgets.rb +2142 -212
- 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/SampleGraph.png +0 -0
- data/media/WadsScreenshot.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/graph.rb +72 -0
- data/samples/star_wars.rb +112 -0
- data/samples/stocks.rb +126 -0
- data/samples/theme_test.rb +256 -0
- metadata +22 -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,1076 @@ 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
|
+
|
809
|
+
#
|
810
|
+
# This is a convenience method that creates a panel divided into a left and right,
|
811
|
+
# or east and west section. It will take up the entire space of the specified
|
812
|
+
# ARG_SECTION in the args map.
|
813
|
+
#
|
814
|
+
def add_east_west_panel(args)
|
815
|
+
section = args[ARG_SECTION]
|
816
|
+
if section.nil?
|
817
|
+
raise "East west panels require the arg '#{ARG_SECTION}' with value #{@container_map.keys.join(', ')}"
|
818
|
+
end
|
819
|
+
container = @container_map[section]
|
820
|
+
new_panel = Panel.new(container.start_x, container.start_y,
|
821
|
+
container.max_width, container.max_height)
|
822
|
+
new_panel.set_layout(LAYOUT_EAST_WEST, args)
|
823
|
+
new_panel.base_z = @parent_widget.base_z
|
824
|
+
new_panel.disable_border
|
825
|
+
@parent_widget.add_child(new_panel)
|
826
|
+
new_panel
|
827
|
+
end
|
828
|
+
end
|
829
|
+
|
830
|
+
# The layout sections are as follows:
|
831
|
+
#
|
832
|
+
# +-------------------------------------------------+
|
833
|
+
# + SECTION_NORTH +
|
834
|
+
# +-------------------------------------------------+
|
835
|
+
# + +
|
836
|
+
# + SECTION_CENTER +
|
837
|
+
# + +
|
838
|
+
# +-------------------------------------------------+
|
839
|
+
class HeaderContentLayout < SectionLayout
|
840
|
+
def initialize(x, y, width, height, parent_widget, args = {})
|
841
|
+
super
|
842
|
+
# Divide the height into 100, 100, and the middle gets everything else
|
843
|
+
# Right now we are using 100 pixels rather than a percentage for the borders
|
844
|
+
middle_section_y_start = y + 100
|
845
|
+
height_middle_section = height - 100
|
846
|
+
@container_map[SECTION_NORTH] = GuiContainer.new(x, y, width, 100)
|
847
|
+
@container_map[SECTION_CENTER] = GuiContainer.new(x, middle_section_y_start, width, height_middle_section, FILL_VERTICAL_STACK)
|
848
|
+
end
|
849
|
+
end
|
850
|
+
|
851
|
+
# The layout sections are as follows:
|
852
|
+
#
|
853
|
+
# +-------------------------------------------------+
|
854
|
+
# + +
|
855
|
+
# + SECTION_CENTER +
|
856
|
+
# + +
|
857
|
+
# +-------------------------------------------------+
|
858
|
+
# + SECTION_SOUTH +
|
859
|
+
# +-------------------------------------------------+
|
860
|
+
class ContentFooterLayout < SectionLayout
|
861
|
+
def initialize(x, y, width, height, parent_widget, args = {})
|
862
|
+
super
|
863
|
+
# Divide the height into 100, 100, and the middle gets everything else
|
864
|
+
# Right now we are using 100 pixels rather than a percentage for the borders
|
865
|
+
bottom_section_height = 100
|
866
|
+
if args[ARG_DESIRED_HEIGHT]
|
867
|
+
bottom_section_height = args[ARG_DESIRED_HEIGHT]
|
868
|
+
end
|
869
|
+
bottom_section_y_start = y + height - bottom_section_height
|
870
|
+
middle_section_height = height - bottom_section_height
|
871
|
+
@container_map[SECTION_CENTER] = GuiContainer.new(x, y, width, middle_section_height, FILL_VERTICAL_STACK)
|
872
|
+
@container_map[SECTION_SOUTH] = GuiContainer.new(x, bottom_section_y_start,
|
873
|
+
width, bottom_section_height)
|
874
|
+
end
|
875
|
+
end
|
876
|
+
|
877
|
+
# The layout sections are as follows:
|
878
|
+
#
|
879
|
+
# +-------------------------------------------------+
|
880
|
+
# + | +
|
881
|
+
# + SECTION_WEST | SECTION_EAST +
|
882
|
+
# + | +
|
883
|
+
# +-------------------------------------------------+
|
884
|
+
#
|
885
|
+
class EastWestLayout < SectionLayout
|
886
|
+
def initialize(x, y, width, height, parent_widget, args = {})
|
887
|
+
super
|
888
|
+
west_section_width = width / 2
|
889
|
+
if args[ARG_PANEL_WIDTH]
|
890
|
+
west_section_width = args[ARG_PANEL_WIDTH]
|
891
|
+
end
|
892
|
+
east_section_width = width - west_section_width
|
893
|
+
@container_map[SECTION_WEST] = GuiContainer.new(x, y,
|
894
|
+
west_section_width, height,
|
895
|
+
FILL_FULL_SIZE)
|
896
|
+
@container_map[SECTION_EAST] = GuiContainer.new(x + west_section_width, y,
|
897
|
+
east_section_width, height,
|
898
|
+
FILL_FULL_SIZE)
|
899
|
+
end
|
900
|
+
end
|
901
|
+
|
902
|
+
# The layout sections are as follows:
|
903
|
+
#
|
904
|
+
# +-------------------------------------------------+
|
905
|
+
# + SECTION_NORTH +
|
906
|
+
# +-------------------------------------------------+
|
907
|
+
# + +
|
908
|
+
# + SECTION_CENTER +
|
909
|
+
# + +
|
910
|
+
# +-------------------------------------------------+
|
911
|
+
# + SECTION_SOUTH +
|
912
|
+
# +-------------------------------------------------+
|
913
|
+
class TopMiddleBottomLayout < SectionLayout
|
914
|
+
def initialize(x, y, width, height, parent_widget, args = {})
|
915
|
+
super
|
916
|
+
# Divide the height into 100, 100, and the middle gets everything else
|
917
|
+
# Right now we are using 100 pixels rather than a percentage for the borders
|
918
|
+
middle_section_y_start = y + 100
|
919
|
+
bottom_section_y_start = y + height - 100
|
920
|
+
height_middle_section = height - 200
|
921
|
+
@container_map[SECTION_NORTH] = GuiContainer.new(x, y, width, 100)
|
922
|
+
@container_map[SECTION_CENTER] = GuiContainer.new(x, middle_section_y_start,
|
923
|
+
width, height_middle_section, FILL_VERTICAL_STACK)
|
924
|
+
@container_map[SECTION_SOUTH] = GuiContainer.new(x, bottom_section_y_start, width, 100)
|
925
|
+
end
|
926
|
+
end
|
927
|
+
|
928
|
+
# The layout sections are as follows:
|
929
|
+
#
|
930
|
+
# +-------------------------------------------------+
|
931
|
+
# + SECTION_NORTH +
|
932
|
+
# +-------------------------------------------------+
|
933
|
+
# + | | +
|
934
|
+
# + SECTION_WEST | SECTION_CENTER | SECTION_EAST +
|
935
|
+
# + | | +
|
936
|
+
# +-------------------------------------------------+
|
937
|
+
# + SECTION_SOUTH +
|
938
|
+
# +-------------------------------------------------+
|
939
|
+
class BorderLayout < SectionLayout
|
940
|
+
def initialize(x, y, width, height, parent_widget, args = {})
|
941
|
+
super
|
942
|
+
# Divide the height into 100, 100, and the middle gets everything else
|
943
|
+
# Right now we are using 100 pixels rather than a percentage for the borders
|
944
|
+
middle_section_y_start = y + 100
|
945
|
+
bottom_section_y_start = y + height - 100
|
946
|
+
|
947
|
+
height_middle_section = bottom_section_y_start - middle_section_y_start
|
948
|
+
|
949
|
+
middle_section_x_start = x + 100
|
950
|
+
right_section_x_start = x + width - 100
|
951
|
+
width_middle_section = right_section_x_start - middle_section_x_start
|
952
|
+
|
953
|
+
@container_map[SECTION_NORTH] = GuiContainer.new(x, y, width, 100)
|
954
|
+
@container_map[SECTION_WEST] = GuiContainer.new(
|
955
|
+
x, middle_section_y_start, 100, height_middle_section, FILL_VERTICAL_STACK)
|
956
|
+
@container_map[SECTION_CENTER] = GuiContainer.new(
|
957
|
+
middle_section_x_start,
|
958
|
+
middle_section_y_start,
|
959
|
+
width_middle_section,
|
960
|
+
height_middle_section,
|
961
|
+
FILL_VERTICAL_STACK)
|
962
|
+
@container_map[SECTION_EAST] = GuiContainer.new(
|
963
|
+
right_section_x_start,
|
964
|
+
middle_section_y_start,
|
965
|
+
100,
|
966
|
+
height_middle_section,
|
967
|
+
FILL_VERTICAL_STACK)
|
968
|
+
@container_map[SECTION_SOUTH] = GuiContainer.new(x, bottom_section_y_start, width, 100)
|
969
|
+
end
|
970
|
+
end
|
971
|
+
|
972
|
+
# The base class for all widgets. This class provides basic functionality for
|
973
|
+
# all gui widgets including maintaining the coordinates and layout used.
|
974
|
+
# A widget has a border and background, whose colors are defined by the theme.
|
975
|
+
# These can be turned off using the disable_border and disable_background methods.
|
976
|
+
# Widgets support a hierarchy of visible elements on the screen. For example,
|
977
|
+
# a parent widget may be a form, and it may contain many child widgets such as
|
978
|
+
# text labels, input fields, and a submit button. You can add children to a
|
979
|
+
# widget using the add or add_child methods. Children are automatically rendered
|
980
|
+
# so any child does not need an explicit call to its draw or render method.
|
981
|
+
# Children can be placed with x, y positioning relative to their parent for convenience
|
982
|
+
# (see the relative_x and relative_y methods).
|
983
|
+
#
|
984
|
+
# The draw and update methods are used by their Gosu counterparts.
|
985
|
+
# Typically there is one parent Wads widget used by a Gosu app, and it controls
|
986
|
+
# drawing all of the child widgets, invoking update on all widgets, and delegating
|
987
|
+
# user events. Widgets can override a render method for any specific drawing logic.
|
988
|
+
# It is worth showing the draw method here to amplify the point. You do not need
|
989
|
+
# to specifically call draw or render on any children. If you want to manage GUI
|
990
|
+
# elements outside of the widget hierarchy, then render is the best place to do it.
|
991
|
+
#
|
992
|
+
# Likewise, the update method recursively calls the handle_update method on all
|
993
|
+
# children in this widget's hierarchy.
|
994
|
+
#
|
995
|
+
# A commonly used method is contains_click(mouse_x, mouse_y) which returns
|
996
|
+
# whether this widget contained the mouse event. For a example, a button widget
|
997
|
+
# uses this method to determine if it was clicked.
|
998
|
+
#
|
45
999
|
class Widget
|
46
1000
|
attr_accessor :x
|
47
1001
|
attr_accessor :y
|
48
1002
|
attr_accessor :base_z
|
49
|
-
attr_accessor :
|
50
|
-
attr_accessor :
|
51
|
-
attr_accessor :border_color
|
1003
|
+
attr_accessor :gui_theme
|
1004
|
+
attr_accessor :layout
|
52
1005
|
attr_accessor :width
|
53
1006
|
attr_accessor :height
|
54
1007
|
attr_accessor :visible
|
55
|
-
attr_accessor :font
|
56
1008
|
attr_accessor :children
|
57
1009
|
attr_accessor :overlay_widget
|
58
|
-
attr_accessor :
|
1010
|
+
attr_accessor :override_color
|
1011
|
+
attr_accessor :is_selected
|
1012
|
+
attr_accessor :text_input_fields
|
59
1013
|
|
60
|
-
def initialize(x, y,
|
61
|
-
|
62
|
-
|
63
|
-
@
|
64
|
-
|
65
|
-
|
1014
|
+
def initialize(x, y, width = 10, height = 10, layout = nil, theme = nil)
|
1015
|
+
set_absolute_position(x, y)
|
1016
|
+
set_dimensions(width, height)
|
1017
|
+
@base_z = 0
|
1018
|
+
if uses_layout
|
1019
|
+
if layout.nil?
|
1020
|
+
@layout = WadsConfig.instance.create_layout(x, y, width, height, self)
|
1021
|
+
else
|
1022
|
+
@layout = layout
|
1023
|
+
end
|
1024
|
+
end
|
1025
|
+
if theme.nil?
|
1026
|
+
@gui_theme = WadsConfig.instance.current_theme
|
1027
|
+
else
|
1028
|
+
@gui_theme = theme
|
1029
|
+
end
|
66
1030
|
@visible = true
|
67
1031
|
@children = []
|
68
|
-
@
|
69
|
-
@
|
1032
|
+
@show_background = true
|
1033
|
+
@show_border = true
|
1034
|
+
@is_selected = false
|
1035
|
+
@text_input_fields = []
|
1036
|
+
end
|
1037
|
+
|
1038
|
+
def debug(message)
|
1039
|
+
WadsConfig.instance.get_logger.debug message
|
1040
|
+
end
|
1041
|
+
def info(message)
|
1042
|
+
WadsConfig.instance.get_logger.info message
|
1043
|
+
end
|
1044
|
+
def warn(message)
|
1045
|
+
WadsConfig.instance.get_logger.warn message
|
1046
|
+
end
|
1047
|
+
def error(message)
|
1048
|
+
WadsConfig.instance.get_logger.error message
|
70
1049
|
end
|
71
1050
|
|
1051
|
+
def set_absolute_position(x, y)
|
1052
|
+
@x = x
|
1053
|
+
@y = y
|
1054
|
+
end
|
1055
|
+
|
1056
|
+
def set_dimensions(width, height)
|
1057
|
+
@width = width
|
1058
|
+
@height = height
|
1059
|
+
end
|
1060
|
+
|
1061
|
+
def uses_layout
|
1062
|
+
true
|
1063
|
+
end
|
1064
|
+
|
1065
|
+
def get_layout
|
1066
|
+
if not uses_layout
|
1067
|
+
raise "The widget #{self.class.name} does not support layouts"
|
1068
|
+
end
|
1069
|
+
if @layout.nil?
|
1070
|
+
raise "No layout was defined for #{self.class.name}"
|
1071
|
+
end
|
1072
|
+
@layout
|
1073
|
+
end
|
1074
|
+
|
1075
|
+
def set_layout(layout_type, args = {})
|
1076
|
+
@layout = WadsConfig.instance.create_layout_for_widget(self, layout_type, args)
|
1077
|
+
end
|
1078
|
+
|
1079
|
+
def add_panel(section, args = {})
|
1080
|
+
get_layout.add_max_panel({ ARG_SECTION => section,
|
1081
|
+
ARG_THEME => @gui_theme}.merge(args))
|
1082
|
+
end
|
1083
|
+
|
1084
|
+
def get_theme
|
1085
|
+
@gui_theme
|
1086
|
+
end
|
1087
|
+
|
1088
|
+
def set_theme(new_theme)
|
1089
|
+
@gui_theme = new_theme
|
1090
|
+
end
|
1091
|
+
|
1092
|
+
def set_selected
|
1093
|
+
@is_selected = true
|
1094
|
+
end
|
1095
|
+
|
1096
|
+
def unset_selected
|
1097
|
+
@is_selected = false
|
1098
|
+
end
|
1099
|
+
|
1100
|
+
def graphics_color
|
1101
|
+
if @override_color
|
1102
|
+
return @override_color
|
1103
|
+
end
|
1104
|
+
@gui_theme.graphic_elements_color
|
1105
|
+
end
|
1106
|
+
|
1107
|
+
def text_color
|
1108
|
+
if @override_color
|
1109
|
+
return @override_color
|
1110
|
+
end
|
1111
|
+
@gui_theme.text_color
|
1112
|
+
end
|
1113
|
+
|
1114
|
+
def selection_color
|
1115
|
+
@gui_theme.selection_color
|
1116
|
+
end
|
1117
|
+
|
1118
|
+
def border_color
|
1119
|
+
@gui_theme.border_color
|
1120
|
+
end
|
1121
|
+
|
1122
|
+
#
|
1123
|
+
# The z order is determined by taking the base_z and adding the widget specific value.
|
1124
|
+
# An overlay widget has a base_z that is +10 higher than the widget underneath it.
|
1125
|
+
# The widget_z method provides a relative ordering that is common for user interfaces.
|
1126
|
+
# For example, text is higher than graphic elements and backgrounds.
|
1127
|
+
#
|
72
1128
|
def z_order
|
73
1129
|
@base_z + widget_z
|
74
1130
|
end
|
@@ -77,63 +1133,180 @@ module Wads
|
|
77
1133
|
@base_z + relative_order
|
78
1134
|
end
|
79
1135
|
|
1136
|
+
#
|
1137
|
+
# Add a child widget that will automatically be drawn by this widget and will received
|
1138
|
+
# delegated events. This is an alias for add_child
|
1139
|
+
#
|
1140
|
+
def add(child)
|
1141
|
+
add_child(child)
|
1142
|
+
end
|
1143
|
+
|
1144
|
+
#
|
1145
|
+
# Add a child widget that will automatically be drawn by this widget and will received
|
1146
|
+
# delegated events.
|
1147
|
+
#
|
80
1148
|
def add_child(child)
|
81
1149
|
@children << child
|
82
1150
|
end
|
83
1151
|
|
1152
|
+
#
|
1153
|
+
# Remove the given child widget
|
1154
|
+
#
|
84
1155
|
def remove_child(child)
|
85
1156
|
@children.delete(child)
|
86
1157
|
end
|
87
1158
|
|
1159
|
+
#
|
1160
|
+
# Remove a list of child widgets
|
1161
|
+
#
|
1162
|
+
def remove_children(list)
|
1163
|
+
list.each do |child|
|
1164
|
+
@children.delete(child)
|
1165
|
+
end
|
1166
|
+
end
|
1167
|
+
|
1168
|
+
#
|
1169
|
+
# Remove all children whose class name includes the given token.
|
1170
|
+
# This method can be used if you do not have a saved list of the
|
1171
|
+
# widgets you want to remove.
|
1172
|
+
#
|
1173
|
+
def remove_children_by_type(class_name_token)
|
1174
|
+
children_to_remove = []
|
1175
|
+
@children.each do |child|
|
1176
|
+
if child.class.name.include? class_name_token
|
1177
|
+
children_to_remove << child
|
1178
|
+
end
|
1179
|
+
end
|
1180
|
+
children_to_remove.each do |child|
|
1181
|
+
@children.delete(child)
|
1182
|
+
end
|
1183
|
+
end
|
1184
|
+
|
1185
|
+
#
|
1186
|
+
# Remove all children from this widget
|
1187
|
+
#
|
88
1188
|
def clear_children
|
89
1189
|
@children = []
|
90
1190
|
end
|
91
1191
|
|
92
|
-
|
93
|
-
|
1192
|
+
#
|
1193
|
+
# Drawing the background is on by default. Use this method to prevent drawing a background.
|
1194
|
+
#
|
1195
|
+
def disable_background
|
1196
|
+
@show_background = false
|
94
1197
|
end
|
95
1198
|
|
96
|
-
|
97
|
-
|
1199
|
+
#
|
1200
|
+
# Drawing the border is on by default. Use this method to prevent drawing a border.
|
1201
|
+
#
|
1202
|
+
def disable_border
|
1203
|
+
@show_border = false
|
98
1204
|
end
|
99
1205
|
|
100
|
-
|
101
|
-
|
1206
|
+
#
|
1207
|
+
# Turn back on drawing of the border
|
1208
|
+
#
|
1209
|
+
def enable_border
|
1210
|
+
@show_border = true
|
102
1211
|
end
|
103
1212
|
|
104
|
-
|
105
|
-
|
1213
|
+
#
|
1214
|
+
# Turn back on drawing of the background
|
1215
|
+
#
|
1216
|
+
def enable_background
|
1217
|
+
@show_background = true
|
106
1218
|
end
|
107
1219
|
|
108
|
-
|
109
|
-
|
110
|
-
|
1220
|
+
#
|
1221
|
+
# A convenience method, or alias, to return the left x coordinate of this widget.
|
1222
|
+
#
|
1223
|
+
def left_edge
|
1224
|
+
@x
|
111
1225
|
end
|
112
1226
|
|
1227
|
+
#
|
1228
|
+
# A convenience method to return the right x coordinate of this widget.
|
1229
|
+
#
|
113
1230
|
def right_edge
|
114
1231
|
@x + @width - 1
|
115
1232
|
end
|
116
|
-
|
1233
|
+
|
1234
|
+
#
|
1235
|
+
# A convenience method, or alias, to return the top y coordinate of this widget.
|
1236
|
+
#
|
1237
|
+
def top_edge
|
1238
|
+
@y
|
1239
|
+
end
|
1240
|
+
|
1241
|
+
#
|
1242
|
+
# A convenience method to return the bottom y coordinate of this widget
|
1243
|
+
#
|
117
1244
|
def bottom_edge
|
118
1245
|
@y + @height - 1
|
119
1246
|
end
|
120
1247
|
|
1248
|
+
#
|
1249
|
+
# A convenience method to return the center x coordinate of this widget
|
1250
|
+
#
|
121
1251
|
def center_x
|
122
1252
|
@x + ((right_edge - @x) / 2)
|
123
1253
|
end
|
124
1254
|
|
1255
|
+
#
|
1256
|
+
# A convenience method to return the center y coordinate of this widget
|
1257
|
+
#
|
125
1258
|
def center_y
|
126
1259
|
@y + ((bottom_edge - @y) / 2)
|
127
1260
|
end
|
128
1261
|
|
1262
|
+
#
|
1263
|
+
# Move this widget to an absolute x, y position on the screen.
|
1264
|
+
# It will automatically move all child widgets, however be warned that
|
1265
|
+
# if you are manually rendering any elements within your own render
|
1266
|
+
# logic, you will need to deal with that seperately as the base class
|
1267
|
+
# does not have access to its coordinates.
|
1268
|
+
#
|
1269
|
+
def move_recursive_absolute(new_x, new_y)
|
1270
|
+
delta_x = new_x - @x
|
1271
|
+
delta_y = new_y - @y
|
1272
|
+
move_recursive_delta(delta_x, delta_y)
|
1273
|
+
end
|
1274
|
+
|
1275
|
+
#
|
1276
|
+
# Move this widget to a relative number of x, y pixels on the screen.
|
1277
|
+
# It will automatically move all child widgets, however be warned that
|
1278
|
+
# if you are manually rendering any elements within your own render
|
1279
|
+
# logic, you will need to deal with that seperately as the base class
|
1280
|
+
# does not have access to its coordinates.
|
1281
|
+
#
|
1282
|
+
def move_recursive_delta(delta_x, delta_y)
|
1283
|
+
@x = @x + delta_x
|
1284
|
+
@y = @y + delta_y
|
1285
|
+
@children.each do |child|
|
1286
|
+
child.move_recursive_delta(delta_x, delta_y)
|
1287
|
+
end
|
1288
|
+
end
|
1289
|
+
|
1290
|
+
#
|
1291
|
+
# The primary draw method, used by the main Gosu loop draw method.
|
1292
|
+
# A common usage pattern is to have a primary widget in your Gosu app
|
1293
|
+
# that calls this draw method. All children of this widget are then
|
1294
|
+
# automatically drawn by this method recursively.
|
1295
|
+
# Note that as a widget author, you should only implement/override the
|
1296
|
+
# render method. This is a framework implementation that will
|
1297
|
+
# handle child rendering and invoke render as a user-implemented
|
1298
|
+
# callback.
|
1299
|
+
#
|
129
1300
|
def draw
|
130
1301
|
if @visible
|
131
1302
|
render
|
132
|
-
if @
|
1303
|
+
if @is_selected
|
1304
|
+
draw_background(Z_ORDER_SELECTION_BACKGROUND, @gui_theme.selection_color)
|
1305
|
+
elsif @show_background
|
133
1306
|
draw_background
|
134
1307
|
end
|
135
|
-
if @
|
136
|
-
draw_border
|
1308
|
+
if @show_border
|
1309
|
+
draw_border
|
137
1310
|
end
|
138
1311
|
@children.each do |child|
|
139
1312
|
child.draw
|
@@ -141,38 +1314,61 @@ module Wads
|
|
141
1314
|
end
|
142
1315
|
end
|
143
1316
|
|
144
|
-
def draw_background(z_override = nil)
|
1317
|
+
def draw_background(z_override = nil, color_override = nil)
|
1318
|
+
if color_override.nil?
|
1319
|
+
bgcolor = @gui_theme.background_color
|
1320
|
+
else
|
1321
|
+
bgcolor = color_override
|
1322
|
+
end
|
145
1323
|
if z_override
|
146
1324
|
z = relative_z_order(z_override)
|
147
1325
|
else
|
148
1326
|
z = relative_z_order(Z_ORDER_BACKGROUND)
|
149
1327
|
end
|
150
|
-
Gosu::draw_rect(@x + 1, @y + 1, @width - 3, @height - 3,
|
151
|
-
end
|
152
|
-
|
153
|
-
def draw_shadow(color)
|
154
|
-
Gosu::draw_line @x - 1, @y - 1, color, right_edge - 1, @y - 1, color, relative_z_order(Z_ORDER_BORDER)
|
155
|
-
Gosu::draw_line @x - 1, @y - 1, color, @x - 1, bottom_edge - 1, color, relative_z_order(Z_ORDER_BORDER)
|
1328
|
+
Gosu::draw_rect(@x + 1, @y + 1, @width - 3, @height - 3, bgcolor, z)
|
156
1329
|
end
|
157
1330
|
|
158
|
-
def draw_border
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
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)
|
1331
|
+
def draw_border
|
1332
|
+
Gosu::draw_line @x, @y, @gui_theme.border_color, right_edge, @y, @gui_theme.border_color, relative_z_order(Z_ORDER_BORDER)
|
1333
|
+
Gosu::draw_line @x, @y, @gui_theme.border_color, @x, bottom_edge, @gui_theme.border_color, relative_z_order(Z_ORDER_BORDER)
|
1334
|
+
Gosu::draw_line @x,bottom_edge, @gui_theme.border_color, right_edge, bottom_edge, @gui_theme.border_color, relative_z_order(Z_ORDER_BORDER)
|
1335
|
+
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
1336
|
end
|
171
1337
|
|
172
1338
|
def contains_click(mouse_x, mouse_y)
|
173
1339
|
mouse_x >= @x and mouse_x <= right_edge and mouse_y >= @y and mouse_y <= bottom_edge
|
174
1340
|
end
|
175
1341
|
|
1342
|
+
#
|
1343
|
+
# Return true if any part of the given widget overlaps on the screen with this widget
|
1344
|
+
# as defined by the rectangle from the upper left corner to the bottom right.
|
1345
|
+
# Note that your widget may not necessariliy draw pixels in this entire space.
|
1346
|
+
#
|
1347
|
+
def overlaps_with(other_widget)
|
1348
|
+
if other_widget.contains_click(@x, @y)
|
1349
|
+
return true
|
1350
|
+
end
|
1351
|
+
if other_widget.contains_click(right_edge, @y)
|
1352
|
+
return true
|
1353
|
+
end
|
1354
|
+
if other_widget.contains_click(right_edge, bottom_edge - 1)
|
1355
|
+
return true
|
1356
|
+
end
|
1357
|
+
if other_widget.contains_click(@x, bottom_edge - 1)
|
1358
|
+
return true
|
1359
|
+
end
|
1360
|
+
if other_widget.contains_click(center_x, center_y)
|
1361
|
+
return true
|
1362
|
+
end
|
1363
|
+
return false
|
1364
|
+
end
|
1365
|
+
|
1366
|
+
#
|
1367
|
+
# The framework implementation of the main Gosu update loop. This method
|
1368
|
+
# propagates the event to all child widgets as well.
|
1369
|
+
# As a widget author, do not override this method.
|
1370
|
+
# Your callback to implement is the handle_update(update_count, mouse_x, mouse_y) method.
|
1371
|
+
#
|
176
1372
|
def update(update_count, mouse_x, mouse_y)
|
177
1373
|
if @overlay_widget
|
178
1374
|
@overlay_widget.update(update_count, mouse_x, mouse_y)
|
@@ -183,8 +1379,16 @@ module Wads
|
|
183
1379
|
end
|
184
1380
|
end
|
185
1381
|
|
1382
|
+
#
|
1383
|
+
# The framework implementation of the main Gosu button down method.
|
1384
|
+
# This method separates out mouse events from keyboard events, and calls the appropriate
|
1385
|
+
# callback. As a widget author, do not override this method.
|
1386
|
+
# Your callbacks to implement are:
|
1387
|
+
# handle_mouse_down(mouse_x, mouse_y)
|
1388
|
+
# handle_right_mouse(mouse_x, mouse_y)
|
1389
|
+
# handle_key_press(id, mouse_x, mouse_y)
|
1390
|
+
#
|
186
1391
|
def button_down(id, mouse_x, mouse_y)
|
187
|
-
puts "Base widget #{self.class.name} button down #{id}." unless @debug_off
|
188
1392
|
if @overlay_widget
|
189
1393
|
result = @overlay_widget.button_down(id, mouse_x, mouse_y)
|
190
1394
|
if not result.nil? and result.is_a? WidgetResult
|
@@ -200,6 +1404,12 @@ module Wads
|
|
200
1404
|
end
|
201
1405
|
|
202
1406
|
if id == Gosu::MsLeft
|
1407
|
+
# Special handling for text input fields
|
1408
|
+
# Mouse click: Select text field based on mouse position.
|
1409
|
+
WadsConfig.instance.get_window.text_input = @text_input_fields.find { |tf| tf.under_point?(mouse_x, mouse_y) }
|
1410
|
+
# Advanced: Move caret to clicked position
|
1411
|
+
WadsConfig.instance.get_window.text_input.move_caret(mouse_x) unless WadsConfig.instance.get_window.text_input.nil?
|
1412
|
+
|
203
1413
|
result = handle_mouse_down mouse_x, mouse_y
|
204
1414
|
elsif id == Gosu::MsRight
|
205
1415
|
result = handle_right_mouse mouse_x, mouse_y
|
@@ -208,7 +1418,6 @@ module Wads
|
|
208
1418
|
end
|
209
1419
|
|
210
1420
|
if not result.nil? and result.is_a? WidgetResult
|
211
|
-
puts "Base widget #{self.class.name} returning own #{result}" unless @debug_off
|
212
1421
|
return result
|
213
1422
|
end
|
214
1423
|
|
@@ -218,7 +1427,6 @@ module Wads
|
|
218
1427
|
result = child.button_down id, mouse_x, mouse_y
|
219
1428
|
if not result.nil? and result.is_a? WidgetResult
|
220
1429
|
intercept_widget_event(result)
|
221
|
-
puts "Base widget #{self.class.name} returning child #{result}" unless @debug_off
|
222
1430
|
return result
|
223
1431
|
end
|
224
1432
|
end
|
@@ -226,13 +1434,20 @@ module Wads
|
|
226
1434
|
result = child.button_down id, mouse_x, mouse_y
|
227
1435
|
if not result.nil? and result.is_a? WidgetResult
|
228
1436
|
intercept_widget_event(result)
|
229
|
-
puts "Base widget #{self.class.name} returning child #{result}" unless @debug_off
|
230
1437
|
return result
|
231
1438
|
end
|
232
1439
|
end
|
233
1440
|
end
|
234
1441
|
end
|
235
1442
|
|
1443
|
+
#
|
1444
|
+
# The framework implementation of the main Gosu button up method.
|
1445
|
+
# This method separates out mouse events from keyboard events.
|
1446
|
+
# Only the mouse up event is propagated through the child hierarchy.
|
1447
|
+
# As a widget author, do not override this method.
|
1448
|
+
# Your callback to implement is:
|
1449
|
+
# handle_mouse_up(mouse_x, mouse_y)
|
1450
|
+
#
|
236
1451
|
def button_up(id, mouse_x, mouse_y)
|
237
1452
|
if @overlay_widget
|
238
1453
|
return @overlay_widget.button_up(id, mouse_x, mouse_y)
|
@@ -257,133 +1472,261 @@ module Wads
|
|
257
1472
|
end
|
258
1473
|
end
|
259
1474
|
|
1475
|
+
#
|
1476
|
+
# Return the absolute x coordinate given the relative x pixel to this widget
|
1477
|
+
#
|
1478
|
+
def relative_x(x)
|
1479
|
+
x_pixel_to_screen(x)
|
1480
|
+
end
|
1481
|
+
|
1482
|
+
# An alias for relative_x
|
260
1483
|
def x_pixel_to_screen(x)
|
261
1484
|
@x + x
|
262
1485
|
end
|
263
1486
|
|
1487
|
+
#
|
1488
|
+
# Return the absolute y coordinate given the relative y pixel to this widget
|
1489
|
+
#
|
1490
|
+
def relative_y(y)
|
1491
|
+
y_pixel_to_screen(y)
|
1492
|
+
end
|
1493
|
+
|
1494
|
+
# An alias for relative_y
|
264
1495
|
def y_pixel_to_screen(y)
|
265
1496
|
@y + y
|
266
1497
|
end
|
267
1498
|
|
268
|
-
|
269
|
-
|
1499
|
+
#
|
1500
|
+
# Add a child text widget using x, y positioning relative to this widget
|
1501
|
+
#
|
1502
|
+
def add_text(message, rel_x, rel_y, color = nil, use_large_font = false)
|
1503
|
+
new_text = Text.new(x_pixel_to_screen(rel_x), y_pixel_to_screen(rel_y), message,
|
1504
|
+
{ ARG_COLOR => color, ARG_USE_LARGE_FONT => use_large_font})
|
270
1505
|
new_text.base_z = @base_z
|
1506
|
+
new_text.gui_theme = @gui_theme
|
271
1507
|
add_child(new_text)
|
272
1508
|
new_text
|
273
1509
|
end
|
274
1510
|
|
1511
|
+
#
|
1512
|
+
# Add a child document widget using x, y positioning relative to this widget
|
1513
|
+
#
|
275
1514
|
def add_document(content, rel_x, rel_y, width, height)
|
276
|
-
new_doc = Document.new(
|
1515
|
+
new_doc = Document.new(x_pixel_to_screen(rel_x), y_pixel_to_screen(rel_y),
|
1516
|
+
width, height,
|
1517
|
+
content)
|
277
1518
|
new_doc.base_z = @base_z
|
1519
|
+
new_doc.gui_theme = @gui_theme
|
278
1520
|
add_child(new_doc)
|
279
1521
|
new_doc
|
280
1522
|
end
|
281
1523
|
|
1524
|
+
#
|
1525
|
+
# Add a child button widget using x, y positioning relative to this widget.
|
1526
|
+
# The width of the button will be determined based on the label text unless
|
1527
|
+
# specified in the optional parameter. The code to execute is provided as a
|
1528
|
+
# block, as shown in the example below.
|
1529
|
+
# add_button("Test Button", 10, 10) do
|
1530
|
+
# puts "User hit the test button"
|
1531
|
+
# end
|
282
1532
|
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
1533
|
if width.nil?
|
285
|
-
|
1534
|
+
args = {}
|
1535
|
+
else
|
1536
|
+
args = { ARG_DESIRED_WIDTH => width }
|
286
1537
|
end
|
287
|
-
new_button
|
1538
|
+
new_button = Button.new(x_pixel_to_screen(rel_x), y_pixel_to_screen(rel_y), label, args)
|
288
1539
|
new_button.set_action(&block)
|
289
1540
|
new_button.base_z = @base_z
|
1541
|
+
new_button.gui_theme = @gui_theme
|
290
1542
|
add_child(new_button)
|
291
1543
|
new_button
|
292
1544
|
end
|
293
1545
|
|
1546
|
+
#
|
1547
|
+
# Add a child delete button widget using x, y positioning relative to this widget.
|
1548
|
+
# A delete button is a regular button that is rendered as a red X, instead of a text label.
|
1549
|
+
#
|
294
1550
|
def add_delete_button(rel_x, rel_y, &block)
|
295
1551
|
new_delete_button = DeleteButton.new(x_pixel_to_screen(rel_x), y_pixel_to_screen(rel_y))
|
296
1552
|
new_delete_button.set_action(&block)
|
297
1553
|
new_delete_button.base_z = @base_z
|
1554
|
+
new_delete_button.gui_theme = @gui_theme
|
298
1555
|
add_child(new_delete_button)
|
299
1556
|
new_delete_button
|
300
1557
|
end
|
301
1558
|
|
302
|
-
|
1559
|
+
#
|
1560
|
+
# Add a child table widget using x, y positioning relative to this widget.
|
1561
|
+
#
|
1562
|
+
def add_table(rel_x, rel_y, width, height, column_headers, max_visible_rows = 10)
|
303
1563
|
new_table = Table.new(x_pixel_to_screen(rel_x), y_pixel_to_screen(rel_y),
|
304
|
-
width, height, column_headers,
|
1564
|
+
width, height, column_headers, max_visible_rows)
|
305
1565
|
new_table.base_z = @base_z
|
1566
|
+
new_table.gui_theme = @gui_theme
|
306
1567
|
add_child(new_table)
|
307
1568
|
new_table
|
308
1569
|
end
|
309
1570
|
|
310
|
-
|
1571
|
+
#
|
1572
|
+
# Add a child table widget using x, y positioning relative to this widget.
|
1573
|
+
# The user can select up to one and only one item in the table.
|
1574
|
+
#
|
1575
|
+
def add_single_select_table(rel_x, rel_y, width, height, column_headers, max_visible_rows = 10)
|
311
1576
|
new_table = SingleSelectTable.new(x_pixel_to_screen(rel_x), y_pixel_to_screen(rel_y),
|
312
|
-
width, height, column_headers,
|
1577
|
+
width, height, column_headers, max_visible_rows)
|
313
1578
|
new_table.base_z = @base_z
|
1579
|
+
new_table.gui_theme = @gui_theme
|
314
1580
|
add_child(new_table)
|
315
1581
|
new_table
|
316
1582
|
end
|
317
1583
|
|
318
|
-
|
1584
|
+
#
|
1585
|
+
# Add a child table widget using x, y positioning relative to this widget.
|
1586
|
+
# The user can zero to many items in the table.
|
1587
|
+
#
|
1588
|
+
def add_multi_select_table(rel_x, rel_y, width, height, column_headers, max_visible_rows = 10)
|
319
1589
|
new_table = MultiSelectTable.new(x_pixel_to_screen(rel_x), y_pixel_to_screen(rel_y),
|
320
|
-
width, height, column_headers,
|
1590
|
+
width, height, column_headers, max_visible_rows)
|
321
1591
|
new_table.base_z = @base_z
|
1592
|
+
new_table.gui_theme = @gui_theme
|
322
1593
|
add_child(new_table)
|
323
1594
|
new_table
|
324
1595
|
end
|
325
1596
|
|
1597
|
+
#
|
1598
|
+
# Add a child graph display widget using x, y positioning relative to this widget.
|
1599
|
+
#
|
326
1600
|
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,
|
1601
|
+
new_graph = GraphWidget.new(x_pixel_to_screen(rel_x), y_pixel_to_screen(rel_y), width, height, graph)
|
328
1602
|
new_graph.base_z = @base_z
|
329
1603
|
add_child(new_graph)
|
330
1604
|
new_graph
|
331
1605
|
end
|
332
1606
|
|
1607
|
+
#
|
1608
|
+
# Add a child plot display widget using x, y positioning relative to this widget.
|
1609
|
+
#
|
333
1610
|
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
|
1611
|
+
new_plot = Plot.new(x_pixel_to_screen(rel_x), y_pixel_to_screen(rel_y), width, height)
|
335
1612
|
new_plot.base_z = @base_z
|
1613
|
+
new_plot.gui_theme = @gui_theme
|
336
1614
|
add_child(new_plot)
|
337
1615
|
new_plot
|
338
1616
|
end
|
339
1617
|
|
340
|
-
|
341
|
-
|
1618
|
+
#
|
1619
|
+
# Add child axis lines widget using x, y positioning relative to this widget.
|
1620
|
+
#
|
1621
|
+
def add_axis_lines(rel_x, rel_y, width, height)
|
1622
|
+
new_axis_lines = AxisLines.new(x_pixel_to_screen(rel_x), y_pixel_to_screen(rel_y), width, height)
|
342
1623
|
new_axis_lines.base_z = @base_z
|
1624
|
+
new_axis_lines.gui_theme = @gui_theme
|
343
1625
|
add_child(new_axis_lines)
|
344
1626
|
new_axis_lines
|
345
1627
|
end
|
346
1628
|
|
1629
|
+
#
|
1630
|
+
# Add a child image widget using x, y positioning relative to this widget.
|
1631
|
+
#
|
1632
|
+
def add_image(filename, rel_x, rel_y)
|
1633
|
+
new_image = ImageWidget.new(x_pixel_to_screen(rel_x), y_pixel_to_screen(rel_y), img)
|
1634
|
+
new_image.base_z = @base_z
|
1635
|
+
new_image.gui_theme = @gui_theme
|
1636
|
+
add_child(new_image)
|
1637
|
+
new_image
|
1638
|
+
end
|
1639
|
+
|
1640
|
+
#
|
1641
|
+
# Add an overlay widget that is drawn on top of (at a higher z level) this widget
|
1642
|
+
#
|
347
1643
|
def add_overlay(overlay)
|
348
1644
|
overlay.base_z = @base_z + 10
|
349
1645
|
add_child(overlay)
|
350
1646
|
@overlay_widget = overlay
|
351
1647
|
end
|
352
1648
|
|
1649
|
+
# For all child widgets, adjust the x coordinate
|
1650
|
+
# so that they are centered.
|
1651
|
+
def center_children
|
1652
|
+
if @children.empty?
|
1653
|
+
return
|
1654
|
+
end
|
1655
|
+
number_of_children = @children.size
|
1656
|
+
total_width_of_children = 0
|
1657
|
+
@children.each do |child|
|
1658
|
+
total_width_of_children = total_width_of_children + child.width + 5
|
1659
|
+
end
|
1660
|
+
total_width_of_children = total_width_of_children - 5
|
1661
|
+
|
1662
|
+
start_x = (@width - total_width_of_children) / 2
|
1663
|
+
@children.each do |child|
|
1664
|
+
child.x = start_x
|
1665
|
+
start_x = start_x + child.width + 5
|
1666
|
+
end
|
1667
|
+
end
|
1668
|
+
|
353
1669
|
#
|
354
|
-
#
|
1670
|
+
# Override this method in your subclass to process mouse down events.
|
1671
|
+
# The base implementation is empty
|
355
1672
|
#
|
356
1673
|
def handle_mouse_down mouse_x, mouse_y
|
357
1674
|
# empty base implementation
|
358
1675
|
end
|
359
1676
|
|
1677
|
+
#
|
1678
|
+
# Override this method in your subclass to process mouse up events.
|
1679
|
+
# The base implementation is empty
|
1680
|
+
#
|
360
1681
|
def handle_mouse_up mouse_x, mouse_y
|
361
1682
|
# empty base implementation
|
362
1683
|
end
|
363
1684
|
|
1685
|
+
#
|
1686
|
+
# Override this method in your subclass to process the right mouse click event.
|
1687
|
+
# Note we do not differentiate between up and down for the right mouse button.
|
1688
|
+
# The base implementation is empty
|
1689
|
+
#
|
364
1690
|
def handle_right_mouse mouse_x, mouse_y
|
365
1691
|
# empty base implementation
|
366
1692
|
end
|
367
1693
|
|
1694
|
+
#
|
1695
|
+
# Override this method in your subclass to process keyboard events.
|
1696
|
+
# The base implementation is empty.
|
1697
|
+
# Note that the mouse was not necessarily positioned over this widget.
|
1698
|
+
# You can check this using the contains_click(mouse_x, mouse_y) method
|
1699
|
+
# and decide if you want to process the event based on that, if desired.
|
1700
|
+
#
|
368
1701
|
def handle_key_press id, mouse_x, mouse_y
|
369
1702
|
# empty base implementation
|
370
1703
|
end
|
371
1704
|
|
1705
|
+
#
|
1706
|
+
# Override this method in your subclass to perform any logic needed
|
1707
|
+
# as part of the main Gosu update loop. In most cases, this method is
|
1708
|
+
# invoked 60 times per second.
|
1709
|
+
#
|
372
1710
|
def handle_update update_count, mouse_x, mouse_y
|
373
1711
|
# empty base implementation
|
374
1712
|
end
|
375
1713
|
|
1714
|
+
#
|
1715
|
+
# Override this method in your subclass to perform any custom rendering logic.
|
1716
|
+
# Note that child widgets are automatically drawn and you do not need to do
|
1717
|
+
# that yourself.
|
1718
|
+
#
|
376
1719
|
def render
|
377
1720
|
# 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
1721
|
end
|
381
1722
|
|
1723
|
+
#
|
1724
|
+
# Return the relative z order compared to other widgets.
|
1725
|
+
# The absolute z order is the base plus this value.
|
1726
|
+
# Its calculated relative so that overlay widgets can be
|
1727
|
+
# on top of base displays.
|
1728
|
+
#
|
382
1729
|
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
1730
|
0
|
388
1731
|
end
|
389
1732
|
|
@@ -393,36 +1736,124 @@ module Wads
|
|
393
1736
|
end
|
394
1737
|
end
|
395
1738
|
|
1739
|
+
#
|
1740
|
+
# A panel is simply an alias for a widget, although you can optionally
|
1741
|
+
# treat them differently if you wish. Generally a panel is used to
|
1742
|
+
# apply a specific layout to a sub-section of the screen.
|
1743
|
+
#
|
1744
|
+
class Panel < Widget
|
1745
|
+
def initialize(x, y, w, h, layout = nil, theme = nil)
|
1746
|
+
super(x, y, w, h, layout, theme)
|
1747
|
+
end
|
1748
|
+
end
|
1749
|
+
|
1750
|
+
#
|
1751
|
+
# Displays an image on the screen at the specific x, y location. The image
|
1752
|
+
# can be scaled by setting the scale attribute. The image attribute to the
|
1753
|
+
# construcor can be the string file location or a Gosu::Image instance
|
1754
|
+
#
|
1755
|
+
class ImageWidget < Widget
|
1756
|
+
attr_accessor :img
|
1757
|
+
attr_accessor :scale
|
1758
|
+
|
1759
|
+
def initialize(x, y, image, args = {})
|
1760
|
+
super(x, y)
|
1761
|
+
if image.is_a? String
|
1762
|
+
@img = Gosu::Image.new(image)
|
1763
|
+
elsif image.is_a? Gosu::Image
|
1764
|
+
@img = image
|
1765
|
+
else
|
1766
|
+
raise "ImageWidget requires either a filename or a Gosu::Image object"
|
1767
|
+
end
|
1768
|
+
if args[ARG_THEME]
|
1769
|
+
@gui_theme = args[ARG_THEME]
|
1770
|
+
end
|
1771
|
+
@scale = 1
|
1772
|
+
disable_border
|
1773
|
+
disable_background
|
1774
|
+
set_dimensions(@img.width, @img.height)
|
1775
|
+
end
|
1776
|
+
|
1777
|
+
def render
|
1778
|
+
@img.draw @x, @y, z_order, @scale, @scale
|
1779
|
+
end
|
1780
|
+
|
1781
|
+
def widget_z
|
1782
|
+
Z_ORDER_FOCAL_ELEMENTS
|
1783
|
+
end
|
1784
|
+
end
|
1785
|
+
|
1786
|
+
#
|
1787
|
+
# Displays a text label on the screen at the specific x, y location.
|
1788
|
+
# The font specified by the current theme is used.
|
1789
|
+
# The theme text color is used, unless the color parameter specifies an override.
|
1790
|
+
# The small font is used by default, unless the use_large_font parameter is true.
|
1791
|
+
#
|
396
1792
|
class Text < Widget
|
397
|
-
attr_accessor :
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
@
|
1793
|
+
attr_accessor :label
|
1794
|
+
|
1795
|
+
def initialize(x, y, label, args = {})
|
1796
|
+
super(x, y)
|
1797
|
+
@label = label
|
1798
|
+
if args[ARG_THEME]
|
1799
|
+
@gui_theme = args[ARG_THEME]
|
1800
|
+
end
|
1801
|
+
if args[ARG_USE_LARGE_FONT]
|
1802
|
+
@use_large_font = args[ARG_USE_LARGE_FONT]
|
1803
|
+
end
|
1804
|
+
if args[ARG_COLOR]
|
1805
|
+
@override_color = args[ARG_COLOR]
|
1806
|
+
end
|
1807
|
+
disable_border
|
1808
|
+
if @use_large_font
|
1809
|
+
set_dimensions(@gui_theme.font_large.text_width(@label) + 10, 20)
|
1810
|
+
else
|
1811
|
+
set_dimensions(@gui_theme.font.text_width(@label) + 10, 20)
|
1812
|
+
end
|
402
1813
|
end
|
1814
|
+
|
1815
|
+
def set_text(new_text)
|
1816
|
+
@label = new_text
|
1817
|
+
end
|
1818
|
+
|
1819
|
+
def change_text(new_text)
|
1820
|
+
set_text(new_text)
|
1821
|
+
end
|
1822
|
+
|
403
1823
|
def render
|
404
|
-
|
1824
|
+
if @use_large_font
|
1825
|
+
get_theme.font_large.draw_text(@label, @x, @y, z_order, 1, 1, text_color)
|
1826
|
+
else
|
1827
|
+
get_theme.font.draw_text(@label, @x, @y, z_order, 1, 1, text_color)
|
1828
|
+
end
|
405
1829
|
end
|
1830
|
+
|
406
1831
|
def widget_z
|
407
1832
|
Z_ORDER_TEXT
|
408
1833
|
end
|
409
1834
|
end
|
410
1835
|
|
1836
|
+
#
|
1837
|
+
# An ErrorMessage is a subclass of text that uses a red color
|
1838
|
+
#
|
411
1839
|
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)
|
1840
|
+
def initialize(x, y, message)
|
1841
|
+
super(x, y, "ERROR: #{message}", COLOR_ERROR_CODE_RED)
|
416
1842
|
end
|
417
1843
|
end
|
418
1844
|
|
1845
|
+
#
|
1846
|
+
# A data point to be used in a Plot widget. This object holds
|
1847
|
+
# the x, y screen location as well as the data values for x, y.
|
1848
|
+
#
|
419
1849
|
class PlotPoint < Widget
|
420
1850
|
attr_accessor :data_x
|
421
1851
|
attr_accessor :data_y
|
422
1852
|
attr_accessor :data_point_size
|
423
1853
|
|
424
1854
|
def initialize(x, y, data_x, data_y, color = COLOR_MAROON, size = 4)
|
425
|
-
super(x, y
|
1855
|
+
super(x, y)
|
1856
|
+
@override_color = color
|
426
1857
|
@data_x = data_x
|
427
1858
|
@data_y = data_y
|
428
1859
|
@data_point_size = size
|
@@ -436,7 +1867,7 @@ module Wads
|
|
436
1867
|
half_size = size_to_draw / 2
|
437
1868
|
Gosu::draw_rect(@x - half_size, @y - half_size,
|
438
1869
|
size_to_draw, size_to_draw,
|
439
|
-
|
1870
|
+
graphics_color, z_order)
|
440
1871
|
end
|
441
1872
|
|
442
1873
|
def widget_z
|
@@ -458,32 +1889,41 @@ module Wads
|
|
458
1889
|
end
|
459
1890
|
end
|
460
1891
|
|
1892
|
+
#
|
1893
|
+
# Displays a button at the specified x, y location.
|
1894
|
+
# The button width is based on the label text unless specified
|
1895
|
+
# using the optional parameter. The code to executeon a button
|
1896
|
+
# click is specified using the set_action method, however typical
|
1897
|
+
# using involves the widget or layout form of add_button. For example:
|
1898
|
+
# add_button("Test Button", 10, 10) do
|
1899
|
+
# puts "User hit the test button"
|
1900
|
+
# end
|
1901
|
+
|
461
1902
|
class Button < Widget
|
462
1903
|
attr_accessor :label
|
463
|
-
attr_accessor :text_color
|
464
1904
|
attr_accessor :is_pressed
|
465
1905
|
attr_accessor :action_code
|
466
1906
|
|
467
|
-
def initialize(
|
468
|
-
super(x, y
|
469
|
-
set_font(font)
|
1907
|
+
def initialize(x, y, label, args = {})
|
1908
|
+
super(x, y)
|
470
1909
|
@label = label
|
471
|
-
|
472
|
-
|
473
|
-
|
1910
|
+
if args[ARG_THEME]
|
1911
|
+
@gui_theme = args[ARG_THEME]
|
1912
|
+
end
|
1913
|
+
@text_pixel_width = @gui_theme.font.text_width(@label)
|
1914
|
+
if args[ARG_DESIRED_WIDTH]
|
1915
|
+
@width = args[ARG_DESIRED_WIDTH]
|
474
1916
|
else
|
475
|
-
@width =
|
1917
|
+
@width = @text_pixel_width + 10
|
476
1918
|
end
|
477
1919
|
@height = 26
|
478
|
-
@text_color = text_color
|
479
1920
|
@is_pressed = false
|
480
1921
|
@is_pressed_update_count = -100
|
481
1922
|
end
|
482
1923
|
|
483
1924
|
def render
|
484
|
-
draw_border(@color)
|
485
1925
|
text_x = center_x - (@text_pixel_width / 2)
|
486
|
-
@font.draw_text(@label, text_x, @y, z_order, 1, 1,
|
1926
|
+
@gui_theme.font.draw_text(@label, text_x, @y, z_order, 1, 1, text_color)
|
487
1927
|
end
|
488
1928
|
|
489
1929
|
def widget_z
|
@@ -508,22 +1948,24 @@ module Wads
|
|
508
1948
|
end
|
509
1949
|
|
510
1950
|
if update_count < @is_pressed_update_count + 15
|
511
|
-
|
1951
|
+
unset_selected
|
512
1952
|
elsif contains_click(mouse_x, mouse_y)
|
513
|
-
|
1953
|
+
set_selected
|
514
1954
|
else
|
515
|
-
|
1955
|
+
unset_selected
|
516
1956
|
end
|
517
1957
|
end
|
518
1958
|
end
|
519
1959
|
|
1960
|
+
#
|
1961
|
+
# A subclass of button that renders a red X instead of label text
|
1962
|
+
#
|
520
1963
|
class DeleteButton < Button
|
521
|
-
def initialize(x, y)
|
522
|
-
super("ignore",
|
1964
|
+
def initialize(x, y, args = {})
|
1965
|
+
super(x, y, "ignore", {ARG_DESIRED_WIDTH => 50}.merge(args))
|
523
1966
|
set_dimensions(14, 14)
|
524
1967
|
add_child(Line.new(@x, @y, right_edge, bottom_edge, COLOR_ERROR_CODE_RED))
|
525
1968
|
add_child(Line.new(@x, bottom_edge, right_edge, @y, COLOR_ERROR_CODE_RED))
|
526
|
-
set_border(@color)
|
527
1969
|
end
|
528
1970
|
|
529
1971
|
def render
|
@@ -531,20 +1973,26 @@ module Wads
|
|
531
1973
|
end
|
532
1974
|
end
|
533
1975
|
|
1976
|
+
#
|
1977
|
+
# Displays multiple lines of text content at the specified coordinates
|
1978
|
+
#
|
534
1979
|
class Document < Widget
|
535
1980
|
attr_accessor :lines
|
536
1981
|
|
537
|
-
def initialize(
|
538
|
-
super(x, y
|
539
|
-
set_font(font)
|
1982
|
+
def initialize(x, y, width, height, content, args = {})
|
1983
|
+
super(x, y)
|
540
1984
|
set_dimensions(width, height)
|
541
1985
|
@lines = content.split("\n")
|
1986
|
+
disable_border
|
1987
|
+
if args[ARG_THEME]
|
1988
|
+
@gui_theme = args[ARG_THEME]
|
1989
|
+
end
|
542
1990
|
end
|
543
1991
|
|
544
1992
|
def render
|
545
1993
|
y = @y + 4
|
546
1994
|
@lines.each do |line|
|
547
|
-
@font.draw_text(line, @x + 5, y, z_order, 1, 1,
|
1995
|
+
@gui_theme.font.draw_text(line, @x + 5, y, z_order, 1, 1, text_color)
|
548
1996
|
y = y + 26
|
549
1997
|
end
|
550
1998
|
end
|
@@ -555,19 +2003,18 @@ module Wads
|
|
555
2003
|
end
|
556
2004
|
|
557
2005
|
class InfoBox < Widget
|
558
|
-
def initialize(
|
2006
|
+
def initialize(x, y, width, height, title, content, args = {})
|
559
2007
|
super(x, y)
|
560
|
-
set_font(font)
|
561
2008
|
set_dimensions(width, height)
|
562
|
-
set_border(COLOR_WHITE)
|
563
|
-
set_background(COLOR_OFF_GRAY)
|
564
2009
|
@base_z = 10
|
2010
|
+
if args[ARG_THEME]
|
2011
|
+
@gui_theme = args[ARG_THEME]
|
2012
|
+
end
|
565
2013
|
add_text(title, 5, 5)
|
566
2014
|
add_document(content, 5, 52, width, height - 52)
|
567
2015
|
ok_button = add_button("OK", (@width / 2) - 50, height - 26) do
|
568
2016
|
WidgetResult.new(true)
|
569
2017
|
end
|
570
|
-
ok_button.text_color = COLOR_WHITE
|
571
2018
|
ok_button.width = 100
|
572
2019
|
end
|
573
2020
|
|
@@ -581,13 +2028,8 @@ module Wads
|
|
581
2028
|
class Dialog < Widget
|
582
2029
|
attr_accessor :textinput
|
583
2030
|
|
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)
|
2031
|
+
def initialize(x, y, width, height, title, text_input_default)
|
2032
|
+
super(x, y, width, height)
|
591
2033
|
@base_z = 10
|
592
2034
|
@error_message = nil
|
593
2035
|
|
@@ -596,7 +2038,7 @@ module Wads
|
|
596
2038
|
add_document(content, 0, 56, width, height)
|
597
2039
|
|
598
2040
|
# Forms automatically get a text input widget
|
599
|
-
@textinput = TextField.new(
|
2041
|
+
@textinput = TextField.new(x + 10, bottom_edge - 80, text_input_default, 600)
|
600
2042
|
@textinput.base_z = 10
|
601
2043
|
add_child(@textinput)
|
602
2044
|
|
@@ -604,15 +2046,11 @@ module Wads
|
|
604
2046
|
ok_button = add_button("OK", (@width / 2) - 100, height - 32) do
|
605
2047
|
handle_ok
|
606
2048
|
end
|
607
|
-
ok_button.color = COLOR_FORM_BUTTON
|
608
|
-
ok_button.text_color = COLOR_WHITE
|
609
2049
|
ok_button.width = 100
|
610
2050
|
|
611
2051
|
cancel_button = add_button("Cancel", (@width / 2) + 50, height - 32) do
|
612
2052
|
WidgetResult.new(true)
|
613
2053
|
end
|
614
|
-
cancel_button.color = COLOR_FORM_BUTTON
|
615
|
-
cancel_button.text_color = COLOR_WHITE
|
616
2054
|
cancel_button.width = 100
|
617
2055
|
end
|
618
2056
|
|
@@ -624,7 +2062,7 @@ module Wads
|
|
624
2062
|
end
|
625
2063
|
|
626
2064
|
def add_error_message(msg)
|
627
|
-
@error_message = ErrorMessage.new(
|
2065
|
+
@error_message = ErrorMessage.new(x + 10, bottom_edge - 120, msg)
|
628
2066
|
@error_message.base_z = @base_z
|
629
2067
|
end
|
630
2068
|
|
@@ -647,9 +2085,9 @@ module Wads
|
|
647
2085
|
|
648
2086
|
def handle_mouse_down mouse_x, mouse_y
|
649
2087
|
# Mouse click: Select text field based on mouse position.
|
650
|
-
|
2088
|
+
WadsConfig.instance.get_window.text_input = [@textinput].find { |tf| tf.under_point?(mouse_x, mouse_y) }
|
651
2089
|
# Advanced: Move caret to clicked position
|
652
|
-
|
2090
|
+
WadsConfig.instance.get_window.text_input.move_caret(mouse_x) unless WadsConfig.instance.get_window.text_input.nil?
|
653
2091
|
|
654
2092
|
handle_mouse_click(mouse_x, mouse_y)
|
655
2093
|
end
|
@@ -661,6 +2099,14 @@ module Wads
|
|
661
2099
|
end
|
662
2100
|
end
|
663
2101
|
|
2102
|
+
#
|
2103
|
+
# A result object returned from handle methods that instructs the parent widget
|
2104
|
+
# what to do. A close_widget value of true instructs the recipient to close
|
2105
|
+
# either the overlay window or the entire app, based on the context of the receiver.
|
2106
|
+
# In the case of a form being submitted, the action may be "OK" and the form_data
|
2107
|
+
# contains the information supplied by the user.
|
2108
|
+
# WidgetResult is intentionally generic so it can support a wide variety of use cases.
|
2109
|
+
#
|
664
2110
|
class WidgetResult
|
665
2111
|
attr_accessor :close_widget
|
666
2112
|
attr_accessor :action
|
@@ -673,80 +2119,124 @@ module Wads
|
|
673
2119
|
end
|
674
2120
|
end
|
675
2121
|
|
2122
|
+
#
|
2123
|
+
# Renders a line from x, y to x2, y2. The theme graphics elements color
|
2124
|
+
# is used by default, unless specified using the optional parameter.
|
2125
|
+
#
|
676
2126
|
class Line < Widget
|
677
2127
|
attr_accessor :x2
|
678
2128
|
attr_accessor :y2
|
679
2129
|
|
680
|
-
def initialize(x, y, x2, y2, color =
|
681
|
-
super
|
2130
|
+
def initialize(x, y, x2, y2, color = nil)
|
2131
|
+
super(x, y)
|
2132
|
+
@override_color = color
|
682
2133
|
@x2 = x2
|
683
2134
|
@y2 = y2
|
2135
|
+
disable_border
|
2136
|
+
disable_background
|
684
2137
|
end
|
685
2138
|
|
686
2139
|
def render
|
687
|
-
Gosu::draw_line x, y,
|
2140
|
+
Gosu::draw_line x, y, graphics_color, x2, y2, graphics_color, z_order
|
688
2141
|
end
|
689
2142
|
|
690
2143
|
def widget_z
|
691
2144
|
Z_ORDER_GRAPHIC_ELEMENTS
|
692
2145
|
end
|
2146
|
+
|
2147
|
+
def uses_layout
|
2148
|
+
false
|
2149
|
+
end
|
693
2150
|
end
|
694
2151
|
|
2152
|
+
#
|
2153
|
+
# A very specific widget used along with a Plot to draw the x and y axis lines.
|
2154
|
+
# Note that the labels are drawn using separate widgets.
|
2155
|
+
#
|
695
2156
|
class AxisLines < Widget
|
696
|
-
def initialize(x, y, width, height, color =
|
697
|
-
super
|
698
|
-
|
699
|
-
|
2157
|
+
def initialize(x, y, width, height, color = nil)
|
2158
|
+
super(x, y)
|
2159
|
+
set_dimensions(width, height)
|
2160
|
+
disable_border
|
2161
|
+
disable_background
|
700
2162
|
end
|
701
2163
|
|
702
2164
|
def render
|
703
|
-
add_child(Line.new(@x, @y, @x, bottom_edge,
|
704
|
-
add_child(Line.new(@x, bottom_edge, right_edge, bottom_edge,
|
2165
|
+
add_child(Line.new(@x, @y, @x, bottom_edge, graphics_color))
|
2166
|
+
add_child(Line.new(@x, bottom_edge, right_edge, bottom_edge, graphics_color))
|
2167
|
+
end
|
2168
|
+
|
2169
|
+
def uses_layout
|
2170
|
+
false
|
705
2171
|
end
|
706
2172
|
end
|
707
2173
|
|
2174
|
+
#
|
2175
|
+
# Labels and tic marks for the vertical axis on a plot
|
2176
|
+
#
|
708
2177
|
class VerticalAxisLabel < Widget
|
709
2178
|
attr_accessor :label
|
710
2179
|
|
711
|
-
def initialize(x, y, label,
|
712
|
-
super
|
713
|
-
set_font(font)
|
2180
|
+
def initialize(x, y, label, color = nil)
|
2181
|
+
super(x, y)
|
714
2182
|
@label = label
|
2183
|
+
@override_color = color
|
2184
|
+
text_pixel_width = @gui_theme.font.text_width(@label)
|
2185
|
+
add_text(@label, -text_pixel_width - 28, -12)
|
2186
|
+
disable_border
|
2187
|
+
disable_background
|
715
2188
|
end
|
716
2189
|
|
717
2190
|
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)
|
2191
|
+
Gosu::draw_line @x - 20, @y, graphics_color,
|
2192
|
+
@x, @y, graphics_color, z_order
|
723
2193
|
end
|
724
2194
|
|
725
2195
|
def widget_z
|
726
2196
|
Z_ORDER_GRAPHIC_ELEMENTS
|
727
2197
|
end
|
2198
|
+
|
2199
|
+
def uses_layout
|
2200
|
+
false
|
2201
|
+
end
|
728
2202
|
end
|
729
2203
|
|
2204
|
+
#
|
2205
|
+
# Labels and tic marks for the horizontal axis on a plot
|
2206
|
+
#
|
730
2207
|
class HorizontalAxisLabel < Widget
|
731
2208
|
attr_accessor :label
|
732
2209
|
|
733
|
-
def initialize(x, y, label,
|
734
|
-
super
|
735
|
-
set_font(font)
|
2210
|
+
def initialize(x, y, label, color = nil)
|
2211
|
+
super(x, y)
|
736
2212
|
@label = label
|
2213
|
+
@override_color = color
|
2214
|
+
text_pixel_width = @gui_theme.font.text_width(@label)
|
2215
|
+
add_text(@label, -(text_pixel_width / 2), 26)
|
2216
|
+
disable_border
|
2217
|
+
disable_background
|
737
2218
|
end
|
738
2219
|
|
739
2220
|
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)
|
2221
|
+
Gosu::draw_line @x, @y, graphics_color, @x, @y + 20, graphics_color, z_order
|
743
2222
|
end
|
744
2223
|
|
745
2224
|
def widget_z
|
746
2225
|
Z_ORDER_TEXT
|
747
2226
|
end
|
2227
|
+
|
2228
|
+
def uses_layout
|
2229
|
+
false
|
2230
|
+
end
|
748
2231
|
end
|
749
2232
|
|
2233
|
+
#
|
2234
|
+
# Displays a table of information at the given coordinates.
|
2235
|
+
# The headers are an array of text labels to display at the top of each column.
|
2236
|
+
# The max_visible_rows specifies how many rows are visible at once.
|
2237
|
+
# If there are more data rows than the max, the arrow keys can be used to
|
2238
|
+
# page up or down through the rows in the table.
|
2239
|
+
#
|
750
2240
|
class Table < Widget
|
751
2241
|
attr_accessor :data_rows
|
752
2242
|
attr_accessor :row_colors
|
@@ -755,10 +2245,12 @@ module Wads
|
|
755
2245
|
attr_accessor :current_row
|
756
2246
|
attr_accessor :can_delete_rows
|
757
2247
|
|
758
|
-
def initialize(x, y, width, height, headers,
|
759
|
-
super(x, y
|
760
|
-
set_font(font)
|
2248
|
+
def initialize(x, y, width, height, headers, max_visible_rows = 10, args = {})
|
2249
|
+
super(x, y)
|
761
2250
|
set_dimensions(width, height)
|
2251
|
+
if args[ARG_THEME]
|
2252
|
+
@gui_theme = args[ARG_THEME]
|
2253
|
+
end
|
762
2254
|
@headers = headers
|
763
2255
|
@current_row = 0
|
764
2256
|
@max_visible_rows = max_visible_rows
|
@@ -785,7 +2277,7 @@ module Wads
|
|
785
2277
|
@row_colors = []
|
786
2278
|
end
|
787
2279
|
|
788
|
-
def add_row(data_row, color =
|
2280
|
+
def add_row(data_row, color = text_color )
|
789
2281
|
@data_rows << data_row
|
790
2282
|
@row_colors << color
|
791
2283
|
end
|
@@ -840,9 +2332,9 @@ module Wads
|
|
840
2332
|
column_widths = []
|
841
2333
|
number_of_columns = @data_rows[0].size
|
842
2334
|
(0..number_of_columns-1).each do |c|
|
843
|
-
max_length = @font.text_width(headers[c])
|
2335
|
+
max_length = @gui_theme.font.text_width(headers[c])
|
844
2336
|
(0..number_of_rows-1).each do |r|
|
845
|
-
text_pixel_width = @font.text_width(@data_rows[r][c])
|
2337
|
+
text_pixel_width = @gui_theme.font.text_width(@data_rows[r][c])
|
846
2338
|
if text_pixel_width > max_length
|
847
2339
|
max_length = text_pixel_width
|
848
2340
|
end
|
@@ -850,18 +2342,22 @@ module Wads
|
|
850
2342
|
column_widths[c] = max_length
|
851
2343
|
end
|
852
2344
|
|
2345
|
+
# Draw a horizontal line between header and data rows
|
853
2346
|
x = @x + 10
|
854
2347
|
if number_of_columns > 1
|
855
2348
|
(0..number_of_columns-2).each do |c|
|
856
2349
|
x = x + column_widths[c] + 20
|
857
|
-
Gosu::draw_line x, @y,
|
2350
|
+
Gosu::draw_line x, @y, graphics_color, x, @y + @height, graphics_color, z_order
|
858
2351
|
end
|
859
2352
|
end
|
860
2353
|
|
861
|
-
|
2354
|
+
# Draw the header row
|
2355
|
+
y = @y
|
2356
|
+
Gosu::draw_rect(@x + 1, y, @width - 3, 28, graphics_color, relative_z_order(Z_ORDER_SELECTION_BACKGROUND))
|
2357
|
+
|
862
2358
|
x = @x + 20
|
863
2359
|
(0..number_of_columns-1).each do |c|
|
864
|
-
@font.draw_text(@headers[c], x, y, z_order, 1, 1,
|
2360
|
+
@gui_theme.font.draw_text(@headers[c], x, y + 3, z_order, 1, 1, text_color)
|
865
2361
|
x = x + column_widths[c] + 20
|
866
2362
|
end
|
867
2363
|
y = y + 30
|
@@ -873,7 +2369,7 @@ module Wads
|
|
873
2369
|
elsif count < @current_row + @max_visible_rows
|
874
2370
|
x = @x + 20
|
875
2371
|
(0..number_of_columns-1).each do |c|
|
876
|
-
@font.draw_text(row[c], x, y + 2, z_order, 1, 1, @row_colors[count])
|
2372
|
+
@gui_theme.font.draw_text(row[c], x, y + 2, z_order, 1, 1, @row_colors[count])
|
877
2373
|
x = x + column_widths[c] + 20
|
878
2374
|
end
|
879
2375
|
y = y + 30
|
@@ -894,15 +2390,31 @@ module Wads
|
|
894
2390
|
def widget_z
|
895
2391
|
Z_ORDER_TEXT
|
896
2392
|
end
|
2393
|
+
|
2394
|
+
def uses_layout
|
2395
|
+
false
|
2396
|
+
end
|
897
2397
|
end
|
898
2398
|
|
2399
|
+
#
|
2400
|
+
# A table where the user can select one row at a time.
|
2401
|
+
# The selected row has a background color specified by the selection color of the
|
2402
|
+
# current theme.
|
2403
|
+
#
|
899
2404
|
class SingleSelectTable < Table
|
900
2405
|
attr_accessor :selected_row
|
901
|
-
attr_accessor :selected_color
|
902
2406
|
|
903
|
-
def initialize(x, y, width, height, headers,
|
904
|
-
super(x, y, width, height, headers,
|
905
|
-
|
2407
|
+
def initialize(x, y, width, height, headers, max_visible_rows = 10, args = {})
|
2408
|
+
super(x, y, width, height, headers, max_visible_rows, args)
|
2409
|
+
end
|
2410
|
+
|
2411
|
+
def is_row_selected(mouse_y)
|
2412
|
+
row_number = determine_row_number(mouse_y)
|
2413
|
+
if row_number.nil?
|
2414
|
+
return false
|
2415
|
+
end
|
2416
|
+
selected_row = @current_row + row_number
|
2417
|
+
@selected_row == selected_row
|
906
2418
|
end
|
907
2419
|
|
908
2420
|
def set_selected_row(mouse_y, column_number)
|
@@ -919,12 +2431,22 @@ module Wads
|
|
919
2431
|
end
|
920
2432
|
end
|
921
2433
|
|
2434
|
+
def unset_selected_row(mouse_y, column_number)
|
2435
|
+
row_number = determine_row_number(mouse_y)
|
2436
|
+
if not row_number.nil?
|
2437
|
+
this_selected_row = @current_row + row_number
|
2438
|
+
@selected_row = this_selected_row
|
2439
|
+
return @data_rows[this_selected_row][column_number]
|
2440
|
+
end
|
2441
|
+
nil
|
2442
|
+
end
|
2443
|
+
|
922
2444
|
def render
|
923
2445
|
super
|
924
2446
|
if @selected_row
|
925
2447
|
if @selected_row >= @current_row and @selected_row < @current_row + @max_visible_rows
|
926
2448
|
y = @y + 30 + ((@selected_row - @current_row) * 30)
|
927
|
-
Gosu::draw_rect(@x + 20, y, @width - 30, 28, @
|
2449
|
+
Gosu::draw_rect(@x + 20, y, @width - 30, 28, @gui_theme.selection_color, relative_z_order(Z_ORDER_SELECTION_BACKGROUND))
|
928
2450
|
end
|
929
2451
|
end
|
930
2452
|
end
|
@@ -932,18 +2454,53 @@ module Wads
|
|
932
2454
|
def widget_z
|
933
2455
|
Z_ORDER_TEXT
|
934
2456
|
end
|
2457
|
+
|
2458
|
+
def handle_mouse_down mouse_x, mouse_y
|
2459
|
+
if contains_click(mouse_x, mouse_y)
|
2460
|
+
row_number = determine_row_number(mouse_y)
|
2461
|
+
if row_number.nil?
|
2462
|
+
return WidgetResult.new(false)
|
2463
|
+
end
|
2464
|
+
# First check if its the delete button that got this
|
2465
|
+
delete_this_row = false
|
2466
|
+
@delete_buttons.each do |db|
|
2467
|
+
if db.contains_click(mouse_x, mouse_y)
|
2468
|
+
delete_this_row = true
|
2469
|
+
end
|
2470
|
+
end
|
2471
|
+
if delete_this_row
|
2472
|
+
if not row_number.nil?
|
2473
|
+
data_set_row_to_delete = @current_row + row_number
|
2474
|
+
data_set_name_to_delete = @data_rows[data_set_row_to_delete][1]
|
2475
|
+
@data_rows.delete_at(data_set_row_to_delete)
|
2476
|
+
return WidgetResult.new(false, EVENT_TABLE_ROW_DELETE, [data_set_name_to_delete])
|
2477
|
+
end
|
2478
|
+
else
|
2479
|
+
if is_row_selected(mouse_y)
|
2480
|
+
unset_selected_row(mouse_y, 0)
|
2481
|
+
return WidgetResult.new(false, EVENT_TABLE_UNSELECT, @data_rows[row_number])
|
2482
|
+
else
|
2483
|
+
set_selected_row(mouse_y, 0)
|
2484
|
+
return WidgetResult.new(false, EVENT_TABLE_SELECT, @data_rows[row_number])
|
2485
|
+
end
|
2486
|
+
end
|
2487
|
+
end
|
2488
|
+
end
|
935
2489
|
end
|
936
2490
|
|
2491
|
+
#
|
2492
|
+
# A table where the user can select multiple rows at a time.
|
2493
|
+
# Selected rows have a background color specified by the selection color of the
|
2494
|
+
# current theme.
|
2495
|
+
#
|
937
2496
|
class MultiSelectTable < Table
|
938
2497
|
attr_accessor :selected_rows
|
939
|
-
attr_accessor :selection_color
|
940
2498
|
|
941
|
-
def initialize(x, y, width, height, headers,
|
942
|
-
super(x, y, width, height, headers,
|
2499
|
+
def initialize(x, y, width, height, headers, max_visible_rows = 10, args = {})
|
2500
|
+
super(x, y, width, height, headers, max_visible_rows, args)
|
943
2501
|
@selected_rows = []
|
944
|
-
|
945
|
-
|
946
|
-
|
2502
|
+
end
|
2503
|
+
|
947
2504
|
def is_row_selected(mouse_y)
|
948
2505
|
row_number = determine_row_number(mouse_y)
|
949
2506
|
if row_number.nil?
|
@@ -983,7 +2540,7 @@ module Wads
|
|
983
2540
|
width_of_selection_background = width_of_selection_background - 20
|
984
2541
|
end
|
985
2542
|
Gosu::draw_rect(@x + 20, y, width_of_selection_background, 28,
|
986
|
-
@selection_color,
|
2543
|
+
@gui_theme.selection_color,
|
987
2544
|
relative_z_order(Z_ORDER_SELECTION_BACKGROUND))
|
988
2545
|
end
|
989
2546
|
y = y + 30
|
@@ -1028,6 +2585,11 @@ module Wads
|
|
1028
2585
|
end
|
1029
2586
|
end
|
1030
2587
|
|
2588
|
+
#
|
2589
|
+
# A two-dimensional graph display which plots a number of PlotPoint objects.
|
2590
|
+
# Options include grid lines that can be displayed, as well as whether lines
|
2591
|
+
# should be drawn connecting each point in a data set.
|
2592
|
+
#
|
1031
2593
|
class Plot < Widget
|
1032
2594
|
attr_accessor :points
|
1033
2595
|
attr_accessor :visible_range
|
@@ -1036,9 +2598,8 @@ module Wads
|
|
1036
2598
|
attr_accessor :zoom_level
|
1037
2599
|
attr_accessor :visibility_map
|
1038
2600
|
|
1039
|
-
def initialize(x, y, width, height
|
1040
|
-
super
|
1041
|
-
set_font(font)
|
2601
|
+
def initialize(x, y, width, height)
|
2602
|
+
super(x, y)
|
1042
2603
|
set_dimensions(width, height)
|
1043
2604
|
@display_grid = false
|
1044
2605
|
@display_lines = true
|
@@ -1050,6 +2611,7 @@ module Wads
|
|
1050
2611
|
# Hash of rendered points keyed by data set name, so we can toggle visibility
|
1051
2612
|
@points_by_data_set_name = {}
|
1052
2613
|
@visibility_map = {}
|
2614
|
+
disable_border
|
1053
2615
|
end
|
1054
2616
|
|
1055
2617
|
def toggle_visibility(data_set_name)
|
@@ -1111,6 +2673,24 @@ module Wads
|
|
1111
2673
|
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
|
1112
2674
|
end
|
1113
2675
|
|
2676
|
+
def add_data_point(data_set_name, data_x, data_y, color = COLOR_MAROON)
|
2677
|
+
if range_set?
|
2678
|
+
rendered_points = @points_by_data_set_name[data_set_name]
|
2679
|
+
if rendered_points.nil?
|
2680
|
+
rendered_points = []
|
2681
|
+
@points_by_data_set_name[data_set_name] = rendered_points
|
2682
|
+
end
|
2683
|
+
rendered_points << PlotPoint.new(draw_x(data_x), draw_y(data_y),
|
2684
|
+
data_x, data_y,
|
2685
|
+
color)
|
2686
|
+
if @visibility_map[data_set_name].nil?
|
2687
|
+
@visibility_map[data_set_name] = true
|
2688
|
+
end
|
2689
|
+
else
|
2690
|
+
error("ERROR: range not set, cannot add data")
|
2691
|
+
end
|
2692
|
+
end
|
2693
|
+
|
1114
2694
|
def add_data_set(data_set_name, rendered_points)
|
1115
2695
|
if range_set?
|
1116
2696
|
@points_by_data_set_name[data_set_name] = rendered_points
|
@@ -1118,7 +2698,7 @@ module Wads
|
|
1118
2698
|
@visibility_map[data_set_name] = true
|
1119
2699
|
end
|
1120
2700
|
else
|
1121
|
-
|
2701
|
+
error("ERROR: range not set, cannot add data")
|
1122
2702
|
end
|
1123
2703
|
end
|
1124
2704
|
|
@@ -1168,8 +2748,8 @@ module Wads
|
|
1168
2748
|
if points.length > 1
|
1169
2749
|
points.inject(points[0]) do |last, the_next|
|
1170
2750
|
if last.x < the_next.x
|
1171
|
-
Gosu::draw_line last.x, last.y, last.
|
1172
|
-
the_next.x, the_next.y, last.
|
2751
|
+
Gosu::draw_line last.x, last.y, last.graphics_color,
|
2752
|
+
the_next.x, the_next.y, last.graphics_color, relative_z_order(Z_ORDER_GRAPHIC_ELEMENTS)
|
1173
2753
|
end
|
1174
2754
|
the_next
|
1175
2755
|
end
|
@@ -1232,19 +2812,47 @@ module Wads
|
|
1232
2812
|
end
|
1233
2813
|
end
|
1234
2814
|
|
2815
|
+
#
|
2816
|
+
# A graphical representation of a node in a graph using a button-style, i.e
|
2817
|
+
# a rectangular border with a text label.
|
2818
|
+
# The choice to use this display class is dictated by the use_icons attribute
|
2819
|
+
# of the current theme.
|
2820
|
+
# Like images, the size of node widgets can be scaled.
|
2821
|
+
#
|
1235
2822
|
class NodeWidget < Button
|
1236
2823
|
attr_accessor :data_node
|
1237
2824
|
|
1238
|
-
def initialize(
|
1239
|
-
super(
|
1240
|
-
|
2825
|
+
def initialize(x, y, node, color = nil, initial_scale = 1, is_explorer = false)
|
2826
|
+
super(x, y, node.name)
|
2827
|
+
@orig_width = @width
|
2828
|
+
@orig_height = @height
|
1241
2829
|
@data_node = node
|
2830
|
+
@override_color = color
|
2831
|
+
set_scale(initial_scale, @is_explorer)
|
2832
|
+
end
|
2833
|
+
|
2834
|
+
def is_background
|
2835
|
+
@scale <= 1 and @is_explorer
|
2836
|
+
end
|
2837
|
+
|
2838
|
+
def set_scale(value, is_explorer = false)
|
2839
|
+
@scale = value
|
2840
|
+
@is_explorer = is_explorer
|
2841
|
+
if value < 1
|
2842
|
+
value = 1
|
2843
|
+
end
|
2844
|
+
@width = @orig_width * @scale.to_f
|
2845
|
+
debug("In regular node widget Setting scale of #{@label} to #{@scale}")
|
2846
|
+
end
|
2847
|
+
|
2848
|
+
def get_text_widget
|
2849
|
+
nil
|
1242
2850
|
end
|
1243
2851
|
|
1244
2852
|
def render
|
1245
2853
|
super
|
1246
2854
|
draw_background(Z_ORDER_FOCAL_ELEMENTS)
|
1247
|
-
draw_shadow(COLOR_GRAY)
|
2855
|
+
#draw_shadow(COLOR_GRAY)
|
1248
2856
|
end
|
1249
2857
|
|
1250
2858
|
def widget_z
|
@@ -1252,26 +2860,127 @@ module Wads
|
|
1252
2860
|
end
|
1253
2861
|
end
|
1254
2862
|
|
2863
|
+
#
|
2864
|
+
# A graphical representation of a node in a graph using circular icons
|
2865
|
+
# and adjacent text labels.
|
2866
|
+
# The choice to use this display class is dictated by the use_icons attribute
|
2867
|
+
# of the current theme.
|
2868
|
+
# Like images, the size of node widgets can be scaled.
|
2869
|
+
#
|
2870
|
+
class NodeIconWidget < Widget
|
2871
|
+
attr_accessor :data_node
|
2872
|
+
attr_accessor :image
|
2873
|
+
attr_accessor :scale
|
2874
|
+
attr_accessor :label
|
2875
|
+
attr_accessor :is_explorer
|
2876
|
+
|
2877
|
+
def initialize(x, y, node, color = nil, initial_scale = 1, is_explorer = false)
|
2878
|
+
super(x, y)
|
2879
|
+
@override_color = color
|
2880
|
+
@data_node = node
|
2881
|
+
@label = node.name
|
2882
|
+
circle_image = WadsConfig.instance.circle(color)
|
2883
|
+
if circle_image.nil?
|
2884
|
+
@image = WadsConfig.instance.circle(COLOR_BLUE)
|
2885
|
+
else
|
2886
|
+
@image = circle_image
|
2887
|
+
end
|
2888
|
+
@is_explorer = is_explorer
|
2889
|
+
set_scale(initial_scale, @is_explorer)
|
2890
|
+
disable_border
|
2891
|
+
end
|
2892
|
+
|
2893
|
+
def name
|
2894
|
+
@data_node.name
|
2895
|
+
end
|
2896
|
+
|
2897
|
+
def is_background
|
2898
|
+
@scale <= 0.1 and @is_explorer
|
2899
|
+
end
|
2900
|
+
|
2901
|
+
def set_scale(value, is_explorer = false)
|
2902
|
+
@is_explorer = is_explorer
|
2903
|
+
if value < 0.5
|
2904
|
+
value = 0.5
|
2905
|
+
end
|
2906
|
+
@scale = value / 10.to_f
|
2907
|
+
#debug("In node widget Setting scale of #{@label} to #{value} = #{@scale}")
|
2908
|
+
@width = IMAGE_CIRCLE_SIZE * scale.to_f
|
2909
|
+
@height = IMAGE_CIRCLE_SIZE * scale.to_f
|
2910
|
+
# Only in explorer mode do we dull out nodes on the outer edge
|
2911
|
+
if is_background
|
2912
|
+
@image = WadsConfig.instance.circle(COLOR_ALPHA)
|
2913
|
+
else
|
2914
|
+
text_pixel_width = @gui_theme.font.text_width(@label)
|
2915
|
+
clear_children # the text widget is the only child, so we can remove all
|
2916
|
+
add_text(@label, (@width / 2) - (text_pixel_width / 2), -20)
|
2917
|
+
end
|
2918
|
+
end
|
2919
|
+
|
2920
|
+
def get_text_widget
|
2921
|
+
if @children.size > 0
|
2922
|
+
return @children[0]
|
2923
|
+
end
|
2924
|
+
#raise "No text widget for NodeIconWidget"
|
2925
|
+
nil
|
2926
|
+
end
|
2927
|
+
|
2928
|
+
def render
|
2929
|
+
@image.draw @x, @y, relative_z_order(Z_ORDER_FOCAL_ELEMENTS), @scale, @scale
|
2930
|
+
end
|
2931
|
+
|
2932
|
+
def widget_z
|
2933
|
+
Z_ORDER_TEXT
|
2934
|
+
end
|
2935
|
+
end
|
2936
|
+
|
2937
|
+
#
|
2938
|
+
# Given a single node or a graph data structure, this widget displays
|
2939
|
+
# a visualization of the graph using one of the available node widget classes.
|
2940
|
+
# There are different display modes that control what nodes within the graph
|
2941
|
+
# are shown. The default display mode, GRAPH_DISPLAY_ALL, shows all nodes
|
2942
|
+
# as the name implies. GRAPH_DISPLAY_TREE assumes an acyclic graph and renders
|
2943
|
+
# the graph in a tree-like structure. GRAPH_DISPLAY_EXPLORER has a chosen
|
2944
|
+
# center focus node with connected nodes circled around it based on the depth
|
2945
|
+
# or distance from that node. This mode also allows the user to click on
|
2946
|
+
# different nodes to navigate the graph and change focus nodes.
|
2947
|
+
#
|
1255
2948
|
class GraphWidget < Widget
|
1256
2949
|
attr_accessor :graph
|
1257
|
-
attr_accessor :center_node
|
1258
2950
|
attr_accessor :selected_node
|
1259
2951
|
attr_accessor :selected_node_x_offset
|
1260
2952
|
attr_accessor :selected_node_y_offset
|
2953
|
+
attr_accessor :size_by_connections
|
2954
|
+
attr_accessor :is_explorer
|
1261
2955
|
|
1262
|
-
def initialize(x, y, width, height,
|
1263
|
-
super
|
1264
|
-
set_font(font)
|
2956
|
+
def initialize(x, y, width, height, graph, display_mode = GRAPH_DISPLAY_ALL)
|
2957
|
+
super(x, y)
|
1265
2958
|
set_dimensions(width, height)
|
1266
|
-
|
1267
|
-
|
1268
|
-
|
2959
|
+
if graph.is_a? Node
|
2960
|
+
@graph = Graph.new(graph)
|
2961
|
+
else
|
2962
|
+
@graph = graph
|
2963
|
+
end
|
2964
|
+
@size_by_connections = false
|
2965
|
+
@is_explorer = false
|
2966
|
+
if [GRAPH_DISPLAY_ALL, GRAPH_DISPLAY_TREE, GRAPH_DISPLAY_EXPLORER].include? display_mode
|
2967
|
+
debug("Displaying graph in #{display_mode} mode")
|
2968
|
+
else
|
2969
|
+
raise "#{display_mode} is not a valid display mode for Graph Widget"
|
2970
|
+
end
|
2971
|
+
if display_mode == GRAPH_DISPLAY_ALL
|
2972
|
+
set_all_nodes_for_display
|
2973
|
+
elsif display_mode == GRAPH_DISPLAY_TREE
|
2974
|
+
set_tree_display
|
2975
|
+
else
|
2976
|
+
set_explorer_display
|
2977
|
+
end
|
1269
2978
|
end
|
1270
2979
|
|
1271
2980
|
def handle_update update_count, mouse_x, mouse_y
|
1272
2981
|
if contains_click(mouse_x, mouse_y) and @selected_node
|
1273
|
-
@selected_node.
|
1274
|
-
|
2982
|
+
@selected_node.move_recursive_absolute(mouse_x - @selected_node_x_offset,
|
2983
|
+
mouse_y - @selected_node_y_offset)
|
1275
2984
|
end
|
1276
2985
|
end
|
1277
2986
|
|
@@ -1283,6 +2992,7 @@ module Wads
|
|
1283
2992
|
@selected_node = rn
|
1284
2993
|
@selected_node_x_offset = mouse_x - rn.x
|
1285
2994
|
@selected_node_y_offset = mouse_y - rn.y
|
2995
|
+
@click_timestamp = Time.now
|
1286
2996
|
end
|
1287
2997
|
end
|
1288
2998
|
end
|
@@ -1291,11 +3001,40 @@ module Wads
|
|
1291
3001
|
|
1292
3002
|
def handle_mouse_up mouse_x, mouse_y
|
1293
3003
|
if @selected_node
|
3004
|
+
if @is_explorer
|
3005
|
+
time_between_mouse_up_down = Time.now - @click_timestamp
|
3006
|
+
if time_between_mouse_up_down < 0.2
|
3007
|
+
# Treat this as a single click and make the selected
|
3008
|
+
# node the new center node of the graph
|
3009
|
+
set_explorer_display(@selected_node.data_node)
|
3010
|
+
end
|
3011
|
+
end
|
1294
3012
|
@selected_node = nil
|
1295
3013
|
end
|
1296
3014
|
end
|
1297
3015
|
|
1298
|
-
def
|
3016
|
+
def set_explorer_display(center_node = nil)
|
3017
|
+
if center_node.nil?
|
3018
|
+
# If not specified, pick a center node as the one with the most connections
|
3019
|
+
center_node = @graph.node_with_most_connections
|
3020
|
+
end
|
3021
|
+
|
3022
|
+
@graph.reset_visited
|
3023
|
+
@visible_data_nodes = {}
|
3024
|
+
center_node.bfs(4) do |n|
|
3025
|
+
@visible_data_nodes[n.name] = n
|
3026
|
+
end
|
3027
|
+
|
3028
|
+
@size_by_connections = false
|
3029
|
+
@is_explorer = true
|
3030
|
+
|
3031
|
+
@rendered_nodes = {}
|
3032
|
+
populate_rendered_nodes
|
3033
|
+
|
3034
|
+
prevent_text_overlap
|
3035
|
+
end
|
3036
|
+
|
3037
|
+
def set_tree_display
|
1299
3038
|
@graph.reset_visited
|
1300
3039
|
@visible_data_nodes = @graph.node_map
|
1301
3040
|
@rendered_nodes = {}
|
@@ -1305,7 +3044,7 @@ module Wads
|
|
1305
3044
|
width_for_each_root_tree = @width / number_of_root_nodes
|
1306
3045
|
|
1307
3046
|
start_x = 0
|
1308
|
-
y_level =
|
3047
|
+
y_level = 20
|
1309
3048
|
root_nodes.each do |root|
|
1310
3049
|
set_tree_recursive(root, start_x, start_x + width_for_each_root_tree - 1, y_level)
|
1311
3050
|
start_x = start_x + width_for_each_root_tree
|
@@ -1315,8 +3054,108 @@ module Wads
|
|
1315
3054
|
@rendered_nodes.values.each do |rn|
|
1316
3055
|
rn.base_z = @base_z
|
1317
3056
|
end
|
3057
|
+
|
3058
|
+
if @size_by_connections
|
3059
|
+
scale_node_size
|
3060
|
+
end
|
3061
|
+
|
3062
|
+
prevent_text_overlap
|
3063
|
+
end
|
3064
|
+
|
3065
|
+
def scale_node_size
|
3066
|
+
range = @graph.get_number_of_connections_range
|
3067
|
+
# There are six colors. Any number of scale sizes
|
3068
|
+
# Lets try 4 first as a max size.
|
3069
|
+
bins = range.bin_max_values(4)
|
3070
|
+
|
3071
|
+
# Set the scale for each node
|
3072
|
+
@visible_data_nodes.values.each do |node|
|
3073
|
+
num_links = node.number_of_links
|
3074
|
+
index = 0
|
3075
|
+
while index < bins.size
|
3076
|
+
if num_links <= bins[index]
|
3077
|
+
@rendered_nodes[node.name].set_scale(index + 1, @is_explorer)
|
3078
|
+
index = bins.size
|
3079
|
+
end
|
3080
|
+
index = index + 1
|
3081
|
+
end
|
3082
|
+
end
|
3083
|
+
end
|
3084
|
+
|
3085
|
+
def prevent_text_overlap
|
3086
|
+
@rendered_nodes.values.each do |rn|
|
3087
|
+
text = rn.get_text_widget
|
3088
|
+
if text
|
3089
|
+
if overlaps_with_a_node(text)
|
3090
|
+
move_text_for_node(rn)
|
3091
|
+
else
|
3092
|
+
move_in_bounds = false
|
3093
|
+
# We also check to see if the text is outside the edges of this widget
|
3094
|
+
if text.x < @x or text.right_edge > right_edge
|
3095
|
+
move_in_bounds = true
|
3096
|
+
elsif text.y < @y or text.bottom_edge > bottom_edge
|
3097
|
+
move_in_bounds = true
|
3098
|
+
end
|
3099
|
+
if move_in_bounds
|
3100
|
+
debug("#{text.label} was out of bounds")
|
3101
|
+
move_text_for_node(rn)
|
3102
|
+
end
|
3103
|
+
end
|
3104
|
+
end
|
3105
|
+
end
|
3106
|
+
end
|
3107
|
+
|
3108
|
+
def move_text_for_node(rendered_node)
|
3109
|
+
text = rendered_node.get_text_widget
|
3110
|
+
if text.nil?
|
3111
|
+
return
|
3112
|
+
end
|
3113
|
+
radians_between_attempts = DEG_360 / 24
|
3114
|
+
current_radians = 0.05
|
3115
|
+
done = false
|
3116
|
+
while not done
|
3117
|
+
# Use radians to spread the other nodes around the center node
|
3118
|
+
# TODO base the distance off of scale
|
3119
|
+
text_x = rendered_node.center_x + ((rendered_node.width / 2) * Math.cos(current_radians))
|
3120
|
+
text_y = rendered_node.center_y - ((rendered_node.height / 2) * Math.sin(current_radians))
|
3121
|
+
if text_x < @x
|
3122
|
+
text_x = @x + 1
|
3123
|
+
elsif text_x > right_edge - 20
|
3124
|
+
text_x = right_edge - 20
|
3125
|
+
end
|
3126
|
+
if text_y < @y
|
3127
|
+
text_y = @y + 1
|
3128
|
+
elsif text_y > bottom_edge - 26
|
3129
|
+
text_y = bottom_edge - 26
|
3130
|
+
end
|
3131
|
+
text.x = text_x
|
3132
|
+
text.y = text_y
|
3133
|
+
current_radians = current_radians + radians_between_attempts
|
3134
|
+
if overlaps_with_a_node(text)
|
3135
|
+
# check for done
|
3136
|
+
if current_radians > DEG_360
|
3137
|
+
done = true
|
3138
|
+
error("ERROR: could not find a spot to put the text")
|
3139
|
+
end
|
3140
|
+
else
|
3141
|
+
done = true
|
3142
|
+
end
|
3143
|
+
end
|
1318
3144
|
end
|
1319
3145
|
|
3146
|
+
def overlaps_with_a_node(text)
|
3147
|
+
@rendered_nodes.values.each do |rn|
|
3148
|
+
if text.label == rn.label
|
3149
|
+
# don't compare to yourself
|
3150
|
+
else
|
3151
|
+
if rn.overlaps_with(text)
|
3152
|
+
return true
|
3153
|
+
end
|
3154
|
+
end
|
3155
|
+
end
|
3156
|
+
false
|
3157
|
+
end
|
3158
|
+
|
1320
3159
|
def set_tree_recursive(current_node, start_x, end_x, y_level)
|
1321
3160
|
# Draw the current node, and then recursively divide up
|
1322
3161
|
# and call again for each of the children
|
@@ -1325,13 +3164,19 @@ module Wads
|
|
1325
3164
|
end
|
1326
3165
|
current_node.visited = true
|
1327
3166
|
|
1328
|
-
@
|
3167
|
+
if @gui_theme.use_icons
|
3168
|
+
@rendered_nodes[current_node.name] = NodeIconWidget.new(
|
3169
|
+
x_pixel_to_screen(start_x + ((end_x - start_x) / 2)),
|
3170
|
+
y_pixel_to_screen(y_level),
|
1329
3171
|
current_node,
|
3172
|
+
get_node_color(current_node))
|
3173
|
+
else
|
3174
|
+
@rendered_nodes[current_node.name] = NodeWidget.new(
|
1330
3175
|
x_pixel_to_screen(start_x + ((end_x - start_x) / 2)),
|
1331
3176
|
y_pixel_to_screen(y_level),
|
1332
|
-
|
1333
|
-
get_node_color(current_node),
|
3177
|
+
current_node,
|
1334
3178
|
get_node_color(current_node))
|
3179
|
+
end
|
1335
3180
|
|
1336
3181
|
number_of_child_nodes = current_node.outputs.size
|
1337
3182
|
if number_of_child_nodes == 0
|
@@ -1353,10 +3198,14 @@ module Wads
|
|
1353
3198
|
@visible_data_nodes = @graph.node_map
|
1354
3199
|
@rendered_nodes = {}
|
1355
3200
|
populate_rendered_nodes
|
3201
|
+
if @size_by_connections
|
3202
|
+
scale_node_size
|
3203
|
+
end
|
3204
|
+
prevent_text_overlap
|
1356
3205
|
end
|
1357
3206
|
|
1358
3207
|
def get_node_color(node)
|
1359
|
-
color_tag = node.get_tag(
|
3208
|
+
color_tag = node.get_tag(COLOR_TAG)
|
1360
3209
|
if color_tag.nil?
|
1361
3210
|
return @color
|
1362
3211
|
end
|
@@ -1371,18 +3220,60 @@ module Wads
|
|
1371
3220
|
# Convert the data nodes to rendered nodes
|
1372
3221
|
# Start by putting the center node in the center, then draw others around it
|
1373
3222
|
@rendered_nodes = {}
|
1374
|
-
@
|
1375
|
-
|
3223
|
+
if @gui_theme.use_icons
|
3224
|
+
@rendered_nodes[center_node.name] = NodeIconWidget.new(
|
3225
|
+
center_x, center_y, center_node, get_node_color(center_node))
|
3226
|
+
else
|
3227
|
+
@rendered_nodes[center_node.name] = NodeWidget.new(center_x, center_y,
|
3228
|
+
center_node, get_node_color(center_node), get_node_color(center_node))
|
3229
|
+
end
|
1376
3230
|
|
1377
3231
|
populate_rendered_nodes(center_node)
|
3232
|
+
|
3233
|
+
if @size_by_connections
|
3234
|
+
scale_node_size
|
3235
|
+
end
|
3236
|
+
prevent_text_overlap
|
1378
3237
|
end
|
1379
3238
|
|
1380
3239
|
def populate_rendered_nodes(center_node = nil)
|
1381
3240
|
# Spread out the other nodes around the center node
|
1382
|
-
# going in a circle
|
1383
|
-
|
1384
|
-
|
1385
|
-
|
3241
|
+
# going in a circle at each depth level
|
3242
|
+
stats = Stats.new("NodesPerDepth")
|
3243
|
+
@visible_data_nodes.values.each do |n|
|
3244
|
+
stats.increment(n.depth)
|
3245
|
+
end
|
3246
|
+
current_radians = []
|
3247
|
+
radians_increment = []
|
3248
|
+
(1..4).each do |n|
|
3249
|
+
number_of_nodes_at_depth = stats.count(n)
|
3250
|
+
radians_increment[n] = DEG_360 / number_of_nodes_at_depth.to_f
|
3251
|
+
current_radians[n] = 0.05
|
3252
|
+
end
|
3253
|
+
|
3254
|
+
padding = 100
|
3255
|
+
size_of_x_band = (@width - padding) / 6
|
3256
|
+
size_of_y_band = (@height - padding) / 6
|
3257
|
+
random_x = size_of_x_band / 8
|
3258
|
+
random_y = size_of_y_band / 8
|
3259
|
+
half_random_x = random_x / 2
|
3260
|
+
half_random_y = random_y / 2
|
3261
|
+
|
3262
|
+
# Precompute the band center points
|
3263
|
+
# then reference by the scale or depth values below
|
3264
|
+
band_center_x = padding + (size_of_x_band / 2)
|
3265
|
+
band_center_y = padding + (size_of_y_band / 2)
|
3266
|
+
# depth 1 [0] - center node, distance should be zero. Should be only one
|
3267
|
+
# depth 2 [1] - band one
|
3268
|
+
# depth 3 [2] - band two
|
3269
|
+
# depth 4 [3] - band three
|
3270
|
+
bands_x = [0, band_center_x]
|
3271
|
+
bands_x << band_center_x + size_of_x_band
|
3272
|
+
bands_x << band_center_x + size_of_x_band + size_of_x_band
|
3273
|
+
|
3274
|
+
bands_y = [0, band_center_y]
|
3275
|
+
bands_y << band_center_y + size_of_y_band
|
3276
|
+
bands_y << band_center_y + size_of_y_band + size_of_y_band
|
1386
3277
|
|
1387
3278
|
@visible_data_nodes.each do |node_name, data_node|
|
1388
3279
|
process_this_node = true
|
@@ -1392,10 +3283,33 @@ module Wads
|
|
1392
3283
|
end
|
1393
3284
|
end
|
1394
3285
|
if process_this_node
|
3286
|
+
scale_to_use = 1
|
3287
|
+
if stats.count(1) > 0 and stats.count(2) == 0
|
3288
|
+
# if all nodes are depth 1, then size everything
|
3289
|
+
# as a small node
|
3290
|
+
elsif data_node.depth < 4
|
3291
|
+
scale_to_use = 5 - data_node.depth
|
3292
|
+
end
|
3293
|
+
if @is_explorer
|
3294
|
+
# TODO Layer the nodes around the center
|
3295
|
+
# We need a better multiplier based on the height and width
|
3296
|
+
# max distance x would be (@width / 2) - padding
|
3297
|
+
# divide that into three regions, layer 2, 3, and 4
|
3298
|
+
# get the center point for each of these regions, and do a random from there
|
3299
|
+
# scale to use determines which of the regions
|
3300
|
+
band_index = 4 - scale_to_use
|
3301
|
+
distance_from_center_x = bands_x[band_index] + rand(random_x) - half_random_x
|
3302
|
+
distance_from_center_y = bands_y[band_index] + rand(random_y) - half_random_y
|
3303
|
+
else
|
3304
|
+
distance_from_center_x = 80 + rand(200)
|
3305
|
+
distance_from_center_y = 40 + rand(100)
|
3306
|
+
end
|
1395
3307
|
# Use radians to spread the other nodes around the center node
|
1396
|
-
|
1397
|
-
|
1398
|
-
|
3308
|
+
radians_to_use = current_radians[data_node.depth]
|
3309
|
+
radians_to_use = radians_to_use + (rand(radians_increment[data_node.depth]) / 2)
|
3310
|
+
current_radians[data_node.depth] = current_radians[data_node.depth] + radians_increment[data_node.depth]
|
3311
|
+
node_x = center_x + (distance_from_center_x * Math.cos(radians_to_use))
|
3312
|
+
node_y = center_y - (distance_from_center_y * Math.sin(radians_to_use))
|
1399
3313
|
if node_x < @x
|
1400
3314
|
node_x = @x + 1
|
1401
3315
|
elsif node_x > right_edge - 20
|
@@ -1406,17 +3320,26 @@ module Wads
|
|
1406
3320
|
elsif node_y > bottom_edge - 26
|
1407
3321
|
node_y = bottom_edge - 26
|
1408
3322
|
end
|
1409
|
-
current_radians = current_radians + radians_between_nodes
|
1410
3323
|
|
1411
3324
|
# Note we can link between data nodes and rendered nodes using the node name
|
1412
3325
|
# We have a map of each
|
1413
|
-
@
|
3326
|
+
if @gui_theme.use_icons
|
3327
|
+
@rendered_nodes[data_node.name] = NodeIconWidget.new(
|
3328
|
+
node_x,
|
3329
|
+
node_y,
|
1414
3330
|
data_node,
|
3331
|
+
get_node_color(data_node),
|
3332
|
+
scale_to_use,
|
3333
|
+
@is_explorer)
|
3334
|
+
else
|
3335
|
+
@rendered_nodes[data_node.name] = NodeWidget.new(
|
1415
3336
|
node_x,
|
1416
3337
|
node_y,
|
1417
|
-
|
3338
|
+
data_node,
|
1418
3339
|
get_node_color(data_node),
|
1419
|
-
|
3340
|
+
scale_to_use,
|
3341
|
+
@is_explorer)
|
3342
|
+
end
|
1420
3343
|
end
|
1421
3344
|
end
|
1422
3345
|
@rendered_nodes.values.each do |rn|
|
@@ -1441,9 +3364,16 @@ module Wads
|
|
1441
3364
|
if connected_rendered_node.nil?
|
1442
3365
|
# Don't draw if it is not currently visible
|
1443
3366
|
else
|
1444
|
-
|
1445
|
-
|
3367
|
+
if @is_explorer and (rendered_node.is_background or connected_rendered_node.is_background)
|
3368
|
+
# Use a dull gray color for the line
|
3369
|
+
Gosu::draw_line rendered_node.center_x, rendered_node.center_y, COLOR_LIGHT_GRAY,
|
3370
|
+
connected_rendered_node.center_x, connected_rendered_node.center_y, COLOR_LIGHT_GRAY,
|
3371
|
+
relative_z_order(Z_ORDER_GRAPHIC_ELEMENTS)
|
3372
|
+
else
|
3373
|
+
Gosu::draw_line rendered_node.center_x, rendered_node.center_y, rendered_node.graphics_color,
|
3374
|
+
connected_rendered_node.center_x, connected_rendered_node.center_y, connected_rendered_node.graphics_color,
|
1446
3375
|
relative_z_order(Z_ORDER_GRAPHIC_ELEMENTS)
|
3376
|
+
end
|
1447
3377
|
end
|
1448
3378
|
end
|
1449
3379
|
end
|