module WhirledPeas module Graphics module ScrollbarHelper # Contants to paint scrollbars HORIZONTAL = [' ', '▗', '▖', '▄'] VERTICAL = [ ' ', '▗', '▝', '▐' ] # The number of units of scrollbar a single character can be divided into SCROLL_HANDLE_SCALE = 2 private_constant :SCROLL_HANDLE_SCALE class << self # Determine the character to paint the horizontal scroll bar with for the given column # # @see #scroll_char for more details def horiz_char(col_count, viewable_col_count, first_visible_col, curr_col) scroll_char(col_count, viewable_col_count, first_visible_col, curr_col, HORIZONTAL) end # Determine the character to paint the vertical scroll bar with for the given row # # @see #scroll_char for more details def vert_char(row_count, viewable_row_count, first_visible_row, curr_row) scroll_char(row_count, viewable_row_count, first_visible_row, curr_row, VERTICAL) end private # Determine which character to paint a for a scrollbar # # @param total_count [Integer] total number of rows/columns in the content # @param viewable_count [Integer] number of rows/columns visible in the viewport # @param first_visible [Integer] zero-based index of the first row/column that is visible # in the viewport # @param curr [Integer] zero-based index of the row/column (relative to the first visible # row/column) that the painted character is being requested for # @param chars [Array<String>] an array with three 1-character strings, the frist is the # "second half" scrollbar character, the second is the "full" scrollbar character, and # the third is the "first half" scrollbar character. def scroll_char(total_count, viewable_count, first_visible, curr, chars) return chars[0] if total_count == 0 return chars[0] if viewable_count == 0 return chars[0] if viewable_count >= total_count # The scroll handle has the exact same relative size and position in the scroll gutter # that the viewable content has in the total content area. For example, a content area # that is 50 columns wide with a view port that is 20 columns wide might look like # # +---------1-----****2*********3******---4---------+ # | * * | # | hidden * viewable * hidden | # | * * | # +---------1-----****2*********3******---4---------+ # # The scoll gutter, would look like # # |......********.....| # # Scrolling all the way to the right results in # # +---------1---------2---------3*********4*********+ # | * * # | hidden * viewable * # | * * # +---------1---------2---------3*********4*********+ # |...........********| # # Returning to the first example, we can match up the arguments to this method to the # diagram # # total_count = 50 # |<----------------------------------------------->| # | | # | veiwable_count = 20 | # | |<----------------->| | # ↓ ↓ ↓ ↓ # +---------1-----****2*********3******---4---------+ # | * * | # | hidden * viewable * hidden | # | * * | # +---------1-----****2*********3******---4---------+ # |......****?***.....| # ↑ ↑ # first_visible = 16 | # curr = 11 # Always use the same length for the scrollbar so it does not give an inchworm effect # as it scrolls along. This will calculate the "ideal" length of the scroll bar if we # could infinitely divide a character. scrollbar_length = (viewable_count ** 2).to_f / total_count # Round the length to the nearst "scaled" value scrollbar_length = (SCROLL_HANDLE_SCALE * scrollbar_length).round.to_f / SCROLL_HANDLE_SCALE # Ensure we have a scrollbar! scrollbar_length = 1.0 / SCROLL_HANDLE_SCALE if scrollbar_length == 0 # Find the "ideal" position of where the scrollbar should start. scrollbar_start = first_visible * viewable_count.to_f / total_count # Round the start to the nearest "scaled" value scrollbar_start = (SCROLL_HANDLE_SCALE * scrollbar_start).round.to_f / SCROLL_HANDLE_SCALE # Make sure we didn't scroll off the page! scrollbar_start -= scrollbar_length if scrollbar_start == viewable_count # Create "scaled" indexes for the subdivided current character curr_0 = curr curr_1 = curr + 1.0 / SCROLL_HANDLE_SCALE first_half = scrollbar_start <= curr_0 && curr_0 < scrollbar_start + scrollbar_length ? 2 : 0 second_half = scrollbar_start <= curr_1 && curr_1 < scrollbar_start + scrollbar_length ? 1 : 0 chars[second_half | first_half] end end end end end