{"id":48904569,"url":"https://github.com/yehorsyrin/alivejTUI","last_synced_at":"2026-04-19T10:01:11.424Z","repository":{"id":347656738,"uuid":"1193962821","full_name":"yehorsyrin/alivejTUI","owner":"yehorsyrin","description":"Declarative TUI library for Java","archived":false,"fork":false,"pushed_at":"2026-04-02T20:41:20.000Z","size":5433,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-04-03T03:30:27.317Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/yehorsyrin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-27T19:11:54.000Z","updated_at":"2026-04-02T20:41:25.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/yehorsyrin/alivejTUI","commit_stats":null,"previous_names":["yehorsyrin/alivejtui"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/yehorsyrin/alivejTUI","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yehorsyrin%2FalivejTUI","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yehorsyrin%2FalivejTUI/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yehorsyrin%2FalivejTUI/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yehorsyrin%2FalivejTUI/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/yehorsyrin","download_url":"https://codeload.github.com/yehorsyrin/alivejTUI/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yehorsyrin%2FalivejTUI/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32002361,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-18T20:23:30.271Z","status":"online","status_checked_at":"2026-04-19T02:00:07.110Z","response_time":55,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-04-16T19:00:43.472Z","updated_at":"2026-04-19T10:01:11.409Z","avatar_url":"https://github.com/yehorsyrin.png","language":"Java","funding_links":[],"categories":["Projects"],"sub_categories":["CLI"],"readme":"```\n╔══════════════════════════════════════════════════════════════════════════╗\n║                                                                          ║\n║   █████╗ ██╗     ██╗██╗   ██╗███████╗      ██╗████████╗██╗   ██╗██╗      ║\n║  ██╔══██╗██║     ██║██║   ██║██╔════╝      ██║╚══██╔══╝██║   ██║██║      ║\n║  ███████║██║     ██║╚██╗ ██╔╝█████╗        ██║   ██║   ██║   ██║██║      ║\n║  ██╔══██║██║     ██║ ╚████╔╝ ██╔══╝   ██   ██║   ██║   ██║   ██║██║      ║\n║  ██║  ██║███████╗██║  ╚═══╝  ███████╗ ╚█████╔╝   ██║   ╚██████╔╝██║      ║\n║  ╚═╝  ╚═╝╚══════╝╚═╝         ╚══════╝  ╚════╝    ╚═╝    ╚═════╝ ╚═╝      ║\n║                                                                          ║\n║          Declarative TUI library for Java              v0.2.0            ║\n║          ─────────────────────────────────────────────────────────       ║\n║                    crafted with pride by  J A R V I S  (AI)              ║\n╚══════════════════════════════════════════════════════════════════════════╝\n```\n\n# AliveJTUI\n\n[![Maven Central](https://img.shields.io/maven-central/v/io.github.yehorsyrin/alivejTUI)](https://central.sonatype.com/artifact/io.github.yehorsyrin/alivejTUI)\n[![SonarCloud](https://github.com/yehorsyrin/alivejTUI/actions/workflows/sonar.yml/badge.svg)](https://github.com/yehorsyrin/alivejTUI/actions/workflows/sonar.yml)\n[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=yehorsyrin_alivejTUI\u0026metric=alert_status)](https://sonarcloud.io/summary/new_code?id=yehorsyrin_alivejTUI)\n[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=yehorsyrin_alivejTUI\u0026metric=coverage)](https://sonarcloud.io/summary/new_code?id=yehorsyrin_alivejTUI)\n[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=yehorsyrin_alivejTUI\u0026metric=bugs)](https://sonarcloud.io/summary/new_code?id=yehorsyrin_alivejTUI)\n[![Java](https://img.shields.io/badge/Java-17%2B-blue)](https://openjdk.org/)\n[![License](https://img.shields.io/badge/license-MIT-green)](LICENSE)\n\nA declarative TUI (Terminal User Interface) library for Java.\nBuild terminal UIs as component trees — like React, but for the terminal.\n\n**[Documentation](https://yehorsyrin.github.io/alivejTUI)**\n\n```\n AliveJTUI Demo v0.2.0  theme: [Dark]\n 1:Widgets  2:Table  3:VirtualList  4:Text  5:Layout  6:Login\n──────────────────────────────────────────────────────────────\n  [ Click Me! ]  Clicked: 3  Spin: |\n  Progress [+][-]\n  [████████████░░░░░░░░]  60%\n  ☑ Notifications enabled    Input: [hello_]\n  Theme radio [Up/Down]:  (x) Dark  ( ) Light\n  Color select [S]:  \u003c\u003c Cyan \u003e\u003e\n──────────────────────────────────────────────────────────────\n  1-6:Tab  T:Theme  D:Dialog  N:Notify  C:Collapse  X:Checkbox  S:Select  +/-:Progress  ESC:Quit\n```\n\n---\n\n## Features\n\n- **Declarative rendering** — describe UI as a `Node` tree; the library diffs and redraws only changed cells\n- **React-style components** — subclass `Component`, call `setState()`, let the framework re-render\n- **Rich node library** — text, buttons, inputs, checkboxes, radio groups, selects, tables, virtual lists, viewports, dialogs, spinners, progress bars, and more\n- **Focus management** — `Tab` / `Shift+Tab` cycle through focusable nodes; `Enter` fires the focused button\n- **Theme system** — swap `Theme.DARK` / `Theme.LIGHT` (or implement your own) at runtime\n- **CSS-like styling** — `StyleSheet` with `#id`, `.class`, and type selectors\n- **Overlay API** — push/pop dialogs and toast notifications on top of any UI\n- **Async state** — run background work and apply results safely on the event loop thread\n- **Timers** — one-shot and repeating callbacks with automatic re-render\n- **Virtual lists** — render 10,000+ items with only visible rows drawn\n- **Undo/Redo** — built-in `UndoManager` for reversible operations\n- **Diff-based renderer** — only changed terminal cells are redrawn; no full-screen flicker\n- **Pluggable backends** — `LanternaBackend` (default), `MockBackend` (testing), or bring your own\n\n---\n\n## Requirements\n\n- Java 17+\n- Maven 3.8+\n\n---\n\n## Quick Start\n\n### 1. Add the dependency\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003eio.github.yehorsyrin\u003c/groupId\u003e\n    \u003cartifactId\u003ealivejTUI\u003c/artifactId\u003e\n    \u003cversion\u003e0.2.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n### 2. Write a component\n\n```java\nimport io.github.yehorsyrin.tui.core.*;\nimport io.github.yehorsyrin.tui.event.*;\nimport io.github.yehorsyrin.tui.node.*;\nimport io.github.yehorsyrin.tui.style.Color;\n\npublic class CounterApp extends Component {\n\n    private int count = 0;\n\n    @Override\n    public void mount(Runnable onStateChange, EventBus eventBus) {\n        super.mount(onStateChange, eventBus);\n        onKey(KeyType.ARROW_UP,   () -\u003e setState(() -\u003e count++));\n        onKey(KeyType.ARROW_DOWN, () -\u003e setState(() -\u003e count--));\n    }\n\n    @Override\n    public Node render() {\n        return VBox.of(\n            Text.of(\"  Counter Demo\").bold().color(Color.CYAN),\n            Divider.horizontal(),\n            HBox.of(\n                Text.of(\"  Count: \").dim(),\n                Text.of(String.valueOf(count)).bold().color(Color.GREEN)\n            ),\n            Text.of(\"\"),\n            Text.of(\"  Up/Down: +/-   ESC: quit\").dim()\n        );\n    }\n\n    public static void main(String[] args) {\n        AliveJTUI.run(new CounterApp());\n    }\n}\n```\n\n### 3. Run the demo jar\n\n```bash\njava -jar alivejTUI-demo.jar\n```\n\nOn GUI desktops (macOS/Windows/X11/Wayland) a Swing window opens.\nOn headless servers and CI the app runs directly in the terminal via the native backend.\n\n---\n\n## Component Model\n\n### Subclassing Component\n\n```java\npublic class MyApp extends Component {\n\n    // State fields\n    private String text = \"\";\n    private boolean checked = false;\n\n    @Override\n    public void mount(Runnable onStateChange, EventBus eventBus) {\n        super.mount(onStateChange, eventBus);\n        // Register key handlers here (auto-unregistered on unmount)\n        onKey(KeyType.ENTER, () -\u003e setState(() -\u003e text += \"!\"));\n        eventBus.registerCharacter(c -\u003e {\n            if (c \u003e= 32) setState(() -\u003e text += c);\n        });\n    }\n\n    @Override\n    public Node render() {\n        // Return a new Node tree every call — the diff engine handles the rest\n        return VBox.of(\n            Text.of(\"Input: \" + text),\n            Checkbox.of(\"Option\", checked, () -\u003e setState(() -\u003e checked = !checked))\n        );\n    }\n}\n```\n\n### setState\n\n`setState(Runnable mutation)` — applies the mutation and triggers a re-render.\n\n```java\nsetState(() -\u003e {\n    this.count++;\n    this.label = \"clicked\";\n});\n```\n\n### Async state\n\n`setStateAsync(Supplier\u003cRunnable\u003e task)` — runs work on a background thread, then applies the mutation on the event loop thread.\n\n```java\nsetStateAsync(() -\u003e {\n    String result = fetchFromNetwork(); // background thread\n    return () -\u003e this.data = result;    // event loop thread\n});\n```\n\nOr use `AliveJTUI.runAsync(AsyncTask)` directly:\n\n```java\nAliveJTUI.runAsync(AsyncTask.of(\n    () -\u003e fetchData(),\n    result -\u003e setState(() -\u003e this.data = result),\n    err    -\u003e setState(() -\u003e this.error = err.getMessage())\n));\n```\n\n### Lifecycle\n\n| Method | Called when |\n|--------|-------------|\n| `mount(onStateChange, eventBus)` | Component enters the UI tree |\n| `render()` | State changes; should return a pure Node tree |\n| `unmount()` | Component leaves the UI tree; key handlers auto-unregistered |\n| `onError(Exception)` | `render()` throws; return a fallback node |\n| `shouldUpdate()` | Override to skip re-render (optimization; default: always) |\n\n---\n\n## Node Reference\n\nAll factory methods return a node with a fluent builder API.\n\n### Text \u0026 Display\n\n| Expression | Description |\n|------------|-------------|\n| `Text.of(\"hello\")` | Plain single-line text |\n| `Text.of(\"hello\").bold()` | Bold text |\n| `Text.of(\"hello\").italic()` | Italic text |\n| `Text.of(\"hello\").underline()` | Underlined text |\n| `Text.of(\"hello\").strikethrough()` | Strikethrough text |\n| `Text.of(\"hello\").dim()` | Dimmed/muted text |\n| `Text.of(\"hello\").color(Color.CYAN)` | Foreground color |\n| `Text.of(\"hello\").background(Color.BLUE)` | Background color |\n| `Text.ofMarkdown(\"**bold** and *italic*\")` | Inline markdown |\n| `Paragraph.of(\"long text...\")` | Word-wrapped plain text |\n| `Paragraph.ofMarkdown(\"**bold** paragraph\")` | Word-wrapped markdown |\n| `Divider.horizontal()` | Horizontal rule `───────` |\n| `Divider.vertical()` | Vertical rule `│` |\n\n**Supported markdown syntax:** `**bold**`, `*italic*`, `` `code` ``, `~~strikethrough~~`\n\n### Layout\n\n| Expression | Description |\n|------------|-------------|\n| `VBox.of(node1, node2, ...)` | Vertical stack |\n| `VBox.of(nodes).gap(1)` | Vertical stack with spacing |\n| `HBox.of(node1, node2, ...)` | Horizontal stack |\n| `HBox.of(nodes).gap(2)` | Horizontal stack with spacing |\n| `new BoxNode(child, true, borderStyle)` | Bordered container |\n\n### Interactive Widgets\n\n#### Button\n\n```java\nButtonNode btn = Button.of(\"[ OK ]\", () -\u003e System.out.println(\"clicked\"));\nregisterFocusable(btn);   // participate in Tab cycling\n// Tab to focus, Enter to click\n```\n\n#### Text Input\n\n```java\nInputNode input = Input.of(\"initial\", value -\u003e setState(() -\u003e this.text = value));\nregisterFocusable(input); // Tab to focus; typed characters update value\n```\n\n#### TextArea\n\n```java\nTextAreaNode area = TextArea.of(\"\", 5); // 5 visible rows\narea.insertChar('h');\narea.insertChar('i');\nString content = area.getText();\n```\n\n#### Checkbox\n\n```java\nCheckboxNode cb = Checkbox.of(\"Enable feature\", checked, () -\u003e setState(() -\u003e checked = !checked));\n// Press X (or bind any key) to toggle\n```\n\n#### RadioGroup\n\n```java\nRadioGroupNode radio = RadioGroup.of(\"Option A\", \"Option B\", \"Option C\");\n// radio.getSelectedIndex(), radio.setSelectedIndex(1)\n```\n\n#### Select\n\n```java\nSelectNode sel = Select.of(\"Red\", \"Green\", \"Blue\");\n// sel.getSelectedValue(), sel.setSelectedIndex(2)\n```\n\n### Lists \u0026 Tables\n\n#### Table\n\n```java\nList\u003cString\u003e headers = List.of(\"Name\", \"Role\", \"City\");\nList\u003cList\u003cString\u003e\u003e rows = List.of(\n    List.of(\"Alice\", \"Engineer\", \"Berlin\"),\n    List.of(\"Bob\",   \"Designer\", \"London\")\n);\nTableNode table = Table.of(headers, rows, 8); // show 8 rows\ntable.selectDown(); // navigate\ntable.selectUp();\n```\n\n#### VirtualList (large data sets)\n\n```java\nList\u003cString\u003e items = IntStream.range(1, 100_001)\n    .mapToObj(i -\u003e \"Item \" + i)\n    .collect(toList());\n\nVirtualListNode list = VirtualList.of(items, 15); // 15 visible rows\nlist.selectDown();\nlist.selectUp();\nlist.pageDown();\nlist.pageUp();\nlist.selectFirst();\nlist.selectLast();\nint idx = list.getSelectedIndex();\n```\n\n#### Viewport (scrollable window)\n\n```java\nNode content = VBox.of(/* many nodes */);\nViewportNode vp = Viewport.of(content, 10); // 10 visible rows\nvp.scrollDown();\nvp.scrollUp();\nvp.pageDown();\nvp.pageUp();\nvp.scrollToTop();\nvp.scrollToBottom();\nvp.showScrollbar(false); // hide scroll bar\n```\n\nStore the viewport as a field and wire scroll to key handlers:\n\n```java\n// in mount():\nonKey(KeyType.ARROW_DOWN, () -\u003e setState(() -\u003e viewport.scrollDown()));\nonKey(KeyType.ARROW_UP,   () -\u003e setState(() -\u003e viewport.scrollUp()));\n```\n\n### Progress Bar\n\n```java\nProgressBarNode bar = new ProgressBarNode(0.65); // 65%\nbar.setProgress(0.80);\nbar.filledStyle(Style.DEFAULT.withForeground(Color.GREEN));\nbar.emptyStyle(Style.DEFAULT.withForeground(Color.BRIGHT_BLACK));\n```\n\n### Spinner\n\n```java\nSpinnerNode spin = Spinner.of();            // default frames: | / - \\\nSpinnerNode spin = Spinner.of(new String[]{ \"⠋\",\"⠙\",\"⠹\",\"⠸\",\"⠼\",\"⠴\",\"⠦\",\"⠧\",\"⠇\",\"⠏\" });\n\n// Advance frame on a timer:\nAliveJTUI.scheduleRepeating(100, () -\u003e setState(() -\u003e spin.nextFrame()));\n```\n\n### Collapsible\n\n```java\nNode section = Collapsible.of(\"Settings\",\n    Text.of(\"  Option A\"),\n    Text.of(\"  Option B\")\n);\n// Collapsible.expanded(\"Title\", ...) — starts expanded\nCollapsibleNode col = (CollapsibleNode) section;\ncol.toggle();    // expand/collapse\ncol.expand();\ncol.collapse();\nboolean open = col.isExpanded();\n```\n\n### Dialog\n\n```java\n// Push a dialog overlay\nNode dialog = Dialog.of(\"Confirm\", VBox.of(\n    Text.of(\"Are you sure?\"),\n    HBox.of(\n        Button.of(\"[Yes]\", () -\u003e setState(() -\u003e { dialogNode = null; /* confirm */ })),\n        Button.of(\"[No]\",  () -\u003e setState(() -\u003e dialogNode = null))\n    )\n));\nAliveJTUI.pushOverlay(dialog);\n\n// Dismiss\nAliveJTUI.popOverlay();\n```\n\n### Help Panel\n\n```java\nNode help = HelpPanel.of(\n    new KeyBinding(\"Tab\",     \"Next field\"),\n    new KeyBinding(\"Enter\",   \"Confirm\"),\n    new KeyBinding(\"ESC\",     \"Quit\")\n);\n```\n\n---\n\n## Styling\n\n### Style\n\n`Style` is immutable. Build from `Style.DEFAULT`:\n\n```java\nStyle bold  = Style.DEFAULT.withBold(true);\nStyle fancy = Style.DEFAULT\n    .withForeground(Color.CYAN)\n    .withBackground(Color.BRIGHT_BLACK)\n    .withBold(true)\n    .withItalic(true);\n```\n\nApply to a node:\n\n```java\nText.of(\"hello\").style(fancy)\n// or shorthand:\nText.of(\"hello\").bold().color(Color.CYAN)\n```\n\n### Color\n\n```java\n// Standard ANSI 16\nColor.RED, Color.GREEN, Color.YELLOW, Color.BLUE,\nColor.CYAN, Color.MAGENTA, Color.WHITE, Color.BLACK,\nColor.BRIGHT_RED, Color.BRIGHT_GREEN, /* ... */\n\n// 256-color\nColor.ansi256(202) // orange\n\n// True color\nColor.rgb(255, 128, 0) // orange (if terminal supports it)\n```\n\n### Theme\n\n```java\n// Global theme (default: DARK)\nAliveJTUI.setTheme(Theme.LIGHT);\nAliveJTUI.setTheme(Theme.DARK);\n\n// Use in components\nTheme t = AliveJTUI.getTheme();\nText.of(\"Title\").style(t.primary())\nText.of(\"Hint\").style(t.muted())\nText.of(\"OK\").style(t.success())\nText.of(\"Error\").style(t.error())\n```\n\nSemantic roles: `foreground()`, `muted()`, `primary()`, `secondary()`, `success()`, `warning()`, `error()`, `focused()`.\n\n**Custom theme:**\n\n```java\nTheme myTheme = new Theme.BuiltinTheme(\n    Style.DEFAULT,                                         // foreground\n    Style.DEFAULT.withDim(true),                          // muted\n    Style.DEFAULT.withForeground(Color.rgb(0,200,255)).withBold(true), // primary\n    Style.DEFAULT.withForeground(Color.MAGENTA),          // secondary\n    Style.DEFAULT.withForeground(Color.GREEN),            // success\n    Style.DEFAULT.withForeground(Color.YELLOW),           // warning\n    Style.DEFAULT.withForeground(Color.RED),              // error\n    Style.DEFAULT.withForeground(Color.CYAN).withBold(true) // focused\n);\nAliveJTUI.setTheme(myTheme);\n```\n\n### StyleSheet (CSS-like)\n\n```java\nStyleSheet sheet = new StyleSheet()\n    .add(Selector.byId(\"title\"),        Style.DEFAULT.withForeground(Color.CYAN).withBold(true))\n    .add(Selector.byClass(\"muted\"),     Style.DEFAULT.withDim(true))\n    .add(Selector.byType(ButtonNode.class), Style.DEFAULT.withForeground(Color.YELLOW));\n\n// Tag nodes\nText.of(\"Hello\").withId(\"title\")\nText.of(\"hint\").withClassName(\"muted\")\n\n// Apply to tree\nsheet.applyToTree(rootNode);\n```\n\n---\n\n## Notifications\n\n```java\nNotificationManager notif = new NotificationManager(() -\u003e setState(() -\u003e {}));\n\nnotif.show(\"Saved successfully!\", 2000);\nnotif.show(\"Warning: low disk space\", 4000, NotificationType.WARNING);\nnotif.show(\"Error: connection failed\", 5000, NotificationType.ERROR);\nnotif.show(\"File uploaded\",            2500, NotificationType.SUCCESS);\n// NotificationType: INFO (default), SUCCESS, WARNING, ERROR\n\n// In render():\nNode overlay = notif.buildOverlay();\nif (overlay != null) AliveJTUI.pushOverlay(overlay);\n```\n\n---\n\n## Timers\n\n```java\n// One-shot after 2 seconds\nAliveJTUI.schedule(2000, () -\u003e setState(() -\u003e this.message = \"done\"));\n\n// Repeating every 150ms (e.g. spinner animation)\nRunnable tick = () -\u003e setState(() -\u003e spinFrame = (spinFrame + 1) % SPIN.length);\nAliveJTUI.scheduleRepeating(150, tick);\n\n// Cancel\nAliveJTUI.cancelTimer(tick);\n```\n\n---\n\n## Focus Management\n\n```java\n// Register focusable nodes in mount():\nregisterFocusable(myButton);\nregisterFocusable(myInput);\nregisterFocusable(anotherButton);\n\n// Tab  → focusNext()\n// Shift+Tab → focusPrev()\n// Enter → click() on the focused ButtonNode\n```\n\nNodes implementing `Focusable`: `ButtonNode`, `InputNode`, `TextAreaNode`, `CheckboxNode`, `RadioGroupNode`, `SelectNode`, `VirtualListNode`.\n\n---\n\n## Undo / Redo\n\n```java\nUndoManager undo = new UndoManager(); // default 100 entries\n// or: new UndoManager(50);\n\nString prev = text;\ntext = \"new value\";\nundo.record(\n    () -\u003e setState(() -\u003e text = prev),          // undo\n    () -\u003e setState(() -\u003e text = \"new value\")    // redo\n);\n\n// In key handlers:\nonKey(KeyType.CHARACTER, () -\u003e {\n    if (event.ctrl() \u0026\u0026 event.character() == 'z') undo.undo();\n    if (event.ctrl() \u0026\u0026 event.character() == 'y') undo.redo();\n});\n\nboolean canUndo = undo.canUndo();\nboolean canRedo = undo.canRedo();\nundo.clear();\n```\n\n---\n\n## Key Handling\n\n```java\n@Override\npublic void mount(Runnable onStateChange, EventBus eventBus) {\n    super.mount(onStateChange, eventBus);\n\n    // Special keys\n    onKey(KeyType.ARROW_DOWN, () -\u003e setState(() -\u003e selectedRow++));\n    onKey(KeyType.ARROW_UP,   () -\u003e setState(() -\u003e selectedRow--));\n    onKey(KeyType.PAGE_DOWN,  () -\u003e setState(() -\u003e selectedRow += 10));\n    onKey(KeyType.ENTER,      () -\u003e confirmSelection());\n\n    // Consuming (return true stops propagation)\n    onKey(KeyType.BACKSPACE, () -\u003e {\n        if (myInput.length() \u003e 0) {\n            setState(() -\u003e myInput = myInput.substring(0, myInput.length() - 1));\n            return true; // consumed\n        }\n        return false;\n    });\n\n    // Any printable character\n    eventBus.registerCharacter(c -\u003e {\n        if (c \u003e= 32) setState(() -\u003e inputText += c);\n    });\n}\n```\n\n**All key types:** `CHARACTER`, `ENTER`, `BACKSPACE`, `DELETE`, `ARROW_UP`, `ARROW_DOWN`, `ARROW_LEFT`, `ARROW_RIGHT`, `ESCAPE`, `TAB`, `SHIFT_TAB`, `HOME`, `END`, `PAGE_UP`, `PAGE_DOWN`, `EOF`.\n\n---\n\n## Backends\n\n| Backend | Use case |\n|---------|----------|\n| `Backends.createAuto()` | Default. Opens a Swing window on GUI desktops; uses native terminal on headless/server environments. |\n| `Backends.createSwing()` | Swing window — for GUI desktop environments. |\n| `Backends.createNative()` | Raw ANSI/POSIX or Windows VT — for terminal emulators and headless servers. |\n| `Backends.createMock(w, h)` | Unit testing — no real terminal required. |\n\n```java\n// Default (auto-selects Swing or native)\nAliveJTUI.run(new MyApp());\n\n// Explicit backend\nAliveJTUI.run(new MyApp(), Backends.createNative());\nAliveJTUI.run(new MyApp(), Backends.createSwing());\n\n// Custom backend — implement TerminalBackend\nAliveJTUI.run(new MyApp(), new MyCustomBackend());\n\n// Testing\nAliveJTUI.run(new MyApp(), Backends.createMock(80, 24));\n```\n\n`TerminalBackend` is a plain interface — implement it to integrate any other rendering layer (ncurses, raw ANSI, WebSocket, etc.).\n\n---\n\n## Testing\n\nUse `MockBackend` to test components without a real terminal:\n\n```java\nMockBackend backend = (MockBackend) Backends.createMock(80, 24);\nAliveJTUI.run(new MyApp(), backend);\n\n// Simulate key presses\nbackend.sendKey(KeyEvent.of(KeyType.ARROW_DOWN));\nbackend.sendKey(KeyEvent.ofCharacter('x'));\n\n// Inspect rendered output\nString cell = backend.getCell(0, 0); // character at col=0, row=0\n```\n\n---\n\n## Demo Application\n\n```bash\njava -jar alivejTUI-demo.jar\n```\n\n### Navigation\n\n| Key | Action |\n|-----|--------|\n| `1` – `6` | Switch tab |\n| `T` | Toggle Dark / Light theme |\n| `D` | Open confirmation dialog |\n| `N` | Show notification toast |\n| `Tab` | Move focus to next widget |\n| `Enter` | Click focused button |\n| `↑ ↓` | Navigate table rows / virtual list / scroll viewport (tab 5) |\n| `PgUp PgDn` | Page through virtual list / viewport |\n| `Home End` | Jump to top / bottom of virtual list |\n| `+` / `-` | Increase / decrease progress bar |\n| `X` | Toggle checkbox |\n| `C` | Expand / collapse section |\n| `S` | Cycle color select |\n| `ESC` | Quit |\n\n### Tabs\n\n| Tab | Content |\n|-----|---------|\n| `1:Widgets` | Button, progress bar, checkbox, input, radio, spinner, select |\n| `2:Table` | Scrollable data table with keyboard navigation |\n| `3:VirtualList` | 10,000 items — only visible rows rendered |\n| `4:Text` | All text styles, inline markdown, word wrapping |\n| `5:Layout` | BoxNode panels, collapsible section, scrollable viewport |\n| `6:Login` | Login form demonstrating focus management, inputs, and validation |\n\n---\n\n## Building\n\n```bash\n# Compile and run tests\nmvn test\n\n# Build library jar + demo fat-jar\nmvn package\n# produces target/alivejTUI-demo.jar\n```\n\n---\n\n## Project Structure\n\n```\nsrc/\n  main/java/io/github/yehorsyrin/tui/\n    core/         AliveJTUI, Component, Node, FocusManager, NotificationManager,\n                  TimerManager, UndoManager, AsyncTask, Focusable\n    node/         All node types: Text, VBox, HBox, Button, Input, TextArea,\n                  Checkbox, RadioGroup, Select, Table, VirtualList, Viewport,\n                  ProgressBar, Spinner, Dialog, Collapsible, HelpPanel, ...\n    style/        Color, Style, Theme, StyleSheet, Selector\n    event/        EventBus, KeyEvent, KeyType\n    backend/      TerminalBackend (interface), MockBackend, TerminalCapabilities\n    platform/     Backends (factory), NativeTerminalBackend, SwingBackend,\n                  AnsiKeyDecoder, AnsiWriter, PosixRawMode, WindowsRawMode,\n                  ResizePoller, TerminalSizeDetector\n    render/       Renderer, LayoutEngine, Differ, TreeFlattener\n    example/      DemoNative, DemoApp, TodoApp, Showcase\n  test/           unit tests\n```\n\n---\n\n## Known Issues\n\n| # | Description | Status |\n|---|-------------|--------|\n| 1 | `InputNode` did not show focus highlight when tabbing between fields — bold style is invisible on empty inputs | Fixed in 0.2.0 (underline style applied on focus) |\n\n---\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyehorsyrin%2FalivejTUI","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyehorsyrin%2FalivejTUI","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyehorsyrin%2FalivejTUI/lists"}