Files
infinitefusion-e18/Data/Scripts/905_New controls/101_scrollbar.rb
2023-10-23 22:36:43 +01:00

157 lines
5.1 KiB
Ruby

#===============================================================================
# TODO: Make the slider a separate sprite that moves, instead of redrawing this
# sprite's bitmap whenever it moves? Intended to reduce lag. There doesn't
# seem to be any lag at the moment with a tall scrollbar.
#===============================================================================
class UIControls::Scrollbar < UIControls::BaseControl
SLIDER_WIDTH = 16
WIDTH_PADDING = 0
SCROLL_DISTANCE = 16
TRAY_COLOR = Color.white
SLIDER_COLOR = Color.black
GRAB_COLOR = HOVER_COLOR # Cyan
attr_reader :slider_top
def initialize(x, y, size, viewport, horizontal = false, always_visible = false)
if horizontal
super(size, SLIDER_WIDTH, viewport)
else
super(SLIDER_WIDTH, size, viewport)
end
self.x = x
self.y = y
@horizontal = horizontal # Is vertical if not horizontal
@tray_size = size # Number of pixels the scrollbar can move around in
@slider_size = size
@range = size # Total distance of the area this scrollbar is for
@slider_top = 0 # Top pixel within @size of the scrollbar
@always_visible = always_visible
self.visible = @always_visible
end
def position
return 0 if @range <= @tray_size
return (@range - @tray_size) * @slider_top / (@tray_size - @slider_size)
end
# Range is the total size of the large area that the scrollbar is able to
# show part of.
def range=(new_val)
raise "Can't set a scrollbar's range to 0!" if new_val == 0
@range = new_val
@slider_size = (@tray_size * [@tray_size.to_f / @range, 1].min).round
if @horizontal
@slider.width = @slider_size
else # Vertical
@slider.height = @slider_size
end
self.slider_top = @slider_top
self.visible = (@always_visible || @range > @tray_size)
invalidate
end
def slider_top=(new_val)
old_val = @slider_top
@slider_top = new_val.clamp(0, @tray_size - @slider_size)
if @horizontal
@slider.x = @slider_top
else # Vertical
@slider.y = @slider_top
end
invalidate if @slider_top != old_val
end
def set_interactive_rects
@interactions = {}
if @horizontal
@slider = Rect.new(@slider_top, WIDTH_PADDING, @slider_size, height - (WIDTH_PADDING * 2))
else # Vertical
@slider = Rect.new(WIDTH_PADDING, @slider_top, width - (WIDTH_PADDING * 2), @slider_size)
end
@interactions[:slider] = @slider
@slider_tray = Rect.new(0, 0, width, height)
@interactions[:slider_tray] = @slider_tray
end
#-----------------------------------------------------------------------------
def refresh
super
return if !self.visible
# Draw the tray
self.bitmap.fill_rect(@slider_tray.x, @slider_tray.y, @slider_tray.width, @slider_tray.height, TRAY_COLOR)
# Draw the slider
if @slider_size < @tray_size
bar_color = SLIDER_COLOR
if @captured_area == :slider || (!@captured_area && @hover_area == :slider)
bar_color = GRAB_COLOR
end
self.bitmap.fill_rect(@slider.x, @slider.y, @slider.width, @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 !rect.contains?(mouse_x, mouse_y)
@captured_area = area
if area == :slider
if @horizontal
@slider_mouse_offset = mouse_x - rect.x
else
@slider_mouse_offset = mouse_y - rect.y
end
end
invalidate
break
end
end
def on_mouse_release
super if @captured_area
end
def update
return if !self.visible
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
long_coord = (@horizontal) ? mouse_x : mouse_y
self.slider_top = long_coord - @slider_mouse_offset
elsif @captured_area == :slider_tray
if Input.repeat?(Input::MOUSELEFT) && @hover_area == :slider_tray
mouse_x, mouse_y = mouse_pos
return if !mouse_x || !mouse_y
long_coord = (@horizontal) ? mouse_x : mouse_y
if long_coord < @slider_top
self.slider_top = @slider_top - ((@tray_size - @slider_size) / 4.0).ceil
else
self.slider_top = @slider_top + ((@tray_size - @slider_size) / 4.0).ceil
end
end
else
mouse_x, mouse_y = mouse_pos
if mouse_x && mouse_y && @interactions[:slider_tray].contains?(mouse_x, mouse_y)
wheel_v = Input.scroll_v
if wheel_v > 0 # Scroll up
self.slider_top -= SCROLL_DISTANCE
elsif wheel_v < 0 # Scroll down
self.slider_top += SCROLL_DISTANCE
end
end
end
end
end