mirror of
https://github.com/infinitefusion/infinitefusion-e18.git
synced 2025-12-06 06:01:46 +00:00
313 lines
10 KiB
Ruby
313 lines
10 KiB
Ruby
#===============================================================================
|
|
# 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 = 5
|
|
|
|
def initialize(width, height, viewport, value = "")
|
|
super(width, height, viewport)
|
|
@value = value
|
|
@cursor_pos = -1
|
|
@display_pos = 0
|
|
@cursor_timer = nil
|
|
@cursor_shown = false
|
|
@blacklist = []
|
|
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_blacklist(*list)
|
|
@blacklist = list
|
|
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 disabled?
|
|
val = (@value.respond_to?("strip!")) ? @value.strip : @value
|
|
return true if @blacklist.include?(val)
|
|
return super
|
|
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 disabled colour
|
|
if disabled?
|
|
self.bitmap.fill_rect(@text_box_rect.x, @text_box_rect.y,
|
|
@text_box_rect.width, @text_box_rect.height,
|
|
DISABLED_COLOR)
|
|
end
|
|
# 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
|
|
@value.strip! if @value.respond_to?("strip!")
|
|
@value = @initial_value if disabled?
|
|
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
|
|
@value.strip! if @value.respond_to?("strip!")
|
|
@value = @initial_value if disabled?
|
|
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)
|
|
@value.strip! if @value.respond_to?("strip!")
|
|
@value = @initial_value if disabled?
|
|
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
|
|
return if !self.visible
|
|
super
|
|
# 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
|