- Go 100%
| dialog | ||
| event | ||
| examples | ||
| internal | ||
| layout | ||
| screen | ||
| theme | ||
| vendor | ||
| widget | ||
| window | ||
| .gitignore | ||
| go.mod | ||
| go.sum | ||
| LICENSE | ||
| README.md | ||
| ROADMAP.md | ||
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, object‑oriented API for creating interactive terminal applications with windows, widgets, layouts, and event‑driven input handling.
Features
- Window‑based architecture: Z‑ordered 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 window‑specific events with routing and accelerators
- Efficient rendering: Double‑buffered output with dirty‑rectangle tracking
- Smooth scrolling: Terminal‑based 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
- Double‑buffer pooling: ~4.8× faster buffer allocation with automatic pooling
- All example applications: File browser, system monitor, todo list, KV store, and smooth‑scroll 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
Here’s 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 Z‑order, 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): Single‑line text entry with cursor movement - List (
widget/list.go): Scrollable list with selection and keyboard navigation - TextArea (
widget/textarea.go): Multi‑line text editing - Progress (
widget/progress.go): Wraps Konsoru’s 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 top‑most window under the cursor (mouse). Containers forward events to children and handle accelerator matching.
Examples
The examples/ directory contains complete, runnable applications:
smoothscroll/: Demonstrates terminal‑based smooth scrolling in a viewportsysmon/: System monitor showing CPU, memory, and load averagestodo/: Simple todo list manager with add/remove operationsfilebrowser/: Basic file system navigationkvstore/: Key‑value 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 Z‑order
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
Double‑Buffer Pooling
The screen manager uses a pooled double‑buffer 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 pixel‑perfect, 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 terminal’s background color (via OSC 11). Windows can override their theme individually.
Contributing
- Fork the repository
- Create a feature branch (
feature/descriptionorfix/issue‑description) - Write tests for your changes
- Ensure
go test ./...passes - 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.