summaryrefslogtreecommitdiffstats
path: root/src/tui/ncurses.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/tui/ncurses.go')
-rw-r--r--src/tui/ncurses.go268
1 files changed, 138 insertions, 130 deletions
diff --git a/src/tui/ncurses.go b/src/tui/ncurses.go
index 7f515b2f..0978ea8a 100644
--- a/src/tui/ncurses.go
+++ b/src/tui/ncurses.go
@@ -25,7 +25,6 @@ int c_getcurx(WINDOW* win) {
import "C"
import (
- "fmt"
"os"
"strconv"
"strings"
@@ -33,9 +32,39 @@ import (
"unicode/utf8"
)
-type ColorPair int16
type Attr C.uint
-type WindowImpl C.WINDOW
+
+type CursesWindow struct {
+ impl *C.WINDOW
+ top int
+ left int
+ width int
+ height int
+}
+
+func (w *CursesWindow) Top() int {
+ return w.top
+}
+
+func (w *CursesWindow) Left() int {
+ return w.left
+}
+
+func (w *CursesWindow) Width() int {
+ return w.width
+}
+
+func (w *CursesWindow) Height() int {
+ return w.height
+}
+
+func (w *CursesWindow) Refresh() {
+ C.wnoutrefresh(w.impl)
+}
+
+func (w *CursesWindow) FinishFill() {
+ // NO-OP
+}
const (
Bold Attr = C.A_BOLD
@@ -51,31 +80,14 @@ const (
AttrRegular Attr = 0
)
-// Pallete
-const (
- ColDefault ColorPair = iota
- ColNormal
- ColPrompt
- ColMatch
- ColCurrent
- ColCurrentMatch
- ColSpinner
- ColInfo
- ColCursor
- ColSelected
- ColHeader
- ColBorder
- ColUser // Should be the last entry
-)
-
var (
_screen *C.SCREEN
- _colorMap map[int]ColorPair
+ _colorMap map[int]int16
_colorFn func(ColorPair, Attr) (C.short, C.int)
)
func init() {
- _colorMap = make(map[int]ColorPair)
+ _colorMap = make(map[int]int16)
if strings.HasPrefix(C.GoString(C.curses_version()), "ncurses 5") {
Italic = C.A_NORMAL
}
@@ -85,27 +97,25 @@ func (a Attr) Merge(b Attr) Attr {
return a | b
}
-func DefaultTheme() *ColorTheme {
+func (r *FullscreenRenderer) defaultTheme() *ColorTheme {
if C.tigetnum(C.CString("colors")) >= 256 {
return Dark256
}
return Default16
}
-func Init(theme *ColorTheme, black bool, mouse bool) {
+func (r *FullscreenRenderer) Init() {
C.setlocale(C.LC_ALL, C.CString(""))
tty := C.c_tty()
if tty == nil {
- fmt.Println("Failed to open /dev/tty")
- os.Exit(2)
+ errorExit("Failed to open /dev/tty")
}
_screen = C.c_newterm(tty)
if _screen == nil {
- fmt.Println("Invalid $TERM: " + os.Getenv("TERM"))
- os.Exit(2)
+ errorExit("Invalid $TERM: " + os.Getenv("TERM"))
}
C.set_term(_screen)
- if mouse {
+ if r.mouse {
C.mousemask(C.ALL_MOUSE_EVENTS, nil)
C.mouseinterval(0)
}
@@ -124,14 +134,14 @@ func Init(theme *ColorTheme, black bool, mouse bool) {
}
C.set_escdelay(C.int(delay))
- _color = theme != nil
- if _color {
+ if r.theme != nil {
C.start_color()
- InitTheme(theme, black)
- initPairs(theme)
- C.bkgd(C.chtype(C.COLOR_PAIR(C.int(ColNormal))))
+ initTheme(r.theme, r.defaultTheme(), r.forceBlack)
+ initPairs(r.theme)
+ C.bkgd(C.chtype(C.COLOR_PAIR(C.int(ColNormal.index()))))
_colorFn = attrColored
} else {
+ initTheme(r.theme, nil, r.forceBlack)
_colorFn = attrMono
}
@@ -145,39 +155,39 @@ func Init(theme *ColorTheme, black bool, mouse bool) {
func initPairs(theme *ColorTheme) {
C.assume_default_colors(C.int(theme.Fg), C.int(theme.Bg))
- initPair := func(group ColorPair, fg Color, bg Color) {
- C.init_pair(C.short(group), C.short(fg), C.short(bg))
+ for _, pair := range []ColorPair{
+ ColNormal,
+ ColPrompt,
+ ColMatch,
+ ColCurrent,
+ ColCurrentMatch,
+ ColSpinner,
+ ColInfo,
+ ColCursor,
+ ColSelected,
+ ColHeader,
+ ColBorder} {
+ C.init_pair(C.short(pair.index()), C.short(pair.Fg()), C.short(pair.Bg()))
}
- initPair(ColNormal, theme.Fg, theme.Bg)
- initPair(ColPrompt, theme.Prompt, theme.Bg)
- initPair(ColMatch, theme.Match, theme.Bg)
- initPair(ColCurrent, theme.Current, theme.DarkBg)
- initPair(ColCurrentMatch, theme.CurrentMatch, theme.DarkBg)
- initPair(ColSpinner, theme.Spinner, theme.Bg)
- initPair(ColInfo, theme.Info, theme.Bg)
- initPair(ColCursor, theme.Cursor, theme.DarkBg)
- initPair(ColSelected, theme.Selected, theme.DarkBg)
- initPair(ColHeader, theme.Header, theme.Bg)
- initPair(ColBorder, theme.Border, theme.Bg)
-}
-
-func Pause() {
+}
+
+func (r *FullscreenRenderer) Pause() {
C.endwin()
}
-func Resume() bool {
+func (r *FullscreenRenderer) Resume() bool {
return false
}
-func Close() {
+func (r *FullscreenRenderer) Close() {
C.endwin()
C.delscreen(_screen)
}
-func NewWindow(top int, left int, width int, height int, border bool) *Window {
+func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, border bool) Window {
win := C.newwin(C.int(height), C.int(width), C.int(top), C.int(left))
- if _color {
- C.wbkgd(win, C.chtype(C.COLOR_PAIR(C.int(ColNormal))))
+ if r.theme != nil {
+ C.wbkgd(win, C.chtype(C.COLOR_PAIR(C.int(ColNormal.index()))))
}
if border {
pair, attr := _colorFn(ColBorder, 0)
@@ -188,66 +198,50 @@ func NewWindow(top int, left int, width int, height int, border bool) *Window {
C.wcolor_set(win, 0, nil)
}
- return &Window{
- impl: (*WindowImpl)(win),
- Top: top,
- Left: left,
- Width: width,
- Height: height,
+ return &CursesWindow{
+ impl: win,
+ top: top,
+ left: left,
+ width: width,
+ height: height,
}
}
-func attrColored(pair ColorPair, a Attr) (C.short, C.int) {
- return C.short(pair), C.int(a)
+func attrColored(color ColorPair, a Attr) (C.short, C.int) {
+ return C.short(color.index()), C.int(a)
}
-func attrMono(pair ColorPair, a Attr) (C.short, C.int) {
- var attr C.int
- switch pair {
- case ColCurrent:
- attr = C.A_REVERSE
- case ColMatch:
- attr = C.A_UNDERLINE
- case ColCurrentMatch:
- attr = C.A_UNDERLINE | C.A_REVERSE
- }
- if C.int(a)&C.A_BOLD == C.A_BOLD {
- attr = attr | C.A_BOLD
- }
- return 0, attr
+func attrMono(color ColorPair, a Attr) (C.short, C.int) {
+ return 0, C.int(attrFor(color, a))
}
-func MaxX() int {
+func (r *FullscreenRenderer) MaxX() int {
return int(C.COLS)
}
-func MaxY() int {
+func (r *FullscreenRenderer) MaxY() int {
return int(C.LINES)
}
-func (w *Window) win() *C.WINDOW {
- return (*C.WINDOW)(w.impl)
-}
-
-func (w *Window) Close() {
- C.delwin(w.win())
+func (w *CursesWindow) Close() {
+ C.delwin(w.impl)
}
-func (w *Window) Enclose(y int, x int) bool {
- return bool(C.wenclose(w.win(), C.int(y), C.int(x)))
+func (w *CursesWindow) Enclose(y int, x int) bool {
+ return bool(C.wenclose(w.impl, C.int(y), C.int(x)))
}
-func (w *Window) Move(y int, x int) {
- C.wmove(w.win(), C.int(y), C.int(x))
+func (w *CursesWindow) Move(y int, x int) {
+ C.wmove(w.impl, C.int(y), C.int(x))
}
-func (w *Window) MoveAndClear(y int, x int) {
+func (w *CursesWindow) MoveAndClear(y int, x int) {
w.Move(y, x)
- C.wclrtoeol(w.win())
+ C.wclrtoeol(w.impl)
}
-func (w *Window) Print(text string) {
- C.waddstr(w.win(), C.CString(strings.Map(func(r rune) rune {
+func (w *CursesWindow) Print(text string) {
+ C.waddstr(w.impl, C.CString(strings.Map(func(r rune) rune {
if r < 32 {
return -1
}
@@ -255,69 +249,81 @@ func (w *Window) Print(text string) {
}, text)))
}
-func (w *Window) CPrint(pair ColorPair, attr Attr, text string) {
- p, a := _colorFn(pair, attr)
- C.wcolor_set(w.win(), p, nil)
- C.wattron(w.win(), a)
+func (w *CursesWindow) CPrint(color ColorPair, attr Attr, text string) {
+ p, a := _colorFn(color, attr)
+ C.wcolor_set(w.impl, p, nil)
+ C.wattron(w.impl, a)
w.Print(text)
- C.wattroff(w.win(), a)
- C.wcolor_set(w.win(), 0, nil)
+ C.wattroff(w.impl, a)
+ C.wcolor_set(w.impl, 0, nil)
}
-func Clear() {
+func (r *FullscreenRenderer) Clear() {
C.clear()
C.endwin()
}
-func Refresh() {
+func (r *FullscreenRenderer) Refresh() {
C.refresh()
}
-func (w *Window) Erase() {
- C.werase(w.win())
+func (w *CursesWindow) Erase() {
+ C.werase(w.impl)
}
-func (w *Window) X() int {
- return int(C.c_getcurx(w.win()))
+func (w *CursesWindow) X() int {
+ return int(C.c_getcurx(w.impl))
}
-func DoesAutoWrap() bool {
+func (r *FullscreenRenderer) DoesAutoWrap() bool {
return true
}
-func (w *Window) Fill(str string) bool {
- return C.waddstr(w.win(), C.CString(str)) == C.OK
+func (r *FullscreenRenderer) IsOptimized() bool {
+ return true
}
-func (w *Window) CFill(str string, fg Color, bg Color, attr Attr) bool {
- pair := PairFor(fg, bg)
- C.wcolor_set(w.win(), C.short(pair), nil)
- C.wattron(w.win(), C.int(attr))
+func (w *CursesWindow) Fill(str string) FillReturn {
+ if C.waddstr(w.impl, C.CString(str)) == C.OK {
+ return FillContinue
+ }
+ return FillSuspend
+}
+
+func (w *CursesWindow) CFill(fg Color, bg Color, attr Attr, str string) FillReturn {
+ index := ColorPair{fg, bg, -1}.index()
+ C.wcolor_set(w.impl, C.short(index), nil)
+ C.wattron(w.impl, C.int(attr))
ret := w.Fill(str)
- C.wattroff(w.win(), C.int(attr))
- C.wcolor_set(w.win(), 0, nil)
+ C.wattroff(w.impl, C.int(attr))
+ C.wcolor_set(w.impl, 0, nil)
return ret
}
-func RefreshWindows(windows []*Window) {
+func (r *FullscreenRenderer) RefreshWindows(windows []Window) {
for _, w := range windows {
- C.wnoutrefresh(w.win())
+ w.Refresh()
}
C.doupdate()
}
-func PairFor(fg Color, bg Color) ColorPair {
+func (p ColorPair) index() int16 {
+ if p.id >= 0 {
+ return p.id
+ }
+
// ncurses does not support 24-bit colors
- if fg.is24() || bg.is24() {
- return ColDefault
+ if p.is24() {
+ return ColDefault.index()
}
- key := (int(fg) << 8) + int(bg)
+
+ key := p.key()
if found, prs := _colorMap[key]; prs {
return found
}
- id := ColorPair(len(_colorMap) + int(ColUser))
- C.init_pair(C.short(id), C.short(fg), C.short(bg))
+ id := int16(len(_colorMap)) + ColUser.id
+ C.init_pair(C.short(id), C.short(p.Fg()), C.short(p.Bg()))
_colorMap[key] = id
return id
}
@@ -369,11 +375,13 @@ func escSequence() Event {
return Event{Invalid, 0, nil}
}
-func GetChar() Event {
+func (r *FullscreenRenderer) GetChar() Event {
c := C.getch()
switch c {
case C.ERR:
- return Event{Invalid, 0, nil}
+ // Unexpected error from blocking read
+ r.Close()
+ errorExit("Failed to read /dev/tty")
case C.KEY_UP:
return Event{Up, 0, nil}
case C.KEY_DOWN:
@@ -435,17 +443,17 @@ func GetChar() Event {
/* Cannot use BUTTON1_DOUBLE_CLICKED due to mouseinterval(0) */
if (me.bstate & C.BUTTON1_PRESSED) > 0 {
now := time.Now()
- if now.Sub(_prevDownTime) < doubleClickDuration {
- _clickY = append(_clickY, y)
+ if now.Sub(r.prevDownTime) < doubleClickDuration {
+ r.clickY = append(r.clickY, y)
} else {
- _clickY = []int{y}
- _prevDownTime = now
+ r.clickY = []int{y}
+ r.prevDownTime = now
}
return Event{Mouse, 0, &MouseEvent{y, x, 0, true, false, mod}}
} else if (me.bstate & C.BUTTON1_RELEASED) > 0 {
double := false
- if len(_clickY) > 1 && _clickY[0] == _clickY[1] &&
- time.Now().Sub(_prevDownTime) < doubleClickDuration {
+ if len(r.clickY) > 1 && r.clickY[0] == r.clickY[1] &&
+ time.Now().Sub(r.prevDownTime) < doubleClickDuration {
double = true
}
return Event{Mouse, 0, &MouseEvent{y, x, 0, false, double, mod}}