Class: Tui::Screen

Inherits:
Object
  • Object
show all
Includes:
Helpers
Defined in:
lib/tui.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Helpers

#accent, #bold, #dim, #emoji, #fill, #highlight

Constructor Details

#initialize(io: $stderr, width: nil, height: nil) ⇒ Screen

Returns a new instance of Screen.



353
354
355
356
357
358
359
360
361
362
363
364
365
# File 'lib/tui.rb', line 353

def initialize(io: $stderr, width: nil, height: nil)
  @io = io
  @fixed_width = width
  @fixed_height = height
  @width = @height = nil
  refresh_size
  @header = Section.new(self)
  @body   = Section.new(self)
  @footer = Section.new(self)
  @sections = [@header, @body, @footer]
  @input_field = nil
  @cursor_row = nil
end

Instance Attribute Details

#bodyObject (readonly)

Returns the value of attribute body.



351
352
353
# File 'lib/tui.rb', line 351

def body
  @body
end

Returns the value of attribute footer.



351
352
353
# File 'lib/tui.rb', line 351

def footer
  @footer
end

#headerObject (readonly)

Returns the value of attribute header.



351
352
353
# File 'lib/tui.rb', line 351

def header
  @header
end

#heightObject (readonly)

Returns the value of attribute height.



351
352
353
# File 'lib/tui.rb', line 351

def height
  @height
end

#input_fieldObject (readonly)

Returns the value of attribute input_field.



351
352
353
# File 'lib/tui.rb', line 351

def input_field
  @input_field
end

#widthObject (readonly)

Returns the value of attribute width.



351
352
353
# File 'lib/tui.rb', line 351

def width
  @width
end

Instance Method Details

#clearObject



379
380
381
382
# File 'lib/tui.rb', line 379

def clear
  @sections.each(&:clear)
  self
end

#flushObject



384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
# File 'lib/tui.rb', line 384

def flush
  refresh_size

  # Build entire frame in a single buffer to avoid flicker from partial writes
  buf = String.new(ANSI::HOME)

  cursor_row = nil
  cursor_col = nil
  current_row = 0

  # Render header at top
  @header.lines.each do |line|
    if @input_field && line.has_input?
      cursor_row = current_row + 1
      cursor_col = line.cursor_column(@input_field, @width)
    end
    line.render(buf, @width)
    current_row += 1
  end

  # Calculate available body space (total height minus header and footer)
  footer_lines = @footer.lines.length
  body_space = @height - current_row - footer_lines

  # Render body lines (limited to available space)
  body_rendered = 0
  @body.lines.each do |line|
    break if body_rendered >= body_space
    if @input_field && line.has_input?
      cursor_row = current_row + 1
      cursor_col = line.cursor_column(@input_field, @width)
    end
    line.render(buf, @width)
    current_row += 1
    body_rendered += 1
  end

  # Fill gap between body and footer with blank lines
  # Use \r to position at column 0, clear line, fill with spaces for reliability
  gap = body_space - body_rendered
  blank_line = "\r#{ANSI::CLEAR_EOL}#{' ' * (@width - 1)}\n"
  blank_line_no_newline = "\r#{ANSI::CLEAR_EOL}#{' ' * (@width - 1)}"
  gap.times do |i|
    # Last gap line without newline if no footer follows
    if i == gap - 1 && @footer.lines.empty?
      buf << blank_line_no_newline
    else
      buf << blank_line
    end
    current_row += 1
  end

  # Render footer at the bottom (sticky)
  @footer.lines.each_with_index do |line, idx|
    if @input_field && line.has_input?
      cursor_row = current_row + 1
      cursor_col = line.cursor_column(@input_field, @width)
    end
    # Last line: don't write \n to avoid scrolling
    if idx == footer_lines - 1
      line.render_no_newline(buf, @width)
    else
      line.render(buf, @width)
    end
    current_row += 1
  end

  # Position cursor at input field if present, otherwise hide cursor
  if cursor_row && cursor_col && @input_field
    buf << "\e[#{cursor_row};#{cursor_col}H"
    buf << ANSI::SHOW
  else
    buf << ANSI::HIDE
  end

  buf << ANSI::RESET

  # Single write for the entire frame - eliminates flicker
  begin
    @io.write(buf)
    @io.flush
  rescue IOError
  end
ensure
  clear
end

#input(placeholder = "", value: "", cursor: nil) ⇒ Object

Raises:

  • (ArgumentError)


374
375
376
377
# File 'lib/tui.rb', line 374

def input(placeholder = "", value: "", cursor: nil)
  raise ArgumentError, "screen already has an input" if @input_field
  @input_field = InputField.new(placeholder: placeholder, text: value, cursor: cursor)
end

#refresh_sizeObject



367
368
369
370
371
372
# File 'lib/tui.rb', line 367

def refresh_size
  rows, cols = Terminal.size(@io)
  @height = @fixed_height || rows
  @width = @fixed_width || cols
  self
end