Skip to content

oleksandr-balyshyn/glyphora

Repository files navigation

glyphora

⚡ glyphora

Build swaggy terminal UIs in Scala 3 — a signals-driven widget toolkit with app chrome, animations, mouse support, and first-class GraalVM native-image binaries.

CI Docs Release Scala Mill Native Image Zero Reflection License

 glyphora   Widgets │ Log │ About
┌Menu────────────────┐Widgets │ Log │ About
│  dashboard         │
│  deployments       │  note: type here…
│  services          │
│  settings          │                      10%
│                    │██▇▅▄▂▁   ▁▃▄▆▇██▇▆▅▃▂▁  ▁▂▃▅▆▇██▇▆▄▃▁
│                    │
│                    │── chrome ──────────────────────────────────────
└────────────────────┘Tab cycles focus · ctrl+p opens the palette
 ctrl+t switch theme  │  ctrl+n show a toast  │  ctrl+o open modal  │

(a real frame from examples/showcase, captured headlessly — see Testing)

📖 oleksandr-balyshyn.github.io/glyphora — the full guide, cookbook, and per-module API reference.


✨ Why glyphora

  • 🧠 Signals, not spaghetti — state lives in Signal/Computed; whatever your view reads, re-renders when it changes. No dispatch loops, no dependency arrays.
  • 🧱 40+ widgets — from Block and Gauge to DataTable, TextArea (undo, cluster-safe editing), DirectoryTree, Markdown, braille Charts, and a half-block Image.
  • 🏛️ App chrome built inscaffold with top bar / sidebar / status line, themes, key-binding registry, screens, toasts, and a fuzzy Ctrl+P command palette.
  • 🎬 Motion — a post-render effects engine (fadeIn, coalesce, typewriter, …) with easing and combinators, plus skippable splash screens.
  • 🖱️ Mouse-aware — click to focus/activate, wheel to scroll, drag sliders and split panes.
  • 🌍 Unicode-correct — display width from the Unicode Character Database: CJK, emoji ZWJ families, flags, combining marks all measure right.
  • 📦 Native binaries — every example compiles with native-image --no-fallback and zero reflect-config, starting in milliseconds.
  • 🧪 Testable by design — a headless backend + Pilot driver run full event/render cycles in plain unit tests.

🚀 Quick start

// build.mill
def mvnDeps = Seq(mvn"io.worxbend::tui-dsl:0.9.0")
import io.worxbend.tui.dsl.*

object Hello extends TuiApp:
  val count = Signal(0)

  override def bindings = KeyBindings(
    binding("+", "increment")(count.update(_ + 1)),
    binding("q", "quit")(quit()),
  )

  def view(using ReactiveScope): Element =
    scaffold(statusBar = Some(statusBar(bindings))) {
      centered(30, 5) {
        panel("Hello")(
          text(s"count: ${count.get}").bold.color(Color.Cyan),
          text("press + to bump it").dim,
        ).rounded
      }
    }

  def main(args: Array[String]): Unit = run().foreach(_ => ())

One import gives you every factory, the styling/layout extensions, and the core vocabulary. More recipes in the 📖 cookbook; complete apps in examples/; the full guide and API reference are on the docs site.

🧩 Widget catalog

🧱 Layout & chrome Block (per-side borders, padding), Row/Column, Spacer, Rule, Scrollbar, ScrollView, TabbedContent, Collapsible, SplitPane, layers
📄 Content Paragraph (cluster-safe wrap), ListView, Table, Tabs, BigText, Log (follow-tail), Markdown, Link (OSC 8), Image (half-block)
⌨️ Input TextInput, TextArea (undo), Checkbox, Toggle, Select, RadioGroup, Slider, NumberInput, MaskedInput, SelectionList, Autocomplete, FilePicker, Button, Form (compile-time derived)
📊 Data viz Gauge, LineGauge, Sparkline, DualSparkline, BarChart, StackedBarChart, Chart (braille/half-block), PieChart, Heatmap, Canvas + shapes, Calendar, DataTable (sort/filter)
Motion & feedback Spinner, Skeleton, IndeterminateBar, Marquee, WaveText, Dialog, toasts, splash screens, the Effect engine

🏗️ Architecture

 ┌────────────┐   ┌────────────┐   ┌────────────┐
 │  tui-dsl   │──▶│tui-widgets │──▶│  tui-core  │   Element tree → Widgets → Buffer
 └─────┬──────┘   └────────────┘   └─────▲──────┘
       │          ┌────────────┐   ┌─────┴──────┐
       └─────────▶│tui-runtime │──▶│tui-terminal│   signals/loop → diff → ANSI
                  └────────────┘   └────────────┘
Module What it owns
core/ Buffer/Cell, Style, Layout solver, Widget traits, event ADT, CharWidth (UCD-generated width table)
terminal/ Backend trait, JLine 3 impl (diff flush, input decoding), HeadlessBackend
widgets/ every built-in widget — backend-agnostic, render-to-Buffer tested
runtime/ Signal/Computed, render thread, runner loop, Effect engine
dsl/ TuiApp, Element tree, focus/mouse routing, chrome presets, screens/toasts/palette
macros/ deriveForm/bindAction — compile-time only, keeps native-image reflect-config-free
test-support/ Pilot driver + buffer assertions

Per-module Scaladoc for every published module is on the docs site's API reference.

House rules (CI-enforced): no java.lang.reflect/Class.forName anywhere; no String.length/substring for layout math outside CharWidth; warnings are errors; scalafmt owns formatting.

🧪 Test your app headlessly

val backend = HeadlessBackend(Size(60, 16))
val pilot   = Pilot.start(backend) { app.runWith(backend) }

pilot.typeText("deploy").pressKey(KeyCode.Enter).waitForIdle()
assert(pilot.screenText.contains("deployed ✓"))

Full event/render cycles, no PTY, CI-friendly. All 1,500+ tests in this repo run this way.

⚡ Native binaries

./mill show examples.showcase.nativeImage   # → a self-contained executable

Every example builds with --no-fallback and no reflect-config JSON — the framework bridges user code with Scala 3 inline/Mirror instead of reflection.

🛠️ Developing glyphora

./mill __.compile                                   # build everything
./mill __.test                                      # ~1.5k tests, headless
./mill mill.scalalib.scalafmt.ScalafmtModule/reformatAll __.sources
./mill widgets.test.runMain io.worxbend.tui.widgets.RenderLoopBench      # fps check
./mill examples.showcase.test.runMain \
      io.worxbend.tui.examples.showcase.ScreenshotMain 70 17             # README shot
./mill examples.showcase.run                        # drive it for real

Adding a widget — the checklist that keeps quality flat:

  1. Implement against Widget/StatefulWidget[S] in widgets/ (state is caller-owned; all width math through CharWidth).
  2. Render-to-Buffer tests via BufferAssertions.rendered.
  3. DSL factory in dsl/Element.scala + export in dsl.scala (focusable elements get a builtinKeyHandler, mouse behavior via builtinMouseHandler).
  4. If interactive: an end-to-end Pilot test.

🔖 Versioning

Pre-1.0: minor versions (0.x) may break APIs, patches never do. tui-core is the stability anchor — additive changes only since 0.2. Releases are git tags (vX.Y.Z); pushing a tag publishes all modules to Maven Central via the Publish workflow (binary-compatibility gates via MiMa arrive with the first Central release as baseline).

📜 License

MIT — go build something glyphorious. ✨

About

No description, website, or topics provided.

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors