minigl 2.2.2 → 2.2.3

Sign up to get free protection for your applications and to get access to all the features.
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