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.



356
357
358
359
360
361
362
363
364
365
366
367
368
# File 'lib/tui.rb', line 356

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.



354
355
356
# File 'lib/tui.rb', line 354

def body
  @body
end

Returns the value of attribute footer.



354
355
356
# File 'lib/tui.rb', line 354

def footer
  @footer
end

#headerObject (readonly)

Returns the value of attribute header.



354
355
356
# File 'lib/tui.rb', line 354

def header
  @header
end

#heightObject (readonly)

Returns the value of attribute height.



354
355
356
# File 'lib/tui.rb', line 354

def height
  @height
end

#input_fieldObject (readonly)

Returns the value of attribute input_field.



354
355
356
# File 'lib/tui.rb', line 354

def input_field
  @input_field
end

#widthObject (readonly)

Returns the value of attribute width.



354
355
356
# File 'lib/tui.rb', line 354

def width
  @width
end

Instance Method Details

#clearObject



382
383
384
385
# File 'lib/tui.rb', line 382

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

#flushObject



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
470
471
472
# File 'lib/tui.rb', line 387

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)


377
378
379
380
# File 'lib/tui.rb', line 377

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



370
371
372
373
374
375
# File 'lib/tui.rb', line 370

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