Skip to content

TUI Toolkit#38

Draft
noelwelsh wants to merge 27 commits intomainfrom
feature/ui
Draft

TUI Toolkit#38
noelwelsh wants to merge 27 commits intomainfrom
feature/ui

Conversation

@noelwelsh
Copy link
Copy Markdown
Contributor

Add a TUI toolkit building on the basic Terminus terminal support.

noelwelsh and others added 27 commits April 6, 2026 10:47
Terminal programs that wrap other terminal programs have a simpler
representation. Instead of using recursive types, which ultimately
cannot be expressed without creating a concrete type, we ask the
"syntax" to create a thunk that applies the effects, and pass that the
inner program. See `design.md` for a fuller description.

Update implementations accordingly.

Update to Scala Native 0.5.10. Reading key presses is currently broken
on MacOS, which I think traces back to the `read(timeout)` method, which
in turn probably goes to the termios calls not working correctly.
- Calling read(duration) doesn't clear the input queue when changing the
terminal settings. This is usually what we want, as this method is used
to read multi-byte character sequences. This fixes a problem we observed
on Scala Native

- Add a few tests for the CLong termios variant.

- Add a method to get special characters, used in testing.

- A bit of cleanup
Ghostty sends newline when enter is pressed.
- UIs are modelled as effects on a `RenderContext`

- Rendering is done via a cell buffer, which will allow double buffering
  and diffing in the future.

- Very basic fixed size layout.

- Fix `NativeTerminal` to support printing wide characters.
- Update scalafmt
- Add scalafmt setting to rewrite to braceless style
- Reformat
This prevents scalafmt reformatting these into Scala 3 style.

Also reinstate AnsiCodes.scala, which got deleted somewhere along the way.
Support italic, underline, etc.
- Support characters that have a width or 0 or 2. This enables support
  for emojis and east Asian languages.

- Add very basic styling support to allow use of extended styling

- Update example to show new features
- Add method `renderDiff` that only renders the difference between two buffers.

- Add tests for diffing
Column is the vertical counterpart to Row: it stacks children top to
bottom, accumulating height and taking the max width. Includes layout
tests for both Row and Column covering size accumulation, single-axis
placement, multi-size children, and nested Row/Column combinations.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Newline characters were treated as width-1 glyphs, writing a literal
\n into a buffer cell. At render time this emitted a terminal line-feed
mid-row, shifting the cursor and causing subsequent cells to land on
the wrong row — text after the first \n was invisible because the next
row's render immediately overwrote it.

putString now resets the column to x and advances the row on \n, matching
the expected behaviour for multi-line text content.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Documents the UI module architecture and styling design decisions for
persistence across machines and conversations.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduces ComponentStyle (border, borderStyle, background, padding)
separate from the cell-level Style. Box now fills the interior with the
background style and applies borderStyle to border characters. Text
splits into box and content style parameters, using Box.innerRect to
position content correctly regardless of padding.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This allows size to change dynamically, which in turn allows components
to react to user input.
Introduces the capability-passing reactive model for interactive UIs:

- Signal[A] / ReadSignal[A] for reactive state; set() schedules a re-render

- EventContext owns signals and key handlers for a screen's lifetime

- ComponentContext stub for future subtree re-render dependency tracking

- AppContext combines ComponentContext, EventContext, and RenderContext into
  a single capability; Row/Column thread it through via AppContext.child

- LeafContent[A] and AppContent[A] type aliases for component signatures

- Text content changed to by-name (=> String) so it re-evaluates each frame

- FullScreen.run implements the event loop: build tree once, render on signal
  change, dispatch keys to handlers, exit on stop() or EOF

- Fix NativeTerminal.flush() flushing stdin instead of stdout
- Add Ansi codes to show and hide cursor
- Wrap in effects and syntax
- Use in FullScreen app to hide the cursor
This prevents lag when waiting to disambiguate ESC from an escape code.
Correct the signal API in the notes to match the current implementation,
add AppContext documentation, and expand deferred work with focus model,
dynamic layout, and more components as immediate priorities. Change the
interactive demo quit key from Esc to q.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant