From 193f01f70b16cd2030d0fd1d766c9526185d7318 Mon Sep 17 00:00:00 2001 From: Maruno17 Date: Fri, 6 Oct 2023 20:59:30 +0100 Subject: [PATCH] Refactored scrollbar into its own control --- Data/Scripts/905_New controls/008_list.rb | 125 +++++++-------- .../Scripts/905_New controls/101_scrollbar.rb | 143 ++++++++++++++++++ .../910_New anim editor/001_anim selection.rb | 4 + 3 files changed, 198 insertions(+), 74 deletions(-) create mode 100644 Data/Scripts/905_New controls/101_scrollbar.rb diff --git a/Data/Scripts/905_New controls/008_list.rb b/Data/Scripts/905_New controls/008_list.rb index 020295306..d04c51666 100644 --- a/Data/Scripts/905_New controls/008_list.rb +++ b/Data/Scripts/905_New controls/008_list.rb @@ -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 diff --git a/Data/Scripts/905_New controls/101_scrollbar.rb b/Data/Scripts/905_New controls/101_scrollbar.rb new file mode 100644 index 000000000..05dbb8523 --- /dev/null +++ b/Data/Scripts/905_New controls/101_scrollbar.rb @@ -0,0 +1,143 @@ +#=============================================================================== +# 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 + TRAY_COLOR = Color.white + SLIDER_COLOR = Color.black + GRAB_COLOR = HOVER_COLOR # Cyan + + 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 + @visible = @always_visible + @always_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 + end + end +end diff --git a/Data/Scripts/910_New anim editor/001_anim selection.rb b/Data/Scripts/910_New anim editor/001_anim selection.rb index cd8c2d6e8..944598c48 100644 --- a/Data/Scripts/910_New anim editor/001_anim selection.rb +++ b/Data/Scripts/910_New anim editor/001_anim selection.rb @@ -45,6 +45,10 @@ class AnimationEditorLoadScreen end @animations.push([id, name]) end + # TODO: For slider testing purposes. + rand(400).times do |i| + @animations.push([42 + i, "Extra animation #{i + 1}"]) + end end def draw_editor_background