Skip to content

Latest commit

 

History

9 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ncurses

ncurses TUI bindings for Spinel, an ahead-of-time Ruby compiler.

Uses Spinel's built-in FFI DSL (ffi_lib, ffi_func, ffi_const) — no external gems required.

Requirements

  • Spinel (spin)
  • ncurses, statically linked from source (see spin.toml's [[build]]/[native] sections) — no brew install needed at build or run time, the compiled binaries have no ncurses dylib dependency

The first build clones a pinned ncurses tag from ThomasDickey/ncurses-snapshots (the upstream maintainer's own GitHub mirror) and compiles it, then compiles a small C shim (mouse support) against it. Spin will prompt you to allow this native build step — answer always to permanently trust it, or run spin trust ncurses beforehand to skip the prompt entirely. Requires network access on first build; the result is cached afterward.

Usage

Create a new project and add ncurses as a dependency:

mkdir myapp && cd myapp
spin init
spin add ncurses --git https://github.com/jockofcode/ncurses

Put your program in bin/hello.rb:

require "ncurses"

Ncurses::Screen.open do |screen|
  screen.root.write_centered(screen.rows / 2, "Hello, world!")
  screen.root.refresh
  Ncurses::Input.get_char(screen.root)
end

Build and run:

spin run hello          # build + run in one step

Or build first and run the binary directly:

spin build
./build/bin/hello

API

Ncurses::Screen

The entry point. Always use the block form — it handles initscr/endwin and window cleanup.

Ncurses::Screen.open do |screen|
  screen.rows        # terminal height
  screen.cols        # terminal width
  screen.root        # the root Window (stdscr)
  screen.sleep(ms)   # non-blocking millisecond sleep (napms)
end

Ncurses::Window

Wraps an ncurses WINDOW*. The root window is screen.root; subwindows are created with subwindow.

# Creating subwindows — block form, window is a typed parameter
screen.root.subwindow(rows: h, cols: w, top: r, left: c) do |win|
  win.write_at(row, col, str)       # move cursor and write
  win.write_centered(row, str)      # horizontally centered write
  win.fill(char)                    # fill all cells with char
  win.dimensions                    # => [rows, cols]
  win.clear
  win.refresh
  win.border
end

Attribute blocks

Attributes are applied for the duration of the block, then restored.

win.bold      { win.write_at(r, c, "text") }
win.underline { win.write_at(r, c, "text") }
win.reverse   { win.write_at(r, c, "text") }
win.standout  { win.write_at(r, c, "text") }
win.dim       { win.write_at(r, c, "text") }
win.blink     { win.write_at(r, c, "text") }
win.italic    { win.write_at(r, c, "text") }  # ncurses 6+ only

Color

Ncurses::Color.start
pair = Ncurses::Color.pair(fg: Ncurses::Color::RED, bg: Ncurses::Color::BLACK)
win.color(pair) { win.write_at(r, c, "text") }

Scrolling

win.scrollable(true)   # enable ncurses scroll mode
win.scroll(1)          # scroll content up one line
win.scroll(-1)         # scroll content down one line

Ncurses::Color

Ncurses::Color.start                              # call once after initscr
pair = Ncurses::Color.pair(fg: FG, bg: BG)       # register a color pair, returns int id

Color constants: BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE

Ncurses::Input

# Blocking — waits for a keypress, returns key code
Ncurses::Input.get_char(window)

# Non-blocking — returns -1 if no key waiting, key code otherwise
Ncurses::Input.nodelay(window, true)
Ncurses::Input.get_char_nowait(window)

# Buffered string input with backspace support
Ncurses::Input.get_string(window, max_length: 80)

Both get_char and get_char_nowait return LibNCurses::KEY_RESIZE when the terminal is resized. Re-read screen.rows / screen.cols and redraw on that code.

Key constants: KEY_UP KEY_DOWN KEY_LEFT KEY_RIGHT KEY_BACKSPACE KEY_RESIZE KEY_MOUSE

Ncurses::Mouse

Mouse support is implemented via a C shim (ncurses/mouse_shim.c) that wraps getmouse() and exposes the MEVENT fields, since Spinel's FFI DSL has no struct type.

Ncurses::Mouse.enable          # call once after Screen.open — enables all events + position reporting

# In the input loop:
ch = Ncurses::Input.get_char(window)
if ch == LibNCurses::KEY_MOUSE && Ncurses::Mouse.read
  x = Ncurses::Mouse.x        # column
  y = Ncurses::Mouse.y        # row
  Ncurses::Mouse.button1_clicked?   # left click
  Ncurses::Mouse.button1_pressed?   # left button down
  Ncurses::Mouse.button1_released?  # left button up
  Ncurses::Mouse.button2_clicked?   # middle click
  Ncurses::Mouse.button3_clicked?   # right click
  Ncurses::Mouse.button3_pressed?   # right button down
  Ncurses::Mouse.bstate             # raw mmask_t for custom checks
end

Ncurses::Log

Optional file logger, useful when stdout is taken by ncurses.

Ncurses::Log.open("/tmp/ncurses.log")   # call before Screen.open
Ncurses::Log.write("message")
Ncurses::Log.close                      # called automatically by Screen.open

Color constants

Constant Value
A_STANDOUT 0x010000
A_UNDERLINE 0x020000
A_REVERSE 0x040000
A_BLINK 0x080000
A_DIM 0x100000
A_BOLD 0x200000
A_ITALIC 0x80000000

Examples

Program Description
bin/demo.rb Bouncing * animation (DVD screensaver style)
bin/layout.rb Multi-pane layout with header, sidebar, and status bar
bin/input_demo.rb Buffered text input demonstration
bin/scroll.rb Scrollable list with arrow key navigation
bin/life.rb Conway's Game of Life
bin/snake.rb Snake game
bin/mouse_demo.rb Mouse click and movement tracking

Build all examples:

spin build

Run an example:

spin run snake
spin run life

Or after building:

./build/bin/snake
./build/bin/life

Tests

spin test

Snapshot tests print to stdout and diff against .expected files in test/.

About

ncurses library for spinel

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages