module TTY::Box
Constants
- BOX_CHARS
- LINE_BREAK
- NEWLINE
- SPACE
- VERSION
Public Instance Methods
Bottom border
@return [String]
@api private
# File lib/tty/box.rb, line 493 def bottom_border(title, width, border, style) fg, bg = *extract_style(style[:border] || {}) bottom_space_left = width - bottom_space_taken(title, border) bottom_space_before = bottom_space_left / 2 bottom_space_after = bottom_space_left / 2 + bottom_space_left % 2 [ bg.(fg.(bottom_left_corner(border))), bg.(fg.(title[:bottom_left].to_s)), bg.(fg.(line_char(border.type) * bottom_space_before)), bg.(fg.(title[:bottom_center].to_s)), bg.(fg.(line_char(border.type) * bottom_space_after)), bg.(fg.(title[:bottom_right].to_s)), bg.(fg.(bottom_right_corner(border))) ].join end
Bottom left corner
@param [Border] border
@return [String]
@api private
# File lib/tty/box.rb, line 455 def bottom_left_corner(border) return "" unless border.bottom_left? && border.left? send(:"#{border.bottom_left}_char", border.type) end
Bottom right corner
@param [Border] border
@return [String]
@api private
# File lib/tty/box.rb, line 469 def bottom_right_corner(border) return "" unless border.bottom_right? && border.right? send(:"#{border.bottom_right}_char", border.type) end
Bottom space taken by titles and corners
@return [Integer]
@api private
# File lib/tty/box.rb, line 441 def bottom_space_taken(title, border) bottom_titles_size(title) + bottom_left_corner(border).size + bottom_right_corner(border).size end
Bottom titles size
@return [Integer]
@api private
# File lib/tty/box.rb, line 481 def bottom_titles_size(title) color.strip(title[:bottom_left].to_s).size + color.strip(title[:bottom_center].to_s).size + color.strip(title[:bottom_right].to_s).size end
# File lib/tty/box.rb, line 73 def color(enabled: nil) @color ||= Pastel.new(enabled: enabled) end
Convert content array to string
@param [Array<String>] content
@return [String]
@api private
# File lib/tty/box.rb, line 275 def content_to_str(content) case content.size when 0 then "" when 1 then content[0] else content.join(NEWLINE) end end
The maximum content width for all the lines
@param [Array<String>] lines
@return [Integer]
@api private
# File lib/tty/box.rb, line 307 def content_width(lines) return 1 if lines.empty? lines.map(&Strings::ANSI.method(:sanitize)).max_by(&:length).length end
# File lib/tty/box.rb, line 37 def corner_bottom_left_char(border = :light) BOX_CHARS[border][3] end
# File lib/tty/box.rb, line 25 def corner_bottom_right_char(border = :light) BOX_CHARS[border][0] end
# File lib/tty/box.rb, line 33 def corner_top_left_char(border = :light) BOX_CHARS[border][2] end
# File lib/tty/box.rb, line 29 def corner_top_right_char(border = :light) BOX_CHARS[border][1] end
# File lib/tty/box.rb, line 65 def cross_char(border = :light) BOX_CHARS[border][10] end
# File lib/tty/box.rb, line 69 def cursor TTY::Cursor end
# File lib/tty/box.rb, line 49 def divider_down_char(border = :light) BOX_CHARS[border][6] end
# File lib/tty/box.rb, line 41 def divider_left_char(border = :light) BOX_CHARS[border][4] end
# File lib/tty/box.rb, line 53 def divider_right_char(border = :light) BOX_CHARS[border][7] end
# File lib/tty/box.rb, line 45 def divider_up_char(border = :light) BOX_CHARS[border][5] end
A frame for error type message
@param [String] message
the message to display
@api public
# File lib/tty/box.rb, line 152 def error(message, **opts) new_opts = { title: { top_left: " ⨯ ERROR " }, border: { type: :thick }, padding: 1, style: { fg: :bright_white, bg: :red, border: { fg: :bright_white, bg: :red } } }.merge(opts) frame(**new_opts) { message } end
Convert style keywords into styling
@return [Array[Proc, Proc]]
@api private
# File lib/tty/box.rb, line 353 def extract_style(style) fg = style[:fg] ? color.send(style[:fg]).detach : ->(c) { c } bg = style[:bg] ? color.send(:"on_#{style[:bg]}").detach : ->(c) { c } [fg, bg] end
Format content by wrapping, aligning and padding out
@param [Array<String>] lines
the lines to format
@param [Integer] width
the maximum width
@param [Integer, Array<Integer>] padding
the amount of padding
@param [Symbol] align
the type of alignment
@param [String] separator
the newline separator
@return [Array]
@api private
# File lib/tty/box.rb, line 330 def format(lines, width, padding, align, separator) return [] if lines.empty? pad = Strings::Padder.parse(padding) total_width = width - 2 - (pad.left + pad.right) formatted = lines.each_with_object([]) do |line, acc| wrapped = Strings::Wrap.wrap(line, total_width, separator: separator) acc << Strings::Align.align(wrapped, total_width, direction: align, separator: separator) end.join(separator) Strings::Pad.pad(formatted, padding, separator: separator) .split(separator) end
Create a frame
@param [Integer] top
the offset from the terminal top
@param [Integer] left
the offset from the terminal left
@param [Integer] width
the width of the box
@param [Integer] height
the height of the box
@param [Symbol] align
the content alignment
@param [Integer,Array] padding
the padding around content
@param [Hash] title
the title for top or bottom border
@param [Hash, Symbol] border
the border type
@param [Hash] style
the styling for the front and background
@api public
# File lib/tty/box.rb, line 191 def frame(*content, top: nil, left: nil, width: nil, height: nil, align: :left, padding: 0, title: {}, border: :light, style: {}, enable_color: nil) @color = nil color(enabled: enable_color) output = [] sep = NEWLINE position = top && left border = Border.parse(border) top_size = border.top? ? 1 : 0 bottom_size = border.bottom? ? 1 : 0 left_size = border.left? ? 1 : 0 right_size = border.right ? 1 : 0 str = block_given? ? yield : content_to_str(content) sep = str[LINE_BREAK] || NEWLINE # infer line break content_lines = str.split(sep) # infer dimensions dimensions = infer_dimensions(content_lines, padding) width ||= left_size + dimensions[0] + right_size width = [width, top_space_taken(title, border), bottom_space_taken(title, border)].max height ||= top_size + dimensions[1] + bottom_size # apply formatting to content formatted_lines = format(content_lines, width, padding, align, sep) # infer styling fg, bg = *extract_style(style) border_fg, border_bg = *extract_style(style[:border] || {}) if border.top? output << cursor.move_to(left, top) if position output << top_border(title, width, border, style) output << sep unless position end (height - top_size - bottom_size).times do |i| output << cursor.move_to(left, top + i + top_size) if position if border.left? output << border_bg.(border_fg.(pipe_char(border.type))) end filler_size = width - left_size - right_size if formatted_line = formatted_lines[i] output << bg.(fg.(formatted_line)) line_content_size = Strings::ANSI.sanitize(formatted_line) .scan(/[[:print:]]/).join.size filler_size = [filler_size - line_content_size, 0].max end if style[:fg] || style[:bg] || !position # something to color output << bg.(fg.(SPACE * filler_size)) end if border.right? if position output << cursor.move_to(left + width - right_size, top + i + top_size) end output << border_bg.(border_fg.(pipe_char(border.type))) end output << sep unless position end if border.bottom? output << cursor.move_to(left, top + height - bottom_size) if position output << bottom_border(title, width, border, style) output << sep unless position end output.join end
Infer box dimensions based on content lines and padding
@param [Array] lines @param [Array|Integer] padding
@return [Array]
@api private
# File lib/tty/box.rb, line 292 def infer_dimensions(lines, padding) pad = Strings::Padder.parse(padding) width = pad.left + content_width(lines) + pad.right height = pad.top + lines.size + pad.bottom [width, height] end
A frame for info type message
@param [String] message
the message to display
@api public
# File lib/tty/box.rb, line 83 def info(message, **opts) new_opts = { title: { top_left: " ℹ INFO " }, border: { type: :thick }, padding: 1, style: { fg: :black, bg: :bright_blue, border: { fg: :black, bg: :bright_blue } } }.merge(opts) frame(**new_opts) { message } end
# File lib/tty/box.rb, line 57 def line_char(border = :light) BOX_CHARS[border][8] end
# File lib/tty/box.rb, line 61 def pipe_char(border = :light) BOX_CHARS[border][9] end
A frame for for success type message
@param [String] message
the message to display
@api public
# File lib/tty/box.rb, line 129 def success(message, **opts) new_opts = { title: { top_left: " ✔ OK " }, border: { type: :thick }, padding: 1, style: { fg: :black, bg: :bright_green, border: { fg: :black, bg: :bright_green } } }.merge(opts) frame(**new_opts) { message } end
Top border
@return [String]
@api private
# File lib/tty/box.rb, line 417 def top_border(title, width, border, style) fg, bg = *extract_style(style[:border] || {}) top_space_left = width - top_space_taken(title, border) top_space_before = top_space_left / 2 top_space_after = top_space_left / 2 + top_space_left % 2 [ bg.(fg.(top_left_corner(border))), bg.(fg.(title[:top_left].to_s)), bg.(fg.(line_char(border.type) * top_space_before)), bg.(fg.(title[:top_center].to_s)), bg.(fg.(line_char(border.type) * top_space_after)), bg.(fg.(title[:top_right].to_s)), bg.(fg.(top_right_corner(border))) ].join end
Top left corner
@param [Border] border
@return [String]
@api private
# File lib/tty/box.rb, line 379 def top_left_corner(border) return "" unless border.top_left? && border.left? send(:"#{border.top_left}_char", border.type) end
Top right corner
@param [Border] border
@return [String]
@api private
# File lib/tty/box.rb, line 393 def top_right_corner(border) return "" unless border.top_right? && border.right? send(:"#{border.top_right}_char", border.type) end
Top space taken by titles and corners
@return [Integer]
@api private
# File lib/tty/box.rb, line 365 def top_space_taken(title, border) top_titles_size(title) + top_left_corner(border).size + top_right_corner(border).size end
Top titles size
@return [Integer]
@api private
# File lib/tty/box.rb, line 405 def top_titles_size(title) color.strip(title[:top_left].to_s).size + color.strip(title[:top_center].to_s).size + color.strip(title[:top_right].to_s).size end
A frame for warning type message
@param [String] message
the message to display
@api public
# File lib/tty/box.rb, line 106 def warn(message, **opts) new_opts = { title: { top_left: " ⚠ WARNING " }, border: { type: :thick }, padding: 1, style: { fg: :black, bg: :bright_yellow, border: { fg: :black, bg: :bright_yellow } } }.merge(opts) frame(**new_opts) { message } end