Initial proof of concept commit

This commit is contained in:
Maruno17
2023-08-28 22:41:48 +01:00
parent ea7b5d56d2
commit 1041883992
16 changed files with 1358 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
# Container module for control classes.
module UIControls; end

View File

@@ -0,0 +1,180 @@
# TODO: Add "disabled" greying out/non-editable.
# TODO: Add indicator of whether the control's value is "lerping" between frames
# (use yellow somehow?).
#===============================================================================
#
#===============================================================================
class UIControls::BaseControl < BitmapSprite
attr_reader :value
# attr_accessor :disabled # TODO: Make use of this.
TEXT_COLOR = Color.black
TEXT_SIZE = 18 # Default is 22 if size isn't explicitly set
HOVER_COLOR = Color.cyan # For clickable area when hovering over it
CAPTURE_COLOR = Color.pink # For area you clicked in but aren't hovering over
def initialize(width, height, viewport)
super(width, height, viewport)
self.bitmap.font.color = TEXT_COLOR
self.bitmap.font.size = TEXT_SIZE
# @disabled = false # TODO: Make use of this.
@hover_area = nil # Is a symbol from the keys for @interactions if the mouse is hovering over that interaction
@captured_area = nil # Is a symbol from the keys for @interactions (or :none) if this control is clicked in
clear_changed
invalidate
end
def width
return self.bitmap.width
end
def height
return self.bitmap.height
end
def mouse_pos
mouse_coords = Mouse.getMousePos
return nil, nil if !mouse_coords
ret_x = mouse_coords[0] - self.viewport.rect.x - self.x
ret_y = mouse_coords[1] - self.viewport.rect.y - self.y
return ret_x, ret_y
end
def set_interactive_rects
@interactions = {}
end
#-----------------------------------------------------------------------------
def invalid?
return @invalid
end
# Marks that the control must be redrawn to reflect current logic.
def invalidate
@invalid = true
end
# Makes the control no longer invalid.
def validate
@invalid = false
end
def busy?
return !@captured_area.nil?
end
def changed?
return @changed
end
def set_changed
@changed = true
end
def clear_changed
@changed = false
end
#-----------------------------------------------------------------------------
def draw_text(this_bitmap, text_x, text_y, this_text)
text_size = this_bitmap.text_size(this_text)
this_bitmap.draw_text(text_x, text_y, text_size.width, text_size.height, this_text, 0)
end
# Redraws the control only if it is invalid.
def repaint
return if !invalid?
refresh
validate
end
def refresh
# Paint over control to erase contents (intentionally not using self.bitmap.clear)
self.bitmap.clear
draw_area_highlight
end
def draw_area_highlight
return if !@interactions || @interactions.empty?
if !@captured_area || @hover_area == @captured_area
# Draw mouse hover over area highlight
rect = @interactions[@hover_area]
self.bitmap.fill_rect(rect.x, rect.y, rect.width, rect.height, HOVER_COLOR) if rect
elsif @captured_area
# Draw captured area highlight
rect = @interactions[@captured_area]
self.bitmap.fill_rect(rect.x, rect.y, rect.width, rect.height, CAPTURE_COLOR) if rect
end
end
#-----------------------------------------------------------------------------
# This method is only called if the mouse is in the game window and this
# control has interactive elements.
def on_mouse_press
return if !@interactions || @interactions.empty?
return if @captured_area
@captured_area = nil
mouse_x, mouse_y = mouse_pos
return if !mouse_x || !mouse_y
@interactions.each_pair do |area, rect|
next if !rect.contains?(mouse_x, mouse_y)
@captured_area = area
invalidate
break
end
end
# Returns whether this control has been properly decaptured.
def on_mouse_release
@captured_area = nil
invalidate
end
def update_hover_highlight
# Remove the hover highlight if there are no interactions for this control
# or if the mouse is off-screen
mouse_x, mouse_y = mouse_pos
if !@interactions || @interactions.empty? || !mouse_x || !mouse_y
invalidate if @hover_area
@hover_area = nil
return
end
# Check each interactive area for whether the mouse is hovering over it, and
# set @hover_area accordingly
in_area = false
@interactions.each_pair do |area, rect|
next if !rect.contains?(mouse_x, mouse_y)
invalidate if @hover_area != area
@hover_area = area
in_area = true
break
end
if !in_area
invalidate if @hover_area
@hover_area = nil
end
end
# Updates the logic on the control, invalidating it if necessary.
def update
# TODO: Disabled control stuff.
# return if self.disabled
update_hover_highlight
# Detect a mouse press/release
if @interactions && !@interactions.empty?
if Input.trigger?(Input::MOUSELEFT)
on_mouse_press
elsif busy? && Input.release?(Input::MOUSELEFT)
on_mouse_release
end
end
end
end

View File

@@ -0,0 +1,24 @@
#===============================================================================
#
#===============================================================================
class UIControls::Label < UIControls::BaseControl
attr_reader :label
LABEL_END_X = 80
TEXT_OFFSET_Y = 7
def initialize(width, height, viewport, label)
super(width, height, viewport)
@label = label
end
def label=(value)
@label = value
refresh
end
def refresh
super
draw_text(self.bitmap, 4, TEXT_OFFSET_Y, @label)
end
end

View File

@@ -0,0 +1,68 @@
#===============================================================================
# NOTE: Strictly speaking, this is a toggle switch and not a checkbox.
#===============================================================================
class UIControls::Checkbox < UIControls::BaseControl
CHECKBOX_X = 0
CHECKBOX_WIDTH = 40
CHECKBOX_HEIGHT = 24
CHECKBOX_FILL_SIZE = CHECKBOX_HEIGHT - 8
UNCHECKED_COLOR = Color.gray
CHECKED_COLOR = Color.new(64, 255, 64) # Green
def initialize(width, height, viewport, value = false)
super(width, height, viewport)
@value = value
end
def value=(val)
return if @value == val
@value = val
invalidate
end
def set_interactive_rects
@checkbox_rect = Rect.new(CHECKBOX_X, (height - CHECKBOX_HEIGHT) / 2,
CHECKBOX_WIDTH, CHECKBOX_HEIGHT)
@interactions = {
:checkbox => @checkbox_rect
}
end
#-----------------------------------------------------------------------------
def refresh
super
# Draw checkbox outline
self.bitmap.outline_rect(@checkbox_rect.x + 2, @checkbox_rect.y + 2,
@checkbox_rect.width - 4, @checkbox_rect.height - 4,
self.bitmap.font.color)
# Draw checkbox fill
if @value # If checked
self.bitmap.fill_rect(@checkbox_rect.x + @checkbox_rect.width - CHECKBOX_FILL_SIZE - 4, @checkbox_rect.y + 4,
CHECKBOX_FILL_SIZE, CHECKBOX_FILL_SIZE, CHECKED_COLOR)
self.bitmap.outline_rect(@checkbox_rect.x + @checkbox_rect.width - CHECKBOX_FILL_SIZE - 4, @checkbox_rect.y + 4,
CHECKBOX_FILL_SIZE, CHECKBOX_FILL_SIZE, self.bitmap.font.color)
else
self.bitmap.fill_rect(@checkbox_rect.x + 4, @checkbox_rect.y + 4,
CHECKBOX_FILL_SIZE, CHECKBOX_FILL_SIZE, UNCHECKED_COLOR)
self.bitmap.outline_rect(@checkbox_rect.x + 4, @checkbox_rect.y + 4,
CHECKBOX_FILL_SIZE, CHECKBOX_FILL_SIZE, self.bitmap.font.color)
end
end
#-----------------------------------------------------------------------------
def on_mouse_release
return if !@captured_area # Wasn't captured to begin with
# Change this control's value
if @captured_area == :checkbox
mouse_x, mouse_y = mouse_pos
if mouse_x && mouse_y && @interactions[@captured_area].contains?(mouse_x, mouse_y)
@value = !@value # The actual change of this control's value
set_changed
end
end
super # Make this control not busy again
end
end

View File

@@ -0,0 +1,289 @@
#===============================================================================
# TODO: Support selecting part of the text by remembering the initial
# cursor position and using it and the current cursor position to
# decide which characters are selected. Maybe? Note that this method
# is only triggered upon the initial mouse press, and isn't repeated
# while it's still held down.
#===============================================================================
class UIControls::TextBox < UIControls::BaseControl
TEXT_BOX_X = 2
TEXT_BOX_WIDTH = 172
TEXT_BOX_HEIGHT = 24
TEXT_BOX_PADDING = 4 # Gap between sides of text box and text
TEXT_OFFSET_Y = 7
def initialize(width, height, viewport, value = "")
super(width, height, viewport)
@value = value
@cursor_pos = -1
@display_pos = 0
@cursor_timer = nil
@cursor_shown = false
end
def value=(new_value)
return if @value == new_value
@value = new_value
invalidate
end
def insert_char(ch)
@value.insert(@cursor_pos, ch)
@cursor_pos += 1
@cursor_timer = System.uptime
@cursor_shown = true
invalidate
end
def delete_at(index)
@value.slice!(index)
@cursor_pos -= 1 if @cursor_pos > index
@cursor_timer = System.uptime
@cursor_shown = true
invalidate
end
def cursor_pos=(val)
@cursor_pos = val
reset_display_pos
@cursor_timer = System.uptime
@cursor_shown = true
invalidate
end
def set_interactive_rects
@text_box_rect = Rect.new(TEXT_BOX_X, (height - TEXT_BOX_HEIGHT) / 2,
[TEXT_BOX_WIDTH, width].min, TEXT_BOX_HEIGHT)
@interactions = {
:text_box => @text_box_rect
}
end
#-----------------------------------------------------------------------------
def busy?
return @cursor_pos >= 0 if @captured_area == :text_box
return super
end
def reset_interaction
@cursor_pos = -1
@display_pos = 0
@cursor_timer = nil
@initial_value = nil
Input.text_input = false
invalidate
end
#-----------------------------------------------------------------------------
def get_cursor_index_from_mouse_position
char_widths = []
@value.to_s.length.times { |i| char_widths[i] = self.bitmap.text_size(@value.to_s[i]).width }
mouse_x, mouse_y = mouse_pos
mouse_x -= @text_box_rect.x + TEXT_BOX_PADDING
return 0 if mouse_x < 0
(@display_pos...char_widths.length).each do |i|
mouse_x -= char_widths[i]
if mouse_x <= 0
return (mouse_x.abs >= char_widths[i] / 2) ? i : i + 1
end
end
return @value.to_s.length
end
def reset_display_pos
box_width = @text_box_rect.width - (TEXT_BOX_PADDING * 2)
char_widths = []
@value.to_s.length.times { |i| char_widths[i] = self.bitmap.text_size(@value.to_s[i]).width }
# Text isn't wider than the box
if char_widths.sum <= box_width
return false if @display_pos == 0
@display_pos = 0
return true
end
display_pos_changed = false
# Ensure the cursor hasn't gone off the left side of the text box
if @cursor_pos < @display_pos
@display_pos = @cursor_pos
display_pos_changed = true
end
# Ensure the cursor hasn't gone off the right side of the text box
if @cursor_pos > @display_pos
loop do
cursor_x = 0
(@display_pos...@cursor_pos).each do |i|
cursor_x += char_widths[i] if char_widths[i]
end
break if cursor_x < box_width
@display_pos += 1
display_pos_changed = true
break if @display_pos == @cursor_pos
end
end
# Ensure there isn't empty space on the right if the text can be moved to
# the right to fill it
if @display_pos > 0
cursor_x = 0
(@display_pos...char_widths.length).each do |i|
cursor_x += char_widths[i] if char_widths[i]
end
loop do
cursor_x += char_widths[@display_pos - 1]
break if cursor_x >= box_width
@display_pos -= 1
display_pos_changed = true
break if @display_pos == 0
end
end
return display_pos_changed
end
#-----------------------------------------------------------------------------
def draw_area_highlight
return if @captured_area == :text_box && (@hover_area == @captured_area || !Input.press?(Input::MOUSELEFT))
super
end
def draw_cursor(cursor_x)
return if !@cursor_shown || @cursor_pos < 0
cursor_y_offset = ((height - TEXT_BOX_HEIGHT) / 2) + 2
cursor_height = height - (cursor_y_offset * 2)
bitmap.fill_rect(cursor_x, cursor_y_offset, 2, cursor_height, self.bitmap.font.color)
end
def refresh
super
# Draw text box outline
self.bitmap.outline_rect(@text_box_rect.x, @text_box_rect.y,
@text_box_rect.width, @text_box_rect.height,
self.bitmap.font.color)
# Draw value
char_x = @text_box_rect.x + TEXT_BOX_PADDING
last_char_index = @display_pos
(@value.to_s.length - @display_pos).times do |i|
char = @value.to_s[@display_pos + i]
char_width = self.bitmap.text_size(char).width
cannot_display_next_char = char_x + char_width > @text_box_rect.x + @text_box_rect.width - TEXT_BOX_PADDING
draw_text(self.bitmap, char_x, TEXT_OFFSET_Y, char) if !cannot_display_next_char
# Draw cursor
draw_cursor(char_x - 1) if @display_pos + i == @cursor_pos
break if cannot_display_next_char
last_char_index = @display_pos + i
char_x += char_width
end
# Draw cursor at end
draw_cursor(char_x - 1) if @cursor_pos == @value.to_s.length
# Draw left/right arrows to indicate more text beyond the text box sides
if @display_pos > 0
bitmap.fill_rect(@text_box_rect.x, (height / 2) - 4, 1, 8, Color.white)
5.times do |i|
bitmap.fill_rect(@text_box_rect.x - 2 + i, (height / 2) - (i + 1), 1, 2 * (i + 1), self.bitmap.font.color)
end
end
if last_char_index < @value.to_s.length - 1
bitmap.fill_rect(@text_box_rect.x + @text_box_rect.width - 1, (height / 2) - 4, 1, 8, Color.white)
5.times do |i|
bitmap.fill_rect(@text_box_rect.x + @text_box_rect.width + 1 - i, (height / 2) - (i + 1), 1, 2 * (i + 1), self.bitmap.font.color)
end
end
end
#-----------------------------------------------------------------------------
def on_mouse_press
@captured_area = nil
super
if @captured_area == :text_box
# Clicked into the text box; put the text cursor in there
@cursor_pos = get_cursor_index_from_mouse_position
@cursor_timer = System.uptime
invalidate
else
set_changed if @initial_value && @value != @initial_value
reset_interaction
end
end
def on_mouse_release
return if !@captured_area # Wasn't captured to begin with
# Start text entry if clicked and released mouse button in the text box
if @captured_area == :text_box
mouse_x, mouse_y = mouse_pos
if mouse_x && mouse_y && @interactions[@captured_area].contains?(mouse_x, mouse_y)
@initial_value = @value.clone
Input.text_input = true
invalidate
return # This control is still captured
end
end
# Released mouse button outside of text box, or initially clicked outside of
# text box; end interaction with this control
set_changed if @initial_value && @value != @initial_value
reset_interaction
super # Make this control not busy again
end
def update_special_inputs
# Left/right to move cursor
if Input.triggerex?(:LEFT) || Input.repeatex?(:LEFT)
self.cursor_pos = @cursor_pos - 1 if @cursor_pos > 0
elsif Input.triggerex?(:RIGHT) || Input.repeatex?(:RIGHT)
self.cursor_pos = @cursor_pos + 1 if @cursor_pos < @value.to_s.length
end
# Home/End to jump to start/end of the text
if Input.triggerex?(:HOME) || Input.repeatex?(:HOME)
self.cursor_pos = 0
elsif Input.triggerex?(:END) || Input.repeatex?(:END)
self.cursor_pos = @value.to_s.length
end
# Backspace/Delete to remove text
if Input.triggerex?(:BACKSPACE) || Input.repeatex?(:BACKSPACE)
delete_at(@cursor_pos - 1) if @cursor_pos > 0
elsif Input.triggerex?(:DELETE) || Input.repeatex?(:DELETE)
delete_at(@cursor_pos) if @cursor_pos < @value.to_s.length
end
# Return/Escape to end text input (Escape undoes the change)
if Input.triggerex?(:RETURN) || Input.repeatex?(:RETURN) ||
Input.triggerex?(:KP_ENTER) || Input.repeatex?(:KP_ENTER)
set_changed if @initial_value && @value != @initial_value
reset_interaction
@captured_area = nil
elsif Input.triggerex?(:ESCAPE) || Input.repeatex?(:ESCAPE)
@value = @initial_value if @initial_value
reset_interaction
@captured_area = nil
end
end
def update_text_entry
ret = false
Input.gets.each_char do |ch|
insert_char(ch)
ret = true
end
return ret
end
def update
super
# TODO: Disabled control stuff.
# return if self.disabled
# Make the cursor flash
if @captured_area == :text_box
cursor_to_show = ((System.uptime - @cursor_timer) / 0.35).to_i.even?
if cursor_to_show != @cursor_shown
@cursor_shown = cursor_to_show
invalidate
end
old_cursor_pos = @cursor_pos
# Update cursor movement, deletions and ending text input
update_special_inputs
return if @cursor_pos != old_cursor_pos || !busy?
# Detect character input and add them to @value
char_inserted = update_text_entry
invalidate if reset_display_pos || char_inserted
end
end
end

View File

@@ -0,0 +1,128 @@
#===============================================================================
#
#===============================================================================
class UIControls::Slider < UIControls::BaseControl
attr_reader :min_value
attr_reader :max_value
PLUS_MINUS_SIZE = 16
SLIDER_PADDING = 6 # Gap between sides of interactive area for slider and drawn slider bar
MINUS_X = 0
SLIDER_X = MINUS_X + PLUS_MINUS_SIZE + SLIDER_PADDING
SLIDER_LENGTH = 128
PLUS_X = SLIDER_X + SLIDER_LENGTH + SLIDER_PADDING
VALUE_X = PLUS_X + PLUS_MINUS_SIZE + 5
TEXT_OFFSET_Y = 7
SLIDER_KNOB_COLOR = Color.red
def initialize(width, height, viewport, min_value, max_value, value)
super(width, height, viewport)
@min_value = min_value
@max_value = max_value
self.value = value
end
def value=(new_value)
old_val = @value
@value = new_value.to_i.clamp(self.min_value, self.max_value)
self.invalidate if @value != old_val
end
def min_value=(new_min)
return if new_min == @min_value
@min_value = new_min
@value = @value.clamp(self.min_value, self.max_value)
self.invalidate
end
def max_value=(new_max)
return if new_max == @max_value
@max_value = new_max
@value = @value.clamp(self.min_value, self.max_value)
self.invalidate
end
def set_interactive_rects
@slider_rect = Rect.new(SLIDER_X - SLIDER_PADDING, (self.height - PLUS_MINUS_SIZE) / 2, SLIDER_LENGTH + (SLIDER_PADDING * 2), PLUS_MINUS_SIZE)
@minus_rect = Rect.new(MINUS_X, (self.height - PLUS_MINUS_SIZE) / 2, PLUS_MINUS_SIZE, PLUS_MINUS_SIZE)
@plus_rect = Rect.new(PLUS_X, (self.height - PLUS_MINUS_SIZE) / 2, PLUS_MINUS_SIZE, PLUS_MINUS_SIZE)
@interactions = {
:slider => @slider_rect,
:minus => @minus_rect,
:plus => @plus_rect
}
end
#-----------------------------------------------------------------------------
def draw_area_highlight
# Don't want to ever highlight the slider with the capture color, because
# the mouse doesn't need to be on the slider to change this control's value
if @captured_area == :slider
rect = @interactions[@captured_area]
self.bitmap.fill_rect(rect.x, rect.y, rect.width, rect.height, HOVER_COLOR) if rect
else
super
end
end
def refresh
super
# Draw minus button
self.bitmap.fill_rect(@minus_rect.x + 2, @minus_rect.y + (@minus_rect.height / 2) - 2, @minus_rect.width - 4, 4, self.bitmap.font.color)
# Draw slider bar
self.bitmap.fill_rect(SLIDER_X, (self.height / 2) - 1, SLIDER_LENGTH, 2, self.bitmap.font.color)
# Draw notches on slider bar
5.times do |i|
self.bitmap.fill_rect(SLIDER_X - 1 + (i * SLIDER_LENGTH / 4), (self.height / 2) - 2, 2, 4, self.bitmap.font.color)
end
# Draw slider knob
fraction = (self.value - self.min_value) / self.max_value.to_f
knob_x = (SLIDER_LENGTH * fraction).to_i
self.bitmap.fill_rect(SLIDER_X + knob_x - 4, (self.height / 2) - 6, 8, 12, SLIDER_KNOB_COLOR)
# Draw plus button
self.bitmap.fill_rect(@plus_rect.x + 2, @plus_rect.y + (@plus_rect.height / 2) - 2, @plus_rect.width - 4, 4, self.bitmap.font.color)
self.bitmap.fill_rect(@plus_rect.x + (@plus_rect.width / 2) - 2, @plus_rect.y + 2, 4, @plus_rect.height - 4, self.bitmap.font.color)
# Draw value text
draw_text(self.bitmap, VALUE_X, TEXT_OFFSET_Y, self.value.to_s)
end
#-----------------------------------------------------------------------------
def on_mouse_press
super
@initial_value = @value if @captured_area
end
def on_mouse_release
return if !@captured_area # Wasn't captured to begin with
set_changed if @initial_value && @value != @initial_value
@initial_value = nil
super
end
def update
super
# TODO: Disabled control stuff.
# return if self.disabled
case @captured_area
when :minus
# Constant decrement of value while pressing the minus button
if @hover_area == @captured_area && Input.repeat?(Input::MOUSELEFT)
self.value -= 1
end
when :plus
# Constant incrementing of value while pressing the plus button
if @hover_area == @captured_area && Input.repeat?(Input::MOUSELEFT)
self.value += 1
end
when :slider
# Constant updating of value depending on mouse's x position
mouse_x, mouse_y = mouse_pos
return if !mouse_x || !mouse_y
self.value = lerp(self.min_value, self.max_value + (self.max_value & 1), SLIDER_LENGTH, mouse_x - SLIDER_X)
end
end
end

View File

@@ -0,0 +1,139 @@
#===============================================================================
#
#===============================================================================
class UIControls::ValueBox < UIControls::TextBox
attr_reader :min_value
attr_reader :max_value
PLUS_MINUS_SIZE = 16
CONTROL_PADDING = 2 # Gap between buttons and text box
MINUS_X = 0
TEXT_BOX_X = MINUS_X + PLUS_MINUS_SIZE + CONTROL_PADDING
TEXT_BOX_WIDTH = 64
TEXT_BOX_HEIGHT = 24
PLUS_X = TEXT_BOX_X + TEXT_BOX_WIDTH + CONTROL_PADDING
def initialize(width, height, viewport, min_value, max_value, value)
super(width, height, viewport, value)
@min_value = min_value
@max_value = max_value
self.value = value
end
def value=(new_value)
old_val = @value
@value = new_value.to_i.clamp(self.min_value, self.max_value)
self.invalidate if @value != old_val
end
def min_value=(new_min)
return if new_min == @min_value
@min_value = new_min
@value = @value.clamp(self.min_value, self.max_value)
self.invalidate
end
def max_value=(new_max)
return if new_max == @max_value
@max_value = new_max
@value = @value.clamp(self.min_value, self.max_value)
self.invalidate
end
# TODO: If current value is 0, replace it with ch instead of inserting ch?
def insert_char(ch)
self.value = @value.to_s.insert(@cursor_pos, ch).to_i
@cursor_pos += 1
@cursor_pos = @cursor_pos.clamp(0, @value.to_s.length)
@cursor_timer = System.uptime
@cursor_shown = true
invalidate
end
def delete_at(index)
new_val = @value.to_s
new_val.slice!(index)
self.value = new_val.to_i
@cursor_pos -= 1 if @cursor_pos > index
@cursor_pos = @cursor_pos.clamp(0, @value.to_s.length)
@cursor_timer = System.uptime
@cursor_shown = true
invalidate
end
def set_interactive_rects
@text_box_rect = Rect.new(TEXT_BOX_X, (height - TEXT_BOX_HEIGHT) / 2,
TEXT_BOX_WIDTH, TEXT_BOX_HEIGHT)
@minus_rect = Rect.new(MINUS_X, (self.height - PLUS_MINUS_SIZE) / 2, PLUS_MINUS_SIZE, PLUS_MINUS_SIZE)
@plus_rect = Rect.new(PLUS_X, (self.height - PLUS_MINUS_SIZE) / 2, PLUS_MINUS_SIZE, PLUS_MINUS_SIZE)
@interactions = {
:text_box => @text_box_rect,
:minus => @minus_rect,
:plus => @plus_rect
}
end
#-----------------------------------------------------------------------------
def refresh
super
# Draw minus button
self.bitmap.fill_rect(@minus_rect.x + 2, @minus_rect.y + (@minus_rect.height / 2) - 2, @minus_rect.width - 4, 4, self.bitmap.font.color)
# Draw plus button
self.bitmap.fill_rect(@plus_rect.x + 2, @plus_rect.y + (@plus_rect.height / 2) - 2, @plus_rect.width - 4, 4, self.bitmap.font.color)
self.bitmap.fill_rect(@plus_rect.x + (@plus_rect.width / 2) - 2, @plus_rect.y + 2, 4, @plus_rect.height - 4, self.bitmap.font.color)
end
#-----------------------------------------------------------------------------
def on_mouse_press
@captured_area = nil
super
if @captured_area == :text_box
# Clicked into the text box; put the text cursor in there
@cursor_pos = get_cursor_index_from_mouse_position
@cursor_timer = System.uptime
invalidate
elsif @captured_area
@initial_value = @value
else
set_changed if @initial_value && @value != @initial_value
reset_interaction
end
end
def update_text_entry
ret = false
Input.gets.each_char do |ch|
next if !["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "-"].include?(ch)
if ch == "-"
next if @min_value >= 0 || @cursor_pos > 1 || (@cursor_pos > 0 && @value >= 0)
if @value < 0
delete_at(0) # Remove the negative sign
ret = true
next
end
end
insert_char(ch)
ret = true
end
return ret
end
def update
super
case @captured_area
when :minus
# Constant decrement of value while pressing the minus button
if @hover_area == @captured_area && Input.repeat?(Input::MOUSELEFT)
self.value -= 1
end
when :plus
# Constant incrementing of value while pressing the plus button
if @hover_area == @captured_area && Input.repeat?(Input::MOUSELEFT)
self.value += 1
end
end
end
end

View File

@@ -0,0 +1,50 @@
#===============================================================================
#
#===============================================================================
class UIControls::Button < UIControls::BaseControl
BUTTON_X = 2
BUTTON_PADDING = 10
BUTTON_HEIGHT = 28
TEXT_OFFSET_Y = 7
def initialize(width, height, viewport, text = "")
super(width, height, viewport)
@text = text
end
def set_interactive_rects
text_width = self.bitmap.text_size(@text).width
@button_rect = Rect.new(BUTTON_X, (height - BUTTON_HEIGHT) / 2,
text_width + (BUTTON_PADDING * 2), BUTTON_HEIGHT)
@interactions = {
:button => @button_rect
}
end
#-----------------------------------------------------------------------------
# TODO: Make buttons look more different to text boxes?
def refresh
super
# Draw button outline
self.bitmap.outline_rect(@button_rect.x, @button_rect.y,
@button_rect.width, @button_rect.height,
self.bitmap.font.color)
# Draw button text
draw_text(self.bitmap, BUTTON_X + BUTTON_PADDING, TEXT_OFFSET_Y, @text)
end
#-----------------------------------------------------------------------------
def on_mouse_release
return if !@captured_area # Wasn't captured to begin with
# Change this control's value
if @captured_area == :button
mouse_x, mouse_y = mouse_pos
if mouse_x && mouse_y && @interactions[@captured_area].contains?(mouse_x, mouse_y)
set_changed
end
end
super # Make this control not busy again
end
end

View File

@@ -0,0 +1,13 @@
#===============================================================================
# TODO
# TODO: Click an option to select it. It remains selected indefinitely. Once an
# option is selected, there's probably no way to unselect everything; the
# selection can only be moved to a different option.
# TODO: Scrollable.
# TODO: Find some way to not redraw the entire thing if the hovered option
# changes. Maybe have another bitmap to write the text on (refreshed only
# when the list is scrolled), and self's bitmap draws the hover colour
# only.
#===============================================================================
class UIControls::List < UIControls::BaseControl
end

View File

@@ -0,0 +1,9 @@
#===============================================================================
# TODO
#===============================================================================
class UIControls::DropdownList < UIControls::BaseControl
def initialize(width, height, viewport, options, value)
# NOTE: options is a hash: keys are symbols, values are display names.
super(width, height, viewport)
end
end