Class: Potty::Surfaces::InlineSurface
- Inherits:
-
Potty::Surface
- Object
- Potty::Surface
- Potty::Surfaces::InlineSurface
- Defined in:
- lib/potty/surfaces/inline_surface.rb
Overview
Renders an N-line region in place under the cursor — like docker compose / npm / cargo progress — instead of taking over the screen. No init_screen, no alt-screen; the terminal stays in cooked mode, so Ctrl-C behaves normally and input is left alone (passive widgets only).
Model: a small cell grid (rows x cols). Widgets draw into it via the same setpos/addstr/attron calls they use on a curses surface; present repaints the region with ANSI (carriage-return + clear-line per row, then cursor back to the top). finalize freezes the last frame and drops the cursor to the line below so the next prompt lands cleanly.
Instance Method Summary collapse
- #addstr(str) ⇒ Object
- #attron(style) ⇒ Object
- #erase ⇒ Object
- #finalize ⇒ Object
-
#initialize(theme:, lines: nil, tick_interval: 40, out: $stdout, listen: false, input: $stdin) ⇒ InlineSurface
constructor
A new instance of InlineSurface.
- #present ⇒ Object
-
#read_key ⇒ Object
In listen mode: drain raw stdin (non-blocking, waiting up to one tick for the first byte), decode to Keys codes, and return them one per call (queueing the rest).
- #setpos(row, col) ⇒ Object
- #size ⇒ Object
- #start ⇒ Object
Constructor Details
#initialize(theme:, lines: nil, tick_interval: 40, out: $stdout, listen: false, input: $stdin) ⇒ InlineSurface
Returns a new instance of InlineSurface.
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
# File 'lib/potty/surfaces/inline_surface.rb', line 21 def initialize(theme:, lines: nil, tick_interval: 40, out: $stdout, listen: false, input: $stdin) super() @theme = theme @rows = [lines || 1, 1].max @tick_interval = tick_interval @out = out @listen = listen @input = input @cols = detect_cols @cursor = [0, 0] @cur_style = nil @primed = false @raw = false @decoder = (Input::Decoder.new if listen) @queue = [] erase end |
Instance Method Details
#addstr(str) ⇒ Object
68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/potty/surfaces/inline_surface.rb', line 68 def addstr(str) row, col = @cursor return unless row.between?(0, @rows - 1) str.to_s.each_char do |ch| break if col >= @cols @cells[row][col] = [ch, @cur_style] if col >= 0 col += 1 end @cursor = [row, col] end |
#attron(style) ⇒ Object
81 82 83 84 85 86 87 |
# File 'lib/potty/surfaces/inline_surface.rb', line 81 def attron(style) prev = @cur_style @cur_style = style.is_a?(Potty::Style) ? style : nil yield if block_given? ensure @cur_style = prev end |
#erase ⇒ Object
60 61 62 |
# File 'lib/potty/surfaces/inline_surface.rb', line 60 def erase @cells = Array.new(@rows) { Array.new(@cols) { [' ', nil] } } end |
#finalize ⇒ Object
49 50 51 52 53 54 55 56 57 58 |
# File 'lib/potty/surfaces/inline_surface.rb', line 49 def finalize present # freeze the final frame restore_cooked # Explicit CR+LF: present leaves the cursor at the end of the last # rendered row, and in raw mode \n alone is a bare line-feed (no # column reset), which would indent whatever the host prints next. @out.write("\r\n") # drop below the region, at column 0 @out.write("\e[?25h") # restore cursor @out.flush end |
#present ⇒ Object
89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
# File 'lib/potty/surfaces/inline_surface.rb', line 89 def present if @primed @out.write("\e[#{@rows - 1}A") if @rows > 1 # back to the top row else @primed = true end @rows.times do |i| @out.write("\r\e[2K") # carriage return + clear line @out.write(render_row(@cells[i])) @out.write("\n") unless i == @rows - 1 end @out.flush end |
#read_key ⇒ Object
In listen mode: drain raw stdin (non-blocking, waiting up to one tick for the first byte), decode to Keys codes, and return them one per call (queueing the rest). Without listening (or off a real TTY): just pace the loop and return nil, leaving input alone. Ctrl-C arrives as a byte the decoder passes through as Keys::CTRL_C; the event loop raises.
109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/potty/surfaces/inline_surface.rb', line 109 def read_key return @queue.shift unless @queue.empty? if @raw fill_queue @queue.shift else sleep(@tick_interval / 1000.0) if @tick_interval nil end end |
#setpos(row, col) ⇒ Object
64 65 66 |
# File 'lib/potty/surfaces/inline_surface.rb', line 64 def setpos(row, col) @cursor = [row, col] end |
#size ⇒ Object
39 40 41 |
# File 'lib/potty/surfaces/inline_surface.rb', line 39 def size [@rows, @cols] end |
#start ⇒ Object
43 44 45 46 47 |
# File 'lib/potty/surfaces/inline_surface.rb', line 43 def start @out.write("\e[?25l") # hide cursor @out.flush enter_raw if @listen end |