Implemented list control and basic animation-choosing screen for editor

This commit is contained in:
Maruno17
2023-08-31 23:03:47 +01:00
parent 1041883992
commit d4077875a4
7 changed files with 367 additions and 59 deletions

View File

@@ -85,6 +85,11 @@ class UIControls::BaseControl < BitmapSprite
this_bitmap.draw_text(text_x, text_y, text_size.width, text_size.height, this_text, 0)
end
def draw_text_centered(this_bitmap, text_x, text_y, wid, this_text)
text_size = this_bitmap.text_size(this_text)
this_bitmap.draw_text(text_x, text_y, wid, text_size.height, this_text, 1)
end
# Redraws the control only if it is invalid.
def repaint
return if !invalid?

View File

@@ -2,10 +2,10 @@
# NOTE: Strictly speaking, this is a toggle switch and not a checkbox.
#===============================================================================
class UIControls::Checkbox < UIControls::BaseControl
CHECKBOX_X = 0
CHECKBOX_X = 2
CHECKBOX_WIDTH = 40
CHECKBOX_HEIGHT = 24
CHECKBOX_FILL_SIZE = CHECKBOX_HEIGHT - 8
CHECKBOX_FILL_SIZE = CHECKBOX_HEIGHT - 4
UNCHECKED_COLOR = Color.gray
CHECKED_COLOR = Color.new(64, 255, 64) # Green
@@ -34,19 +34,19 @@ class UIControls::Checkbox < UIControls::BaseControl
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.outline_rect(@checkbox_rect.x, @checkbox_rect.y,
@checkbox_rect.width, @checkbox_rect.height,
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,
self.bitmap.fill_rect(@checkbox_rect.x + @checkbox_rect.width - CHECKBOX_FILL_SIZE - 2, @checkbox_rect.y + 2,
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,
self.bitmap.outline_rect(@checkbox_rect.x + @checkbox_rect.width - CHECKBOX_FILL_SIZE - 2, @checkbox_rect.y + 2,
CHECKBOX_FILL_SIZE, CHECKBOX_FILL_SIZE, self.bitmap.font.color)
else
self.bitmap.fill_rect(@checkbox_rect.x + 4, @checkbox_rect.y + 4,
self.bitmap.fill_rect(@checkbox_rect.x + 2, @checkbox_rect.y + 2,
CHECKBOX_FILL_SIZE, CHECKBOX_FILL_SIZE, UNCHECKED_COLOR)
self.bitmap.outline_rect(@checkbox_rect.x + 4, @checkbox_rect.y + 4,
self.bitmap.outline_rect(@checkbox_rect.x + 2, @checkbox_rect.y + 2,
CHECKBOX_FILL_SIZE, CHECKBOX_FILL_SIZE, self.bitmap.font.color)
end
end

View File

@@ -15,7 +15,9 @@ class UIControls::Slider < UIControls::BaseControl
VALUE_X = PLUS_X + PLUS_MINUS_SIZE + 5
TEXT_OFFSET_Y = 7
SLIDER_KNOB_COLOR = Color.red
# TODO: Is there a better knob design than a big black rectangle? I'd rather
# it not be a different colour.
SLIDER_KNOB_COLOR = Color.black
def initialize(width, height, viewport, min_value, max_value, value)
super(width, height, viewport)

View File

@@ -2,20 +2,28 @@
#
#===============================================================================
class UIControls::Button < UIControls::BaseControl
BUTTON_X = 2
BUTTON_PADDING = 10
BUTTON_HEIGHT = 28
TEXT_OFFSET_Y = 7
BUTTON_X = 2
BUTTON_Y = 2
BUTTON_PADDING = 10
BUTTON_HEIGHT = 28
# TODO: This will also depend on the font size.
TEXT_BASE_OFFSET_Y = 18 # Text is centred vertically in the button
def initialize(width, height, viewport, text = "")
super(width, height, viewport)
@text = text
@fixed_size = false
end
def set_fixed_size
@fixed_size = true
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)
button_width = (@fixed_size) ? width - (BUTTON_X * 2) : self.bitmap.text_size(@text).width + (BUTTON_PADDING * 2)
button_height = (@fixed_size) ? height - (2 * BUTTON_Y) : BUTTON_HEIGHT
button_height = [button_height, height - (2 * BUTTON_Y)].min
@button_rect = Rect.new(BUTTON_X, (height - button_height) / 2, button_width, button_height)
@interactions = {
:button => @button_rect
}
@@ -23,15 +31,22 @@ class UIControls::Button < UIControls::BaseControl
#-----------------------------------------------------------------------------
# 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)
# TODO: Make buttons look more different to text boxes?
# shade = self.bitmap.font.color.clone
# shade.alpha = 96
# self.bitmap.outline_rect(@button_rect.x + 1, @button_rect.y + 1,
# @button_rect.width - 2, @button_rect.height - 2,
# shade, 3)
# Draw button text
draw_text(self.bitmap, BUTTON_X + BUTTON_PADDING, TEXT_OFFSET_Y, @text)
draw_text_centered(self.bitmap, @button_rect.x,
@button_rect.y + (@button_rect.height - TEXT_BASE_OFFSET_Y) / 2,
@button_rect.width, @text)
end
#-----------------------------------------------------------------------------

View File

@@ -1,13 +1,228 @@
#===============================================================================
# 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.
# TODO: Do I need to split self's bitmap into two (one for highlights and one
# for text/slider)? This would be to reduce lag caused by redrawing text
# and the slider even if you're just waving the mouse over the control.
# There doesn't seem to be any lag at the moment with a tall list.
#===============================================================================
class UIControls::List < UIControls::BaseControl
LIST_X = 0
LIST_Y = 0
ROW_HEIGHT = 24
TEXT_PADDING_X = 4
TEXT_OFFSET_Y = 3
SLIDER_WIDTH = 16
SELECTED_ROW_COLOR = Color.green
def initialize(width, height, viewport, values = [])
super(width, height, viewport)
@rows_count = (height / ROW_HEIGHT).floor # Number of rows visible at once
@top_row = 0
@selected = -1
@show_slider = false
self.values = values
end
# Each value in @values is an array: [id, text].
def values=(new_vals)
@values = new_vals
@show_slider = (@values.length > @rows_count)
set_interactive_rects
if @show_slider
self.top_row = @top_row
else
self.top_row = 0
end
invalidate
end
def top_row=(val)
old_val = @top_row
@top_row = val
if @show_slider
@top_row = @top_row.clamp(0, @values.length - @rows_count)
@slider.y = lerp(0, height - @slider.height, @values.length - @rows_count, 0, @top_row).round
else
@top_row = 0
end
invalidate if @top_row != old_val
end
def selected=(val)
return if @selected == val
@selected = val
invalidate
end
# Returns the ID of the selected row.
def value
return nil if @selected < 0
return @values[@selected][0]
end
def set_interactive_rects
@interactions = {}
@slider = nil
if @show_slider
@slider = Rect.new(LIST_X + width - SLIDER_WIDTH, LIST_Y,
SLIDER_WIDTH, height * @rows_count / @values.length)
@interactions[:slider] = @slider
@slider_tray = Rect.new(LIST_X + width - SLIDER_WIDTH, LIST_Y, SLIDER_WIDTH, height)
@interactions[:slider_tray] = @slider_tray
end
@values.length.times do |i|
@interactions[i] = Rect.new(LIST_X, LIST_Y + (ROW_HEIGHT * i), width - LIST_X, ROW_HEIGHT)
end
end
#-----------------------------------------------------------------------------
def busy?
return !@captured_area.nil?
end
#-----------------------------------------------------------------------------
def draw_area_highlight
# If a row is captured, it will automatically be selected and the selection
# colour will be drawn over the highlight. The slider tray background
# (white) is drawn over the slider/slider tray's highlight. Either way,
# there's no point drawing a highlight at all if anything is captured.
return if @captured_area
# The slider tray background (white) is drawn over the slider/slider tray's
# highlight. There's no point drawing any highlight for the slider now; this
# is done in def refresh instead.
return if [:slider, :slider_tray].include?(@hover_area)
# Draw mouse hover over row highlight
rect = @interactions[@hover_area]
if rect
rect_y = rect.y
rect_y -= @top_row * ROW_HEIGHT if @hover_area.is_a?(Integer)
self.bitmap.fill_rect(rect.x, rect_y, rect.width, rect.height, HOVER_COLOR)
end
end
def refresh
super
# Draw text options
@values.each_with_index do |val, i|
next if i < @top_row || i >= @top_row + @rows_count
if @selected == i
self.bitmap.fill_rect(
@interactions[i].x,
@interactions[i].y - (@top_row * ROW_HEIGHT),
@interactions[i].width, @interactions[i].height,
SELECTED_ROW_COLOR
)
end
draw_text(self.bitmap,
@interactions[i].x + TEXT_PADDING_X,
@interactions[i].y + TEXT_OFFSET_Y - (@top_row * ROW_HEIGHT),
val[1])
end
# Draw vertical slider
if @show_slider
self.bitmap.fill_rect(@slider_tray.x, @slider_tray.y, @slider_tray.width, @slider_tray.height, Color.white)
bar_color = self.bitmap.font.color
if @captured_area == :slider || (!@captured_area && @hover_area == :slider)
bar_color = HOVER_COLOR
end
self.bitmap.fill_rect(@slider.x + 1, @slider.y, @slider.width - 1, @slider.height, bar_color)
end
end
#-----------------------------------------------------------------------------
def on_mouse_press
@captured_area = nil
mouse_x, mouse_y = mouse_pos
return if !mouse_x || !mouse_y
# Check for mouse presses on slider/slider tray
@interactions.each_pair do |area, rect|
next if area.is_a?(Integer)
next if !rect.contains?(mouse_x, mouse_y)
@captured_area = area
@slider_mouse_offset = mouse_y - rect.y if area == :slider
invalidate
break
end
return if @captured_area
# Check for mouse presses on rows
mouse_y += @top_row * ROW_HEIGHT
@interactions.each_pair do |area, rect|
next if !area.is_a?(Integer) || area < @top_row || area >= @top_row + @rows_count
next if !rect.contains?(mouse_x, mouse_y)
@captured_area = area
invalidate
break
end
end
def on_mouse_release
return if !@captured_area # Wasn't captured to begin with
set_changed if @captured_area != :slider
super
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 area.is_a?(Integer)
next if !rect.contains?(mouse_x, mouse_y)
invalidate if @hover_area != area
@hover_area = area
in_area = true
break
end
if !in_area
mouse_y += @top_row * ROW_HEIGHT
@interactions.each_pair do |area, rect|
next if !area.is_a?(Integer) || area < @top_row || area >= @top_row + @rows_count
next if !rect.contains?(mouse_x, mouse_y)
invalidate if @hover_area != area
@hover_area = area
in_area = true
break
end
end
if !in_area
invalidate if @hover_area
@hover_area = nil
end
end
def update
super
# TODO: Disabled control stuff.
# return if self.disabled
if @captured_area == :slider
# TODO: Have a display y position for the slider bar which is in pixels,
# and round it to the nearest row when setting @top_row? This is
# just to make the slider bar movement smoother.
mouse_x, mouse_y = mouse_pos
return if !mouse_x || !mouse_y
self.top_row = lerp(0, @values.length - @rows_count, height - @slider.height, 0, mouse_y - @slider_mouse_offset).round
elsif @captured_area == :slider_tray
if Input.repeat?(Input::MOUSELEFT) && @hover_area == :slider_tray
if mouse_y < @slider.y
self.top_row = @top_row - (@rows_count / 2)
else
self.top_row = @top_row + (@rows_count / 2)
end
end
elsif @captured_area
# Have clicked on a row; set the selected row to the row themouse is over
@selected = @hover_area if @hover_area.is_a?(Integer)
end
end
end