#=============================================================================== # #=============================================================================== class UIControls::List < UIControls::BaseControl LIST_X = 0 LIST_Y = 0 ROW_HEIGHT = 24 TEXT_PADDING_X = 4 TEXT_OFFSET_Y = 3 SELECTED_ROW_COLOR = Color.green def initialize(width, height, viewport, values = []) super(width, height, viewport) @scrollbar = UIControls::Scrollbar.new(LIST_X + width - UIControls::Scrollbar::SLIDER_WIDTH, LIST_Y, height, viewport) @scrollbar.set_interactive_rects @scrollbar.range = ROW_HEIGHT @scrollbar.z = self.z + 1 @rows_count = (height / ROW_HEIGHT).floor # Number of rows visible at once @top_row = 0 @selected = -1 self.values = values end def dispose @scrollbar.dispose @scrollbar = nil super end def x=(new_val) super(new_val) @scrollbar.x = new_val + LIST_X + width - UIControls::Scrollbar::SLIDER_WIDTH end def y=(new_val) super(new_val) @scrollbar.y = new_val + LIST_Y end def z=(new_val) super(new_val) @scrollbar.z = new_val + 1 end def visible=(new_val) super @scrollbar.visible = new_val end # Each value in @values is an array: [id, text]. def values=(new_vals) @values = new_vals set_interactive_rects @scrollbar.range = [@values.length, 1].max * ROW_HEIGHT if @scrollbar.visible self.top_row = (@scrollbar.position.to_f / ROW_HEIGHT).round else self.top_row = 0 end self.selected = -1 if @selected >= @values.length invalidate end def top_row=(val) old_val = @top_row @top_row = val if @scrollbar.visible @top_row = @top_row.clamp(0, @values.length - @rows_count) 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 if @values.is_a?(Array) return (@values[@selected].is_a?(Array)) ? @values[@selected][0] : @selected elsif @values.is_a?(Hash) return @values.keys[@selected] end return nil end def mouse_in_control? mouse_x, mouse_y = mouse_pos return false if !mouse_x || !mouse_y return true if Rect.new(0, 0, width, height).contains?(mouse_x, mouse_y) return true if @scrollbar.mouse_in_control? return false end def set_interactive_rects @interactions = {} @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. There's no point drawing a # highlight at all if anything is captured. return if @captured_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 repaint @scrollbar.repaint if @scrollbar.invalid? super if invalid? 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 txt = (val.is_a?(Array)) ? val[1] : val draw_text(self.bitmap, @interactions[i].x + TEXT_PADDING_X, @interactions[i].y + TEXT_OFFSET_Y - (@top_row * ROW_HEIGHT), txt) end end #----------------------------------------------------------------------------- def on_mouse_press @captured_area = nil mouse_x, mouse_y = mouse_pos return if !mouse_x || !mouse_y return if @scrollbar.visible && (@scrollbar.busy? || mouse_x >= @scrollbar.x - self.x) # 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 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 # Don't update the highlight if the mouse is using the scrollbar if @scrollbar.visible && (@scrollbar.busy? || mouse_x >= @scrollbar.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) || 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 invalidate if @hover_area @hover_area = nil end end def update return if !self.visible @scrollbar.update super # Refresh the list's position if changed by moving the scrollbar self.top_row = (@scrollbar.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) elsif @hover_area wheel_v = Input.scroll_v scroll_dist = UIControls::Scrollbar::SCROLL_DISTANCE scroll_dist /= 2 if @values.length / @rows_count > 20 # Arbitrary 20 if wheel_v > 0 # Scroll up @scrollbar.slider_top -= scroll_dist elsif wheel_v < 0 # Scroll down @scrollbar.slider_top += scroll_dist end if wheel_v != 0 self.top_row = (@scrollbar.position.to_f / ROW_HEIGHT).round update_hover_highlight end end end end