Refactored scrollbar into its own control

This commit is contained in:
Maruno17
2023-10-06 20:59:30 +01:00
parent 79ffcd3230
commit 193f01f70b
3 changed files with 198 additions and 74 deletions

View File

@@ -1,8 +1,10 @@
#===============================================================================
# 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.
# for text)? This would be to reduce lag caused by redrawing text 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.
# TODO: Make a viewport for the list, and allow scrolling positions halfway
# through a line? Nah.
#===============================================================================
class UIControls::List < UIControls::BaseControl
LIST_X = 0
@@ -10,26 +12,44 @@ class UIControls::List < UIControls::BaseControl
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)
@slider = UIControls::Scrollbar.new(LIST_X + width - UIControls::Scrollbar::SLIDER_WIDTH, LIST_Y, height, viewport)
@slider.set_interactive_rects
@slider.range = ROW_HEIGHT
@slider.z = self.z + 1
@rows_count = (height / ROW_HEIGHT).floor # Number of rows visible at once
@top_row = 0
@selected = -1
@show_slider = false
self.values = values
end
def dispose
@slider.dispose
@slider = nil
super
end
def x=(new_val)
super(new_val)
@slider.x = new_val + LIST_X + width - UIControls::Scrollbar::SLIDER_WIDTH
end
def y=(new_val)
super(new_val)
@slider.y = new_val + LIST_Y
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
@slider.range = @values.length * ROW_HEIGHT
if @slider.visible
self.top_row = (@slider.position.to_f / ROW_HEIGHT).round
else
self.top_row = 0
end
@@ -40,9 +60,8 @@ class UIControls::List < UIControls::BaseControl
def top_row=(val)
old_val = @top_row
@top_row = val
if @show_slider
if @slider.visible
@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
@@ -63,14 +82,6 @@ class UIControls::List < UIControls::BaseControl
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
@@ -86,14 +97,9 @@ class UIControls::List < UIControls::BaseControl
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.
# colour will be drawn over the highlight. 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
@@ -103,6 +109,11 @@ class UIControls::List < UIControls::BaseControl
end
end
def repaint
@slider.repaint if @slider.invalid?
super if invalid?
end
def refresh
super
# Draw text options
@@ -121,15 +132,6 @@ class UIControls::List < UIControls::BaseControl
@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
#-----------------------------------------------------------------------------
@@ -138,16 +140,7 @@ class UIControls::List < UIControls::BaseControl
@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
return if @slider.visible && (@slider.busy? || mouse_x >= @slider.x - self.x)
# Check for mouse presses on rows
mouse_y += @top_row * ROW_HEIGHT
@interactions.each_pair do |area, rect|
@@ -161,7 +154,7 @@ class UIControls::List < UIControls::BaseControl
def on_mouse_release
return if !@captured_area # Wasn't captured to begin with
set_changed if @captured_area != :slider
set_changed
super
end
@@ -174,28 +167,24 @@ class UIControls::List < UIControls::BaseControl
@hover_area = nil
return
end
# Don't update the highlight if the mouse is using the scrollbar
if @slider.visible && (@slider.busy? || mouse_x >= @slider.x - self.x)
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
mouse_y += @top_row * ROW_HEIGHT
@interactions.each_pair do |area, rect|
next if area.is_a?(Integer)
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
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
@@ -204,26 +193,14 @@ class UIControls::List < UIControls::BaseControl
def update
return if !self.visible
@slider.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
# Refresh the list's position if changed by moving the slider
self.top_row = (@slider.position.to_f / ROW_HEIGHT).round
# Set the selected row to the row the mouse is over, if clicked on
if @captured_area
@selected = @hover_area if @hover_area.is_a?(Integer)
end
end