minigl 2.2.2 → 2.2.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c09ee988024d7d8ae2fc35518a5ff3a3f26e9c98753895993efabc573619b79e
4
- data.tar.gz: 67a7fdaf0290b31f53421708a23ec26e3e61c0b123b7f5e0d184338c4c88159f
3
+ metadata.gz: 1992d50afbe577ea889048ecee2c5349f600949adeadf49ffeec5eb833956507
4
+ data.tar.gz: dfac8a52254501a07b0518cb70d0d776b330cfaeb13c81c410eaae38a0a9fc0e
5
5
  SHA512:
6
- metadata.gz: 0be725ee0c7a6cb07ed7fdbfbba527a6920bea971ed13b52326ac3f35a22d619399e0e3eef21f3e236441971f2f199ff9a939e7face5649a8adb335c9a7d8834
7
- data.tar.gz: efb4a5e10e4d54036b4d6df3852fdef80c11f18f00ac4fe9fe33df0c2b6957b2c8363902c1a4ce9289bc7257ef517f3942b761d1e36737874923762a9b707589
6
+ metadata.gz: fb17f314f857fc4ae5054462b1d59fac085831cd922ecbabf2e90c598a3067f25ccd061efb72ca372a3cf9057f64dc1a368c34c62e6209b668fc912edede3ef1
7
+ data.tar.gz: 95e04aa6b389b558cc8aae26809dc85d019aca7ca472c9ea62183daf0e4ed5c7ccba8a7c11c0d5ecec820cdaa5779eb4d6a7641a5d4f8e26e7c77b7aa1a6c32b
data/LICENSE CHANGED
@@ -1,19 +1,19 @@
1
- Copyright (c) 2014 Victor David Santos
2
-
3
- Permission is hereby granted, free of charge, to any person obtaining a copy
4
- of this software and associated documentation files (the "Software"), to deal
5
- in the Software without restriction, including without limitation the rights
6
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
- copies of the Software, and to permit persons to whom the Software is
8
- furnished to do so, subject to the following conditions:
9
-
10
- The above copyright notice and this permission notice shall be included in
11
- all copies or substantial portions of the Software.
12
-
13
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
- THE SOFTWARE.
1
+ Copyright (c) 2014 Victor David Santos
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md CHANGED
@@ -1,36 +1,35 @@
1
- # MiniGL
2
-
3
- MiniGL is a minimal **2D Game** Library, available as a Ruby gem, and built on
4
- top of the [Gosu](http://www.libgosu.org/) gem.
5
-
6
- It provides the following features:
7
-
8
- * Resource management (images, sounds, ...)
9
- * Input manipulation (keyboard, mouse, ...)
10
- * UI (text, buttons, text fields, ...)
11
- * Basic physics and collision checking
12
- * Animated objects
13
-
14
- More functionalities are coming. Feel free to contribute! You can send feedback
15
- to victordavidsantos@gmail.com.
16
-
17
- ## Installing
18
-
19
- MiniGL was built on top of the Gosu gem. This gem has its own dependencies for
20
- compiling extensions. Visit
21
- [this page](https://github.com/jlnr/gosu/wiki/Getting-Started-on-Linux) for
22
- details.
23
-
24
- After installing the Gosu dependencies, you can just `gem install minigl`.
25
-
26
- ## Documentation
27
-
28
- * The library is 100% RDoc-documented [here](http://www.rubydoc.info/gems/minigl).
29
- * The [wiki](https://github.com/victords/minigl/wiki) is a work in progress with tutorials and examples.
30
- * Test package and examples aren't complete!
31
-
32
- ## Version 2.2.1
33
-
34
- * Added the `Label` class.
35
- * Made the `x`, `y`, `w` and `h` of `Panel`s accessible.
36
- * Fixed bug in `TextField` with starting text.
1
+ # MiniGL
2
+
3
+ MiniGL is a minimal **2D Game** Library, available as a Ruby gem, and built on
4
+ top of the [Gosu](http://www.libgosu.org/) gem.
5
+
6
+ It provides the following features:
7
+
8
+ * Resource management (images, sounds, ...)
9
+ * Input manipulation (keyboard, mouse, ...)
10
+ * UI (text, buttons, text fields, ...)
11
+ * Basic physics and collision checking
12
+ * Animated objects
13
+
14
+ More functionalities are coming. Feel free to contribute! You can send feedback
15
+ to victordavidsantos@gmail.com.
16
+
17
+ ## Installing
18
+
19
+ MiniGL was built on top of the Gosu gem. This gem has its own dependencies for
20
+ compiling extensions. Visit
21
+ [this page](https://github.com/jlnr/gosu/wiki/Getting-Started-on-Linux) for
22
+ details.
23
+
24
+ After installing the Gosu dependencies, you can just `gem install minigl`.
25
+
26
+ ## Documentation
27
+
28
+ * The library is 100% RDoc-documented [here](http://www.rubydoc.info/gems/minigl).
29
+ * The [wiki](https://github.com/victords/minigl/wiki) is a work in progress with tutorials and examples.
30
+ * Test package and examples aren't complete!
31
+
32
+ ## Version 2.2.3
33
+
34
+ * `TextHelper` methods now allow markup (bold, italic and colors).
35
+ * Removed the deprecation warnings from `TextHelper` and forms components' methods.
data/Rakefile CHANGED
@@ -1,11 +1,11 @@
1
- require 'rake/testtask'
2
-
3
- Rake::TestTask.new do |t|
4
- t.libs << "test"
5
- t.test_files = FileList['test/*_tests.rb']
6
- t.verbose = true
7
- end
8
-
9
- task :doc do |t|
10
- sh "rdoc lib/minigl/*.rb"
11
- end
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new do |t|
4
+ t.libs << "test"
5
+ t.test_files = FileList['test/*_tests.rb']
6
+ t.verbose = true
7
+ end
8
+
9
+ task :doc do |t|
10
+ sh "rdoc lib/minigl/*.rb"
11
+ end
@@ -1,4 +1,4 @@
1
- require_relative 'minigl/game_object'
2
- require_relative 'minigl/map'
3
- require_relative 'minigl/text'
4
- require_relative 'minigl/forms'
1
+ require_relative 'minigl/game_object'
2
+ require_relative 'minigl/map'
3
+ require_relative 'minigl/text'
4
+ require_relative 'minigl/forms'
@@ -1,1485 +1,1485 @@
1
- require_relative 'global'
2
-
3
- module MiniGL
4
- module FormUtils # :nodoc:
5
- def self.check_anchor(anchor, x, y, w, h, area_w = G.window.width, area_h = G.window.height)
6
- if anchor
7
- case anchor
8
- when /^top(_center)?$|^north$/i then anchor_alias = :top_center; x += (area_w - w) / 2
9
- when /^top_right$|^northeast$/i then anchor_alias = :top_right; x = area_w - w - x
10
- when /^(center_)?left$|^west$/i then anchor_alias = :center_left; y += (area_h - h) / 2
11
- when /^center$/i then anchor_alias = :center; x += (area_w - w) / 2; y += (area_h - h) / 2
12
- when /^(center_)?right$|^east$/i then anchor_alias = :center_right; x = area_w - w - x; y += (area_h - h) / 2
13
- when /^bottom_left$|^southwest$/i then anchor_alias = :bottom_left; y = area_h - h - y
14
- when /^bottom(_center)?$|^south$/i then anchor_alias = :bottom_center; x += (area_w - w) / 2; y = area_h - h - y
15
- when /^bottom_right$|^southeast$/i then anchor_alias = :bottom_right; x = area_w - w - x; y = area_h - h - y
16
- else anchor_alias = :top_left
17
- end
18
- else
19
- anchor_alias = :top_left
20
- end
21
- [anchor_alias, x, y]
22
- end
23
- end
24
-
25
- # This class is an abstract ancestor for all form components (Button,
26
- # ToggleButton, TextField, DropDownList and ProgressBar).
27
- class Component
28
- # The horizontal coordinate of the component
29
- attr_reader :x
30
-
31
- # The vertical coordinate of the component
32
- attr_reader :y
33
-
34
- # The width of the component
35
- attr_reader :w
36
-
37
- # The height of the component
38
- attr_reader :h
39
-
40
- attr_reader :anchor, :anchor_offset_x, :anchor_offset_y # :nodoc:
41
-
42
- # Determines whether the control is enabled, i.e., will process user input.
43
- attr_accessor :enabled
44
-
45
- # Determines whether the control is visible, i.e., will be drawn in the
46
- # screen and process user input, if enabled.
47
- attr_accessor :visible
48
-
49
- # A container for any parameters to be passed to the code blocks called
50
- # in response to events of the control (click of a button, change of the
51
- # text in a text field, etc.). More detail can be found in the constructor
52
- # for each specific component class.
53
- attr_accessor :params
54
-
55
- def initialize(x, y, font, text, text_color, disabled_text_color) # :nodoc:
56
- @x = x
57
- @y = y
58
- @font = font
59
- @text = text
60
- @text_color = text_color
61
- @disabled_text_color = disabled_text_color
62
- @enabled = @visible = true
63
- end
64
-
65
- def update; end # :nodoc:
66
-
67
- # Sets the position of the component.
68
- # Parameters:
69
- # [x] The new x coordinate.
70
- # [y] The new y coordinate.
71
- def set_position(x, y)
72
- @x = x; @y = y
73
- end
74
- end
75
-
76
- # Represents a container of form components.
77
- class Panel
78
- # The horizontal position of the panel from the top left corner of the window.
79
- attr_reader :x
80
-
81
- # The vertical position of the panel from the top left corner of the window.
82
- attr_reader :y
83
-
84
- # The width of the panel in pixels.
85
- attr_reader :w
86
-
87
- # The height of the panel in pixels.
88
- attr_reader :h
89
-
90
- # Whether the components inside this panel are enabled.
91
- attr_reader :enabled
92
-
93
- # Gets or sets whether the panel (and thus all components inside it) are visible.
94
- attr_accessor :visible
95
-
96
- # Creates a new Panel.
97
- # Parameters:
98
- # [x] The horizontal coordinate of the top-left corner of the panel, or the horizontal offset from the anchor, if provided.
99
- # [y] The vertical coordinate of the top-left corner of the panel, or the vertical offset from the anchor, if provided.
100
- # [w] The width of the panel, in pixels.
101
- # [h] The height of the panel, in pixels.
102
- # [controls] An array of <code>Component</code>s that will be initially inside this panel.
103
- # [img] Identifier of the image for the panel (see details in +Res::img+).
104
- # [img_mode] Mode to scale the image. If +:normal+ (default), the image will be loaded as a single image and scaled to fit the entire size of the panel;
105
- # if +:tiled+, the image will be loaded as a 3x3 spritesheet, where the "corner" images (i.e., indices 0, 2, 6 and 8) will be scaled by the +scale_x+ and +scale_y+ parameters,
106
- # the "border" images (indices 1, 3, 5 and 7) will be stretched in the corresponding direction (indices 1 and 7 will be horizontally stretched and indices 3 and 5, vertically),
107
- # and the "center" image (index 4) will be stretched in both directions, as needed, to fill the width and height of the panel.
108
- # [retro] Whether the image should be loaded in retro mode.
109
- # [scale_x] The fixed horizontal scale for "corner" and left and right "border" images (if +img_mode+ is +:tiled+).
110
- # [scale_y] The fixed vertical scale for "corner" and top and bottom "border" images (if +img_mode+ is +:tiled+).
111
- # [anchor] An alias for a predefined position of the window to be used as "anchor", i.e., reference for the positioning of the panel.
112
- # Following are the valid values and a description of the corresponding position if +x+ and +y+ are 0 (these will be offsets from the reference position):
113
- # * +:north+ or +:top+ or +:top_center+: the panel will be horizontally centered and its top will be at the top of the window.
114
- # * +:northeast+ or +:top_right+: the top-right corner of the panel will meet the top-right corner of the window.
115
- # * +:west+ or +:left+ or +:center_left+: the panel will be vertically centered and its left edge will be at the left edge of the window.
116
- # * +:center+: the panel will be horizontally and vertically centered on the window.
117
- # * +:east+ or +:right+ or +:center_right+: the panel will be vertically centered and its right edge will be at the right edge of the window.
118
- # * +:southwest+ or +:bottom_left+: the bottom-left corner of the panel will meet the bottom-left corner of the window.
119
- # * +:south+ or +:bottom+ or +:bottom_center+: the panel will be horizontally centered and its bottom will be at the bottom of the window.
120
- # * +:southeast+ or +:bottom_right+: the bottom-right corner of the panel will meet the bottom-right corner of the window.
121
- # If a value is not provided, the reference is the top-left corner of the screen.
122
- # Components added as children of <code>Panel</code>s use the panel's coordinates as reference instead of the window.
123
- def initialize(x, y, w, h, controls = [], img = nil, img_mode = :normal, retro = nil, scale_x = 1, scale_y = 1, anchor = nil)
124
- _, x, y = FormUtils.check_anchor(anchor, x, y, w, h)
125
- @x = x; @y = y; @w = w; @h = h
126
- @controls = controls
127
- controls.each do |c|
128
- _, x, y = FormUtils.check_anchor(c.anchor, c.anchor_offset_x, c.anchor_offset_y, c.w, c.h, @w, @h)
129
- c.set_position(@x + x, @y + y)
130
- end
131
-
132
- if img
133
- retro = Res.retro_images if retro.nil?
134
- if img_mode == :tiled
135
- @img = Res.imgs(img, 3, 3, true, '.png', retro, true)
136
- @scale_x = scale_x
137
- @scale_y = scale_y
138
- @tile_w = @img[0].width * @scale_x
139
- @tile_h = @img[0].height * @scale_y
140
- @draw_center_x = @w > 2 * @tile_w
141
- @draw_center_y = @h > 2 * @tile_h
142
- @center_scale_x = (@w - 2 * @tile_w).to_f / @tile_w * @scale_x
143
- @center_scale_y = (@h - 2 * @tile_h).to_f / @tile_h * @scale_y
144
- else
145
- @img = Res.img(img, true, false, '.png', retro)
146
- end
147
- end
148
-
149
- @visible = @enabled = true
150
- end
151
-
152
- # Updates all child components of this panel.
153
- def update
154
- @controls.each(&:update)
155
- end
156
-
157
- # Enables or disables all child components of this panel.
158
- # Parameters:
159
- # [value] Whether the components should be enabled.
160
- def enabled=(value)
161
- @enabled = value
162
- @controls.each { |c| c.enabled = value }
163
- end
164
-
165
- # Adds a component to this panel.
166
- # Parameters:
167
- # [c] The component to add.
168
- def add_component(c)
169
- _, x, y = FormUtils.check_anchor(c.anchor, c.anchor_offset_x, c.anchor_offset_y, c.w, c.h, @w, @h)
170
- c.set_position(@x + x, @y + y)
171
- @controls << c
172
- end
173
-
174
- # Draws the panel and all its child components.
175
- # Parameters:
176
- # [alpha] The opacity of the panel (0 = fully transparent, 255 = fully opaque).
177
- # [z_index] The z-index to draw the panel.
178
- # [color] The color to apply as filter to the panel image and to all child components' images as well.
179
- def draw(alpha = 255, z_index = 0, color = 0xffffff)
180
- return unless @visible
181
-
182
- c = (alpha << 24) | color
183
- if @img
184
- if @img.is_a?(Array)
185
- @img[0].draw(@x, @y, z_index, @scale_x, @scale_y, c)
186
- @img[1].draw(@x + @tile_w, @y, z_index, @center_scale_x, @scale_y, c) if @draw_center_x
187
- @img[2].draw(@x + @w - @tile_w, @y, z_index, @scale_x, @scale_y, c)
188
- @img[3].draw(@x, @y + @tile_h, z_index, @scale_x, @center_scale_y, c) if @draw_center_y
189
- @img[4].draw(@x + @tile_w, @y + @tile_h, z_index, @center_scale_x, @center_scale_y, c) if @draw_center_x && @draw_center_y
190
- @img[5].draw(@x + @w - @tile_w, @y + @tile_h, z_index, @scale_x, @center_scale_y, c) if @draw_center_y
191
- @img[6].draw(@x, @y + @h - @tile_h, z_index, @scale_x, @scale_y, c)
192
- @img[7].draw(@x + @tile_w, @y + @h - @tile_h, z_index, @center_scale_x, @scale_y, c) if @draw_center_x
193
- @img[8].draw(@x + @w - @tile_w, @y + @h - @tile_h, z_index, @scale_x, @scale_y, c)
194
- else
195
- @img.draw(@x, @y, z_index, @w.to_f / @img.width, @h.to_f / @img.height)
196
- end
197
- end
198
-
199
- @controls.each { |k| k.draw(alpha, z_index, color) if k.visible }
200
- end
201
- end
202
-
203
- # This class represents a button.
204
- class Button < Component
205
- # The current state of the button.
206
- attr_reader :state
207
-
208
- # The text of the button.
209
- attr_accessor :text
210
-
211
- # Creates a button.
212
- #
213
- # Parameters:
214
- # [x] The x-coordinate where the button will be drawn in the screen.
215
- # [y] The y-coordinate where the button will be drawn in the screen.
216
- # [font] The <code>Gosu::Font</code> object that will be used to draw the
217
- # button text.
218
- # [text] The button text. Can be +nil+ or empty.
219
- # [img] A spritesheet containing four images in a column, representing,
220
- # from top to bottom, the default state, the hover state (when the
221
- # mouse is over the button), the pressed state (when the mouse
222
- # button is down and the cursor is over the button) and the disabled
223
- # state. If +nil+, the +width+ and +height+ parameters must be
224
- # provided.
225
- # [text_color] Color of the button text, in hexadecimal RRGGBB format.
226
- # [disabled_text_color] Color of the button text, when it's disabled, in
227
- # hexadecimal RRGGBB format.
228
- # [over_text_color] Color of the button text, when the cursor is over it
229
- # (hexadecimal RRGGBB).
230
- # [down_text_color] Color of the button text, when it is pressed
231
- # (hexadecimal RRGGBB).
232
- # [center_x] Whether the button text should be horizontally centered in its
233
- # area (the area is defined by the image size, if an image is
234
- # given, or by the +width+ and +height+ parameters, otherwise).
235
- # [center_y] Whether the button text should be vertically centered in its
236
- # area (the area is defined by the image size, if an image is
237
- # given, or by the +width+ and +height+ parameters, otherwise).
238
- # [margin_x] The x offset, from the button x-coordinate, to draw the text.
239
- # This parameter is used only if +center+ is false.
240
- # [margin_y] The y offset, from the button y-coordinate, to draw the text.
241
- # This parameter is used only if +center+ is false.
242
- # [width] Width of the button clickable area. This parameter is used only
243
- # if +img+ is +nil+.
244
- # [height] Height of the button clickable area. This parameter is used
245
- # only if +img+ is +nil+.
246
- # [params] An object containing any parameters you want passed to the
247
- # +action+ block. When the button is clicked, the following is
248
- # called:
249
- # @action.call @params
250
- # Note that this doesn't force you to declare a block that takes
251
- # parameters.
252
- # [retro] Whether the image should be loaded with the 'retro' option set
253
- # (see +Gosu::Image+ for details). If the value is omitted, the
254
- # +Res.retro_images+ value will be used.
255
- # [scale_x] Horizontal scale to draw the component with.
256
- # [scale_y] Vertical scale to draw the component with.
257
- # [anchor] See parameter with the same name in <code>Panel#initialize</code> for details.
258
- # [action] The block of code executed when the button is clicked (or by
259
- # calling the +click+ method).
260
- #
261
- # *Obs.:* This method accepts named parameters, but +x+ and +y+ are
262
- # mandatory (also, +img+ is mandatory when +width+ and +height+ are not
263
- # provided, and vice-versa).
264
- def initialize(x, y = nil, font = nil, text = nil, img = nil,
265
- text_color = 0, disabled_text_color = 0, over_text_color = 0, down_text_color = 0,
266
- center_x = true, center_y = true, margin_x = 0, margin_y = 0, width = nil, height = nil,
267
- params = nil, retro = nil, scale_x = 1, scale_y = 1, anchor = nil, &action)
268
- if x.is_a? Hash
269
- y = x[:y]
270
- font = x[:font]
271
- text = x[:text]
272
- img = x[:img]
273
- text_color = x.fetch(:text_color, 0)
274
- disabled_text_color = x.fetch(:disabled_text_color, 0)
275
- over_text_color = x.fetch(:over_text_color, 0)
276
- down_text_color = x.fetch(:down_text_color, 0)
277
- center_x = x.fetch(:center_x, true)
278
- center_y = x.fetch(:center_y, true)
279
- margin_x = x.fetch(:margin_x, 0)
280
- margin_y = x.fetch(:margin_y, 0)
281
- width = x.fetch(:width, nil)
282
- height = x.fetch(:height, nil)
283
- params = x.fetch(:params, nil)
284
- retro = x.fetch(:retro, nil)
285
- scale_x = x.fetch(:scale_x, 1)
286
- scale_y = x.fetch(:scale_y, 1)
287
- anchor = x.fetch(:anchor, nil)
288
- x = x[:x]
289
- end
290
-
291
- retro = Res.retro_images if retro.nil?
292
- @scale_x = scale_x
293
- @scale_y = scale_y
294
- @img =
295
- if img; Res.imgs img, 1, 4, true, '.png', retro
296
- else; nil; end
297
- @w =
298
- if img; @img[0].width * @scale_x
299
- else; width * @scale_x; end
300
- @h =
301
- if img; @img[0].height * @scale_y
302
- else; height * @scale_y; end
303
-
304
- @anchor_offset_x = x; @anchor_offset_y = y
305
- @anchor, x, y = FormUtils.check_anchor(anchor, x, y, @w, @h)
306
-
307
- super x, y, font, text, text_color, disabled_text_color
308
- @over_text_color = over_text_color
309
- @down_text_color = down_text_color
310
- if center_x; @text_x = x + @w / 2 if @w
311
- else; @text_x = x + margin_x * @scale_x; end
312
- if center_y; @text_y = y + @h / 2 if @h
313
- else; @text_y = y + margin_y * @scale_y; end
314
- @center_x = center_x
315
- @center_y = center_y
316
- @action = action
317
- @params = params
318
-
319
- @state = :up
320
- @img_index = @enabled ? 0 : 3
321
- end
322
-
323
- # Updates the button, checking the mouse movement and buttons to define
324
- # the button state.
325
- def update
326
- return unless @enabled and @visible
327
-
328
- mouse_over = Mouse.over? @x, @y, @w, @h
329
- mouse_press = Mouse.button_pressed? :left
330
- mouse_rel = Mouse.button_released? :left
331
-
332
- if @state == :up
333
- if mouse_over
334
- @img_index = 1
335
- @state = :over
336
- else
337
- @img_index = 0
338
- end
339
- elsif @state == :over
340
- if not mouse_over
341
- @img_index = 0
342
- @state = :up
343
- elsif mouse_press
344
- @img_index = 2
345
- @state = :down
346
- else
347
- @img_index = 1
348
- end
349
- elsif @state == :down
350
- if not mouse_over
351
- @img_index = 0
352
- @state = :down_out
353
- elsif mouse_rel
354
- @img_index = 1
355
- @state = :over
356
- click
357
- else
358
- @img_index = 2
359
- end
360
- else # :down_out
361
- if mouse_over
362
- @img_index = 2
363
- @state = :down
364
- elsif mouse_rel
365
- @img_index = 0
366
- @state = :up
367
- else
368
- @img_index = 0
369
- end
370
- end
371
- end
372
-
373
- # Executes the button click action.
374
- def click
375
- @action.call @params if @action
376
- end
377
-
378
- # Sets the position of the button in the screen.
379
- #
380
- # Parameters:
381
- # [x] The new x-coordinate for the button.
382
- # [y] The new y-coordinate for the button.
383
- def set_position(x, y)
384
- if @center_x; @text_x = x + @w / 2
385
- else; @text_x += x - @x; end
386
- if @center_y; @text_y = y + @h / 2
387
- else; @text_y += y - @y; end
388
- @x = x; @y = y
389
- end
390
-
391
- # Draws the button in the screen.
392
- #
393
- # Parameters:
394
- # [alpha] The opacity with which the button will be drawn. Allowed values
395
- # vary between 0 (fully transparent) and 255 (fully opaque).
396
- # [z_index] The z-order to draw the object. Objects with larger z-orders
397
- # will be drawn on top of the ones with smaller z-orders.
398
- # [color] Color to apply a filter to the image.
399
- def draw(alpha = 0xff, z_index = 0, color = 0xffffff)
400
- return unless @visible
401
-
402
- color = (alpha << 24) | color
403
- text_color =
404
- if @enabled
405
- if @state == :down
406
- @down_text_color
407
- else
408
- @state == :over ? @over_text_color : @text_color
409
- end
410
- else
411
- @disabled_text_color
412
- end
413
- text_color = (alpha << 24) | text_color
414
- @img[@img_index].draw @x, @y, z_index, @scale_x, @scale_y, color if @img
415
- if @text
416
- if @center_x or @center_y
417
- rel_x = @center_x ? 0.5 : 0
418
- rel_y = @center_y ? 0.5 : 0
419
- @font.draw_rel @text, @text_x, @text_y, z_index, rel_x, rel_y, @scale_x, @scale_y, text_color
420
- else
421
- @font.draw @text, @text_x, @text_y, z_index, @scale_x, @scale_y, text_color
422
- end
423
- end
424
- end
425
-
426
- def enabled=(value) # :nodoc:
427
- @enabled = value
428
- @state = :up
429
- @img_index = 3
430
- end
431
- end
432
-
433
- # This class represents a toggle button, which can be also interpreted as a
434
- # check box. It is always in one of two states, given as +true+ or +false+
435
- # by its property +checked+.
436
- class ToggleButton < Button
437
- # Defines the state of the button (returns +true+ or +false+).
438
- attr_reader :checked
439
-
440
- # Creates a ToggleButton. All parameters work the same as in Button,
441
- # except for the image, +img+, which now has to be composed of two columns
442
- # and four rows, the first column with images for the unchecked state,
443
- # and the second with images for the checked state, and for +checked+,
444
- # which defines the initial state of the ToggleButton.
445
- #
446
- # The +action+ block now will always receive a first boolean parameter
447
- # corresponding to the value of +checked+. So, if you want to pass
448
- # parameters to the block, you should declare it like this:
449
- # b = ToggleButton.new ... { |checked, params|
450
- # puts "button was checked" if checked
451
- # # do something with params
452
- # }
453
- #
454
- # *Obs.:* This method accepts named parameters, but +x+ and +y+ are
455
- # mandatory (also, +img+ is mandatory when +width+ and +height+ are not
456
- # provided, and vice-versa).
457
- def initialize(x, y = nil, font = nil, text = nil, img = nil, checked = false,
458
- text_color = 0, disabled_text_color = 0, over_text_color = 0, down_text_color = 0,
459
- center_x = true, center_y = true, margin_x = 0, margin_y = 0, width = nil, height = nil,
460
- params = nil, retro = nil, scale_x = 1, scale_y = 1, anchor = nil, &action)
461
- if x.is_a? Hash
462
- y = x[:y]
463
- font = x[:font]
464
- text = x[:text]
465
- img = x[:img]
466
- checked = x.fetch(:checked, false)
467
- text_color = x.fetch(:text_color, 0)
468
- disabled_text_color = x.fetch(:disabled_text_color, 0)
469
- over_text_color = x.fetch(:over_text_color, 0)
470
- down_text_color = x.fetch(:down_text_color, 0)
471
- center_x = x.fetch(:center_x, true)
472
- center_y = x.fetch(:center_y, true)
473
- margin_x = x.fetch(:margin_x, 0)
474
- margin_y = x.fetch(:margin_y, 0)
475
- width = x.fetch(:width, nil)
476
- height = x.fetch(:height, nil)
477
- params = x.fetch(:params, nil)
478
- retro = x.fetch(:retro, nil)
479
- scale_x = x.fetch(:scale_x, 1)
480
- scale_y = x.fetch(:scale_y, 1)
481
- anchor = x.fetch(:anchor, nil)
482
- x = x[:x]
483
- end
484
-
485
- super x, y, font, text, nil, text_color, disabled_text_color, over_text_color, down_text_color,
486
- center_x, center_y, margin_x, margin_y, 0, 0, params, retro, scale_x, scale_y, anchor, &action
487
- @img =
488
- if img; Res.imgs img, 2, 4, true, '.png', retro
489
- else; nil; end
490
- @w =
491
- if img; @img[0].width * @scale_x
492
- else; width * @scale_x; end
493
- @h =
494
- if img; @img[0].height * @scale_y
495
- else; height * @scale_y; end
496
- _, x, y = FormUtils.check_anchor(anchor, @anchor_offset_x, @anchor_offset_y, @w, @h)
497
- set_position(x, y)
498
- @text_x = x + @w / 2 if center_x
499
- @text_y = y + @h / 2 if center_y
500
- @checked = checked
501
- end
502
-
503
- # Updates the button, checking the mouse movement and buttons to define
504
- # the button state.
505
- def update
506
- return unless @enabled and @visible
507
-
508
- super
509
- @img_index *= 2
510
- @img_index += 1 if @checked
511
- end
512
-
513
- # Executes the button click action, and toggles its state. The +action+
514
- # block always receives as a first parameter +true+, if the button has
515
- # been changed to checked, or +false+, otherwise.
516
- def click
517
- @checked = !@checked
518
- @action.call @checked, @params if @action
519
- end
520
-
521
- # Sets the state of the button to the value given.
522
- #
523
- # Parameters:
524
- # [value] The state to be set (+true+ for checked, +false+ for unchecked).
525
- def checked=(value)
526
- click if value != @checked
527
- @checked = value
528
- end
529
-
530
- def enabled=(value) # :nodoc:
531
- @enabled = value
532
- @state = :up
533
- @img_index = @checked ? 7 : 6
534
- end
535
- end
536
-
537
- # This class represents a text field (input).
538
- class TextField < Component
539
- # The current text inside the text field.
540
- attr_reader :text
541
-
542
- # The current 'locale' used for detecting the keys. THIS FEATURE IS
543
- # INCOMPLETE!
544
- attr_reader :locale
545
-
546
- # Creates a new text field.
547
- #
548
- # Parameters:
549
- # [x] The x-coordinate where the text field will be drawn in the screen.
550
- # [y] The y-coordinate where the text field will be drawn in the screen.
551
- # [font] The <code>Gosu::Font</code> object that will be used to draw the
552
- # text inside the field.
553
- # [img] The image of the text field. For a good result, you would likely
554
- # want something like a rectangle, horizontally wide, vertically
555
- # short, and with a color that contrasts with the +text_color+.
556
- # [cursor_img] An image for the blinking cursor that stands in the point
557
- # where text will be inserted. If +nil+, a simple black line
558
- # will be drawn instead.
559
- # [disabled_img] Image for the text field when it's disabled. If +nil+,
560
- # a darkened version of +img+ will be used.
561
- # [text_color] Color of the button text, in hexadecimal RRGGBB format.
562
- # [margin_x] The x offset, from the field x-coordinate, to draw the text.
563
- # [margin_y] The y offset, from the field y-coordinate, to draw the text.
564
- # [max_length] The maximum length of the text inside the field.
565
- # [active] Whether the text field must be focused by default. If +false+,
566
- # focus can be granted by clicking inside the text field or by
567
- # calling the +focus+ method.
568
- # [text] The starting text. Must not be +nil+.
569
- # [allowed_chars] A string containing all characters that can be typed
570
- # inside the text field. The complete set of supported
571
- # characters is given by the string
572
- # <code>"abcdefghijklmnopqrstuvwxyz1234567890 ABCDEFGHIJKLMNOPQRSTUVWXYZ'-=/[]\\\\,.;\"_+?{}|<>:!@#$%¨&*()"</code>.
573
- # [text_color] The color with which the text will be drawn, in hexadecimal
574
- # RRGGBB format.
575
- # [disabled_text_color] The color with which the text will be drawn, when
576
- # the text field is disabled, in hexadecimal RRGGBB
577
- # format.
578
- # [selection_color] The color of the rectangle highlighting selected text,
579
- # in hexadecimal RRGGBB format. The rectangle will
580
- # always be drawn with 50% of opacity.
581
- # [locale] The locale to be used when detecting keys. By now, only 'en-US'
582
- # and 'pt-BR' are **partially** supported. Default is 'en-US'. If
583
- # any different value is supplied, all typed characters will be
584
- # mapped to '#'.
585
- # [params] An object containing any parameters you want passed to the
586
- # +on_text_changed+ block. When the text of the text field is
587
- # changed, the following is called:
588
- # @on_text_changed.call @text, @params
589
- # Thus, +params+ will be the second parameter. Note that this
590
- # doesn't force you to declare a block that takes parameters.
591
- # [retro] Whether the images should be loaded with the 'retro' option set
592
- # (see +Gosu::Image+ for details). If the value is omitted, the
593
- # +Res.retro_images+ value will be used.
594
- # [scale_x] Horizontal scale to draw the component with.
595
- # [scale_y] Vertical scale to draw the component with.
596
- # [anchor] See parameter with the same name in <code>Panel#initialize</code> for details.
597
- # [on_text_changed] The block of code executed when the text in the text
598
- # field is changed, either by user input or by calling
599
- # +text=+. The new text is passed as a first parameter
600
- # to this block, followed by +params+. Can be +nil+.
601
- #
602
- # *Obs.:* This method accepts named parameters, but +x+, +y+, +font+ and
603
- # +img+ are mandatory.
604
- def initialize(x, y = nil, font = nil, img = nil, cursor_img = nil, disabled_img = nil, margin_x = 0, margin_y = 0,
605
- max_length = 100, active = false, text = '', allowed_chars = nil,
606
- text_color = 0, disabled_text_color = 0, selection_color = 0, locale = 'en-us',
607
- params = nil, retro = nil, scale_x = 1, scale_y = 1, anchor = nil, &on_text_changed)
608
- if x.is_a? Hash
609
- y = x[:y]
610
- font = x[:font]
611
- img = x[:img]
612
- cursor_img = x.fetch(:cursor_img, nil)
613
- disabled_img = x.fetch(:disabled_img, nil)
614
- margin_x = x.fetch(:margin_x, 0)
615
- margin_y = x.fetch(:margin_y, 0)
616
- max_length = x.fetch(:max_length, 100)
617
- active = x.fetch(:active, false)
618
- text = x.fetch(:text, '')
619
- allowed_chars = x.fetch(:allowed_chars, nil)
620
- text_color = x.fetch(:text_color, 0)
621
- disabled_text_color = x.fetch(:disabled_text_color, 0)
622
- selection_color = x.fetch(:selection_color, 0)
623
- locale = x.fetch(:locale, 'en-us')
624
- params = x.fetch(:params, nil)
625
- retro = x.fetch(:retro, nil)
626
- scale_x = x.fetch(:scale_x, 1)
627
- scale_y = x.fetch(:scale_y, 1)
628
- anchor = x.fetch(:anchor, nil)
629
- x = x[:x]
630
- end
631
-
632
- retro = Res.retro_images if retro.nil?
633
- @scale_x = scale_x
634
- @scale_y = scale_y
635
- @img = Res.img img, false, false, '.png', retro
636
- @w = @img.width * @scale_x
637
- @h = @img.height * @scale_y
638
-
639
- @anchor_offset_x = x; @anchor_offset_y = y
640
- @anchor, x, y = FormUtils.check_anchor(anchor, x, y, @w, @h)
641
-
642
- super x, y, font, text, text_color, disabled_text_color
643
- @cursor_img = Res.img(cursor_img, false, false, '.png', retro) if cursor_img
644
- @disabled_img = Res.img(disabled_img, false, false, '.png', retro) if disabled_img
645
- @max_length = max_length
646
- @active = active
647
- @text_x = x + margin_x * @scale_x
648
- @text_y = y + margin_y * @scale_y
649
- @selection_color = selection_color
650
-
651
- @nodes = [@text_x]
652
- send(:text=, text, false) if text
653
-
654
- @cur_node = 0
655
- @cursor_visible = false
656
- @cursor_timer = 0
657
-
658
- @k = [
659
- Gosu::KbA, Gosu::KbB, Gosu::KbC, Gosu::KbD, Gosu::KbE, Gosu::KbF,
660
- Gosu::KbG, Gosu::KbH, Gosu::KbI, Gosu::KbJ, Gosu::KbK, Gosu::KbL,
661
- Gosu::KbM, Gosu::KbN, Gosu::KbO, Gosu::KbP, Gosu::KbQ, Gosu::KbR,
662
- Gosu::KbS, Gosu::KbT, Gosu::KbU, Gosu::KbV, Gosu::KbW, Gosu::KbX,
663
- Gosu::KbY, Gosu::KbZ, Gosu::Kb1, Gosu::Kb2, Gosu::Kb3, Gosu::Kb4,
664
- Gosu::Kb5, Gosu::Kb6, Gosu::Kb7, Gosu::Kb8, Gosu::Kb9, Gosu::Kb0,
665
- Gosu::KbNumpad1, Gosu::KbNumpad2, Gosu::KbNumpad3, Gosu::KbNumpad4,
666
- Gosu::KbNumpad5, Gosu::KbNumpad6, Gosu::KbNumpad7, Gosu::KbNumpad8,
667
- Gosu::KbNumpad9, Gosu::KbNumpad0, Gosu::KbSpace, Gosu::KbBackspace,
668
- Gosu::KbDelete, Gosu::KbLeft, Gosu::KbRight, Gosu::KbHome,
669
- Gosu::KbEnd, Gosu::KbLeftShift, Gosu::KbRightShift,
670
- Gosu::KbBacktick, Gosu::KbMinus, Gosu::KbEqual, Gosu::KbBracketLeft,
671
- Gosu::KbBracketRight, Gosu::KbBackslash, Gosu::KbSemicolon,
672
- Gosu::KbApostrophe, Gosu::KbComma, Gosu::KbPeriod, Gosu::KbSlash,
673
- Gosu::KbNumpadAdd, Gosu::KbNumpadSubtract,
674
- Gosu::KbNumpadMultiply, Gosu::KbNumpadDivide
675
- ]
676
- @user_allowed_chars = allowed_chars
677
- self.locale = locale
678
-
679
- @on_text_changed = on_text_changed
680
- @params = params
681
- end
682
-
683
- # Updates the text field, checking for mouse events and keyboard input.
684
- def update
685
- return unless @enabled and @visible
686
-
687
- ################################ Mouse ################################
688
- if Mouse.over? @x, @y, @w, @h
689
- if not @active and Mouse.button_pressed? :left
690
- focus
691
- end
692
- elsif Mouse.button_pressed? :left
693
- unfocus
694
- end
695
-
696
- return unless @active
697
-
698
- if Mouse.double_click? :left
699
- if @nodes.size > 1
700
- @anchor1 = 0
701
- @anchor2 = @nodes.size - 1
702
- @cur_node = @anchor2
703
- @double_clicked = true
704
- end
705
- set_cursor_visible
706
- elsif Mouse.button_pressed? :left
707
- set_node_by_mouse
708
- @anchor1 = @cur_node
709
- @anchor2 = nil
710
- @double_clicked = false
711
- set_cursor_visible
712
- elsif Mouse.button_down? :left
713
- if @anchor1 and not @double_clicked
714
- set_node_by_mouse
715
- if @cur_node != @anchor1; @anchor2 = @cur_node
716
- else; @anchor2 = nil; end
717
- set_cursor_visible
718
- end
719
- elsif Mouse.button_released? :left
720
- if @anchor1 and not @double_clicked
721
- if @cur_node != @anchor1; @anchor2 = @cur_node
722
- else; @anchor1 = nil; end
723
- end
724
- end
725
-
726
- @cursor_timer += 1
727
- if @cursor_timer >= 30
728
- @cursor_visible = (not @cursor_visible)
729
- @cursor_timer = 0
730
- end
731
-
732
- ############################### Keyboard ##############################
733
- shift = (KB.key_down?(@k[53]) or KB.key_down?(@k[54]))
734
- if KB.key_pressed?(@k[53]) or KB.key_pressed?(@k[54]) # shift
735
- @anchor1 = @cur_node if @anchor1.nil?
736
- elsif KB.key_released?(@k[53]) or KB.key_released?(@k[54])
737
- @anchor1 = nil if @anchor2.nil?
738
- end
739
- inserted = false
740
- for i in 0..46 # alnum
741
- if KB.key_pressed?(@k[i]) or KB.key_held?(@k[i])
742
- remove_interval true if @anchor1 and @anchor2
743
- if i < 26
744
- if shift
745
- insert_char @chars[i + 37]
746
- else
747
- insert_char @chars[i]
748
- end
749
- elsif i < 36
750
- if shift; insert_char @chars[i + 59]
751
- else; insert_char @chars[i]; end
752
- elsif shift
753
- insert_char(@chars[i + 49])
754
- else
755
- insert_char(@chars[i - 10])
756
- end
757
- inserted = true
758
- break
759
- end
760
- end
761
-
762
- return if inserted
763
- for i in 55..65 # special
764
- if KB.key_pressed?(@k[i]) or KB.key_held?(@k[i])
765
- remove_interval true if @anchor1 and @anchor2
766
- if shift; insert_char @chars[i + 19]
767
- else; insert_char @chars[i + 8]; end
768
- inserted = true
769
- break
770
- end
771
- end
772
-
773
- return if inserted
774
- for i in 66..69 # numpad operators
775
- if KB.key_pressed?(@k[i]) or KB.key_held?(@k[i])
776
- remove_interval true if @anchor1 and @anchor2
777
- insert_char @chars[i + 19]
778
- inserted = true
779
- break
780
- end
781
- end
782
-
783
- return if inserted
784
- if KB.key_pressed?(@k[47]) or KB.key_held?(@k[47]) # back
785
- if @anchor1 and @anchor2
786
- remove_interval
787
- elsif @cur_node > 0
788
- remove_char true
789
- end
790
- elsif KB.key_pressed?(@k[48]) or KB.key_held?(@k[48]) # del
791
- if @anchor1 and @anchor2
792
- remove_interval
793
- elsif @cur_node < @nodes.size - 1
794
- remove_char false
795
- end
796
- elsif KB.key_pressed?(@k[49]) or KB.key_held?(@k[49]) # left
797
- if @anchor1
798
- if shift
799
- if @cur_node > 0
800
- @cur_node -= 1
801
- @anchor2 = @cur_node
802
- set_cursor_visible
803
- end
804
- elsif @anchor2
805
- @cur_node = @anchor1 < @anchor2 ? @anchor1 : @anchor2
806
- @anchor1 = nil
807
- @anchor2 = nil
808
- set_cursor_visible
809
- end
810
- elsif @cur_node > 0
811
- @cur_node -= 1
812
- set_cursor_visible
813
- end
814
- elsif KB.key_pressed?(@k[50]) or KB.key_held?(@k[50]) # right
815
- if @anchor1
816
- if shift
817
- if @cur_node < @nodes.size - 1
818
- @cur_node += 1
819
- @anchor2 = @cur_node
820
- set_cursor_visible
821
- end
822
- elsif @anchor2
823
- @cur_node = @anchor1 > @anchor2 ? @anchor1 : @anchor2
824
- @anchor1 = nil
825
- @anchor2 = nil
826
- set_cursor_visible
827
- end
828
- elsif @cur_node < @nodes.size - 1
829
- @cur_node += 1
830
- set_cursor_visible
831
- end
832
- elsif KB.key_pressed?(@k[51]) # home
833
- @cur_node = 0
834
- if shift; @anchor2 = @cur_node
835
- else
836
- @anchor1 = nil
837
- @anchor2 = nil
838
- end
839
- set_cursor_visible
840
- elsif KB.key_pressed?(@k[52]) # end
841
- @cur_node = @nodes.size - 1
842
- if shift; @anchor2 = @cur_node
843
- else
844
- @anchor1 = nil
845
- @anchor2 = nil
846
- end
847
- set_cursor_visible
848
- end
849
- end
850
-
851
- # Sets the text of the text field to the specified value.
852
- #
853
- # Parameters:
854
- # [value] The new text to be set. If it's longer than the +max_length+
855
- # parameter used in the constructor, it will be truncated to
856
- # +max_length+ characters.
857
- def text=(value, trigger_changed = true)
858
- @text = value[0...@max_length]
859
- @nodes.clear; @nodes << @text_x
860
- x = @nodes[0]
861
- @text.chars.each { |char|
862
- x += @font.text_width(char) * @scale_x
863
- @nodes << x
864
- }
865
- @cur_node = @nodes.size - 1
866
- @anchor1 = nil
867
- @anchor2 = nil
868
- set_cursor_visible
869
- @on_text_changed.call @text, @params if trigger_changed && @on_text_changed
870
- end
871
-
872
- # Sets the locale used by the text field to detect keys. Only 'en-us' and
873
- # 'pt-br' are **partially** supported. If any different value is supplied,
874
- # all typed characters will be mapped to '#'.
875
- def locale=(value)
876
- @locale = value.downcase
877
- @chars =
878
- case @locale
879
- when 'en-us' then "abcdefghijklmnopqrstuvwxyz1234567890 ABCDEFGHIJKLMNOPQRSTUVWXYZ`-=[]\\;',./~_+{}|:\"<>?!@#$%^&*()+-*/"
880
- when 'pt-br' then "abcdefghijklmnopqrstuvwxyz1234567890 ABCDEFGHIJKLMNOPQRSTUVWXYZ'-=/[]ç~,.;\"_+?{}Ç^<>:!@#$%¨&*()+-*/"
881
- else '###################################################################################################'
882
- end
883
- @allowed_chars =
884
- if @user_allowed_chars
885
- @user_allowed_chars
886
- else
887
- @chars
888
- end
889
- end
890
-
891
- # Returns the currently selected text.
892
- def selected_text
893
- return '' if @anchor2.nil?
894
- min = @anchor1 < @anchor2 ? @anchor1 : @anchor2
895
- max = min == @anchor1 ? @anchor2 : @anchor1
896
- @text[min..max]
897
- end
898
-
899
- # Grants focus to the text field, so that it allows keyboard input.
900
- def focus
901
- @active = true
902
- end
903
-
904
- # Removes focus from the text field, so that no keyboard input will be
905
- # accepted.
906
- def unfocus
907
- @anchor1 = @anchor2 = nil
908
- @cursor_visible = false
909
- @cursor_timer = 0
910
- @active = false
911
- end
912
-
913
- # Sets the position of the text field in the screen.
914
- #
915
- # Parameters:
916
- # [x] The new x-coordinate for the text field.
917
- # [y] The new y-coordinate for the text field.
918
- def set_position(x, y)
919
- d_x = x - @x
920
- d_y = y - @y
921
- @x = x; @y = y
922
- @text_x += d_x
923
- @text_y += d_y
924
- @nodes.map! do |n|
925
- n + d_x
926
- end
927
- end
928
-
929
- # Draws the text field in the screen.
930
- #
931
- # Parameters:
932
- # [alpha] The opacity with which the text field will be drawn. Allowed
933
- # values vary between 0 (fully transparent) and 255 (fully opaque).
934
- # [z_index] The z-order to draw the object. Objects with larger z-orders
935
- # will be drawn on top of the ones with smaller z-orders.
936
- # [color] Color to apply a filter to the image.
937
- # [disabled_color] Color to apply a filter to the image when the field is
938
- # disabled.
939
- def draw(alpha = 0xff, z_index = 0, color = 0xffffff, disabled_color = 0x808080)
940
- return unless @visible
941
-
942
- color = (alpha << 24) | ((@enabled or @disabled_img) ? color : disabled_color)
943
- text_color = (alpha << 24) | (@enabled ? @text_color : @disabled_text_color)
944
- img = ((@enabled or @disabled_img.nil?) ? @img : @disabled_img)
945
- img.draw @x, @y, z_index, @scale_x, @scale_y, color
946
- @font.draw @text, @text_x, @text_y, z_index, @scale_x, @scale_y, text_color
947
-
948
- if @anchor1 and @anchor2
949
- selection_color = ((alpha / 2) << 24) | @selection_color
950
- G.window.draw_quad @nodes[@anchor1], @text_y, selection_color,
951
- @nodes[@anchor2] + 1, @text_y, selection_color,
952
- @nodes[@anchor2] + 1, @text_y + @font.height * @scale_y, selection_color,
953
- @nodes[@anchor1], @text_y + @font.height * @scale_y, selection_color, z_index
954
- end
955
-
956
- if @cursor_visible
957
- if @cursor_img
958
- @cursor_img.draw @nodes[@cur_node] - (@cursor_img.width * @scale_x) / 2, @text_y, z_index, @scale_x, @scale_y
959
- else
960
- cursor_color = alpha << 24
961
- G.window.draw_quad @nodes[@cur_node], @text_y, cursor_color,
962
- @nodes[@cur_node] + 1, @text_y, cursor_color,
963
- @nodes[@cur_node] + 1, @text_y + @font.height * @scale_y, cursor_color,
964
- @nodes[@cur_node], @text_y + @font.height * @scale_y, cursor_color, z_index
965
- end
966
- end
967
- end
968
-
969
- def enabled=(value) # :nodoc:
970
- @enabled = value
971
- unfocus unless @enabled
972
- end
973
-
974
- def visible=(value) # :nodoc:
975
- @visible = value
976
- unfocus unless @visible
977
- end
978
-
979
- private
980
-
981
- def set_cursor_visible
982
- @cursor_visible = true
983
- @cursor_timer = 0
984
- end
985
-
986
- def set_node_by_mouse
987
- index = @nodes.size - 1
988
- @nodes.each_with_index do |n, i|
989
- if n >= Mouse.x
990
- index = i
991
- break
992
- end
993
- end
994
- if index > 0
995
- d1 = @nodes[index] - Mouse.x; d2 = Mouse.x - @nodes[index - 1]
996
- index -= 1 if d1 > d2
997
- end
998
- @cur_node = index
999
- end
1000
-
1001
- def insert_char(char)
1002
- return unless @allowed_chars.index char and @text.length < @max_length
1003
- @text.insert @cur_node, char
1004
- @nodes.insert @cur_node + 1, @nodes[@cur_node] + @font.text_width(char) * @scale_x
1005
- for i in (@cur_node + 2)..(@nodes.size - 1)
1006
- @nodes[i] += @font.text_width(char) * @scale_x
1007
- end
1008
- @cur_node += 1
1009
- set_cursor_visible
1010
- @on_text_changed.call @text, @params if @on_text_changed
1011
- end
1012
-
1013
- def remove_interval(will_insert = false)
1014
- min = @anchor1 < @anchor2 ? @anchor1 : @anchor2
1015
- max = min == @anchor1 ? @anchor2 : @anchor1
1016
- interval_width = 0
1017
- for i in min...max
1018
- interval_width += @font.text_width(@text[i]) * @scale_x
1019
- @nodes.delete_at min + 1
1020
- end
1021
- @text[min...max] = ''
1022
- for i in (min + 1)..(@nodes.size - 1)
1023
- @nodes[i] -= interval_width
1024
- end
1025
- @cur_node = min
1026
- @anchor1 = nil
1027
- @anchor2 = nil
1028
- set_cursor_visible
1029
- @on_text_changed.call @text, @params if @on_text_changed and not will_insert
1030
- end
1031
-
1032
- def remove_char(back)
1033
- @cur_node -= 1 if back
1034
- char_width = @font.text_width(@text[@cur_node]) * @scale_x
1035
- @text[@cur_node] = ''
1036
- @nodes.delete_at @cur_node + 1
1037
- for i in (@cur_node + 1)..(@nodes.size - 1)
1038
- @nodes[i] -= char_width
1039
- end
1040
- set_cursor_visible
1041
- @on_text_changed.call @text, @params if @on_text_changed
1042
- end
1043
- end
1044
-
1045
- # Represents a progress bar.
1046
- class ProgressBar < Component
1047
- # The maximum value for this progress bar (when the current value equals
1048
- # the maximum, the bar is full).
1049
- attr_reader :max_value
1050
-
1051
- # The current value of the progress bar (an integer greater than or equal
1052
- # to zero, and less than or equal to +max_value+).
1053
- attr_reader :value
1054
-
1055
- # Creates a progress bar.
1056
- #
1057
- # Parameters:
1058
- # [x] The x-coordinate of the progress bar on the screen.
1059
- # [y] The y-coordinate of the progress bar on the screen.
1060
- # [w] Width of the progress bar, in pixels. This is the maximum space the
1061
- # bar foreground can occupy. Note that the width of the foreground image
1062
- # (+fg+) can be less than this, in which case the image will be
1063
- # horizontally repeated to fill all the needed space.
1064
- # [h] Height of the progress bar. This will be the height of the bar
1065
- # foreground when +fg+ is a color (when it is an image, the height of
1066
- # the image will be kept).
1067
- # [bg] A background image (string or symbol that will be passed to
1068
- # +Res.img+) or color (in RRGGBB hexadecimal format).
1069
- # [fg] A foreground image (string or symbol that will be passed to
1070
- # +Res.img+) or color (in RRGGBB hexadecimal format). The image will
1071
- # be horizontally repeated when needed, if its width is less than +w+.
1072
- # [max_value] The maximum value the progress bar can reach (an integer).
1073
- # [value] The starting value for the progress bar.
1074
- # [fg_margin_x] Horizontal margin between the background image and the
1075
- # foreground image (when these are provided).
1076
- # [fg_margin_y] Vertical margin between the background image and the
1077
- # foreground image (when these are provided).
1078
- # [font] Font that will be used to draw a text indicating the value of the
1079
- # progress bar.
1080
- # [text_color] Color of the text.
1081
- # [format] Format to display the value. Specify '%' for a percentage and
1082
- # anything else for absolute values (current/maximum).
1083
- # [retro] Whether the images should be loaded with the 'retro' option set
1084
- # (see +Gosu::Image+ for details). If the value is omitted, the
1085
- # +Res.retro_images+ value will be used.
1086
- # [scale_x] Horizontal scale to draw the component with.
1087
- # [scale_y] Vertical scale to draw the component with.
1088
- # [anchor] See parameter with the same name in <code>Panel#initialize</code> for details.
1089
- #
1090
- # *Obs.:* This method accepts named parameters, but +x+, +y+, +w+, +h+, +bg+
1091
- # and +fg+ are mandatory.
1092
- def initialize(x, y = nil, w = nil, h = nil, bg = nil, fg = nil,
1093
- max_value = 100, value = 100, fg_margin_x = 0, fg_margin_y = 0, # fg_left = nil, fg_right = nil,
1094
- font = nil, text_color = 0, format = nil, retro = nil, scale_x = 1, scale_y = 1, anchor = nil)
1095
- if x.is_a? Hash
1096
- y = x[:y]
1097
- w = x[:w]
1098
- h = x[:h]
1099
- bg = x[:bg]
1100
- fg = x[:fg]
1101
- max_value = x.fetch(:max_value, 100)
1102
- value = x.fetch(:value, 100)
1103
- fg_margin_x = x.fetch(:fg_margin_x, 0)
1104
- fg_margin_y = x.fetch(:fg_margin_y, 0)
1105
- font = x.fetch(:font, nil)
1106
- text_color = x.fetch(:text_color, 0)
1107
- format = x.fetch(:format, nil)
1108
- retro = x.fetch(:retro, nil)
1109
- scale_x = x.fetch(:scale_x, 1)
1110
- scale_y = x.fetch(:scale_y, 1)
1111
- anchor = x.fetch(:anchor, nil)
1112
- x = x[:x]
1113
- end
1114
-
1115
- @scale_x = scale_x
1116
- @scale_y = scale_y
1117
- retro = Res.retro_images if retro.nil?
1118
- if bg.is_a? Integer
1119
- @bg_color = bg
1120
- else # String or Symbol
1121
- @bg = Res.img bg, false, false, '.png', retro
1122
- end
1123
- if fg.is_a? Integer
1124
- @fg_color = fg
1125
- else # String or Symbol
1126
- @fg = Res.img fg, false, false, '.png', retro
1127
- @fg_path = "#{Res.prefix}#{Res.img_dir}#{fg.to_s.gsub(Res.separator, '/')}.png"
1128
- end
1129
- @fg_margin_x = fg_margin_x * @scale_x
1130
- @fg_margin_y = fg_margin_y * @scale_y
1131
-
1132
- @w = (@bg ? @bg.width : w) * @scale_x
1133
- @h = (@bg ? @bg.height : h) * @scale_y
1134
-
1135
- @anchor_offset_x = x; @anchor_offset_y = y
1136
- @anchor, x, y = FormUtils.check_anchor(anchor, x, y, @w, @h)
1137
-
1138
- super x, y, font, '', text_color, text_color
1139
- # @fg_left = fg_left
1140
- # @fg_right = fg_right
1141
- @max_value = max_value
1142
- self.value = value
1143
- @format = format
1144
- @retro = retro
1145
- end
1146
-
1147
- # Increases the current value of the progress bar by the given amount.
1148
- #
1149
- # Parameters:
1150
- # [amount] (+Integer+) The amount to be added to the current value. If the
1151
- # sum surpasses +max_value+, it is set to +max_value+.
1152
- def increase(amount)
1153
- @value += amount
1154
- @value = @max_value if @value > @max_value
1155
- end
1156
-
1157
- # Descreases the current value of the progress bar by the given amount.
1158
- #
1159
- # Parameters:
1160
- # [amount] (+Integer+) The amount to be subtracted from the current value.
1161
- # If the result is less than zero, it is set to zero.
1162
- def decrease(amount)
1163
- @value -= amount
1164
- @value = 0 if @value < 0
1165
- end
1166
-
1167
- # Sets the value of the progress bar.
1168
- #
1169
- # Parameters:
1170
- # [val] (+Integer+) The value to be set. It will be changed as needed to be
1171
- # between zero and +max_value+.
1172
- def value=(val)
1173
- @value = val
1174
- if @value > @max_value
1175
- @value = @max_value
1176
- elsif @value < 0
1177
- @value = 0
1178
- end
1179
- end
1180
-
1181
- # Sets the value of the progress bar to a given percentage of +max_value+.
1182
- #
1183
- # Parameters:
1184
- # [pct] (+Numeric+) The percentage of +max_value+ to set the current value
1185
- # to. The final result will be changed as needed to be between zero
1186
- # and +max_value+.
1187
- def percentage=(pct)
1188
- self.value = (pct * @max_value).round
1189
- end
1190
-
1191
- # Draws the progress bar.
1192
- #
1193
- # Parameters:
1194
- # [alpha] (+Fixnum+) The opacity with which the progress bar will be drawn.
1195
- # Allowed values vary between 0 (fully transparent) and 255 (fully
1196
- # opaque).
1197
- # [z_index] (+Fixnum+) The z-order to draw the object. Objects with larger
1198
- # z-orders will be drawn on top of the ones with smaller z-orders.
1199
- # [color] Color to apply a filter to the images (when these are provided).
1200
- def draw(alpha = 0xff, z_index = 0, color = 0xffffff)
1201
- return unless @visible
1202
-
1203
- if @bg
1204
- c = (alpha << 24) | color
1205
- @bg.draw @x, @y, z_index, @scale_x, @scale_y, c
1206
- else
1207
- c = (alpha << 24) | @bg_color
1208
- G.window.draw_quad @x, @y, c,
1209
- @x + @w, @y, c,
1210
- @x + @w, @y + @h, c,
1211
- @x, @y + @h, c, z_index
1212
- end
1213
- if @fg
1214
- c = (alpha << 24) | color
1215
- w1 = @fg.width * @scale_x
1216
- w2 = (@value.to_f / @max_value * @w).round
1217
- x0 = @x + @fg_margin_x
1218
- x = 0
1219
- while x <= w2 - w1
1220
- @fg.draw x0 + x, @y + @fg_margin_y, z_index, @scale_x, @scale_y, c
1221
- x += w1
1222
- end
1223
- if w2 - x > 0
1224
- img = Gosu::Image.new(@fg_path, tileable: true, retro: @retro, rect: [0, 0, ((w2 - x) / @scale_x).round, @fg.height])
1225
- img.draw x0 + x, @y + @fg_margin_y, z_index, @scale_x, @scale_y, c
1226
- end
1227
- else
1228
- c = (alpha << 24) | @fg_color
1229
- rect_r = @x + (@value.to_f / @max_value * @w).round
1230
- G.window.draw_quad @x, @y, c,
1231
- rect_r, @y, c,
1232
- rect_r, @y + @h, c,
1233
- @x, @y + @h, c, z_index
1234
- end
1235
- if @font
1236
- c = (alpha << 24) | @text_color
1237
- @text = @format == '%' ? "#{(@value.to_f / @max_value * 100).round}%" : "#{@value}/#{@max_value}"
1238
- @font.draw_rel @text, @x + @w / 2, @y + @h / 2, z_index, 0.5, 0.5, @scale_x, @scale_y, c
1239
- end
1240
- end
1241
- end
1242
-
1243
- # This class represents a "drop-down list" form component, here composed of a
1244
- # group of +Button+ objects.
1245
- class DropDownList < Component
1246
- # The selected value in the drop-down list. This is one of the +options+.
1247
- attr_reader :value
1248
-
1249
- # An array containing all the options (each of them +String+s) that can be
1250
- # selected in the drop-down list.
1251
- attr_accessor :options
1252
-
1253
- # Creates a new drop-down list.
1254
- #
1255
- # Parameters:
1256
- # [x] The x-coordinate of the object.
1257
- # [y] The y-coordinate of the object.
1258
- # [font] Font to be used by the buttons that compose the drop-down list.
1259
- # [img] Image of the main button, i.e., the one at the top, that toggles
1260
- # visibility of the other buttons (the "option" buttons).
1261
- # [opt_img] Image for the "option" buttons, as described above.
1262
- # [options] Array of available options for this control (+String+s).
1263
- # [option] Index of the firstly selected option.
1264
- # [text_margin] Left margin of the text inside the buttons (vertically, the
1265
- # text will always be centered).
1266
- # [width] Width of the control, used when no image is provided.
1267
- # [height] Height of the control, used when no image is provided.
1268
- # [text_color] Used as the +text_color+ parameter in the constructor of the
1269
- # buttons.
1270
- # [disabled_text_color] Analogous to +text_color+.
1271
- # [over_text_color] Same as above.
1272
- # [down_text_color] Same as above.
1273
- # [retro] Whether the images should be loaded with the 'retro' option set
1274
- # (see +Gosu::Image+ for details). If the value is omitted, the
1275
- # +Res.retro_images+ value will be used.
1276
- # [scale_x] Horizontal scale to draw the component with.
1277
- # [scale_y] Vertical scale to draw the component with.
1278
- # [anchor] See parameter with the same name in <code>Panel#initialize</code> for details.
1279
- # [on_changed] Action performed when the value of the dropdown is changed.
1280
- # It must be a block with two parameters, which will receive
1281
- # the old and the new value, respectively.
1282
- #
1283
- # *Obs.:* This method accepts named parameters, but +x+, +y+, +font+ and
1284
- # +options+ are mandatory (also, +img+ and +opt_img+ are mandatory when
1285
- # +width+ and +height+ are not provided, and vice-versa).
1286
- def initialize(x, y = nil, font = nil, img = nil, opt_img = nil, options = nil,
1287
- option = 0, text_margin = 0, width = nil, height = nil,
1288
- text_color = 0, disabled_text_color = 0, over_text_color = 0, down_text_color = 0,
1289
- retro = nil, scale_x = 1, scale_y = 1, anchor = nil, &on_changed)
1290
- if x.is_a? Hash
1291
- y = x[:y]
1292
- font = x[:font]
1293
- img = x[:img]
1294
- opt_img = x[:opt_img]
1295
- options = x[:options]
1296
- option = x.fetch(:option, 0)
1297
- text_margin = x.fetch(:text_margin, 0)
1298
- width = x.fetch(:width, nil)
1299
- height = x.fetch(:height, nil)
1300
- text_color = x.fetch(:text_color, 0)
1301
- disabled_text_color = x.fetch(:disabled_text_color, 0)
1302
- over_text_color = x.fetch(:over_text_color, 0)
1303
- down_text_color = x.fetch(:down_text_color, 0)
1304
- retro = x.fetch(:retro, nil)
1305
- scale_x = x.fetch(:scale_x, 1)
1306
- scale_y = x.fetch(:scale_y, 1)
1307
- anchor = x.fetch(:anchor, nil)
1308
- x = x[:x]
1309
- end
1310
- @img = img
1311
- @opt_img = opt_img
1312
- @options = options
1313
- @value = @options[option]
1314
- @open = false
1315
- @buttons = []
1316
- @buttons.push(
1317
- Button.new(x, y, font, @value, img, text_color, disabled_text_color, over_text_color, down_text_color,
1318
- false, true, text_margin, 0, width, height, nil, retro, scale_x, scale_y) {
1319
- toggle
1320
- }
1321
- )
1322
-
1323
- @scale_x = scale_x
1324
- @scale_y = scale_y
1325
- @w = @buttons[0].w
1326
- @h = @buttons[0].h
1327
- @max_h = (@options.size + 1) * @h
1328
-
1329
- @anchor_offset_x = x; @anchor_offset_y = y
1330
- @anchor, x, y = FormUtils.check_anchor(anchor, x, y, @w, @h)
1331
- super x, y, font, options[option], text_color, disabled_text_color
1332
- @buttons[0].set_position(x, y)
1333
-
1334
- @options.each_with_index do |o, i|
1335
- b = Button.new(x, y + (i+1) * @h, font, o, opt_img, text_color, disabled_text_color, over_text_color, down_text_color,
1336
- false, true, text_margin, 0, width, height, nil, retro, scale_x, scale_y) {
1337
- old = @value
1338
- @value = @buttons[0].text = o
1339
- @on_changed.call(old, o) if @on_changed
1340
- toggle
1341
- }
1342
- b.visible = false
1343
- @buttons.push b
1344
- end
1345
-
1346
- @on_changed = on_changed
1347
- end
1348
-
1349
- # Updates the control.
1350
- def update
1351
- return unless @enabled and @visible
1352
- if @open and Mouse.button_pressed? :left and not Mouse.over?(@x, @y, @w, @max_h)
1353
- toggle
1354
- return
1355
- end
1356
- @buttons.each { |b| b.update }
1357
- end
1358
-
1359
- # Sets the currently selected value of the drop-down list. It is ignored if
1360
- # it is not among the available options.
1361
- def value=(val)
1362
- if @options.include? val
1363
- old = @value
1364
- @value = @buttons[0].text = val
1365
- @on_changed.call(old, val) if @on_changed
1366
- end
1367
- end
1368
-
1369
- def enabled=(value) # :nodoc:
1370
- toggle if @open
1371
- @buttons[0].enabled = value
1372
- @enabled = value
1373
- end
1374
-
1375
- def set_position(x, y)
1376
- @x = x; @y = y
1377
- @buttons.each_with_index { |b, i| b.set_position(x, y + i * @h) }
1378
- end
1379
-
1380
- # Draws the drop-down list.
1381
- #
1382
- # Parameters:
1383
- # [alpha] (+Fixnum+) The opacity with which the drop-down list will be
1384
- # drawn. Allowed values vary between 0 (fully transparent) and 255
1385
- # (fully opaque).
1386
- # [z_index] (+Fixnum+) The z-order to draw the object. Objects with larger
1387
- # z-orders will be drawn on top of the ones with smaller z-orders.
1388
- # [color] Color of the buttons, if no image was provided, or color to apply
1389
- # a filter to the images.
1390
- # [over_color] Color of the buttons when the mouse is over them (when no
1391
- # image was provided).
1392
- def draw(alpha = 0xff, z_index = 0, color = 0xffffff, over_color = 0xcccccc)
1393
- return unless @visible
1394
- unless @img
1395
- bottom = @y + (@open ? @max_h : @h) + @scale_y
1396
- b_color = (alpha << 24)
1397
- G.window.draw_quad @x - @scale_x, @y - @scale_y, b_color,
1398
- @x + @w + @scale_x, @y - @scale_y, b_color,
1399
- @x + @w + @scale_x, bottom, b_color,
1400
- @x - @scale_x, bottom, b_color, z_index
1401
- @buttons.each do |b|
1402
- c = (alpha << 24) | (b.state == :over ? over_color : color)
1403
- G.window.draw_quad b.x, b.y, c,
1404
- b.x + b.w, b.y, c,
1405
- b.x + b.w, b.y + b.h, c,
1406
- b.x, b.y + b.h, c, z_index + 1 if b.visible
1407
- end
1408
- end
1409
- @buttons[0].draw(alpha, z_index, color)
1410
- @buttons[1..-1].each { |b| b.draw alpha, z_index + 1, color }
1411
- end
1412
-
1413
- private
1414
-
1415
- def toggle
1416
- if @open
1417
- @buttons[1..-1].each { |b| b.visible = false }
1418
- @open = false
1419
- else
1420
- @buttons[1..-1].each { |b| b.visible = true }
1421
- @open = true
1422
- end
1423
- end
1424
- end
1425
-
1426
- # This class represents a label.
1427
- class Label < Component
1428
- # Creates a new label.
1429
- #
1430
- # Parameters:
1431
- # [x] The x-coordinate of the label.
1432
- # [y] The x-coordinate of the label.
1433
- # [font] Font that will be used to draw the label's text.
1434
- # [text] The label's text.
1435
- # [text_color] The default text color.
1436
- # [disabled_text_color] The text color when the label is disabled.
1437
- # [scale_x] The horizontal scale factor.
1438
- # [scale_y] The vertical scale factor.
1439
- # [anchor] See parameter with the same name in <code>Panel#initialize</code> for details.
1440
- def initialize(x, y = nil, font = nil, text = nil, text_color = 0, disabled_text_color = 0, scale_x = 1, scale_y = 1, anchor = nil)
1441
- if x.is_a? Hash
1442
- y = x[:y]
1443
- font = x[:font]
1444
- text = x[:text]
1445
- text_color = x.fetch(:text_color, 0)
1446
- disabled_text_color = x.fetch(:disabled_text_color, 0)
1447
- scale_x = x.fetch(:scale_x, 1)
1448
- scale_y = x.fetch(:scale_y, 1)
1449
- anchor = x.fetch(:anchor, nil)
1450
- x = x[:x]
1451
- end
1452
-
1453
- @scale_x = scale_x
1454
- @scale_y = scale_y
1455
- @w = font.text_width(text) * scale_x
1456
- @h = font.height * scale_y
1457
- @anchor_offset_x = x; @anchor_offset_y = y
1458
- @anchor, x, y = FormUtils.check_anchor(anchor, x, y, @w, @h)
1459
- super(x, y, font, text, text_color, disabled_text_color)
1460
- end
1461
-
1462
- # Draws the label.
1463
- #
1464
- # Parameters:
1465
- # [alpha] The opacity with which the label will be drawn. Allowed values
1466
- # vary between 0 (fully transparent) and 255 (fully opaque).
1467
- # [z_index] The z-order to draw the object. Objects with larger z-orders
1468
- # will be drawn on top of the ones with smaller z-orders.
1469
- # [color] Color to apply a filter to the text.
1470
- def draw(alpha = 255, z_index = 0, color = 0xffffff)
1471
- c = @enabled ? @text_color : @disabled_text_color
1472
- r1 = c >> 16
1473
- g1 = (c & 0xff00) >> 8
1474
- b1 = (c & 0xff)
1475
- r2 = color >> 16
1476
- g2 = (color & 0xff00) >> 8
1477
- b2 = (color & 0xff)
1478
- r1 *= r2; r1 /= 255
1479
- g1 *= g2; g1 /= 255
1480
- b1 *= b2; b1 /= 255
1481
- color = (alpha << 24) | (r1 << 16) | (g1 << 8) | b1
1482
- @font.draw(@text, @x, @y, z_index, @scale_x, @scale_y, color)
1483
- end
1484
- end
1485
- end
1
+ require_relative 'global'
2
+
3
+ module MiniGL
4
+ module FormUtils # :nodoc:
5
+ def self.check_anchor(anchor, x, y, w, h, area_w = G.window.width, area_h = G.window.height)
6
+ if anchor
7
+ case anchor
8
+ when /^top(_center)?$|^north$/i then anchor_alias = :top_center; x += (area_w - w) / 2
9
+ when /^top_right$|^northeast$/i then anchor_alias = :top_right; x = area_w - w - x
10
+ when /^(center_)?left$|^west$/i then anchor_alias = :center_left; y += (area_h - h) / 2
11
+ when /^center$/i then anchor_alias = :center; x += (area_w - w) / 2; y += (area_h - h) / 2
12
+ when /^(center_)?right$|^east$/i then anchor_alias = :center_right; x = area_w - w - x; y += (area_h - h) / 2
13
+ when /^bottom_left$|^southwest$/i then anchor_alias = :bottom_left; y = area_h - h - y
14
+ when /^bottom(_center)?$|^south$/i then anchor_alias = :bottom_center; x += (area_w - w) / 2; y = area_h - h - y
15
+ when /^bottom_right$|^southeast$/i then anchor_alias = :bottom_right; x = area_w - w - x; y = area_h - h - y
16
+ else anchor_alias = :top_left
17
+ end
18
+ else
19
+ anchor_alias = :top_left
20
+ end
21
+ [anchor_alias, x, y]
22
+ end
23
+ end
24
+
25
+ # This class is an abstract ancestor for all form components (Button,
26
+ # ToggleButton, TextField, DropDownList and ProgressBar).
27
+ class Component
28
+ # The horizontal coordinate of the component
29
+ attr_reader :x
30
+
31
+ # The vertical coordinate of the component
32
+ attr_reader :y
33
+
34
+ # The width of the component
35
+ attr_reader :w
36
+
37
+ # The height of the component
38
+ attr_reader :h
39
+
40
+ attr_reader :anchor, :anchor_offset_x, :anchor_offset_y # :nodoc:
41
+
42
+ # Determines whether the control is enabled, i.e., will process user input.
43
+ attr_accessor :enabled
44
+
45
+ # Determines whether the control is visible, i.e., will be drawn in the
46
+ # screen and process user input, if enabled.
47
+ attr_accessor :visible
48
+
49
+ # A container for any parameters to be passed to the code blocks called
50
+ # in response to events of the control (click of a button, change of the
51
+ # text in a text field, etc.). More detail can be found in the constructor
52
+ # for each specific component class.
53
+ attr_accessor :params
54
+
55
+ def initialize(x, y, font, text, text_color, disabled_text_color) # :nodoc:
56
+ @x = x
57
+ @y = y
58
+ @font = font
59
+ @text = text
60
+ @text_color = text_color
61
+ @disabled_text_color = disabled_text_color
62
+ @enabled = @visible = true
63
+ end
64
+
65
+ def update; end # :nodoc:
66
+
67
+ # Sets the position of the component.
68
+ # Parameters:
69
+ # [x] The new x coordinate.
70
+ # [y] The new y coordinate.
71
+ def set_position(x, y)
72
+ @x = x; @y = y
73
+ end
74
+ end
75
+
76
+ # Represents a container of form components.
77
+ class Panel
78
+ # The horizontal position of the panel from the top left corner of the window.
79
+ attr_reader :x
80
+
81
+ # The vertical position of the panel from the top left corner of the window.
82
+ attr_reader :y
83
+
84
+ # The width of the panel in pixels.
85
+ attr_reader :w
86
+
87
+ # The height of the panel in pixels.
88
+ attr_reader :h
89
+
90
+ # Whether the components inside this panel are enabled.
91
+ attr_reader :enabled
92
+
93
+ # Gets or sets whether the panel (and thus all components inside it) are visible.
94
+ attr_accessor :visible
95
+
96
+ # Creates a new Panel.
97
+ # Parameters:
98
+ # [x] The horizontal coordinate of the top-left corner of the panel, or the horizontal offset from the anchor, if provided.
99
+ # [y] The vertical coordinate of the top-left corner of the panel, or the vertical offset from the anchor, if provided.
100
+ # [w] The width of the panel, in pixels.
101
+ # [h] The height of the panel, in pixels.
102
+ # [controls] An array of <code>Component</code>s that will be initially inside this panel.
103
+ # [img] Identifier of the image for the panel (see details in +Res::img+).
104
+ # [img_mode] Mode to scale the image. If +:normal+ (default), the image will be loaded as a single image and scaled to fit the entire size of the panel;
105
+ # if +:tiled+, the image will be loaded as a 3x3 spritesheet, where the "corner" images (i.e., indices 0, 2, 6 and 8) will be scaled by the +scale_x+ and +scale_y+ parameters,
106
+ # the "border" images (indices 1, 3, 5 and 7) will be stretched in the corresponding direction (indices 1 and 7 will be horizontally stretched and indices 3 and 5, vertically),
107
+ # and the "center" image (index 4) will be stretched in both directions, as needed, to fill the width and height of the panel.
108
+ # [retro] Whether the image should be loaded in retro mode.
109
+ # [scale_x] The fixed horizontal scale for "corner" and left and right "border" images (if +img_mode+ is +:tiled+).
110
+ # [scale_y] The fixed vertical scale for "corner" and top and bottom "border" images (if +img_mode+ is +:tiled+).
111
+ # [anchor] An alias for a predefined position of the window to be used as "anchor", i.e., reference for the positioning of the panel.
112
+ # Following are the valid values and a description of the corresponding position if +x+ and +y+ are 0 (these will be offsets from the reference position):
113
+ # * +:north+ or +:top+ or +:top_center+: the panel will be horizontally centered and its top will be at the top of the window.
114
+ # * +:northeast+ or +:top_right+: the top-right corner of the panel will meet the top-right corner of the window.
115
+ # * +:west+ or +:left+ or +:center_left+: the panel will be vertically centered and its left edge will be at the left edge of the window.
116
+ # * +:center+: the panel will be horizontally and vertically centered on the window.
117
+ # * +:east+ or +:right+ or +:center_right+: the panel will be vertically centered and its right edge will be at the right edge of the window.
118
+ # * +:southwest+ or +:bottom_left+: the bottom-left corner of the panel will meet the bottom-left corner of the window.
119
+ # * +:south+ or +:bottom+ or +:bottom_center+: the panel will be horizontally centered and its bottom will be at the bottom of the window.
120
+ # * +:southeast+ or +:bottom_right+: the bottom-right corner of the panel will meet the bottom-right corner of the window.
121
+ # If a value is not provided, the reference is the top-left corner of the screen.
122
+ # Components added as children of <code>Panel</code>s use the panel's coordinates as reference instead of the window.
123
+ def initialize(x, y, w, h, controls = [], img = nil, img_mode = :normal, retro = nil, scale_x = 1, scale_y = 1, anchor = nil)
124
+ _, x, y = FormUtils.check_anchor(anchor, x, y, w, h)
125
+ @x = x; @y = y; @w = w; @h = h
126
+ @controls = controls
127
+ controls.each do |c|
128
+ _, x, y = FormUtils.check_anchor(c.anchor, c.anchor_offset_x, c.anchor_offset_y, c.w, c.h, @w, @h)
129
+ c.set_position(@x + x, @y + y)
130
+ end
131
+
132
+ if img
133
+ retro = Res.retro_images if retro.nil?
134
+ if img_mode == :tiled
135
+ @img = Res.imgs(img, 3, 3, true, '.png', retro, true)
136
+ @scale_x = scale_x
137
+ @scale_y = scale_y
138
+ @tile_w = @img[0].width * @scale_x
139
+ @tile_h = @img[0].height * @scale_y
140
+ @draw_center_x = @w > 2 * @tile_w
141
+ @draw_center_y = @h > 2 * @tile_h
142
+ @center_scale_x = (@w - 2 * @tile_w).to_f / @tile_w * @scale_x
143
+ @center_scale_y = (@h - 2 * @tile_h).to_f / @tile_h * @scale_y
144
+ else
145
+ @img = Res.img(img, true, false, '.png', retro)
146
+ end
147
+ end
148
+
149
+ @visible = @enabled = true
150
+ end
151
+
152
+ # Updates all child components of this panel.
153
+ def update
154
+ @controls.each(&:update)
155
+ end
156
+
157
+ # Enables or disables all child components of this panel.
158
+ # Parameters:
159
+ # [value] Whether the components should be enabled.
160
+ def enabled=(value)
161
+ @enabled = value
162
+ @controls.each { |c| c.enabled = value }
163
+ end
164
+
165
+ # Adds a component to this panel.
166
+ # Parameters:
167
+ # [c] The component to add.
168
+ def add_component(c)
169
+ _, x, y = FormUtils.check_anchor(c.anchor, c.anchor_offset_x, c.anchor_offset_y, c.w, c.h, @w, @h)
170
+ c.set_position(@x + x, @y + y)
171
+ @controls << c
172
+ end
173
+
174
+ # Draws the panel and all its child components.
175
+ # Parameters:
176
+ # [alpha] The opacity of the panel (0 = fully transparent, 255 = fully opaque).
177
+ # [z_index] The z-index to draw the panel.
178
+ # [color] The color to apply as filter to the panel image and to all child components' images as well.
179
+ def draw(alpha = 255, z_index = 0, color = 0xffffff)
180
+ return unless @visible
181
+
182
+ c = (alpha << 24) | color
183
+ if @img
184
+ if @img.is_a?(Array)
185
+ @img[0].draw(@x, @y, z_index, @scale_x, @scale_y, c)
186
+ @img[1].draw(@x + @tile_w, @y, z_index, @center_scale_x, @scale_y, c) if @draw_center_x
187
+ @img[2].draw(@x + @w - @tile_w, @y, z_index, @scale_x, @scale_y, c)
188
+ @img[3].draw(@x, @y + @tile_h, z_index, @scale_x, @center_scale_y, c) if @draw_center_y
189
+ @img[4].draw(@x + @tile_w, @y + @tile_h, z_index, @center_scale_x, @center_scale_y, c) if @draw_center_x && @draw_center_y
190
+ @img[5].draw(@x + @w - @tile_w, @y + @tile_h, z_index, @scale_x, @center_scale_y, c) if @draw_center_y
191
+ @img[6].draw(@x, @y + @h - @tile_h, z_index, @scale_x, @scale_y, c)
192
+ @img[7].draw(@x + @tile_w, @y + @h - @tile_h, z_index, @center_scale_x, @scale_y, c) if @draw_center_x
193
+ @img[8].draw(@x + @w - @tile_w, @y + @h - @tile_h, z_index, @scale_x, @scale_y, c)
194
+ else
195
+ @img.draw(@x, @y, z_index, @w.to_f / @img.width, @h.to_f / @img.height)
196
+ end
197
+ end
198
+
199
+ @controls.each { |k| k.draw(alpha, z_index, color) if k.visible }
200
+ end
201
+ end
202
+
203
+ # This class represents a button.
204
+ class Button < Component
205
+ # The current state of the button.
206
+ attr_reader :state
207
+
208
+ # The text of the button.
209
+ attr_accessor :text
210
+
211
+ # Creates a button.
212
+ #
213
+ # Parameters:
214
+ # [x] The x-coordinate where the button will be drawn in the screen.
215
+ # [y] The y-coordinate where the button will be drawn in the screen.
216
+ # [font] The <code>Gosu::Font</code> object that will be used to draw the
217
+ # button text.
218
+ # [text] The button text. Can be +nil+ or empty.
219
+ # [img] A spritesheet containing four images in a column, representing,
220
+ # from top to bottom, the default state, the hover state (when the
221
+ # mouse is over the button), the pressed state (when the mouse
222
+ # button is down and the cursor is over the button) and the disabled
223
+ # state. If +nil+, the +width+ and +height+ parameters must be
224
+ # provided.
225
+ # [text_color] Color of the button text, in hexadecimal RRGGBB format.
226
+ # [disabled_text_color] Color of the button text, when it's disabled, in
227
+ # hexadecimal RRGGBB format.
228
+ # [over_text_color] Color of the button text, when the cursor is over it
229
+ # (hexadecimal RRGGBB).
230
+ # [down_text_color] Color of the button text, when it is pressed
231
+ # (hexadecimal RRGGBB).
232
+ # [center_x] Whether the button text should be horizontally centered in its
233
+ # area (the area is defined by the image size, if an image is
234
+ # given, or by the +width+ and +height+ parameters, otherwise).
235
+ # [center_y] Whether the button text should be vertically centered in its
236
+ # area (the area is defined by the image size, if an image is
237
+ # given, or by the +width+ and +height+ parameters, otherwise).
238
+ # [margin_x] The x offset, from the button x-coordinate, to draw the text.
239
+ # This parameter is used only if +center+ is false.
240
+ # [margin_y] The y offset, from the button y-coordinate, to draw the text.
241
+ # This parameter is used only if +center+ is false.
242
+ # [width] Width of the button clickable area. This parameter is used only
243
+ # if +img+ is +nil+.
244
+ # [height] Height of the button clickable area. This parameter is used
245
+ # only if +img+ is +nil+.
246
+ # [params] An object containing any parameters you want passed to the
247
+ # +action+ block. When the button is clicked, the following is
248
+ # called:
249
+ # @action.call @params
250
+ # Note that this doesn't force you to declare a block that takes
251
+ # parameters.
252
+ # [retro] Whether the image should be loaded with the 'retro' option set
253
+ # (see +Gosu::Image+ for details). If the value is omitted, the
254
+ # +Res.retro_images+ value will be used.
255
+ # [scale_x] Horizontal scale to draw the component with.
256
+ # [scale_y] Vertical scale to draw the component with.
257
+ # [anchor] See parameter with the same name in <code>Panel#initialize</code> for details.
258
+ # [action] The block of code executed when the button is clicked (or by
259
+ # calling the +click+ method).
260
+ #
261
+ # *Obs.:* This method accepts named parameters, but +x+ and +y+ are
262
+ # mandatory (also, +img+ is mandatory when +width+ and +height+ are not
263
+ # provided, and vice-versa).
264
+ def initialize(x, y = nil, font = nil, text = nil, img = nil,
265
+ text_color = 0, disabled_text_color = 0, over_text_color = 0, down_text_color = 0,
266
+ center_x = true, center_y = true, margin_x = 0, margin_y = 0, width = nil, height = nil,
267
+ params = nil, retro = nil, scale_x = 1, scale_y = 1, anchor = nil, &action)
268
+ if x.is_a? Hash
269
+ y = x[:y]
270
+ font = x[:font]
271
+ text = x[:text]
272
+ img = x[:img]
273
+ text_color = x.fetch(:text_color, 0)
274
+ disabled_text_color = x.fetch(:disabled_text_color, 0)
275
+ over_text_color = x.fetch(:over_text_color, 0)
276
+ down_text_color = x.fetch(:down_text_color, 0)
277
+ center_x = x.fetch(:center_x, true)
278
+ center_y = x.fetch(:center_y, true)
279
+ margin_x = x.fetch(:margin_x, 0)
280
+ margin_y = x.fetch(:margin_y, 0)
281
+ width = x.fetch(:width, nil)
282
+ height = x.fetch(:height, nil)
283
+ params = x.fetch(:params, nil)
284
+ retro = x.fetch(:retro, nil)
285
+ scale_x = x.fetch(:scale_x, 1)
286
+ scale_y = x.fetch(:scale_y, 1)
287
+ anchor = x.fetch(:anchor, nil)
288
+ x = x[:x]
289
+ end
290
+
291
+ retro = Res.retro_images if retro.nil?
292
+ @scale_x = scale_x
293
+ @scale_y = scale_y
294
+ @img =
295
+ if img; Res.imgs img, 1, 4, true, '.png', retro
296
+ else; nil; end
297
+ @w =
298
+ if img; @img[0].width * @scale_x
299
+ else; width * @scale_x; end
300
+ @h =
301
+ if img; @img[0].height * @scale_y
302
+ else; height * @scale_y; end
303
+
304
+ @anchor_offset_x = x; @anchor_offset_y = y
305
+ @anchor, x, y = FormUtils.check_anchor(anchor, x, y, @w, @h)
306
+
307
+ super x, y, font, text, text_color, disabled_text_color
308
+ @over_text_color = over_text_color
309
+ @down_text_color = down_text_color
310
+ if center_x; @text_x = x + @w / 2 if @w
311
+ else; @text_x = x + margin_x * @scale_x; end
312
+ if center_y; @text_y = y + @h / 2 if @h
313
+ else; @text_y = y + margin_y * @scale_y; end
314
+ @center_x = center_x
315
+ @center_y = center_y
316
+ @action = action
317
+ @params = params
318
+
319
+ @state = :up
320
+ @img_index = @enabled ? 0 : 3
321
+ end
322
+
323
+ # Updates the button, checking the mouse movement and buttons to define
324
+ # the button state.
325
+ def update
326
+ return unless @enabled and @visible
327
+
328
+ mouse_over = Mouse.over? @x, @y, @w, @h
329
+ mouse_press = Mouse.button_pressed? :left
330
+ mouse_rel = Mouse.button_released? :left
331
+
332
+ if @state == :up
333
+ if mouse_over
334
+ @img_index = 1
335
+ @state = :over
336
+ else
337
+ @img_index = 0
338
+ end
339
+ elsif @state == :over
340
+ if not mouse_over
341
+ @img_index = 0
342
+ @state = :up
343
+ elsif mouse_press
344
+ @img_index = 2
345
+ @state = :down
346
+ else
347
+ @img_index = 1
348
+ end
349
+ elsif @state == :down
350
+ if not mouse_over
351
+ @img_index = 0
352
+ @state = :down_out
353
+ elsif mouse_rel
354
+ @img_index = 1
355
+ @state = :over
356
+ click
357
+ else
358
+ @img_index = 2
359
+ end
360
+ else # :down_out
361
+ if mouse_over
362
+ @img_index = 2
363
+ @state = :down
364
+ elsif mouse_rel
365
+ @img_index = 0
366
+ @state = :up
367
+ else
368
+ @img_index = 0
369
+ end
370
+ end
371
+ end
372
+
373
+ # Executes the button click action.
374
+ def click
375
+ @action.call @params if @action
376
+ end
377
+
378
+ # Sets the position of the button in the screen.
379
+ #
380
+ # Parameters:
381
+ # [x] The new x-coordinate for the button.
382
+ # [y] The new y-coordinate for the button.
383
+ def set_position(x, y)
384
+ if @center_x; @text_x = x + @w / 2
385
+ else; @text_x += x - @x; end
386
+ if @center_y; @text_y = y + @h / 2
387
+ else; @text_y += y - @y; end
388
+ @x = x; @y = y
389
+ end
390
+
391
+ # Draws the button in the screen.
392
+ #
393
+ # Parameters:
394
+ # [alpha] The opacity with which the button will be drawn. Allowed values
395
+ # vary between 0 (fully transparent) and 255 (fully opaque).
396
+ # [z_index] The z-order to draw the object. Objects with larger z-orders
397
+ # will be drawn on top of the ones with smaller z-orders.
398
+ # [color] Color to apply a filter to the image.
399
+ def draw(alpha = 0xff, z_index = 0, color = 0xffffff)
400
+ return unless @visible
401
+
402
+ color = (alpha << 24) | color
403
+ text_color =
404
+ if @enabled
405
+ if @state == :down
406
+ @down_text_color
407
+ else
408
+ @state == :over ? @over_text_color : @text_color
409
+ end
410
+ else
411
+ @disabled_text_color
412
+ end
413
+ text_color = (alpha << 24) | text_color
414
+ @img[@img_index].draw @x, @y, z_index, @scale_x, @scale_y, color if @img
415
+ if @text
416
+ if @center_x or @center_y
417
+ rel_x = @center_x ? 0.5 : 0
418
+ rel_y = @center_y ? 0.5 : 0
419
+ @font.draw_text_rel @text, @text_x, @text_y, z_index, rel_x, rel_y, @scale_x, @scale_y, text_color
420
+ else
421
+ @font.draw_text @text, @text_x, @text_y, z_index, @scale_x, @scale_y, text_color
422
+ end
423
+ end
424
+ end
425
+
426
+ def enabled=(value) # :nodoc:
427
+ @enabled = value
428
+ @state = :up
429
+ @img_index = 3
430
+ end
431
+ end
432
+
433
+ # This class represents a toggle button, which can be also interpreted as a
434
+ # check box. It is always in one of two states, given as +true+ or +false+
435
+ # by its property +checked+.
436
+ class ToggleButton < Button
437
+ # Defines the state of the button (returns +true+ or +false+).
438
+ attr_reader :checked
439
+
440
+ # Creates a ToggleButton. All parameters work the same as in Button,
441
+ # except for the image, +img+, which now has to be composed of two columns
442
+ # and four rows, the first column with images for the unchecked state,
443
+ # and the second with images for the checked state, and for +checked+,
444
+ # which defines the initial state of the ToggleButton.
445
+ #
446
+ # The +action+ block now will always receive a first boolean parameter
447
+ # corresponding to the value of +checked+. So, if you want to pass
448
+ # parameters to the block, you should declare it like this:
449
+ # b = ToggleButton.new ... { |checked, params|
450
+ # puts "button was checked" if checked
451
+ # # do something with params
452
+ # }
453
+ #
454
+ # *Obs.:* This method accepts named parameters, but +x+ and +y+ are
455
+ # mandatory (also, +img+ is mandatory when +width+ and +height+ are not
456
+ # provided, and vice-versa).
457
+ def initialize(x, y = nil, font = nil, text = nil, img = nil, checked = false,
458
+ text_color = 0, disabled_text_color = 0, over_text_color = 0, down_text_color = 0,
459
+ center_x = true, center_y = true, margin_x = 0, margin_y = 0, width = nil, height = nil,
460
+ params = nil, retro = nil, scale_x = 1, scale_y = 1, anchor = nil, &action)
461
+ if x.is_a? Hash
462
+ y = x[:y]
463
+ font = x[:font]
464
+ text = x[:text]
465
+ img = x[:img]
466
+ checked = x.fetch(:checked, false)
467
+ text_color = x.fetch(:text_color, 0)
468
+ disabled_text_color = x.fetch(:disabled_text_color, 0)
469
+ over_text_color = x.fetch(:over_text_color, 0)
470
+ down_text_color = x.fetch(:down_text_color, 0)
471
+ center_x = x.fetch(:center_x, true)
472
+ center_y = x.fetch(:center_y, true)
473
+ margin_x = x.fetch(:margin_x, 0)
474
+ margin_y = x.fetch(:margin_y, 0)
475
+ width = x.fetch(:width, nil)
476
+ height = x.fetch(:height, nil)
477
+ params = x.fetch(:params, nil)
478
+ retro = x.fetch(:retro, nil)
479
+ scale_x = x.fetch(:scale_x, 1)
480
+ scale_y = x.fetch(:scale_y, 1)
481
+ anchor = x.fetch(:anchor, nil)
482
+ x = x[:x]
483
+ end
484
+
485
+ super x, y, font, text, nil, text_color, disabled_text_color, over_text_color, down_text_color,
486
+ center_x, center_y, margin_x, margin_y, 0, 0, params, retro, scale_x, scale_y, anchor, &action
487
+ @img =
488
+ if img; Res.imgs img, 2, 4, true, '.png', retro
489
+ else; nil; end
490
+ @w =
491
+ if img; @img[0].width * @scale_x
492
+ else; width * @scale_x; end
493
+ @h =
494
+ if img; @img[0].height * @scale_y
495
+ else; height * @scale_y; end
496
+ _, x, y = FormUtils.check_anchor(anchor, @anchor_offset_x, @anchor_offset_y, @w, @h)
497
+ set_position(x, y)
498
+ @text_x = x + @w / 2 if center_x
499
+ @text_y = y + @h / 2 if center_y
500
+ @checked = checked
501
+ end
502
+
503
+ # Updates the button, checking the mouse movement and buttons to define
504
+ # the button state.
505
+ def update
506
+ return unless @enabled and @visible
507
+
508
+ super
509
+ @img_index *= 2
510
+ @img_index += 1 if @checked
511
+ end
512
+
513
+ # Executes the button click action, and toggles its state. The +action+
514
+ # block always receives as a first parameter +true+, if the button has
515
+ # been changed to checked, or +false+, otherwise.
516
+ def click
517
+ @checked = !@checked
518
+ @action.call @checked, @params if @action
519
+ end
520
+
521
+ # Sets the state of the button to the value given.
522
+ #
523
+ # Parameters:
524
+ # [value] The state to be set (+true+ for checked, +false+ for unchecked).
525
+ def checked=(value)
526
+ click if value != @checked
527
+ @checked = value
528
+ end
529
+
530
+ def enabled=(value) # :nodoc:
531
+ @enabled = value
532
+ @state = :up
533
+ @img_index = @checked ? 7 : 6
534
+ end
535
+ end
536
+
537
+ # This class represents a text field (input).
538
+ class TextField < Component
539
+ # The current text inside the text field.
540
+ attr_reader :text
541
+
542
+ # The current 'locale' used for detecting the keys. THIS FEATURE IS
543
+ # INCOMPLETE!
544
+ attr_reader :locale
545
+
546
+ # Creates a new text field.
547
+ #
548
+ # Parameters:
549
+ # [x] The x-coordinate where the text field will be drawn in the screen.
550
+ # [y] The y-coordinate where the text field will be drawn in the screen.
551
+ # [font] The <code>Gosu::Font</code> object that will be used to draw the
552
+ # text inside the field.
553
+ # [img] The image of the text field. For a good result, you would likely
554
+ # want something like a rectangle, horizontally wide, vertically
555
+ # short, and with a color that contrasts with the +text_color+.
556
+ # [cursor_img] An image for the blinking cursor that stands in the point
557
+ # where text will be inserted. If +nil+, a simple black line
558
+ # will be drawn instead.
559
+ # [disabled_img] Image for the text field when it's disabled. If +nil+,
560
+ # a darkened version of +img+ will be used.
561
+ # [text_color] Color of the button text, in hexadecimal RRGGBB format.
562
+ # [margin_x] The x offset, from the field x-coordinate, to draw the text.
563
+ # [margin_y] The y offset, from the field y-coordinate, to draw the text.
564
+ # [max_length] The maximum length of the text inside the field.
565
+ # [active] Whether the text field must be focused by default. If +false+,
566
+ # focus can be granted by clicking inside the text field or by
567
+ # calling the +focus+ method.
568
+ # [text] The starting text. Must not be +nil+.
569
+ # [allowed_chars] A string containing all characters that can be typed
570
+ # inside the text field. The complete set of supported
571
+ # characters is given by the string
572
+ # <code>"abcdefghijklmnopqrstuvwxyz1234567890 ABCDEFGHIJKLMNOPQRSTUVWXYZ'-=/[]\\\\,.;\"_+?{}|<>:!@#$%¨&*()"</code>.
573
+ # [text_color] The color with which the text will be drawn, in hexadecimal
574
+ # RRGGBB format.
575
+ # [disabled_text_color] The color with which the text will be drawn, when
576
+ # the text field is disabled, in hexadecimal RRGGBB
577
+ # format.
578
+ # [selection_color] The color of the rectangle highlighting selected text,
579
+ # in hexadecimal RRGGBB format. The rectangle will
580
+ # always be drawn with 50% of opacity.
581
+ # [locale] The locale to be used when detecting keys. By now, only 'en-US'
582
+ # and 'pt-BR' are **partially** supported. Default is 'en-US'. If
583
+ # any different value is supplied, all typed characters will be
584
+ # mapped to '#'.
585
+ # [params] An object containing any parameters you want passed to the
586
+ # +on_text_changed+ block. When the text of the text field is
587
+ # changed, the following is called:
588
+ # @on_text_changed.call @text, @params
589
+ # Thus, +params+ will be the second parameter. Note that this
590
+ # doesn't force you to declare a block that takes parameters.
591
+ # [retro] Whether the images should be loaded with the 'retro' option set
592
+ # (see +Gosu::Image+ for details). If the value is omitted, the
593
+ # +Res.retro_images+ value will be used.
594
+ # [scale_x] Horizontal scale to draw the component with.
595
+ # [scale_y] Vertical scale to draw the component with.
596
+ # [anchor] See parameter with the same name in <code>Panel#initialize</code> for details.
597
+ # [on_text_changed] The block of code executed when the text in the text
598
+ # field is changed, either by user input or by calling
599
+ # +text=+. The new text is passed as a first parameter
600
+ # to this block, followed by +params+. Can be +nil+.
601
+ #
602
+ # *Obs.:* This method accepts named parameters, but +x+, +y+, +font+ and
603
+ # +img+ are mandatory.
604
+ def initialize(x, y = nil, font = nil, img = nil, cursor_img = nil, disabled_img = nil, margin_x = 0, margin_y = 0,
605
+ max_length = 100, active = false, text = '', allowed_chars = nil,
606
+ text_color = 0, disabled_text_color = 0, selection_color = 0, locale = 'en-us',
607
+ params = nil, retro = nil, scale_x = 1, scale_y = 1, anchor = nil, &on_text_changed)
608
+ if x.is_a? Hash
609
+ y = x[:y]
610
+ font = x[:font]
611
+ img = x[:img]
612
+ cursor_img = x.fetch(:cursor_img, nil)
613
+ disabled_img = x.fetch(:disabled_img, nil)
614
+ margin_x = x.fetch(:margin_x, 0)
615
+ margin_y = x.fetch(:margin_y, 0)
616
+ max_length = x.fetch(:max_length, 100)
617
+ active = x.fetch(:active, false)
618
+ text = x.fetch(:text, '')
619
+ allowed_chars = x.fetch(:allowed_chars, nil)
620
+ text_color = x.fetch(:text_color, 0)
621
+ disabled_text_color = x.fetch(:disabled_text_color, 0)
622
+ selection_color = x.fetch(:selection_color, 0)
623
+ locale = x.fetch(:locale, 'en-us')
624
+ params = x.fetch(:params, nil)
625
+ retro = x.fetch(:retro, nil)
626
+ scale_x = x.fetch(:scale_x, 1)
627
+ scale_y = x.fetch(:scale_y, 1)
628
+ anchor = x.fetch(:anchor, nil)
629
+ x = x[:x]
630
+ end
631
+
632
+ retro = Res.retro_images if retro.nil?
633
+ @scale_x = scale_x
634
+ @scale_y = scale_y
635
+ @img = Res.img img, false, false, '.png', retro
636
+ @w = @img.width * @scale_x
637
+ @h = @img.height * @scale_y
638
+
639
+ @anchor_offset_x = x; @anchor_offset_y = y
640
+ @anchor, x, y = FormUtils.check_anchor(anchor, x, y, @w, @h)
641
+
642
+ super x, y, font, text, text_color, disabled_text_color
643
+ @cursor_img = Res.img(cursor_img, false, false, '.png', retro) if cursor_img
644
+ @disabled_img = Res.img(disabled_img, false, false, '.png', retro) if disabled_img
645
+ @max_length = max_length
646
+ @active = active
647
+ @text_x = x + margin_x * @scale_x
648
+ @text_y = y + margin_y * @scale_y
649
+ @selection_color = selection_color
650
+
651
+ @nodes = [@text_x]
652
+ send(:text=, text, false) if text
653
+
654
+ @cur_node = 0
655
+ @cursor_visible = false
656
+ @cursor_timer = 0
657
+
658
+ @k = [
659
+ Gosu::KbA, Gosu::KbB, Gosu::KbC, Gosu::KbD, Gosu::KbE, Gosu::KbF,
660
+ Gosu::KbG, Gosu::KbH, Gosu::KbI, Gosu::KbJ, Gosu::KbK, Gosu::KbL,
661
+ Gosu::KbM, Gosu::KbN, Gosu::KbO, Gosu::KbP, Gosu::KbQ, Gosu::KbR,
662
+ Gosu::KbS, Gosu::KbT, Gosu::KbU, Gosu::KbV, Gosu::KbW, Gosu::KbX,
663
+ Gosu::KbY, Gosu::KbZ, Gosu::Kb1, Gosu::Kb2, Gosu::Kb3, Gosu::Kb4,
664
+ Gosu::Kb5, Gosu::Kb6, Gosu::Kb7, Gosu::Kb8, Gosu::Kb9, Gosu::Kb0,
665
+ Gosu::KbNumpad1, Gosu::KbNumpad2, Gosu::KbNumpad3, Gosu::KbNumpad4,
666
+ Gosu::KbNumpad5, Gosu::KbNumpad6, Gosu::KbNumpad7, Gosu::KbNumpad8,
667
+ Gosu::KbNumpad9, Gosu::KbNumpad0, Gosu::KbSpace, Gosu::KbBackspace,
668
+ Gosu::KbDelete, Gosu::KbLeft, Gosu::KbRight, Gosu::KbHome,
669
+ Gosu::KbEnd, Gosu::KbLeftShift, Gosu::KbRightShift,
670
+ Gosu::KbBacktick, Gosu::KbMinus, Gosu::KbEqual, Gosu::KbBracketLeft,
671
+ Gosu::KbBracketRight, Gosu::KbBackslash, Gosu::KbSemicolon,
672
+ Gosu::KbApostrophe, Gosu::KbComma, Gosu::KbPeriod, Gosu::KbSlash,
673
+ Gosu::KbNumpadAdd, Gosu::KbNumpadSubtract,
674
+ Gosu::KbNumpadMultiply, Gosu::KbNumpadDivide
675
+ ]
676
+ @user_allowed_chars = allowed_chars
677
+ self.locale = locale
678
+
679
+ @on_text_changed = on_text_changed
680
+ @params = params
681
+ end
682
+
683
+ # Updates the text field, checking for mouse events and keyboard input.
684
+ def update
685
+ return unless @enabled and @visible
686
+
687
+ ################################ Mouse ################################
688
+ if Mouse.over? @x, @y, @w, @h
689
+ if not @active and Mouse.button_pressed? :left
690
+ focus
691
+ end
692
+ elsif Mouse.button_pressed? :left
693
+ unfocus
694
+ end
695
+
696
+ return unless @active
697
+
698
+ if Mouse.double_click? :left
699
+ if @nodes.size > 1
700
+ @anchor1 = 0
701
+ @anchor2 = @nodes.size - 1
702
+ @cur_node = @anchor2
703
+ @double_clicked = true
704
+ end
705
+ set_cursor_visible
706
+ elsif Mouse.button_pressed? :left
707
+ set_node_by_mouse
708
+ @anchor1 = @cur_node
709
+ @anchor2 = nil
710
+ @double_clicked = false
711
+ set_cursor_visible
712
+ elsif Mouse.button_down? :left
713
+ if @anchor1 and not @double_clicked
714
+ set_node_by_mouse
715
+ if @cur_node != @anchor1; @anchor2 = @cur_node
716
+ else; @anchor2 = nil; end
717
+ set_cursor_visible
718
+ end
719
+ elsif Mouse.button_released? :left
720
+ if @anchor1 and not @double_clicked
721
+ if @cur_node != @anchor1; @anchor2 = @cur_node
722
+ else; @anchor1 = nil; end
723
+ end
724
+ end
725
+
726
+ @cursor_timer += 1
727
+ if @cursor_timer >= 30
728
+ @cursor_visible = (not @cursor_visible)
729
+ @cursor_timer = 0
730
+ end
731
+
732
+ ############################### Keyboard ##############################
733
+ shift = (KB.key_down?(@k[53]) or KB.key_down?(@k[54]))
734
+ if KB.key_pressed?(@k[53]) or KB.key_pressed?(@k[54]) # shift
735
+ @anchor1 = @cur_node if @anchor1.nil?
736
+ elsif KB.key_released?(@k[53]) or KB.key_released?(@k[54])
737
+ @anchor1 = nil if @anchor2.nil?
738
+ end
739
+ inserted = false
740
+ for i in 0..46 # alnum
741
+ if KB.key_pressed?(@k[i]) or KB.key_held?(@k[i])
742
+ remove_interval true if @anchor1 and @anchor2
743
+ if i < 26
744
+ if shift
745
+ insert_char @chars[i + 37]
746
+ else
747
+ insert_char @chars[i]
748
+ end
749
+ elsif i < 36
750
+ if shift; insert_char @chars[i + 59]
751
+ else; insert_char @chars[i]; end
752
+ elsif shift
753
+ insert_char(@chars[i + 49])
754
+ else
755
+ insert_char(@chars[i - 10])
756
+ end
757
+ inserted = true
758
+ break
759
+ end
760
+ end
761
+
762
+ return if inserted
763
+ for i in 55..65 # special
764
+ if KB.key_pressed?(@k[i]) or KB.key_held?(@k[i])
765
+ remove_interval true if @anchor1 and @anchor2
766
+ if shift; insert_char @chars[i + 19]
767
+ else; insert_char @chars[i + 8]; end
768
+ inserted = true
769
+ break
770
+ end
771
+ end
772
+
773
+ return if inserted
774
+ for i in 66..69 # numpad operators
775
+ if KB.key_pressed?(@k[i]) or KB.key_held?(@k[i])
776
+ remove_interval true if @anchor1 and @anchor2
777
+ insert_char @chars[i + 19]
778
+ inserted = true
779
+ break
780
+ end
781
+ end
782
+
783
+ return if inserted
784
+ if KB.key_pressed?(@k[47]) or KB.key_held?(@k[47]) # back
785
+ if @anchor1 and @anchor2
786
+ remove_interval
787
+ elsif @cur_node > 0
788
+ remove_char true
789
+ end
790
+ elsif KB.key_pressed?(@k[48]) or KB.key_held?(@k[48]) # del
791
+ if @anchor1 and @anchor2
792
+ remove_interval
793
+ elsif @cur_node < @nodes.size - 1
794
+ remove_char false
795
+ end
796
+ elsif KB.key_pressed?(@k[49]) or KB.key_held?(@k[49]) # left
797
+ if @anchor1
798
+ if shift
799
+ if @cur_node > 0
800
+ @cur_node -= 1
801
+ @anchor2 = @cur_node
802
+ set_cursor_visible
803
+ end
804
+ elsif @anchor2
805
+ @cur_node = @anchor1 < @anchor2 ? @anchor1 : @anchor2
806
+ @anchor1 = nil
807
+ @anchor2 = nil
808
+ set_cursor_visible
809
+ end
810
+ elsif @cur_node > 0
811
+ @cur_node -= 1
812
+ set_cursor_visible
813
+ end
814
+ elsif KB.key_pressed?(@k[50]) or KB.key_held?(@k[50]) # right
815
+ if @anchor1
816
+ if shift
817
+ if @cur_node < @nodes.size - 1
818
+ @cur_node += 1
819
+ @anchor2 = @cur_node
820
+ set_cursor_visible
821
+ end
822
+ elsif @anchor2
823
+ @cur_node = @anchor1 > @anchor2 ? @anchor1 : @anchor2
824
+ @anchor1 = nil
825
+ @anchor2 = nil
826
+ set_cursor_visible
827
+ end
828
+ elsif @cur_node < @nodes.size - 1
829
+ @cur_node += 1
830
+ set_cursor_visible
831
+ end
832
+ elsif KB.key_pressed?(@k[51]) # home
833
+ @cur_node = 0
834
+ if shift; @anchor2 = @cur_node
835
+ else
836
+ @anchor1 = nil
837
+ @anchor2 = nil
838
+ end
839
+ set_cursor_visible
840
+ elsif KB.key_pressed?(@k[52]) # end
841
+ @cur_node = @nodes.size - 1
842
+ if shift; @anchor2 = @cur_node
843
+ else
844
+ @anchor1 = nil
845
+ @anchor2 = nil
846
+ end
847
+ set_cursor_visible
848
+ end
849
+ end
850
+
851
+ # Sets the text of the text field to the specified value.
852
+ #
853
+ # Parameters:
854
+ # [value] The new text to be set. If it's longer than the +max_length+
855
+ # parameter used in the constructor, it will be truncated to
856
+ # +max_length+ characters.
857
+ def text=(value, trigger_changed = true)
858
+ @text = value[0...@max_length]
859
+ @nodes.clear; @nodes << @text_x
860
+ x = @nodes[0]
861
+ @text.chars.each { |char|
862
+ x += @font.text_width(char) * @scale_x
863
+ @nodes << x
864
+ }
865
+ @cur_node = @nodes.size - 1
866
+ @anchor1 = nil
867
+ @anchor2 = nil
868
+ set_cursor_visible
869
+ @on_text_changed.call @text, @params if trigger_changed && @on_text_changed
870
+ end
871
+
872
+ # Sets the locale used by the text field to detect keys. Only 'en-us' and
873
+ # 'pt-br' are **partially** supported. If any different value is supplied,
874
+ # all typed characters will be mapped to '#'.
875
+ def locale=(value)
876
+ @locale = value.downcase
877
+ @chars =
878
+ case @locale
879
+ when 'en-us' then "abcdefghijklmnopqrstuvwxyz1234567890 ABCDEFGHIJKLMNOPQRSTUVWXYZ`-=[]\\;',./~_+{}|:\"<>?!@#$%^&*()+-*/"
880
+ when 'pt-br' then "abcdefghijklmnopqrstuvwxyz1234567890 ABCDEFGHIJKLMNOPQRSTUVWXYZ'-=/[]ç~,.;\"_+?{}Ç^<>:!@#$%¨&*()+-*/"
881
+ else '###################################################################################################'
882
+ end
883
+ @allowed_chars =
884
+ if @user_allowed_chars
885
+ @user_allowed_chars
886
+ else
887
+ @chars
888
+ end
889
+ end
890
+
891
+ # Returns the currently selected text.
892
+ def selected_text
893
+ return '' if @anchor2.nil?
894
+ min = @anchor1 < @anchor2 ? @anchor1 : @anchor2
895
+ max = min == @anchor1 ? @anchor2 : @anchor1
896
+ @text[min..max]
897
+ end
898
+
899
+ # Grants focus to the text field, so that it allows keyboard input.
900
+ def focus
901
+ @active = true
902
+ end
903
+
904
+ # Removes focus from the text field, so that no keyboard input will be
905
+ # accepted.
906
+ def unfocus
907
+ @anchor1 = @anchor2 = nil
908
+ @cursor_visible = false
909
+ @cursor_timer = 0
910
+ @active = false
911
+ end
912
+
913
+ # Sets the position of the text field in the screen.
914
+ #
915
+ # Parameters:
916
+ # [x] The new x-coordinate for the text field.
917
+ # [y] The new y-coordinate for the text field.
918
+ def set_position(x, y)
919
+ d_x = x - @x
920
+ d_y = y - @y
921
+ @x = x; @y = y
922
+ @text_x += d_x
923
+ @text_y += d_y
924
+ @nodes.map! do |n|
925
+ n + d_x
926
+ end
927
+ end
928
+
929
+ # Draws the text field in the screen.
930
+ #
931
+ # Parameters:
932
+ # [alpha] The opacity with which the text field will be drawn. Allowed
933
+ # values vary between 0 (fully transparent) and 255 (fully opaque).
934
+ # [z_index] The z-order to draw the object. Objects with larger z-orders
935
+ # will be drawn on top of the ones with smaller z-orders.
936
+ # [color] Color to apply a filter to the image.
937
+ # [disabled_color] Color to apply a filter to the image when the field is
938
+ # disabled.
939
+ def draw(alpha = 0xff, z_index = 0, color = 0xffffff, disabled_color = 0x808080)
940
+ return unless @visible
941
+
942
+ color = (alpha << 24) | ((@enabled or @disabled_img) ? color : disabled_color)
943
+ text_color = (alpha << 24) | (@enabled ? @text_color : @disabled_text_color)
944
+ img = ((@enabled or @disabled_img.nil?) ? @img : @disabled_img)
945
+ img.draw @x, @y, z_index, @scale_x, @scale_y, color
946
+ @font.draw_text @text, @text_x, @text_y, z_index, @scale_x, @scale_y, text_color
947
+
948
+ if @anchor1 and @anchor2
949
+ selection_color = ((alpha / 2) << 24) | @selection_color
950
+ G.window.draw_quad @nodes[@anchor1], @text_y, selection_color,
951
+ @nodes[@anchor2] + 1, @text_y, selection_color,
952
+ @nodes[@anchor2] + 1, @text_y + @font.height * @scale_y, selection_color,
953
+ @nodes[@anchor1], @text_y + @font.height * @scale_y, selection_color, z_index
954
+ end
955
+
956
+ if @cursor_visible
957
+ if @cursor_img
958
+ @cursor_img.draw @nodes[@cur_node] - (@cursor_img.width * @scale_x) / 2, @text_y, z_index, @scale_x, @scale_y
959
+ else
960
+ cursor_color = alpha << 24
961
+ G.window.draw_quad @nodes[@cur_node], @text_y, cursor_color,
962
+ @nodes[@cur_node] + 1, @text_y, cursor_color,
963
+ @nodes[@cur_node] + 1, @text_y + @font.height * @scale_y, cursor_color,
964
+ @nodes[@cur_node], @text_y + @font.height * @scale_y, cursor_color, z_index
965
+ end
966
+ end
967
+ end
968
+
969
+ def enabled=(value) # :nodoc:
970
+ @enabled = value
971
+ unfocus unless @enabled
972
+ end
973
+
974
+ def visible=(value) # :nodoc:
975
+ @visible = value
976
+ unfocus unless @visible
977
+ end
978
+
979
+ private
980
+
981
+ def set_cursor_visible
982
+ @cursor_visible = true
983
+ @cursor_timer = 0
984
+ end
985
+
986
+ def set_node_by_mouse
987
+ index = @nodes.size - 1
988
+ @nodes.each_with_index do |n, i|
989
+ if n >= Mouse.x
990
+ index = i
991
+ break
992
+ end
993
+ end
994
+ if index > 0
995
+ d1 = @nodes[index] - Mouse.x; d2 = Mouse.x - @nodes[index - 1]
996
+ index -= 1 if d1 > d2
997
+ end
998
+ @cur_node = index
999
+ end
1000
+
1001
+ def insert_char(char)
1002
+ return unless @allowed_chars.index char and @text.length < @max_length
1003
+ @text.insert @cur_node, char
1004
+ @nodes.insert @cur_node + 1, @nodes[@cur_node] + @font.text_width(char) * @scale_x
1005
+ for i in (@cur_node + 2)..(@nodes.size - 1)
1006
+ @nodes[i] += @font.text_width(char) * @scale_x
1007
+ end
1008
+ @cur_node += 1
1009
+ set_cursor_visible
1010
+ @on_text_changed.call @text, @params if @on_text_changed
1011
+ end
1012
+
1013
+ def remove_interval(will_insert = false)
1014
+ min = @anchor1 < @anchor2 ? @anchor1 : @anchor2
1015
+ max = min == @anchor1 ? @anchor2 : @anchor1
1016
+ interval_width = 0
1017
+ for i in min...max
1018
+ interval_width += @font.text_width(@text[i]) * @scale_x
1019
+ @nodes.delete_at min + 1
1020
+ end
1021
+ @text[min...max] = ''
1022
+ for i in (min + 1)..(@nodes.size - 1)
1023
+ @nodes[i] -= interval_width
1024
+ end
1025
+ @cur_node = min
1026
+ @anchor1 = nil
1027
+ @anchor2 = nil
1028
+ set_cursor_visible
1029
+ @on_text_changed.call @text, @params if @on_text_changed and not will_insert
1030
+ end
1031
+
1032
+ def remove_char(back)
1033
+ @cur_node -= 1 if back
1034
+ char_width = @font.text_width(@text[@cur_node]) * @scale_x
1035
+ @text[@cur_node] = ''
1036
+ @nodes.delete_at @cur_node + 1
1037
+ for i in (@cur_node + 1)..(@nodes.size - 1)
1038
+ @nodes[i] -= char_width
1039
+ end
1040
+ set_cursor_visible
1041
+ @on_text_changed.call @text, @params if @on_text_changed
1042
+ end
1043
+ end
1044
+
1045
+ # Represents a progress bar.
1046
+ class ProgressBar < Component
1047
+ # The maximum value for this progress bar (when the current value equals
1048
+ # the maximum, the bar is full).
1049
+ attr_reader :max_value
1050
+
1051
+ # The current value of the progress bar (an integer greater than or equal
1052
+ # to zero, and less than or equal to +max_value+).
1053
+ attr_reader :value
1054
+
1055
+ # Creates a progress bar.
1056
+ #
1057
+ # Parameters:
1058
+ # [x] The x-coordinate of the progress bar on the screen.
1059
+ # [y] The y-coordinate of the progress bar on the screen.
1060
+ # [w] Width of the progress bar, in pixels. This is the maximum space the
1061
+ # bar foreground can occupy. Note that the width of the foreground image
1062
+ # (+fg+) can be less than this, in which case the image will be
1063
+ # horizontally repeated to fill all the needed space.
1064
+ # [h] Height of the progress bar. This will be the height of the bar
1065
+ # foreground when +fg+ is a color (when it is an image, the height of
1066
+ # the image will be kept).
1067
+ # [bg] A background image (string or symbol that will be passed to
1068
+ # +Res.img+) or color (in RRGGBB hexadecimal format).
1069
+ # [fg] A foreground image (string or symbol that will be passed to
1070
+ # +Res.img+) or color (in RRGGBB hexadecimal format). The image will
1071
+ # be horizontally repeated when needed, if its width is less than +w+.
1072
+ # [max_value] The maximum value the progress bar can reach (an integer).
1073
+ # [value] The starting value for the progress bar.
1074
+ # [fg_margin_x] Horizontal margin between the background image and the
1075
+ # foreground image (when these are provided).
1076
+ # [fg_margin_y] Vertical margin between the background image and the
1077
+ # foreground image (when these are provided).
1078
+ # [font] Font that will be used to draw a text indicating the value of the
1079
+ # progress bar.
1080
+ # [text_color] Color of the text.
1081
+ # [format] Format to display the value. Specify '%' for a percentage and
1082
+ # anything else for absolute values (current/maximum).
1083
+ # [retro] Whether the images should be loaded with the 'retro' option set
1084
+ # (see +Gosu::Image+ for details). If the value is omitted, the
1085
+ # +Res.retro_images+ value will be used.
1086
+ # [scale_x] Horizontal scale to draw the component with.
1087
+ # [scale_y] Vertical scale to draw the component with.
1088
+ # [anchor] See parameter with the same name in <code>Panel#initialize</code> for details.
1089
+ #
1090
+ # *Obs.:* This method accepts named parameters, but +x+, +y+, +w+, +h+, +bg+
1091
+ # and +fg+ are mandatory.
1092
+ def initialize(x, y = nil, w = nil, h = nil, bg = nil, fg = nil,
1093
+ max_value = 100, value = 100, fg_margin_x = 0, fg_margin_y = 0, # fg_left = nil, fg_right = nil,
1094
+ font = nil, text_color = 0, format = nil, retro = nil, scale_x = 1, scale_y = 1, anchor = nil)
1095
+ if x.is_a? Hash
1096
+ y = x[:y]
1097
+ w = x[:w]
1098
+ h = x[:h]
1099
+ bg = x[:bg]
1100
+ fg = x[:fg]
1101
+ max_value = x.fetch(:max_value, 100)
1102
+ value = x.fetch(:value, 100)
1103
+ fg_margin_x = x.fetch(:fg_margin_x, 0)
1104
+ fg_margin_y = x.fetch(:fg_margin_y, 0)
1105
+ font = x.fetch(:font, nil)
1106
+ text_color = x.fetch(:text_color, 0)
1107
+ format = x.fetch(:format, nil)
1108
+ retro = x.fetch(:retro, nil)
1109
+ scale_x = x.fetch(:scale_x, 1)
1110
+ scale_y = x.fetch(:scale_y, 1)
1111
+ anchor = x.fetch(:anchor, nil)
1112
+ x = x[:x]
1113
+ end
1114
+
1115
+ @scale_x = scale_x
1116
+ @scale_y = scale_y
1117
+ retro = Res.retro_images if retro.nil?
1118
+ if bg.is_a? Integer
1119
+ @bg_color = bg
1120
+ else # String or Symbol
1121
+ @bg = Res.img bg, false, false, '.png', retro
1122
+ end
1123
+ if fg.is_a? Integer
1124
+ @fg_color = fg
1125
+ else # String or Symbol
1126
+ @fg = Res.img fg, false, false, '.png', retro
1127
+ @fg_path = "#{Res.prefix}#{Res.img_dir}#{fg.to_s.gsub(Res.separator, '/')}.png"
1128
+ end
1129
+ @fg_margin_x = fg_margin_x * @scale_x
1130
+ @fg_margin_y = fg_margin_y * @scale_y
1131
+
1132
+ @w = (@bg ? @bg.width : w) * @scale_x
1133
+ @h = (@bg ? @bg.height : h) * @scale_y
1134
+
1135
+ @anchor_offset_x = x; @anchor_offset_y = y
1136
+ @anchor, x, y = FormUtils.check_anchor(anchor, x, y, @w, @h)
1137
+
1138
+ super x, y, font, '', text_color, text_color
1139
+ # @fg_left = fg_left
1140
+ # @fg_right = fg_right
1141
+ @max_value = max_value
1142
+ self.value = value
1143
+ @format = format
1144
+ @retro = retro
1145
+ end
1146
+
1147
+ # Increases the current value of the progress bar by the given amount.
1148
+ #
1149
+ # Parameters:
1150
+ # [amount] (+Integer+) The amount to be added to the current value. If the
1151
+ # sum surpasses +max_value+, it is set to +max_value+.
1152
+ def increase(amount)
1153
+ @value += amount
1154
+ @value = @max_value if @value > @max_value
1155
+ end
1156
+
1157
+ # Descreases the current value of the progress bar by the given amount.
1158
+ #
1159
+ # Parameters:
1160
+ # [amount] (+Integer+) The amount to be subtracted from the current value.
1161
+ # If the result is less than zero, it is set to zero.
1162
+ def decrease(amount)
1163
+ @value -= amount
1164
+ @value = 0 if @value < 0
1165
+ end
1166
+
1167
+ # Sets the value of the progress bar.
1168
+ #
1169
+ # Parameters:
1170
+ # [val] (+Integer+) The value to be set. It will be changed as needed to be
1171
+ # between zero and +max_value+.
1172
+ def value=(val)
1173
+ @value = val
1174
+ if @value > @max_value
1175
+ @value = @max_value
1176
+ elsif @value < 0
1177
+ @value = 0
1178
+ end
1179
+ end
1180
+
1181
+ # Sets the value of the progress bar to a given percentage of +max_value+.
1182
+ #
1183
+ # Parameters:
1184
+ # [pct] (+Numeric+) The percentage of +max_value+ to set the current value
1185
+ # to. The final result will be changed as needed to be between zero
1186
+ # and +max_value+.
1187
+ def percentage=(pct)
1188
+ self.value = (pct * @max_value).round
1189
+ end
1190
+
1191
+ # Draws the progress bar.
1192
+ #
1193
+ # Parameters:
1194
+ # [alpha] (+Fixnum+) The opacity with which the progress bar will be drawn.
1195
+ # Allowed values vary between 0 (fully transparent) and 255 (fully
1196
+ # opaque).
1197
+ # [z_index] (+Fixnum+) The z-order to draw the object. Objects with larger
1198
+ # z-orders will be drawn on top of the ones with smaller z-orders.
1199
+ # [color] Color to apply a filter to the images (when these are provided).
1200
+ def draw(alpha = 0xff, z_index = 0, color = 0xffffff)
1201
+ return unless @visible
1202
+
1203
+ if @bg
1204
+ c = (alpha << 24) | color
1205
+ @bg.draw @x, @y, z_index, @scale_x, @scale_y, c
1206
+ else
1207
+ c = (alpha << 24) | @bg_color
1208
+ G.window.draw_quad @x, @y, c,
1209
+ @x + @w, @y, c,
1210
+ @x + @w, @y + @h, c,
1211
+ @x, @y + @h, c, z_index
1212
+ end
1213
+ if @fg
1214
+ c = (alpha << 24) | color
1215
+ w1 = @fg.width * @scale_x
1216
+ w2 = (@value.to_f / @max_value * @w).round
1217
+ x0 = @x + @fg_margin_x
1218
+ x = 0
1219
+ while x <= w2 - w1
1220
+ @fg.draw x0 + x, @y + @fg_margin_y, z_index, @scale_x, @scale_y, c
1221
+ x += w1
1222
+ end
1223
+ if w2 - x > 0
1224
+ img = Gosu::Image.new(@fg_path, tileable: true, retro: @retro, rect: [0, 0, ((w2 - x) / @scale_x).round, @fg.height])
1225
+ img.draw x0 + x, @y + @fg_margin_y, z_index, @scale_x, @scale_y, c
1226
+ end
1227
+ else
1228
+ c = (alpha << 24) | @fg_color
1229
+ rect_r = @x + (@value.to_f / @max_value * @w).round
1230
+ G.window.draw_quad @x, @y, c,
1231
+ rect_r, @y, c,
1232
+ rect_r, @y + @h, c,
1233
+ @x, @y + @h, c, z_index
1234
+ end
1235
+ if @font
1236
+ c = (alpha << 24) | @text_color
1237
+ @text = @format == '%' ? "#{(@value.to_f / @max_value * 100).round}%" : "#{@value}/#{@max_value}"
1238
+ @font.draw_text_rel @text, @x + @w / 2, @y + @h / 2, z_index, 0.5, 0.5, @scale_x, @scale_y, c
1239
+ end
1240
+ end
1241
+ end
1242
+
1243
+ # This class represents a "drop-down list" form component, here composed of a
1244
+ # group of +Button+ objects.
1245
+ class DropDownList < Component
1246
+ # The selected value in the drop-down list. This is one of the +options+.
1247
+ attr_reader :value
1248
+
1249
+ # An array containing all the options (each of them +String+s) that can be
1250
+ # selected in the drop-down list.
1251
+ attr_accessor :options
1252
+
1253
+ # Creates a new drop-down list.
1254
+ #
1255
+ # Parameters:
1256
+ # [x] The x-coordinate of the object.
1257
+ # [y] The y-coordinate of the object.
1258
+ # [font] Font to be used by the buttons that compose the drop-down list.
1259
+ # [img] Image of the main button, i.e., the one at the top, that toggles
1260
+ # visibility of the other buttons (the "option" buttons).
1261
+ # [opt_img] Image for the "option" buttons, as described above.
1262
+ # [options] Array of available options for this control (+String+s).
1263
+ # [option] Index of the firstly selected option.
1264
+ # [text_margin] Left margin of the text inside the buttons (vertically, the
1265
+ # text will always be centered).
1266
+ # [width] Width of the control, used when no image is provided.
1267
+ # [height] Height of the control, used when no image is provided.
1268
+ # [text_color] Used as the +text_color+ parameter in the constructor of the
1269
+ # buttons.
1270
+ # [disabled_text_color] Analogous to +text_color+.
1271
+ # [over_text_color] Same as above.
1272
+ # [down_text_color] Same as above.
1273
+ # [retro] Whether the images should be loaded with the 'retro' option set
1274
+ # (see +Gosu::Image+ for details). If the value is omitted, the
1275
+ # +Res.retro_images+ value will be used.
1276
+ # [scale_x] Horizontal scale to draw the component with.
1277
+ # [scale_y] Vertical scale to draw the component with.
1278
+ # [anchor] See parameter with the same name in <code>Panel#initialize</code> for details.
1279
+ # [on_changed] Action performed when the value of the dropdown is changed.
1280
+ # It must be a block with two parameters, which will receive
1281
+ # the old and the new value, respectively.
1282
+ #
1283
+ # *Obs.:* This method accepts named parameters, but +x+, +y+, +font+ and
1284
+ # +options+ are mandatory (also, +img+ and +opt_img+ are mandatory when
1285
+ # +width+ and +height+ are not provided, and vice-versa).
1286
+ def initialize(x, y = nil, font = nil, img = nil, opt_img = nil, options = nil,
1287
+ option = 0, text_margin = 0, width = nil, height = nil,
1288
+ text_color = 0, disabled_text_color = 0, over_text_color = 0, down_text_color = 0,
1289
+ retro = nil, scale_x = 1, scale_y = 1, anchor = nil, &on_changed)
1290
+ if x.is_a? Hash
1291
+ y = x[:y]
1292
+ font = x[:font]
1293
+ img = x[:img]
1294
+ opt_img = x[:opt_img]
1295
+ options = x[:options]
1296
+ option = x.fetch(:option, 0)
1297
+ text_margin = x.fetch(:text_margin, 0)
1298
+ width = x.fetch(:width, nil)
1299
+ height = x.fetch(:height, nil)
1300
+ text_color = x.fetch(:text_color, 0)
1301
+ disabled_text_color = x.fetch(:disabled_text_color, 0)
1302
+ over_text_color = x.fetch(:over_text_color, 0)
1303
+ down_text_color = x.fetch(:down_text_color, 0)
1304
+ retro = x.fetch(:retro, nil)
1305
+ scale_x = x.fetch(:scale_x, 1)
1306
+ scale_y = x.fetch(:scale_y, 1)
1307
+ anchor = x.fetch(:anchor, nil)
1308
+ x = x[:x]
1309
+ end
1310
+ @img = img
1311
+ @opt_img = opt_img
1312
+ @options = options
1313
+ @value = @options[option]
1314
+ @open = false
1315
+ @buttons = []
1316
+ @buttons.push(
1317
+ Button.new(x, y, font, @value, img, text_color, disabled_text_color, over_text_color, down_text_color,
1318
+ false, true, text_margin, 0, width, height, nil, retro, scale_x, scale_y) {
1319
+ toggle
1320
+ }
1321
+ )
1322
+
1323
+ @scale_x = scale_x
1324
+ @scale_y = scale_y
1325
+ @w = @buttons[0].w
1326
+ @h = @buttons[0].h
1327
+ @max_h = (@options.size + 1) * @h
1328
+
1329
+ @anchor_offset_x = x; @anchor_offset_y = y
1330
+ @anchor, x, y = FormUtils.check_anchor(anchor, x, y, @w, @h)
1331
+ super x, y, font, options[option], text_color, disabled_text_color
1332
+ @buttons[0].set_position(x, y)
1333
+
1334
+ @options.each_with_index do |o, i|
1335
+ b = Button.new(x, y + (i+1) * @h, font, o, opt_img, text_color, disabled_text_color, over_text_color, down_text_color,
1336
+ false, true, text_margin, 0, width, height, nil, retro, scale_x, scale_y) {
1337
+ old = @value
1338
+ @value = @buttons[0].text = o
1339
+ @on_changed.call(old, o) if @on_changed
1340
+ toggle
1341
+ }
1342
+ b.visible = false
1343
+ @buttons.push b
1344
+ end
1345
+
1346
+ @on_changed = on_changed
1347
+ end
1348
+
1349
+ # Updates the control.
1350
+ def update
1351
+ return unless @enabled and @visible
1352
+ if @open and Mouse.button_pressed? :left and not Mouse.over?(@x, @y, @w, @max_h)
1353
+ toggle
1354
+ return
1355
+ end
1356
+ @buttons.each { |b| b.update }
1357
+ end
1358
+
1359
+ # Sets the currently selected value of the drop-down list. It is ignored if
1360
+ # it is not among the available options.
1361
+ def value=(val)
1362
+ if @options.include? val
1363
+ old = @value
1364
+ @value = @buttons[0].text = val
1365
+ @on_changed.call(old, val) if @on_changed
1366
+ end
1367
+ end
1368
+
1369
+ def enabled=(value) # :nodoc:
1370
+ toggle if @open
1371
+ @buttons[0].enabled = value
1372
+ @enabled = value
1373
+ end
1374
+
1375
+ def set_position(x, y)
1376
+ @x = x; @y = y
1377
+ @buttons.each_with_index { |b, i| b.set_position(x, y + i * @h) }
1378
+ end
1379
+
1380
+ # Draws the drop-down list.
1381
+ #
1382
+ # Parameters:
1383
+ # [alpha] (+Fixnum+) The opacity with which the drop-down list will be
1384
+ # drawn. Allowed values vary between 0 (fully transparent) and 255
1385
+ # (fully opaque).
1386
+ # [z_index] (+Fixnum+) The z-order to draw the object. Objects with larger
1387
+ # z-orders will be drawn on top of the ones with smaller z-orders.
1388
+ # [color] Color of the buttons, if no image was provided, or color to apply
1389
+ # a filter to the images.
1390
+ # [over_color] Color of the buttons when the mouse is over them (when no
1391
+ # image was provided).
1392
+ def draw(alpha = 0xff, z_index = 0, color = 0xffffff, over_color = 0xcccccc)
1393
+ return unless @visible
1394
+ unless @img
1395
+ bottom = @y + (@open ? @max_h : @h) + @scale_y
1396
+ b_color = (alpha << 24)
1397
+ G.window.draw_quad @x - @scale_x, @y - @scale_y, b_color,
1398
+ @x + @w + @scale_x, @y - @scale_y, b_color,
1399
+ @x + @w + @scale_x, bottom, b_color,
1400
+ @x - @scale_x, bottom, b_color, z_index
1401
+ @buttons.each do |b|
1402
+ c = (alpha << 24) | (b.state == :over ? over_color : color)
1403
+ G.window.draw_quad b.x, b.y, c,
1404
+ b.x + b.w, b.y, c,
1405
+ b.x + b.w, b.y + b.h, c,
1406
+ b.x, b.y + b.h, c, z_index + 1 if b.visible
1407
+ end
1408
+ end
1409
+ @buttons[0].draw(alpha, z_index, color)
1410
+ @buttons[1..-1].each { |b| b.draw alpha, z_index + 1, color }
1411
+ end
1412
+
1413
+ private
1414
+
1415
+ def toggle
1416
+ if @open
1417
+ @buttons[1..-1].each { |b| b.visible = false }
1418
+ @open = false
1419
+ else
1420
+ @buttons[1..-1].each { |b| b.visible = true }
1421
+ @open = true
1422
+ end
1423
+ end
1424
+ end
1425
+
1426
+ # This class represents a label.
1427
+ class Label < Component
1428
+ # Creates a new label.
1429
+ #
1430
+ # Parameters:
1431
+ # [x] The x-coordinate of the label.
1432
+ # [y] The x-coordinate of the label.
1433
+ # [font] Font that will be used to draw the label's text.
1434
+ # [text] The label's text.
1435
+ # [text_color] The default text color.
1436
+ # [disabled_text_color] The text color when the label is disabled.
1437
+ # [scale_x] The horizontal scale factor.
1438
+ # [scale_y] The vertical scale factor.
1439
+ # [anchor] See parameter with the same name in <code>Panel#initialize</code> for details.
1440
+ def initialize(x, y = nil, font = nil, text = nil, text_color = 0, disabled_text_color = 0, scale_x = 1, scale_y = 1, anchor = nil)
1441
+ if x.is_a? Hash
1442
+ y = x[:y]
1443
+ font = x[:font]
1444
+ text = x[:text]
1445
+ text_color = x.fetch(:text_color, 0)
1446
+ disabled_text_color = x.fetch(:disabled_text_color, 0)
1447
+ scale_x = x.fetch(:scale_x, 1)
1448
+ scale_y = x.fetch(:scale_y, 1)
1449
+ anchor = x.fetch(:anchor, nil)
1450
+ x = x[:x]
1451
+ end
1452
+
1453
+ @scale_x = scale_x
1454
+ @scale_y = scale_y
1455
+ @w = font.text_width(text) * scale_x
1456
+ @h = font.height * scale_y
1457
+ @anchor_offset_x = x; @anchor_offset_y = y
1458
+ @anchor, x, y = FormUtils.check_anchor(anchor, x, y, @w, @h)
1459
+ super(x, y, font, text, text_color, disabled_text_color)
1460
+ end
1461
+
1462
+ # Draws the label.
1463
+ #
1464
+ # Parameters:
1465
+ # [alpha] The opacity with which the label will be drawn. Allowed values
1466
+ # vary between 0 (fully transparent) and 255 (fully opaque).
1467
+ # [z_index] The z-order to draw the object. Objects with larger z-orders
1468
+ # will be drawn on top of the ones with smaller z-orders.
1469
+ # [color] Color to apply a filter to the text.
1470
+ def draw(alpha = 255, z_index = 0, color = 0xffffff)
1471
+ c = @enabled ? @text_color : @disabled_text_color
1472
+ r1 = c >> 16
1473
+ g1 = (c & 0xff00) >> 8
1474
+ b1 = (c & 0xff)
1475
+ r2 = color >> 16
1476
+ g2 = (color & 0xff00) >> 8
1477
+ b2 = (color & 0xff)
1478
+ r1 *= r2; r1 /= 255
1479
+ g1 *= g2; g1 /= 255
1480
+ b1 *= b2; b1 /= 255
1481
+ color = (alpha << 24) | (r1 << 16) | (g1 << 8) | b1
1482
+ @font.draw_text(@text, @x, @y, z_index, @scale_x, @scale_y, color)
1483
+ end
1484
+ end
1485
+ end