Class: TuiTui::Canvas

Inherits:
Object
  • Object
show all
Defined in:
lib/tui_tui/canvas.rb

Overview

Pure drawing surface. Coordinates are 1-origin to match terminal cursor addressing, and text layout is terminal-column aware.

Constant Summary collapse

CONTROL_GLYPH =

Control bytes are rendered visibly instead of being emitted to the terminal.

"?"
FRAME =
Style.new(fg: :bright_black)

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(rows, cols, chrome: BoxChrome::ASCII) ⇒ Canvas

Returns a new instance of Canvas.



26
27
28
29
30
31
32
# File 'lib/tui_tui/canvas.rb', line 26

def initialize(rows, cols, chrome: BoxChrome::ASCII)
  @rows = rows
  @cols = cols
  @grid = Array.new(rows) { Array.new(cols, Cell::BLANK) }
  @cursor = nil
  @chrome = chrome
end

Instance Attribute Details

#chromeObject (readonly)

Returns the value of attribute chrome.



24
25
26
# File 'lib/tui_tui/canvas.rb', line 24

def chrome
  @chrome
end

#colsObject (readonly)

Returns the value of attribute cols.



22
23
24
# File 'lib/tui_tui/canvas.rb', line 22

def cols
  @cols
end

#cursorObject (readonly)

Returns the value of attribute cursor.



23
24
25
# File 'lib/tui_tui/canvas.rb', line 23

def cursor
  @cursor
end

#rowsObject (readonly)

Returns the value of attribute rows.



22
23
24
# File 'lib/tui_tui/canvas.rb', line 22

def rows
  @rows
end

Class Method Details

.blank(size, chrome: BoxChrome::ASCII) ⇒ Object



18
19
20
# File 'lib/tui_tui/canvas.rb', line 18

def self.blank(size, chrome: BoxChrome::ASCII)
  new(size.rows, size.cols, chrome: chrome)
end

Instance Method Details

#cell(row, col) ⇒ Object



39
40
41
42
43
# File 'lib/tui_tui/canvas.rb', line 39

def cell(row, col)
  return nil unless row.between?(1, @rows) && col.between?(1, @cols)

  @grid[row - 1][col - 1]
end

#cursor_at(row, col) ⇒ Object



34
35
36
37
# File 'lib/tui_tui/canvas.rb', line 34

def cursor_at(row, col)
  @cursor = [row, col] if row.between?(1, @rows) && col.between?(1, @cols)
  self
end

#fill(rect, style, char = " ") ⇒ Object



84
85
86
87
88
89
90
91
92
# File 'lib/tui_tui/canvas.rb', line 84

def fill(rect, style, char = " ")
  cell = Cell.new(char: fill_char(char), style: style)
  rect.rows.times do |dr|
    row = rect.row + dr
    rect.cols.times { |dc| place(row, rect.col + dc, cell) }
  end

  self
end

#frame(rect, style: FRAME, chrome: @chrome) ⇒ Object



98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/tui_tui/canvas.rb', line 98

def frame(rect, style: FRAME, chrome: @chrome)
  fill(rect, nil)
  mid = chrome.h * (rect.cols - 2)
  text(rect.row, rect.col, chrome.tl + mid + chrome.tr, style)
  text(rect.row + rect.rows - 1, rect.col, chrome.bl + mid + chrome.br, style)
  (1...(rect.rows - 1)).each do |dy|
    text(rect.row + dy, rect.col, chrome.v, style)
    text(rect.row + dy, rect.col + rect.cols - 1, chrome.v, style)
  end

  self
end

#grid_row(r) ⇒ Object



111
# File 'lib/tui_tui/canvas.rb', line 111

def grid_row(r) = @grid[r - 1]

#hline(row, col, len, char = "-", style = nil) ⇒ Object



94
95
96
# File 'lib/tui_tui/canvas.rb', line 94

def hline(row, col, len, char = "-", style = nil)
  text(row, col, char * len, style)
end

#line(row, col, spans) ⇒ Object



74
75
76
77
78
79
80
81
82
# File 'lib/tui_tui/canvas.rb', line 74

def line(row, col, spans)
  column = col
  spans.each do |span|
    text(row, column, span.text, span.style)
    column += DisplayText.new(span.text).width
  end

  self
end

#text(row, col, string, style = nil) ⇒ Object



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/tui_tui/canvas.rb', line 45

def text(row, col, string, style = nil)
  return self unless row.between?(1, @rows)

  column = col
  TextSanitizer.sanitize(string.to_s).each_grapheme_cluster do |grapheme|
    if Width.control?(grapheme.ord)
      break if column > @cols

      place(row, column, Cell.new(char: CONTROL_GLYPH, style: style))
      column += 1
      next
    end

    width = Width.cluster(grapheme)
    # Leading combining marks have no base cell to attach to.
    next if width.zero?

    break if column > @cols
    # Do not split a wide glyph across the right edge.
    break if width == 2 && column == @cols

    place(row, column, Cell.new(char: grapheme, style: style))
    place(row, column + 1, Cell.new(char: nil, style: style)) if width == 2
    column += width
  end

  self
end

#to_textObject

Style-free projection of the grid for snapshot tests: continuation cells of wide glyphs contribute nothing, each row loses its trailing blanks, and rows join with "\n" (no trailing newline). Never contains ANSI.



116
117
118
# File 'lib/tui_tui/canvas.rb', line 116

def to_text
  @grid.map { |row| row.filter_map(&:char).join.rstrip }.join("\n")
end