etui/terminal
Types
Where the hardware cursor should be after a frame is drawn.
pub type Cursor {
CursorUntouched
CursorHidden
CursorShown(geometry.Position)
}
Constructors
-
CursorUntouchedLeave the cursor wherever it was. The default, and what a full-screen app wants: it hides the cursor once at start-up and never thinks about it again.
-
CursorHiddenHide the cursor for this frame.
-
CursorShown(geometry.Position)Show the cursor at a 0-based cell position. Text inputs want this so the insertion point is where the terminal actually blinks.
One frame under construction: the area it covers, the cells drawn into it so far, and where the cursor should end up.
pub type Frame {
Frame(
area: geometry.Rect,
buffer: buffer.Buffer,
cursor: Cursor,
)
}
Constructors
-
Frame(area: geometry.Rect, buffer: buffer.Buffer, cursor: Cursor)
An open terminal: the backend, its state, and what was last drawn.
pub opaque type Terminal(backend_state)
How much of the terminal an app takes over.
| Viewport | What it uses | What happens to the scrollback |
|---|---|---|
Fullscreen | the whole terminal, on the alternate screen | untouched, and the shell reappears on exit |
Inline(n) | the bottom n rows of the normal screen | kept, and the app’s last frame stays above the prompt |
Fixed(rect) | one rect of the normal screen | everything outside the rect is left alone |
Inline is how a build tool or an installer draws a progress area without
taking the screen away from you: the rows above it keep scrolling, and what
it drew is still on screen after it exits.
pub type Viewport {
Fullscreen
Inline(height: Int)
Fixed(area: geometry.Rect)
}
Constructors
-
Fullscreen -
Inline(height: Int) -
Fixed(area: geometry.Rect)
Values
pub fn area(term: Terminal(backend_state)) -> geometry.Rect
The area a frame will be given, which is the viewport rather than always the whole screen.
pub fn close_viewport(
vp: Viewport,
area: geometry.Rect,
) -> List(backend.RenderOp)
Ops that hand the terminal back, once the app is done.
Public for the same reason as frame_ops: what a viewport does on the way
out is worth being able to check without a terminal to do it to, and for an
inline app it is the visible difference from a full-screen one.
pub fn draw(
term: Terminal(backend_state),
build: fn(Frame) -> Frame,
) -> Result(Terminal(backend_state), backend.Error)
Draw one frame. build is handed an empty frame the size of the screen and
returns it filled in.
pub fn draw_widget(
frame: Frame,
area: geometry.Rect,
w: fn(buffer.Buffer, geometry.Rect) -> buffer.Buffer,
) -> Frame
Draw a widget into part of the frame.
pub fn draw_with(
term: Terminal(backend_state),
build: fn(Frame) -> #(Frame, a),
) -> Result(#(Terminal(backend_state), a), backend.Error)
Draw one frame and carry a value back out of it.
A frame closure works out things the rest of your program wants: the rects the layout produced, so a click can be matched against them, or the state a list settled on once it knew how tall it was. Without a way out those die inside the closure and have to be computed a second time.
let assert Ok(#(term, panes)) =
terminal.draw_with(term, fn(frame) {
let panes = geometry.split_h(frame.area, [Fill, Fill])
#(draw_panes(frame, panes), panes)
})
// `panes` is now available for hit-testing the next mouse event
pub fn frame_ops(
prev: buffer.Buffer,
curr: buffer.Buffer,
first_frame: Bool,
cur: Cursor,
clear_first: Bool,
) -> List(backend.RenderOp)
The render ops that take the terminal from prev to curr.
A first frame, at start-up or after a resize, repaints everything: what the terminal is showing is unknown, so there is nothing to diff against. Every frame after that emits only the cells that changed.
A frame that emits anything is wrapped in synchronized output, so the terminal shows the old frame until the whole new one has arrived.
Public because it is worth being able to check what a frame will emit
without a terminal to emit it into, which is how the diffing and cursor
rules are tested. draw is what an app calls.
pub fn new(
b: backend.Backend(backend_state),
) -> Result(Terminal(backend_state), backend.Error)
Open a terminal that takes over the whole screen.
pub fn new_with_viewport(
b: backend.Backend(backend_state),
vp: Viewport,
) -> Result(Terminal(backend_state), backend.Error)
Open a terminal that uses only part of the screen.
// A five-row progress area under whatever the shell has already printed
let assert Ok(term) = terminal.new_with_viewport(default.new(), terminal.Inline(5))
pub fn open_viewport(vp: Viewport) -> List(backend.RenderOp)
Ops that make room for the viewport before the first frame.
An inline viewport prints its own height in newlines, which scrolls whatever was on screen up and leaves the bottom rows blank for the app. Without it the first frame would draw over the last lines of output.
Public alongside close_viewport; see there.
pub fn poll(
term: Terminal(backend_state),
timeout_ms: Int,
) -> Result(
#(backend.InputEvent, Terminal(backend_state)),
backend.Error,
)
Wait up to timeout_ms for an event.
A resize is reported like any other event, and also resets the terminal’s
idea of what is on screen, so the next draw repaints at the new size.
pub fn restore(term: Terminal(backend_state)) -> Nil
Leave the alternate screen, restore the cursor and hand the terminal back.
pub fn set_cursor(frame: Frame, pos: geometry.Position) -> Frame
Put the cursor at pos when this frame is drawn.
pub fn viewport(term: Terminal(backend_state)) -> Viewport
The viewport this terminal was opened with.
pub fn with_buffer(frame: Frame, buf: buffer.Buffer) -> Frame
Replace the frame’s buffer, for code that renders by returning a buffer rather than by applying widgets.