module Vedeu # A Viewport is the visible part of the content within an interface. # # When a buffer has more lines than the defined height, or more columns than # the defined width of the interface, the Viewport class provides 'scrolling' # via the cursor's position. # class Viewport extend Forwardable def_delegators :interface, :geometry, :lines, :lines?, :cursor def_delegators :cursor, :ox, :oy def_delegators :geometry, :height, :width # Returns an instance of Viewport. # # @param interface [Interface] An instance of interface. # @return [Viewport] def initialize(interface) @interface = interface end # Returns the interface with border (if enabled) and the content for the # interface. # # @return [Array<Array<String>>] def render if border? out = [] out << border.top if border.top? show[0...bordered_height].each do |line| out << [border.left, line[0...bordered_width], border.right].flatten end out << border.bottom if border.bottom? out else show end end # Returns a string representation of the viewport. # # @return [String] def to_s render.map(&:join).join("\n") end private attr_reader :interface # Returns the visible content for the interface. # # @note If there are no lines of content, we return an empty array. If there # are no more columns of content we return a space enclosed in an array; # this prevents a weird line hopping bug which occurs when the current # line has no more content, but subsequent lines do. # # @return [Array] def show return [] unless lines? padded_lines.map { |line| padded_columns(line) }.compact end # Returns the lines, padded where necessary for the viewport. # # @return [Array<Array<String>>] def padded_lines visible = lines[rows] || [] pad(visible, :height) end # Returns the columns, padded where necessary for the given line. # # @param line [Array<String>] # @return [Array<String>] def padded_columns(line) visible = line.chars[columns] || [] pad(visible, :width) end # Pads the number of rows or columns to always return an Array of the same # length for each viewport or line respectively. # # @param visible [Array<Array<String>>|Array<String>] # @param dimension [Symbol] The dimension to pad (:height or :width). # @return [Array<Array<String>>|Array<String>] def pad(visible, dimension) dim = send(dimension) size = visible.size return visible unless size < dim visible + [" "] * (dim - size) end # Using the current cursor's y position, return a range of visible lines. # # Scrolls the content vertically when the stored cursor's y position for the # interface is outside of the visible area. # # @return [Range] def rows top..(top + (height - 1)) end # Using the current cursor's x position, return a range of visible columns. # # Scrolls the content horizontally when the stored cursor's x position for # the interface is outside of the visible area. # # @return [Range] def columns left..(left + (width - 1)) end # Returns the offset for the content based on the offset. # # @return [Fixnum] def left @left ||= reposition_x? ? reposition_x : 0 end # Returns the offset for the content based on the offset. # # @return [Fixnum] def top @top ||= reposition_y? ? reposition_y : 0 end # Returns a boolean indicating whether the x offset is greater than or equal # to the bordered width. # # @return [Boolean] def reposition_x? ox >= bordered_width end # Returns a boolean indicating whether the y offset is greater than or equal # to the bordered height. # # @return [Boolean] def reposition_y? oy >= bordered_height end # Returns the number of columns to change the viewport by on the x axis, # determined by the position of the x offset. # # @return [Fixnum] def reposition_x ((ox - bordered_width) <= 0) ? 0 : (ox - bordered_width) end # Returns the number of rows to change the viewport by on the y axis, # determined by the position of the y offset. # # @return [Fixnum] def reposition_y ((oy - bordered_height) <= 0) ? 0 : (oy - bordered_height) end # When the viewport has a border, we need to account for that in our # redrawing. # # @return [Fixnum] def bordered_width return border.width if border? width end # When the viewport has a border, we need to account for that in our # redrawing. # # @return [Fixnum] def bordered_height return border.height if border? height end # Return the border associated with the interface we are drawing. # # @return [Vedeu::Border] def border @border ||= Vedeu.borders.find(interface.name) end # Returns a boolean indicating the interface we are drawing has a border. # # @return [Boolean] def border? Vedeu.borders.registered?(interface.name) end end # Viewport end # Vedeu