Class: RubyTUI::Buffer
- Inherits:
-
Object
- Object
- RubyTUI::Buffer
- Defined in:
- lib/rubytui/buffer.rb
Overview
2D grid of styled Cells used for rendering.
Buffer is the core rendering target. Widgets write into a Buffer via #set_string and #set_style. The Terminal then diffs two buffers to send only changed cells to the backend.
For one-shot CLI output, use #to_ansi or #print to convert the buffer contents to an ANSI-styled string without needing a Terminal.
Instance Attribute Summary collapse
-
#area ⇒ Rect
readonly
The rectangular bounds of this buffer.
-
#image_placements ⇒ Hash{Array => Hash{Symbol => Object}}
readonly
Image placements keyed by [x, y, content_hash] (or [x, y] when no content hash is given), registered via #set_image by Widgets::Image#render.
Deprecated Unicode utilities — use the Unicode module instead collapse
-
.char_width(ch) ⇒ Integer
deprecated
Deprecated.
Use Unicode.char_width instead.
-
.cluster_width(cluster) ⇒ Integer
deprecated
Deprecated.
Use Unicode.cluster_width instead.
-
.combining_mark?(cp) ⇒ Boolean
deprecated
Deprecated.
Use Unicode.combining_mark? instead.
-
.string_width(string) ⇒ Integer
deprecated
Deprecated.
Use Unicode.string_width instead.
-
.truncate_to_width(string, max_width) ⇒ String
deprecated
Deprecated.
Use Unicode.truncate_to_width instead.
-
.wide_char?(cp) ⇒ Boolean
deprecated
Deprecated.
Use Unicode.wide_char? instead.
Instance Method Summary collapse
-
#[](x, y) ⇒ Cell?
Get the cell at position (x, y).
-
#[]=(x, y, cell) ⇒ void
Set the cell at position (x, y).
-
#diff(other) ⇒ Array<Array(Integer, Integer, Cell)>
Diff this buffer against another, returning changed cells.
-
#each_cell_with_position {|x, y, cell| ... } ⇒ void
Iterate over all cells with their absolute positions.
-
#initialize(area_or_width, height = nil) ⇒ Buffer
constructor
Create a new Buffer.
-
#merge(other) ⇒ void
Merge another buffer on top of this one (for overlays).
-
#print(output = $stdout) ⇒ void
Print the buffer's ANSI output to a stream.
-
#reset! ⇒ void
Reset all cells to empty (space character, default style).
-
#reset_cells! ⇒ void
Reset cells only, keeping image placements — used by Terminal for full repaints, where placements must still diff against the previous frame to detect deletions.
-
#set_image(x:, y:, cols:, rows:, data:, pixel_width:, pixel_height:, format:, content_hash: nil, src_x: nil, src_y: nil, src_w: nil, src_h: nil) ⇒ void
Register an image placement for Kitty graphics protocol rendering.
-
#set_string(x, y, string, style = Style::DEFAULT) ⇒ Integer
Write a styled string starting at (x, y).
-
#set_style(area, style) ⇒ void
Apply a style to all cells within the given area.
-
#to_ansi ⇒ String
Convert buffer contents to an ANSI-styled string for one-shot printing.
Constructor Details
#initialize(width, height) ⇒ Buffer #initialize(area) ⇒ Buffer
Create a new Buffer.
34 35 36 37 38 39 40 41 42 |
# File 'lib/rubytui/buffer.rb', line 34 def initialize(area_or_width, height = nil) @area = if height Rect.new(0, 0, area_or_width, height) else area_or_width end @cells = Array.new(@area.area) { Cell.new } @image_placements = {} end |
Instance Attribute Details
#area ⇒ Rect (readonly)
Returns the rectangular bounds of this buffer.
19 20 21 |
# File 'lib/rubytui/buffer.rb', line 19 def area @area end |
#image_placements ⇒ Hash{Array => Hash{Symbol => Object}} (readonly)
Returns image placements keyed by [x, y, content_hash] (or [x, y] when no content hash is given), registered via #set_image by Widgets::Image#render.
23 24 25 |
# File 'lib/rubytui/buffer.rb', line 23 def image_placements @image_placements end |
Class Method Details
.char_width(ch) ⇒ Integer
Use Unicode.char_width instead.
Returns 0 for combining marks, 2 for wide characters, 1 otherwise.
83 84 85 86 |
# File 'lib/rubytui/buffer.rb', line 83 def self.char_width(ch) = Unicode.char_width(ch) # @deprecated Use {Unicode.combining_mark?} instead. # @param cp [Integer] Unicode codepoint # @return [Boolean] true if codepoint is a combining mark (zero-width) |
.cluster_width(cluster) ⇒ Integer
Use Unicode.cluster_width instead.
Returns 0, 1, or 2.
74 75 76 77 78 |
# File 'lib/rubytui/buffer.rb', line 74 def self.cluster_width(cluster) = Unicode.cluster_width(cluster) # @deprecated Use {Unicode.truncate_to_width} instead. # @param string [String] the string to truncate # @param max_width [Integer] maximum display width in columns # @return [String] truncated string |
.combining_mark?(cp) ⇒ Boolean
Use Unicode.combining_mark? instead.
Returns true if codepoint is a combining mark (zero-width).
87 88 89 90 |
# File 'lib/rubytui/buffer.rb', line 87 def self.combining_mark?(cp) = Unicode.combining_mark?(cp) # @deprecated Use {Unicode.wide_char?} instead. # @param cp [Integer] Unicode codepoint # @return [Boolean] true if codepoint is a wide character (2-column) |
.string_width(string) ⇒ Integer
Use Unicode.string_width instead.
Returns display width in terminal columns.
70 71 72 73 |
# File 'lib/rubytui/buffer.rb', line 70 def self.string_width(string) = Unicode.string_width(string) # @deprecated Use {Unicode.cluster_width} instead. # @param cluster [String] a grapheme cluster (one or more codepoints) # @return [Integer] 0, 1, or 2 |
.truncate_to_width(string, max_width) ⇒ String
Use Unicode.truncate_to_width instead.
Returns truncated string.
79 80 81 82 |
# File 'lib/rubytui/buffer.rb', line 79 def self.truncate_to_width(string, max_width) = Unicode.truncate_to_width(string, max_width) # @deprecated Use {Unicode.char_width} instead. # @param ch [String] a single character # @return [Integer] 0 for combining marks, 2 for wide characters, 1 otherwise |
.wide_char?(cp) ⇒ Boolean
Use Unicode.wide_char? instead.
Returns true if codepoint is a wide character (2-column).
91 |
# File 'lib/rubytui/buffer.rb', line 91 def self.wide_char?(cp) = Unicode.wide_char?(cp) |
Instance Method Details
#[](x, y) ⇒ Cell?
Get the cell at position (x, y).
49 50 51 52 |
# File 'lib/rubytui/buffer.rb', line 49 def [](x, y) idx = index(x, y) idx ? @cells[idx] : nil end |
#[]=(x, y, cell) ⇒ void
This method returns an undefined value.
Set the cell at position (x, y).
60 61 62 63 |
# File 'lib/rubytui/buffer.rb', line 60 def []=(x, y, cell) idx = index(x, y) @cells[idx] = cell if idx end |
#diff(other) ⇒ Array<Array(Integer, Integer, Cell)>
Diff this buffer against another, returning changed cells.
Used by Terminal#draw for minimal screen updates.
159 160 161 162 163 164 165 166 167 168 169 |
# File 'lib/rubytui/buffer.rb', line 159 def diff(other) changes = [] @cells.each_with_index do |cell, i| next if cell == other.cells[i] x = @area.x + (i % @area.width) y = @area.y + (i / @area.width) changes << [x, y, cell] end changes end |
#each_cell_with_position {|x, y, cell| ... } ⇒ void
This method returns an undefined value.
Iterate over all cells with their absolute positions.
251 252 253 254 255 256 257 |
# File 'lib/rubytui/buffer.rb', line 251 def each_cell_with_position @cells.each_with_index do |cell, i| x = @area.x + (i % @area.width) y = @area.y + (i / @area.width) yield x, y, cell end end |
#merge(other) ⇒ void
This method returns an undefined value.
Merge another buffer on top of this one (for overlays).
Non-empty cells from the other buffer overwrite cells in this buffer.
235 236 237 238 239 240 241 242 |
# File 'lib/rubytui/buffer.rb', line 235 def merge(other) other.each_cell_with_position do |x, y, cell| next if cell.empty? idx = index(x, y) @cells[idx] = cell if idx end end |
#print(output = $stdout) ⇒ void
This method returns an undefined value.
Print the buffer's ANSI output to a stream.
Convenience wrapper around #to_ansi.
336 337 338 |
# File 'lib/rubytui/buffer.rb', line 336 def print(output = $stdout) output.write(to_ansi) end |
#reset! ⇒ void
This method returns an undefined value.
Reset all cells to empty (space character, default style).
174 175 176 177 |
# File 'lib/rubytui/buffer.rb', line 174 def reset! reset_cells! @image_placements.clear end |
#reset_cells! ⇒ void
This method returns an undefined value.
Reset cells only, keeping image placements — used by Terminal for full repaints, where placements must still diff against the previous frame to detect deletions.
184 185 186 |
# File 'lib/rubytui/buffer.rb', line 184 def reset_cells! @cells.each(&:reset!) end |
#set_image(x:, y:, cols:, rows:, data:, pixel_width:, pixel_height:, format:, content_hash: nil, src_x: nil, src_y: nil, src_w: nil, src_h: nil) ⇒ void
This method returns an undefined value.
Register an image placement for Kitty graphics protocol rendering.
Called by Widgets::Image#render. The placement is stored and later emitted by the backend during Terminal#draw.
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 |
# File 'lib/rubytui/buffer.rb', line 207 def set_image(x:, y:, cols:, rows:, data:, pixel_width:, pixel_height:, format:, content_hash: nil, src_x: nil, src_y: nil, src_w: nil, src_h: nil) # Key by position + content hash to detect when image changes at same position key = content_hash ? [x, y, content_hash] : [x, y] placement = { x: x, y: y, cols: cols, rows: rows, data: data, pixel_width: pixel_width, pixel_height: pixel_height, format: format, content_hash: content_hash } # Add source rectangle if specified if src_x && src_y && src_w && src_h placement[:src_x] = src_x placement[:src_y] = src_y placement[:src_w] = src_w placement[:src_h] = src_h end @image_placements[key] = placement end |
#set_string(x, y, string, style = Style::DEFAULT) ⇒ Integer
Write a styled string starting at (x, y).
Iterates by grapheme cluster so that combining sequences (Devanagari matras/virama, emoji ZWJ, Latin diacritics) are stored as a single cell symbol. Wide clusters (2-column) mark the next cell as a spacer. Clips at buffer boundaries.
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/rubytui/buffer.rb', line 107 def set_string(x, y, string, style = Style::DEFAULT) return 0 unless y >= @area.y && y < @area.bottom count = 0 col = x string.grapheme_clusters.each do |gc| break if col >= @area.right w = Unicode.cluster_width(gc) if col >= @area.x idx = index(col, y) if idx @cells[idx].symbol = gc @cells[idx].style = style count += w # For wide clusters, mark the next cell as a spacer if w == 2 && col + 1 < @area.right next_idx = index(col + 1, y) if next_idx @cells[next_idx].symbol = "" @cells[next_idx].style = style end end end end col += w end count end |
#set_style(area, style) ⇒ void
This method returns an undefined value.
Apply a style to all cells within the given area.
Uses Style#patch so the applied style merges with existing cell styles.
147 148 149 150 151 |
# File 'lib/rubytui/buffer.rb', line 147 def set_style(area, style) each_cell_in(area) do |idx| @cells[idx].style = @cells[idx].style.patch(style) end end |
#to_ansi ⇒ String
Convert buffer contents to an ANSI-styled string for one-shot printing.
Each row becomes a line separated by \n. Styles are emitted as
ANSI SGR escape codes with reset at end of each styled row.
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 |
# File 'lib/rubytui/buffer.rb', line 270 def to_ansi csi = "\e[" result = String.new(capacity: @area.area * 2) prev_style = nil @area.height.times do |row| # Build the row content and track the last non-empty column row_buf = String.new(capacity: @area.width * 2) last_styled_col = -1 col = 0 display_col = 0 # track actual terminal column for cursor correction while col < @area.width cell = @cells[row * @area.width + col] # Skip spacer cells from wide characters if cell.symbol.empty? col += 1 next end # Apply style changes style = cell.style unless style == prev_style codes = style_to_sgr_codes(style, prev_style) row_buf << "#{csi}#{codes.join(";")}m" unless codes.empty? prev_style = style end row_buf << cell.symbol # Track last non-default cell for trimming unless cell.empty? last_styled_col = col end w = Unicode.string_width(cell.symbol) display_col += w col += [w, 1].max # After wide characters, emit cursor column to correct any # terminal rendering drift (terminals may disagree about emoji width) if w >= 2 && col < @area.width row_buf << "#{csi}#{display_col + 1}G" end end # Reset at end of row if we had any styling if prev_style && prev_style != Style::DEFAULT row_buf << "#{csi}0m" prev_style = nil end result << row_buf result << "\n" unless row == @area.height - 1 end result end |