wads 0.1.1 → 0.2.1
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 +11 -0
- data/README.md +51 -3
- data/data/sample_graph.csv +11 -0
- data/lib/wads/app.rb +40 -298
- data/lib/wads/data_structures.rb +527 -28
- data/lib/wads/textinput.rb +66 -15
- data/lib/wads/version.rb +1 -1
- data/lib/wads/widgets.rb +2720 -332
- 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/StocksSample.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 +25 -5
- data/run-sample-app +0 -3
- data/sample_app.rb +0 -58
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,87 +38,1275 @@ 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
|
33
46
|
Z_ORDER_SELECTION_BACKGROUND = 4
|
34
47
|
Z_ORDER_GRAPHIC_ELEMENTS = 5
|
35
48
|
Z_ORDER_PLOT_POINTS = 6
|
36
|
-
|
37
|
-
|
38
|
-
|
49
|
+
Z_ORDER_FOCAL_ELEMENTS = 8
|
50
|
+
Z_ORDER_TEXT = 9
|
51
|
+
|
52
|
+
EVENT_OK = "ok"
|
53
|
+
EVENT_TEXT_INPUT = "textinput"
|
54
|
+
EVENT_TABLE_SELECT = "tableselect"
|
55
|
+
EVENT_TABLE_UNSELECT = "tableunselect"
|
56
|
+
EVENT_TABLE_ROW_DELETE = "tablerowdelete"
|
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
|
39
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
|
+
#
|
40
999
|
class Widget
|
41
1000
|
attr_accessor :x
|
42
1001
|
attr_accessor :y
|
43
|
-
attr_accessor :
|
1002
|
+
attr_accessor :base_z
|
1003
|
+
attr_accessor :gui_theme
|
1004
|
+
attr_accessor :layout
|
44
1005
|
attr_accessor :width
|
45
1006
|
attr_accessor :height
|
46
1007
|
attr_accessor :visible
|
47
1008
|
attr_accessor :children
|
48
|
-
attr_accessor :
|
49
|
-
attr_accessor :
|
50
|
-
attr_accessor :
|
1009
|
+
attr_accessor :overlay_widget
|
1010
|
+
attr_accessor :override_color
|
1011
|
+
attr_accessor :is_selected
|
1012
|
+
attr_accessor :text_input_fields
|
51
1013
|
|
52
|
-
def initialize(x, y,
|
53
|
-
|
54
|
-
|
55
|
-
@
|
56
|
-
|
57
|
-
|
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
|
58
1030
|
@visible = true
|
59
1031
|
@children = []
|
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
|
1049
|
+
end
|
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
|
+
#
|
1128
|
+
def z_order
|
1129
|
+
@base_z + widget_z
|
60
1130
|
end
|
61
1131
|
|
1132
|
+
def relative_z_order(relative_order)
|
1133
|
+
@base_z + relative_order
|
1134
|
+
end
|
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
|
+
#
|
62
1148
|
def add_child(child)
|
63
1149
|
@children << child
|
64
1150
|
end
|
65
1151
|
|
1152
|
+
#
|
1153
|
+
# Remove the given child widget
|
1154
|
+
#
|
1155
|
+
def remove_child(child)
|
1156
|
+
@children.delete(child)
|
1157
|
+
end
|
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
|
+
#
|
66
1188
|
def clear_children
|
67
1189
|
@children = []
|
68
1190
|
end
|
69
1191
|
|
70
|
-
|
71
|
-
|
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
|
72
1197
|
end
|
73
1198
|
|
74
|
-
|
75
|
-
|
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
|
76
1204
|
end
|
77
1205
|
|
78
|
-
|
79
|
-
|
1206
|
+
#
|
1207
|
+
# Turn back on drawing of the border
|
1208
|
+
#
|
1209
|
+
def enable_border
|
1210
|
+
@show_border = true
|
80
1211
|
end
|
81
1212
|
|
82
|
-
|
83
|
-
|
84
|
-
|
1213
|
+
#
|
1214
|
+
# Turn back on drawing of the background
|
1215
|
+
#
|
1216
|
+
def enable_background
|
1217
|
+
@show_background = true
|
85
1218
|
end
|
86
1219
|
|
1220
|
+
#
|
1221
|
+
# A convenience method, or alias, to return the left x coordinate of this widget.
|
1222
|
+
#
|
1223
|
+
def left_edge
|
1224
|
+
@x
|
1225
|
+
end
|
1226
|
+
|
1227
|
+
#
|
1228
|
+
# A convenience method to return the right x coordinate of this widget.
|
1229
|
+
#
|
87
1230
|
def right_edge
|
88
1231
|
@x + @width - 1
|
89
1232
|
end
|
90
|
-
|
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
|
+
#
|
91
1244
|
def bottom_edge
|
92
1245
|
@y + @height - 1
|
93
1246
|
end
|
94
1247
|
|
1248
|
+
#
|
1249
|
+
# A convenience method to return the center x coordinate of this widget
|
1250
|
+
#
|
95
1251
|
def center_x
|
96
1252
|
@x + ((right_edge - @x) / 2)
|
97
1253
|
end
|
98
1254
|
|
1255
|
+
#
|
1256
|
+
# A convenience method to return the center y coordinate of this widget
|
1257
|
+
#
|
99
1258
|
def center_y
|
100
1259
|
@y + ((bottom_edge - @y) / 2)
|
101
1260
|
end
|
102
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
|
+
#
|
103
1300
|
def draw
|
104
1301
|
if @visible
|
105
1302
|
render
|
106
|
-
if @
|
1303
|
+
if @is_selected
|
1304
|
+
draw_background(Z_ORDER_SELECTION_BACKGROUND, @gui_theme.selection_color)
|
1305
|
+
elsif @show_background
|
107
1306
|
draw_background
|
108
1307
|
end
|
109
|
-
if @
|
110
|
-
draw_border
|
1308
|
+
if @show_border
|
1309
|
+
draw_border
|
111
1310
|
end
|
112
1311
|
@children.each do |child|
|
113
1312
|
child.draw
|
@@ -115,87 +1314,568 @@ module Wads
|
|
115
1314
|
end
|
116
1315
|
end
|
117
1316
|
|
118
|
-
def draw_background(
|
119
|
-
|
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
|
1323
|
+
if z_override
|
1324
|
+
z = relative_z_order(z_override)
|
1325
|
+
else
|
1326
|
+
z = relative_z_order(Z_ORDER_BACKGROUND)
|
1327
|
+
end
|
1328
|
+
Gosu::draw_rect(@x + 1, @y + 1, @width - 3, @height - 3, bgcolor, z)
|
120
1329
|
end
|
121
1330
|
|
122
|
-
def
|
123
|
-
Gosu::draw_line @x
|
124
|
-
Gosu::draw_line @x
|
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)
|
125
1336
|
end
|
126
1337
|
|
127
|
-
def
|
128
|
-
|
129
|
-
|
130
|
-
# render is for specific drawing done by the widget
|
131
|
-
end
|
1338
|
+
def contains_click(mouse_x, mouse_y)
|
1339
|
+
mouse_x >= @x and mouse_x <= right_edge and mouse_y >= @y and mouse_y <= bottom_edge
|
1340
|
+
end
|
132
1341
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
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
|
+
#
|
1372
|
+
def update(update_count, mouse_x, mouse_y)
|
1373
|
+
if @overlay_widget
|
1374
|
+
@overlay_widget.update(update_count, mouse_x, mouse_y)
|
1375
|
+
end
|
1376
|
+
handle_update(update_count, mouse_x, mouse_y)
|
1377
|
+
@children.each do |child|
|
1378
|
+
child.update(update_count, mouse_x, mouse_y)
|
1379
|
+
end
|
1380
|
+
end
|
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
|
+
#
|
1391
|
+
def button_down(id, mouse_x, mouse_y)
|
1392
|
+
if @overlay_widget
|
1393
|
+
result = @overlay_widget.button_down(id, mouse_x, mouse_y)
|
1394
|
+
if not result.nil? and result.is_a? WidgetResult
|
1395
|
+
intercept_widget_event(result)
|
1396
|
+
if result.close_widget
|
1397
|
+
# remove the overlay widget frmo children, set to null
|
1398
|
+
# hopefully this closes and gets us back to normal
|
1399
|
+
remove_child(@overlay_widget)
|
1400
|
+
@overlay_widget = nil
|
1401
|
+
end
|
139
1402
|
end
|
1403
|
+
return
|
140
1404
|
end
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
1405
|
+
|
1406
|
+
if id == Gosu::MsLeft
|
1407
|
+
# Special handling for text input fields
|
1408
|
+
# Mouse click: Select text field based on mouse position.
|
1409
|
+
if not @text_input_fields.empty?
|
1410
|
+
WadsConfig.instance.get_window.text_input = @text_input_fields.find { |tf| tf.under_point?(mouse_x, mouse_y) }
|
1411
|
+
# Advanced: Move caret to clicked position
|
1412
|
+
WadsConfig.instance.get_window.text_input.move_caret(mouse_x) unless WadsConfig.instance.get_window.text_input.nil?
|
1413
|
+
end
|
1414
|
+
|
1415
|
+
result = handle_mouse_down mouse_x, mouse_y
|
1416
|
+
elsif id == Gosu::MsRight
|
1417
|
+
result = handle_right_mouse mouse_x, mouse_y
|
1418
|
+
else
|
1419
|
+
result = handle_key_press id, mouse_x, mouse_y
|
1420
|
+
end
|
1421
|
+
|
1422
|
+
if not result.nil? and result.is_a? WidgetResult
|
1423
|
+
return result
|
1424
|
+
end
|
1425
|
+
|
1426
|
+
@children.each do |child|
|
1427
|
+
if id == Gosu::MsLeft
|
1428
|
+
if child.contains_click(mouse_x, mouse_y)
|
1429
|
+
result = child.button_down id, mouse_x, mouse_y
|
1430
|
+
if not result.nil? and result.is_a? WidgetResult
|
1431
|
+
intercept_widget_event(result)
|
1432
|
+
return result
|
1433
|
+
end
|
1434
|
+
end
|
1435
|
+
else
|
1436
|
+
result = child.button_down id, mouse_x, mouse_y
|
1437
|
+
if not result.nil? and result.is_a? WidgetResult
|
1438
|
+
intercept_widget_event(result)
|
1439
|
+
return result
|
1440
|
+
end
|
1441
|
+
end
|
1442
|
+
end
|
145
1443
|
end
|
146
1444
|
|
147
|
-
|
148
|
-
|
1445
|
+
#
|
1446
|
+
# The framework implementation of the main Gosu button up method.
|
1447
|
+
# This method separates out mouse events from keyboard events.
|
1448
|
+
# Only the mouse up event is propagated through the child hierarchy.
|
1449
|
+
# As a widget author, do not override this method.
|
1450
|
+
# Your callback to implement is:
|
1451
|
+
# handle_mouse_up(mouse_x, mouse_y)
|
1452
|
+
#
|
1453
|
+
def button_up(id, mouse_x, mouse_y)
|
1454
|
+
if @overlay_widget
|
1455
|
+
return @overlay_widget.button_up(id, mouse_x, mouse_y)
|
1456
|
+
end
|
1457
|
+
|
1458
|
+
if id == Gosu::MsLeft
|
1459
|
+
result = handle_mouse_up mouse_x, mouse_y
|
1460
|
+
if not result.nil? and result.is_a? WidgetResult
|
1461
|
+
return result
|
1462
|
+
end
|
1463
|
+
end
|
1464
|
+
|
1465
|
+
@children.each do |child|
|
1466
|
+
if id == Gosu::MsLeft
|
1467
|
+
if child.contains_click(mouse_x, mouse_y)
|
1468
|
+
result = child.handle_mouse_up mouse_x, mouse_y
|
1469
|
+
if not result.nil? and result.is_a? WidgetResult
|
1470
|
+
return result
|
1471
|
+
end
|
1472
|
+
end
|
1473
|
+
end
|
1474
|
+
end
|
1475
|
+
end
|
1476
|
+
|
1477
|
+
#
|
1478
|
+
# Return the absolute x coordinate given the relative x pixel to this widget
|
1479
|
+
#
|
1480
|
+
def relative_x(x)
|
1481
|
+
x_pixel_to_screen(x)
|
1482
|
+
end
|
1483
|
+
|
1484
|
+
# An alias for relative_x
|
1485
|
+
def x_pixel_to_screen(x)
|
1486
|
+
@x + x
|
1487
|
+
end
|
1488
|
+
|
1489
|
+
#
|
1490
|
+
# Return the absolute y coordinate given the relative y pixel to this widget
|
1491
|
+
#
|
1492
|
+
def relative_y(y)
|
1493
|
+
y_pixel_to_screen(y)
|
1494
|
+
end
|
1495
|
+
|
1496
|
+
# An alias for relative_y
|
1497
|
+
def y_pixel_to_screen(y)
|
1498
|
+
@y + y
|
1499
|
+
end
|
1500
|
+
|
1501
|
+
#
|
1502
|
+
# Add a child text widget using x, y positioning relative to this widget
|
1503
|
+
#
|
1504
|
+
def add_text(message, rel_x, rel_y, color = nil, use_large_font = false)
|
1505
|
+
new_text = Text.new(x_pixel_to_screen(rel_x), y_pixel_to_screen(rel_y), message,
|
1506
|
+
{ ARG_COLOR => color, ARG_USE_LARGE_FONT => use_large_font})
|
1507
|
+
new_text.base_z = @base_z
|
1508
|
+
new_text.gui_theme = @gui_theme
|
1509
|
+
add_child(new_text)
|
1510
|
+
new_text
|
1511
|
+
end
|
1512
|
+
|
1513
|
+
#
|
1514
|
+
# Add a child document widget using x, y positioning relative to this widget
|
1515
|
+
#
|
1516
|
+
def add_document(content, rel_x, rel_y, width, height)
|
1517
|
+
new_doc = Document.new(x_pixel_to_screen(rel_x), y_pixel_to_screen(rel_y),
|
1518
|
+
width, height,
|
1519
|
+
content)
|
1520
|
+
new_doc.base_z = @base_z
|
1521
|
+
new_doc.gui_theme = @gui_theme
|
1522
|
+
add_child(new_doc)
|
1523
|
+
new_doc
|
1524
|
+
end
|
1525
|
+
|
1526
|
+
#
|
1527
|
+
# Add a child button widget using x, y positioning relative to this widget.
|
1528
|
+
# The width of the button will be determined based on the label text unless
|
1529
|
+
# specified in the optional parameter. The code to execute is provided as a
|
1530
|
+
# block, as shown in the example below.
|
1531
|
+
# add_button("Test Button", 10, 10) do
|
1532
|
+
# puts "User hit the test button"
|
1533
|
+
# end
|
1534
|
+
def add_button(label, rel_x, rel_y, width = nil, &block)
|
1535
|
+
if width.nil?
|
1536
|
+
args = {}
|
1537
|
+
else
|
1538
|
+
args = { ARG_DESIRED_WIDTH => width }
|
1539
|
+
end
|
1540
|
+
new_button = Button.new(x_pixel_to_screen(rel_x), y_pixel_to_screen(rel_y), label, args)
|
1541
|
+
new_button.set_action(&block)
|
1542
|
+
new_button.base_z = @base_z
|
1543
|
+
new_button.gui_theme = @gui_theme
|
1544
|
+
add_child(new_button)
|
1545
|
+
new_button
|
1546
|
+
end
|
1547
|
+
|
1548
|
+
#
|
1549
|
+
# Add a child delete button widget using x, y positioning relative to this widget.
|
1550
|
+
# A delete button is a regular button that is rendered as a red X, instead of a text label.
|
1551
|
+
#
|
1552
|
+
def add_delete_button(rel_x, rel_y, &block)
|
1553
|
+
new_delete_button = DeleteButton.new(x_pixel_to_screen(rel_x), y_pixel_to_screen(rel_y))
|
1554
|
+
new_delete_button.set_action(&block)
|
1555
|
+
new_delete_button.base_z = @base_z
|
1556
|
+
new_delete_button.gui_theme = @gui_theme
|
1557
|
+
add_child(new_delete_button)
|
1558
|
+
new_delete_button
|
1559
|
+
end
|
1560
|
+
|
1561
|
+
#
|
1562
|
+
# Add a child table widget using x, y positioning relative to this widget.
|
1563
|
+
#
|
1564
|
+
def add_table(rel_x, rel_y, width, height, column_headers, max_visible_rows = 10)
|
1565
|
+
new_table = Table.new(x_pixel_to_screen(rel_x), y_pixel_to_screen(rel_y),
|
1566
|
+
width, height, column_headers, max_visible_rows)
|
1567
|
+
new_table.base_z = @base_z
|
1568
|
+
new_table.gui_theme = @gui_theme
|
1569
|
+
add_child(new_table)
|
1570
|
+
new_table
|
1571
|
+
end
|
1572
|
+
|
1573
|
+
#
|
1574
|
+
# Add a child table widget using x, y positioning relative to this widget.
|
1575
|
+
# The user can select up to one and only one item in the table.
|
1576
|
+
#
|
1577
|
+
def add_single_select_table(rel_x, rel_y, width, height, column_headers, max_visible_rows = 10)
|
1578
|
+
new_table = SingleSelectTable.new(x_pixel_to_screen(rel_x), y_pixel_to_screen(rel_y),
|
1579
|
+
width, height, column_headers, max_visible_rows)
|
1580
|
+
new_table.base_z = @base_z
|
1581
|
+
new_table.gui_theme = @gui_theme
|
1582
|
+
add_child(new_table)
|
1583
|
+
new_table
|
1584
|
+
end
|
1585
|
+
|
1586
|
+
#
|
1587
|
+
# Add a child table widget using x, y positioning relative to this widget.
|
1588
|
+
# The user can zero to many items in the table.
|
1589
|
+
#
|
1590
|
+
def add_multi_select_table(rel_x, rel_y, width, height, column_headers, max_visible_rows = 10)
|
1591
|
+
new_table = MultiSelectTable.new(x_pixel_to_screen(rel_x), y_pixel_to_screen(rel_y),
|
1592
|
+
width, height, column_headers, max_visible_rows)
|
1593
|
+
new_table.base_z = @base_z
|
1594
|
+
new_table.gui_theme = @gui_theme
|
1595
|
+
add_child(new_table)
|
1596
|
+
new_table
|
1597
|
+
end
|
1598
|
+
|
1599
|
+
#
|
1600
|
+
# Add a child graph display widget using x, y positioning relative to this widget.
|
1601
|
+
#
|
1602
|
+
def add_graph_display(rel_x, rel_y, width, height, graph)
|
1603
|
+
new_graph = GraphWidget.new(x_pixel_to_screen(rel_x), y_pixel_to_screen(rel_y), width, height, graph)
|
1604
|
+
new_graph.base_z = @base_z
|
1605
|
+
add_child(new_graph)
|
1606
|
+
new_graph
|
1607
|
+
end
|
1608
|
+
|
1609
|
+
#
|
1610
|
+
# Add a child plot display widget using x, y positioning relative to this widget.
|
1611
|
+
#
|
1612
|
+
def add_plot(rel_x, rel_y, width, height)
|
1613
|
+
new_plot = Plot.new(x_pixel_to_screen(rel_x), y_pixel_to_screen(rel_y), width, height)
|
1614
|
+
new_plot.base_z = @base_z
|
1615
|
+
new_plot.gui_theme = @gui_theme
|
1616
|
+
add_child(new_plot)
|
1617
|
+
new_plot
|
1618
|
+
end
|
1619
|
+
|
1620
|
+
#
|
1621
|
+
# Add child axis lines widget using x, y positioning relative to this widget.
|
1622
|
+
#
|
1623
|
+
def add_axis_lines(rel_x, rel_y, width, height)
|
1624
|
+
new_axis_lines = AxisLines.new(x_pixel_to_screen(rel_x), y_pixel_to_screen(rel_y), width, height)
|
1625
|
+
new_axis_lines.base_z = @base_z
|
1626
|
+
new_axis_lines.gui_theme = @gui_theme
|
1627
|
+
add_child(new_axis_lines)
|
1628
|
+
new_axis_lines
|
1629
|
+
end
|
1630
|
+
|
1631
|
+
#
|
1632
|
+
# Add a child image widget using x, y positioning relative to this widget.
|
1633
|
+
#
|
1634
|
+
def add_image(filename, rel_x, rel_y)
|
1635
|
+
new_image = ImageWidget.new(x_pixel_to_screen(rel_x), y_pixel_to_screen(rel_y), img)
|
1636
|
+
new_image.base_z = @base_z
|
1637
|
+
new_image.gui_theme = @gui_theme
|
1638
|
+
add_child(new_image)
|
1639
|
+
new_image
|
1640
|
+
end
|
1641
|
+
|
1642
|
+
#
|
1643
|
+
# Add an overlay widget that is drawn on top of (at a higher z level) this widget
|
1644
|
+
#
|
1645
|
+
def add_overlay(overlay)
|
1646
|
+
overlay.base_z = @base_z + 10
|
1647
|
+
add_child(overlay)
|
1648
|
+
@overlay_widget = overlay
|
1649
|
+
end
|
1650
|
+
|
1651
|
+
# For all child widgets, adjust the x coordinate
|
1652
|
+
# so that they are centered.
|
1653
|
+
def center_children
|
1654
|
+
if @children.empty?
|
1655
|
+
return
|
1656
|
+
end
|
1657
|
+
number_of_children = @children.size
|
1658
|
+
total_width_of_children = 0
|
1659
|
+
@children.each do |child|
|
1660
|
+
total_width_of_children = total_width_of_children + child.width + 5
|
1661
|
+
end
|
1662
|
+
total_width_of_children = total_width_of_children - 5
|
1663
|
+
|
1664
|
+
start_x = (@width - total_width_of_children) / 2
|
1665
|
+
@children.each do |child|
|
1666
|
+
child.x = start_x
|
1667
|
+
start_x = start_x + child.width + 5
|
1668
|
+
end
|
1669
|
+
end
|
1670
|
+
|
1671
|
+
#
|
1672
|
+
# Override this method in your subclass to process mouse down events.
|
1673
|
+
# The base implementation is empty
|
1674
|
+
#
|
1675
|
+
def handle_mouse_down mouse_x, mouse_y
|
1676
|
+
# empty base implementation
|
149
1677
|
end
|
150
1678
|
|
151
|
-
|
1679
|
+
#
|
1680
|
+
# Override this method in your subclass to process mouse up events.
|
1681
|
+
# The base implementation is empty
|
1682
|
+
#
|
1683
|
+
def handle_mouse_up mouse_x, mouse_y
|
152
1684
|
# empty base implementation
|
153
1685
|
end
|
154
1686
|
|
155
|
-
|
1687
|
+
#
|
1688
|
+
# Override this method in your subclass to process the right mouse click event.
|
1689
|
+
# Note we do not differentiate between up and down for the right mouse button.
|
1690
|
+
# The base implementation is empty
|
1691
|
+
#
|
1692
|
+
def handle_right_mouse mouse_x, mouse_y
|
156
1693
|
# empty base implementation
|
157
1694
|
end
|
158
1695
|
|
159
|
-
|
1696
|
+
#
|
1697
|
+
# Override this method in your subclass to process keyboard events.
|
1698
|
+
# The base implementation is empty.
|
1699
|
+
# Note that the mouse was not necessarily positioned over this widget.
|
1700
|
+
# You can check this using the contains_click(mouse_x, mouse_y) method
|
1701
|
+
# and decide if you want to process the event based on that, if desired.
|
1702
|
+
#
|
1703
|
+
def handle_key_press id, mouse_x, mouse_y
|
160
1704
|
# empty base implementation
|
161
1705
|
end
|
1706
|
+
|
1707
|
+
#
|
1708
|
+
# Override this method in your subclass to perform any logic needed
|
1709
|
+
# as part of the main Gosu update loop. In most cases, this method is
|
1710
|
+
# invoked 60 times per second.
|
1711
|
+
#
|
1712
|
+
def handle_update update_count, mouse_x, mouse_y
|
1713
|
+
# empty base implementation
|
1714
|
+
end
|
1715
|
+
|
1716
|
+
#
|
1717
|
+
# Override this method in your subclass to perform any custom rendering logic.
|
1718
|
+
# Note that child widgets are automatically drawn and you do not need to do
|
1719
|
+
# that yourself.
|
1720
|
+
#
|
1721
|
+
def render
|
1722
|
+
# Base implementation is empty
|
1723
|
+
end
|
1724
|
+
|
1725
|
+
#
|
1726
|
+
# Return the relative z order compared to other widgets.
|
1727
|
+
# The absolute z order is the base plus this value.
|
1728
|
+
# Its calculated relative so that overlay widgets can be
|
1729
|
+
# on top of base displays.
|
1730
|
+
#
|
1731
|
+
def widget_z
|
1732
|
+
0
|
1733
|
+
end
|
1734
|
+
|
1735
|
+
def intercept_widget_event(result)
|
1736
|
+
# Base implementation just relays the event
|
1737
|
+
result
|
1738
|
+
end
|
1739
|
+
end
|
1740
|
+
|
1741
|
+
#
|
1742
|
+
# A panel is simply an alias for a widget, although you can optionally
|
1743
|
+
# treat them differently if you wish. Generally a panel is used to
|
1744
|
+
# apply a specific layout to a sub-section of the screen.
|
1745
|
+
#
|
1746
|
+
class Panel < Widget
|
1747
|
+
def initialize(x, y, w, h, layout = nil, theme = nil)
|
1748
|
+
super(x, y, w, h, layout, theme)
|
1749
|
+
end
|
1750
|
+
end
|
1751
|
+
|
1752
|
+
#
|
1753
|
+
# Displays an image on the screen at the specific x, y location. The image
|
1754
|
+
# can be scaled by setting the scale attribute. The image attribute to the
|
1755
|
+
# construcor can be the string file location or a Gosu::Image instance
|
1756
|
+
#
|
1757
|
+
class ImageWidget < Widget
|
1758
|
+
attr_accessor :img
|
1759
|
+
attr_accessor :scale
|
1760
|
+
|
1761
|
+
def initialize(x, y, image, args = {})
|
1762
|
+
super(x, y)
|
1763
|
+
if image.is_a? String
|
1764
|
+
@img = Gosu::Image.new(image)
|
1765
|
+
elsif image.is_a? Gosu::Image
|
1766
|
+
@img = image
|
1767
|
+
else
|
1768
|
+
raise "ImageWidget requires either a filename or a Gosu::Image object"
|
1769
|
+
end
|
1770
|
+
if args[ARG_THEME]
|
1771
|
+
@gui_theme = args[ARG_THEME]
|
1772
|
+
end
|
1773
|
+
@scale = 1
|
1774
|
+
disable_border
|
1775
|
+
disable_background
|
1776
|
+
set_dimensions(@img.width, @img.height)
|
1777
|
+
end
|
1778
|
+
|
1779
|
+
def render
|
1780
|
+
@img.draw @x, @y, z_order, @scale, @scale
|
1781
|
+
end
|
1782
|
+
|
1783
|
+
def widget_z
|
1784
|
+
Z_ORDER_FOCAL_ELEMENTS
|
1785
|
+
end
|
162
1786
|
end
|
163
1787
|
|
1788
|
+
#
|
1789
|
+
# Displays a text label on the screen at the specific x, y location.
|
1790
|
+
# The font specified by the current theme is used.
|
1791
|
+
# The theme text color is used, unless the color parameter specifies an override.
|
1792
|
+
# The small font is used by default, unless the use_large_font parameter is true.
|
1793
|
+
#
|
164
1794
|
class Text < Widget
|
165
|
-
attr_accessor :
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
@
|
1795
|
+
attr_accessor :label
|
1796
|
+
|
1797
|
+
def initialize(x, y, label, args = {})
|
1798
|
+
super(x, y)
|
1799
|
+
@label = label
|
1800
|
+
if args[ARG_THEME]
|
1801
|
+
@gui_theme = args[ARG_THEME]
|
1802
|
+
end
|
1803
|
+
if args[ARG_USE_LARGE_FONT]
|
1804
|
+
@use_large_font = args[ARG_USE_LARGE_FONT]
|
1805
|
+
end
|
1806
|
+
if args[ARG_COLOR]
|
1807
|
+
@override_color = args[ARG_COLOR]
|
1808
|
+
end
|
1809
|
+
disable_border
|
1810
|
+
if @use_large_font
|
1811
|
+
set_dimensions(@gui_theme.font_large.text_width(@label) + 10, 20)
|
1812
|
+
else
|
1813
|
+
set_dimensions(@gui_theme.font.text_width(@label) + 10, 20)
|
1814
|
+
end
|
1815
|
+
end
|
1816
|
+
|
1817
|
+
def set_text(new_text)
|
1818
|
+
@label = new_text
|
170
1819
|
end
|
1820
|
+
|
1821
|
+
def change_text(new_text)
|
1822
|
+
set_text(new_text)
|
1823
|
+
end
|
1824
|
+
|
171
1825
|
def render
|
172
|
-
|
1826
|
+
if @use_large_font
|
1827
|
+
get_theme.font_large.draw_text(@label, @x, @y, z_order, 1, 1, text_color)
|
1828
|
+
else
|
1829
|
+
get_theme.font.draw_text(@label, @x, @y, z_order, 1, 1, text_color)
|
1830
|
+
end
|
1831
|
+
end
|
1832
|
+
|
1833
|
+
def widget_z
|
1834
|
+
Z_ORDER_TEXT
|
173
1835
|
end
|
174
1836
|
end
|
175
1837
|
|
1838
|
+
#
|
1839
|
+
# An ErrorMessage is a subclass of text that uses a red color
|
1840
|
+
#
|
176
1841
|
class ErrorMessage < Text
|
177
|
-
|
178
|
-
|
179
|
-
super("ERROR: #{str}", x, y, font, COLOR_ERROR_CODE_RED)
|
180
|
-
set_dimensions(@font.text_width(@str) + 4, 36)
|
1842
|
+
def initialize(x, y, message)
|
1843
|
+
super(x, y, "ERROR: #{message}", COLOR_ERROR_CODE_RED)
|
181
1844
|
end
|
182
1845
|
end
|
183
1846
|
|
1847
|
+
#
|
1848
|
+
# A data point to be used in a Plot widget. This object holds
|
1849
|
+
# the x, y screen location as well as the data values for x, y.
|
1850
|
+
#
|
184
1851
|
class PlotPoint < Widget
|
1852
|
+
attr_accessor :data_x
|
1853
|
+
attr_accessor :data_y
|
185
1854
|
attr_accessor :data_point_size
|
186
1855
|
|
187
|
-
def initialize(x, y, color = COLOR_MAROON, size = 4)
|
188
|
-
super(x, y
|
1856
|
+
def initialize(x, y, data_x, data_y, color = COLOR_MAROON, size = 4)
|
1857
|
+
super(x, y)
|
1858
|
+
@override_color = color
|
1859
|
+
@data_x = data_x
|
1860
|
+
@data_y = data_y
|
189
1861
|
@data_point_size = size
|
190
1862
|
end
|
191
1863
|
|
192
|
-
def render
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
1864
|
+
def render(override_size = nil)
|
1865
|
+
size_to_draw = @data_point_size
|
1866
|
+
if override_size
|
1867
|
+
size_to_draw = override_size
|
1868
|
+
end
|
1869
|
+
half_size = size_to_draw / 2
|
1870
|
+
Gosu::draw_rect(@x - half_size, @y - half_size,
|
1871
|
+
size_to_draw, size_to_draw,
|
1872
|
+
graphics_color, z_order)
|
197
1873
|
end
|
198
1874
|
|
1875
|
+
def widget_z
|
1876
|
+
Z_ORDER_PLOT_POINTS
|
1877
|
+
end
|
1878
|
+
|
199
1879
|
def to_display
|
200
1880
|
"#{@x}, #{@y}"
|
201
1881
|
end
|
@@ -211,102 +1891,169 @@ module Wads
|
|
211
1891
|
end
|
212
1892
|
end
|
213
1893
|
|
1894
|
+
#
|
1895
|
+
# Displays a button at the specified x, y location.
|
1896
|
+
# The button width is based on the label text unless specified
|
1897
|
+
# using the optional parameter. The code to executeon a button
|
1898
|
+
# click is specified using the set_action method, however typical
|
1899
|
+
# using involves the widget or layout form of add_button. For example:
|
1900
|
+
# add_button("Test Button", 10, 10) do
|
1901
|
+
# puts "User hit the test button"
|
1902
|
+
# end
|
1903
|
+
|
214
1904
|
class Button < Widget
|
215
1905
|
attr_accessor :label
|
216
1906
|
attr_accessor :is_pressed
|
1907
|
+
attr_accessor :action_code
|
217
1908
|
|
218
|
-
def initialize(
|
219
|
-
super(x, y
|
220
|
-
set_font(font)
|
1909
|
+
def initialize(x, y, label, args = {})
|
1910
|
+
super(x, y)
|
221
1911
|
@label = label
|
222
|
-
|
223
|
-
|
224
|
-
|
1912
|
+
if args[ARG_THEME]
|
1913
|
+
@gui_theme = args[ARG_THEME]
|
1914
|
+
end
|
1915
|
+
@text_pixel_width = @gui_theme.font.text_width(@label)
|
1916
|
+
if args[ARG_DESIRED_WIDTH]
|
1917
|
+
@width = args[ARG_DESIRED_WIDTH]
|
225
1918
|
else
|
226
|
-
@width =
|
1919
|
+
@width = @text_pixel_width + 10
|
227
1920
|
end
|
228
1921
|
@height = 26
|
229
1922
|
@is_pressed = false
|
230
|
-
@
|
1923
|
+
@is_pressed_update_count = -100
|
231
1924
|
end
|
232
1925
|
|
233
1926
|
def render
|
234
|
-
draw_border(@color)
|
235
1927
|
text_x = center_x - (@text_pixel_width / 2)
|
236
|
-
@font.draw_text(@label, text_x, @y,
|
1928
|
+
@gui_theme.font.draw_text(@label, text_x, @y, z_order, 1, 1, text_color)
|
1929
|
+
end
|
1930
|
+
|
1931
|
+
def widget_z
|
1932
|
+
Z_ORDER_TEXT
|
1933
|
+
end
|
1934
|
+
|
1935
|
+
def set_action(&block)
|
1936
|
+
@action_code = block
|
1937
|
+
end
|
1938
|
+
|
1939
|
+
def handle_mouse_down mouse_x, mouse_y
|
1940
|
+
@is_pressed = true
|
1941
|
+
if @action_code
|
1942
|
+
@action_code.call
|
1943
|
+
end
|
1944
|
+
end
|
1945
|
+
|
1946
|
+
def handle_update update_count, mouse_x, mouse_y
|
1947
|
+
if @is_pressed
|
1948
|
+
@is_pressed_update_count = update_count
|
1949
|
+
@is_pressed = false
|
1950
|
+
end
|
1951
|
+
|
1952
|
+
if update_count < @is_pressed_update_count + 15
|
1953
|
+
unset_selected
|
1954
|
+
elsif contains_click(mouse_x, mouse_y)
|
1955
|
+
set_selected
|
1956
|
+
else
|
1957
|
+
unset_selected
|
1958
|
+
end
|
1959
|
+
end
|
1960
|
+
end
|
1961
|
+
|
1962
|
+
#
|
1963
|
+
# A subclass of button that renders a red X instead of label text
|
1964
|
+
#
|
1965
|
+
class DeleteButton < Button
|
1966
|
+
def initialize(x, y, args = {})
|
1967
|
+
super(x, y, "ignore", {ARG_DESIRED_WIDTH => 50}.merge(args))
|
1968
|
+
set_dimensions(14, 14)
|
1969
|
+
add_child(Line.new(@x, @y, right_edge, bottom_edge, COLOR_ERROR_CODE_RED))
|
1970
|
+
add_child(Line.new(@x, bottom_edge, right_edge, @y, COLOR_ERROR_CODE_RED))
|
1971
|
+
end
|
1972
|
+
|
1973
|
+
def render
|
1974
|
+
# do nothing, just override the parent so we don't draw a label
|
237
1975
|
end
|
238
1976
|
end
|
239
1977
|
|
1978
|
+
#
|
1979
|
+
# Displays multiple lines of text content at the specified coordinates
|
1980
|
+
#
|
240
1981
|
class Document < Widget
|
241
1982
|
attr_accessor :lines
|
242
1983
|
|
243
|
-
def initialize(
|
244
|
-
super(x, y
|
245
|
-
set_font(font)
|
1984
|
+
def initialize(x, y, width, height, content, args = {})
|
1985
|
+
super(x, y)
|
246
1986
|
set_dimensions(width, height)
|
247
1987
|
@lines = content.split("\n")
|
1988
|
+
disable_border
|
1989
|
+
if args[ARG_THEME]
|
1990
|
+
@gui_theme = args[ARG_THEME]
|
1991
|
+
end
|
248
1992
|
end
|
249
1993
|
|
250
1994
|
def render
|
251
1995
|
y = @y + 4
|
252
1996
|
@lines.each do |line|
|
253
|
-
@font.draw_text(line, @x + 5, y,
|
1997
|
+
@gui_theme.font.draw_text(line, @x + 5, y, z_order, 1, 1, text_color)
|
254
1998
|
y = y + 26
|
255
1999
|
end
|
256
2000
|
end
|
2001
|
+
|
2002
|
+
def widget_z
|
2003
|
+
Z_ORDER_TEXT
|
2004
|
+
end
|
257
2005
|
end
|
258
2006
|
|
259
2007
|
class InfoBox < Widget
|
260
|
-
def initialize(
|
2008
|
+
def initialize(x, y, width, height, title, content, args = {})
|
261
2009
|
super(x, y)
|
262
|
-
set_font(font)
|
263
2010
|
set_dimensions(width, height)
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
2011
|
+
@base_z = 10
|
2012
|
+
if args[ARG_THEME]
|
2013
|
+
@gui_theme = args[ARG_THEME]
|
2014
|
+
end
|
2015
|
+
add_text(title, 5, 5)
|
2016
|
+
add_document(content, 5, 52, width, height - 52)
|
2017
|
+
ok_button = add_button("OK", (@width / 2) - 50, height - 26) do
|
2018
|
+
WidgetResult.new(true)
|
2019
|
+
end
|
2020
|
+
ok_button.width = 100
|
271
2021
|
end
|
272
2022
|
|
273
|
-
def
|
2023
|
+
def handle_key_press id, mouse_x, mouse_y
|
274
2024
|
if id == Gosu::KbEscape
|
275
2025
|
return WidgetResult.new(true)
|
276
|
-
elsif id == Gosu::MsLeft
|
277
|
-
if @ok_button.contains_click(mouse_x, mouse_y)
|
278
|
-
return WidgetResult.new(true)
|
279
|
-
end
|
280
2026
|
end
|
281
|
-
|
282
|
-
end
|
2027
|
+
end
|
283
2028
|
end
|
284
2029
|
|
285
2030
|
class Dialog < Widget
|
286
2031
|
attr_accessor :textinput
|
287
2032
|
|
288
|
-
def initialize(
|
289
|
-
super(x, y)
|
290
|
-
@
|
291
|
-
set_font(font)
|
292
|
-
set_dimensions(width, height)
|
293
|
-
set_background(0xff566573 )
|
294
|
-
set_border(COLOR_WHITE)
|
2033
|
+
def initialize(x, y, width, height, title, text_input_default)
|
2034
|
+
super(x, y, width, height)
|
2035
|
+
@base_z = 10
|
295
2036
|
@error_message = nil
|
296
2037
|
|
297
|
-
|
2038
|
+
add_text(title, 5, 5)
|
298
2039
|
# Forms automatically have some explanatory content
|
299
|
-
|
2040
|
+
add_document(content, 0, 56, width, height)
|
300
2041
|
|
301
2042
|
# Forms automatically get a text input widget
|
302
|
-
@textinput = TextField.new(
|
2043
|
+
@textinput = TextField.new(x + 10, bottom_edge - 80, text_input_default, 600)
|
2044
|
+
@textinput.base_z = 10
|
303
2045
|
add_child(@textinput)
|
304
2046
|
|
305
2047
|
# Forms automatically get OK and Cancel buttons
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
2048
|
+
ok_button = add_button("OK", (@width / 2) - 100, height - 32) do
|
2049
|
+
handle_ok
|
2050
|
+
end
|
2051
|
+
ok_button.width = 100
|
2052
|
+
|
2053
|
+
cancel_button = add_button("Cancel", (@width / 2) + 50, height - 32) do
|
2054
|
+
WidgetResult.new(true)
|
2055
|
+
end
|
2056
|
+
cancel_button.width = 100
|
310
2057
|
end
|
311
2058
|
|
312
2059
|
def content
|
@@ -317,7 +2064,8 @@ module Wads
|
|
317
2064
|
end
|
318
2065
|
|
319
2066
|
def add_error_message(msg)
|
320
|
-
@error_message = ErrorMessage.new(
|
2067
|
+
@error_message = ErrorMessage.new(x + 10, bottom_edge - 120, msg)
|
2068
|
+
@error_message.base_z = @base_z
|
321
2069
|
end
|
322
2070
|
|
323
2071
|
def render
|
@@ -329,21 +2077,7 @@ module Wads
|
|
329
2077
|
def handle_ok
|
330
2078
|
# Default behavior is to do nothing except tell the caller to
|
331
2079
|
# close the dialog
|
332
|
-
return WidgetResult.new(true)
|
333
|
-
end
|
334
|
-
|
335
|
-
def handle_cancel
|
336
|
-
# Default behavior is to do nothing except tell the caller to
|
337
|
-
# close the dialog
|
338
|
-
return WidgetResult.new(true)
|
339
|
-
end
|
340
|
-
|
341
|
-
def handle_up(mouse_x, mouse_y)
|
342
|
-
# empty implementation of up arrow
|
343
|
-
end
|
344
|
-
|
345
|
-
def handle_down(mouse_x, mouse_y)
|
346
|
-
# empty implementation of down arrow
|
2080
|
+
return WidgetResult.new(true, EVENT_OK)
|
347
2081
|
end
|
348
2082
|
|
349
2083
|
def handle_mouse_click(mouse_x, mouse_y)
|
@@ -351,40 +2085,30 @@ module Wads
|
|
351
2085
|
# of standard form elements in this dialog
|
352
2086
|
end
|
353
2087
|
|
354
|
-
def
|
355
|
-
#
|
356
|
-
|
2088
|
+
def handle_mouse_down mouse_x, mouse_y
|
2089
|
+
# Mouse click: Select text field based on mouse position.
|
2090
|
+
WadsConfig.instance.get_window.text_input = [@textinput].find { |tf| tf.under_point?(mouse_x, mouse_y) }
|
2091
|
+
# Advanced: Move caret to clicked position
|
2092
|
+
WadsConfig.instance.get_window.text_input.move_caret(mouse_x) unless WadsConfig.instance.get_window.text_input.nil?
|
2093
|
+
|
2094
|
+
handle_mouse_click(mouse_x, mouse_y)
|
357
2095
|
end
|
358
2096
|
|
359
|
-
def
|
2097
|
+
def handle_key_press id, mouse_x, mouse_y
|
360
2098
|
if id == Gosu::KbEscape
|
361
2099
|
return WidgetResult.new(true)
|
362
|
-
elsif id == Gosu::KbUp
|
363
|
-
handle_up(mouse_x, mouse_y)
|
364
|
-
elsif id == Gosu::KbDown
|
365
|
-
handle_down(mouse_x, mouse_y)
|
366
|
-
elsif id == Gosu::MsLeft
|
367
|
-
if @ok_button.contains_click(mouse_x, mouse_y)
|
368
|
-
return handle_ok
|
369
|
-
elsif @cancel_button.contains_click(mouse_x, mouse_y)
|
370
|
-
return handle_cancel
|
371
|
-
else
|
372
|
-
# Mouse click: Select text field based on mouse position.
|
373
|
-
@window.text_input = [@textinput].find { |tf| tf.under_point?(mouse_x, mouse_y) }
|
374
|
-
# Advanced: Move caret to clicked position
|
375
|
-
@window.text_input.move_caret(mouse_x) unless @window.text_input.nil?
|
376
|
-
|
377
|
-
handle_mouse_click(mouse_x, mouse_y)
|
378
|
-
end
|
379
|
-
else
|
380
|
-
if @window.text_input
|
381
|
-
text_input_updated(@textinput.text)
|
382
|
-
end
|
383
2100
|
end
|
384
|
-
WidgetResult.new(false)
|
385
2101
|
end
|
386
2102
|
end
|
387
2103
|
|
2104
|
+
#
|
2105
|
+
# A result object returned from handle methods that instructs the parent widget
|
2106
|
+
# what to do. A close_widget value of true instructs the recipient to close
|
2107
|
+
# either the overlay window or the entire app, based on the context of the receiver.
|
2108
|
+
# In the case of a form being submitted, the action may be "OK" and the form_data
|
2109
|
+
# contains the information supplied by the user.
|
2110
|
+
# WidgetResult is intentionally generic so it can support a wide variety of use cases.
|
2111
|
+
#
|
388
2112
|
class WidgetResult
|
389
2113
|
attr_accessor :close_widget
|
390
2114
|
attr_accessor :action
|
@@ -397,83 +2121,145 @@ module Wads
|
|
397
2121
|
end
|
398
2122
|
end
|
399
2123
|
|
2124
|
+
#
|
2125
|
+
# Renders a line from x, y to x2, y2. The theme graphics elements color
|
2126
|
+
# is used by default, unless specified using the optional parameter.
|
2127
|
+
#
|
400
2128
|
class Line < Widget
|
401
2129
|
attr_accessor :x2
|
402
2130
|
attr_accessor :y2
|
403
2131
|
|
404
|
-
def initialize(x, y, x2, y2, color =
|
405
|
-
super
|
406
|
-
@
|
407
|
-
@
|
2132
|
+
def initialize(x, y, x2, y2, color = nil)
|
2133
|
+
super(x, y)
|
2134
|
+
@override_color = color
|
2135
|
+
@x2 = x2
|
2136
|
+
@y2 = y2
|
2137
|
+
disable_border
|
2138
|
+
disable_background
|
2139
|
+
end
|
2140
|
+
|
2141
|
+
def render
|
2142
|
+
Gosu::draw_line x, y, graphics_color, x2, y2, graphics_color, z_order
|
2143
|
+
end
|
2144
|
+
|
2145
|
+
def widget_z
|
2146
|
+
Z_ORDER_GRAPHIC_ELEMENTS
|
408
2147
|
end
|
409
2148
|
|
410
|
-
def
|
411
|
-
|
2149
|
+
def uses_layout
|
2150
|
+
false
|
412
2151
|
end
|
413
2152
|
end
|
414
2153
|
|
2154
|
+
#
|
2155
|
+
# A very specific widget used along with a Plot to draw the x and y axis lines.
|
2156
|
+
# Note that the labels are drawn using separate widgets.
|
2157
|
+
#
|
415
2158
|
class AxisLines < Widget
|
416
|
-
def initialize(x, y, width, height, color =
|
417
|
-
super
|
418
|
-
|
419
|
-
|
2159
|
+
def initialize(x, y, width, height, color = nil)
|
2160
|
+
super(x, y)
|
2161
|
+
set_dimensions(width, height)
|
2162
|
+
disable_border
|
2163
|
+
disable_background
|
420
2164
|
end
|
421
2165
|
|
422
2166
|
def render
|
423
|
-
add_child(Line.new(@x, @y, @x, bottom_edge,
|
424
|
-
add_child(Line.new(@x, bottom_edge, right_edge, bottom_edge,
|
2167
|
+
add_child(Line.new(@x, @y, @x, bottom_edge, graphics_color))
|
2168
|
+
add_child(Line.new(@x, bottom_edge, right_edge, bottom_edge, graphics_color))
|
2169
|
+
end
|
2170
|
+
|
2171
|
+
def uses_layout
|
2172
|
+
false
|
425
2173
|
end
|
426
2174
|
end
|
427
2175
|
|
2176
|
+
#
|
2177
|
+
# Labels and tic marks for the vertical axis on a plot
|
2178
|
+
#
|
428
2179
|
class VerticalAxisLabel < Widget
|
429
2180
|
attr_accessor :label
|
430
2181
|
|
431
|
-
def initialize(x, y, label,
|
432
|
-
super
|
433
|
-
set_font(font)
|
2182
|
+
def initialize(x, y, label, color = nil)
|
2183
|
+
super(x, y)
|
434
2184
|
@label = label
|
2185
|
+
@override_color = color
|
2186
|
+
text_pixel_width = @gui_theme.font.text_width(@label)
|
2187
|
+
add_text(@label, -text_pixel_width - 28, -12)
|
2188
|
+
disable_border
|
2189
|
+
disable_background
|
435
2190
|
end
|
436
2191
|
|
437
2192
|
def render
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
2193
|
+
Gosu::draw_line @x - 20, @y, graphics_color,
|
2194
|
+
@x, @y, graphics_color, z_order
|
2195
|
+
end
|
2196
|
+
|
2197
|
+
def widget_z
|
2198
|
+
Z_ORDER_GRAPHIC_ELEMENTS
|
2199
|
+
end
|
2200
|
+
|
2201
|
+
def uses_layout
|
2202
|
+
false
|
443
2203
|
end
|
444
2204
|
end
|
445
2205
|
|
2206
|
+
#
|
2207
|
+
# Labels and tic marks for the horizontal axis on a plot
|
2208
|
+
#
|
446
2209
|
class HorizontalAxisLabel < Widget
|
447
2210
|
attr_accessor :label
|
448
2211
|
|
449
|
-
def initialize(x, y, label,
|
450
|
-
super
|
451
|
-
set_font(font)
|
2212
|
+
def initialize(x, y, label, color = nil)
|
2213
|
+
super(x, y)
|
452
2214
|
@label = label
|
2215
|
+
@override_color = color
|
2216
|
+
text_pixel_width = @gui_theme.font.text_width(@label)
|
2217
|
+
add_text(@label, -(text_pixel_width / 2), 26)
|
2218
|
+
disable_border
|
2219
|
+
disable_background
|
453
2220
|
end
|
454
2221
|
|
455
2222
|
def render
|
456
|
-
|
457
|
-
|
458
|
-
|
2223
|
+
Gosu::draw_line @x, @y, graphics_color, @x, @y + 20, graphics_color, z_order
|
2224
|
+
end
|
2225
|
+
|
2226
|
+
def widget_z
|
2227
|
+
Z_ORDER_TEXT
|
2228
|
+
end
|
2229
|
+
|
2230
|
+
def uses_layout
|
2231
|
+
false
|
459
2232
|
end
|
460
2233
|
end
|
461
2234
|
|
2235
|
+
#
|
2236
|
+
# Displays a table of information at the given coordinates.
|
2237
|
+
# The headers are an array of text labels to display at the top of each column.
|
2238
|
+
# The max_visible_rows specifies how many rows are visible at once.
|
2239
|
+
# If there are more data rows than the max, the arrow keys can be used to
|
2240
|
+
# page up or down through the rows in the table.
|
2241
|
+
#
|
462
2242
|
class Table < Widget
|
463
2243
|
attr_accessor :data_rows
|
464
2244
|
attr_accessor :row_colors
|
465
2245
|
attr_accessor :headers
|
466
2246
|
attr_accessor :max_visible_rows
|
467
2247
|
attr_accessor :current_row
|
2248
|
+
attr_accessor :can_delete_rows
|
468
2249
|
|
469
|
-
def initialize(x, y, width, height, headers,
|
470
|
-
super(x, y
|
471
|
-
set_font(font)
|
2250
|
+
def initialize(x, y, width, height, headers, max_visible_rows = 10, args = {})
|
2251
|
+
super(x, y)
|
472
2252
|
set_dimensions(width, height)
|
2253
|
+
if args[ARG_THEME]
|
2254
|
+
@gui_theme = args[ARG_THEME]
|
2255
|
+
end
|
473
2256
|
@headers = headers
|
474
2257
|
@current_row = 0
|
475
2258
|
@max_visible_rows = max_visible_rows
|
476
|
-
clear_rows
|
2259
|
+
clear_rows
|
2260
|
+
@can_delete_rows = false
|
2261
|
+
@delete_buttons = []
|
2262
|
+
@next_delete_button_y = 38
|
477
2263
|
end
|
478
2264
|
|
479
2265
|
def scroll_up
|
@@ -493,11 +2279,50 @@ module Wads
|
|
493
2279
|
@row_colors = []
|
494
2280
|
end
|
495
2281
|
|
496
|
-
def add_row(data_row, color =
|
2282
|
+
def add_row(data_row, color = text_color )
|
497
2283
|
@data_rows << data_row
|
498
2284
|
@row_colors << color
|
499
2285
|
end
|
500
2286
|
|
2287
|
+
def add_table_delete_button
|
2288
|
+
if @delete_buttons.size < @max_visible_rows
|
2289
|
+
new_button = add_delete_button(@width - 18, @next_delete_button_y) do
|
2290
|
+
# nothing to do here, handled in parent widget by event
|
2291
|
+
end
|
2292
|
+
@delete_buttons << new_button
|
2293
|
+
@next_delete_button_y = @next_delete_button_y + 30
|
2294
|
+
end
|
2295
|
+
end
|
2296
|
+
|
2297
|
+
def remove_table_delete_button
|
2298
|
+
if not @delete_buttons.empty?
|
2299
|
+
@delete_buttons.pop
|
2300
|
+
@children.pop
|
2301
|
+
@next_delete_button_y = @next_delete_button_y - 30
|
2302
|
+
end
|
2303
|
+
end
|
2304
|
+
|
2305
|
+
def handle_update update_count, mouse_x, mouse_y
|
2306
|
+
# How many visible data rows are there
|
2307
|
+
if @can_delete_rows
|
2308
|
+
number_of_visible_rows = @data_rows.size - @current_row
|
2309
|
+
if number_of_visible_rows > @max_visible_rows
|
2310
|
+
number_of_visible_rows = @max_visible_rows
|
2311
|
+
end
|
2312
|
+
if number_of_visible_rows > @delete_buttons.size
|
2313
|
+
number_to_add = number_of_visible_rows - @delete_buttons.size
|
2314
|
+
number_to_add.times do
|
2315
|
+
add_table_delete_button
|
2316
|
+
end
|
2317
|
+
elsif number_of_visible_rows < @delete_buttons.size
|
2318
|
+
number_to_remove = @delete_buttons.size - number_of_visible_rows
|
2319
|
+
number_to_remove.times do
|
2320
|
+
remove_table_delete_button
|
2321
|
+
end
|
2322
|
+
end
|
2323
|
+
end
|
2324
|
+
end
|
2325
|
+
|
501
2326
|
def number_of_rows
|
502
2327
|
@data_rows.size
|
503
2328
|
end
|
@@ -509,9 +2334,9 @@ module Wads
|
|
509
2334
|
column_widths = []
|
510
2335
|
number_of_columns = @data_rows[0].size
|
511
2336
|
(0..number_of_columns-1).each do |c|
|
512
|
-
max_length = @font.text_width(headers[c])
|
2337
|
+
max_length = @gui_theme.font.text_width(headers[c])
|
513
2338
|
(0..number_of_rows-1).each do |r|
|
514
|
-
text_pixel_width = @font.text_width(@data_rows[r][c])
|
2339
|
+
text_pixel_width = @gui_theme.font.text_width(@data_rows[r][c])
|
515
2340
|
if text_pixel_width > max_length
|
516
2341
|
max_length = text_pixel_width
|
517
2342
|
end
|
@@ -519,18 +2344,22 @@ module Wads
|
|
519
2344
|
column_widths[c] = max_length
|
520
2345
|
end
|
521
2346
|
|
2347
|
+
# Draw a horizontal line between header and data rows
|
522
2348
|
x = @x + 10
|
523
2349
|
if number_of_columns > 1
|
524
2350
|
(0..number_of_columns-2).each do |c|
|
525
2351
|
x = x + column_widths[c] + 20
|
526
|
-
Gosu::draw_line x, @y,
|
2352
|
+
Gosu::draw_line x, @y, graphics_color, x, @y + @height, graphics_color, z_order
|
527
2353
|
end
|
528
2354
|
end
|
529
2355
|
|
530
|
-
|
2356
|
+
# Draw the header row
|
2357
|
+
y = @y
|
2358
|
+
Gosu::draw_rect(@x + 1, y, @width - 3, 28, graphics_color, relative_z_order(Z_ORDER_SELECTION_BACKGROUND))
|
2359
|
+
|
531
2360
|
x = @x + 20
|
532
2361
|
(0..number_of_columns-1).each do |c|
|
533
|
-
@font.draw_text(@headers[c], x, y,
|
2362
|
+
@gui_theme.font.draw_text(@headers[c], x, y + 3, z_order, 1, 1, text_color)
|
534
2363
|
x = x + column_widths[c] + 20
|
535
2364
|
end
|
536
2365
|
y = y + 30
|
@@ -542,7 +2371,7 @@ module Wads
|
|
542
2371
|
elsif count < @current_row + @max_visible_rows
|
543
2372
|
x = @x + 20
|
544
2373
|
(0..number_of_columns-1).each do |c|
|
545
|
-
@font.draw_text(row[c], x, y + 2,
|
2374
|
+
@gui_theme.font.draw_text(row[c], x, y + 2, z_order, 1, 1, @row_colors[count])
|
546
2375
|
x = x + column_widths[c] + 20
|
547
2376
|
end
|
548
2377
|
y = y + 30
|
@@ -559,15 +2388,35 @@ module Wads
|
|
559
2388
|
end
|
560
2389
|
row_number
|
561
2390
|
end
|
2391
|
+
|
2392
|
+
def widget_z
|
2393
|
+
Z_ORDER_TEXT
|
2394
|
+
end
|
2395
|
+
|
2396
|
+
def uses_layout
|
2397
|
+
false
|
2398
|
+
end
|
562
2399
|
end
|
563
2400
|
|
2401
|
+
#
|
2402
|
+
# A table where the user can select one row at a time.
|
2403
|
+
# The selected row has a background color specified by the selection color of the
|
2404
|
+
# current theme.
|
2405
|
+
#
|
564
2406
|
class SingleSelectTable < Table
|
565
2407
|
attr_accessor :selected_row
|
566
|
-
attr_accessor :selected_color
|
567
2408
|
|
568
|
-
def initialize(x, y, width, height, headers,
|
569
|
-
super(x, y, width, height, headers,
|
570
|
-
|
2409
|
+
def initialize(x, y, width, height, headers, max_visible_rows = 10, args = {})
|
2410
|
+
super(x, y, width, height, headers, max_visible_rows, args)
|
2411
|
+
end
|
2412
|
+
|
2413
|
+
def is_row_selected(mouse_y)
|
2414
|
+
row_number = determine_row_number(mouse_y)
|
2415
|
+
if row_number.nil?
|
2416
|
+
return false
|
2417
|
+
end
|
2418
|
+
selected_row = @current_row + row_number
|
2419
|
+
@selected_row == selected_row
|
571
2420
|
end
|
572
2421
|
|
573
2422
|
def set_selected_row(mouse_y, column_number)
|
@@ -584,29 +2433,81 @@ module Wads
|
|
584
2433
|
end
|
585
2434
|
end
|
586
2435
|
|
2436
|
+
def unset_selected_row(mouse_y, column_number)
|
2437
|
+
row_number = determine_row_number(mouse_y)
|
2438
|
+
if not row_number.nil?
|
2439
|
+
this_selected_row = @current_row + row_number
|
2440
|
+
@selected_row = this_selected_row
|
2441
|
+
return @data_rows[this_selected_row][column_number]
|
2442
|
+
end
|
2443
|
+
nil
|
2444
|
+
end
|
2445
|
+
|
587
2446
|
def render
|
588
2447
|
super
|
589
2448
|
if @selected_row
|
590
2449
|
if @selected_row >= @current_row and @selected_row < @current_row + @max_visible_rows
|
591
2450
|
y = @y + 30 + ((@selected_row - @current_row) * 30)
|
592
|
-
Gosu::draw_rect(@x + 20, y, @width - 30, 28, @
|
2451
|
+
Gosu::draw_rect(@x + 20, y, @width - 30, 28, @gui_theme.selection_color, relative_z_order(Z_ORDER_SELECTION_BACKGROUND))
|
2452
|
+
end
|
2453
|
+
end
|
2454
|
+
end
|
2455
|
+
|
2456
|
+
def widget_z
|
2457
|
+
Z_ORDER_TEXT
|
2458
|
+
end
|
2459
|
+
|
2460
|
+
def handle_mouse_down mouse_x, mouse_y
|
2461
|
+
if contains_click(mouse_x, mouse_y)
|
2462
|
+
row_number = determine_row_number(mouse_y)
|
2463
|
+
if row_number.nil?
|
2464
|
+
return WidgetResult.new(false)
|
2465
|
+
end
|
2466
|
+
# First check if its the delete button that got this
|
2467
|
+
delete_this_row = false
|
2468
|
+
@delete_buttons.each do |db|
|
2469
|
+
if db.contains_click(mouse_x, mouse_y)
|
2470
|
+
delete_this_row = true
|
2471
|
+
end
|
593
2472
|
end
|
2473
|
+
if delete_this_row
|
2474
|
+
if not row_number.nil?
|
2475
|
+
data_set_row_to_delete = @current_row + row_number
|
2476
|
+
data_set_name_to_delete = @data_rows[data_set_row_to_delete][1]
|
2477
|
+
@data_rows.delete_at(data_set_row_to_delete)
|
2478
|
+
return WidgetResult.new(false, EVENT_TABLE_ROW_DELETE, [data_set_name_to_delete])
|
2479
|
+
end
|
2480
|
+
else
|
2481
|
+
if is_row_selected(mouse_y)
|
2482
|
+
unset_selected_row(mouse_y, 0)
|
2483
|
+
return WidgetResult.new(false, EVENT_TABLE_UNSELECT, @data_rows[row_number])
|
2484
|
+
else
|
2485
|
+
set_selected_row(mouse_y, 0)
|
2486
|
+
return WidgetResult.new(false, EVENT_TABLE_SELECT, @data_rows[row_number])
|
2487
|
+
end
|
2488
|
+
end
|
594
2489
|
end
|
595
2490
|
end
|
596
2491
|
end
|
597
2492
|
|
2493
|
+
#
|
2494
|
+
# A table where the user can select multiple rows at a time.
|
2495
|
+
# Selected rows have a background color specified by the selection color of the
|
2496
|
+
# current theme.
|
2497
|
+
#
|
598
2498
|
class MultiSelectTable < Table
|
599
2499
|
attr_accessor :selected_rows
|
600
|
-
attr_accessor :selection_color
|
601
2500
|
|
602
|
-
def initialize(x, y, width, height, headers,
|
603
|
-
super(x, y, width, height, headers,
|
2501
|
+
def initialize(x, y, width, height, headers, max_visible_rows = 10, args = {})
|
2502
|
+
super(x, y, width, height, headers, max_visible_rows, args)
|
604
2503
|
@selected_rows = []
|
605
|
-
|
606
|
-
|
607
|
-
|
2504
|
+
end
|
2505
|
+
|
608
2506
|
def is_row_selected(mouse_y)
|
609
2507
|
row_number = determine_row_number(mouse_y)
|
2508
|
+
if row_number.nil?
|
2509
|
+
return false
|
2510
|
+
end
|
610
2511
|
@selected_rows.include?(@current_row + row_number)
|
611
2512
|
end
|
612
2513
|
|
@@ -635,61 +2536,112 @@ module Wads
|
|
635
2536
|
y = @y + 30
|
636
2537
|
row_count = @current_row
|
637
2538
|
while row_count < @data_rows.size
|
638
|
-
if @selected_rows.include? row_count
|
639
|
-
|
2539
|
+
if @selected_rows.include? row_count
|
2540
|
+
width_of_selection_background = @width - 30
|
2541
|
+
if @can_delete_rows
|
2542
|
+
width_of_selection_background = width_of_selection_background - 20
|
2543
|
+
end
|
2544
|
+
Gosu::draw_rect(@x + 20, y, width_of_selection_background, 28,
|
2545
|
+
@gui_theme.selection_color,
|
2546
|
+
relative_z_order(Z_ORDER_SELECTION_BACKGROUND))
|
640
2547
|
end
|
641
2548
|
y = y + 30
|
642
2549
|
row_count = row_count + 1
|
643
2550
|
end
|
644
2551
|
end
|
2552
|
+
|
2553
|
+
def widget_z
|
2554
|
+
Z_ORDER_TEXT
|
2555
|
+
end
|
2556
|
+
|
2557
|
+
def handle_mouse_down mouse_x, mouse_y
|
2558
|
+
if contains_click(mouse_x, mouse_y)
|
2559
|
+
row_number = determine_row_number(mouse_y)
|
2560
|
+
if row_number.nil?
|
2561
|
+
return WidgetResult.new(false)
|
2562
|
+
end
|
2563
|
+
# First check if its the delete button that got this
|
2564
|
+
delete_this_row = false
|
2565
|
+
@delete_buttons.each do |db|
|
2566
|
+
if db.contains_click(mouse_x, mouse_y)
|
2567
|
+
delete_this_row = true
|
2568
|
+
end
|
2569
|
+
end
|
2570
|
+
if delete_this_row
|
2571
|
+
if not row_number.nil?
|
2572
|
+
data_set_row_to_delete = @current_row + row_number
|
2573
|
+
data_set_name_to_delete = @data_rows[data_set_row_to_delete][1]
|
2574
|
+
@data_rows.delete_at(data_set_row_to_delete)
|
2575
|
+
return WidgetResult.new(false, EVENT_TABLE_ROW_DELETE, [data_set_name_to_delete])
|
2576
|
+
end
|
2577
|
+
else
|
2578
|
+
if is_row_selected(mouse_y)
|
2579
|
+
unset_selected_row(mouse_y, 0)
|
2580
|
+
return WidgetResult.new(false, EVENT_TABLE_UNSELECT, @data_rows[row_number])
|
2581
|
+
else
|
2582
|
+
set_selected_row(mouse_y, 0)
|
2583
|
+
return WidgetResult.new(false, EVENT_TABLE_SELECT, @data_rows[row_number])
|
2584
|
+
end
|
2585
|
+
end
|
2586
|
+
end
|
2587
|
+
end
|
645
2588
|
end
|
646
2589
|
|
2590
|
+
#
|
2591
|
+
# A two-dimensional graph display which plots a number of PlotPoint objects.
|
2592
|
+
# Options include grid lines that can be displayed, as well as whether lines
|
2593
|
+
# should be drawn connecting each point in a data set.
|
2594
|
+
#
|
647
2595
|
class Plot < Widget
|
648
2596
|
attr_accessor :points
|
649
2597
|
attr_accessor :visible_range
|
650
2598
|
attr_accessor :display_grid
|
651
2599
|
attr_accessor :display_lines
|
652
2600
|
attr_accessor :zoom_level
|
2601
|
+
attr_accessor :visibility_map
|
653
2602
|
|
654
|
-
def initialize(x, y, width, height
|
655
|
-
super
|
656
|
-
set_font(font)
|
2603
|
+
def initialize(x, y, width, height)
|
2604
|
+
super(x, y)
|
657
2605
|
set_dimensions(width, height)
|
658
2606
|
@display_grid = false
|
659
2607
|
@display_lines = true
|
660
|
-
@
|
661
|
-
@grid_line_color = COLOR_CYAN
|
2608
|
+
@grid_line_color = COLOR_LIGHT_GRAY
|
662
2609
|
@cursor_line_color = COLOR_DARK_GRAY
|
663
|
-
@zero_line_color =
|
2610
|
+
@zero_line_color = COLOR_HEADER_BRIGHT_BLUE
|
664
2611
|
@zoom_level = 1
|
2612
|
+
@data_point_size = 4
|
2613
|
+
# Hash of rendered points keyed by data set name, so we can toggle visibility
|
2614
|
+
@points_by_data_set_name = {}
|
2615
|
+
@visibility_map = {}
|
2616
|
+
disable_border
|
665
2617
|
end
|
666
2618
|
|
667
|
-
def
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
point.increase_size
|
672
|
-
end
|
2619
|
+
def toggle_visibility(data_set_name)
|
2620
|
+
is_visible = @visibility_map[data_set_name]
|
2621
|
+
if is_visible.nil?
|
2622
|
+
return
|
673
2623
|
end
|
2624
|
+
@visibility_map[data_set_name] = !is_visible
|
2625
|
+
end
|
2626
|
+
|
2627
|
+
def increase_data_point_size
|
2628
|
+
@data_point_size = @data_point_size + 2
|
674
2629
|
end
|
675
2630
|
|
676
2631
|
def decrease_data_point_size
|
677
|
-
@
|
678
|
-
|
679
|
-
data_set.rendered_points.each do |point|
|
680
|
-
point.decrease_size
|
681
|
-
end
|
2632
|
+
if @data_point_size > 2
|
2633
|
+
@data_point_size = @data_point_size - 2
|
682
2634
|
end
|
683
2635
|
end
|
684
2636
|
|
685
2637
|
def zoom_out
|
686
|
-
@zoom_level = @zoom_level + 0.
|
2638
|
+
@zoom_level = @zoom_level + 0.15
|
687
2639
|
visible_range.scale(@zoom_level)
|
688
2640
|
end
|
689
2641
|
|
690
2642
|
def zoom_in
|
691
2643
|
if @zoom_level > 0.11
|
692
|
-
@zoom_level = @zoom_level - 0.
|
2644
|
+
@zoom_level = @zoom_level - 0.15
|
693
2645
|
end
|
694
2646
|
visible_range.scale(@zoom_level)
|
695
2647
|
end
|
@@ -713,11 +2665,6 @@ module Wads
|
|
713
2665
|
def define_range(range)
|
714
2666
|
@visible_range = range
|
715
2667
|
@zoom_level = 1
|
716
|
-
@data_set_hash.keys.each do |key|
|
717
|
-
data_set = @data_set_hash[key]
|
718
|
-
puts "Calling derive values on #{key}"
|
719
|
-
data_set.derive_values(range, @data_set_hash)
|
720
|
-
end
|
721
2668
|
end
|
722
2669
|
|
723
2670
|
def range_set?
|
@@ -725,24 +2672,43 @@ module Wads
|
|
725
2672
|
end
|
726
2673
|
|
727
2674
|
def is_on_screen(point)
|
728
|
-
point.
|
2675
|
+
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
|
729
2676
|
end
|
730
2677
|
|
731
|
-
def
|
2678
|
+
def add_data_point(data_set_name, data_x, data_y, color = COLOR_MAROON)
|
732
2679
|
if range_set?
|
733
|
-
@
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
2680
|
+
rendered_points = @points_by_data_set_name[data_set_name]
|
2681
|
+
if rendered_points.nil?
|
2682
|
+
rendered_points = []
|
2683
|
+
@points_by_data_set_name[data_set_name] = rendered_points
|
2684
|
+
end
|
2685
|
+
rendered_points << PlotPoint.new(draw_x(data_x), draw_y(data_y),
|
2686
|
+
data_x, data_y,
|
2687
|
+
color)
|
2688
|
+
if @visibility_map[data_set_name].nil?
|
2689
|
+
@visibility_map[data_set_name] = true
|
740
2690
|
end
|
741
2691
|
else
|
742
|
-
|
2692
|
+
error("ERROR: range not set, cannot add data")
|
743
2693
|
end
|
744
2694
|
end
|
745
2695
|
|
2696
|
+
def add_data_set(data_set_name, rendered_points)
|
2697
|
+
if range_set?
|
2698
|
+
@points_by_data_set_name[data_set_name] = rendered_points
|
2699
|
+
if @visibility_map[data_set_name].nil?
|
2700
|
+
@visibility_map[data_set_name] = true
|
2701
|
+
end
|
2702
|
+
else
|
2703
|
+
error("ERROR: range not set, cannot add data")
|
2704
|
+
end
|
2705
|
+
end
|
2706
|
+
|
2707
|
+
def remove_data_set(data_set_name)
|
2708
|
+
@points_by_data_set_name.delete(data_set_name)
|
2709
|
+
@visibility_map.delete(data_set_name)
|
2710
|
+
end
|
2711
|
+
|
746
2712
|
def x_val_to_pixel(val)
|
747
2713
|
x_pct = (@visible_range.right_x - val).to_f / @visible_range.x_range
|
748
2714
|
@width - (@width.to_f * x_pct).round
|
@@ -753,14 +2719,6 @@ module Wads
|
|
753
2719
|
(@height.to_f * y_pct).round
|
754
2720
|
end
|
755
2721
|
|
756
|
-
def x_pixel_to_screen(x)
|
757
|
-
@x + x
|
758
|
-
end
|
759
|
-
|
760
|
-
def y_pixel_to_screen(y)
|
761
|
-
@y + y
|
762
|
-
end
|
763
|
-
|
764
2722
|
def draw_x(x)
|
765
2723
|
x_pixel_to_screen(x_val_to_pixel(x))
|
766
2724
|
end
|
@@ -770,63 +2728,63 @@ module Wads
|
|
770
2728
|
end
|
771
2729
|
|
772
2730
|
def render
|
773
|
-
@
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
point
|
2731
|
+
@points_by_data_set_name.keys.each do |key|
|
2732
|
+
if @visibility_map[key]
|
2733
|
+
data_set_points = @points_by_data_set_name[key]
|
2734
|
+
data_set_points.each do |point|
|
2735
|
+
if is_on_screen(point)
|
2736
|
+
point.render(@data_point_size)
|
2737
|
+
end
|
778
2738
|
end
|
779
2739
|
if @display_lines
|
780
|
-
display_lines_for_point_set(
|
2740
|
+
display_lines_for_point_set(data_set_points)
|
781
2741
|
end
|
782
2742
|
end
|
783
|
-
|
784
|
-
|
785
|
-
|
2743
|
+
if @display_grid and range_set?
|
2744
|
+
display_grid_lines
|
2745
|
+
end
|
786
2746
|
end
|
787
2747
|
end
|
788
2748
|
|
789
2749
|
def display_lines_for_point_set(points)
|
790
2750
|
if points.length > 1
|
791
2751
|
points.inject(points[0]) do |last, the_next|
|
792
|
-
|
793
|
-
|
2752
|
+
if last.x < the_next.x
|
2753
|
+
Gosu::draw_line last.x, last.y, last.graphics_color,
|
2754
|
+
the_next.x, the_next.y, last.graphics_color, relative_z_order(Z_ORDER_GRAPHIC_ELEMENTS)
|
2755
|
+
end
|
794
2756
|
the_next
|
795
2757
|
end
|
796
2758
|
end
|
797
2759
|
end
|
798
2760
|
|
799
2761
|
def display_grid_lines
|
800
|
-
# TODO this is bnot working well for large ranges with the given increment of 1
|
801
|
-
# We don't want to draw hundreds of grid lines
|
802
2762
|
grid_widgets = []
|
803
2763
|
|
804
|
-
|
805
|
-
|
806
|
-
|
2764
|
+
x_lines = @visible_range.grid_line_x_values
|
2765
|
+
y_lines = @visible_range.grid_line_y_values
|
2766
|
+
first_x = draw_x(@visible_range.left_x)
|
2767
|
+
last_x = draw_x(@visible_range.right_x)
|
2768
|
+
first_y = draw_y(@visible_range.bottom_y)
|
2769
|
+
last_y = draw_y(@visible_range.top_y)
|
2770
|
+
|
2771
|
+
x_lines.each do |grid_x|
|
807
2772
|
dx = draw_x(grid_x)
|
808
|
-
dy = draw_y(grid_y)
|
809
|
-
last_x = draw_x(@visible_range.right_x)
|
810
2773
|
color = @grid_line_color
|
811
|
-
if
|
812
|
-
color = @zero_line_color
|
813
|
-
end
|
814
|
-
grid_widgets << Line.new(dx,
|
815
|
-
grid_y = grid_y + 1
|
2774
|
+
if grid_x == 0 and grid_x != @visible_range.left_x.to_i
|
2775
|
+
color = @zero_line_color
|
2776
|
+
end
|
2777
|
+
grid_widgets << Line.new(dx, first_y, dx, last_y, color)
|
816
2778
|
end
|
817
|
-
|
818
|
-
grid_y
|
819
|
-
while grid_x < @visible_range.right_x
|
820
|
-
dx = draw_x(grid_x)
|
2779
|
+
|
2780
|
+
y_lines.each do |grid_y|
|
821
2781
|
dy = draw_y(grid_y)
|
822
|
-
last_y = draw_y(@visible_range.top_y)
|
823
2782
|
color = @grid_line_color
|
824
|
-
if
|
825
|
-
color = @zero_line_color
|
2783
|
+
if grid_y == 0 and grid_y != @visible_range.bottom_y.to_i
|
2784
|
+
color = @zero_line_color
|
826
2785
|
end
|
827
|
-
grid_widgets << Line.new(
|
828
|
-
|
829
|
-
end
|
2786
|
+
grid_widgets << Line.new(first_x, dy, last_x, dy, color)
|
2787
|
+
end
|
830
2788
|
|
831
2789
|
grid_widgets.each do |gw|
|
832
2790
|
gw.draw
|
@@ -856,94 +2814,504 @@ module Wads
|
|
856
2814
|
end
|
857
2815
|
end
|
858
2816
|
|
2817
|
+
#
|
2818
|
+
# A graphical representation of a node in a graph using a button-style, i.e
|
2819
|
+
# a rectangular border with a text label.
|
2820
|
+
# The choice to use this display class is dictated by the use_icons attribute
|
2821
|
+
# of the current theme.
|
2822
|
+
# Like images, the size of node widgets can be scaled.
|
2823
|
+
#
|
859
2824
|
class NodeWidget < Button
|
860
2825
|
attr_accessor :data_node
|
861
2826
|
|
862
|
-
def initialize(
|
863
|
-
super(
|
864
|
-
|
2827
|
+
def initialize(x, y, node, color = nil, initial_scale = 1, is_explorer = false)
|
2828
|
+
super(x, y, node.name)
|
2829
|
+
@orig_width = @width
|
2830
|
+
@orig_height = @height
|
865
2831
|
@data_node = node
|
2832
|
+
@override_color = color
|
2833
|
+
set_scale(initial_scale, @is_explorer)
|
2834
|
+
end
|
2835
|
+
|
2836
|
+
def is_background
|
2837
|
+
@scale <= 1 and @is_explorer
|
2838
|
+
end
|
2839
|
+
|
2840
|
+
def set_scale(value, is_explorer = false)
|
2841
|
+
@scale = value
|
2842
|
+
@is_explorer = is_explorer
|
2843
|
+
if value < 1
|
2844
|
+
value = 1
|
2845
|
+
end
|
2846
|
+
@width = @orig_width * @scale.to_f
|
2847
|
+
debug("In regular node widget Setting scale of #{@label} to #{@scale}")
|
2848
|
+
end
|
2849
|
+
|
2850
|
+
def get_text_widget
|
2851
|
+
nil
|
866
2852
|
end
|
867
2853
|
|
868
2854
|
def render
|
869
2855
|
super
|
870
|
-
draw_background(
|
871
|
-
draw_shadow(COLOR_GRAY)
|
2856
|
+
draw_background(Z_ORDER_FOCAL_ELEMENTS)
|
2857
|
+
#draw_shadow(COLOR_GRAY)
|
2858
|
+
end
|
2859
|
+
|
2860
|
+
def widget_z
|
2861
|
+
Z_ORDER_TEXT
|
2862
|
+
end
|
2863
|
+
end
|
2864
|
+
|
2865
|
+
#
|
2866
|
+
# A graphical representation of a node in a graph using circular icons
|
2867
|
+
# and adjacent text labels.
|
2868
|
+
# The choice to use this display class is dictated by the use_icons attribute
|
2869
|
+
# of the current theme.
|
2870
|
+
# Like images, the size of node widgets can be scaled.
|
2871
|
+
#
|
2872
|
+
class NodeIconWidget < Widget
|
2873
|
+
attr_accessor :data_node
|
2874
|
+
attr_accessor :image
|
2875
|
+
attr_accessor :scale
|
2876
|
+
attr_accessor :label
|
2877
|
+
attr_accessor :is_explorer
|
2878
|
+
|
2879
|
+
def initialize(x, y, node, color = nil, initial_scale = 1, is_explorer = false)
|
2880
|
+
super(x, y)
|
2881
|
+
@override_color = color
|
2882
|
+
@data_node = node
|
2883
|
+
@label = node.name
|
2884
|
+
circle_image = WadsConfig.instance.circle(color)
|
2885
|
+
if circle_image.nil?
|
2886
|
+
@image = WadsConfig.instance.circle(COLOR_BLUE)
|
2887
|
+
else
|
2888
|
+
@image = circle_image
|
2889
|
+
end
|
2890
|
+
@is_explorer = is_explorer
|
2891
|
+
set_scale(initial_scale, @is_explorer)
|
2892
|
+
disable_border
|
2893
|
+
end
|
2894
|
+
|
2895
|
+
def name
|
2896
|
+
@data_node.name
|
2897
|
+
end
|
2898
|
+
|
2899
|
+
def is_background
|
2900
|
+
@scale <= 0.1 and @is_explorer
|
2901
|
+
end
|
2902
|
+
|
2903
|
+
def set_scale(value, is_explorer = false)
|
2904
|
+
@is_explorer = is_explorer
|
2905
|
+
if value < 0.5
|
2906
|
+
value = 0.5
|
2907
|
+
end
|
2908
|
+
@scale = value / 10.to_f
|
2909
|
+
#debug("In node widget Setting scale of #{@label} to #{value} = #{@scale}")
|
2910
|
+
@width = IMAGE_CIRCLE_SIZE * scale.to_f
|
2911
|
+
@height = IMAGE_CIRCLE_SIZE * scale.to_f
|
2912
|
+
# Only in explorer mode do we dull out nodes on the outer edge
|
2913
|
+
if is_background
|
2914
|
+
@image = WadsConfig.instance.circle(COLOR_ALPHA)
|
2915
|
+
else
|
2916
|
+
text_pixel_width = @gui_theme.font.text_width(@label)
|
2917
|
+
clear_children # the text widget is the only child, so we can remove all
|
2918
|
+
add_text(@label, (@width / 2) - (text_pixel_width / 2), -20)
|
2919
|
+
end
|
2920
|
+
end
|
2921
|
+
|
2922
|
+
def get_text_widget
|
2923
|
+
if @children.size > 0
|
2924
|
+
return @children[0]
|
2925
|
+
end
|
2926
|
+
#raise "No text widget for NodeIconWidget"
|
2927
|
+
nil
|
2928
|
+
end
|
2929
|
+
|
2930
|
+
def render
|
2931
|
+
@image.draw @x, @y, relative_z_order(Z_ORDER_FOCAL_ELEMENTS), @scale, @scale
|
2932
|
+
end
|
2933
|
+
|
2934
|
+
def widget_z
|
2935
|
+
Z_ORDER_TEXT
|
872
2936
|
end
|
873
2937
|
end
|
874
2938
|
|
2939
|
+
#
|
2940
|
+
# Given a single node or a graph data structure, this widget displays
|
2941
|
+
# a visualization of the graph using one of the available node widget classes.
|
2942
|
+
# There are different display modes that control what nodes within the graph
|
2943
|
+
# are shown. The default display mode, GRAPH_DISPLAY_ALL, shows all nodes
|
2944
|
+
# as the name implies. GRAPH_DISPLAY_TREE assumes an acyclic graph and renders
|
2945
|
+
# the graph in a tree-like structure. GRAPH_DISPLAY_EXPLORER has a chosen
|
2946
|
+
# center focus node with connected nodes circled around it based on the depth
|
2947
|
+
# or distance from that node. This mode also allows the user to click on
|
2948
|
+
# different nodes to navigate the graph and change focus nodes.
|
2949
|
+
#
|
875
2950
|
class GraphWidget < Widget
|
876
2951
|
attr_accessor :graph
|
877
|
-
attr_accessor :center_node
|
878
|
-
attr_accessor :depth
|
879
2952
|
attr_accessor :selected_node
|
880
2953
|
attr_accessor :selected_node_x_offset
|
881
2954
|
attr_accessor :selected_node_y_offset
|
2955
|
+
attr_accessor :size_by_connections
|
2956
|
+
attr_accessor :is_explorer
|
882
2957
|
|
883
|
-
def initialize(x, y, width, height,
|
884
|
-
super
|
885
|
-
set_font(font)
|
2958
|
+
def initialize(x, y, width, height, graph, display_mode = GRAPH_DISPLAY_ALL)
|
2959
|
+
super(x, y)
|
886
2960
|
set_dimensions(width, height)
|
887
|
-
|
888
|
-
|
2961
|
+
if graph.is_a? Node
|
2962
|
+
@graph = Graph.new(graph)
|
2963
|
+
else
|
2964
|
+
@graph = graph
|
2965
|
+
end
|
2966
|
+
@size_by_connections = false
|
2967
|
+
@is_explorer = false
|
2968
|
+
if [GRAPH_DISPLAY_ALL, GRAPH_DISPLAY_TREE, GRAPH_DISPLAY_EXPLORER].include? display_mode
|
2969
|
+
debug("Displaying graph in #{display_mode} mode")
|
2970
|
+
else
|
2971
|
+
raise "#{display_mode} is not a valid display mode for Graph Widget"
|
2972
|
+
end
|
2973
|
+
if display_mode == GRAPH_DISPLAY_ALL
|
2974
|
+
set_all_nodes_for_display
|
2975
|
+
elsif display_mode == GRAPH_DISPLAY_TREE
|
2976
|
+
set_tree_display
|
2977
|
+
else
|
2978
|
+
set_explorer_display
|
2979
|
+
end
|
889
2980
|
end
|
890
2981
|
|
891
|
-
def
|
2982
|
+
def handle_update update_count, mouse_x, mouse_y
|
892
2983
|
if contains_click(mouse_x, mouse_y) and @selected_node
|
893
|
-
@selected_node.
|
894
|
-
|
2984
|
+
@selected_node.move_recursive_absolute(mouse_x - @selected_node_x_offset,
|
2985
|
+
mouse_y - @selected_node_y_offset)
|
895
2986
|
end
|
896
2987
|
end
|
897
2988
|
|
898
|
-
def
|
899
|
-
if
|
900
|
-
|
901
|
-
|
902
|
-
|
903
|
-
|
904
|
-
|
905
|
-
|
906
|
-
|
907
|
-
end
|
2989
|
+
def handle_mouse_down mouse_x, mouse_y
|
2990
|
+
# check to see if any node was selected
|
2991
|
+
if @rendered_nodes
|
2992
|
+
@rendered_nodes.values.each do |rn|
|
2993
|
+
if rn.contains_click(mouse_x, mouse_y)
|
2994
|
+
@selected_node = rn
|
2995
|
+
@selected_node_x_offset = mouse_x - rn.x
|
2996
|
+
@selected_node_y_offset = mouse_y - rn.y
|
2997
|
+
@click_timestamp = Time.now
|
908
2998
|
end
|
909
2999
|
end
|
910
3000
|
end
|
911
3001
|
WidgetResult.new(false)
|
912
3002
|
end
|
913
3003
|
|
914
|
-
def
|
915
|
-
if
|
916
|
-
if @
|
917
|
-
|
3004
|
+
def handle_mouse_up mouse_x, mouse_y
|
3005
|
+
if @selected_node
|
3006
|
+
if @is_explorer
|
3007
|
+
time_between_mouse_up_down = Time.now - @click_timestamp
|
3008
|
+
if time_between_mouse_up_down < 0.2
|
3009
|
+
# Treat this as a single click and make the selected
|
3010
|
+
# node the new center node of the graph
|
3011
|
+
set_explorer_display(@selected_node.data_node)
|
3012
|
+
end
|
3013
|
+
end
|
3014
|
+
@selected_node = nil
|
3015
|
+
end
|
3016
|
+
end
|
3017
|
+
|
3018
|
+
def set_explorer_display(center_node = nil)
|
3019
|
+
if center_node.nil?
|
3020
|
+
# If not specified, pick a center node as the one with the most connections
|
3021
|
+
center_node = @graph.node_with_most_connections
|
3022
|
+
end
|
3023
|
+
|
3024
|
+
@graph.reset_visited
|
3025
|
+
@visible_data_nodes = {}
|
3026
|
+
center_node.bfs(4) do |n|
|
3027
|
+
@visible_data_nodes[n.name] = n
|
3028
|
+
end
|
3029
|
+
|
3030
|
+
@size_by_connections = false
|
3031
|
+
@is_explorer = true
|
3032
|
+
|
3033
|
+
@rendered_nodes = {}
|
3034
|
+
populate_rendered_nodes
|
3035
|
+
|
3036
|
+
prevent_text_overlap
|
3037
|
+
end
|
3038
|
+
|
3039
|
+
def set_tree_display
|
3040
|
+
@graph.reset_visited
|
3041
|
+
@visible_data_nodes = @graph.node_map
|
3042
|
+
@rendered_nodes = {}
|
3043
|
+
|
3044
|
+
root_nodes = @graph.root_nodes
|
3045
|
+
number_of_root_nodes = root_nodes.size
|
3046
|
+
width_for_each_root_tree = @width / number_of_root_nodes
|
3047
|
+
|
3048
|
+
start_x = 0
|
3049
|
+
y_level = 20
|
3050
|
+
root_nodes.each do |root|
|
3051
|
+
set_tree_recursive(root, start_x, start_x + width_for_each_root_tree - 1, y_level)
|
3052
|
+
start_x = start_x + width_for_each_root_tree
|
3053
|
+
y_level = y_level + 40
|
3054
|
+
end
|
3055
|
+
|
3056
|
+
@rendered_nodes.values.each do |rn|
|
3057
|
+
rn.base_z = @base_z
|
3058
|
+
end
|
3059
|
+
|
3060
|
+
if @size_by_connections
|
3061
|
+
scale_node_size
|
3062
|
+
end
|
3063
|
+
|
3064
|
+
prevent_text_overlap
|
3065
|
+
end
|
3066
|
+
|
3067
|
+
def scale_node_size
|
3068
|
+
range = @graph.get_number_of_connections_range
|
3069
|
+
# There are six colors. Any number of scale sizes
|
3070
|
+
# Lets try 4 first as a max size.
|
3071
|
+
bins = range.bin_max_values(4)
|
3072
|
+
|
3073
|
+
# Set the scale for each node
|
3074
|
+
@visible_data_nodes.values.each do |node|
|
3075
|
+
num_links = node.number_of_links
|
3076
|
+
index = 0
|
3077
|
+
while index < bins.size
|
3078
|
+
if num_links <= bins[index]
|
3079
|
+
@rendered_nodes[node.name].set_scale(index + 1, @is_explorer)
|
3080
|
+
index = bins.size
|
3081
|
+
end
|
3082
|
+
index = index + 1
|
3083
|
+
end
|
3084
|
+
end
|
3085
|
+
end
|
3086
|
+
|
3087
|
+
def prevent_text_overlap
|
3088
|
+
@rendered_nodes.values.each do |rn|
|
3089
|
+
text = rn.get_text_widget
|
3090
|
+
if text
|
3091
|
+
if overlaps_with_a_node(text)
|
3092
|
+
move_text_for_node(rn)
|
3093
|
+
else
|
3094
|
+
move_in_bounds = false
|
3095
|
+
# We also check to see if the text is outside the edges of this widget
|
3096
|
+
if text.x < @x or text.right_edge > right_edge
|
3097
|
+
move_in_bounds = true
|
3098
|
+
elsif text.y < @y or text.bottom_edge > bottom_edge
|
3099
|
+
move_in_bounds = true
|
3100
|
+
end
|
3101
|
+
if move_in_bounds
|
3102
|
+
debug("#{text.label} was out of bounds")
|
3103
|
+
move_text_for_node(rn)
|
3104
|
+
end
|
3105
|
+
end
|
3106
|
+
end
|
3107
|
+
end
|
3108
|
+
end
|
3109
|
+
|
3110
|
+
def move_text_for_node(rendered_node)
|
3111
|
+
text = rendered_node.get_text_widget
|
3112
|
+
if text.nil?
|
3113
|
+
return
|
3114
|
+
end
|
3115
|
+
radians_between_attempts = DEG_360 / 24
|
3116
|
+
current_radians = 0.05
|
3117
|
+
done = false
|
3118
|
+
while not done
|
3119
|
+
# Use radians to spread the other nodes around the center node
|
3120
|
+
# TODO base the distance off of scale
|
3121
|
+
text_x = rendered_node.center_x + ((rendered_node.width / 2) * Math.cos(current_radians))
|
3122
|
+
text_y = rendered_node.center_y - ((rendered_node.height / 2) * Math.sin(current_radians))
|
3123
|
+
if text_x < @x
|
3124
|
+
text_x = @x + 1
|
3125
|
+
elsif text_x > right_edge - 20
|
3126
|
+
text_x = right_edge - 20
|
918
3127
|
end
|
3128
|
+
if text_y < @y
|
3129
|
+
text_y = @y + 1
|
3130
|
+
elsif text_y > bottom_edge - 26
|
3131
|
+
text_y = bottom_edge - 26
|
3132
|
+
end
|
3133
|
+
text.x = text_x
|
3134
|
+
text.y = text_y
|
3135
|
+
current_radians = current_radians + radians_between_attempts
|
3136
|
+
if overlaps_with_a_node(text)
|
3137
|
+
# check for done
|
3138
|
+
if current_radians > DEG_360
|
3139
|
+
done = true
|
3140
|
+
error("ERROR: could not find a spot to put the text")
|
3141
|
+
end
|
3142
|
+
else
|
3143
|
+
done = true
|
3144
|
+
end
|
3145
|
+
end
|
3146
|
+
end
|
3147
|
+
|
3148
|
+
def overlaps_with_a_node(text)
|
3149
|
+
@rendered_nodes.values.each do |rn|
|
3150
|
+
if text.label == rn.label
|
3151
|
+
# don't compare to yourself
|
3152
|
+
else
|
3153
|
+
if rn.overlaps_with(text)
|
3154
|
+
return true
|
3155
|
+
end
|
3156
|
+
end
|
3157
|
+
end
|
3158
|
+
false
|
3159
|
+
end
|
3160
|
+
|
3161
|
+
def set_tree_recursive(current_node, start_x, end_x, y_level)
|
3162
|
+
# Draw the current node, and then recursively divide up
|
3163
|
+
# and call again for each of the children
|
3164
|
+
if current_node.visited
|
3165
|
+
return
|
3166
|
+
end
|
3167
|
+
current_node.visited = true
|
3168
|
+
|
3169
|
+
if @gui_theme.use_icons
|
3170
|
+
@rendered_nodes[current_node.name] = NodeIconWidget.new(
|
3171
|
+
x_pixel_to_screen(start_x + ((end_x - start_x) / 2)),
|
3172
|
+
y_pixel_to_screen(y_level),
|
3173
|
+
current_node,
|
3174
|
+
get_node_color(current_node))
|
3175
|
+
else
|
3176
|
+
@rendered_nodes[current_node.name] = NodeWidget.new(
|
3177
|
+
x_pixel_to_screen(start_x + ((end_x - start_x) / 2)),
|
3178
|
+
y_pixel_to_screen(y_level),
|
3179
|
+
current_node,
|
3180
|
+
get_node_color(current_node))
|
3181
|
+
end
|
3182
|
+
|
3183
|
+
number_of_child_nodes = current_node.outputs.size
|
3184
|
+
if number_of_child_nodes == 0
|
3185
|
+
return
|
3186
|
+
end
|
3187
|
+
width_for_each_child_tree = (end_x - start_x) / number_of_child_nodes
|
3188
|
+
start_child_x = start_x + 5
|
3189
|
+
|
3190
|
+
current_node.outputs.each do |child|
|
3191
|
+
if child.is_a? Edge
|
3192
|
+
child = child.destination
|
3193
|
+
end
|
3194
|
+
set_tree_recursive(child, start_child_x, start_child_x + width_for_each_child_tree - 1, y_level + 40)
|
3195
|
+
start_child_x = start_child_x + width_for_each_child_tree
|
919
3196
|
end
|
920
3197
|
end
|
921
3198
|
|
922
|
-
def
|
3199
|
+
def set_all_nodes_for_display
|
3200
|
+
@visible_data_nodes = @graph.node_map
|
3201
|
+
@rendered_nodes = {}
|
3202
|
+
populate_rendered_nodes
|
3203
|
+
if @size_by_connections
|
3204
|
+
scale_node_size
|
3205
|
+
end
|
3206
|
+
prevent_text_overlap
|
3207
|
+
end
|
3208
|
+
|
3209
|
+
def get_node_color(node)
|
3210
|
+
color_tag = node.get_tag(COLOR_TAG)
|
3211
|
+
if color_tag.nil?
|
3212
|
+
return @color
|
3213
|
+
end
|
3214
|
+
color_tag
|
3215
|
+
end
|
3216
|
+
|
3217
|
+
def set_center_node(center_node, max_depth = -1)
|
923
3218
|
# Determine the list of nodes to draw
|
924
|
-
@
|
925
|
-
@visible_data_nodes = @graph.
|
3219
|
+
@graph.reset_visited
|
3220
|
+
@visible_data_nodes = @graph.traverse_and_collect_nodes(center_node, max_depth)
|
926
3221
|
|
927
3222
|
# Convert the data nodes to rendered nodes
|
928
3223
|
# Start by putting the center node in the center, then draw others around it
|
929
3224
|
@rendered_nodes = {}
|
930
|
-
@
|
931
|
-
|
3225
|
+
if @gui_theme.use_icons
|
3226
|
+
@rendered_nodes[center_node.name] = NodeIconWidget.new(
|
3227
|
+
center_x, center_y, center_node, get_node_color(center_node))
|
3228
|
+
else
|
3229
|
+
@rendered_nodes[center_node.name] = NodeWidget.new(center_x, center_y,
|
3230
|
+
center_node, get_node_color(center_node), get_node_color(center_node))
|
3231
|
+
end
|
3232
|
+
|
3233
|
+
populate_rendered_nodes(center_node)
|
3234
|
+
|
3235
|
+
if @size_by_connections
|
3236
|
+
scale_node_size
|
3237
|
+
end
|
3238
|
+
prevent_text_overlap
|
3239
|
+
end
|
932
3240
|
|
3241
|
+
def populate_rendered_nodes(center_node = nil)
|
933
3242
|
# Spread out the other nodes around the center node
|
934
|
-
# going in a circle
|
935
|
-
|
936
|
-
|
937
|
-
|
3243
|
+
# going in a circle at each depth level
|
3244
|
+
stats = Stats.new("NodesPerDepth")
|
3245
|
+
@visible_data_nodes.values.each do |n|
|
3246
|
+
stats.increment(n.depth)
|
3247
|
+
end
|
3248
|
+
current_radians = []
|
3249
|
+
radians_increment = []
|
3250
|
+
(1..4).each do |n|
|
3251
|
+
number_of_nodes_at_depth = stats.count(n)
|
3252
|
+
radians_increment[n] = DEG_360 / number_of_nodes_at_depth.to_f
|
3253
|
+
current_radians[n] = 0.05
|
3254
|
+
end
|
3255
|
+
|
3256
|
+
padding = 100
|
3257
|
+
size_of_x_band = (@width - padding) / 6
|
3258
|
+
size_of_y_band = (@height - padding) / 6
|
3259
|
+
random_x = size_of_x_band / 8
|
3260
|
+
random_y = size_of_y_band / 8
|
3261
|
+
half_random_x = random_x / 2
|
3262
|
+
half_random_y = random_y / 2
|
3263
|
+
|
3264
|
+
# Precompute the band center points
|
3265
|
+
# then reference by the scale or depth values below
|
3266
|
+
band_center_x = padding + (size_of_x_band / 2)
|
3267
|
+
band_center_y = padding + (size_of_y_band / 2)
|
3268
|
+
# depth 1 [0] - center node, distance should be zero. Should be only one
|
3269
|
+
# depth 2 [1] - band one
|
3270
|
+
# depth 3 [2] - band two
|
3271
|
+
# depth 4 [3] - band three
|
3272
|
+
bands_x = [0, band_center_x]
|
3273
|
+
bands_x << band_center_x + size_of_x_band
|
3274
|
+
bands_x << band_center_x + size_of_x_band + size_of_x_band
|
3275
|
+
|
3276
|
+
bands_y = [0, band_center_y]
|
3277
|
+
bands_y << band_center_y + size_of_y_band
|
3278
|
+
bands_y << band_center_y + size_of_y_band + size_of_y_band
|
938
3279
|
|
939
3280
|
@visible_data_nodes.each do |node_name, data_node|
|
940
|
-
|
941
|
-
|
942
|
-
|
3281
|
+
process_this_node = true
|
3282
|
+
if center_node
|
3283
|
+
if node_name == center_node.name
|
3284
|
+
process_this_node = false
|
3285
|
+
end
|
3286
|
+
end
|
3287
|
+
if process_this_node
|
3288
|
+
scale_to_use = 1
|
3289
|
+
if stats.count(1) > 0 and stats.count(2) == 0
|
3290
|
+
# if all nodes are depth 1, then size everything
|
3291
|
+
# as a small node
|
3292
|
+
elsif data_node.depth < 4
|
3293
|
+
scale_to_use = 5 - data_node.depth
|
3294
|
+
end
|
3295
|
+
if @is_explorer
|
3296
|
+
# TODO Layer the nodes around the center
|
3297
|
+
# We need a better multiplier based on the height and width
|
3298
|
+
# max distance x would be (@width / 2) - padding
|
3299
|
+
# divide that into three regions, layer 2, 3, and 4
|
3300
|
+
# get the center point for each of these regions, and do a random from there
|
3301
|
+
# scale to use determines which of the regions
|
3302
|
+
band_index = 4 - scale_to_use
|
3303
|
+
distance_from_center_x = bands_x[band_index] + rand(random_x) - half_random_x
|
3304
|
+
distance_from_center_y = bands_y[band_index] + rand(random_y) - half_random_y
|
3305
|
+
else
|
3306
|
+
distance_from_center_x = 80 + rand(200)
|
3307
|
+
distance_from_center_y = 40 + rand(100)
|
3308
|
+
end
|
943
3309
|
# Use radians to spread the other nodes around the center node
|
944
|
-
|
945
|
-
|
946
|
-
|
3310
|
+
radians_to_use = current_radians[data_node.depth]
|
3311
|
+
radians_to_use = radians_to_use + (rand(radians_increment[data_node.depth]) / 2)
|
3312
|
+
current_radians[data_node.depth] = current_radians[data_node.depth] + radians_increment[data_node.depth]
|
3313
|
+
node_x = center_x + (distance_from_center_x * Math.cos(radians_to_use))
|
3314
|
+
node_y = center_y - (distance_from_center_y * Math.sin(radians_to_use))
|
947
3315
|
if node_x < @x
|
948
3316
|
node_x = @x + 1
|
949
3317
|
elsif node_x > right_edge - 20
|
@@ -954,19 +3322,31 @@ module Wads
|
|
954
3322
|
elsif node_y > bottom_edge - 26
|
955
3323
|
node_y = bottom_edge - 26
|
956
3324
|
end
|
957
|
-
current_radians = current_radians + radians_between_nodes
|
958
3325
|
|
959
3326
|
# Note we can link between data nodes and rendered nodes using the node name
|
960
3327
|
# We have a map of each
|
961
|
-
@
|
3328
|
+
if @gui_theme.use_icons
|
3329
|
+
@rendered_nodes[data_node.name] = NodeIconWidget.new(
|
3330
|
+
node_x,
|
3331
|
+
node_y,
|
962
3332
|
data_node,
|
3333
|
+
get_node_color(data_node),
|
3334
|
+
scale_to_use,
|
3335
|
+
@is_explorer)
|
3336
|
+
else
|
3337
|
+
@rendered_nodes[data_node.name] = NodeWidget.new(
|
963
3338
|
node_x,
|
964
3339
|
node_y,
|
965
|
-
|
966
|
-
data_node
|
967
|
-
|
3340
|
+
data_node,
|
3341
|
+
get_node_color(data_node),
|
3342
|
+
scale_to_use,
|
3343
|
+
@is_explorer)
|
3344
|
+
end
|
968
3345
|
end
|
969
3346
|
end
|
3347
|
+
@rendered_nodes.values.each do |rn|
|
3348
|
+
rn.base_z = @base_z
|
3349
|
+
end
|
970
3350
|
end
|
971
3351
|
|
972
3352
|
def render
|
@@ -974,6 +3354,7 @@ module Wads
|
|
974
3354
|
@rendered_nodes.values.each do |vn|
|
975
3355
|
vn.draw
|
976
3356
|
end
|
3357
|
+
|
977
3358
|
# Draw the connections between nodes
|
978
3359
|
@visible_data_nodes.values.each do |data_node|
|
979
3360
|
data_node.outputs.each do |connected_data_node|
|
@@ -985,9 +3366,16 @@ module Wads
|
|
985
3366
|
if connected_rendered_node.nil?
|
986
3367
|
# Don't draw if it is not currently visible
|
987
3368
|
else
|
988
|
-
|
989
|
-
|
990
|
-
|
3369
|
+
if @is_explorer and (rendered_node.is_background or connected_rendered_node.is_background)
|
3370
|
+
# Use a dull gray color for the line
|
3371
|
+
Gosu::draw_line rendered_node.center_x, rendered_node.center_y, COLOR_LIGHT_GRAY,
|
3372
|
+
connected_rendered_node.center_x, connected_rendered_node.center_y, COLOR_LIGHT_GRAY,
|
3373
|
+
relative_z_order(Z_ORDER_GRAPHIC_ELEMENTS)
|
3374
|
+
else
|
3375
|
+
Gosu::draw_line rendered_node.center_x, rendered_node.center_y, rendered_node.graphics_color,
|
3376
|
+
connected_rendered_node.center_x, connected_rendered_node.center_y, connected_rendered_node.graphics_color,
|
3377
|
+
relative_z_order(Z_ORDER_GRAPHIC_ELEMENTS)
|
3378
|
+
end
|
991
3379
|
end
|
992
3380
|
end
|
993
3381
|
end
|