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)

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(width, height) ⇒ Screen

Returns a new instance of Screen.



29
30
31
32
33
34
35
# File 'lib/rufio/screen.rb', line 29

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

Instance Attribute Details

#heightObject (readonly)

Returns the value of attribute height.



27
28
29
# File 'lib/rufio/screen.rb', line 27

def height
  @height
end

#widthObject (readonly)

Returns the value of attribute width.



27
28
29
# File 'lib/rufio/screen.rb', line 27

def width
  @width
end

Instance Method Details

#clearObject

Clear the entire screen



157
158
159
160
161
162
163
# File 'lib/rufio/screen.rb', line 157

def clear
  @cells.each do |row|
    row.fill { default_cell }
  end
  # Phase1: Clear dirty rows after full clear
  @dirty_rows.clear
end

#clear_dirtyObject

Phase1: Clear dirty row tracking



173
174
175
# File 'lib/rufio/screen.rb', line 173

def clear_dirty
  @dirty_rows.clear
end

#clear_overlayObject

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



203
204
205
206
207
208
209
210
211
212
# File 'lib/rufio/screen.rb', line 203

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



168
169
170
# File 'lib/rufio/screen.rb', line 168

def dirty_rows
  @dirty_rows.to_a
end

#disable_overlayObject

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



189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/rufio/screen.rb', line 189

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

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



182
183
184
185
186
# File 'lib/rufio/screen.rb', line 182

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:



104
105
106
107
# File 'lib/rufio/screen.rb', line 104

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

#overlay_enabled?Boolean

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

Returns:

  • (Boolean)


215
216
217
# File 'lib/rufio/screen.rb', line 215

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)



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
73
# File 'lib/rufio/screen.rb', line 45

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)
  @cells[y][x] = {
    char: char,
    fg: fg,
    bg: bg,
    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
      @cells[y][next_x] = {
        char: '',
        fg: nil,
        bg: nil,
        width: 0
      }
    end
  end
end

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

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



220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
# File 'lib/rufio/screen.rb', line 220

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

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



250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/rufio/screen.rb', line 250

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



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

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



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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/rufio/screen.rb', line 113

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