Class: Rufio::Screen

Inherits:
Object
  • Object
show all
Defined in:
lib/rufio/screen.rb

Overview

Screen class - Back buffer for double buffering

Manages a virtual screen buffer where each cell contains:

  • Character
  • Foreground color (ANSI code)
  • Background color (ANSI code)
  • Display width (for multibyte characters)

Supports:

  • ASCII characters (width = 1)
  • Full-width characters (width = 2, e.g., Japanese, Chinese)
  • Emoji (width = 2+)

Phase1 Optimizations:

  • Width pre-calculation (computed once in put method)
  • Dirty row tracking (only render changed rows)
  • Optimized format_cell (String.new with capacity)
  • Optimized row generation (width accumulation, no ANSI strip)
  • Minimal ANSI stripping (only once in put_string)

Defined Under Namespace

Classes: Cell

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(width, height) ⇒ Screen

Returns a new instance of Screen.



33
34
35
36
37
38
39
# File 'lib/rufio/screen.rb', line 33

def initialize(width, height)
  @width = width
  @height = height
  @cells = Array.new(height) { Array.new(width) { Cell.new(' ', nil, nil, 1) } }
  @overlay_cells = nil  # オーバーレイレイヤー(ダイアログ用)
  @dirty_rows = Set.new  # Phase1: Dirty row tracking
end

Instance Attribute Details

#heightObject (readonly)

Returns the value of attribute height.



31
32
33
# File 'lib/rufio/screen.rb', line 31

def height
  @height
end

#widthObject (readonly)

Returns the value of attribute width.



31
32
33
# File 'lib/rufio/screen.rb', line 31

def width
  @width
end

Instance Method Details

#clearObject

Clear the entire screen



159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/rufio/screen.rb', line 159

def clear
  @cells.each do |row|
    row.each do |cell|
      cell.char  = ' '
      cell.fg    = nil
      cell.bg    = nil
      cell.width = 1
    end
  end
  # Phase1: Clear dirty rows after full clear
  @dirty_rows.clear
end

#clear_dirtyObject

Phase1: Clear dirty row tracking



180
181
182
# File 'lib/rufio/screen.rb', line 180

def clear_dirty
  @dirty_rows.clear
end

#clear_overlayObject

オーバーレイレイヤーをクリア



210
211
212
213
214
215
216
217
218
219
# File 'lib/rufio/screen.rb', line 210

def clear_overlay
  return unless @overlay_cells

  @height.times do |y|
    if @overlay_cells[y].any? { |cell| cell }
      @dirty_rows.add(y)
      @overlay_cells[y] = Array.new(@width) { nil }
    end
  end
end

#dirty_rowsArray<Integer>

Phase1: Get dirty rows (rows that have been modified since last clear)

Returns:

  • (Array<Integer>)

    Array of dirty row indices



175
176
177
# File 'lib/rufio/screen.rb', line 175

def dirty_rows
  @dirty_rows.to_a
end

#disable_overlayObject

オーバーレイレイヤーを無効化してクリア



196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/rufio/screen.rb', line 196

def disable_overlay
  return unless @overlay_cells

  # オーバーレイが描画されていた行をdirtyにマーク
  @height.times do |y|
    if @overlay_cells[y].any? { |cell| cell }
      @dirty_rows.add(y)
    end
  end

  @overlay_cells = nil
end

#enable_overlayObject

オーバーレイレイヤーを有効化



189
190
191
192
193
# File 'lib/rufio/screen.rb', line 189

def enable_overlay
  return if @overlay_cells

  @overlay_cells = Array.new(@height) { Array.new(@width) { nil } }
end

#get_cell(x, y) ⇒ Hash

Get the cell at (x, y)

Parameters:

  • x (Integer)

    X position

  • y (Integer)

    Y position

Returns:

  • (Hash)

    Cell data fg:, bg:, width:



106
107
108
109
# File 'lib/rufio/screen.rb', line 106

def get_cell(x, y)
  return default_cell if out_of_bounds?(x, y)
  @cells[y][x]
end

#overlay_enabled?Boolean

オーバーレイレイヤーが有効かどうか

Returns:

  • (Boolean)


222
223
224
# File 'lib/rufio/screen.rb', line 222

def overlay_enabled?
  !@overlay_cells.nil?
end

#put(x, y, char, fg: nil, bg: nil, width: nil) ⇒ Object

Put a single character at (x, y) with optional color

Parameters:

  • x (Integer)

    X position (0-indexed)

  • y (Integer)

    Y position (0-indexed)

  • char (String)

    Character to put

  • fg (String, nil) (defaults to: nil)

    Foreground ANSI color code

  • bg (String, nil) (defaults to: nil)

    Background ANSI color code

  • width (Integer, nil) (defaults to: nil)

    Display width (auto-detected if not provided)



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/rufio/screen.rb', line 49

def put(x, y, char, fg: nil, bg: nil, width: nil)
  return if out_of_bounds?(x, y)

  # Phase1: Width is calculated once here (not in rendering loop)
  char_width = width || TextUtils.display_width(char)
  cell = @cells[y][x]
  cell.char  = char
  cell.fg    = fg
  cell.bg    = bg
  cell.width = char_width

  # Phase1: Mark row as dirty
  @dirty_rows.add(y)

  # For full-width characters, mark the next cell as occupied
  if char_width >= 2 && x + 1 < @width
    (char_width - 1).times do |offset|
      next_x = x + 1 + offset
      break if next_x >= @width
      marker = @cells[y][next_x]
      marker.char  = ''
      marker.fg    = nil
      marker.bg    = nil
      marker.width = 0
    end
  end
end

#put_overlay(x, y, char, fg: nil, bg: nil, width: nil) ⇒ Object

オーバーレイレイヤーに描画



227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
# File 'lib/rufio/screen.rb', line 227

def put_overlay(x, y, char, fg: nil, bg: nil, width: nil)
  return unless @overlay_cells
  return if out_of_bounds?(x, y)

  char_width = width || TextUtils.display_width(char)
  @overlay_cells[y][x] = {
    char: char,
    fg: fg,
    bg: bg,
    width: char_width
  }

  @dirty_rows.add(y)

  # 全角文字の場合、次のセルをマーク
  if char_width >= 2 && x + 1 < @width
    (char_width - 1).times do |offset|
      next_x = x + 1 + offset
      break if next_x >= @width
      @overlay_cells[y][next_x] = {
        char: '',
        fg: nil,
        bg: nil,
        width: 0
      }
    end
  end
end

#put_overlay_string(x, y, str, fg: nil, bg: nil) ⇒ Object

オーバーレイレイヤーに文字列を描画



257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
# File 'lib/rufio/screen.rb', line 257

def put_overlay_string(x, y, str, fg: nil, bg: nil)
  return unless @overlay_cells
  return if out_of_bounds?(x, y)

  clean_str = str.include?("\e") ? ColorHelper.strip_ansi(str) : str

  current_x = x
  clean_str.each_char do |char|
    break if current_x >= @width

    char_width = TextUtils.display_width(char)
    put_overlay(current_x, y, char, fg: fg, bg: bg, width: char_width)
    current_x += char_width
  end
end

#put_string(x, y, str, fg: nil, bg: nil) ⇒ Object

Put a string starting at (x, y)

Parameters:

  • x (Integer)

    Starting X position

  • y (Integer)

    Y position

  • str (String)

    String to put (ANSI codes will be stripped)

  • fg (String, nil) (defaults to: nil)

    Foreground ANSI color code

  • bg (String, nil) (defaults to: nil)

    Background ANSI color code



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/rufio/screen.rb', line 84

def put_string(x, y, str, fg: nil, bg: nil)
  return if out_of_bounds?(x, y)

  # Phase1: ANSI stripping only once (minimal processing)
  # Only strip if the string contains ANSI codes
  clean_str = str.include?("\e") ? ColorHelper.strip_ansi(str) : str

  current_x = x
  clean_str.each_char do |char|
    break if current_x >= @width

    char_width = TextUtils.display_width(char)
    put(current_x, y, char, fg: fg, bg: bg, width: char_width)
    current_x += char_width
  end
end

#row(y) ⇒ String

Get a row as a formatted string

Parameters:

  • y (Integer)

    Row number

Returns:

  • (String)

    Formatted row with ANSI codes



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/rufio/screen.rb', line 115

def row(y)
  return " " * @width if y < 0 || y >= @height

  # Phase1: Pre-allocate string capacity for better performance
  # String.new("", capacity: N) inherits UTF-8 encoding from ""; String.new(capacity: N) creates ASCII-8BIT
  result = String.new("", capacity: @width * 20)
  current_width = 0  # Phase1: Accumulate width from cells (no recalculation)

  # オーバーレイがある場合はベース + オーバーレイを合成
  if @overlay_cells
    @width.times do |x|
      overlay_cell = @overlay_cells[y][x]
      base_cell = @cells[y][x]

      # オーバーレイがあればオーバーレイを使用、なければベースを使用
      cell = overlay_cell || base_cell

      # マーカーセル(全角文字の2セル目)はスキップ
      next if cell[:width] == 0

      result << format_cell(cell)
      current_width += cell[:width]
    end
  else
    # オーバーレイなしの場合は従来の処理
    @cells[y].each do |cell|
      # Skip marker cells for full-width characters
      next if cell[:width] == 0

      result << format_cell(cell)
      current_width += cell[:width]  # Phase1: Use pre-calculated width
    end
  end

  # Pad the row to full width
  # Phase1: No ANSI stripping or width recalculation needed
  if current_width < @width
    result << (" " * (@width - current_width))
  end

  result
end