require_relative 'global'
module MiniGL
module FormUtils # :nodoc:
def self.check_anchor(anchor, x, y, w, h, area_w = G.window.width, area_h = G.window.height)
if anchor
case anchor
when /^top(_center)?$|^north$/i then anchor_alias = :top_center; x += (area_w - w) / 2
when /^top_right$|^northeast$/i then anchor_alias = :top_right; x = area_w - w - x
when /^(center_)?left$|^west$/i then anchor_alias = :center_left; y += (area_h - h) / 2
when /^center$/i then anchor_alias = :center; x += (area_w - w) / 2; y += (area_h - h) / 2
when /^(center_)?right$|^east$/i then anchor_alias = :center_right; x = area_w - w - x; y += (area_h - h) / 2
when /^bottom_left$|^southwest$/i then anchor_alias = :bottom_left; y = area_h - h - y
when /^bottom(_center)?$|^south$/i then anchor_alias = :bottom_center; x += (area_w - w) / 2; y = area_h - h - y
when /^bottom_right$|^southeast$/i then anchor_alias = :bottom_right; x = area_w - w - x; y = area_h - h - y
else anchor_alias = :top_left
end
else
anchor_alias = :top_left
end
[anchor_alias, x, y]
end
end
# This class is an abstract ancestor for all form components (Button,
# ToggleButton, TextField, DropDownList and ProgressBar).
class Component
# The horizontal coordinate of the component
attr_reader :x
# The vertical coordinate of the component
attr_reader :y
# The width of the component
attr_reader :w
# The height of the component
attr_reader :h
# The text of the component
attr_reader :text
attr_reader :anchor, :anchor_offset_x, :anchor_offset_y # :nodoc:
# Determines whether the control is enabled, i.e., will process user input.
attr_accessor :enabled
# Determines whether the control is visible, i.e., will be drawn in the
# screen and process user input, if enabled.
attr_accessor :visible
# A container for any parameters to be passed to the code blocks called
# in response to events of the control (click of a button, change of the
# text in a text field, etc.). More detail can be found in the constructor
# for each specific component class.
attr_accessor :params
attr_accessor :panel # :nodoc:
def initialize(x, y, font, text, text_color, disabled_text_color) # :nodoc:
@x = x
@y = y
@font = font
@text = text
@text_color = text_color
@disabled_text_color = disabled_text_color
@enabled = @visible = true
end
def update; end # :nodoc:
# Sets the position of the component.
# Parameters:
# [x] The new x coordinate.
# [y] The new y coordinate.
def set_position(x, y)
@x = x; @y = y
end
end
# Represents a container of form components.
class Panel
# The horizontal position of the panel from the top left corner of the window.
attr_reader :x
# The vertical position of the panel from the top left corner of the window.
attr_reader :y
# The width of the panel in pixels.
attr_reader :w
# The height of the panel in pixels.
attr_reader :h
# Whether the components inside this panel are enabled.
attr_reader :enabled
# Gets or sets whether the panel (and thus all components inside it) are visible.
attr_accessor :visible
# The components contained in this panel.
attr_reader :controls
# Creates a new Panel.
# Parameters:
# [x] The horizontal coordinate of the top-left corner of the panel, or the horizontal offset from the anchor, if provided.
# [y] The vertical coordinate of the top-left corner of the panel, or the vertical offset from the anchor, if provided.
# [w] The width of the panel, in pixels.
# [h] The height of the panel, in pixels.
# [controls] An array of Component
s that will be initially inside this panel.
# [img] Identifier of the image for the panel (see details in +Res::img+).
# [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;
# 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,
# 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),
# and the "center" image (index 4) will be stretched in both directions, as needed, to fill the width and height of the panel.
# [retro] Whether the image should be loaded in retro mode.
# [scale_x] The fixed horizontal scale for "corner" and left and right "border" images (if +img_mode+ is +:tiled+).
# [scale_y] The fixed vertical scale for "corner" and top and bottom "border" images (if +img_mode+ is +:tiled+).
# [anchor] An alias for a predefined position of the window to be used as "anchor", i.e., reference for the positioning of the panel.
# 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):
# * +:north+ or +:top+ or +:top_center+: the panel will be horizontally centered and its top will be at the top of the window.
# * +:northeast+ or +:top_right+: the top-right corner of the panel will meet the top-right corner of the window.
# * +: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.
# * +:center+: the panel will be horizontally and vertically centered on the window.
# * +: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.
# * +:southwest+ or +:bottom_left+: the bottom-left corner of the panel will meet the bottom-left corner of the window.
# * +:south+ or +:bottom+ or +:bottom_center+: the panel will be horizontally centered and its bottom will be at the bottom of the window.
# * +:southeast+ or +:bottom_right+: the bottom-right corner of the panel will meet the bottom-right corner of the window.
# If a value is not provided, the reference is the top-left corner of the screen.
# Components added as children of Panel
s use the panel's coordinates as reference instead of the window.
def initialize(x, y, w, h, controls = [], img = nil, img_mode = :normal, retro = nil, scale_x = 1, scale_y = 1, anchor = nil)
_, x, y = FormUtils.check_anchor(anchor, x, y, w, h)
@x = x; @y = y; @w = w; @h = h
@controls = controls
controls.each do |c|
_, x, y = FormUtils.check_anchor(c.anchor, c.anchor_offset_x, c.anchor_offset_y, c.w, c.h, @w, @h)
c.set_position(@x + x, @y + y)
c.panel = self
end
if img
retro = Res.retro_images if retro.nil?
if img_mode == :tiled
@img = Res.imgs(img, 3, 3, true, '.png', retro, true)
@scale_x = scale_x
@scale_y = scale_y
@tile_w = @img[0].width * @scale_x
@tile_h = @img[0].height * @scale_y
@draw_center_x = @w > 2 * @tile_w
@draw_center_y = @h > 2 * @tile_h
@center_scale_x = (@w - 2 * @tile_w).to_f / @tile_w * @scale_x
@center_scale_y = (@h - 2 * @tile_h).to_f / @tile_h * @scale_y
else
@img = Res.img(img, true, false, '.png', retro)
end
end
@visible = @enabled = true
end
# Updates all child components of this panel.
def update
return unless @visible
@controls.each(&:update)
end
# Enables or disables all child components of this panel.
# Parameters:
# [value] Whether the components should be enabled.
def enabled=(value)
@enabled = value
@controls.each { |c| c.enabled = value }
end
# Adds a component to this panel.
# Parameters:
# [c] The component to add.
def add_component(c)
_, x, y = FormUtils.check_anchor(c.anchor, c.anchor_offset_x, c.anchor_offset_y, c.w, c.h, @w, @h)
c.set_position(@x + x, @y + y)
@controls << c
end
# Draws the panel and all its child components.
# Parameters:
# [alpha] The opacity of the panel (0 = fully transparent, 255 = fully opaque).
# [z_index] The z-index to draw the panel.
# [color] The color to apply as filter to the panel image and to all child components' images as well.
def draw(alpha = 255, z_index = 0, color = 0xffffff)
return unless @visible
c = (alpha << 24) | color
if @img
if @img.is_a?(Array)
@img[0].draw(@x, @y, z_index, @scale_x, @scale_y, c)
@img[1].draw(@x + @tile_w, @y, z_index, @center_scale_x, @scale_y, c) if @draw_center_x
@img[2].draw(@x + @w - @tile_w, @y, z_index, @scale_x, @scale_y, c)
@img[3].draw(@x, @y + @tile_h, z_index, @scale_x, @center_scale_y, c) if @draw_center_y
@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
@img[5].draw(@x + @w - @tile_w, @y + @tile_h, z_index, @scale_x, @center_scale_y, c) if @draw_center_y
@img[6].draw(@x, @y + @h - @tile_h, z_index, @scale_x, @scale_y, c)
@img[7].draw(@x + @tile_w, @y + @h - @tile_h, z_index, @center_scale_x, @scale_y, c) if @draw_center_x
@img[8].draw(@x + @w - @tile_w, @y + @h - @tile_h, z_index, @scale_x, @scale_y, c)
else
@img.draw(@x, @y, z_index, @w.to_f / @img.width, @h.to_f / @img.height)
end
end
@controls.each { |k| k.draw(alpha, z_index, color) if k.visible }
end
end
# This class represents a button.
class Button < Component
# The current state of the button.
attr_reader :state
# The text of the button.
attr_accessor :text
# Creates a button.
#
# Parameters:
# [x] The x-coordinate where the button will be drawn in the screen.
# [y] The y-coordinate where the button will be drawn in the screen.
# [font] The Gosu::Font
object that will be used to draw the
# button text.
# [text] The button text. Can be +nil+ or empty.
# [img] A spritesheet containing four images in a column, representing,
# from top to bottom, the default state, the hover state (when the
# mouse is over the button), the pressed state (when the mouse
# button is down and the cursor is over the button) and the disabled
# state. If +nil+, the +width+ and +height+ parameters must be
# provided.
# [text_color] Color of the button text, in hexadecimal RRGGBB format.
# [disabled_text_color] Color of the button text, when it's disabled, in
# hexadecimal RRGGBB format.
# [over_text_color] Color of the button text, when the cursor is over it
# (hexadecimal RRGGBB).
# [down_text_color] Color of the button text, when it is pressed
# (hexadecimal RRGGBB).
# [center_x] Whether the button text should be horizontally centered in its
# area (the area is defined by the image size, if an image is
# given, or by the +width+ and +height+ parameters, otherwise).
# [center_y] Whether the button text should be vertically centered in its
# area (the area is defined by the image size, if an image is
# given, or by the +width+ and +height+ parameters, otherwise).
# [margin_x] The x offset, from the button x-coordinate, to draw the text.
# This parameter is used only if +center+ is false.
# [margin_y] The y offset, from the button y-coordinate, to draw the text.
# This parameter is used only if +center+ is false.
# [width] Width of the button clickable area. This parameter is used only
# if +img+ is +nil+.
# [height] Height of the button clickable area. This parameter is used
# only if +img+ is +nil+.
# [params] An object containing any parameters you want passed to the
# +action+ block. When the button is clicked, the following is
# called:
# @action.call @params
# Note that this doesn't force you to declare a block that takes
# parameters.
# [retro] Whether the image should be loaded with the 'retro' option set
# (see +Gosu::Image+ for details). If the value is omitted, the
# +Res.retro_images+ value will be used.
# [scale_x] Horizontal scale to draw the component with.
# [scale_y] Vertical scale to draw the component with.
# [anchor] See parameter with the same name in Panel#initialize
for details.
# [action] The block of code executed when the button is clicked (or by
# calling the +click+ method).
#
# *Obs.:* This method accepts named parameters, but +x+ and +y+ are
# mandatory (also, +img+ is mandatory when +width+ and +height+ are not
# provided, and vice-versa).
def initialize(x, y = nil, font = nil, text = nil, img = nil,
text_color = 0, disabled_text_color = 0, over_text_color = 0, down_text_color = 0,
center_x = true, center_y = true, margin_x = 0, margin_y = 0, width = nil, height = nil,
params = nil, retro = nil, scale_x = 1, scale_y = 1, anchor = nil, &action)
if x.is_a? Hash
y = x[:y]
font = x[:font]
text = x[:text]
img = x[:img]
text_color = x.fetch(:text_color, 0)
disabled_text_color = x.fetch(:disabled_text_color, 0)
over_text_color = x.fetch(:over_text_color, 0)
down_text_color = x.fetch(:down_text_color, 0)
center_x = x.fetch(:center_x, true)
center_y = x.fetch(:center_y, true)
margin_x = x.fetch(:margin_x, 0)
margin_y = x.fetch(:margin_y, 0)
width = x.fetch(:width, nil)
height = x.fetch(:height, nil)
params = x.fetch(:params, nil)
retro = x.fetch(:retro, nil)
scale_x = x.fetch(:scale_x, 1)
scale_y = x.fetch(:scale_y, 1)
anchor = x.fetch(:anchor, nil)
x = x[:x]
end
retro = Res.retro_images if retro.nil?
@scale_x = scale_x
@scale_y = scale_y
@img =
if img; Res.imgs img, 1, 4, true, '.png', retro
else; nil; end
@w =
if img; @img[0].width * @scale_x
else; width * @scale_x; end
@h =
if img; @img[0].height * @scale_y
else; height * @scale_y; end
@anchor_offset_x = x; @anchor_offset_y = y
@anchor, x, y = FormUtils.check_anchor(anchor, x, y, @w, @h)
super x, y, font, text, text_color, disabled_text_color
@over_text_color = over_text_color
@down_text_color = down_text_color
if center_x; @text_x = x + @w / 2 if @w
else; @text_x = x + margin_x * @scale_x; end
if center_y; @text_y = y + @h / 2 if @h
else; @text_y = y + margin_y * @scale_y; end
@center_x = center_x
@center_y = center_y
@action = action
@params = params
@state = :up
@img_index = @enabled ? 0 : 3
end
# Updates the button, checking the mouse movement and buttons to define
# the button state.
def update
return unless @enabled and @visible
mouse_over = Mouse.over? @x, @y, @w, @h
mouse_press = Mouse.button_pressed? :left
mouse_rel = Mouse.button_released? :left
if @state == :up
if mouse_over
@img_index = 1
@state = :over
else
@img_index = 0
end
elsif @state == :over
if not mouse_over
@img_index = 0
@state = :up
elsif mouse_press
Mouse.add_click(@z_index || 0, lambda do
@img_index = 2
@state = :down
end)
else
@img_index = 1
end
elsif @state == :down
if not mouse_over
@img_index = 0
@state = :down_out
elsif mouse_rel
@img_index = 1
@state = :over
Mouse.add_click(@z_index || 0, method(:perform_action))
else
@img_index = 2
end
else # :down_out
if mouse_over
@img_index = 2
@state = :down
elsif mouse_rel
@img_index = 0
@state = :up
else
@img_index = 0
end
end
end
# Executes the button click action.
def click
perform_action
end
# Sets the position of the button in the screen.
#
# Parameters:
# [x] The new x-coordinate for the button.
# [y] The new y-coordinate for the button.
def set_position(x, y)
if @center_x; @text_x = x + @w / 2
else; @text_x += x - @x; end
if @center_y; @text_y = y + @h / 2
else; @text_y += y - @y; end
@x = x; @y = y
end
# Draws the button in the screen.
#
# Parameters:
# [alpha] The opacity with which the button will be drawn. Allowed values
# vary between 0 (fully transparent) and 255 (fully opaque).
# [z_index] The z-order to draw the object. Objects with larger z-orders
# will be drawn on top of the ones with smaller z-orders.
# [color] Color to apply a filter to the image.
def draw(alpha = 0xff, z_index = 0, color = 0xffffff)
@z_index = z_index
return unless @visible
color = (alpha << 24) | color
text_color =
if @enabled
if @state == :down
@down_text_color
else
@state == :over ? @over_text_color : @text_color
end
else
@disabled_text_color
end
text_color = (alpha << 24) | text_color
@img[@img_index].draw @x, @y, z_index, @scale_x, @scale_y, color if @img
if @text
if @center_x or @center_y
rel_x = @center_x ? 0.5 : 0
rel_y = @center_y ? 0.5 : 0
@font.draw_text_rel @text, @text_x, @text_y, z_index, rel_x, rel_y, @scale_x, @scale_y, text_color
else
@font.draw_text @text, @text_x, @text_y, z_index, @scale_x, @scale_y, text_color
end
end
end
def enabled=(value) # :nodoc:
@enabled = value
@state = :up
@img_index = 3
end
private
def perform_action
@action.call @params if @action
end
end
# This class represents a toggle button, which can be also interpreted as a
# check box. It is always in one of two states, given as +true+ or +false+
# by its property +checked+.
class ToggleButton < Button
# Defines the state of the button (returns +true+ or +false+).
attr_reader :checked
# Creates a ToggleButton. All parameters work the same as in Button,
# except for the image, +img+, which now has to be composed of two columns
# and four rows, the first column with images for the unchecked state,
# and the second with images for the checked state, and for +checked+,
# which defines the initial state of the ToggleButton.
#
# The +action+ block now will always receive a first boolean parameter
# corresponding to the value of +checked+. So, if you want to pass
# parameters to the block, you should declare it like this:
# b = ToggleButton.new ... { |checked, params|
# puts "button was checked" if checked
# # do something with params
# }
#
# *Obs.:* This method accepts named parameters, but +x+ and +y+ are
# mandatory (also, +img+ is mandatory when +width+ and +height+ are not
# provided, and vice-versa).
def initialize(x, y = nil, font = nil, text = nil, img = nil, checked = false,
text_color = 0, disabled_text_color = 0, over_text_color = 0, down_text_color = 0,
center_x = true, center_y = true, margin_x = 0, margin_y = 0, width = nil, height = nil,
params = nil, retro = nil, scale_x = 1, scale_y = 1, anchor = nil, &action)
if x.is_a? Hash
y = x[:y]
font = x[:font]
text = x[:text]
img = x[:img]
checked = x.fetch(:checked, false)
text_color = x.fetch(:text_color, 0)
disabled_text_color = x.fetch(:disabled_text_color, 0)
over_text_color = x.fetch(:over_text_color, 0)
down_text_color = x.fetch(:down_text_color, 0)
center_x = x.fetch(:center_x, true)
center_y = x.fetch(:center_y, true)
margin_x = x.fetch(:margin_x, 0)
margin_y = x.fetch(:margin_y, 0)
width = x.fetch(:width, nil)
height = x.fetch(:height, nil)
params = x.fetch(:params, nil)
retro = x.fetch(:retro, nil)
scale_x = x.fetch(:scale_x, 1)
scale_y = x.fetch(:scale_y, 1)
anchor = x.fetch(:anchor, nil)
x = x[:x]
end
super x, y, font, text, nil, text_color, disabled_text_color, over_text_color, down_text_color,
center_x, center_y, margin_x, margin_y, 0, 0, params, retro, scale_x, scale_y, anchor, &action
@img =
if img; Res.imgs img, 2, 4, true, '.png', retro
else; nil; end
@w =
if img; @img[0].width * @scale_x
else; width * @scale_x; end
@h =
if img; @img[0].height * @scale_y
else; height * @scale_y; end
_, x, y = FormUtils.check_anchor(anchor, @anchor_offset_x, @anchor_offset_y, @w, @h)
set_position(x, y)
@text_x = x + @w / 2 if center_x
@text_y = y + @h / 2 if center_y
@checked = checked
end
# Updates the button, checking the mouse movement and buttons to define
# the button state.
def update
return unless @enabled and @visible
super
@img_index *= 2
@img_index += 1 if @checked
end
# Sets the state of the button to the value given.
#
# Parameters:
# [value] The state to be set (+true+ for checked, +false+ for unchecked).
def checked=(value)
@action.call(value, @params) if @action && value != @checked
@checked = value
end
def enabled=(value) # :nodoc:
@enabled = value
@state = :up
@img_index = @checked ? 7 : 6
end
private
def perform_action
@checked = !@checked
@action.call(@checked, @params) if @action
end
end
# This class represents a text field (input).
class TextField < Component
# The current 'locale' used for detecting the keys. THIS FEATURE IS
# INCOMPLETE!
attr_reader :locale
# Whether the text field is focused (accepting input)
attr_reader :focused
# Creates a new text field.
#
# Parameters:
# [x] The x-coordinate where the text field will be drawn in the screen.
# [y] The y-coordinate where the text field will be drawn in the screen.
# [font] The Gosu::Font
object that will be used to draw the
# text inside the field.
# [img] The image of the text field. For a good result, you would likely
# want something like a rectangle, horizontally wide, vertically
# short, and with a color that contrasts with the +text_color+.
# [cursor_img] An image for the blinking cursor that stands in the point
# where text will be inserted. If +nil+, a simple black line
# will be drawn instead.
# [disabled_img] Image for the text field when it's disabled. If +nil+,
# a darkened version of +img+ will be used.
# [text_color] Color of the button text, in hexadecimal RRGGBB format.
# [margin_x] The x offset, from the field x-coordinate, to draw the text.
# [margin_y] The y offset, from the field y-coordinate, to draw the text.
# [max_length] The maximum length of the text inside the field.
# [focused] Whether the text field must be focused by default. If +false+,
# focus can be granted by clicking inside the text field or by
# calling the +focus+ method.
# [text] The starting text. Must not be +nil+.
# [allowed_chars] A string containing all characters that can be typed
# inside the text field. The complete set of supported
# characters is given by the string
# "abcdefghijklmnopqrstuvwxyz1234567890 ABCDEFGHIJKLMNOPQRSTUVWXYZ'-=/[]\\\\,.;\"_+?{}|<>:!@#$%¨&*()"
.
# [text_color] The color with which the text will be drawn, in hexadecimal
# RRGGBB format.
# [disabled_text_color] The color with which the text will be drawn, when
# the text field is disabled, in hexadecimal RRGGBB
# format.
# [selection_color] The color of the rectangle highlighting selected text,
# in hexadecimal RRGGBB format. The rectangle will
# always be drawn with 50% of opacity.
# [locale] The locale to be used when detecting keys. By now, only 'en-US'
# and 'pt-BR' are **partially** supported. Default is 'en-US'. If
# any different value is supplied, all typed characters will be
# mapped to '#'.
# [params] An object containing any parameters you want passed to the
# +on_text_changed+ block. When the text of the text field is
# changed, the following is called:
# @on_text_changed.call @text, @params
# Thus, +params+ will be the second parameter. Note that this
# doesn't force you to declare a block that takes parameters.
# [retro] Whether the images should be loaded with the 'retro' option set
# (see +Gosu::Image+ for details). If the value is omitted, the
# +Res.retro_images+ value will be used.
# [scale_x] Horizontal scale to draw the component with.
# [scale_y] Vertical scale to draw the component with.
# [anchor] See parameter with the same name in Panel#initialize
for details.
# [on_text_changed] The block of code executed when the text in the text
# field is changed, either by user input or by calling
# +text=+. The new text is passed as a first parameter
# to this block, followed by +params+. Can be +nil+.
#
# *Obs.:* This method accepts named parameters, but +x+, +y+, +font+ and
# +img+ are mandatory.
def initialize(x, y = nil, font = nil, img = nil, cursor_img = nil, disabled_img = nil, margin_x = 0, margin_y = 0,
max_length = 100, focused = false, text = '', allowed_chars = nil,
text_color = 0, disabled_text_color = 0, selection_color = 0, locale = 'en-us',
params = nil, retro = nil, scale_x = 1, scale_y = 1, anchor = nil, &on_text_changed)
if x.is_a? Hash
y = x[:y]
font = x[:font]
img = x[:img]
cursor_img = x.fetch(:cursor_img, nil)
disabled_img = x.fetch(:disabled_img, nil)
margin_x = x.fetch(:margin_x, 0)
margin_y = x.fetch(:margin_y, 0)
max_length = x.fetch(:max_length, 100)
focused = x.fetch(:focused, false)
text = x.fetch(:text, '')
allowed_chars = x.fetch(:allowed_chars, nil)
text_color = x.fetch(:text_color, 0)
disabled_text_color = x.fetch(:disabled_text_color, 0)
selection_color = x.fetch(:selection_color, 0)
locale = x.fetch(:locale, 'en-us')
params = x.fetch(:params, nil)
retro = x.fetch(:retro, nil)
scale_x = x.fetch(:scale_x, 1)
scale_y = x.fetch(:scale_y, 1)
anchor = x.fetch(:anchor, nil)
x = x[:x]
end
retro = Res.retro_images if retro.nil?
@scale_x = scale_x
@scale_y = scale_y
@img = Res.img img, false, false, '.png', retro
@w = @img.width * @scale_x
@h = @img.height * @scale_y
@anchor_offset_x = x; @anchor_offset_y = y
@anchor, x, y = FormUtils.check_anchor(anchor, x, y, @w, @h)
super x, y, font, text, text_color, disabled_text_color
@cursor_img = Res.img(cursor_img, false, false, '.png', retro) if cursor_img
@disabled_img = Res.img(disabled_img, false, false, '.png', retro) if disabled_img
@max_length = max_length
@focused = focused
@text_x = x + margin_x * @scale_x
@text_y = y + margin_y * @scale_y
@selection_color = selection_color
@nodes = [@text_x]
send(:text=, text, false) if text
@cur_node = 0
@cursor_visible = false
@cursor_timer = 0
@k = [
Gosu::KbA, Gosu::KbB, Gosu::KbC, Gosu::KbD, Gosu::KbE, Gosu::KbF,
Gosu::KbG, Gosu::KbH, Gosu::KbI, Gosu::KbJ, Gosu::KbK, Gosu::KbL,
Gosu::KbM, Gosu::KbN, Gosu::KbO, Gosu::KbP, Gosu::KbQ, Gosu::KbR,
Gosu::KbS, Gosu::KbT, Gosu::KbU, Gosu::KbV, Gosu::KbW, Gosu::KbX,
Gosu::KbY, Gosu::KbZ, Gosu::Kb1, Gosu::Kb2, Gosu::Kb3, Gosu::Kb4,
Gosu::Kb5, Gosu::Kb6, Gosu::Kb7, Gosu::Kb8, Gosu::Kb9, Gosu::Kb0,
Gosu::KbNumpad1, Gosu::KbNumpad2, Gosu::KbNumpad3, Gosu::KbNumpad4,
Gosu::KbNumpad5, Gosu::KbNumpad6, Gosu::KbNumpad7, Gosu::KbNumpad8,
Gosu::KbNumpad9, Gosu::KbNumpad0, Gosu::KbSpace, Gosu::KbBackspace,
Gosu::KbDelete, Gosu::KbLeft, Gosu::KbRight, Gosu::KbHome,
Gosu::KbEnd, Gosu::KbLeftShift, Gosu::KbRightShift,
Gosu::KbBacktick, Gosu::KbMinus, Gosu::KbEqual, Gosu::KbBracketLeft,
Gosu::KbBracketRight, Gosu::KbBackslash, Gosu::KbSemicolon,
Gosu::KbApostrophe, Gosu::KbComma, Gosu::KbPeriod, Gosu::KbSlash,
Gosu::KbNumpadAdd, Gosu::KbNumpadSubtract,
Gosu::KbNumpadMultiply, Gosu::KbNumpadDivide
]
@user_allowed_chars = allowed_chars
self.locale = locale
@on_text_changed = on_text_changed
@params = params
end
# Updates the text field, checking for mouse events and keyboard input.
def update
return unless @enabled and @visible
################################ Mouse ################################
if Mouse.over? @x, @y, @w, @h
if not @focused and Mouse.button_pressed? :left
Mouse.add_click(@z_index || 0, method(:focus))
end
elsif Mouse.button_pressed? :left
unfocus
end
return unless @focused
if Mouse.double_click? :left
if @nodes.size > 1
@anchor1 = 0
@anchor2 = @nodes.size - 1
@cur_node = @anchor2
@double_clicked = true
end
set_cursor_visible
elsif Mouse.button_pressed? :left
Mouse.add_click(@z_index || 0, method(:focus))
elsif Mouse.button_down? :left
if @anchor1 and not @double_clicked
set_node_by_mouse
if @cur_node != @anchor1; @anchor2 = @cur_node
else; @anchor2 = nil; end
set_cursor_visible
end
elsif Mouse.button_released? :left
if @anchor1 and not @double_clicked
if @cur_node != @anchor1; @anchor2 = @cur_node
else; @anchor1 = nil; end
end
end
@cursor_timer += 1
if @cursor_timer >= 30
@cursor_visible = (not @cursor_visible)
@cursor_timer = 0
end
############################### Keyboard ##############################
shift = (KB.key_down?(@k[53]) or KB.key_down?(@k[54]))
if KB.key_pressed?(@k[53]) or KB.key_pressed?(@k[54]) # shift
@anchor1 = @cur_node if @anchor1.nil?
elsif KB.key_released?(@k[53]) or KB.key_released?(@k[54])
@anchor1 = nil if @anchor2.nil?
end
inserted = false
for i in 0..46 # alnum
if KB.key_pressed?(@k[i]) or KB.key_held?(@k[i])
remove_interval true if @anchor1 and @anchor2
if i < 26
if shift
insert_char @chars[i + 37]
else
insert_char @chars[i]
end
elsif i < 36
if shift; insert_char @chars[i + 59]
else; insert_char @chars[i]; end
elsif shift
insert_char(@chars[i + 49])
else
insert_char(@chars[i - 10])
end
inserted = true
break
end
end
return if inserted
for i in 55..65 # special
if KB.key_pressed?(@k[i]) or KB.key_held?(@k[i])
remove_interval true if @anchor1 and @anchor2
if shift; insert_char @chars[i + 19]
else; insert_char @chars[i + 8]; end
inserted = true
break
end
end
return if inserted
for i in 66..69 # numpad operators
if KB.key_pressed?(@k[i]) or KB.key_held?(@k[i])
remove_interval true if @anchor1 and @anchor2
insert_char @chars[i + 19]
inserted = true
break
end
end
return if inserted
if KB.key_pressed?(@k[47]) or KB.key_held?(@k[47]) # back
if @anchor1 and @anchor2
remove_interval
elsif @cur_node > 0
remove_char true
end
elsif KB.key_pressed?(@k[48]) or KB.key_held?(@k[48]) # del
if @anchor1 and @anchor2
remove_interval
elsif @cur_node < @nodes.size - 1
remove_char false
end
elsif KB.key_pressed?(@k[49]) or KB.key_held?(@k[49]) # left
if @anchor1
if shift
if @cur_node > 0
@cur_node -= 1
@anchor2 = @cur_node
set_cursor_visible
end
elsif @anchor2
@cur_node = @anchor1 < @anchor2 ? @anchor1 : @anchor2
@anchor1 = nil
@anchor2 = nil
set_cursor_visible
end
elsif @cur_node > 0
@cur_node -= 1
set_cursor_visible
end
elsif KB.key_pressed?(@k[50]) or KB.key_held?(@k[50]) # right
if @anchor1
if shift
if @cur_node < @nodes.size - 1
@cur_node += 1
@anchor2 = @cur_node
set_cursor_visible
end
elsif @anchor2
@cur_node = @anchor1 > @anchor2 ? @anchor1 : @anchor2
@anchor1 = nil
@anchor2 = nil
set_cursor_visible
end
elsif @cur_node < @nodes.size - 1
@cur_node += 1
set_cursor_visible
end
elsif KB.key_pressed?(@k[51]) # home
@cur_node = 0
if shift; @anchor2 = @cur_node
else
@anchor1 = nil
@anchor2 = nil
end
set_cursor_visible
elsif KB.key_pressed?(@k[52]) # end
@cur_node = @nodes.size - 1
if shift; @anchor2 = @cur_node
else
@anchor1 = nil
@anchor2 = nil
end
set_cursor_visible
end
end
# Sets the text of the text field to the specified value.
#
# Parameters:
# [value] The new text to be set. If it's longer than the +max_length+
# parameter used in the constructor, it will be truncated to
# +max_length+ characters.
def text=(value, trigger_changed = true)
@text = value[0...@max_length]
@nodes.clear; @nodes << @text_x
x = @nodes[0]
@text.chars.each { |char|
x += @font.text_width(char) * @scale_x
@nodes << x
}
@cur_node = @nodes.size - 1
@anchor1 = nil
@anchor2 = nil
set_cursor_visible
@on_text_changed.call @text, @params if trigger_changed && @on_text_changed
end
# Sets the locale used by the text field to detect keys. Only 'en-us' and
# 'pt-br' are **partially** supported. If any different value is supplied,
# all typed characters will be mapped to '#'.
def locale=(value)
@locale = value.downcase
@chars =
case @locale
when 'en-us' then "abcdefghijklmnopqrstuvwxyz1234567890 ABCDEFGHIJKLMNOPQRSTUVWXYZ`-=[]\\;',./~_+{}|:\"<>?!@#$%^&*()+-*/"
when 'pt-br' then "abcdefghijklmnopqrstuvwxyz1234567890 ABCDEFGHIJKLMNOPQRSTUVWXYZ'-=/[]ç~,.;\"_+?{}Ç^<>:!@#$%¨&*()+-*/"
else '###################################################################################################'
end
@allowed_chars =
if @user_allowed_chars
@user_allowed_chars
else
@chars
end
end
# Returns the currently selected text.
def selected_text
return '' if @anchor2.nil?
min = @anchor1 < @anchor2 ? @anchor1 : @anchor2
max = min == @anchor1 ? @anchor2 : @anchor1
@text[min..max]
end
# Grants focus to the text field, so that it allows keyboard input.
def focus
@focused = true
set_node_by_mouse
@anchor1 = @cur_node
@anchor2 = nil
@double_clicked = false
set_cursor_visible
end
# Removes focus from the text field, so that no keyboard input will be
# accepted.
def unfocus
@anchor1 = @anchor2 = nil
@cursor_visible = false
@cursor_timer = 0
@focused = false
end
# Sets the position of the text field in the screen.
#
# Parameters:
# [x] The new x-coordinate for the text field.
# [y] The new y-coordinate for the text field.
def set_position(x, y)
d_x = x - @x
d_y = y - @y
@x = x; @y = y
@text_x += d_x
@text_y += d_y
@nodes.map! do |n|
n + d_x
end
end
# Draws the text field in the screen.
#
# Parameters:
# [alpha] The opacity with which the text field will be drawn. Allowed
# values vary between 0 (fully transparent) and 255 (fully opaque).
# [z_index] The z-order to draw the object. Objects with larger z-orders
# will be drawn on top of the ones with smaller z-orders.
# [color] Color to apply a filter to the image.
# [disabled_color] Color to apply a filter to the image when the field is
# disabled.
def draw(alpha = 0xff, z_index = 0, color = 0xffffff, disabled_color = 0x808080)
@z_index = z_index
return unless @visible
color = (alpha << 24) | ((@enabled or @disabled_img) ? color : disabled_color)
text_color = (alpha << 24) | (@enabled ? @text_color : @disabled_text_color)
img = ((@enabled or @disabled_img.nil?) ? @img : @disabled_img)
img.draw @x, @y, z_index, @scale_x, @scale_y, color
@font.draw_text @text, @text_x, @text_y, z_index, @scale_x, @scale_y, text_color
if @anchor1 and @anchor2
selection_color = ((alpha / 2) << 24) | @selection_color
G.window.draw_quad @nodes[@anchor1], @text_y, selection_color,
@nodes[@anchor2] + 1, @text_y, selection_color,
@nodes[@anchor2] + 1, @text_y + @font.height * @scale_y, selection_color,
@nodes[@anchor1], @text_y + @font.height * @scale_y, selection_color, z_index
end
if @cursor_visible
if @cursor_img
@cursor_img.draw @nodes[@cur_node] - (@cursor_img.width * @scale_x) / 2, @text_y, z_index, @scale_x, @scale_y
else
cursor_color = alpha << 24
G.window.draw_quad @nodes[@cur_node], @text_y, cursor_color,
@nodes[@cur_node] + 1, @text_y, cursor_color,
@nodes[@cur_node] + 1, @text_y + @font.height * @scale_y, cursor_color,
@nodes[@cur_node], @text_y + @font.height * @scale_y, cursor_color, z_index
end
end
end
def enabled=(value) # :nodoc:
@enabled = value
unfocus unless @enabled
end
def visible=(value) # :nodoc:
@visible = value
unfocus unless @visible
end
private
def set_cursor_visible
@cursor_visible = true
@cursor_timer = 0
end
def set_node_by_mouse
index = @nodes.size - 1
@nodes.each_with_index do |n, i|
if n >= Mouse.x
index = i
break
end
end
if index > 0
d1 = @nodes[index] - Mouse.x; d2 = Mouse.x - @nodes[index - 1]
index -= 1 if d1 > d2
end
@cur_node = index
end
def insert_char(char)
return unless @allowed_chars.index char and @text.length < @max_length
@text.insert @cur_node, char
@nodes.insert @cur_node + 1, @nodes[@cur_node] + @font.text_width(char) * @scale_x
for i in (@cur_node + 2)..(@nodes.size - 1)
@nodes[i] += @font.text_width(char) * @scale_x
end
@cur_node += 1
set_cursor_visible
@on_text_changed.call @text, @params if @on_text_changed
end
def remove_interval(will_insert = false)
min = @anchor1 < @anchor2 ? @anchor1 : @anchor2
max = min == @anchor1 ? @anchor2 : @anchor1
interval_width = 0
for i in min...max
interval_width += @font.text_width(@text[i]) * @scale_x
@nodes.delete_at min + 1
end
@text[min...max] = ''
for i in (min + 1)..(@nodes.size - 1)
@nodes[i] -= interval_width
end
@cur_node = min
@anchor1 = nil
@anchor2 = nil
set_cursor_visible
@on_text_changed.call @text, @params if @on_text_changed and not will_insert
end
def remove_char(back)
@cur_node -= 1 if back
char_width = @font.text_width(@text[@cur_node]) * @scale_x
@text[@cur_node] = ''
@nodes.delete_at @cur_node + 1
for i in (@cur_node + 1)..(@nodes.size - 1)
@nodes[i] -= char_width
end
set_cursor_visible
@on_text_changed.call @text, @params if @on_text_changed
end
end
# Represents a progress bar.
class ProgressBar < Component
# The maximum value for this progress bar (when the current value equals
# the maximum, the bar is full).
attr_reader :max_value
# The current value of the progress bar (an integer greater than or equal
# to zero, and less than or equal to +max_value+).
attr_reader :value
# Creates a progress bar.
#
# Parameters:
# [x] The x-coordinate of the progress bar on the screen.
# [y] The y-coordinate of the progress bar on the screen.
# [w] Width of the progress bar, in pixels. This is the maximum space the
# bar foreground can occupy. Note that the width of the foreground image
# (+fg+) can be less than this, in which case the image will be
# horizontally repeated to fill all the needed space.
# [h] Height of the progress bar. This will be the height of the bar
# foreground when +fg+ is a color (when it is an image, the height of
# the image will be kept).
# [bg] A background image (string or symbol that will be passed to
# +Res.img+) or color (in RRGGBB hexadecimal format).
# [fg] A foreground image (string or symbol that will be passed to
# +Res.img+) or color (in RRGGBB hexadecimal format). The image will
# be horizontally repeated when needed, if its width is less than +w+.
# [max_value] The maximum value the progress bar can reach (an integer).
# [value] The starting value for the progress bar.
# [fg_margin_x] Horizontal margin between the background image and the
# foreground image (when these are provided).
# [fg_margin_y] Vertical margin between the background image and the
# foreground image (when these are provided).
# [font] Font that will be used to draw a text indicating the value of the
# progress bar.
# [text_color] Color of the text.
# [format] Format to display the value. Specify '%' for a percentage and
# anything else for absolute values (current/maximum).
# [retro] Whether the images should be loaded with the 'retro' option set
# (see +Gosu::Image+ for details). If the value is omitted, the
# +Res.retro_images+ value will be used.
# [scale_x] Horizontal scale to draw the component with.
# [scale_y] Vertical scale to draw the component with.
# [anchor] See parameter with the same name in Panel#initialize
for details.
#
# *Obs.:* This method accepts named parameters, but +x+, +y+, +w+, +h+, +bg+
# and +fg+ are mandatory.
def initialize(x, y = nil, w = nil, h = nil, bg = nil, fg = nil,
max_value = 100, value = 100, fg_margin_x = 0, fg_margin_y = 0, # fg_left = nil, fg_right = nil,
font = nil, text_color = 0, format = nil, retro = nil, scale_x = 1, scale_y = 1, anchor = nil)
if x.is_a? Hash
y = x[:y]
w = x[:w]
h = x[:h]
bg = x[:bg]
fg = x[:fg]
max_value = x.fetch(:max_value, 100)
value = x.fetch(:value, 100)
fg_margin_x = x.fetch(:fg_margin_x, 0)
fg_margin_y = x.fetch(:fg_margin_y, 0)
font = x.fetch(:font, nil)
text_color = x.fetch(:text_color, 0)
format = x.fetch(:format, nil)
retro = x.fetch(:retro, nil)
scale_x = x.fetch(:scale_x, 1)
scale_y = x.fetch(:scale_y, 1)
anchor = x.fetch(:anchor, nil)
x = x[:x]
end
@scale_x = scale_x
@scale_y = scale_y
retro = Res.retro_images if retro.nil?
if bg.is_a? Integer
@bg_color = bg
else # String or Symbol
@bg = Res.img bg, false, false, '.png', retro
end
if fg.is_a? Integer
@fg_color = fg
else # String or Symbol
@fg = Res.img fg, false, false, '.png', retro
@fg_path = "#{Res.prefix}#{Res.img_dir}#{fg.to_s.gsub(Res.separator, '/')}.png"
end
@fg_margin_x = fg_margin_x * @scale_x
@fg_margin_y = fg_margin_y * @scale_y
@w = (@bg ? @bg.width : w) * @scale_x
@h = (@bg ? @bg.height : h) * @scale_y
@anchor_offset_x = x; @anchor_offset_y = y
@anchor, x, y = FormUtils.check_anchor(anchor, x, y, @w, @h)
super x, y, font, '', text_color, text_color
# @fg_left = fg_left
# @fg_right = fg_right
@max_value = max_value
self.value = value
@format = format
@retro = retro
end
# Increases the current value of the progress bar by the given amount.
#
# Parameters:
# [amount] (+Integer+) The amount to be added to the current value. If the
# sum surpasses +max_value+, it is set to +max_value+.
def increase(amount)
@value += amount
@value = @max_value if @value > @max_value
end
# Descreases the current value of the progress bar by the given amount.
#
# Parameters:
# [amount] (+Integer+) The amount to be subtracted from the current value.
# If the result is less than zero, it is set to zero.
def decrease(amount)
@value -= amount
@value = 0 if @value < 0
end
# Sets the value of the progress bar.
#
# Parameters:
# [val] (+Integer+) The value to be set. It will be changed as needed to be
# between zero and +max_value+.
def value=(val)
@value = val
if @value > @max_value
@value = @max_value
elsif @value < 0
@value = 0
end
end
# Sets the value of the progress bar to a given percentage of +max_value+.
#
# Parameters:
# [pct] (+Numeric+) The percentage of +max_value+ to set the current value
# to. The final result will be changed as needed to be between zero
# and +max_value+.
def percentage=(pct)
self.value = (pct * @max_value).round
end
# Draws the progress bar.
#
# Parameters:
# [alpha] (+Fixnum+) The opacity with which the progress bar will be drawn.
# Allowed values vary between 0 (fully transparent) and 255 (fully
# opaque).
# [z_index] (+Fixnum+) The z-order to draw the object. Objects with larger
# z-orders will be drawn on top of the ones with smaller z-orders.
# [color] Color to apply a filter to the images (when these are provided).
def draw(alpha = 0xff, z_index = 0, color = 0xffffff)
return unless @visible
if @bg
c = (alpha << 24) | color
@bg.draw @x, @y, z_index, @scale_x, @scale_y, c
else
c = (alpha << 24) | @bg_color
G.window.draw_quad @x, @y, c,
@x + @w, @y, c,
@x + @w, @y + @h, c,
@x, @y + @h, c, z_index
end
if @fg
c = (alpha << 24) | color
w1 = @fg.width * @scale_x
w2 = (@value.to_f / @max_value * @w).round
x0 = @x + @fg_margin_x
x = 0
while x <= w2 - w1
@fg.draw x0 + x, @y + @fg_margin_y, z_index, @scale_x, @scale_y, c
x += w1
end
if w2 - x > 0
img = Gosu::Image.new(@fg_path, tileable: true, retro: @retro, rect: [0, 0, ((w2 - x) / @scale_x).round, @fg.height])
img.draw x0 + x, @y + @fg_margin_y, z_index, @scale_x, @scale_y, c
end
else
c = (alpha << 24) | @fg_color
rect_r = @x + (@value.to_f / @max_value * @w).round
G.window.draw_quad @x, @y, c,
rect_r, @y, c,
rect_r, @y + @h, c,
@x, @y + @h, c, z_index
end
if @font
c = (alpha << 24) | @text_color
@text = @format == '%' ? "#{(@value.to_f / @max_value * 100).round}%" : "#{@value}/#{@max_value}"
@font.draw_text_rel @text, @x + @w / 2, @y + @h / 2, z_index, 0.5, 0.5, @scale_x, @scale_y, c
end
end
end
# This class represents a "drop-down list" form component, here composed of a
# group of +Button+ objects.
class DropDownList < Component
# The selected value in the drop-down list. This is one of the +options+.
attr_reader :value
# Whether the list of options is currently visible.
attr_reader :open
# An array containing all the options (each of them +String+s) that can be
# selected in the drop-down list.
attr_accessor :options
# Creates a new drop-down list.
#
# Parameters:
# [x] The x-coordinate of the object.
# [y] The y-coordinate of the object.
# [font] Font to be used by the buttons that compose the drop-down list.
# [img] Image of the main button, i.e., the one at the top, that toggles
# visibility of the other buttons (the "option" buttons).
# [opt_img] Image for the "option" buttons, as described above.
# [options] Array of available options for this control (+String+s).
# [option] Index of the firstly selected option.
# [text_margin] Left margin of the text inside the buttons (vertically, the
# text will always be centered).
# [width] Width of the control, used when no image is provided.
# [height] Height of the control, used when no image is provided.
# [text_color] Used as the +text_color+ parameter in the constructor of the
# buttons.
# [disabled_text_color] Analogous to +text_color+.
# [over_text_color] Same as above.
# [down_text_color] Same as above.
# [retro] Whether the images should be loaded with the 'retro' option set
# (see +Gosu::Image+ for details). If the value is omitted, the
# +Res.retro_images+ value will be used.
# [scale_x] Horizontal scale to draw the component with.
# [scale_y] Vertical scale to draw the component with.
# [anchor] See parameter with the same name in Panel#initialize
for details.
# [on_changed] Action performed when the value of the dropdown is changed.
# It must be a block with two parameters, which will receive
# the old and the new value, respectively.
#
# *Obs.:* This method accepts named parameters, but +x+, +y+, +font+ and
# +options+ are mandatory (also, +img+ and +opt_img+ are mandatory when
# +width+ and +height+ are not provided, and vice-versa).
def initialize(x, y = nil, font = nil, img = nil, opt_img = nil, options = nil,
option = 0, text_margin = 0, width = nil, height = nil,
text_color = 0, disabled_text_color = 0, over_text_color = 0, down_text_color = 0,
retro = nil, scale_x = 1, scale_y = 1, anchor = nil, &on_changed)
if x.is_a? Hash
y = x[:y]
font = x[:font]
img = x[:img]
opt_img = x[:opt_img]
options = x[:options]
option = x.fetch(:option, 0)
text_margin = x.fetch(:text_margin, 0)
width = x.fetch(:width, nil)
height = x.fetch(:height, nil)
text_color = x.fetch(:text_color, 0)
disabled_text_color = x.fetch(:disabled_text_color, 0)
over_text_color = x.fetch(:over_text_color, 0)
down_text_color = x.fetch(:down_text_color, 0)
retro = x.fetch(:retro, nil)
scale_x = x.fetch(:scale_x, 1)
scale_y = x.fetch(:scale_y, 1)
anchor = x.fetch(:anchor, nil)
x = x[:x]
end
@img = img
@opt_img = opt_img
@options = options
@value = @options[option]
@open = false
@buttons = []
@buttons.push(
Button.new(x, y, font, @value, img, text_color, disabled_text_color, over_text_color, down_text_color,
false, true, text_margin, 0, width, height, nil, retro, scale_x, scale_y) {
toggle
}
)
@scale_x = scale_x
@scale_y = scale_y
@w = @buttons[0].w
@h = @buttons[0].h
@max_h = (@options.size + 1) * @h
@anchor_offset_x = x; @anchor_offset_y = y
@anchor, x, y = FormUtils.check_anchor(anchor, x, y, @w, @h)
super x, y, font, options[option], text_color, disabled_text_color
@buttons[0].set_position(x, y)
@options.each_with_index do |o, i|
b = Button.new(x, y + (i+1) * @h, font, o, opt_img, text_color, disabled_text_color, over_text_color, down_text_color,
false, true, text_margin, 0, width, height, nil, retro, scale_x, scale_y) {
old = @value
@value = @buttons[0].text = o
@on_changed.call(old, o) if @on_changed
toggle
}
b.visible = false
@buttons.push b
end
@on_changed = on_changed
end
# Updates the control.
def update
return unless @enabled and @visible
if @open and Mouse.button_pressed? :left and not Mouse.over?(@x, @y, @w, @max_h)
toggle
return
end
@buttons.each { |b| b.update }
end
# Sets the currently selected value of the drop-down list. It is ignored if
# it is not among the available options.
def value=(val)
if @options.include? val
old = @value
@value = @buttons[0].text = val
@on_changed.call(old, val) if @on_changed
end
end
def enabled=(value) # :nodoc:
toggle if @open
@buttons[0].enabled = value
@enabled = value
end
def set_position(x, y)
@x = x; @y = y
@buttons.each_with_index { |b, i| b.set_position(x, y + i * @h) }
end
# Draws the drop-down list.
#
# Parameters:
# [alpha] (+Fixnum+) The opacity with which the drop-down list will be
# drawn. Allowed values vary between 0 (fully transparent) and 255
# (fully opaque).
# [z_index] (+Fixnum+) The z-order to draw the object. Objects with larger
# z-orders will be drawn on top of the ones with smaller z-orders.
# [color] Color of the buttons, if no image was provided, or color to apply
# a filter to the images.
# [over_color] Color of the buttons when the mouse is over them (when no
# image was provided).
def draw(alpha = 0xff, z_index = 0, color = 0xffffff, over_color = 0xcccccc)
return unless @visible
unless @img
bottom = @y + (@open ? @max_h : @h) + @scale_y
b_color = (alpha << 24)
G.window.draw_quad @x - @scale_x, @y - @scale_y, b_color,
@x + @w + @scale_x, @y - @scale_y, b_color,
@x + @w + @scale_x, bottom, b_color,
@x - @scale_x, bottom, b_color, z_index
@buttons.each do |b|
c = (alpha << 24) | (b.state == :over ? over_color : color)
G.window.draw_quad b.x, b.y, c,
b.x + b.w, b.y, c,
b.x + b.w, b.y + b.h, c,
b.x, b.y + b.h, c, z_index + 1 if b.visible
end
end
@buttons[0].draw(alpha, z_index, color)
@buttons[1..-1].each { |b| b.draw alpha, z_index + 1, color }
end
private
def toggle
if @open
@buttons[1..-1].each { |b| b.visible = false }
@open = false
else
@buttons[1..-1].each { |b| b.visible = true }
@open = true
end
end
end
# This class represents a label.
class Label < Component
# Creates a new label.
#
# Parameters:
# [x] The x-coordinate of the label.
# [y] The x-coordinate of the label.
# [font] Font that will be used to draw the label's text.
# [text] The label's text.
# [text_color] The default text color.
# [disabled_text_color] The text color when the label is disabled.
# [scale_x] The horizontal scale factor.
# [scale_y] The vertical scale factor.
# [anchor] See parameter with the same name in Panel#initialize
for details.
def initialize(x, y = nil, font = nil, text = nil, text_color = 0, disabled_text_color = 0, scale_x = 1, scale_y = 1, anchor = nil)
if x.is_a? Hash
y = x[:y]
font = x[:font]
text = x[:text]
text_color = x.fetch(:text_color, 0)
disabled_text_color = x.fetch(:disabled_text_color, 0)
scale_x = x.fetch(:scale_x, 1)
scale_y = x.fetch(:scale_y, 1)
anchor = x.fetch(:anchor, nil)
x = x[:x]
end
@scale_x = scale_x
@scale_y = scale_y
@w = font.text_width(text) * scale_x
@h = font.height * scale_y
@anchor_offset_x = x; @anchor_offset_y = y
@anchor, x, y = FormUtils.check_anchor(anchor, x, y, @w, @h)
super(x, y, font, text, text_color, disabled_text_color)
end
# Changes the label's text.
#
# Parameters:
# [new_text] The new text to show in the label.
def text=(new_text)
@text = new_text
@w = @font.text_width(@text) * @scale_x
x = @anchor_offset_x; y = @anchor_offset_y
_, x, y = FormUtils.check_anchor(@anchor, x, y, @w, @h, panel ? panel.w : G.window.width, panel ? panel.h : G.window.height)
if panel
set_position(panel.x + x, panel.y + y)
else
set_position(x, y)
end
end
# Draws the label.
#
# Parameters:
# [alpha] The opacity with which the label will be drawn. Allowed values
# vary between 0 (fully transparent) and 255 (fully opaque).
# [z_index] The z-order to draw the object. Objects with larger z-orders
# will be drawn on top of the ones with smaller z-orders.
# [color] Color to apply a filter to the text.
def draw(alpha = 255, z_index = 0, color = 0xffffff)
c = @enabled ? @text_color : @disabled_text_color
r1 = c >> 16
g1 = (c & 0xff00) >> 8
b1 = (c & 0xff)
r2 = color >> 16
g2 = (color & 0xff00) >> 8
b2 = (color & 0xff)
r1 *= r2; r1 /= 255
g1 *= g2; g1 /= 255
b1 *= b2; b1 /= 255
color = (alpha << 24) | (r1 << 16) | (g1 << 8) | b1
@font.draw_text(@text, @x, @y, z_index, @scale_x, @scale_y, color)
end
end
end