Merge pull request #3 from lyricnz/tcell-events

So much good stuff!
pull/232/head
Simon Roberts 3 years ago committed by GitHub
commit 13c391335f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

1
.gitignore vendored

@ -57,6 +57,7 @@ wasm
.appimage_workspace
todo.txt
.vscode
docs/public
deploy_docs.sh

@ -490,8 +490,7 @@ func (ct *Cointop) Run() error {
return err
}
ui.SetFgColor(ct.colorscheme.BaseFg())
ui.SetBgColor(ct.colorscheme.BaseBg())
ui.SetStyle(ct.colorscheme.BaseStyle())
ct.ui = ui
ct.g = ui.GetGocui()
defer ui.Close()

@ -3,10 +3,11 @@ package cointop
import (
"fmt"
"strconv"
"strings"
"sync"
"github.com/cointop-sh/cointop/pkg/gocui"
fcolor "github.com/fatih/color"
"github.com/gdamore/tcell/v2"
"github.com/tomnomnom/xtermcolor"
)
@ -50,19 +51,38 @@ var BgColorschemeColorsMap = map[string]fcolor.Attribute{
"yellow": fcolor.BgYellow,
}
var GocuiColorschemeColorsMap = map[string]gocui.Attribute{
"black": gocui.ColorBlack,
"blue": gocui.ColorBlue,
"cyan": gocui.ColorCyan,
"green": gocui.ColorGreen,
"magenta": gocui.ColorMagenta,
"red": gocui.ColorRed,
"white": gocui.ColorWhite,
"yellow": gocui.ColorYellow,
// See more: vendor/github.com/mattn/go-colorable/colorable_windows.go:905
// any new color for the below mapping should be compatible with this above list
var TcellColorschemeColorsMap = map[string]tcell.Color{
"black": tcell.ColorBlack,
"blue": tcell.ColorNavy,
"cyan": tcell.ColorTeal,
"green": tcell.ColorGreen,
"magenta": tcell.ColorPurple,
"red": tcell.ColorMaroon,
"white": tcell.ColorSilver,
"yellow": tcell.ColorOlive,
}
// NewColorscheme ...
func NewColorscheme(colors ColorschemeColors) *Colorscheme {
// Build lookup table for defined values, then replace references to these
const prefix = "define_"
const reference = "$"
defines := ColorschemeColors{}
for k, v := range colors {
if strings.HasPrefix(k, prefix) {
defines[k[len(prefix):]] = v
}
}
for k, v := range colors {
if vs, ok := v.(string); ok {
if strings.HasPrefix(vs, reference) {
colors[k] = defines[vs[len(reference):]]
}
}
}
return &Colorscheme{
colors: colors,
cache: make(ColorCache),
@ -70,14 +90,8 @@ func NewColorscheme(colors ColorschemeColors) *Colorscheme {
}
}
// BaseFg ...
func (c *Colorscheme) BaseFg() gocui.Attribute {
return c.GocuiFgColor("base")
}
// BaseBg ...
func (c *Colorscheme) BaseBg() gocui.Attribute {
return c.GocuiBgColor("base")
func (c *Colorscheme) BaseStyle() tcell.Style {
return c.Style("base")
}
// Chart ...
@ -245,17 +259,44 @@ func (c *Colorscheme) ToSprintf(name string) ISprintf {
return cached
}
// TODO: use c.Style(name)?
var attrs []fcolor.Attribute
if v, ok := c.colors[name+"_fg"].(string); ok {
if fg, ok := c.ToFgAttr(v); ok {
attrs = append(attrs, fg)
} else {
color := tcell.GetColor(v)
if color != tcell.ColorDefault {
// 24-bit foreground 38;2;⟨r⟩;⟨g⟩;⟨b⟩
r, g, b := color.RGB()
attrs = append(attrs, 38)
attrs = append(attrs, 2)
attrs = append(attrs, fcolor.Attribute(r))
attrs = append(attrs, fcolor.Attribute(g))
attrs = append(attrs, fcolor.Attribute(b))
// log.Debugf("XXX added FG color %s", attrs)
}
}
}
if v, ok := c.colors[name+"_bg"].(string); ok {
if bg, ok := c.ToBgAttr(v); ok {
attrs = append(attrs, bg)
} else {
color := tcell.GetColor(v)
if color != tcell.ColorDefault {
// 24-bit background 48;2;⟨r⟩;⟨g⟩;⟨b⟩
r, g, b := color.RGB()
attrs = append(attrs, 48)
attrs = append(attrs, 2)
attrs = append(attrs, fcolor.Attribute(r))
attrs = append(attrs, fcolor.Attribute(g))
attrs = append(attrs, fcolor.Attribute(b))
// log.Debugf("XXX added BG color %s", attrs)
}
}
}
if v, ok := c.colors[name+"_bold"].(bool); ok {
if bold, ok := c.ToBoldAttr(v); ok {
attrs = append(attrs, bold)
@ -275,42 +316,47 @@ func (c *Colorscheme) Color(name string, a ...interface{}) string {
return c.ToSprintf(name)(a...)
}
func (c *Colorscheme) GocuiFgColor(name string) gocui.Attribute {
var attrs []gocui.Attribute
if v, ok := c.colors[name+"_fg"].(string); ok {
if fg, ok := c.ToGocuiAttr(v); ok {
attrs = append(attrs, fg)
}
}
func (c *Colorscheme) Style(name string) tcell.Style {
st := tcell.StyleDefault
st = st.Foreground(c.tcellColor(name + "_fg"))
st = st.Background(c.tcellColor(name + "_bg"))
if v, ok := c.colors[name+"_bold"].(bool); ok {
if v {
attrs = append(attrs, gocui.AttrBold)
}
st = st.Bold(v)
}
if v, ok := c.colors[name+"_underline"].(bool); ok {
if v {
attrs = append(attrs, gocui.AttrUnderline)
}
st = st.Underline(v)
}
if len(attrs) > 0 {
var combined gocui.Attribute
for _, v := range attrs {
combined = combined ^ v
}
return combined
// TODO: Blink Dim Italic Reverse Strikethrough
return st
}
// tcellColor can supply for types of color name: specific mapped name, tcell color name, hex
// Examples: black, honeydew, #000000
func (c *Colorscheme) tcellColor(name string) tcell.Color {
v, ok := c.colors[name].(string)
if !ok {
// log.Debugf("XXX tcellColor(%s) could not be found!", name)
return tcell.ColorDefault
}
return gocui.ColorDefault
}
if color, found := TcellColorschemeColorsMap[v]; found {
// log.Debugf("XXX tcellColor(%s => %s) FOUND %s", name, v, color)
return color
}
func (c *Colorscheme) GocuiBgColor(name string) gocui.Attribute {
if v, ok := c.colors[name+"_bg"].(string); ok {
if bg, ok := c.ToGocuiAttr(v); ok {
return bg
}
color := tcell.GetColor(v)
if color != tcell.ColorDefault {
// log.Debugf("XXX tcellColor(%s => %s) GET %s", name, v, color)
return color
}
return gocui.ColorDefault
// find closest X11 color to RGB
// if code, ok := HexToAnsi(v); ok {
// // log.Debugf("XXX tcellColor(%s => %s) HEX %s", name, v, code)
// return tcell.PaletteColor(int(code) & 0xff)
// }
// log.Debugf("XXX tcellColor(%s => %s) FALLTHROUGH %s", name, v, color)
return color
}
func (c *Colorscheme) ToFgAttr(v string) (fcolor.Attribute, bool) {
@ -318,9 +364,10 @@ func (c *Colorscheme) ToFgAttr(v string) (fcolor.Attribute, bool) {
return attr, true
}
if code, ok := HexToAnsi(v); ok {
return fcolor.Attribute(code), true
}
// find closest X11 color to RGB
// if code, ok := HexToAnsi(v); ok {
// return fcolor.Attribute(code), true
// }
return 0, false
}
@ -330,9 +377,10 @@ func (c *Colorscheme) ToBgAttr(v string) (fcolor.Attribute, bool) {
return attr, true
}
if code, ok := HexToAnsi(v); ok {
return fcolor.Attribute(code), true
}
// find closest X11 color to RGB
// if code, ok := HexToAnsi(v); ok {
// return fcolor.Attribute(code), true
// }
return 0, false
}
@ -347,19 +395,6 @@ func (c *Colorscheme) ToUnderlineAttr(v bool) (fcolor.Attribute, bool) {
return fcolor.Underline, v
}
// ToGocuiAttr converts a color string name to a gocui Attribute type
func (c *Colorscheme) ToGocuiAttr(v string) (gocui.Attribute, bool) {
if attr, ok := GocuiColorschemeColorsMap[v]; ok {
return attr, true
}
if code, ok := HexToAnsi(v); ok {
return gocui.Attribute(code), true
}
return 0, false
}
// HexToAnsi converts a hex color string to a uint8 ansi code
func HexToAnsi(h string) (uint8, bool) {
if h == "" {
@ -373,6 +408,7 @@ func HexToAnsi(h string) (uint8, bool) {
}
}
// TODO: only use if exact, otherwise use 24-bit version
code, err := xtermcolor.FromHexStr(h)
if err != nil {
return 0, false
@ -380,5 +416,3 @@ func HexToAnsi(h string) (uint8, bool) {
return code, true
}
// gocui can use xterm colors

@ -7,7 +7,8 @@ import (
"sort"
"strings"
"github.com/cointop-sh/cointop/pkg/color"
fcolor "github.com/fatih/color"
"github.com/cointop-sh/cointop/pkg/pad"
"github.com/mattn/go-runewidth"
log "github.com/sirupsen/logrus"
@ -177,9 +178,10 @@ func (ct *Cointop) UpdateConvertMenu() error {
}
shortcut := string(alphanumericcharacters[i])
if key == ct.State.currencyConversion {
shortcut = ct.colorscheme.MenuLabelActive(color.Bold("*"))
key = ct.colorscheme.Menu(color.Bold(key))
currency = ct.colorscheme.MenuLabelActive(color.Bold(currency))
Bold := fcolor.New(fcolor.Bold).SprintFunc()
shortcut = ct.colorscheme.MenuLabelActive(Bold("*"))
key = ct.colorscheme.Menu(Bold(key))
currency = ct.colorscheme.MenuLabelActive(Bold(currency))
} else {
key = ct.colorscheme.Menu(key)
currency = ct.colorscheme.MenuLabel(currency)
@ -303,7 +305,7 @@ func CurrencySymbol(currency string) string {
return "?"
}
// StatusbarMouseLeftClick is called on mouse left click event
// ConversionMouseLeftClick is called on mouse left click event
func (ct *Cointop) ConversionMouseLeftClick() error {
v, x, y, err := ct.g.GetViewRelativeMousePosition(ct.g.CurrentEvent)
if err != nil {

@ -2,173 +2,101 @@ package cointop
import (
"strings"
"unicode"
"github.com/cointop-sh/cointop/pkg/gocui"
"github.com/gdamore/tcell/v2"
log "github.com/sirupsen/logrus"
)
// keyMap translates key alternative names to a canonical version
func keyMap(k string) string {
key := k
switch strings.ToLower(k) {
case "lsqrbracket", "leftsqrbracket", "leftsquarebracket":
key = "["
case "rsqrbracket", "rightsqrbracket", "rightsquarebracket":
key = "]"
case "space", "spacebar":
key = " " // with meta should be "space"
case "\\\\", "backslash":
key = "\\"
case "underscore":
key = "_"
case "arrowup", "uparrow":
key = "Up"
case "arrowdown", "downarrow":
key = "Down"
case "arrowleft", "leftarrow":
key = "Left"
case "arrowright", "rightarrow":
key = "Right"
case "return":
key = "Enter"
case "escape":
key = "Esc"
case "pageup":
key = "PgUp"
case "pagedown", "pgdown":
key = "PgDn"
}
return key
}
// ParseKeys returns string keyboard key as gocui key type
func (ct *Cointop) ParseKeys(s string) (interface{}, gocui.Modifier) {
func (ct *Cointop) ParseKeys(s string) (interface{}, tcell.ModMask) {
// TODO: change file convention to match tcell (no aliases, dash between mod and key)
// TODO: change to return EventKey?
var key interface{}
mod := gocui.ModNone
split := strings.Split(s, "+")
mod := tcell.ModNone
// translate legacy and special names for keys
keyName := strings.TrimSpace(strings.Replace(s, "+", "-", -1))
split := strings.Split(keyName, "-")
if len(split) > 1 {
m := strings.ToLower(strings.TrimSpace(split[0]))
k := strings.ToLower(strings.TrimSpace(split[1]))
k := strings.TrimSpace(split[1])
k = keyMap(k)
if k == " " {
k = "Space" // fix mod+space
}
if m == "alt" {
mod = gocui.ModAlt
s = k
mod = tcell.ModAlt
keyName = k
} else if m == "ctrl" {
switch k {
case "0":
key = '0'
case "1":
key = '1'
case "2":
key = gocui.KeyCtrl2
case "3":
key = gocui.KeyCtrl3
case "4":
key = gocui.KeyCtrl4
case "5":
key = gocui.KeyCtrl5
case "6":
key = gocui.KeyCtrl6
case "7":
key = gocui.KeyCtrl7
case "8":
key = gocui.KeyCtrl8
case "9":
key = '9'
case "a":
key = gocui.KeyCtrlA
case "b":
key = gocui.KeyCtrlB
case "c":
key = gocui.KeyCtrlC
case "d":
key = gocui.KeyCtrlD
case "e":
key = gocui.KeyCtrlE
case "f":
key = gocui.KeyCtrlF
case "g":
key = gocui.KeyCtrlG
case "h":
key = gocui.KeyCtrlH
case "i":
key = gocui.KeyCtrlI
case "j":
key = gocui.KeyCtrlJ
case "k":
key = gocui.KeyCtrlK
case "l":
key = gocui.KeyCtrlL
case "m":
key = gocui.KeyCtrlL
case "n":
key = gocui.KeyCtrlN
case "o":
key = gocui.KeyCtrlO
case "p":
key = gocui.KeyCtrlP
case "q":
key = gocui.KeyCtrlQ
case "r":
key = gocui.KeyCtrlR
case "s":
key = gocui.KeyCtrlS
case "t":
key = gocui.KeyCtrlT
case "u":
key = gocui.KeyCtrlU
case "v":
key = gocui.KeyCtrlV
case "w":
key = gocui.KeyCtrlW
case "x":
key = gocui.KeyCtrlX
case "y":
key = gocui.KeyCtrlY
case "z":
key = gocui.KeyCtrlZ
case "~":
key = gocui.KeyCtrlTilde
case "[", "lsqrbracket", "leftsqrbracket", "leftsquarebracket":
key = gocui.KeyCtrlLsqBracket
case "]", "rsqrbracket", "rightsqrbracket", "rightsquarebracket":
key = gocui.KeyCtrlRsqBracket
case "space":
key = gocui.KeyCtrlSpace
case "backslash":
key = gocui.KeyCtrlBackslash
case "underscore":
key = gocui.KeyCtrlUnderscore
case "\\\\":
key = '\\'
// let the lookup handle it
keyName = m + "-" + k
} else {
keyName = m + "-" + k
}
// TODO: other mods?
} else {
keyName = keyMap(keyName)
}
// First try looking up keyname directly
lcKeyName := strings.ToLower(keyName)
for key, name := range tcell.KeyNames {
if strings.ToLower(name) == lcKeyName {
if strings.HasPrefix(name, "Ctrl-") {
mod = tcell.ModCtrl
}
return key, mod
}
}
if len(s) == 1 {
r := []rune(s)
// Then try one-rune variants
if len(keyName) == 1 {
r := []rune(keyName)
key = r[0]
return key, mod
}
s = strings.ToLower(s)
switch s {
case "arrowup", "uparrow", "up":
key = gocui.KeyArrowUp
case "arrowdown", "downarrow", "down":
key = gocui.KeyArrowDown
case "arrowleft", "leftarrow", "left":
key = gocui.KeyArrowLeft
case "arrowright", "rightarrow", "right":
key = gocui.KeyArrowRight
case "enter", "return":
key = gocui.KeyEnter
case "space", "spacebar":
key = gocui.KeySpace
case "esc", "escape":
key = gocui.KeyEsc
case "f1":
key = gocui.KeyF1
case "f2":
key = gocui.KeyF2
case "f3":
key = gocui.KeyF3
case "f4":
key = gocui.KeyF4
case "f5":
key = gocui.KeyF5
case "f6":
key = gocui.KeyF6
case "f7":
key = gocui.KeyF7
case "f8":
key = gocui.KeyF8
case "f9":
key = gocui.KeyF9
case "tab":
key = gocui.KeyTab
case "pageup", "pgup":
key = gocui.KeyPgup
case "pagedown", "pgdown", "pgdn":
key = gocui.KeyPgdn
case "home":
key = gocui.KeyHome
case "end":
key = gocui.KeyEnd
case "\\\\":
key = '\\'
case "scrollup":
key = gocui.MouseWheelUp
case "scrolldown":
key = gocui.MouseWheelDown
if key == nil {
log.Debugf("Could not map key descriptio '%s' to key", s)
}
return key, mod
}
@ -341,6 +269,13 @@ func (ct *Cointop) SetKeybindingAction(shortcutKey string, action string) error
}
ct.SetKeybindingMod(key, mod, fn, view)
// Bind `shift+key` for uppercased character
r, isRune := key.(rune)
if isRune && unicode.IsUpper(r) {
ct.SetKeybindingMod(key, tcell.ModShift, fn, view)
}
return nil
}
@ -353,47 +288,51 @@ func (ct *Cointop) SetKeybindings() error {
}
// keys to force quit
ct.SetKeybindingMod(gocui.KeyCtrlC, gocui.ModNone, ct.Keyfn(ct.Quit), "")
ct.SetKeybindingMod(gocui.KeyCtrlZ, gocui.ModNone, ct.Keyfn(ct.Quit), "")
ct.SetKeybindingMod(tcell.KeyCtrlC, tcell.ModNone, ct.Keyfn(ct.Quit), "")
ct.SetKeybindingMod(tcell.KeyCtrlZ, tcell.ModNone, ct.Keyfn(ct.Quit), "")
// searchfield keys
ct.SetKeybindingMod(gocui.KeyEnter, gocui.ModNone, ct.Keyfn(ct.DoSearch), ct.Views.SearchField.Name())
ct.SetKeybindingMod(gocui.KeyEsc, gocui.ModNone, ct.Keyfn(ct.CancelSearch), ct.Views.SearchField.Name())
ct.SetKeybindingMod(tcell.KeyEnter, tcell.ModNone, ct.Keyfn(ct.DoSearch), ct.Views.SearchField.Name())
ct.SetKeybindingMod(tcell.KeyEsc, tcell.ModNone, ct.Keyfn(ct.CancelSearch), ct.Views.SearchField.Name())
// keys to quit help when open
ct.SetKeybindingMod(gocui.KeyEsc, gocui.ModNone, ct.Keyfn(ct.HideHelp), ct.Views.Menu.Name())
ct.SetKeybindingMod('q', gocui.ModNone, ct.Keyfn(ct.HideHelp), ct.Views.Menu.Name())
ct.SetKeybindingMod(tcell.KeyEsc, tcell.ModNone, ct.Keyfn(ct.HideHelp), ct.Views.Menu.Name())
ct.SetKeybindingMod('q', tcell.ModNone, ct.Keyfn(ct.HideHelp), ct.Views.Menu.Name())
// keys to quit portfolio update menu when open
ct.SetKeybindingMod(gocui.KeyEsc, gocui.ModNone, ct.Keyfn(ct.HidePortfolioUpdateMenu), ct.Views.Input.Name())
ct.SetKeybindingMod('q', gocui.ModNone, ct.Keyfn(ct.HidePortfolioUpdateMenu), ct.Views.Input.Name())
ct.SetKeybindingMod(tcell.KeyEsc, tcell.ModNone, ct.Keyfn(ct.HidePortfolioUpdateMenu), ct.Views.Input.Name())
ct.SetKeybindingMod('q', tcell.ModNone, ct.Keyfn(ct.HidePortfolioUpdateMenu), ct.Views.Input.Name())
// keys to quit convert menu when open
ct.SetKeybindingMod(gocui.KeyEsc, gocui.ModNone, ct.Keyfn(ct.HideConvertMenu), ct.Views.Menu.Name())
ct.SetKeybindingMod('q', gocui.ModNone, ct.Keyfn(ct.HideConvertMenu), ct.Views.Menu.Name())
ct.SetKeybindingMod(tcell.KeyEsc, tcell.ModNone, ct.Keyfn(ct.HideConvertMenu), ct.Views.Menu.Name())
ct.SetKeybindingMod('q', tcell.ModNone, ct.Keyfn(ct.HideConvertMenu), ct.Views.Menu.Name())
// keys to update portfolio holdings
ct.SetKeybindingMod(gocui.KeyEnter, gocui.ModNone, ct.Keyfn(ct.EnterKeyPressHandler), ct.Views.Input.Name())
ct.SetKeybindingMod(tcell.KeyEnter, tcell.ModNone, ct.Keyfn(ct.EnterKeyPressHandler), ct.Views.Input.Name())
key, mod := ct.ParseKeys("/")
ct.DeleteKeybindingMod(key, mod, "")
// TODO: FIXME
// key, mod := ct.ParseKeys("/")
// ct.DeleteKeybindingMod(key, mod, "")
// mouse events
ct.SetKeybindingMod(gocui.MouseLeft, gocui.ModNone, ct.Keyfn(ct.MouseLeftClick), ct.Views.Table.Name()) // click to focus
ct.SetMousebindingMod(tcell.Button1, tcell.ModNone, ct.Keyfn(ct.MouseLeftClick), ct.Views.Table.Name()) // click to focus
// clicking table headers sorts table
ct.SetKeybindingMod(gocui.MouseLeft, gocui.ModNone, ct.Keyfn(ct.TableHeaderMouseLeftClick), ct.Views.TableHeader.Name())
ct.SetKeybindingMod(gocui.MouseLeft, gocui.ModNone, ct.Keyfn(ct.StatusbarMouseLeftClick), ct.Views.Statusbar.Name())
ct.SetMousebindingMod(tcell.Button1, tcell.ModNone, ct.Keyfn(ct.TableHeaderMouseLeftClick), ct.Views.TableHeader.Name())
ct.SetMousebindingMod(tcell.Button1, tcell.ModNone, ct.Keyfn(ct.StatusbarMouseLeftClick), ct.Views.Statusbar.Name())
// debug mouse clicks
ct.SetKeybindingMod(gocui.MouseLeft, gocui.ModNone, ct.Keyfn(ct.MouseDebug), "")
ct.SetMousebindingMod(tcell.Button1, tcell.ModNone, ct.Keyfn(ct.MouseDebug), "")
ct.SetMousebindingMod(tcell.WheelUp, tcell.ModNone, ct.Keyfn(ct.CursorUpOrPreviousPage), ct.Views.Table.Name())
ct.SetMousebindingMod(tcell.WheelDown, tcell.ModNone, ct.Keyfn(ct.CursorDownOrNextPage), ct.Views.Table.Name())
// character key press to select option
// TODO: use scrolling table
keys := ct.SortedSupportedCurrencyConversions()
for i, k := range keys {
ct.SetKeybindingMod(alphanumericcharacters[i], gocui.ModNone, ct.Keyfn(ct.SetCurrencyConverstionFn(k)), ct.Views.Menu.Name())
ct.SetKeybindingMod(alphanumericcharacters[i], tcell.ModNone, ct.Keyfn(ct.SetCurrencyConverstionFn(k)), ct.Views.Menu.Name())
}
ct.SetKeybindingMod(gocui.MouseLeft, gocui.ModNone, ct.Keyfn(ct.ConversionMouseLeftClick), ct.Views.Menu.Name())
ct.SetMousebindingMod(tcell.Button1, tcell.ModNone, ct.Keyfn(ct.ConversionMouseLeftClick), ct.Views.Menu.Name())
return nil
}
@ -409,17 +348,23 @@ func (ct *Cointop) MouseDebug() error {
}
// SetKeybindingMod sets the keybinding modifier key
func (ct *Cointop) SetKeybindingMod(key interface{}, mod gocui.Modifier, callback func(g *gocui.Gui, v *gocui.View) error, view string) error {
func (ct *Cointop) SetKeybindingMod(key interface{}, mod tcell.ModMask, callback func(g *gocui.Gui, v *gocui.View) error, view string) error {
// TODO: take EventKey?
var err error
switch t := key.(type) {
case gocui.Key:
err = ct.g.SetKeybinding(view, t, mod, callback)
case tcell.Key:
err = ct.g.SetKeybinding(view, t, 0, mod, callback)
case rune:
err = ct.g.SetKeybinding(view, t, mod, callback)
err = ct.g.SetKeybinding(view, tcell.KeyRune, t, mod, callback)
}
return err
}
// SetMousebindingMod adds a binding for a mouse eventdef
func (ct *Cointop) SetMousebindingMod(btn tcell.ButtonMask, mod tcell.ModMask, callback func(g *gocui.Gui, v *gocui.View) error, view string) error {
return ct.g.SetMousebinding(view, btn, mod, callback)
}
// DeleteKeybinding ...
func (ct *Cointop) DeleteKeybinding(shortcutKey string) error {
key, mod := ct.ParseKeys(shortcutKey)
@ -427,13 +372,14 @@ func (ct *Cointop) DeleteKeybinding(shortcutKey string) error {
}
// DeleteKeybindingMod ...
func (ct *Cointop) DeleteKeybindingMod(key interface{}, mod gocui.Modifier, view string) error {
func (ct *Cointop) DeleteKeybindingMod(key interface{}, mod tcell.ModMask, view string) error {
// TODO: take EventKey
var err error
switch t := key.(type) {
case gocui.Key:
err = ct.g.DeleteKeybinding(view, t, mod)
case tcell.Key:
err = ct.g.DeleteKeybinding(view, t, 0, mod)
case rune:
err = ct.g.DeleteKeybinding(view, t, mod)
err = ct.g.DeleteKeybinding(view, tcell.KeyRune, t, mod)
}
return err
}

@ -58,8 +58,7 @@ func (ct *Cointop) layout() error {
} else {
if err := ct.ui.SetView(ct.Views.Marketbar, 0, topOffset-1, maxX, marketbarHeight+1); err != nil {
ct.Views.Marketbar.SetFrame(false)
ct.Views.Marketbar.SetFgColor(ct.colorscheme.GocuiFgColor(ct.Views.Marketbar.Name()))
ct.Views.Marketbar.SetBgColor(ct.colorscheme.GocuiBgColor(ct.Views.Marketbar.Name()))
ct.Views.Marketbar.SetStyle(ct.colorscheme.Style(ct.Views.Marketbar.Name()))
go func() {
ct.UpdateMarketbar()
_, found := ct.cache.Get(ct.Views.Marketbar.Name())
@ -92,8 +91,7 @@ func (ct *Cointop) layout() error {
if err := ct.ui.SetView(ct.Views.Chart, 0, chartTopOffset, maxX, topOffset+chartHeight); err != nil {
ct.Views.Chart.Clear()
ct.Views.Chart.SetFrame(false)
ct.Views.Chart.SetFgColor(ct.colorscheme.GocuiFgColor(ct.Views.Chart.Name()))
ct.Views.Chart.SetBgColor(ct.colorscheme.GocuiBgColor(ct.Views.Chart.Name()))
ct.Views.Chart.SetStyle(ct.colorscheme.Style(ct.Views.Chart.Name()))
go func() {
ct.UpdateChart()
cachekey := ct.CompositeCacheKey("globaldata", "", "", ct.State.selectedChartRange)
@ -124,8 +122,7 @@ func (ct *Cointop) layout() error {
topOffset = topOffset + chartHeight
if err := ct.ui.SetView(ct.Views.TableHeader, tableOffsetX, topOffset-1, maxX, topOffset+1); err != nil {
ct.Views.TableHeader.SetFrame(false)
ct.Views.TableHeader.SetFgColor(ct.colorscheme.GocuiFgColor(ct.Views.TableHeader.Name()))
ct.Views.TableHeader.SetBgColor(ct.colorscheme.GocuiBgColor(ct.Views.TableHeader.Name()))
ct.Views.TableHeader.SetStyle(ct.colorscheme.Style(ct.Views.TableHeader.Name()))
go ct.UpdateTableHeader()
}
@ -133,8 +130,7 @@ func (ct *Cointop) layout() error {
if err := ct.ui.SetView(ct.Views.Table, tableOffsetX, topOffset-1, maxX, maxY-statusbarHeight); err != nil {
ct.Views.Table.SetFrame(false)
ct.Views.Table.SetHighlight(true)
ct.Views.Table.SetSelFgColor(ct.colorscheme.GocuiFgColor("table_row_active"))
ct.Views.Table.SetSelBgColor(ct.colorscheme.GocuiBgColor("table_row_active"))
ct.Views.Table.SetSelStyle(ct.colorscheme.Style("table_row_active"))
_, found := ct.cache.Get("allCoinsSlugMap")
if found {
ct.cache.Delete("allCoinsSlugMap")
@ -149,8 +145,7 @@ func (ct *Cointop) layout() error {
if !ct.State.hideStatusbar {
if err := ct.ui.SetView(ct.Views.Statusbar, 0, maxY-statusbarHeight-1, maxX, maxY); err != nil {
ct.Views.Statusbar.SetFrame(false)
ct.Views.Statusbar.SetFgColor(ct.colorscheme.GocuiFgColor(ct.Views.Statusbar.Name()))
ct.Views.Statusbar.SetBgColor(ct.colorscheme.GocuiBgColor(ct.Views.Statusbar.Name()))
ct.Views.Statusbar.SetStyle(ct.colorscheme.Style(ct.Views.Statusbar.Name()))
go ct.UpdateStatusbar("")
}
} else {
@ -166,22 +161,19 @@ func (ct *Cointop) layout() error {
ct.Views.SearchField.SetEditable(true)
ct.Views.SearchField.SetWrap(true)
ct.Views.SearchField.SetFrame(false)
ct.Views.SearchField.SetFgColor(ct.colorscheme.GocuiFgColor("searchbar"))
ct.Views.SearchField.SetBgColor(ct.colorscheme.GocuiBgColor("searchbar"))
ct.Views.SearchField.SetStyle(ct.colorscheme.Style("searchbar"))
}
if err := ct.ui.SetView(ct.Views.Menu, 1, 1, maxX-1, maxY-1); err != nil {
ct.Views.Menu.SetFrame(false)
ct.Views.Menu.SetFgColor(ct.colorscheme.GocuiFgColor("menu"))
ct.Views.Menu.SetBgColor(ct.colorscheme.GocuiBgColor("menu"))
ct.Views.Menu.SetStyle(ct.colorscheme.Style("menu"))
}
if err := ct.ui.SetView(ct.Views.Input, 3, 6, 30, 8); err != nil {
ct.Views.Input.SetFrame(true)
ct.Views.Input.SetEditable(true)
ct.Views.Input.SetWrap(true)
ct.Views.Input.SetFgColor(ct.colorscheme.GocuiFgColor("menu"))
ct.Views.Input.SetBgColor(ct.colorscheme.GocuiBgColor("menu"))
ct.Views.Input.SetStyle(ct.colorscheme.Style("menu"))
// run only once on init.
// this bit of code should be at the bottom

@ -6,8 +6,9 @@ import (
"strings"
"time"
fcolor "github.com/fatih/color"
"github.com/cointop-sh/cointop/pkg/api/types"
"github.com/cointop-sh/cointop/pkg/color"
"github.com/cointop-sh/cointop/pkg/humanize"
"github.com/cointop-sh/cointop/pkg/pad"
"github.com/cointop-sh/cointop/pkg/ui"
@ -28,7 +29,9 @@ func (ct *Cointop) UpdateMarketbar() error {
maxX := ct.Width()
logo := "cointop"
if ct.colorschemeName == "cointop" {
logo = fmt.Sprintf("%s%s%s%s", color.Green(""), color.Cyan(""), color.Green(""), color.Cyan("cointop"))
Green := fcolor.New(fcolor.FgGreen).SprintFunc()
Cyan := fcolor.New(fcolor.FgCyan).SprintFunc()
logo = fmt.Sprintf("%s%s%s%s", Green(""), Cyan(""), Green(""), Cyan("cointop"))
}
var content string

@ -42,13 +42,18 @@ draft: false
Copy an existing [colorscheme](https://github.com/cointop-sh/colors/blob/master/cointop.toml) to `~/.config/cointop/colors/` and customize the colors. Then run cointop with `--colorscheme <colorscheme>` to use the colorscheme.
## How do I make the background color transparent?
You can use any of the 250-odd X11 colors by name. See https://en.wikipedia.org/wiki/X11_color_names (use lower-case and without spaces). You can also include 24-bit colors by using the #rrggbb hex code.
Change the background color options in the colorscheme file to `default` to use the system default color, eg. `base_bg = "default"`
You can also define values in the colorscheme file, and reference them from throughout the file, using the following syntax:
## Why don't colorschemes support RGB or hex colors?
```toml
define_base03 = "#002b36"
menu_header_fg = "$base03"
```
Some of the cointop underlying rendering libraries don't support true colors. See [issue](https://github.com/nsf/termbox/issues/37).
## How do I make the background color transparent?
Change the background color options in the colorscheme file to `default` to use the system default color, eg. `base_bg = "default"`
## Where is the config file located?

@ -1,39 +0,0 @@
package color
import "github.com/fatih/color"
// Color struct
type Color color.Color
var (
// Bold color
Bold = color.New(color.Bold).SprintFunc()
// Black color
Black = color.New(color.FgBlack).SprintFunc()
// BlackBg color
BlackBg = color.New(color.BgBlack, color.FgWhite).SprintFunc()
// White color
White = color.New(color.FgWhite).SprintFunc()
// WhiteBold bold
WhiteBold = color.New(color.FgWhite, color.Bold).SprintFunc()
// Yellow color
Yellow = color.New(color.FgYellow).SprintFunc()
// YellowBold color
YellowBold = color.New(color.FgYellow, color.Bold).SprintFunc()
// YellowBg color
YellowBg = color.New(color.BgYellow, color.FgBlack).SprintFunc()
// Green color
Green = color.New(color.FgGreen).SprintFunc()
// GreenBg color
GreenBg = color.New(color.BgGreen, color.FgBlack).SprintFunc()
// Red color
Red = color.New(color.FgRed).SprintFunc()
// Cyan color
Cyan = color.New(color.FgCyan).SprintFunc()
// CyanBg color
CyanBg = color.New(color.BgCyan, color.FgBlack).SprintFunc()
// Blue color
Blue = color.New(color.FgBlue).SprintFunc()
// BlueBg color
BlueBg = color.New(color.BgBlue).SprintFunc()
)

@ -1,32 +0,0 @@
// Copyright 2014 The gocui Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package gocui
import "github.com/cointop-sh/cointop/pkg/termbox"
// Attribute represents a terminal attribute, like color, font style, etc. They
// can be combined using bitwise OR (|). Note that it is not possible to
// combine multiple color attributes.
type Attribute termbox.Attribute
// Color attributes.
const (
ColorDefault Attribute = Attribute(termbox.ColorDefault)
ColorBlack = Attribute(termbox.ColorBlack)
ColorRed = Attribute(termbox.ColorRed)
ColorGreen = Attribute(termbox.ColorGreen)
ColorYellow = Attribute(termbox.ColorYellow)
ColorBlue = Attribute(termbox.ColorBlue)
ColorMagenta = Attribute(termbox.ColorMagenta)
ColorCyan = Attribute(termbox.ColorCyan)
ColorWhite = Attribute(termbox.ColorWhite)
)
// Text style attributes.
const (
AttrBold Attribute = Attribute(termbox.AttrBold)
AttrUnderline = Attribute(termbox.AttrUnderline)
AttrReverse = Attribute(termbox.AttrReverse)
)

@ -1,118 +0,0 @@
// Copyright 2014 The gocui Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
/*
Package gocui allows to create console user interfaces.
Create a new GUI:
g, err := gocui.NewGui(gocui.OutputNormal)
if err != nil {
// handle error
}
defer g.Close()
// Set GUI managers and key bindings
// ...
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
// handle error
}
Set GUI managers:
g.SetManager(mgr1, mgr2)
Managers are in charge of GUI's layout and can be used to build widgets. On
each iteration of the GUI's main loop, the Layout function of each configured
manager is executed. Managers are used to set-up and update the application's
main views, being possible to freely change them during execution. Also, it is
important to mention that a main loop iteration is executed on each reported
event (key-press, mouse event, window resize, etc).
GUIs are composed by Views, you can think of it as buffers. Views implement the
io.ReadWriter interface, so you can just write to them if you want to modify
their content. The same is valid for reading.
Create and initialize a view with absolute coordinates:
if v, err := g.SetView("viewname", 2, 2, 22, 7); err != nil {
if err != gocui.ErrUnknownView {
// handle error
}
fmt.Fprintln(v, "This is a new view")
// ...
}
Views can also be created using relative coordinates:
maxX, maxY := g.Size()
if v, err := g.SetView("viewname", maxX/2-30, maxY/2, maxX/2+30, maxY/2+2); err != nil {
// ...
}
Configure keybindings:
if err := g.SetKeybinding("viewname", gocui.KeyEnter, gocui.ModNone, fcn); err != nil {
// handle error
}
gocui implements full mouse support that can be enabled with:
g.Mouse = true
Mouse events are handled like any other keybinding:
if err := g.SetKeybinding("viewname", gocui.MouseLeft, gocui.ModNone, fcn); err != nil {
// handle error
}
IMPORTANT: Views can only be created, destroyed or updated in three ways: from
the Layout function within managers, from keybinding callbacks or via
*Gui.Update(). The reason for this is that it allows gocui to be
concurrent-safe. So, if you want to update your GUI from a goroutine, you must
use *Gui.Update(). For example:
g.Update(func(g *gocui.Gui) error {
v, err := g.View("viewname")
if err != nil {
// handle error
}
v.Clear()
fmt.Fprintln(v, "Writing from different goroutines")
return nil
})
By default, gocui provides a basic edition mode. This mode can be extended
and customized creating a new Editor and assigning it to *View.Editor:
type Editor interface {
Edit(v *View, key Key, ch rune, mod Modifier)
}
DefaultEditor can be taken as example to create your own custom Editor:
var DefaultEditor Editor = EditorFunc(simpleEditor)
func simpleEditor(v *View, key Key, ch rune, mod Modifier) {
switch {
case ch != 0 && mod == 0:
v.EditWrite(ch)
case key == KeySpace:
v.EditWrite(' ')
case key == KeyBackspace || key == KeyBackspace2:
v.EditDelete(true)
// ...
}
}
Colored text:
Views allow to add colored text using ANSI colors. For example:
fmt.Fprintln(v, "\x1b[0;31mHello world")
For more information, see the examples in folder "_examples/".
*/
package gocui

@ -4,22 +4,26 @@
package gocui
import "errors"
import (
"errors"
"github.com/gdamore/tcell/v2"
)
const maxInt = int(^uint(0) >> 1)
// Editor interface must be satisfied by gocui editors.
type Editor interface {
Edit(v *View, key Key, ch rune, mod Modifier)
Edit(v *View, key tcell.Key, ch rune, mod tcell.ModMask)
}
// The EditorFunc type is an adapter to allow the use of ordinary functions as
// Editors. If f is a function with the appropriate signature, EditorFunc(f)
// is an Editor object that calls f.
type EditorFunc func(v *View, key Key, ch rune, mod Modifier)
type EditorFunc func(v *View, key tcell.Key, ch rune, mod tcell.ModMask)
// Edit calls f(v, key, ch, mod)
func (f EditorFunc) Edit(v *View, key Key, ch rune, mod Modifier) {
func (f EditorFunc) Edit(v *View, key tcell.Key, ch rune, mod tcell.ModMask) {
f(v, key, ch, mod)
}
@ -27,27 +31,27 @@ func (f EditorFunc) Edit(v *View, key Key, ch rune, mod Modifier) {
var DefaultEditor Editor = EditorFunc(simpleEditor)
// simpleEditor is used as the default gocui editor.
func simpleEditor(v *View, key Key, ch rune, mod Modifier) {
func simpleEditor(v *View, key tcell.Key, ch rune, mod tcell.ModMask) {
switch {
case ch != 0 && mod == 0:
case key == tcell.KeyRune && ch != 0 && mod == 0:
v.EditWrite(ch)
case key == KeySpace:
case key == ' ':
v.EditWrite(' ')
case key == KeyBackspace || key == KeyBackspace2:
case key == tcell.KeyBackspace || key == tcell.KeyBackspace2:
v.EditDelete(true)
case key == KeyDelete:
case key == tcell.KeyDelete:
v.EditDelete(false)
case key == KeyInsert:
case key == tcell.KeyInsert:
v.Overwrite = !v.Overwrite
case key == KeyEnter:
case key == tcell.KeyEnter:
v.EditNewLine()
case key == KeyArrowDown:
case key == tcell.KeyDown:
v.MoveCursor(0, 1, false)
case key == KeyArrowUp:
case key == tcell.KeyUp:
v.MoveCursor(0, -1, false)
case key == KeyArrowLeft:
case key == tcell.KeyLeft:
v.MoveCursor(-1, 0, false)
case key == KeyArrowRight:
case key == tcell.KeyRight:
v.MoveCursor(1, 0, false)
}
}
@ -265,9 +269,8 @@ func (v *View) writeRune(x, y int, ch rune) error {
copy(v.lines[y][x+1:], v.lines[y][x:])
}
v.lines[y][x] = cell{
fgColor: v.FgColor,
bgColor: v.BgColor,
chr: ch,
style: v.Style,
chr: ch,
}
return nil

@ -7,14 +7,16 @@ package gocui
import (
"errors"
"strconv"
"github.com/gdamore/tcell/v2"
)
type escapeInterpreter struct {
state escapeState
curch rune
csiParam []string
curFgColor, curBgColor Attribute
mode OutputMode
state escapeState
curch rune
csiParam []string
curStyle tcell.Style
// mode OutputMode
}
type escapeState int
@ -54,12 +56,11 @@ func (ei *escapeInterpreter) runes() []rune {
// newEscapeInterpreter returns an escapeInterpreter that will be able to parse
// terminal escape sequences.
func newEscapeInterpreter(mode OutputMode) *escapeInterpreter {
func newEscapeInterpreter() *escapeInterpreter {
ei := &escapeInterpreter{
state: stateNone,
curFgColor: ColorDefault,
curBgColor: ColorDefault,
mode: mode,
state: stateNone,
curStyle: tcell.StyleDefault,
// mode: mode,
}
return ei
}
@ -67,8 +68,7 @@ func newEscapeInterpreter(mode OutputMode) *escapeInterpreter {
// reset sets the escapeInterpreter in initial state.
func (ei *escapeInterpreter) reset() {
ei.state = stateNone
ei.curFgColor = ColorDefault
ei.curBgColor = ColorDefault
ei.curStyle = tcell.StyleDefault
ei.csiParam = nil
}
@ -120,12 +120,13 @@ func (ei *escapeInterpreter) parseOne(ch rune) (isEscape bool, err error) {
return true, nil
case ch == 'm':
var err error
switch ei.mode {
case OutputNormal:
err = ei.outputNormal()
case Output256:
err = ei.output256()
}
err = ei.parseEscapeParams()
// switch ei.mode {
// case OutputNormal:
// err = ei.outputNormal()
// case Output256:
// err = ei.output256()
// }
if err != nil {
return false, errCSIParseError
}
@ -140,90 +141,72 @@ func (ei *escapeInterpreter) parseOne(ch rune) (isEscape bool, err error) {
return false, nil
}
// outputNormal provides 8 different colors:
// black, red, green, yellow, blue, magenta, cyan, white
func (ei *escapeInterpreter) outputNormal() error {
for _, param := range ei.csiParam {
p, err := strconv.Atoi(param)
if err != nil {
// parseEscapeParams interprets an escape sequence as a style modifier
// allows you to leverage the 256-colors terminal mode:
// 0x01 - 0x08: the 8 colors as in OutputNormal (black, red, green, yellow, blue, magenta, cyan, white)
// 0x09 - 0x10: Color* | AttrBold
// 0x11 - 0xe8: 216 different colors
// 0xe9 - 0x1ff: 24 different shades of grey
// see https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
// see https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters
// 256-colors: ESC[ 38;5;${ID}m # foreground
// 256-colors: ESC[ 48;5;${ID}m # background
// 24-bit ESC[ 38;2;⟨r⟩;⟨g⟩;⟨b⟩ m Select RGB foreground color
// 24-bit ESC[ 48;2;⟨r⟩;⟨g⟩;⟨b⟩ m Select RGB background color
func (ei *escapeInterpreter) parseEscapeParams() error {
// TODO: cache escape -> Style
// convert params to int
params := make([]int, len(ei.csiParam))
for i, param := range ei.csiParam {
if p, err := strconv.Atoi(param); err == nil {
params[i] = p
} else {
return errCSIParseError
}
}
// consume elements of params until done
pos := 0
for ok := true; ok; ok = pos < len(params) {
p := params[pos]
switch {
case p >= 30 && p <= 37:
ei.curFgColor = Attribute(p - 30 + 1)
ei.curStyle = ei.curStyle.Foreground(tcell.PaletteColor(p - 30))
case p == 39:
ei.curFgColor = ColorDefault
ei.curStyle = ei.curStyle.Foreground(tcell.ColorDefault)
case p >= 40 && p <= 47:
ei.curBgColor = Attribute(p - 40 + 1)
ei.curStyle = ei.curStyle.Background(tcell.PaletteColor(p - 40))
case p == 49:
ei.curBgColor = ColorDefault
ei.curStyle = ei.curStyle.Background(tcell.ColorDefault)
case p == 1:
ei.curFgColor |= AttrBold
ei.curStyle = ei.curStyle.Bold(true)
case p == 4:
ei.curFgColor |= AttrUnderline
ei.curStyle = ei.curStyle.Underline(true)
case p == 7:
ei.curFgColor |= AttrReverse
ei.curStyle = ei.curStyle.Reverse(true)
case p == 0:
ei.curFgColor = ColorDefault
ei.curBgColor = ColorDefault
}
}
return nil
}
// output256 allows you to leverage the 256-colors terminal mode:
// 0x01 - 0x08: the 8 colors as in OutputNormal
// 0x09 - 0x10: Color* | AttrBold
// 0x11 - 0xe8: 216 different colors
// 0xe9 - 0x1ff: 24 different shades of grey
func (ei *escapeInterpreter) output256() error {
if len(ei.csiParam) < 3 {
return ei.outputNormal()
}
mode, err := strconv.Atoi(ei.csiParam[1])
if err != nil {
return errCSIParseError
}
if mode != 5 {
return ei.outputNormal()
}
fgbg, err := strconv.Atoi(ei.csiParam[0])
if err != nil {
return errCSIParseError
}
color, err := strconv.Atoi(ei.csiParam[2])
if err != nil {
return errCSIParseError
}
switch fgbg {
case 38:
ei.curFgColor = Attribute(color + 1)
for _, param := range ei.csiParam[3:] {
p, err := strconv.Atoi(param)
if err != nil {
return errCSIParseError
ei.curStyle = tcell.StyleDefault
case p == 38 || p == 48: // 256-color or 24-bit
// parse mode and additional params to generate a color
mode := params[pos+1] // second param - 2 or 5
var x tcell.Color
if mode == 5 { // 256 color
x = tcell.PaletteColor(params[pos+2] + 1)
pos += 2 // two additional (5+index)
} else if mode == 2 { // 24-bit
x = tcell.NewRGBColor(int32(params[pos+2]), int32(params[pos+3]), int32(params[pos+4]))
pos += 4 // four additional (2+r/g/b)
} else {
return errCSIParseError // invalid mode
}
switch {
case p == 1:
ei.curFgColor |= AttrBold
case p == 4:
ei.curFgColor |= AttrUnderline
case p == 7:
ei.curFgColor |= AttrReverse
if p == 38 {
ei.curStyle = ei.curStyle.Foreground(x)
} else {
ei.curStyle = ei.curStyle.Background(x)
}
}
case 48:
ei.curBgColor = Attribute(color + 1)
default:
return errCSIParseError
}
pos += 1 // move along 1 by default
}
return nil
}

@ -0,0 +1,64 @@
// Copyright 2014 The gocui Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package gocui
import (
"github.com/gdamore/tcell/v2"
)
// eventBinding are used to link a given key-press event with a handler.
type eventBinding struct {
viewName string
ev tcell.Event // ignore the Time
handler func(*Gui, *View) error
}
// newKeybinding returns a new eventBinding object for a key event.
func newKeybinding(viewname string, key tcell.Key, ch rune, mod tcell.ModMask, handler func(*Gui, *View) error) (kb *eventBinding) {
kb = &eventBinding{
viewName: viewname,
ev: tcell.NewEventKey(key, ch, mod),
handler: handler,
}
return kb
}
// newKeybinding returns a new eventBinding object for a mouse event.
func newMouseBinding(viewname string, btn tcell.ButtonMask, mod tcell.ModMask, handler func(*Gui, *View) error) (kb *eventBinding) {
kb = &eventBinding{
viewName: viewname,
ev: tcell.NewEventMouse(0, 0, btn, mod),
handler: handler,
}
return kb
}
func (kb *eventBinding) matchEvent(e tcell.Event) bool {
// TODO: check mask not ==mod?
switch tev := e.(type) {
case *tcell.EventKey:
if kbe, ok := kb.ev.(*tcell.EventKey); ok {
if tev.Key() == tcell.KeyRune {
return tev.Key() == kbe.Key() && tev.Rune() == kbe.Rune() && tev.Modifiers() == kbe.Modifiers()
}
return tev.Key() == kbe.Key() && tev.Modifiers() == kbe.Modifiers()
}
case *tcell.EventMouse:
if kbe, ok := kb.ev.(*tcell.EventMouse); ok {
return kbe.Buttons() == tev.Buttons() && kbe.Modifiers() == tev.Modifiers()
}
}
return false
}
// matchView returns if the eventBinding matches the current view.
func (kb *eventBinding) matchView(v *View) bool {
if kb.viewName == "" {
return true
}
return v != nil && kb.viewName == v.name
}

@ -7,7 +7,7 @@ package gocui
import (
"errors"
"github.com/cointop-sh/cointop/pkg/termbox"
"github.com/gdamore/tcell/v2"
)
var (
@ -19,35 +19,36 @@ var (
)
// OutputMode represents the terminal's output mode (8 or 256 colors).
type OutputMode termbox.OutputMode
// type OutputMode termbox.OutputMode // TODO: die
const (
// OutputNormal provides 8-colors terminal mode.
OutputNormal = OutputMode(termbox.OutputNormal)
// const ( // TODO: die
// // OutputNormal provides 8-colors terminal mode.
// OutputNormal = OutputMode(termbox.OutputNormal)
// Output256 provides 256-colors terminal mode.
Output256 = OutputMode(termbox.Output256)
)
// // Output256 provides 256-colors terminal mode.
// Output256 = OutputMode(termbox.Output256)
// )
// Gui represents the whole User Interface, including the views, layouts
// and keybindings.
// and eventBindings.
type Gui struct {
tbEvents chan termbox.Event
userEvents chan userEvent
views []*View
currentView *View
managers []Manager
keybindings []*keybinding
maxX, maxY int
outputMode OutputMode
tbEvents chan tcell.Event
userEvents chan userEvent
views []*View
currentView *View
managers []Manager
eventBindings []*eventBinding
maxX, maxY int
// outputMode OutputMode // TODO: die
screen tcell.Screen
// BgColor and FgColor allow to configure the background and foreground
// colors of the GUI.
BgColor, FgColor Attribute
Style tcell.Style
// SelBgColor and SelFgColor allow to configure the background and
// foreground colors of the frame of the current view.
SelBgColor, SelFgColor Attribute
SelStyle tcell.Style
// If Highlight is true, Sel{Bg,Fg}Colors will be used to draw the
// frame of the current view.
@ -68,27 +69,34 @@ type Gui struct {
ASCII bool
// The current event while in the handlers.
CurrentEvent *termbox.Event
CurrentEvent tcell.Event
}
// NewGui returns a new Gui object with a given output mode.
func NewGui(mode OutputMode) (*Gui, error) {
if err := termbox.Init(); err != nil {
return nil, err
}
// func NewGui(mode OutputMode) (*Gui, error) {
func NewGui() (*Gui, error) {
g := &Gui{}
g.outputMode = mode
termbox.SetOutputMode(termbox.OutputMode(mode))
// outMode = OutputNormal
if s, e := tcell.NewScreen(); e != nil {
return nil, e
} else if e = s.Init(); e != nil {
return nil, e
} else {
g.screen = s
}
g.tbEvents = make(chan termbox.Event, 20)
// g.outputMode = mode
// termbox.SetScreen(g.Screen) // ugly global
// termbox.SetOutputMode(termbox.OutputMode(mode))
g.tbEvents = make(chan tcell.Event, 20)
g.userEvents = make(chan userEvent, 20)
g.maxX, g.maxY = termbox.Size()
g.maxX, g.maxY = g.screen.Size()
g.BgColor, g.FgColor = ColorDefault, ColorDefault
g.SelBgColor, g.SelFgColor = ColorDefault, ColorDefault
g.Style = tcell.StyleDefault
g.SelStyle = tcell.StyleDefault
return g, nil
}
@ -96,7 +104,7 @@ func NewGui(mode OutputMode) (*Gui, error) {
// Close finalizes the library. It should be called after a successful
// initialization and when gocui is not needed anymore.
func (g *Gui) Close() {
termbox.Close()
g.screen.Fini()
}
// Size returns the terminal's size.
@ -104,14 +112,36 @@ func (g *Gui) Size() (x, y int) {
return g.maxX, g.maxY
}
// temporary kludge for the pretty
func (g *Gui) prettyColor(x, y int, st tcell.Style) tcell.Style {
if true {
w, h := g.screen.Size()
// dark blue gradient background
red := int32(0)
grn := int32(0)
blu := int32(50 * float64(y) / float64(h))
st = st.Background(tcell.NewRGBColor(red, grn, blu))
// two-axis green-blue gradient
red = int32(200)
grn = int32(255 * float64(y) / float64(h))
blu = int32(255 * float64(x) / float64(w))
st = st.Foreground(tcell.NewRGBColor(red, grn, blu))
}
return st
}
// SetRune writes a rune at the given point, relative to the top-left
// corner of the terminal. It checks if the position is valid and applies
// the given colors.
func (g *Gui) SetRune(x, y int, ch rune, fgColor, bgColor Attribute) error {
func (g *Gui) SetRune(x, y int, ch rune, st tcell.Style) error {
if x < 0 || y < 0 || x >= g.maxX || y >= g.maxY {
return errors.New("invalid point")
}
termbox.SetCell(x, y, ch, termbox.Attribute(fgColor), termbox.Attribute(bgColor))
// temporary kludge for the pretty
// st = g.prettyColor(x, y, st)
g.screen.SetContent(x, y, ch, nil, st)
return nil
}
@ -147,9 +177,9 @@ func (g *Gui) SetView(name string, x0, y0, x1, y1 int) (*View, error) {
return v, nil
}
v := newView(name, x0, y0, x1, y1, g.outputMode)
v.BgColor, v.FgColor = g.BgColor, g.FgColor
v.SelBgColor, v.SelFgColor = g.SelBgColor, g.SelFgColor
v := newView(name, x0, y0, x1, y1, g)
v.Style = g.Style
v.SelStyle = g.SelStyle
g.views = append(g.views, v)
return v, ErrUnknownView
}
@ -246,60 +276,84 @@ func (g *Gui) CurrentView() *View {
return g.currentView
}
// SetKeybinding creates a new keybinding. If viewname equals to ""
// (empty string) then the keybinding will apply to all views. key must
// SetKeybinding creates a new eventBinding. If viewname equals to ""
// (empty string) then the eventBinding will apply to all views. key must
// be a rune or a Key.
func (g *Gui) SetKeybinding(viewname string, key interface{}, mod Modifier, handler func(*Gui, *View) error) error {
var kb *keybinding
// TODO: split into key/mouse bindings?
func (g *Gui) SetKeybinding(viewname string, key tcell.Key, ch rune, mod tcell.ModMask, handler func(*Gui, *View) error) error {
// var kb *eventBinding
// k, ch, err := getKey(key)
// if err != nil {
// return err
// }
// TODO: get rid of this ugly mess
//switch key {
//case termbox.MouseLeft:
// kb = newMouseBinding(viewname, tcell.Button1, mod, handler)
//case termbox.MouseMiddle:
// kb = newMouseBinding(viewname, tcell.Button3, mod, handler)
//case termbox.MouseRight:
// kb = newMouseBinding(viewname, tcell.Button2, mod, handler)
//case termbox.MouseWheelUp:
// kb = newMouseBinding(viewname, tcell.WheelUp, mod, handler)
//case termbox.MouseWheelDown:
// kb = newMouseBinding(viewname, tcell.WheelDown, mod, handler)
//default:
// kb = newKeybinding(viewname, key, ch, mod, handler)
//}
kb := newKeybinding(viewname, key, ch, mod, handler)
g.eventBindings = append(g.eventBindings, kb)
return nil
}
k, ch, err := getKey(key)
if err != nil {
return err
}
kb = newKeybinding(viewname, k, ch, mod, handler)
g.keybindings = append(g.keybindings, kb)
func (g *Gui) SetMousebinding(viewname string, btn tcell.ButtonMask, mod tcell.ModMask, handler func(*Gui, *View) error) error {
kb := newMouseBinding(viewname, btn, mod, handler)
g.eventBindings = append(g.eventBindings, kb)
return nil
}
// DeleteKeybinding deletes a keybinding.
func (g *Gui) DeleteKeybinding(viewname string, key interface{}, mod Modifier) error {
k, ch, err := getKey(key)
if err != nil {
return err
}
// DeleteKeybinding deletes a eventBinding.
func (g *Gui) DeleteKeybinding(viewname string, key tcell.Key, ch rune, mod tcell.ModMask) error {
// k, ch, err := getKey(key)
// if err != nil {
// return err
// }
for i, kb := range g.keybindings {
if kb.viewName == viewname && kb.ch == ch && kb.key == k && kb.mod == mod {
g.keybindings = append(g.keybindings[:i], g.keybindings[i+1:]...)
return nil
for i, kb := range g.eventBindings {
if kbe, ok := kb.ev.(*tcell.EventKey); ok {
if kb.viewName == viewname && kbe.Rune() == ch && kbe.Key() == key && kbe.Modifiers() == mod {
g.eventBindings = append(g.eventBindings[:i], g.eventBindings[i+1:]...)
return nil
}
}
}
return errors.New("keybinding not found")
return errors.New("eventBinding not found")
}
// DeleteKeybindings deletes all keybindings of view.
// DeleteKeybindings deletes all eventBindings of view.
func (g *Gui) DeleteKeybindings(viewname string) {
var s []*keybinding
for _, kb := range g.keybindings {
var s []*eventBinding
for _, kb := range g.eventBindings {
if kb.viewName != viewname {
s = append(s, kb)
}
}
g.keybindings = s
g.eventBindings = s
}
// getKey takes an empty interface with a key and returns the corresponding
// typed Key or rune.
func getKey(key interface{}) (Key, rune, error) {
switch t := key.(type) {
case Key:
return t, 0, nil
case rune:
return 0, t, nil
default:
return 0, 0, errors.New("unknown type")
}
}
// func getKey(key interface{}) (tcell.Key, rune, error) {
// switch t := key.(type) {
// case Key:
// return t, 0, nil
// case rune:
// return 0, t, nil
// default:
// return 0, 0, errors.New("unknown type")
// }
// }
// userEvent represents an event triggered by the user.
type userEvent struct {
@ -333,18 +387,18 @@ func (f ManagerFunc) Layout(g *Gui) error {
}
// SetManager sets the given GUI managers. It deletes all views and
// keybindings.
// eventBindings.
func (g *Gui) SetManager(managers ...Manager) {
g.managers = managers
g.currentView = nil
g.views = nil
g.keybindings = nil
g.eventBindings = nil
go func() { g.tbEvents <- termbox.Event{Type: termbox.EventResize} }()
go func() { g.tbEvents <- tcell.NewEventResize(0, 0) }()
}
// SetManagerFunc sets the given manager function. It deletes all views and
// keybindings.
// eventBindings.
func (g *Gui) SetManagerFunc(manager func(*Gui) error) {
g.SetManager(ManagerFunc(manager))
}
@ -354,18 +408,14 @@ func (g *Gui) SetManagerFunc(manager func(*Gui) error) {
func (g *Gui) MainLoop() error {
go func() {
for {
g.tbEvents <- termbox.PollEvent()
g.tbEvents <- g.screen.PollEvent()
}
}()
inputMode := termbox.InputAlt
if g.InputEsc {
inputMode = termbox.InputEsc
}
if g.Mouse {
inputMode |= termbox.InputMouse
g.screen.EnableMouse()
}
termbox.SetInputMode(inputMode)
// s.EnablePaste()
if err := g.flush(); err != nil {
return err
@ -373,7 +423,7 @@ func (g *Gui) MainLoop() error {
for {
select {
case ev := <-g.tbEvents:
if err := g.handleEvent(&ev); err != nil {
if err := g.handleEvent(ev); err != nil {
return err
}
case ev := <-g.userEvents:
@ -395,7 +445,7 @@ func (g *Gui) consumeevents() error {
for {
select {
case ev := <-g.tbEvents:
if err := g.handleEvent(&ev); err != nil {
if err := g.handleEvent(ev); err != nil {
return err
}
case ev := <-g.userEvents:
@ -410,12 +460,12 @@ func (g *Gui) consumeevents() error {
// handleEvent handles an event, based on its type (key-press, error,
// etc.)
func (g *Gui) handleEvent(ev *termbox.Event) error {
switch ev.Type {
case termbox.EventKey, termbox.EventMouse:
return g.onKey(ev)
case termbox.EventError:
return ev.Err
func (g *Gui) handleEvent(ev tcell.Event) error {
switch tev := ev.(type) {
case *tcell.EventMouse, *tcell.EventKey:
return g.onEvent(tev)
case *tcell.EventError:
return errors.New(tev.Error())
default:
return nil
}
@ -423,9 +473,15 @@ func (g *Gui) handleEvent(ev *termbox.Event) error {
// flush updates the gui, re-drawing frames and buffers.
func (g *Gui) flush() error {
termbox.Clear(termbox.Attribute(g.FgColor), termbox.Attribute(g.BgColor))
// termbox.Clear(termbox.Attribute(g.FgColor), termbox.Attribute(g.BgColor))
w, h := g.screen.Size() // TODO: merge with maxX, maxY below
for row := 0; row < h; row++ {
for col := 0; col < w; col++ {
g.screen.SetContent(col, row, ' ', nil, g.Style)
}
}
maxX, maxY := termbox.Size()
maxX, maxY := g.screen.Size()
// if GUI's size has changed, we need to redraw all views
if maxX != g.maxX || maxY != g.maxY {
for _, v := range g.views {
@ -441,23 +497,20 @@ func (g *Gui) flush() error {
}
for _, v := range g.views {
if v.Frame {
var fgColor, bgColor Attribute
// var fgColor, bgColor Attribute
st := g.Style
if g.Highlight && v == g.currentView {
fgColor = g.SelFgColor
bgColor = g.SelBgColor
} else {
fgColor = g.FgColor
bgColor = g.BgColor
st = g.SelStyle
}
if err := g.drawFrameEdges(v, fgColor, bgColor); err != nil {
if err := g.drawFrameEdges(v, st); err != nil {
return err
}
if err := g.drawFrameCorners(v, fgColor, bgColor); err != nil {
if err := g.drawFrameCorners(v, st); err != nil {
return err
}
if v.Title != "" {
if err := g.drawTitle(v, fgColor, bgColor); err != nil {
if err := g.drawTitle(v, st); err != nil {
return err
}
}
@ -466,12 +519,12 @@ func (g *Gui) flush() error {
return err
}
}
termbox.Flush()
g.screen.Show()
return nil
}
// drawFrameEdges draws the horizontal and vertical edges of a view.
func (g *Gui) drawFrameEdges(v *View, fgColor, bgColor Attribute) error {
func (g *Gui) drawFrameEdges(v *View, st tcell.Style) error {
runeH, runeV := '─', '│'
if g.ASCII {
runeH, runeV = '-', '|'
@ -482,12 +535,12 @@ func (g *Gui) drawFrameEdges(v *View, fgColor, bgColor Attribute) error {
continue
}
if v.y0 > -1 && v.y0 < g.maxY {
if err := g.SetRune(x, v.y0, runeH, fgColor, bgColor); err != nil {
if err := g.SetRune(x, v.y0, runeH, st); err != nil {
return err
}
}
if v.y1 > -1 && v.y1 < g.maxY {
if err := g.SetRune(x, v.y1, runeH, fgColor, bgColor); err != nil {
if err := g.SetRune(x, v.y1, runeH, st); err != nil {
return err
}
}
@ -497,12 +550,12 @@ func (g *Gui) drawFrameEdges(v *View, fgColor, bgColor Attribute) error {
continue
}
if v.x0 > -1 && v.x0 < g.maxX {
if err := g.SetRune(v.x0, y, runeV, fgColor, bgColor); err != nil {
if err := g.SetRune(v.x0, y, runeV, st); err != nil {
return err
}
}
if v.x1 > -1 && v.x1 < g.maxX {
if err := g.SetRune(v.x1, y, runeV, fgColor, bgColor); err != nil {
if err := g.SetRune(v.x1, y, runeV, st); err != nil {
return err
}
}
@ -511,7 +564,7 @@ func (g *Gui) drawFrameEdges(v *View, fgColor, bgColor Attribute) error {
}
// drawFrameCorners draws the corners of the view.
func (g *Gui) drawFrameCorners(v *View, fgColor, bgColor Attribute) error {
func (g *Gui) drawFrameCorners(v *View, st tcell.Style) error {
runeTL, runeTR, runeBL, runeBR := '┌', '┐', '└', '┘'
if g.ASCII {
runeTL, runeTR, runeBL, runeBR = '+', '+', '+', '+'
@ -524,7 +577,7 @@ func (g *Gui) drawFrameCorners(v *View, fgColor, bgColor Attribute) error {
for _, c := range corners {
if c.x >= 0 && c.y >= 0 && c.x < g.maxX && c.y < g.maxY {
if err := g.SetRune(c.x, c.y, c.ch, fgColor, bgColor); err != nil {
if err := g.SetRune(c.x, c.y, c.ch, st); err != nil {
return err
}
}
@ -533,7 +586,7 @@ func (g *Gui) drawFrameCorners(v *View, fgColor, bgColor Attribute) error {
}
// drawTitle draws the title of the view.
func (g *Gui) drawTitle(v *View, fgColor, bgColor Attribute) error {
func (g *Gui) drawTitle(v *View, st tcell.Style) error {
if v.y0 < 0 || v.y0 >= g.maxY {
return nil
}
@ -545,7 +598,7 @@ func (g *Gui) drawTitle(v *View, fgColor, bgColor Attribute) error {
} else if x > v.x1-2 || x >= g.maxX {
break
}
if err := g.SetRune(x, v.y0, ch, fgColor, bgColor); err != nil {
if err := g.SetRune(x, v.y0, ch, st); err != nil {
return err
}
}
@ -571,13 +624,13 @@ func (g *Gui) draw(v *View) error {
gMaxX, gMaxY := g.Size()
cx, cy := curview.x0+curview.cx+1, curview.y0+curview.cy+1
if cx >= 0 && cx < gMaxX && cy >= 0 && cy < gMaxY {
termbox.SetCursor(cx, cy)
g.screen.ShowCursor(cx, cy)
} else {
termbox.HideCursor()
g.screen.ShowCursor(-1, -1) // HideCursor
}
}
} else {
termbox.HideCursor()
g.screen.ShowCursor(-1, -1) // HideCursor
}
v.clearRunes()
@ -587,13 +640,13 @@ func (g *Gui) draw(v *View) error {
return nil
}
// onKey manages key-press events. A keybinding handler is called when
// a key-press or mouse event satisfies a configured keybinding. Furthermore,
// onEvent manages key/mouse events. A eventBinding handler is called when
// a key-press or mouse event satisfies a configured eventBinding. Furthermore,
// currentView's internal buffer is modified if currentView.Editable is true.
func (g *Gui) onKey(ev *termbox.Event) error {
switch ev.Type {
case termbox.EventKey:
matched, err := g.execKeybindings(g.currentView, ev)
func (g *Gui) onEvent(ev tcell.Event) error {
switch tev := ev.(type) {
case *tcell.EventKey:
matched, err := g.execEventBindings(g.currentView, ev)
if err != nil {
return err
}
@ -601,33 +654,34 @@ func (g *Gui) onKey(ev *termbox.Event) error {
break
}
if g.currentView != nil && g.currentView.Editable && g.currentView.Editor != nil {
g.currentView.Editor.Edit(g.currentView, Key(ev.Key), ev.Ch, Modifier(ev.Mod))
g.currentView.Editor.Edit(g.currentView, tev.Key(), tev.Rune(), tev.Modifiers())
}
case termbox.EventMouse:
v, _, _, err := g.GetViewRelativeMousePosition(ev)
case *tcell.EventMouse:
v, _, _, err := g.GetViewRelativeMousePosition(tev)
if err != nil {
break
}
// If the key-binding wants to move the cursor, it should call SetCursorFromCurrentMouseEvent()
// Not all mouse events will want to do this (eg: scroll wheel)
g.CurrentEvent = ev
if _, err := g.execKeybindings(v, ev); err != nil {
if _, err := g.execEventBindings(v, g.CurrentEvent); err != nil {
return err
}
}
return nil
}
// GetViewRelativeMousePosition returns the View and relative x/y for the provided mouse event.
func (g *Gui) GetViewRelativeMousePosition(ev *termbox.Event) (*View, int, int, error) {
// TODO: check ev.Type == termbox.EventMouse ?
mx, my := ev.MouseX, ev.MouseY
v, err := g.ViewByPosition(mx, my)
if err != nil {
return nil, 0, 0, err
func (g *Gui) GetViewRelativeMousePosition(ev tcell.Event) (*View, int, int, error) {
if kbe, ok := ev.(*tcell.EventMouse); ok {
mx, my := kbe.Position()
v, err := g.ViewByPosition(mx, my)
if err != nil {
return nil, 0, 0, err
}
return v, mx - v.x0 - 1, my - v.y0 - 1, nil
}
return v, mx - v.x0 - 1, my - v.y0 - 1, nil
return nil, 0, 0, errors.New("Cannot GetViewRelativeMousePosition on non-mouse event")
}
// SetCursorFromCurrentMouseEvent updates the cursor position based on the mouse coordinates.
@ -642,15 +696,16 @@ func (g *Gui) SetCursorFromCurrentMouseEvent() error {
return nil
}
// execKeybindings executes the keybinding handlers that match the passed view
// execEventBindings executes the handlers that match the passed view
// and event. The value of matched is true if there is a match and no errors.
func (g *Gui) execKeybindings(v *View, ev *termbox.Event) (matched bool, err error) {
// TODO: rename to more generic - it's not just keys (incl mouse)
func (g *Gui) execEventBindings(v *View, xev tcell.Event) (matched bool, err error) {
matched = false
for _, kb := range g.keybindings {
for _, kb := range g.eventBindings {
if kb.handler == nil {
continue
}
if kb.matchKeypress(Key(ev.Key), ev.Ch, Modifier(ev.Mod)) && kb.matchView(v) {
if kb.matchEvent(xev) && kb.matchView(v) {
if err := kb.handler(g, v); err != nil {
return false, err
}

@ -1,137 +0,0 @@
// Copyright 2014 The gocui Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package gocui
import "github.com/cointop-sh/cointop/pkg/termbox"
// Keybidings are used to link a given key-press event with a handler.
type keybinding struct {
viewName string
key Key
ch rune
mod Modifier
handler func(*Gui, *View) error
}
// newKeybinding returns a new Keybinding object.
func newKeybinding(viewname string, key Key, ch rune, mod Modifier, handler func(*Gui, *View) error) (kb *keybinding) {
kb = &keybinding{
viewName: viewname,
key: key,
ch: ch,
mod: mod,
handler: handler,
}
return kb
}
// matchKeypress returns if the keybinding matches the keypress.
func (kb *keybinding) matchKeypress(key Key, ch rune, mod Modifier) bool {
return kb.key == key && kb.ch == ch && kb.mod == mod
}
// matchView returns if the keybinding matches the current view.
func (kb *keybinding) matchView(v *View) bool {
if kb.viewName == "" {
return true
}
return v != nil && kb.viewName == v.name
}
// Key represents special keys or keys combinations.
type Key termbox.Key
// Special keys.
const (
KeyF1 Key = Key(termbox.KeyF1)
KeyF2 = Key(termbox.KeyF2)
KeyF3 = Key(termbox.KeyF3)
KeyF4 = Key(termbox.KeyF4)
KeyF5 = Key(termbox.KeyF5)
KeyF6 = Key(termbox.KeyF6)
KeyF7 = Key(termbox.KeyF7)
KeyF8 = Key(termbox.KeyF8)
KeyF9 = Key(termbox.KeyF9)
KeyF10 = Key(termbox.KeyF10)
KeyF11 = Key(termbox.KeyF11)
KeyF12 = Key(termbox.KeyF12)
KeyInsert = Key(termbox.KeyInsert)
KeyDelete = Key(termbox.KeyDelete)
KeyHome = Key(termbox.KeyHome)
KeyEnd = Key(termbox.KeyEnd)
KeyPgup = Key(termbox.KeyPgup)
KeyPgdn = Key(termbox.KeyPgdn)
KeyArrowUp = Key(termbox.KeyArrowUp)
KeyArrowDown = Key(termbox.KeyArrowDown)
KeyArrowLeft = Key(termbox.KeyArrowLeft)
KeyArrowRight = Key(termbox.KeyArrowRight)
MouseLeft = Key(termbox.MouseLeft)
MouseMiddle = Key(termbox.MouseMiddle)
MouseRight = Key(termbox.MouseRight)
MouseRelease = Key(termbox.MouseRelease)
MouseWheelUp = Key(termbox.MouseWheelUp)
MouseWheelDown = Key(termbox.MouseWheelDown)
)
// Keys combinations.
const (
KeyCtrlTilde Key = Key(termbox.KeyCtrlTilde)
KeyCtrl2 = Key(termbox.KeyCtrl2)
KeyCtrlSpace = Key(termbox.KeyCtrlSpace)
KeyCtrlA = Key(termbox.KeyCtrlA)
KeyCtrlB = Key(termbox.KeyCtrlB)
KeyCtrlC = Key(termbox.KeyCtrlC)
KeyCtrlD = Key(termbox.KeyCtrlD)
KeyCtrlE = Key(termbox.KeyCtrlE)
KeyCtrlF = Key(termbox.KeyCtrlF)
KeyCtrlG = Key(termbox.KeyCtrlG)
KeyBackspace = Key(termbox.KeyBackspace)
KeyCtrlH = Key(termbox.KeyCtrlH)
KeyTab = Key(termbox.KeyTab)
KeyCtrlI = Key(termbox.KeyCtrlI)
KeyCtrlJ = Key(termbox.KeyCtrlJ)
KeyCtrlK = Key(termbox.KeyCtrlK)
KeyCtrlL = Key(termbox.KeyCtrlL)
KeyEnter = Key(termbox.KeyEnter)
KeyCtrlM = Key(termbox.KeyCtrlM)
KeyCtrlN = Key(termbox.KeyCtrlN)
KeyCtrlO = Key(termbox.KeyCtrlO)
KeyCtrlP = Key(termbox.KeyCtrlP)
KeyCtrlQ = Key(termbox.KeyCtrlQ)
KeyCtrlR = Key(termbox.KeyCtrlR)
KeyCtrlS = Key(termbox.KeyCtrlS)
KeyCtrlT = Key(termbox.KeyCtrlT)
KeyCtrlU = Key(termbox.KeyCtrlU)
KeyCtrlV = Key(termbox.KeyCtrlV)
KeyCtrlW = Key(termbox.KeyCtrlW)
KeyCtrlX = Key(termbox.KeyCtrlX)
KeyCtrlY = Key(termbox.KeyCtrlY)
KeyCtrlZ = Key(termbox.KeyCtrlZ)
KeyEsc = Key(termbox.KeyEsc)
KeyCtrlLsqBracket = Key(termbox.KeyCtrlLsqBracket)
KeyCtrl3 = Key(termbox.KeyCtrl3)
KeyCtrl4 = Key(termbox.KeyCtrl4)
KeyCtrlBackslash = Key(termbox.KeyCtrlBackslash)
KeyCtrl5 = Key(termbox.KeyCtrl5)
KeyCtrlRsqBracket = Key(termbox.KeyCtrlRsqBracket)
KeyCtrl6 = Key(termbox.KeyCtrl6)
KeyCtrl7 = Key(termbox.KeyCtrl7)
KeyCtrlSlash = Key(termbox.KeyCtrlSlash)
KeyCtrlUnderscore = Key(termbox.KeyCtrlUnderscore)
KeySpace = Key(termbox.KeySpace)
KeyBackspace2 = Key(termbox.KeyBackspace2)
KeyCtrl8 = Key(termbox.KeyCtrl8)
)
// Modifier allows to define special keys combinations. They can be used
// in combination with Keys or Runes when a new keybinding is defined.
type Modifier termbox.Modifier
// Modifiers.
const (
ModNone Modifier = Modifier(0)
ModAlt = Modifier(termbox.ModAlt)
)

@ -10,7 +10,7 @@ import (
"io"
"strings"
"github.com/cointop-sh/cointop/pkg/termbox"
"github.com/gdamore/tcell/v2"
)
// A View is a window. It maintains its own internal buffer and cursor
@ -31,18 +31,18 @@ type View struct {
// BgColor and FgColor allow to configure the background and foreground
// colors of the View.
BgColor, FgColor Attribute
Style tcell.Style
// SelBgColor and SelFgColor are used to configure the background and
// foreground colors of the selected line, when it is highlighted.
SelBgColor, SelFgColor Attribute
SelStyle tcell.Style
// If Editable is true, keystrokes will be added to the view's internal
// buffer at the cursor position.
Editable bool
// Editor allows to define the editor that manages the edition mode,
// including keybindings or cursor behaviour. DefaultEditor is used by
// including eventBindings or cursor behaviour. DefaultEditor is used by
// default.
Editor Editor
@ -71,6 +71,9 @@ type View struct {
// If Mask is true, the View will display the mask instead of the real
// content
Mask rune
// The gui that owns this view
g *Gui
}
type viewLine struct {
@ -79,8 +82,9 @@ type viewLine struct {
}
type cell struct {
chr rune
bgColor, fgColor Attribute
chr rune
// bgColor, fgColor Attribute
style tcell.Style
}
type lineType []cell
@ -95,7 +99,7 @@ func (l lineType) String() string {
}
// newView returns a new View object.
func newView(name string, x0, y0, x1, y1 int, mode OutputMode) *View {
func newView(name string, x0, y0, x1, y1 int, g *Gui) *View {
v := &View{
name: name,
x0: x0,
@ -105,7 +109,8 @@ func newView(name string, x0, y0, x1, y1 int, mode OutputMode) *View {
Frame: true,
Editor: DefaultEditor,
tainted: true,
ei: newEscapeInterpreter(mode),
ei: newEscapeInterpreter(),
g: g,
}
return v
}
@ -123,7 +128,7 @@ func (v *View) Name() string {
// setRune sets a rune at the given point relative to the view. It applies the
// specified colors, taking into account if the cell must be highlighted. Also,
// it checks if the position is valid.
func (v *View) setRune(x, y int, ch rune, fgColor, bgColor Attribute) error {
func (v *View) setRune(x, y int, ch rune, st tcell.Style) error {
maxX, maxY := v.Size()
if x < 0 || x >= maxX || y < 0 || y >= maxY {
return errors.New("invalid point")
@ -145,16 +150,13 @@ func (v *View) setRune(x, y int, ch rune, fgColor, bgColor Attribute) error {
}
if v.Mask != 0 {
fgColor = v.FgColor
bgColor = v.BgColor
st = v.Style
ch = v.Mask
} else if v.Highlight && ry == rcy {
fgColor = v.SelFgColor
bgColor = v.SelBgColor
st = v.SelStyle
}
termbox.SetCell(v.x0+x+1, v.y0+y+1, ch,
termbox.Attribute(fgColor), termbox.Attribute(bgColor))
v.g.SetRune(v.x0+x+1, v.y0+y+1, ch, st)
return nil
}
@ -240,9 +242,8 @@ func (v *View) parseInput(ch rune) []cell {
if err != nil {
for _, r := range v.ei.runes() {
c := cell{
fgColor: v.FgColor,
bgColor: v.BgColor,
chr: r,
style: v.Style,
chr: r,
}
cells = append(cells, c)
}
@ -252,9 +253,8 @@ func (v *View) parseInput(ch rune) []cell {
return nil
}
c := cell{
fgColor: v.ei.curFgColor,
bgColor: v.ei.curBgColor,
chr: ch,
style: v.ei.curStyle,
chr: ch,
}
cells = append(cells, c)
}
@ -341,16 +341,16 @@ func (v *View) draw() error {
break
}
fgColor := c.fgColor
if fgColor == ColorDefault {
fgColor = v.FgColor
st := c.style
fgColor, bgColor, _ := c.style.Decompose()
vfgColor, vbgColor, _ := v.Style.Decompose()
if fgColor == tcell.ColorDefault {
st = st.Foreground(vfgColor)
}
bgColor := c.bgColor
if bgColor == ColorDefault {
bgColor = v.BgColor
if bgColor == tcell.ColorDefault {
st = st.Background(vbgColor)
}
if err := v.setRune(x, y, c.chr, fgColor, bgColor); err != nil {
if err := v.setRune(x, y, c.chr, st); err != nil {
return err
}
x++
@ -402,8 +402,7 @@ func (v *View) clearRunes() {
maxX, maxY := v.Size()
for x := 0; x < maxX; x++ {
for y := 0; y < maxY; y++ {
termbox.SetCell(v.x0+x+1, v.y0+y+1, ' ',
termbox.Attribute(v.FgColor), termbox.Attribute(v.BgColor))
v.g.SetRune(v.x0+x+1, v.y0+y+1, ' ', v.Style)
}
}
}

@ -1,445 +0,0 @@
// Copyright 2020 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use file except in compliance with the License.
// You may obtain a copy of the license at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package termbox is a compatibility layer to allow tcell to emulate
// the github.com/nsf/termbox package.
package termbox
import (
"errors"
"github.com/gdamore/tcell/v2"
)
var screen tcell.Screen
var outMode OutputMode
// Init initializes the screen for use.
func Init() error {
outMode = OutputNormal
if s, e := tcell.NewScreen(); e != nil {
return e
} else if e = s.Init(); e != nil {
return e
} else {
screen = s
return nil
}
}
// Close cleans up the terminal, restoring terminal modes, etc.
func Close() {
screen.Fini()
}
// Flush updates the screen.
func Flush() error {
screen.Show()
return nil
}
// SetCursor displays the terminal cursor at the given location.
func SetCursor(x, y int) {
screen.ShowCursor(x, y)
}
// HideCursor hides the terminal cursor.
func HideCursor() {
SetCursor(-1, -1)
}
// Size returns the screen size as width, height in character cells.
func Size() (int, int) {
return screen.Size()
}
// Attribute affects the presentation of characters, such as color, boldness,
// and so forth.
type Attribute uint16
// Colors first. The order here is significant.
const (
ColorDefault Attribute = iota
ColorBlack
ColorRed
ColorGreen
ColorYellow
ColorBlue
ColorMagenta
ColorCyan
ColorWhite
)
// Other attributes.
const (
AttrBold Attribute = 1 << (9 + iota)
AttrUnderline
AttrReverse
)
func fixColor(c tcell.Color) tcell.Color {
if c == tcell.ColorDefault {
return c
}
switch outMode {
case OutputNormal:
c = tcell.PaletteColor(int(c) & 0xf)
case Output256:
c = tcell.PaletteColor(int(c) & 0xff)
case Output216:
c = tcell.PaletteColor(int(c)%216 + 16)
case OutputGrayscale:
c %= tcell.PaletteColor(int(c)%24 + 232)
default:
c = tcell.ColorDefault
}
return c
}
func mkStyle(fg, bg Attribute) tcell.Style {
b := tcell.ColorDefault
if bg != ColorDefault {
b = tcell.PaletteColor(int(bg)&0x1ff - 1)
b = fixColor(b)
}
f := tcell.ColorDefault
if fg != ColorDefault {
f = tcell.PaletteColor(int(fg)&0x1ff - 1)
f = fixColor(f)
}
st := tcell.StyleDefault.Foreground(f).Background(b)
if (fg|bg)&AttrBold != 0 {
st = st.Bold(true)
}
if (fg|bg)&AttrUnderline != 0 {
st = st.Underline(true)
}
if (fg|bg)&AttrReverse != 0 {
st = st.Reverse(true)
}
return st
}
// Clear clears the screen with the given attributes.
func Clear(fg, bg Attribute) {
st := mkStyle(fg, bg)
w, h := screen.Size()
for row := 0; row < h; row++ {
for col := 0; col < w; col++ {
screen.SetContent(col, row, ' ', nil, st)
}
}
}
// InputMode is not used.
type InputMode int
// Unused input modes; here for compatibility.
const (
InputCurrent InputMode = iota
InputEsc
InputAlt
InputMouse
)
// SetInputMode enables mouse if requested
func SetInputMode(mode InputMode) InputMode {
if mode&InputMouse != 0 {
screen.EnableMouse()
}
// We don't do anything else right now
return InputEsc
}
// OutputMode represents an output mode, which determines how colors
// are used. See the termbox documentation for an explanation.
type OutputMode int
// OutputMode values.
const (
OutputCurrent OutputMode = iota
OutputNormal
Output256
Output216
OutputGrayscale
)
// SetOutputMode is used to set the color palette used.
func SetOutputMode(mode OutputMode) OutputMode {
if screen.Colors() < 256 {
mode = OutputNormal
}
switch mode {
case OutputCurrent:
return outMode
case OutputNormal, Output256, Output216, OutputGrayscale:
outMode = mode
return mode
default:
return outMode
}
}
// Sync forces a resync of the screen.
func Sync() error {
screen.Sync()
return nil
}
// scaledColor returns a Color that is proportional to the x/y coordinates
// temporary kludge for the pretty
func prettyColor(x, y int, st tcell.Style) tcell.Style {
// fg, bg, _ := st.Decompose()
w, h := screen.Size()
if true {
red := int32(0)
grn := int32(0)
blu := int32(50 * float64(y) / float64(h))
bg := tcell.NewRGBColor(red, grn, blu)
st = st.Background(bg)
}
if true {
red := int32(200)
grn := int32(255 * float64(y) / float64(h))
blu := int32(255 * float64(x) / float64(w))
fg := tcell.NewRGBColor(red, grn, blu)
st = st.Foreground(fg)
}
return st
}
// SetCell sets the character cell at a given location to the given
// content (rune) and attributes.
func SetCell(x, y int, ch rune, fg, bg Attribute) {
st := mkStyle(fg, bg)
// Set the foreground color to a scaled version of the coordinates
if bg == ColorBlack {
st = prettyColor(x, y, st)
}
screen.SetContent(x, y, ch, nil, st)
}
// EventType represents the type of event.
type EventType uint8
// Modifier represents the possible modifier keys.
type Modifier tcell.ModMask
// Key is a key press.
type Key tcell.Key
// Event represents an event like a key press, mouse action, or window resize.
type Event struct {
Type EventType
Mod Modifier
Key Key
Ch rune
Width int
Height int
Err error
MouseX int
MouseY int
N int
}
// Event types.
const (
EventNone EventType = iota
EventKey
EventResize
EventMouse
EventInterrupt
EventError
EventRaw
)
// Keys codes.
const (
KeyF1 = Key(tcell.KeyF1)
KeyF2 = Key(tcell.KeyF2)
KeyF3 = Key(tcell.KeyF3)
KeyF4 = Key(tcell.KeyF4)
KeyF5 = Key(tcell.KeyF5)
KeyF6 = Key(tcell.KeyF6)
KeyF7 = Key(tcell.KeyF7)
KeyF8 = Key(tcell.KeyF8)
KeyF9 = Key(tcell.KeyF9)
KeyF10 = Key(tcell.KeyF10)
KeyF11 = Key(tcell.KeyF11)
KeyF12 = Key(tcell.KeyF12)
KeyInsert = Key(tcell.KeyInsert)
KeyDelete = Key(tcell.KeyDelete)
KeyHome = Key(tcell.KeyHome)
KeyEnd = Key(tcell.KeyEnd)
KeyArrowUp = Key(tcell.KeyUp)
KeyArrowDown = Key(tcell.KeyDown)
KeyArrowRight = Key(tcell.KeyRight)
KeyArrowLeft = Key(tcell.KeyLeft)
KeyCtrlA = Key(tcell.KeyCtrlA)
KeyCtrlB = Key(tcell.KeyCtrlB)
KeyCtrlC = Key(tcell.KeyCtrlC)
KeyCtrlD = Key(tcell.KeyCtrlD)
KeyCtrlE = Key(tcell.KeyCtrlE)
KeyCtrlF = Key(tcell.KeyCtrlF)
KeyCtrlG = Key(tcell.KeyCtrlG)
KeyCtrlH = Key(tcell.KeyCtrlH)
KeyCtrlI = Key(tcell.KeyCtrlI)
KeyCtrlJ = Key(tcell.KeyCtrlJ)
KeyCtrlK = Key(tcell.KeyCtrlK)
KeyCtrlL = Key(tcell.KeyCtrlL)
KeyCtrlM = Key(tcell.KeyCtrlM)
KeyCtrlN = Key(tcell.KeyCtrlN)
KeyCtrlO = Key(tcell.KeyCtrlO)
KeyCtrlP = Key(tcell.KeyCtrlP)
KeyCtrlQ = Key(tcell.KeyCtrlQ)
KeyCtrlR = Key(tcell.KeyCtrlR)
KeyCtrlS = Key(tcell.KeyCtrlS)
KeyCtrlT = Key(tcell.KeyCtrlT)
KeyCtrlU = Key(tcell.KeyCtrlU)
KeyCtrlV = Key(tcell.KeyCtrlV)
KeyCtrlW = Key(tcell.KeyCtrlW)
KeyCtrlX = Key(tcell.KeyCtrlX)
KeyCtrlY = Key(tcell.KeyCtrlY)
KeyCtrlZ = Key(tcell.KeyCtrlZ)
KeyCtrlUnderscore = Key(tcell.KeyCtrlUnderscore)
KeyBackspace = Key(tcell.KeyBackspace)
KeyBackspace2 = Key(tcell.KeyBackspace2)
KeyTab = Key(tcell.KeyTab)
KeyEnter = Key(tcell.KeyEnter)
KeyEsc = Key(tcell.KeyEscape)
KeyPgdn = Key(tcell.KeyPgDn)
KeyPgup = Key(tcell.KeyPgUp)
KeySpace = Key(tcell.Key(' '))
KeyTilde = Key(tcell.Key('~'))
KeyCtrlSpace = Key(tcell.KeyCtrlSpace)
// The following assignments are provided for termbox
// compatibility. Their use in applications is discouraged.
// The mouse keys are completely not supported as tcell uses
// a separate mouse event instead of key strokes.
MouseLeft = Key(tcell.KeyF63) // arbitrary assignments
MouseRight = Key(tcell.KeyF62)
MouseMiddle = Key(tcell.KeyF61)
MouseRelease = Key(tcell.KeyF60)
MouseWheelUp = Key(tcell.KeyF59)
MouseWheelDown = Key(tcell.KeyF58)
KeyCtrlTilde = Key(tcell.KeyCtrlSpace) // termbox defines a bunch of weird ones, don't use them
KeyCtrl2 = Key(tcell.KeyNUL)
KeyCtrl3 = Key(tcell.KeyEscape)
KeyCtrl4 = Key(tcell.KeyCtrlBackslash)
KeyCtrl5 = Key(tcell.KeyCtrlRightSq)
KeyCtrl6 = Key(tcell.KeyCtrlCarat)
KeyCtrl7 = Key(tcell.KeyCtrlUnderscore)
KeyCtrl8 = Key(tcell.KeyDEL)
KeyCtrlSlash = Key(tcell.KeyCtrlUnderscore)
KeyCtrlRsqBracket = Key(tcell.KeyCtrlRightSq)
KeyCtrlBackslash = Key(tcell.KeyCtrlBackslash)
KeyCtrlLsqBracket = Key(tcell.KeyCtrlLeftSq)
)
// Modifiers.
const (
ModAlt = Modifier(tcell.ModAlt)
)
func makeEvent(tev tcell.Event) Event {
switch tev := tev.(type) {
case *tcell.EventInterrupt:
return Event{Type: EventInterrupt}
case *tcell.EventResize:
w, h := tev.Size()
return Event{Type: EventResize, Width: w, Height: h}
case *tcell.EventKey:
k := tev.Key()
ch := rune(0)
if k == tcell.KeyRune {
ch = tev.Rune()
if ch == ' ' {
k = tcell.Key(' ')
} else {
k = tcell.Key(0)
}
}
mod := tev.Modifiers()
return Event{
Type: EventKey,
Key: Key(k),
Ch: ch,
Mod: Modifier(mod),
}
case *tcell.EventMouse:
x, y := tev.Position()
button := tev.Buttons()
// Don't worry about combo buttons for now
key := Key(tcell.KeyNUL)
if button&tcell.Button1 != 0 {
key = MouseLeft
} else if button&tcell.Button2 != 0 {
key = MouseRight
} else if button&tcell.Button3 != 0 {
key = MouseMiddle
} else if button&tcell.WheelUp != 0 {
key = MouseWheelUp
} else if button&tcell.WheelDown != 0 {
key = MouseWheelDown
} else {
return Event{Type: EventNone}
}
return Event{
Type: EventMouse,
MouseX: x,
MouseY: y,
Key: key,
Mod: 0, // not currently supported
}
default:
return Event{Type: EventNone}
}
}
// ParseEvent is not supported.
func ParseEvent(data []byte) Event {
// Not supported
return Event{Type: EventError, Err: errors.New("no raw events")}
}
// PollRawEvent is not supported.
func PollRawEvent(data []byte) Event {
// Not supported
return Event{Type: EventError, Err: errors.New("no raw events")}
}
// PollEvent blocks until an event is ready, and then returns it.
func PollEvent() Event {
ev := screen.PollEvent()
return makeEvent(ev)
}
// Interrupt posts an interrupt event.
func Interrupt() {
screen.PostEvent(tcell.NewEventInterrupt(nil))
}
// Cell represents a single character cell on screen.
type Cell struct {
Ch rune
Fg Attribute
Bg Attribute
}

@ -5,12 +5,13 @@
package termui
import (
"fmt"
"errors"
"path"
"sync"
"time"
"github.com/cointop-sh/cointop/pkg/termbox"
"github.com/gdamore/tcell/v2"
log "github.com/sirupsen/logrus"
)
type Event struct {
@ -28,17 +29,17 @@ type EvtKbd struct {
KeyStr string
}
func evtKbd(e termbox.Event) EvtKbd {
func evtKbd(e tcell.EventKey) EvtKbd {
ek := EvtKbd{}
k := string(e.Ch)
k := string(e.Rune())
pre := ""
mod := ""
if e.Mod == termbox.ModAlt {
if e.Modifiers() == tcell.ModAlt {
mod = "M-"
}
if e.Ch == 0 {
if e.Rune() == 0 {
// Doesn't appear to be used by cointop
// TODO: FIXME
@ -50,64 +51,65 @@ func evtKbd(e termbox.Event) EvtKbd {
// }
// TODO: FIXME
if e.Key <= 0x7F {
pre = "C-"
k = fmt.Sprintf("%v", 'a'-1+int(e.Key))
kmap := map[termbox.Key][2]string{
termbox.KeyCtrlSpace: {"C-", "<space>"}, // TODO: FIXME
termbox.KeyBackspace: {"", "<backspace>"},
termbox.KeyTab: {"", "<tab>"},
termbox.KeyEnter: {"", "<enter>"},
termbox.KeyEsc: {"", "<escape>"},
termbox.KeyCtrlBackslash: {"C-", "\\"},
termbox.KeyCtrlSlash: {"C-", "/"},
termbox.KeySpace: {"", "<space>"},
termbox.KeyCtrl8: {"C-", "8"}, // TODO: FIXME
}
if sk, ok := kmap[e.Key]; ok {
pre = sk[0]
k = sk[1]
}
}
// if e.Key <= 0x7F {
// pre = "C-"
// k = fmt.Sprintf("%v", 'a'-1+int(e.Key))
// kmap := map[termbox.Key][2]string{
// termbox.KeyCtrlSpace: {"C-", "<space>"}, // TODO: FIXME
// termbox.KeyBackspace: {"", "<backspace>"},
// termbox.KeyTab: {"", "<tab>"},
// termbox.KeyEnter: {"", "<enter>"},
// termbox.KeyEsc: {"", "<escape>"},
// termbox.KeyCtrlBackslash: {"C-", "\\"},
// termbox.KeyCtrlSlash: {"C-", "/"},
// termbox.KeySpace: {"", "<space>"},
// termbox.KeyCtrl8: {"C-", "8"}, // TODO: FIXME
// }
// if sk, ok := kmap[e.Key]; ok {
// pre = sk[0]
// k = sk[1]
// }
// }
}
ek.KeyStr = pre + mod + k
return ek
}
func crtTermboxEvt(e termbox.Event) Event {
systypemap := map[termbox.EventType]string{
termbox.EventKey: "keyboard",
termbox.EventResize: "window",
termbox.EventMouse: "mouse",
termbox.EventError: "error",
termbox.EventInterrupt: "interrupt",
}
ne := Event{From: "/sys", Time: time.Now().Unix()}
typ := e.Type
ne.Type = systypemap[typ]
switch typ {
case termbox.EventKey:
kbd := evtKbd(e)
ne.Path = "/sys/kbd/" + kbd.KeyStr
ne.Data = kbd
case termbox.EventResize:
func crtTermboxEvt(e tcell.Event) Event {
log.Debugf("XXX crtTermboxEvt")
ne := Event{From: "/sys", Time: e.When().Unix()}
switch tev := e.(type) {
case *tcell.EventResize:
wnd := EvtWnd{}
wnd.Width = e.Width
wnd.Height = e.Height
wnd.Width, wnd.Height = tev.Size()
ne.Path = "/sys/wnd/resize"
ne.Data = wnd
case termbox.EventError:
err := EvtErr(e.Err)
ne.Path = "/sys/err"
ne.Data = err
case termbox.EventMouse:
ne.Type = "window"
log.Debugf("XXX Resized to %d,%d", wnd.Width, wnd.Height)
return ne
case *tcell.EventMouse:
m := EvtMouse{}
m.X = e.MouseX
m.Y = e.MouseY
m.X, m.Y = tev.Position()
ne.Path = "/sys/mouse"
ne.Data = m
ne.Type = "mouse"
return ne
case *tcell.EventKey:
kbd := evtKbd(*tev)
ne.Path = "/sys/kbd/" + kbd.KeyStr
ne.Data = kbd
ne.Type = "keyboard"
return ne
case *tcell.EventError:
ne.Path = "/sys/err"
ne.Data = errors.New(tev.Error())
ne.Type = "error"
return ne
case *tcell.EventInterrupt:
ne.Type = "interrupt"
default:
ne.Type = "" // TODO: unhandled event?
}
return ne
}
@ -125,17 +127,18 @@ type EvtMouse struct {
type EvtErr error
func hookTermboxEvt() {
for {
e := termbox.PollEvent()
for _, c := range sysEvtChs {
func(ch chan Event) {
ch <- crtTermboxEvt(e)
}(c)
}
}
}
// func hookTermboxEvt() {
// log.Debugf("XXX hookTermboxEvt")
// for {
// e := termbox.PollEvent()
// log.Debugf("XXX event %s", e)
// for _, c := range sysEvtChs {
// func(ch chan Event) {
// ch <- crtTermboxEvt(e)
// }(c)
// }
// }
// }
func NewSysEvtCh() chan Event {
ec := make(chan Event)

@ -8,8 +8,6 @@ import (
"regexp"
"strings"
tm "github.com/cointop-sh/cointop/pkg/termbox"
rw "github.com/mattn/go-runewidth"
)
@ -48,9 +46,9 @@ var (
/* ----------------------- End ----------------------------- */
func toTmAttr(x Attribute) tm.Attribute {
return tm.Attribute(x)
}
// func toTmAttr(x Attribute) tm.Attribute {
// return tm.Attribute(x)
// }
func str2runes(s string) []rune {
return []rune(s)

@ -4,29 +4,12 @@
package termui
import (
"image"
"io"
"sync"
"time"
"fmt"
"os"
"runtime/debug"
"bytes"
tm "github.com/cointop-sh/cointop/pkg/termbox"
"github.com/maruel/panicparse/stack"
)
// Bufferer should be implemented by all renderable components.
type Bufferer interface {
Buffer() Buffer
}
/*
// Init initializes termui library. This function should be called before any others.
// After initialization, the library must be finalized by 'Close' function.
func Init() error {
@ -188,3 +171,4 @@ func Render(bs ...Bufferer) {
//go func() { renderJobs <- bs }()
renderJobs <- bs
}
*/

@ -2,6 +2,7 @@ package ui
import (
"github.com/cointop-sh/cointop/pkg/gocui"
"github.com/gdamore/tcell/v2"
)
// UI is the UI view struct
@ -11,7 +12,7 @@ type UI struct {
// NewUI returns a new UI instance
func NewUI() (*UI, error) {
g, err := gocui.NewGui(gocui.Output256)
g, err := gocui.NewGui()
if err != nil {
return nil, err
}
@ -26,14 +27,9 @@ func (ui *UI) GetGocui() *gocui.Gui {
return ui.g
}
// SetFgColor sets the foreground color
func (ui *UI) SetFgColor(fgColor gocui.Attribute) {
ui.g.FgColor = fgColor
}
// SetBgColor sets the background color
func (ui *UI) SetBgColor(bgColor gocui.Attribute) {
ui.g.BgColor = bgColor
// SetFgColor sets the default style
func (ui *UI) SetStyle(st tcell.Style) {
ui.g.Style = st
}
// SetInputEsc enables the escape key

@ -4,6 +4,7 @@ import (
"fmt"
"github.com/cointop-sh/cointop/pkg/gocui"
"github.com/gdamore/tcell/v2"
)
// IView is the view interface
@ -213,31 +214,17 @@ func (view *View) SetWrap(enabled bool) error {
return nil
}
// SetFgColor sets the foreground color
func (view *View) SetFgColor(color gocui.Attribute) {
// SetStyle sets the text style for the view
func (view *View) SetStyle(st tcell.Style) {
if view.HasBacking() {
view.backing.FgColor = color
view.backing.Style = st
}
}
// SetBgColor sets the background color
func (view *View) SetBgColor(color gocui.Attribute) {
// SetStyle sets the selection text style for the view
func (view *View) SetSelStyle(st tcell.Style) {
if view.HasBacking() {
view.backing.BgColor = color
}
}
// SetSelFgColor sets the foreground color for selection
func (view *View) SetSelFgColor(color gocui.Attribute) {
if view.HasBacking() {
view.backing.SelFgColor = color
}
}
// SetSelBgColor sets the background color for selection
func (view *View) SetSelBgColor(color gocui.Attribute) {
if view.HasBacking() {
view.backing.SelBgColor = color
view.backing.SelStyle = st
}
}

Loading…
Cancel
Save