minigl 2.2.3 → 2.2.4

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