2016-10-24 00:44:56 +00:00
|
|
|
package tui
|
|
|
|
|
|
|
|
import (
|
2017-01-11 14:01:56 +00:00
|
|
|
"fmt"
|
2017-01-10 17:12:32 +00:00
|
|
|
"os"
|
2017-01-09 17:16:12 +00:00
|
|
|
"strconv"
|
2016-10-24 00:44:56 +00:00
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Types of user action
|
|
|
|
const (
|
|
|
|
Rune = iota
|
|
|
|
|
|
|
|
CtrlA
|
|
|
|
CtrlB
|
|
|
|
CtrlC
|
|
|
|
CtrlD
|
|
|
|
CtrlE
|
|
|
|
CtrlF
|
|
|
|
CtrlG
|
|
|
|
CtrlH
|
|
|
|
Tab
|
|
|
|
CtrlJ
|
|
|
|
CtrlK
|
|
|
|
CtrlL
|
|
|
|
CtrlM
|
|
|
|
CtrlN
|
|
|
|
CtrlO
|
|
|
|
CtrlP
|
|
|
|
CtrlQ
|
|
|
|
CtrlR
|
|
|
|
CtrlS
|
|
|
|
CtrlT
|
|
|
|
CtrlU
|
|
|
|
CtrlV
|
|
|
|
CtrlW
|
|
|
|
CtrlX
|
|
|
|
CtrlY
|
|
|
|
CtrlZ
|
|
|
|
ESC
|
2017-01-27 17:54:47 +00:00
|
|
|
CtrlSpace
|
2016-10-24 00:44:56 +00:00
|
|
|
|
|
|
|
Invalid
|
2016-11-22 16:58:46 +00:00
|
|
|
Resize
|
2016-10-24 00:44:56 +00:00
|
|
|
Mouse
|
|
|
|
DoubleClick
|
|
|
|
|
|
|
|
BTab
|
|
|
|
BSpace
|
|
|
|
|
|
|
|
Del
|
|
|
|
PgUp
|
|
|
|
PgDn
|
|
|
|
|
|
|
|
Up
|
|
|
|
Down
|
|
|
|
Left
|
|
|
|
Right
|
|
|
|
Home
|
|
|
|
End
|
|
|
|
|
|
|
|
SLeft
|
|
|
|
SRight
|
|
|
|
|
|
|
|
F1
|
|
|
|
F2
|
|
|
|
F3
|
|
|
|
F4
|
|
|
|
F5
|
|
|
|
F6
|
|
|
|
F7
|
|
|
|
F8
|
|
|
|
F9
|
|
|
|
F10
|
2016-11-19 13:40:28 +00:00
|
|
|
F11
|
|
|
|
F12
|
2016-10-24 00:44:56 +00:00
|
|
|
|
2017-05-22 08:07:05 +00:00
|
|
|
Change
|
|
|
|
|
2016-10-24 00:44:56 +00:00
|
|
|
AltSpace
|
|
|
|
AltSlash
|
|
|
|
AltBS
|
2016-11-19 13:40:28 +00:00
|
|
|
|
|
|
|
Alt0
|
|
|
|
)
|
|
|
|
|
|
|
|
const ( // Reset iota
|
|
|
|
AltA = Alt0 + 'a' - '0' + iota
|
2016-10-24 00:44:56 +00:00
|
|
|
AltB
|
|
|
|
AltC
|
|
|
|
AltD
|
|
|
|
AltE
|
|
|
|
AltF
|
2017-04-27 17:36:36 +00:00
|
|
|
AltZ = AltA + 'z' - 'a'
|
|
|
|
CtrlAltA = AltZ + 1
|
|
|
|
CtrlAltM = CtrlAltA + 'm' - 'a'
|
2016-10-24 00:44:56 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
doubleClickDuration = 500 * time.Millisecond
|
|
|
|
)
|
|
|
|
|
2016-11-25 15:36:38 +00:00
|
|
|
type Color int32
|
|
|
|
|
|
|
|
func (c Color) is24() bool {
|
|
|
|
return c > 0 && (c&(1<<24)) > 0
|
|
|
|
}
|
2016-10-24 00:44:56 +00:00
|
|
|
|
|
|
|
const (
|
|
|
|
colUndefined Color = -2
|
|
|
|
colDefault = -1
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
colBlack Color = iota
|
|
|
|
colRed
|
|
|
|
colGreen
|
|
|
|
colYellow
|
|
|
|
colBlue
|
|
|
|
colMagenta
|
|
|
|
colCyan
|
|
|
|
colWhite
|
|
|
|
)
|
|
|
|
|
2017-01-11 13:13:40 +00:00
|
|
|
type FillReturn int
|
|
|
|
|
|
|
|
const (
|
|
|
|
FillContinue FillReturn = iota
|
|
|
|
FillNextLine
|
|
|
|
FillSuspend
|
|
|
|
)
|
|
|
|
|
2017-01-07 16:30:31 +00:00
|
|
|
type ColorPair struct {
|
|
|
|
fg Color
|
|
|
|
bg Color
|
|
|
|
id int16
|
|
|
|
}
|
|
|
|
|
2017-01-09 17:16:12 +00:00
|
|
|
func HexToColor(rrggbb string) Color {
|
|
|
|
r, _ := strconv.ParseInt(rrggbb[1:3], 16, 0)
|
|
|
|
g, _ := strconv.ParseInt(rrggbb[3:5], 16, 0)
|
|
|
|
b, _ := strconv.ParseInt(rrggbb[5:7], 16, 0)
|
|
|
|
return Color((1 << 24) + (r << 16) + (g << 8) + b)
|
|
|
|
}
|
|
|
|
|
2017-01-07 16:30:31 +00:00
|
|
|
func NewColorPair(fg Color, bg Color) ColorPair {
|
|
|
|
return ColorPair{fg, bg, -1}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p ColorPair) Fg() Color {
|
|
|
|
return p.fg
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p ColorPair) Bg() Color {
|
|
|
|
return p.bg
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p ColorPair) key() int {
|
|
|
|
return (int(p.Fg()) << 8) + int(p.Bg())
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p ColorPair) is24() bool {
|
|
|
|
return p.Fg().is24() || p.Bg().is24()
|
|
|
|
}
|
|
|
|
|
2016-10-24 00:44:56 +00:00
|
|
|
type ColorTheme struct {
|
|
|
|
Fg Color
|
|
|
|
Bg Color
|
|
|
|
DarkBg Color
|
|
|
|
Prompt Color
|
|
|
|
Match Color
|
|
|
|
Current Color
|
|
|
|
CurrentMatch Color
|
|
|
|
Spinner Color
|
|
|
|
Info Color
|
|
|
|
Cursor Color
|
|
|
|
Selected Color
|
|
|
|
Header Color
|
|
|
|
Border Color
|
|
|
|
}
|
|
|
|
|
2017-01-16 03:06:54 +00:00
|
|
|
func (t *ColorTheme) HasBg() bool {
|
|
|
|
return t.Bg != colDefault
|
|
|
|
}
|
|
|
|
|
2016-10-24 00:44:56 +00:00
|
|
|
type Event struct {
|
|
|
|
Type int
|
|
|
|
Char rune
|
|
|
|
MouseEvent *MouseEvent
|
|
|
|
}
|
|
|
|
|
|
|
|
type MouseEvent struct {
|
|
|
|
Y int
|
|
|
|
X int
|
|
|
|
S int
|
|
|
|
Down bool
|
|
|
|
Double bool
|
|
|
|
Mod bool
|
|
|
|
}
|
|
|
|
|
2017-02-04 12:51:22 +00:00
|
|
|
type BorderStyle int
|
|
|
|
|
|
|
|
const (
|
|
|
|
BorderNone BorderStyle = iota
|
|
|
|
BorderAround
|
|
|
|
BorderHorizontal
|
|
|
|
)
|
|
|
|
|
2017-01-07 16:30:31 +00:00
|
|
|
type Renderer interface {
|
|
|
|
Init()
|
2017-04-28 13:58:08 +00:00
|
|
|
Pause(clear bool)
|
|
|
|
Resume(clear bool)
|
2017-01-07 16:30:31 +00:00
|
|
|
Clear()
|
|
|
|
RefreshWindows(windows []Window)
|
|
|
|
Refresh()
|
|
|
|
Close()
|
|
|
|
|
|
|
|
GetChar() Event
|
|
|
|
|
|
|
|
MaxX() int
|
|
|
|
MaxY() int
|
|
|
|
DoesAutoWrap() bool
|
2017-01-15 17:26:36 +00:00
|
|
|
IsOptimized() bool
|
2017-01-07 16:30:31 +00:00
|
|
|
|
2017-02-04 12:51:22 +00:00
|
|
|
NewWindow(top int, left int, width int, height int, borderStyle BorderStyle) Window
|
2017-01-07 16:30:31 +00:00
|
|
|
}
|
2016-10-24 00:44:56 +00:00
|
|
|
|
2017-01-07 16:30:31 +00:00
|
|
|
type Window interface {
|
|
|
|
Top() int
|
|
|
|
Left() int
|
|
|
|
Width() int
|
|
|
|
Height() int
|
|
|
|
|
|
|
|
Refresh()
|
|
|
|
FinishFill()
|
|
|
|
Close()
|
|
|
|
|
|
|
|
X() int
|
|
|
|
Enclose(y int, x int) bool
|
|
|
|
|
|
|
|
Move(y int, x int)
|
|
|
|
MoveAndClear(y int, x int)
|
|
|
|
Print(text string)
|
|
|
|
CPrint(color ColorPair, attr Attr, text string)
|
2017-01-11 13:13:40 +00:00
|
|
|
Fill(text string) FillReturn
|
|
|
|
CFill(fg Color, bg Color, attr Attr, text string) FillReturn
|
2017-01-07 16:30:31 +00:00
|
|
|
Erase()
|
|
|
|
}
|
|
|
|
|
|
|
|
type FullscreenRenderer struct {
|
|
|
|
theme *ColorTheme
|
|
|
|
mouse bool
|
|
|
|
forceBlack bool
|
|
|
|
prevDownTime time.Time
|
|
|
|
clickY []int
|
2016-10-24 00:44:56 +00:00
|
|
|
}
|
|
|
|
|
2017-01-07 16:30:31 +00:00
|
|
|
func NewFullscreenRenderer(theme *ColorTheme, forceBlack bool, mouse bool) Renderer {
|
|
|
|
r := &FullscreenRenderer{
|
|
|
|
theme: theme,
|
|
|
|
mouse: mouse,
|
|
|
|
forceBlack: forceBlack,
|
|
|
|
prevDownTime: time.Unix(0, 0),
|
|
|
|
clickY: []int{}}
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
Default16 *ColorTheme
|
|
|
|
Dark256 *ColorTheme
|
|
|
|
Light256 *ColorTheme
|
|
|
|
|
|
|
|
ColDefault ColorPair
|
|
|
|
ColNormal ColorPair
|
|
|
|
ColPrompt ColorPair
|
|
|
|
ColMatch ColorPair
|
|
|
|
ColCurrent ColorPair
|
|
|
|
ColCurrentMatch ColorPair
|
|
|
|
ColSpinner ColorPair
|
|
|
|
ColInfo ColorPair
|
|
|
|
ColCursor ColorPair
|
|
|
|
ColSelected ColorPair
|
|
|
|
ColHeader ColorPair
|
|
|
|
ColBorder ColorPair
|
|
|
|
ColUser ColorPair
|
|
|
|
)
|
|
|
|
|
2016-10-24 00:44:56 +00:00
|
|
|
func EmptyTheme() *ColorTheme {
|
|
|
|
return &ColorTheme{
|
|
|
|
Fg: colUndefined,
|
|
|
|
Bg: colUndefined,
|
|
|
|
DarkBg: colUndefined,
|
|
|
|
Prompt: colUndefined,
|
|
|
|
Match: colUndefined,
|
|
|
|
Current: colUndefined,
|
|
|
|
CurrentMatch: colUndefined,
|
|
|
|
Spinner: colUndefined,
|
|
|
|
Info: colUndefined,
|
|
|
|
Cursor: colUndefined,
|
|
|
|
Selected: colUndefined,
|
|
|
|
Header: colUndefined,
|
|
|
|
Border: colUndefined}
|
|
|
|
}
|
|
|
|
|
2017-01-11 14:01:56 +00:00
|
|
|
func errorExit(message string) {
|
|
|
|
fmt.Fprintln(os.Stderr, message)
|
2017-01-10 17:12:32 +00:00
|
|
|
os.Exit(2)
|
|
|
|
}
|
|
|
|
|
2016-10-24 00:44:56 +00:00
|
|
|
func init() {
|
|
|
|
Default16 = &ColorTheme{
|
|
|
|
Fg: colDefault,
|
|
|
|
Bg: colDefault,
|
|
|
|
DarkBg: colBlack,
|
|
|
|
Prompt: colBlue,
|
|
|
|
Match: colGreen,
|
|
|
|
Current: colYellow,
|
|
|
|
CurrentMatch: colGreen,
|
|
|
|
Spinner: colGreen,
|
|
|
|
Info: colWhite,
|
|
|
|
Cursor: colRed,
|
|
|
|
Selected: colMagenta,
|
|
|
|
Header: colCyan,
|
|
|
|
Border: colBlack}
|
|
|
|
Dark256 = &ColorTheme{
|
|
|
|
Fg: colDefault,
|
|
|
|
Bg: colDefault,
|
|
|
|
DarkBg: 236,
|
|
|
|
Prompt: 110,
|
|
|
|
Match: 108,
|
|
|
|
Current: 254,
|
|
|
|
CurrentMatch: 151,
|
|
|
|
Spinner: 148,
|
|
|
|
Info: 144,
|
|
|
|
Cursor: 161,
|
|
|
|
Selected: 168,
|
|
|
|
Header: 109,
|
|
|
|
Border: 59}
|
|
|
|
Light256 = &ColorTheme{
|
|
|
|
Fg: colDefault,
|
|
|
|
Bg: colDefault,
|
|
|
|
DarkBg: 251,
|
|
|
|
Prompt: 25,
|
|
|
|
Match: 66,
|
|
|
|
Current: 237,
|
|
|
|
CurrentMatch: 23,
|
|
|
|
Spinner: 65,
|
|
|
|
Info: 101,
|
|
|
|
Cursor: 161,
|
|
|
|
Selected: 168,
|
|
|
|
Header: 31,
|
|
|
|
Border: 145}
|
|
|
|
}
|
|
|
|
|
2017-01-07 16:30:31 +00:00
|
|
|
func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
|
|
|
|
if theme == nil {
|
|
|
|
initPalette(theme)
|
2016-10-24 00:44:56 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-01-07 16:30:31 +00:00
|
|
|
if forceBlack {
|
2016-10-24 00:44:56 +00:00
|
|
|
theme.Bg = colBlack
|
|
|
|
}
|
|
|
|
|
|
|
|
o := func(a Color, b Color) Color {
|
|
|
|
if b == colUndefined {
|
|
|
|
return a
|
|
|
|
}
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
theme.Fg = o(baseTheme.Fg, theme.Fg)
|
|
|
|
theme.Bg = o(baseTheme.Bg, theme.Bg)
|
|
|
|
theme.DarkBg = o(baseTheme.DarkBg, theme.DarkBg)
|
|
|
|
theme.Prompt = o(baseTheme.Prompt, theme.Prompt)
|
|
|
|
theme.Match = o(baseTheme.Match, theme.Match)
|
|
|
|
theme.Current = o(baseTheme.Current, theme.Current)
|
|
|
|
theme.CurrentMatch = o(baseTheme.CurrentMatch, theme.CurrentMatch)
|
|
|
|
theme.Spinner = o(baseTheme.Spinner, theme.Spinner)
|
|
|
|
theme.Info = o(baseTheme.Info, theme.Info)
|
|
|
|
theme.Cursor = o(baseTheme.Cursor, theme.Cursor)
|
|
|
|
theme.Selected = o(baseTheme.Selected, theme.Selected)
|
|
|
|
theme.Header = o(baseTheme.Header, theme.Header)
|
|
|
|
theme.Border = o(baseTheme.Border, theme.Border)
|
2017-01-07 16:30:31 +00:00
|
|
|
|
|
|
|
initPalette(theme)
|
|
|
|
}
|
|
|
|
|
|
|
|
func initPalette(theme *ColorTheme) {
|
|
|
|
ColDefault = ColorPair{colDefault, colDefault, 0}
|
|
|
|
if theme != nil {
|
|
|
|
ColNormal = ColorPair{theme.Fg, theme.Bg, 1}
|
|
|
|
ColPrompt = ColorPair{theme.Prompt, theme.Bg, 2}
|
|
|
|
ColMatch = ColorPair{theme.Match, theme.Bg, 3}
|
|
|
|
ColCurrent = ColorPair{theme.Current, theme.DarkBg, 4}
|
|
|
|
ColCurrentMatch = ColorPair{theme.CurrentMatch, theme.DarkBg, 5}
|
|
|
|
ColSpinner = ColorPair{theme.Spinner, theme.Bg, 6}
|
|
|
|
ColInfo = ColorPair{theme.Info, theme.Bg, 7}
|
|
|
|
ColCursor = ColorPair{theme.Cursor, theme.DarkBg, 8}
|
|
|
|
ColSelected = ColorPair{theme.Selected, theme.DarkBg, 9}
|
|
|
|
ColHeader = ColorPair{theme.Header, theme.Bg, 10}
|
|
|
|
ColBorder = ColorPair{theme.Border, theme.Bg, 11}
|
|
|
|
} else {
|
|
|
|
ColNormal = ColorPair{colDefault, colDefault, 1}
|
|
|
|
ColPrompt = ColorPair{colDefault, colDefault, 2}
|
|
|
|
ColMatch = ColorPair{colDefault, colDefault, 3}
|
|
|
|
ColCurrent = ColorPair{colDefault, colDefault, 4}
|
|
|
|
ColCurrentMatch = ColorPair{colDefault, colDefault, 5}
|
|
|
|
ColSpinner = ColorPair{colDefault, colDefault, 6}
|
|
|
|
ColInfo = ColorPair{colDefault, colDefault, 7}
|
|
|
|
ColCursor = ColorPair{colDefault, colDefault, 8}
|
|
|
|
ColSelected = ColorPair{colDefault, colDefault, 9}
|
|
|
|
ColHeader = ColorPair{colDefault, colDefault, 10}
|
|
|
|
ColBorder = ColorPair{colDefault, colDefault, 11}
|
|
|
|
}
|
|
|
|
ColUser = ColorPair{colDefault, colDefault, 12}
|
|
|
|
}
|
|
|
|
|
|
|
|
func attrFor(color ColorPair, attr Attr) Attr {
|
|
|
|
switch color {
|
|
|
|
case ColCurrent:
|
|
|
|
return attr | Reverse
|
|
|
|
case ColMatch:
|
|
|
|
return attr | Underline
|
|
|
|
case ColCurrentMatch:
|
|
|
|
return attr | Underline | Reverse
|
|
|
|
}
|
|
|
|
return attr
|
2016-10-24 00:44:56 +00:00
|
|
|
}
|