OpenTUI 0.8.0
Most of 0.8.0 makes each frame cheaper. In 0.7.0, every frame cleared and repainted the whole screen and compared every row with the terminal, even when only a spinner had moved. Now an unchanged frame paints nothing, and a frame with a small change repaints only the rows around it. In OpenCode, a frame with a spinner or a streamed line does about a twentieth of the cell operations it did.
ScrollBox now scrolls and follows new content in native code. Animations share one clock: before, an app with several animations could draw twice as many frames as the animations needed.
The native input parser is still opt-in. It now keeps a mouse wheel report that a slow link splits, which the default parser can type into the focused input as text. To try it, set OTUI_NATIVE_INPUT_PARSER=1, or pass experimental_nativeInput: true to the renderer.
Wide emoji sequences, such as a family emoji in tmux, draw at their full width, and long wrapped or highlighted lines draw their last rows as fast as their first.
In 0.7.0, a ScrollBox was twelve scene nodes. It kept its scroll position in four copies, followed its sticky edge through a six-flag state machine, and learned its range from two separate resize hooks. Because the viewport size and the content size came from different hooks, a frame in which the box and its content grew together reset the position to 0. Vertical viewport padding left the last rows out of reach, and the scrollbar thumb was half a cell until the first terminal resize. In 0.8.0, a ScrollBox is one box with a native scroll control. After each layout solve, native code computes the range, follows the sticky edge, clamps the offset, and shows or hides the bars, all from one layout. Following new content no longer needs a round trip to JavaScript.
That change has a cost for code that reached into the parts. wrapper, viewport, content, and the two scrollbars are now views of the native state, not renderables. They still have geometry, colors, padding, and their events, but they have no children of their own. Children of a ScrollBox report the ScrollBox as their parent, and a press on a scrollbar targets the ScrollBox. Code that compared a node with the content part now compares it with the ScrollBox itself:
// 0.7.0
if (node === scroll.content) return
// 0.8.0
if (node === scroll) return
With the scroll state in native code, a scroll can also wait for layout. Before, an app that loaded older messages above the reader and then corrected the position had to wait two animation frames, and the reader saw one frame at the old position. Now scrollTo and scrollBy apply again after the next frame’s layout, and anchor holds a row in place while the content above it changes. Release the anchor in afterLayout, so that the frame that lays out the new rows still holds the place:
const release = scrollbox.anchor(firstVisibleRow)
await loadOlderMessages()
scrollbox.afterLayout(release)
In an OpenCode transcript, this took the shifted frames during a backfill from 1 to 0, and the in-between frames over 6 message jumps from 6 to 0.
An animation slower than the renderer, such as a spinner, used to have two options. It could set live: true, which runs the loop at targetFps and keeps renderer.idle() from resolving. Or it could run its own timer that calls requestRender(). With timers, every component gets its own frames, so three components at 20, 12.5, and 10 fps drew 40 frames per second. renderer.onAnimationTick puts every subscriber on one clock. Due times are multiples of 1000 / fps, so equal rates tick together. Each frame, whatever its cause, also runs every tick that is due within its tolerance. The same three components now draw 20 frames per second, and OpenCode during an agent turn draws 26 instead of 52 to 55:
const frames = ["⠋", "⠙", "⠹", "⠸"]
let i = 0
const stop = renderer.onAnimationTick(12.5, () => {
spinner.content = frames[i++ % frames.length]
})
A renderable can set animationFps instead. React components use useAnimationTick, and Solid components use onAnimationTick.
Before 0.8.0, OpenTUI wrapped every frame in synchronized output (CSI ? 2026 h … CSI ? 2026 l), even when the terminal never reported mode 2026. Over a slow link, those wrappers cost bytes on every frame. A tmux between the app and the link redraws each line that an update touched when the update ends, so a streaming scene sent 64 KB/s instead of 41 KB/s. On a 48 KB/s SSH link, that is a lag of 2.0 s instead of 75 ms. The new default, synchronizedOutput: "auto", removes the wrapper in a remote session once the startup replies arrive, but only when the terminal did not report mode 2026, or when the app writes to process.stdout inside tmux, screen, or Zellij on its own host. In the multiplexer case, OpenTUI gives up whole frames to save bytes, so tmux can show partial frames. If you want whole frames on a slow link, set synchronizedOutput: "always".
Breaking changes
- core: The
wrapper,viewport,content,verticalScrollBar, andhorizontalScrollBarof a ScrollBox are facade objects (ScrollBoxWrapper,ScrollBoxViewport,ScrollBoxContent,ScrollBoxBar), not renderables. Children report the ScrollBox as theirparent, so compare a node with the ScrollBox, not withscroll.content. See ScrollBox. (#1805) - core: ScrollBox scrollbars have no
slider,startArrow, orendArrow,overflowis always"hidden",getChildrenCount()counts the rows, andprimaryAxisfollows the content direction. ScrollBox ignores renderable options on its parts outside each part’s supported color, layout, padding, bar, and callback fields, and viewport padding that is not whole cells throws. See ScrollBox. (#1805) - core:
RenderContextrequires theclockandonAnimationTickfields, so a customRenderContextmust implement them. The renderer and the scrollback snapshot context supply both. See Custom renderables. (#1814, #1803) - core: The protected
Renderable._hasFocusedDescendantfield is removed, so a subclass that reads it getsundefined. ReadhasFocusedDescendantinstead, and useFFIRenderLib.contextDrawSceneBody()instead ofcontextDrawSceneText(). See Custom renderables. (#1778) - native:
OT_SCENE_RECORD_SCENE_TEXTis nowOT_SCENE_RECORD_SCENE_BODY, andot_buffer_draw_scene_textis nowot_buffer_draw_scene_body, with the same value and layout. Thereservedfield ofot_scene_statsis nowdamage_rows. See C. (#1778, #1783) - native: In the Zig module, a
CliRendererexists only as the renderer of a Session, andContext.drawSceneTextanddrawSceneTextOnare nowdrawSceneBodyanddrawSceneBodyOn.BufferedOutputand the standalone create, render, and setup calls are removed, so render through a Session. See Zig. (#1806, #1778)
Added
- core:
renderer.onAnimationTick(fps, callback, { tolerance })runs callbacks on one shared animation clock, so animations at different rates share frames.Renderable.animationFpsticks the protectedonAnimationTick()of a node while it is drawn, and aTimelinewith aframeRatebelowtargetFpsticks on the clock. See Animation and Timeline. (#1803) - react:
useAnimationTick(fps, callback, options?)subscribes a component to the shared animation clock and commits state updates from the callback in the tick’s frame. Anundefinedrate pauses it. See React bindings. (#1803) - solid:
onAnimationTick(fps, callback, options?)subscribes a component to the shared animation clock, andfpscan be anAccessor<number | undefined>that returnsundefinedto pause it. See Solid bindings. (#1803) - core:
ScrollBoxRenderable.anchor(node)keeps a row at its place in the viewport through layout changes, andafterLayout(callback)runs once after the next frame’s layout and before it paints.scrollChildIntoView()accepts a node, analignof"nearest","start", or"end", and aninset. See ScrollBox. (#1811) - core:
ScrollBoxRenderable.scrollXandscrollYread and update which axes scroll after construction. See ScrollBox. (#1805) - core: The test renderer option
nativeSceneVerify, on by default, compares each frame that retained paint skips or repaints in part with a full repaint, and it throws on a mismatch. See Testing. (#1783) - core: Rex gets OSC 8 hyperlinks when its XTVERSION reply or
TERMnamesrexorxterm-rex. See Terminal capabilities. (#1819) - native: A box can be a native scroll node.
ot_scene_set_scroll,ot_scene_scroll_to,ot_scene_get_scroll,ot_scene_scroll_request, andot_scene_set_scroll_arrowconfigure, scroll, and read it, andOT_SCENE_HOOK_SCROLLasks for anOT_SCENE_FRAME_SCROLLrequest when its state changes. See Frames and output. (#1798, #1811) - native:
ot_scene_stats.damage_rowscounts the rows that a frame diffs, and the newOT_SCENE_FULL_PATH_*bits give the reason for a full repaint.OT_SCENE_DEBUG_VERIFYcompares retained frames with a full repaint. See Frames and output. (#1783, #1808) - native:
OT_EDIT_POSITION_ENDandOT_EDIT_POSITION_PREV_CHAR, alsoNativeEditPositionQuery.EndandPrevCharin Core, read the end of the text and the position that Left moves to. They do not move the cursor. See C. (#1848) - native:
OT_INPUT_EXPECT_SENTINELholds split terminal replies while the host waits for a DA1 reply.OT_TERMINAL_REPLY_SENTINEL,OT_TERMINAL_SYNC_ALWAYS,OT_TERMINAL_SYNC_NEVER, andOT_TERMINAL_HOST_MULTIPLEXERset the reply sentinel and the synchronized-output policy of a Session. See Host I/O and time. (#1793, #1816, #1851)
Changed
- core: The new
synchronizedOutputoption defaults to"auto", which removes the synchronized-output wrapper in some remote sessions after the startup replies arrive. Set"always"for the old behavior, or"never"to never wrap a frame. See Renderer. (#1851) - core: The renderer applies capability replies whenever they arrive, and it emits
capabilitiesand repaints only when the capabilities change. After startup, handlers added withaddInputHandlerno longer see these replies, and the theme mode follows each OSC 11 reply. See Terminal capabilities. (#1807) - core: With the native input parser, the renderer ends each batch of terminal queries with DA1 and holds split replies until the DA1 reply comes, so the focused input no longer gets reply text over a slow link. The startup window closes after one round trip instead of 5 s, and with both parsers a pixel-size query without a reply ends after 5 s. See Terminal capabilities. (#1816, #1793)
- core: An unchanged scene frame paints nothing, and a frame with a small change clears and repaints only bands around its damaged rows, also when paint hooks draw it. An OpenCode frame at 160×48 does about 1,000 to 1,600 cell operations instead of about 28,000. See Rendering pipeline. (#1783, #1808, #1810, #1804, #1839, #1840)
- core: Scenes with hooks or a ScrollBox keep their preparation across frames, and feedback visits only the nodes that can send a request or that changed. The order of update calls between nodes whose replies race is not specified. See Rendering pipeline. (#1802, #1809)
- core:
drawFrameBuffer()copies a source withoutrespectAlphaat full opacity, also when the screen or the source holds a grapheme or a link, so transparent source cells show as holes. A transparent box border of space glyphs keeps the glyph below it in every case. See Buffer API. (#1810) - core: Siblings with the same
zIndexpaint in attachment order. A child whosezIndexchanges and then goes back to its old value gets its original paint position back. See Renderables. (#1812) - core: A ScrollBox scrolls and follows new content in native code, so an append to a 1,000-row sticky ScrollBox sends no request to the host. A held scrollbar arrow repeats on the renderer’s
clock, so a customClockdrives it. See ScrollBox. (#1805, #1814) - core:
scrollTo()andscrollBy()scroll at once and again after the next frame’s layout, so a scroll made in the same tick as a content change uses the new layout.scrollTo(scrollHeight)andscrollTo(Infinity)stay at the end after the content grows. See ScrollBox. (#1811) - core: Each row of a long wrapped line, or of a long highlighted line, finds its first cluster and its style span by binary search. The bottom 30 rows of a 131,072-column highlighted line take 480 steps instead of 1.9 million. See Code. (#1781, #1796)
- core: Release builds allocate native memory through libc, which reuses freed memory. A Context with 200 streamed text buffers (7.7 MB live) holds about 11 MB instead of 105 MB. See Resources and ownership. (#1795)
- solid:
@opentui/solidand@opentui/keymapacceptsolid-js^1.9.12as a peer dependency instead of1.9.12alone. See Solid bindings. (#1786)
Fixed
- core: A ScrollBox keeps its position when it and its content grow in the same frame, and vertical viewport padding no longer hides the last rows. The thumb is proportional to the viewport from the first frame, and scrollbar
onChangeoptions run. See ScrollBox. (#1805, #1798) - core: A host clipboard that read once no longer keeps an idle Wayland connection that collects unread offers. On Hyprland, those offers froze the desktop for up to 10 s or more when the process exited or the next paste read them. See Clipboard. (#1825)
- core: The host clipboard tries Wayland again after an unsupported connection, and on WSL it also tries
/mnt/wslg/runtime-dir/$WAYLAND_DISPLAY. On WSLg, a read that loses focus to another window maps a new helper surface, at most two times, instead of failing. See Clipboard. (#1845, #1849) - core: The Wayland clipboard works with libwayland-client 1.10 and later, not only 1.20 and later. On Ubuntu 20.04 and Debian 11, image paste no longer falls back to X11 without a message. See Clipboard. (#1846)
- core: After the X server or XWayland restarts, the X11 clipboard connects again, so operations no longer fail until the app restarts. On WSL, a zero-byte X11 result ends the read as
empty. See Clipboard. (#1844) - core: On Windows and macOS, a running clipboard operation settles at its deadline or on cancel. Before, a read with
timeoutMs: 1000that an unresponsive owner blocked took 17.3 s. See Clipboard. (#1847) - core: A change to
syntaxStyleorfgof aMarkdownRenderablerestyles list markers, and withinternalBlockMode: "top-level"it also restyles list items and table cells. Before, they kept the old theme’s colors. See Markdown. (#1841) - core: When a clip, the buffer edge, or a text view’s edge cuts a wide glyph, each visible cell draws as a space in the glyph’s style. Before, the cell showed what was behind the text, and at a view’s left edge a cut wide glyph or tab shifted the rest of the line left. See Text and terminal cells. (#1838)
- core: Under
wcwidth, a cluster can span up to 16 cells, so ZWJ families of 6 or 8 cells no longer leave blanks or stale cells. A wider cluster draws as blank cells of its width, and OSC 66 explicit width is capped at 7 cells and padded with spaces. See Text and terminal cells. (#1779) - core: Streamed or edited text measures, wraps, draws, and deletes like the same text loaded in one piece, also when an append or edit splits a grapheme. After such an edit, the cursor goes after the joined grapheme and extmarks shift by the change in width. See Editing buffers and views. (#1782, #1848)
- core: A box title is placed against the box’s own edges, so a box at a negative x or past the right edge of the screen shows its title in the correct place. See Box. (#1797)
- core:
TimeToFirstDrawRenderableandLineNumberRenderableat a fractional position no longer fail the frame, and both draw correctly withbuffered: true. See TimeToFirstDraw and Line number gutter. (#1826) - core: A subclass that calls
super.renderSelf()on anArrowRenderableat a fractional position no longer fails the frame,super.renderSelf()on aSliderRenderablematches native thumbs at negative fractional positions, and a bufferedBoxRenderable,SliderRenderable, orArrowRenderabledraws at its own origin instead of shifted. See Custom renderables. (#1799, #1778) - core: With the native input parser, a UTF-8 character that a pause splits waits for its other bytes, and so does a mouse report cut after
ESC [. Before,✓arrived as Alt keys and a wheel notch typed<64;87;26M. 8-bit Meta input now reads as UTF-8. See Keyboard input. (#1791, #1794) - solid: Conditional content that switches off and back on no longer leaves an empty text node in its place. See Solid bindings. (#1786)
Security
- solid:
@opentui/solidusesbabel-preset-solid1.9.16. Solid releases before 1.9.16 depend on Seroval versions with a critical advisory (GHSA-p6vx-979v-rg4c), so installsolid-js1.9.16 or later. See Solid bindings. (#1786)
API changes#
@opentui/core#
Added
class ScrollBoxBar extends ScrollBoxPartclass ScrollBoxContent extends ScrollBoxSizedPartclass ScrollBoxViewport extends ScrollBoxSizedPartclass ScrollBoxWrapper extends ScrollBoxFilledPartconstructor ScrollBoxBar(box: ScrollBoxRenderable, orientation: "vertical" | "horizontal")constructor ScrollBoxContent(box: ScrollBoxRenderable)constructor ScrollBoxViewport(box: ScrollBoxRenderable)enum-member NativeEditPositionQuery.End = 7enum-member NativeEditPositionQuery.PrevChar = 8enum-member NativeSceneFrame.Scrollenum-member NativeSceneHook.Scrollfunction scrollUnitSize(bar: { viewportSize: number; scrollSize: number; scrollStep?: number | null; }, unit: ScrollUnit): numberinterface AnimationTickOptionsinterface ScrollBoxContent extends ContentLayoutinterface ScrollBoxViewportmethod CliRenderer.onAnimationTick(fps: number, callback: AnimationTickCallback, options?: AnimationTickOptions): () => voidmethod FFIRenderLib.contextDrawSceneBody({ context, target, frame }: NativeDrawingTarget, node: SceneNodeHandle, x: number, y: number): voidmethod FFIRenderLib.sceneGetScroll(context: NativeContextHandle, node: SceneNodeHandle): NativeSceneScrollStatemethod FFIRenderLib.sceneScrollRequest(context: NativeContextHandle, node: SceneNodeHandle, kind: "into-view" | "anchor" | "layout", target: SceneNodeHandle | null, align?: "nearest" | "start" | "end", inset?: number): NativeSceneScrollStatemethod FFIRenderLib.sceneScrollTo(context: NativeContextHandle, node: SceneNodeHandle, x: number, y: number, relativeX: boolean, relativeY: boolean, reapply?: boolean): NativeSceneScrollStatemethod FFIRenderLib.sceneSetScroll(context: NativeContextHandle, node: SceneNodeHandle, options: NativeSceneScrollOptions): voidmethod FFIRenderLib.sceneSetScrollArrow(context: NativeContextHandle, node: SceneNodeHandle, axis: number, end: number, options: Omit<NativeSceneArrowOptions, "direction">): voidprotected method Renderable.onAnimationTick(deltaTime: number, ticks: number, time: number): voidmethod ScrollBoxRenderable.afterLayout(callback: () => void): voidmethod ScrollBoxRenderable.anchor(node: Renderable): () => voidproperty CliRendererConfig.synchronizedOutput?: SynchronizedOutputproperty NativeSessionTerminalOptions.hostMultiplexer?: booleanproperty NativeSessionTerminalOptions.replySentinel?: booleanproperty NativeSessionTerminalOptions.synchronizedOutput?: SynchronizedOutputproperty RenderContext.clock: Clockproperty RenderContext.onAnimationTick: (fps: number, callback: AnimationTickCallback, options?: AnimationTickOptions) => () => voidproperty Renderable.animationFps: number | undefinedproperty RenderableOptions.animationFps?: numberproperty ScrollBoxRenderable.overflow: "hidden"readonly property ScrollBoxRenderable.primaryAxis: "row" | "column"property ScrollBoxRenderable.scrollX: booleanproperty ScrollBoxRenderable.scrollY: booleanproperty StdinParserProtocolContext.replySentinelActive: booleanproperty Timeline.frameRate: number | undefinedproperty TimelineOptions.frameRate?: numbertype AnimationTickCallback = (deltaTime: number, ticks: number, time: number) => voidtype SynchronizedOutput = "auto" | "always" | "never"
Changed
protected method Renderable.propagateFocusChange(hasFocus: boolean): voidprotected method Renderable.propagateFocusChange(_hasFocus: boolean): voidmethod ScrollBoxRenderable.scrollChildIntoView(childId: string): voidmethod ScrollBoxRenderable.scrollChildIntoView(child: string | Renderable, options?: { align?: "nearest" | "start" | "end"; inset?: number; }): voidreadonly property NativeRenderableIntegration.lifecycle?: { readonly resize?: { readonly native: (width: number, height: number) => void; }; readonly update?: { readonly idle: (deltaTime: number) => void; readonly active: (renderable: Renderable) => boolean; }; }readonly property NativeRenderableIntegration.lifecycle?: { readonly resize?: { readonly native: (width: number, height: number) => void; }; readonly update?: { readonly idle: (deltaTime: number) => void; readonly active: (renderable: Renderable) => boolean; }; readonly scroll?: { readonly active: (renderable: Renderable) => boolean; readonly notify: (renderable: Renderable) => void; }; }readonly property ScrollBoxRenderable.content: ContentRenderablereadonly property ScrollBoxRenderable.content: ScrollBoxContentreadonly property ScrollBoxRenderable.horizontalScrollBar: ScrollBarRenderablereadonly property ScrollBoxRenderable.horizontalScrollBar: ScrollBoxBarreadonly property ScrollBoxRenderable.verticalScrollBar: ScrollBarRenderablereadonly property ScrollBoxRenderable.verticalScrollBar: ScrollBoxBarreadonly property ScrollBoxRenderable.viewport: BoxRenderablereadonly property ScrollBoxRenderable.viewport: ScrollBoxViewportreadonly property ScrollBoxRenderable.wrapper: BoxRenderablereadonly property ScrollBoxRenderable.wrapper: ScrollBoxWrappertype NativeSessionControl = { kind: "capability-response"; bytes: Uint8Array; } | { kind: "palette-query"; bytes: Uint8Array; } | { kind: "title"; title: string; } | { kind: "mouse"; mode: "disabled" | "drag" | "motion"; } | { kind: "kitty-keyboard-flags"; flags: number; } | { kind: "program-status"; status: ProgramStatus; } | { kind: "restore-modes" | "query-pixel-resolution" | "query-theme-colors" | "reset-background"; }type NativeSessionControl = { kind: "capability-response"; bytes: Uint8Array; } | { kind: "palette-query"; bytes: Uint8Array; } | { kind: "title"; title: string; } | { kind: "mouse"; mode: "disabled" | "drag" | "motion"; } | { kind: "kitty-keyboard-flags"; flags: number; } | { kind: "program-status"; status: ProgramStatus; } | { kind: "query-pixel-resolution"; sentinel?: boolean; } | { kind: "restore-modes" | "query-theme-colors" | "reset-background"; }
Removed
method FFIRenderLib.contextDrawSceneText({ context, target, frame }: NativeDrawingTarget, node: SceneNodeHandle, x: number, y: number): voidmethod ScrollBoxRenderable.add(obj: Renderable | VNode<any, any[]>, index?: number): numbermethod ScrollBoxRenderable.getChildren(): Renderable[]method ScrollBoxRenderable.getRenderable(id: string): Renderable | undefinedmethod ScrollBoxRenderable.insertBefore(obj: Renderable | VNode<any, any[]> | unknown, anchor?: Renderable | unknown): numbermethod ScrollBoxRenderable.remove(child: BaseRenderable): voidprotected property Renderable._hasFocusedDescendant: booleanstatic property ScrollBoxRenderable.idCounter: numberproperty ScrollBoxRenderable.padding: number | `${number}%` | null | undefinedproperty ScrollBoxRenderable.paddingBottom: number | `${number}%` | null | undefinedproperty ScrollBoxRenderable.paddingLeft: number | `${number}%` | null | undefinedproperty ScrollBoxRenderable.paddingRight: number | `${number}%` | null | undefinedproperty ScrollBoxRenderable.paddingTop: number | `${number}%` | null | undefinedproperty ScrollBoxRenderable.paddingX: number | `${number}%` | null | undefinedproperty ScrollBoxRenderable.paddingY: number | `${number}%` | null | undefined
@opentui/core/testing#
Added
property TestRendererOptions.nativeSceneVerify?: boolean
@opentui/react#
Added
const useAnimationTick: (fps: number | undefined, callback: AnimationTickCallback, options?: AnimationTickOptions) => void
@opentui/solid#
Added
const onAnimationTick: (fps: number | Accessor<number | undefined>, callback: AnimationTickCallback, options?: AnimationTickOptions) => void
opentui.h#
Added
const OT_EDIT_POSITION_END = 7const OT_EDIT_POSITION_PREV_CHAR = 8const OT_INPUT_EXPECT_SENTINEL = 8const OT_SCENE_DEBUG_VERIFY = 2const OT_SCENE_FRAME_SCROLL = 5const OT_SCENE_FULL_PATH_NON_LOCAL = 64const OT_SCENE_FULL_PATH_OPTIONS = 32const OT_SCENE_FULL_PATH_TAINTED = 8const OT_SCENE_FULL_PATH_UNRETAINED = 16const OT_SCENE_HOOK_SCROLL = 128const OT_SCENE_RECORD_SCENE_BODY = 10const OT_SCENE_SCROLL_ALIGN_END = 2const OT_SCENE_SCROLL_ALIGN_NEAREST = 0const OT_SCENE_SCROLL_ALIGN_START = 1const OT_SCENE_SCROLL_BAR_ALWAYS = 1const OT_SCENE_SCROLL_BAR_AUTO = 0const OT_SCENE_SCROLL_BAR_INSET = 0const OT_SCENE_SCROLL_BAR_NEVER = 2const OT_SCENE_SCROLL_BAR_OVERLAY = 1const OT_SCENE_SCROLL_CHANGED_CONTENT = 4const OT_SCENE_SCROLL_CHANGED_LAYOUT = 16const OT_SCENE_SCROLL_CHANGED_OFFSET_X = 1const OT_SCENE_SCROLL_CHANGED_OFFSET_Y = 2const OT_SCENE_SCROLL_CHANGED_VIEWPORT = 8const OT_SCENE_SCROLL_FILL_CONTENT = 4const OT_SCENE_SCROLL_FILL_VIEWPORT = 2const OT_SCENE_SCROLL_FILL_WRAPPER = 1const OT_SCENE_SCROLL_FOLLOW_BOTTOM = 2const OT_SCENE_SCROLL_FOLLOW_LEFT = 3const OT_SCENE_SCROLL_FOLLOW_NONE = 0const OT_SCENE_SCROLL_FOLLOW_RIGHT = 4const OT_SCENE_SCROLL_FOLLOW_TOP = 1const OT_SCENE_SCROLL_REAPPLY = 4const OT_SCENE_SCROLL_RELATIVE_X = 1const OT_SCENE_SCROLL_RELATIVE_Y = 2const OT_SCENE_SCROLL_REQUEST_ANCHOR = 1const OT_SCENE_SCROLL_REQUEST_INTO_VIEW = 0const OT_SCENE_SCROLL_REQUEST_LAYOUT = 2const OT_SCENE_SCROLL_X = 1const OT_SCENE_SCROLL_Y = 2const OT_STYLE_CONTENT = 2const OT_TERMINAL_HOST_MULTIPLEXER = 128const OT_TERMINAL_REPLY_SENTINEL = 16const OT_TERMINAL_SYNC_ALWAYS = 32const OT_TERMINAL_SYNC_NEVER = 64field ot_scene_stats.damage_rows: uint32_t, offset 220function ot_buffer_draw_scene_body(ot_context *, const ot_handle *target, const ot_scene_frame_request *, const ot_handle *node, int32_t x, int32_t y): ot_statusfunction ot_scene_get_scroll(ot_context *, const ot_handle *node, ot_scene_scroll_state *out_state): ot_statusfunction ot_scene_scroll_request(ot_context *, const ot_handle *node, uint32_t kind, const ot_handle *target, uint32_t align, uint32_t inset, ot_scene_scroll_state *out_state): ot_statusfunction ot_scene_scroll_to(ot_context *, const ot_handle *node, double x, double y, uint32_t flags, ot_scene_scroll_state *out_state): ot_statusfunction ot_scene_set_scroll(ot_context *, const ot_handle *node, const ot_scene_scroll_options *): ot_statusfunction ot_scene_set_scroll_arrow(ot_context *, const ot_handle *node, uint32_t axis, uint32_t end, const ot_scene_arrow_options *, const uint8_t *text, uint32_t byte_count): ot_statusstruct ot_scene_scroll_bar_optionsstruct ot_scene_scroll_bar_statestruct ot_scene_scroll_optionsstruct ot_scene_scroll_state
Removed
const OT_SCENE_RECORD_SCENE_TEXT = 10field ot_scene_stats.reserved: uint32_t, offset 220function ot_buffer_draw_scene_text(ot_context *, const ot_handle *target, const ot_scene_frame_request *, const ot_handle *node, int32_t x, int32_t y): ot_status