Noroi is a Go library for building terminal user interfaces (TUIs) with a windowing abstraction, event routing, standard widgets, and layout management, built on top of the konsoru terminal primitives.
Find a file
2026-04-04 11:17:20 -07:00
dialog Expand test coverage to doublebuffer, theme, window, screen, and dialog packages 2026-04-04 00:26:17 -07:00
event Fix keyboard accelerator handling and complete Phase 5 tasks 2026-04-04 02:37:26 -07:00
examples feat: improve viewport layout and scrolling with precise clipping and debug logging 2026-04-04 04:19:14 -07:00
internal Add internal debug logging package 2026-04-04 02:41:19 -07:00
layout feat: improve viewport layout and scrolling with precise clipping and debug logging 2026-04-04 04:19:14 -07:00
screen feat: add MarkContainingWindowsDirty and improve Alt+key handling 2026-04-04 11:09:49 -07:00
theme Expand test coverage to doublebuffer, theme, window, screen, and dialog packages 2026-04-04 00:26:17 -07:00
vendor feat: add MarkContainingWindowsDirty and improve Alt+key handling 2026-04-04 11:09:49 -07:00
widget feat: improve viewport layout and scrolling with precise clipping and debug logging 2026-04-04 04:19:14 -07:00
window Expand test coverage to doublebuffer, theme, window, screen, and dialog packages 2026-04-04 00:26:17 -07:00
.gitignore Fix keyboard accelerator handling and complete Phase 5 tasks 2026-04-04 02:37:26 -07:00
go.mod Implement Phase 2: Input Routing and Event System 2026-04-03 09:45:29 -07:00
go.sum Implement Phase 2: Input Routing and Event System 2026-04-03 09:45:29 -07:00
LICENSE feat: add apache 2.0 license file 2026-04-04 11:10:32 -07:00
README.md docs: update README to change Japanese term for Noroi 2026-04-04 11:17:20 -07:00
ROADMAP.md Add terminal‑based smooth scrolling for viewport (closes #0155) 2026-04-04 01:47:53 -07:00

Noroi A Terminal UI Toolkit for Go

Noroi is a modular, efficient Terminal User Interface (TUI) toolkit for Go, built on top of the Konsoru ANSI terminal library. It provides a clean, objectoriented API for creating interactive terminal applications with windows, widgets, layouts, and eventdriven input handling.

Features

  • Windowbased architecture: Zordered windows with borders, titles, and focus management
  • Standard widget library: Buttons, labels, input fields, lists, text areas, progress bars
  • Layout managers: Absolute, stack, and grid layouts with nesting support
  • Event system: Keyboard, mouse, and windowspecific events with routing and accelerators
  • Efficient rendering: Doublebuffered output with dirtyrectangle tracking
  • Smooth scrolling: Terminalbased scroll regions for fluid content movement
  • Color themes: Configurable color schemes with light/dark detection
  • Modular design: Independent packages (screen, window, event, widget, layout)
  • Zero external dependencies: Pure Go, uses only the standard library and vendored Konsoru

Status

Phase 5 complete All features from the ROADMAP.md are implemented and tested. The toolkit is stable and ready for use. Recent improvements include:

  • Keyboard accelerator fixes: Alt+key sequences (e.g., Alt+Q to quit) now work reliably across all widgets
  • Border tearing resolved: Scrollable content no longer overwrites container borders thanks to precise viewport positioning and clipping
  • Doublebuffer pooling: ~4.8× faster buffer allocation with automatic pooling
  • All example applications: File browser, system monitor, todo list, KV store, and smoothscroll demo are fully functional

Installation

go get git.lan.thwap.org/thwap/noroi

All dependencies are vendored, so no extra go get steps are required.

Quick Start

Heres a minimal example that creates a window with a button:

package main

import (
    "context"
    "fmt"
    "os"
    "os/signal"
    "syscall"

    "git.lan.thwap.org/thwap/noroi/layout"
    "git.lan.thwap.org/thwap/noroi/screen"
    "git.lan.thwap.org/thwap/noroi/widget"
)

func main() {
    s, err := screen.NewScreen()
    if err != nil {
        fmt.Fprintf(os.Stderr, "Failed to create screen: %v\n", err)
        os.Exit(1)
    }
    defer s.Close()

    width, height := s.Size()
    mainContainer := layout.NewContainer(2, 2, width-4, height-4, "Hello Noroi")
    s.AddWindow(mainContainer)

    // Add a button with Alt+Q accelerator
    quitBtn := widget.NewButton(width-12, 2, 10, 1, "&Quit")
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()
    quitBtn.SetOnClick(func() { cancel() })
    mainContainer.AddChild(quitBtn)

    // Set focus and start event loop
    mainContainer.SetFocusedChild(quitBtn)
    s.SetFocus(mainContainer)

    sigCh := make(chan os.Signal, 1)
    signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
    go func() { <-sigCh; cancel() }()

    if err := s.StartEventLoop(ctx); err != nil {
        fmt.Fprintf(os.Stderr, "Event loop error: %v\n", err)
        os.Exit(1)
    }
}

Core Concepts

Screen

The Screen (screen/manager.go) manages the terminal display, double buffering, window Zorder, and event dispatch. It switches to the alternate screen buffer on creation and restores the original on exit.

Window

The Window interface (window/window.go) defines the contract for all UI elements: drawing, event handling, bounds, and focus. BaseWindow provides a default implementation with borders and title rendering.

Widgets

Widgets are reusable UI components that implement the Window interface:

  • Button (widget/button.go): Clickable button with mouse/keyboard activation and accelerator support (e.g., &Quit → Alt+Q)
  • Label (widget/label.go): Static or dynamic text with alignment and wrapping
  • Input (widget/input.go): Singleline text entry with cursor movement
  • List (widget/list.go): Scrollable list with selection and keyboard navigation
  • TextArea (widget/textarea.go): Multiline text editing
  • Progress (widget/progress.go): Wraps Konsorus progress bar
  • Viewport (widget/viewport.go): Scrollable container for larger content

Layout

Layout managers (layout/) arrange child windows within a container:

  • AbsoluteLayout: Fixed positions (default)
  • StackLayout: Vertical or horizontal stacking with weights
  • GridLayout: Row/column grid with spanning support

Containers (layout/container.go) can hold child windows and apply a layout. They clip children to their interior and forward events.

Events

The event system (event/) parses terminal input into KeyEvent and MouseEvent objects. Events are routed to the focused window (keyboard) or topmost window under the cursor (mouse). Containers forward events to children and handle accelerator matching.

Examples

The examples/ directory contains complete, runnable applications:

  • smoothscroll/: Demonstrates terminalbased smooth scrolling in a viewport
  • sysmon/: System monitor showing CPU, memory, and load averages
  • todo/: Simple todo list manager with add/remove operations
  • filebrowser/: Basic file system navigation
  • kvstore/: Keyvalue editor with persistent storage

Build and run any example:

cd examples/smoothscroll
go run .   # or go build .

API Reference

Screen Management

screen.NewScreen() (*Screen, error)   // Initialize terminal
screen.Close() error                  // Restore terminal
screen.AddWindow(win Window)          // Add window to Zorder
screen.SetFocus(win Window)           // Focus a window
screen.StartEventLoop(ctx) error      // Begin processing input

Creating Windows

// Base window (border + title)
win := window.NewBaseWindow(x, y, w, h, title)

// Container with layout
container := layout.NewContainer(x, y, w, h, title)
container.AddChild(child)
container.SetLayout(layout.NewStackLayout(layout.Vertical))

// Widgets
btn := widget.NewButton(x, y, w, h, "&Text")
label := widget.NewLabel(x, y, w, h, "Hello")
input := widget.NewInput(x, y, w)
list := widget.NewList(x, y, w, h)

Event Handling

// Implement HandleEvent(ev interface{}) bool in your window
func (w *MyWindow) HandleEvent(ev interface{}) bool {
    switch e := ev.(type) {
    case event.KeyEvent:
        if e.Char == 'q' && e.Alt {
            w.quit()
            return true
        }
    case event.MouseEvent:
        // handle mouse
    case event.FocusEvent, event.BlurEvent:
        // focus changes
    }
    return false
}

Layouts

// Stack layout
stack := layout.NewStackLayout(layout.Vertical)
stack.SetSpacing(1)
container.SetLayout(stack)

// Grid layout  
grid := layout.NewGridLayout(3, 2) // 3 rows, 2 columns
grid.SetColumnWidth(0, layout.Fixed(20))
grid.SetColumnWidth(1, layout.Weight(1))
container.SetLayout(grid)

Keyboard Shortcuts

  • Tab / Shift+Tab: Cycle focus between windows
  • Alt+Q: Quit (standard accelerator for “&Quit” buttons)
  • Arrow keys: Navigate lists, inputs, and scroll viewports
  • Page Up/Down: Scroll by pages
  • Enter / Space: Activate focused button
  • Mouse click: Focus window and interact with widgets

Advanced Features

DoubleBuffer Pooling

The screen manager uses a pooled doublebuffer implementation (internal/doublebuffer) that reduces allocations by ~4.8×. Buffers are automatically released to the pool when resized or closed.

Smooth Scrolling

When a Viewport scrolls its content, it attempts to use terminal scroll regions (ESC[top;bottomr + ESC[dyS ) for pixelperfect, instantaneous scrolling. If the terminal supports it, only newly exposed areas are redrawn.

Accelerator Keys

Buttons (and other actionable widgets) can define keyboard accelerators via the & marker in their label (&Quit → Alt+Q). The container scans all children for matching accelerators and activates the corresponding widget.

Color Themes

The theme package provides a default color scheme that respects the terminals background color (via OSC 11). Windows can override their theme individually.

Contributing

  1. Fork the repository
  2. Create a feature branch (feature/description or fix/issuedescription)
  3. Write tests for your changes
  4. Ensure go test ./... passes
  5. Submit a pull request with a clear description

License

Noroi is distributed under the terms of the Apache License 2.0. See the LICENSE file for details.

Acknowledgments

  • Built on Konsoru, a comprehensive ANSI terminal library
  • Inspired by classical TUI toolkits like Turbo Vision, ncurses, and tview
  • Developed as part of the Thwap ecosystem

Noroi 呪い (Japanese for “curse, spell, malediction”) because terminal interfaces should be simple to create.