From 3dd4f6797e69291c6b60afaee00a1866590e8193 Mon Sep 17 00:00:00 2001 From: Craig Swank Date: Sun, 27 Oct 2019 08:09:45 -0600 Subject: [PATCH 01/50] Adds support for specifying both foreground and background colors. for example: \033[48;5;200;38;5;100mHello results in a foreground color (100) and a background color (200) --- .gitignore | 1 + escape.go | 78 +++++++++++++++++++++++++++++++------------------- escape_test.go | 51 +++++++++++++++++++++++++++++++++ 3 files changed, 101 insertions(+), 29 deletions(-) create mode 100644 escape_test.go diff --git a/.gitignore b/.gitignore index 1377554e..d38c149c 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ *.swp +*~ diff --git a/escape.go b/escape.go index c88309b0..8c45b611 100644 --- a/escape.go +++ b/escape.go @@ -191,39 +191,59 @@ func (ei *escapeInterpreter) output256() error { 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.splitFgBg() { + fgbg, err := strconv.Atoi(param[0]) + if err != nil { + return errCSIParseError + } + color, err := strconv.Atoi(param[2]) + if err != nil { + return errCSIParseError + } - for _, param := range ei.csiParam[3:] { - p, err := strconv.Atoi(param) - if err != nil { - return errCSIParseError + switch fgbg { + case 38: + ei.curFgColor = Attribute(color + 1) + + for _, s := range param[3:] { + p, err := strconv.Atoi(s) + if err != nil { + return errCSIParseError + } + + switch { + case p == 1: + ei.curFgColor |= AttrBold + case p == 4: + ei.curFgColor |= AttrUnderline + case p == 7: + ei.curFgColor |= AttrReverse + + } } + case 48: + ei.curBgColor = Attribute(color + 1) + default: + return errCSIParseError + } + } + return nil +} - switch { - case p == 1: - ei.curFgColor |= AttrBold - case p == 4: - ei.curFgColor |= AttrUnderline - case p == 7: - ei.curFgColor |= AttrReverse - } +func (ei *escapeInterpreter) splitFgBg() [][]string { + var out [][]string + var current []string + for _, p := range ei.csiParam { + if len(current) > 0 && (p == "48" || p == "38") { + out = append(out, current) + current = []string{} } - case 48: - ei.curBgColor = Attribute(color + 1) - default: - return errCSIParseError + current = append(current, p) } - return nil + if len(current) > 0 { + out = append(out, current) + } + + return out } diff --git a/escape_test.go b/escape_test.go new file mode 100644 index 00000000..7caf4698 --- /dev/null +++ b/escape_test.go @@ -0,0 +1,51 @@ +package gocui + +import ( + "fmt" + "testing" +) + +func TestEscape(t *testing.T) { + testCases := []struct { + input string + fg Attribute + bg Attribute + }{ + { + input: "\033[48;5;200;38;5;100mHi!!", + fg: 101, + bg: 201, + }, + { + input: "\033[38;5;100mHi!!", + fg: 101, + bg: ColorDefault, + }, + { + input: "\033[48;5;100mHi!!", + fg: ColorDefault, + bg: 101, + }, + } + + for i, tc := range testCases { + t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) { + ei := newEscapeInterpreter(Output256) + + for _, r := range tc.input { + _, err := ei.parseOne(r) + if err != nil { + t.Fatal(err) + } + } + + if ei.curFgColor != tc.fg { + t.Fatalf("foreground color is not 100: %v", ei.curFgColor) + } + if ei.curBgColor != tc.bg { + t.Fatalf("background color is not 100: %v", ei.curBgColor) + } + }) + } + +} From bb095e99d57db8bd9c5caf756614bdd2da7a66ce Mon Sep 17 00:00:00 2001 From: Craig Swank Date: Sun, 27 Oct 2019 08:15:52 -0600 Subject: [PATCH 02/50] one more test --- escape_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/escape_test.go b/escape_test.go index 7caf4698..3e10620a 100644 --- a/escape_test.go +++ b/escape_test.go @@ -16,6 +16,11 @@ func TestEscape(t *testing.T) { fg: 101, bg: 201, }, + { + input: "\033[38;5;100;48;5;200mHi!!", + fg: 101, + bg: 201, + }, { input: "\033[38;5;100mHi!!", fg: 101, From 87d67d4e60fd677b5d0b82a913d5f3d028a6a239 Mon Sep 17 00:00:00 2001 From: Craig Swank Date: Sun, 27 Oct 2019 08:18:16 -0600 Subject: [PATCH 03/50] vix test failure message --- escape_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/escape_test.go b/escape_test.go index 3e10620a..90867771 100644 --- a/escape_test.go +++ b/escape_test.go @@ -45,10 +45,10 @@ func TestEscape(t *testing.T) { } if ei.curFgColor != tc.fg { - t.Fatalf("foreground color is not 100: %v", ei.curFgColor) + t.Fatalf("foreground color is not %d: %v", tc.fg, ei.curFgColor) } if ei.curBgColor != tc.bg { - t.Fatalf("background color is not 100: %v", ei.curBgColor) + t.Fatalf("background color is not %d: %v", tc.bg, ei.curBgColor) } }) } From 76e6337475d919cb06ea2cd408fedfea5e2ea8e1 Mon Sep 17 00:00:00 2001 From: Craig Swank Date: Sun, 27 Oct 2019 08:22:41 -0600 Subject: [PATCH 04/50] one more test --- escape_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/escape_test.go b/escape_test.go index 90867771..3acbfe10 100644 --- a/escape_test.go +++ b/escape_test.go @@ -31,6 +31,11 @@ func TestEscape(t *testing.T) { fg: ColorDefault, bg: 101, }, + { + input: "Hi!!", + fg: ColorDefault, + bg: ColorDefault, + }, } for i, tc := range testCases { From afd118c4568846a38b2c3d28df4db850ab142183 Mon Sep 17 00:00:00 2001 From: Craig Swank Date: Tue, 29 Oct 2019 08:53:25 -0600 Subject: [PATCH 05/50] remove test --- escape_test.go | 61 -------------------------------------------------- 1 file changed, 61 deletions(-) delete mode 100644 escape_test.go diff --git a/escape_test.go b/escape_test.go deleted file mode 100644 index 3acbfe10..00000000 --- a/escape_test.go +++ /dev/null @@ -1,61 +0,0 @@ -package gocui - -import ( - "fmt" - "testing" -) - -func TestEscape(t *testing.T) { - testCases := []struct { - input string - fg Attribute - bg Attribute - }{ - { - input: "\033[48;5;200;38;5;100mHi!!", - fg: 101, - bg: 201, - }, - { - input: "\033[38;5;100;48;5;200mHi!!", - fg: 101, - bg: 201, - }, - { - input: "\033[38;5;100mHi!!", - fg: 101, - bg: ColorDefault, - }, - { - input: "\033[48;5;100mHi!!", - fg: ColorDefault, - bg: 101, - }, - { - input: "Hi!!", - fg: ColorDefault, - bg: ColorDefault, - }, - } - - for i, tc := range testCases { - t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) { - ei := newEscapeInterpreter(Output256) - - for _, r := range tc.input { - _, err := ei.parseOne(r) - if err != nil { - t.Fatal(err) - } - } - - if ei.curFgColor != tc.fg { - t.Fatalf("foreground color is not %d: %v", tc.fg, ei.curFgColor) - } - if ei.curBgColor != tc.bg { - t.Fatalf("background color is not %d: %v", tc.bg, ei.curBgColor) - } - }) - } - -} From 2553dd85eb8152f3ee42bf37cf69b36b7bc3bc19 Mon Sep 17 00:00:00 2001 From: Craig Swank Date: Tue, 29 Oct 2019 09:04:06 -0600 Subject: [PATCH 06/50] removed emacs gitignore --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index d38c149c..1377554e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ *.swp -*~ From 67078a4855db9a06571ad6beab36e72a89c799bc Mon Sep 17 00:00:00 2001 From: Craig Swank Date: Wed, 30 Oct 2019 17:03:05 -0600 Subject: [PATCH 07/50] use constant for color switch --- escape.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/escape.go b/escape.go index 8c45b611..562513ea 100644 --- a/escape.go +++ b/escape.go @@ -5,8 +5,9 @@ package gocui import ( - "github.com/go-errors/errors" "strconv" + + "github.com/go-errors/errors" ) type escapeInterpreter struct { @@ -24,6 +25,9 @@ const ( stateEscape stateCSI stateParams + + foregroundColor = 38 + backgroundColor = 48 ) var ( @@ -202,7 +206,7 @@ func (ei *escapeInterpreter) output256() error { } switch fgbg { - case 38: + case foregroundColor: ei.curFgColor = Attribute(color + 1) for _, s := range param[3:] { @@ -221,7 +225,7 @@ func (ei *escapeInterpreter) output256() error { } } - case 48: + case backgroundColor: ei.curBgColor = Attribute(color + 1) default: return errCSIParseError From 5cd1f6748e84ed769f6b3978175b24b108bde767 Mon Sep 17 00:00:00 2001 From: Craig Swank Date: Wed, 30 Oct 2019 17:40:51 -0600 Subject: [PATCH 08/50] make fg bg const more descriptive --- escape.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/escape.go b/escape.go index 562513ea..3bb842cc 100644 --- a/escape.go +++ b/escape.go @@ -26,8 +26,8 @@ const ( stateCSI stateParams - foregroundColor = 38 - backgroundColor = 48 + setForegroundColor = 38 + setBackgroundColor = 48 ) var ( @@ -206,7 +206,7 @@ func (ei *escapeInterpreter) output256() error { } switch fgbg { - case foregroundColor: + case setForegroundColor: ei.curFgColor = Attribute(color + 1) for _, s := range param[3:] { @@ -225,7 +225,7 @@ func (ei *escapeInterpreter) output256() error { } } - case backgroundColor: + case setBackgroundColor: ei.curBgColor = Attribute(color + 1) default: return errCSIParseError From e8b6df00998064554943e646937a147025db6efe Mon Sep 17 00:00:00 2001 From: Craig Swank Date: Wed, 30 Oct 2019 17:52:46 -0600 Subject: [PATCH 09/50] more constants for the escape interpreter see https://stackoverflow.com/questions/4842424/list-of-ansi-color-escape-sequences --- escape.go | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/escape.go b/escape.go index 3bb842cc..c55b0fda 100644 --- a/escape.go +++ b/escape.go @@ -18,7 +18,10 @@ type escapeInterpreter struct { mode OutputMode } -type escapeState int +type ( + escapeState int + fontEffect int +) const ( stateNone escapeState = iota @@ -26,8 +29,11 @@ const ( stateCSI stateParams - setForegroundColor = 38 - setBackgroundColor = 48 + bold fontEffect = 1 + underline fontEffect = 4 + reverse fontEffect = 7 + setForegroundColor fontEffect = 38 + setBackgroundColor fontEffect = 48 ) var ( @@ -205,7 +211,7 @@ func (ei *escapeInterpreter) output256() error { return errCSIParseError } - switch fgbg { + switch fontEffect(fgbg) { case setForegroundColor: ei.curFgColor = Attribute(color + 1) @@ -215,12 +221,12 @@ func (ei *escapeInterpreter) output256() error { return errCSIParseError } - switch { - case p == 1: + switch fontEffect(p) { + case bold: ei.curFgColor |= AttrBold - case p == 4: + case underline: ei.curFgColor |= AttrUnderline - case p == 7: + case reverse: ei.curFgColor |= AttrReverse } From 3cd36d313017fbaafde1116f3c02f051b15215cf Mon Sep 17 00:00:00 2001 From: Craig Swank Date: Wed, 30 Oct 2019 19:29:18 -0600 Subject: [PATCH 10/50] handle case where the color is 38 or 48 --- escape.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/escape.go b/escape.go index c55b0fda..97d9b76f 100644 --- a/escape.go +++ b/escape.go @@ -244,7 +244,7 @@ func (ei *escapeInterpreter) splitFgBg() [][]string { var out [][]string var current []string for _, p := range ei.csiParam { - if len(current) > 0 && (p == "48" || p == "38") { + if len(current) == 3 && (p == "48" || p == "38") { out = append(out, current) current = []string{} } From 2c932726590b2c69c444bd38d22af4872afd18a5 Mon Sep 17 00:00:00 2001 From: Craig Swank Date: Fri, 1 Nov 2019 10:26:59 -0600 Subject: [PATCH 11/50] make method a function --- escape.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/escape.go b/escape.go index 97d9b76f..64360802 100644 --- a/escape.go +++ b/escape.go @@ -201,7 +201,7 @@ func (ei *escapeInterpreter) output256() error { return ei.outputNormal() } - for _, param := range ei.splitFgBg() { + for _, param := range splitFgBg(ei.csiParam) { fgbg, err := strconv.Atoi(param[0]) if err != nil { return errCSIParseError @@ -240,10 +240,10 @@ func (ei *escapeInterpreter) output256() error { return nil } -func (ei *escapeInterpreter) splitFgBg() [][]string { +func splitFgBg(params []string) [][]string { var out [][]string var current []string - for _, p := range ei.csiParam { + for _, p := range params { if len(current) == 3 && (p == "48" || p == "38") { out = append(out, current) current = []string{} From 5a578aceb7cdc49e000b08ffe0312dc55781df5b Mon Sep 17 00:00:00 2001 From: mjarkk Date: Wed, 26 Feb 2020 10:08:44 +0100 Subject: [PATCH 12/50] Update go runewidth to latest version --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 5791b4e4..0a90f5b9 100644 --- a/go.mod +++ b/go.mod @@ -5,5 +5,5 @@ go 1.12 require ( github.com/awesome-gocui/termbox-go v0.0.0-20190427202837-c0aef3d18bcc github.com/go-errors/errors v1.0.1 - github.com/mattn/go-runewidth v0.0.4 + github.com/mattn/go-runewidth v0.0.8 ) diff --git a/go.sum b/go.sum index 25f1c037..d562b8eb 100644 --- a/go.sum +++ b/go.sum @@ -4,3 +4,5 @@ github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6 github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuujKs0= +github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= From c69536d32e0e9583ff6f7220f8b50a4042a8ca83 Mon Sep 17 00:00:00 2001 From: mjarkk Date: Tue, 14 Apr 2020 19:42:35 +0200 Subject: [PATCH 13/50] Updated deps --- go.mod | 4 ++-- go.sum | 10 ++++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 0a90f5b9..18e0fda4 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,6 @@ go 1.12 require ( github.com/awesome-gocui/termbox-go v0.0.0-20190427202837-c0aef3d18bcc - github.com/go-errors/errors v1.0.1 - github.com/mattn/go-runewidth v0.0.8 + github.com/go-errors/errors v1.0.2 + github.com/mattn/go-runewidth v0.0.9 ) diff --git a/go.sum b/go.sum index d562b8eb..0736ba83 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,6 @@ github.com/awesome-gocui/termbox-go v0.0.0-20190427202837-c0aef3d18bcc h1:wGNpKcHU8Aadr9yOzsT3GEsFLS7HQu8HxQIomnekqf0= github.com/awesome-gocui/termbox-go v0.0.0-20190427202837-c0aef3d18bcc/go.mod h1:tOy3o5Nf1bA17mnK4W41gD7PS3u4Cv0P0pqFcoWMy8s= -github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= -github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= -github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= -github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuujKs0= -github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/go-errors/errors v1.0.2 h1:xMxH9j2fNg/L4hLn/4y3M0IUsn0M6Wbu/Uh9QlOfBh4= +github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= From 7a7700bfb7128b2c3c811bf155c7d5e78bd41185 Mon Sep 17 00:00:00 2001 From: Glenn Vriesman Date: Wed, 15 Apr 2020 17:04:03 +0200 Subject: [PATCH 14/50] fix: fixed nil view breaking keybind Signed-off-by: Glenn Vriesman --- gui.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui.go b/gui.go index 6fe0d5d8..6092b8ec 100644 --- a/gui.go +++ b/gui.go @@ -787,7 +787,7 @@ func (g *Gui) execKeybindings(v *View, ev *termbox.Event) (matched bool, err err return g.execKeybinding(v, kb) } - if kb.viewName == "" && ((v != nil && !v.Editable) || kb.ch == 0) { + if kb.viewName == "" && (((v != nil && !v.Editable) || kb.ch == 0) || v == nil) { globalKb = kb } } From 718dcb4992aea67ffbe222d6ad9693c26866fadf Mon Sep 17 00:00:00 2001 From: Glenn Vriesman Date: Mon, 1 Jun 2020 22:44:11 +0200 Subject: [PATCH 15/50] fix: use colors in highlight Signed-off-by: Glenn Vriesman --- view.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/view.go b/view.go index 81f90603..ad659366 100644 --- a/view.go +++ b/view.go @@ -194,7 +194,8 @@ func (v *View) setRune(x, y int, ch rune, fgColor, bgColor Attribute) error { bgColor = v.BgColor ch = v.Mask } else if v.Highlight && ry == rcy { - fgColor = fgColor | AttrBold + fgColor = v.SelFgColor | AttrBold + bgColor = v.SelBgColor | AttrBold } // Don't display NUL characters From 92aca5ecc2d438bbcb217ec3f963dc47884012e0 Mon Sep 17 00:00:00 2001 From: Glenn Vriesman Date: Sun, 7 Jun 2020 16:42:11 +0200 Subject: [PATCH 16/50] feat: added option to enable kb in editable view Signed-off-by: Glenn Vriesman --- keybinding.go | 2 +- view.go | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/keybinding.go b/keybinding.go index d294e70d..9b2a14aa 100644 --- a/keybinding.go +++ b/keybinding.go @@ -111,7 +111,7 @@ func (kb *keybinding) matchKeypress(key Key, ch rune, mod Modifier) bool { // matchView returns if the keybinding matches the current view. func (kb *keybinding) matchView(v *View) bool { // if the user is typing in a field, ignore char keys - if v == nil || (v.Editable && kb.ch != 0) { + if v == nil || (v.Editable && kb.ch != 0 && !v.KeybindOnEdit) { return false } return kb.viewName == v.name diff --git a/view.go b/view.go index ad659366..6d547275 100644 --- a/view.go +++ b/view.go @@ -111,6 +111,10 @@ type View struct { // If HasLoader is true, the message will be appended with a spinning loader animation HasLoader bool + + // KeybindOnEdit should be set to true when you want to execute keybindings even when the view is editable + // (this is usually not the case) + KeybindOnEdit bool } type viewLine struct { From bc7b36d3bb59ea13e4631340dc426f8fd52c5a3c Mon Sep 17 00:00:00 2001 From: "R.I.Pienaar" Date: Sat, 4 Jul 2020 10:22:04 +0200 Subject: [PATCH 17/50] Adds an Async versoin of gui.Update Signed-off-by: R.I.Pienaar --- .gitignore | 1 + gui.go | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 1377554e..a505ae07 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ *.swp +.idea diff --git a/gui.go b/gui.go index 6092b8ec..0b198dff 100644 --- a/gui.go +++ b/gui.go @@ -392,7 +392,14 @@ type userEvent struct { // the user events queue. Given that Update spawns a goroutine, the order in // which the user events will be handled is not guaranteed. func (g *Gui) Update(f func(*Gui) error) { - go func() { g.userEvents <- userEvent{f: f} }() + go g.UpdateAsync(f) +} + +// UpdateAsync is a version of Update that does not spawn a go routine, it can +// be a bit more efficient in cases where Update is called many times like when +// tailing a file. In general you should use Update() +func (g *Gui) UpdateAsync(f func(*Gui) error) { + g.userEvents <- userEvent{f: f} } // A Manager is in charge of GUI's layout and can be used to build widgets. From 29ce6c60ce68b4e727e62cd67a2ee6233d74d540 Mon Sep 17 00:00:00 2001 From: "Brian C. Lane" Date: Fri, 7 Aug 2020 07:48:19 -0700 Subject: [PATCH 18/50] doc: Add overlap bool to NewGui example --- doc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc.go b/doc.go index ca7113fa..4b6c499c 100644 --- a/doc.go +++ b/doc.go @@ -7,7 +7,7 @@ Package gocui allows to create console user interfaces. Create a new GUI: - g, err := gocui.NewGui(gocui.OutputNormal) + g, err := gocui.NewGui(gocui.OutputNormal, false) if err != nil { // handle error } From 309e767f8f46c75cbfec55c0e43f6a1136d3cb04 Mon Sep 17 00:00:00 2001 From: "Brian C. Lane" Date: Fri, 7 Aug 2020 07:59:18 -0700 Subject: [PATCH 19/50] doc: Add Overlaps byte to SetView examples --- doc.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc.go b/doc.go index 4b6c499c..b2f8250b 100644 --- a/doc.go +++ b/doc.go @@ -37,7 +37,7 @@ 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 v, err := g.SetView("viewname", 2, 2, 22, 7, 0); err != nil { if !gocui.IsUnknownView(err) { // handle error } @@ -48,7 +48,7 @@ Create and initialize a view with absolute coordinates: 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 { + if v, err := g.SetView("viewname", maxX/2-30, maxY/2, maxX/2+30, maxY/2+2, 0); err != nil { // ... } From cb1cfb0a01a11dfabedad1ce37efac3b3f3f121a Mon Sep 17 00:00:00 2001 From: dankox Date: Wed, 28 Oct 2020 15:47:45 +0100 Subject: [PATCH 20/50] Add frame customization FrameColor can be specified for View. TitleColor can be specified too and is applied to title and subtitle. This color will be used if no Highlight or no current View is displayed. FrameRunes can be specified for View allowing to change frame style. Added custom_frame.go example for demonstration --- _examples/custom_frame.go | 141 ++++++++++++++++++++++++++++++++++++++ gui.go | 73 +++++++++++++++++++- view.go | 21 ++++++ 3 files changed, 233 insertions(+), 2 deletions(-) create mode 100644 _examples/custom_frame.go diff --git a/_examples/custom_frame.go b/_examples/custom_frame.go new file mode 100644 index 00000000..6087bac8 --- /dev/null +++ b/_examples/custom_frame.go @@ -0,0 +1,141 @@ +package main + +import ( + "fmt" + "log" + + "github.com/awesome-gocui/gocui" +) + +var ( + viewArr = []string{"v1", "v2", "v3", "v4"} + active = 0 +) + +func setCurrentViewOnTop(g *gocui.Gui, name string) (*gocui.View, error) { + if _, err := g.SetCurrentView(name); err != nil { + return nil, err + } + return g.SetViewOnTop(name) +} + +func nextView(g *gocui.Gui, v *gocui.View) error { + nextIndex := (active + 1) % len(viewArr) + name := viewArr[nextIndex] + + out, err := g.View("v1") + if err != nil { + return err + } + fmt.Fprintln(out, "Going from view "+v.Name()+" to "+name) + + if _, err := setCurrentViewOnTop(g, name); err != nil { + return err + } + + if nextIndex == 3 { + g.Cursor = true + } else { + g.Cursor = false + } + + active = nextIndex + return nil +} + +func layout(g *gocui.Gui) error { + maxX, maxY := g.Size() + if v, err := g.SetView("v1", 0, 0, maxX/2-1, maxY/2-1, gocui.RIGHT); err != nil { + if !gocui.IsUnknownView(err) { + return err + } + v.Title = "v1" + v.Autoscroll = true + fmt.Fprintln(v, "View with default frame color") + fmt.Fprintln(v, "It's connected to v2 with overlay RIGHT.\n") + if _, err = setCurrentViewOnTop(g, "v1"); err != nil { + return err + } + } + + if v, err := g.SetView("v2", maxX/2-1, 0, maxX-1, maxY/2-1, gocui.LEFT); err != nil { + if !gocui.IsUnknownView(err) { + return err + } + v.Title = "v2" + v.Wrap = true + v.FrameColor = gocui.ColorMagenta + v.FrameRunes = []rune{'═', '│'} + fmt.Fprintln(v, "View with minimum frame customization and colored frame.") + fmt.Fprintln(v, "It's connected to v1 with overlay LEFT.\n") + fmt.Fprintln(v, "\033[35;1mInstructions:\033[0m") + fmt.Fprintln(v, "Press TAB to change current view") + fmt.Fprintln(v, "Press Ctrl+O to toggle gocui.SupportOverlap\n") + fmt.Fprintln(v, "\033[32;2mSelected frame is highlighted with green color\033[0m") + } + if v, err := g.SetView("v3", 0, maxY/2, maxX/2-1, maxY-1, 0); err != nil { + if !gocui.IsUnknownView(err) { + return err + } + v.Title = "v3" + v.Wrap = true + v.Autoscroll = true + v.FrameColor = gocui.ColorCyan + v.TitleColor = gocui.ColorCyan + v.FrameRunes = []rune{'═', '║', '╔', '╗', '╚', '╝'} + fmt.Fprintln(v, "View with basic frame customization and colored frame and title") + fmt.Fprintln(v, "It's not connected to any view.") + } + if v, err := g.SetView("v4", maxX/2, maxY/2, maxX-1, maxY-1, gocui.LEFT); err != nil { + if !gocui.IsUnknownView(err) { + return err + } + v.Title = "v4" + v.Subtitle = "(editable)" + v.Editable = true + v.TitleColor = gocui.ColorYellow + v.FrameColor = gocui.ColorRed + v.FrameRunes = []rune{'═', '║', '╔', '╗', '╚', '╝', '╠', '╣', '╦', '╩', '╬'} + fmt.Fprintln(v, "View with fully customized frame and colored title differently.") + fmt.Fprintln(v, "It's connected to v3 with overlay LEFT.\n") + v.SetCursor(0, 3) + } + return nil +} + +func quit(g *gocui.Gui, v *gocui.View) error { + return gocui.ErrQuit +} + +func toggleOverlap(g *gocui.Gui, v *gocui.View) error { + g.SupportOverlaps = !g.SupportOverlaps + return nil +} + +func main() { + g, err := gocui.NewGui(gocui.OutputNormal, true) + if err != nil { + log.Panicln(err) + } + defer g.Close() + + g.Highlight = true + g.SelFgColor = gocui.ColorGreen + g.SelFrameColor = gocui.ColorGreen + + g.SetManagerFunc(layout) + + if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil { + log.Panicln(err) + } + if err := g.SetKeybinding("", gocui.KeyCtrlO, gocui.ModNone, toggleOverlap); err != nil { + log.Panicln(err) + } + if err := g.SetKeybinding("", gocui.KeyTab, gocui.ModNone, nextView); err != nil { + log.Panicln(err) + } + + if err := g.MainLoop(); err != nil && !gocui.IsQuit(err) { + log.Panicln(err) + } +} diff --git a/gui.go b/gui.go index 0b198dff..3035ded7 100644 --- a/gui.go +++ b/gui.go @@ -547,9 +547,17 @@ func (g *Gui) flush() error { bgColor = g.SelBgColor frameColor = g.SelFrameColor } else { - fgColor = g.FgColor bgColor = g.BgColor - frameColor = g.FrameColor + if v.TitleColor != ColorDefault { + fgColor = v.TitleColor + } else { + fgColor = g.FgColor + } + if v.FrameColor != ColorDefault { + frameColor = v.FrameColor + } else { + frameColor = g.FrameColor + } } if err := g.drawFrameEdges(v, frameColor, bgColor); err != nil { @@ -582,6 +590,8 @@ func (g *Gui) drawFrameEdges(v *View, fgColor, bgColor Attribute) error { runeH, runeV := '─', '│' if g.ASCII { runeH, runeV = '-', '|' + } else if len(v.FrameRunes) >= 2 { + runeH, runeV = v.FrameRunes[0], v.FrameRunes[1] } for x := v.x0 + 1; x < v.x1 && x < g.maxX; x++ { @@ -621,8 +631,64 @@ func cornerRune(index byte) rune { return []rune{' ', '│', '│', '│', '─', '┘', '┐', '┤', '─', '└', '┌', '├', '├', '┴', '┬', '┼'}[index] } +// cornerCustomRune returns rune from `v.FrameRunes` slice. If the length of slice is less than 11 +// all the missing runes will be translated to the default `cornerRune()` +func cornerCustomRune(v *View, index byte) rune { + // Translate `cornerRune()` index + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + // ' ', '│', '│', '│', '─', '┘', '┐', '┤', '─', '└', '┌', '├', '├', '┴', '┬', '┼' + // into `FrameRunes` index + // 0 1 2 3 4 5 6 7 8 9 10 + // '─', '│', '┌', '┐', '└', '┘', '├', '┤', '┬', '┴', '┼' + switch index { + case 1, 2, 3: + return v.FrameRunes[1] + case 4, 8: + return v.FrameRunes[0] + case 5: + return v.FrameRunes[5] + case 6: + return v.FrameRunes[3] + case 7: + if len(v.FrameRunes) < 8 { + break + } + return v.FrameRunes[7] + case 9: + return v.FrameRunes[4] + case 10: + return v.FrameRunes[2] + case 11, 12: + if len(v.FrameRunes) < 7 { + break + } + return v.FrameRunes[6] + case 13: + if len(v.FrameRunes) < 10 { + break + } + return v.FrameRunes[9] + case 14: + if len(v.FrameRunes) < 9 { + break + } + return v.FrameRunes[8] + case 15: + if len(v.FrameRunes) < 11 { + break + } + return v.FrameRunes[10] + default: + return ' ' // cornerRune(0) + } + return cornerRune(index) +} + func corner(v *View, directions byte) rune { index := v.Overlaps | directions + if len(v.FrameRunes) >= 6 { + return cornerCustomRune(v, index) + } return cornerRune(index) } @@ -641,6 +707,9 @@ func (g *Gui) drawFrameCorners(v *View, fgColor, bgColor Attribute) error { } runeTL, runeTR, runeBL, runeBR := '┌', '┐', '└', '┘' + if len(v.FrameRunes) >= 6 { + runeTL, runeTR, runeBL, runeBR = v.FrameRunes[2], v.FrameRunes[3], v.FrameRunes[4], v.FrameRunes[5] + } if g.SupportOverlaps { runeTL = corner(v, BOTTOM|RIGHT) runeTR = corner(v, BOTTOM|LEFT) diff --git a/view.go b/view.go index 6d547275..04002ec7 100644 --- a/view.go +++ b/view.go @@ -87,6 +87,24 @@ type View struct { // If Frame is true, a border will be drawn around the view. Frame bool + // FrameColor allow to configure the color of the Frame when it is not highlighted. + FrameColor Attribute + + // FrameRunes allows to define custom runes for the frame edges. + // The rune slice can be defined with 3 different lengths. + // If slice doesn't match these lengths, default runes will be used instead of missing one. + // + // 2 runes with only horizontal and vertical edges. + // []rune{'─', '│'} + // []rune{'═','║'} + // 6 runes with horizontal, vertical edges and top-left, top-right, bottom-left, bottom-right cornes. + // []rune{'─', '│', '┌', '┐', '└', '┘'} + // []rune{'═','║','╔','╗','╚','╝'} + // 11 runes which can be used with `gocui.Gui.SupportOverlaps` property. + // []rune{'─', '│', '┌', '┐', '└', '┘', '├', '┤', '┬', '┴', '┼'} + // []rune{'═','║','╔','╗','╚','╝','╠','╣','╦','╩','╬'} + FrameRunes []rune + // If Wrap is true, the content that is written to this View is // automatically wrapped when it is longer than its width. If true the // view's x-origin will be ignored. @@ -99,6 +117,9 @@ type View struct { // If Frame is true, Title allows to configure a title for the view. Title string + // TitleColor allow to configure the color of title and subtitle for the view. + TitleColor Attribute + // If Frame is true, Subtitle allows to configure a subtitle for the view. Subtitle string From bf52b10f40ea2d9ca9e732c05d8398b8ebc69351 Mon Sep 17 00:00:00 2001 From: dankox Date: Mon, 2 Nov 2020 19:06:41 +0100 Subject: [PATCH 21/50] first try to swap termbox to tcell --- _examples/colors256.go | 4 ++ _examples/dynamic.go | 2 +- attribute.go | 3 +- go.mod | 1 + go.sum | 10 +++ gui.go | 8 ++- keybinding.go | 147 +++++++++++++++++++++-------------------- view.go | 3 +- 8 files changed, 99 insertions(+), 79 deletions(-) diff --git a/_examples/colors256.go b/_examples/colors256.go index 245b7f7e..1056679d 100644 --- a/_examples/colors256.go +++ b/_examples/colors256.go @@ -25,6 +25,10 @@ func main() { log.Panicln(err) } + if err := g.SetKeybinding("", gocui.KeyF10, gocui.ModNone, quit); err != nil { + log.Panicln(err) + } + if err := g.MainLoop(); err != nil && !gocui.IsQuit(err) { log.Panicln(err) } diff --git a/_examples/dynamic.go b/_examples/dynamic.go index d73d1178..9f3bc670 100644 --- a/_examples/dynamic.go +++ b/_examples/dynamic.go @@ -64,7 +64,7 @@ func layout(g *gocui.Gui) error { } func initKeybindings(g *gocui.Gui) error { - if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, + if err := g.SetKeybinding("", gocui.KeyF10, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error { return gocui.ErrQuit }); err != nil { diff --git a/attribute.go b/attribute.go index 3d986a71..4cd5a69f 100644 --- a/attribute.go +++ b/attribute.go @@ -4,7 +4,8 @@ package gocui -import "github.com/awesome-gocui/termbox-go" +// import "github.com/awesome-gocui/termbox-go" +import "github.com/gdamore/tcell/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 diff --git a/go.mod b/go.mod index 18e0fda4..257ef2bb 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.12 require ( github.com/awesome-gocui/termbox-go v0.0.0-20190427202837-c0aef3d18bcc + github.com/gdamore/tcell v1.4.0 github.com/go-errors/errors v1.0.2 github.com/mattn/go-runewidth v0.0.9 ) diff --git a/go.sum b/go.sum index 0736ba83..8a64094a 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,16 @@ github.com/awesome-gocui/termbox-go v0.0.0-20190427202837-c0aef3d18bcc h1:wGNpKcHU8Aadr9yOzsT3GEsFLS7HQu8HxQIomnekqf0= github.com/awesome-gocui/termbox-go v0.0.0-20190427202837-c0aef3d18bcc/go.mod h1:tOy3o5Nf1bA17mnK4W41gD7PS3u4Cv0P0pqFcoWMy8s= +github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= +github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= +github.com/gdamore/tcell v1.4.0 h1:vUnHwJRvcPQa3tzi+0QI4U9JINXYJlOz9yiaiPQ2wMU= +github.com/gdamore/tcell v1.4.0/go.mod h1:vxEiSDZdW3L+Uhjii9c3375IlDmR05bzxY404ZVSMo0= github.com/go-errors/errors v1.0.2 h1:xMxH9j2fNg/L4hLn/4y3M0IUsn0M6Wbu/Uh9QlOfBh4= github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs= +github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac= +github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/gui.go b/gui.go index 3035ded7..d0a087b2 100644 --- a/gui.go +++ b/gui.go @@ -10,7 +10,8 @@ import ( "github.com/go-errors/errors" - "github.com/awesome-gocui/termbox-go" + // "github.com/awesome-gocui/termbox-go" + "github.com/gdamore/tcell/termbox" ) // OutputMode represents the terminal's output mode (8 or 256 colors). @@ -162,8 +163,9 @@ func (g *Gui) Rune(x, y int) (rune, error) { if x < 0 || y < 0 || x >= g.maxX || y >= g.maxY { return ' ', errors.New("invalid point") } - c := termbox.CellBuffer()[y*g.maxX+x] - return c.Ch, nil + // c := termbox.CellBuffer()[y*g.maxX+x] + // return c.Ch, nil + return ' ', errors.New("invalid point") } // SetView creates a new view with its top-left corner at (x0, y0) diff --git a/keybinding.go b/keybinding.go index 9b2a14aa..17f638b6 100644 --- a/keybinding.go +++ b/keybinding.go @@ -7,7 +7,8 @@ package gocui import ( "strings" - "github.com/awesome-gocui/termbox-go" + // "github.com/awesome-gocui/termbox-go" + "github.com/gdamore/tcell/termbox" ) // Key represents special keys or keys combinations. @@ -119,31 +120,31 @@ func (kb *keybinding) matchView(v *View) bool { // translations for strings to keys var translate = map[string]Key{ - "F1": KeyF1, - "F2": KeyF2, - "F3": KeyF3, - "F4": KeyF4, - "F5": KeyF5, - "F6": KeyF6, - "F7": KeyF7, - "F8": KeyF8, - "F9": KeyF9, - "F10": KeyF10, - "F11": KeyF11, - "F12": KeyF12, - "Insert": KeyInsert, - "Delete": KeyDelete, - "Home": KeyHome, - "End": KeyEnd, - "Pgup": KeyPgup, - "Pgdn": KeyPgdn, - "ArrowUp": KeyArrowUp, - "ArrowDown": KeyArrowDown, - "ArrowLeft": KeyArrowLeft, - "ArrowRight": KeyArrowRight, - "CtrlTilde": KeyCtrlTilde, - "Ctrl2": KeyCtrl2, - "CtrlSpace": KeyCtrlSpace, + "F1": KeyF1, + "F2": KeyF2, + "F3": KeyF3, + "F4": KeyF4, + "F5": KeyF5, + "F6": KeyF6, + "F7": KeyF7, + "F8": KeyF8, + "F9": KeyF9, + "F10": KeyF10, + "F11": KeyF11, + "F12": KeyF12, + "Insert": KeyInsert, + "Delete": KeyDelete, + "Home": KeyHome, + "End": KeyEnd, + "Pgup": KeyPgup, + "Pgdn": KeyPgdn, + "ArrowUp": KeyArrowUp, + "ArrowDown": KeyArrowDown, + "ArrowLeft": KeyArrowLeft, + "ArrowRight": KeyArrowRight, + // "CtrlTilde": KeyCtrlTilde, + "Ctrl2": KeyCtrl2, + // "CtrlSpace": KeyCtrlSpace, "CtrlA": KeyCtrlA, "CtrlB": KeyCtrlB, "CtrlC": KeyCtrlC, @@ -186,7 +187,7 @@ var translate = map[string]Key{ "CtrlUnderscore": KeyCtrlUnderscore, "Space": KeySpace, "Backspace2": KeyBackspace2, - "Ctrl8": KeyCtrl8, + // "Ctrl8": KeyCtrl8, "Mouseleft": MouseLeft, "Mousemiddle": MouseMiddle, "Mouseright": MouseRight, @@ -230,52 +231,52 @@ const ( // 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) + // 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) ) // Modifiers. diff --git a/view.go b/view.go index 04002ec7..0f69cb5e 100644 --- a/view.go +++ b/view.go @@ -13,7 +13,8 @@ import ( "github.com/go-errors/errors" - "github.com/awesome-gocui/termbox-go" + // "github.com/awesome-gocui/termbox-go" + "github.com/gdamore/tcell/termbox" "github.com/mattn/go-runewidth" ) From ace8fe63a9ad9f0867684fd695332e261bce310f Mon Sep 17 00:00:00 2001 From: danko Date: Wed, 4 Nov 2020 11:34:06 +0000 Subject: [PATCH 22/50] copy of termbox compat from tcell with some changes Changes needed to be done for compat, so it was easier to copy it. Further changes will be done, to remove this file totally or at least adapt it better to gocui. --- attribute.go | 33 ----- go.sum | 1 + gui.go | 73 ++++----- keybinding.go | 99 ------------- tcell_compat.go | 385 ++++++++++++++++++++++++++++++++++++++++++++++++ view.go | 11 +- 6 files changed, 418 insertions(+), 184 deletions(-) delete mode 100644 attribute.go create mode 100644 tcell_compat.go diff --git a/attribute.go b/attribute.go deleted file mode 100644 index 4cd5a69f..00000000 --- a/attribute.go +++ /dev/null @@ -1,33 +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/awesome-gocui/termbox-go" -import "github.com/gdamore/tcell/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) -) diff --git a/go.sum b/go.sum index 8a64094a..12ed2e68 100644 --- a/go.sum +++ b/go.sum @@ -11,6 +11,7 @@ github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756 h1:9nuHUbU8dRnRRfj9KjWUVrJeoexdbeMjttk6Oh1rD10= golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/gui.go b/gui.go index d0a087b2..c6d70987 100644 --- a/gui.go +++ b/gui.go @@ -9,13 +9,10 @@ import ( "runtime" "github.com/go-errors/errors" - - // "github.com/awesome-gocui/termbox-go" - "github.com/gdamore/tcell/termbox" ) // OutputMode represents the terminal's output mode (8 or 256 colors). -type OutputMode termbox.OutputMode +// type OutputMode OutputMode var ( // ErrAlreadyBlacklisted is returned when the keybinding is already blacklisted. @@ -37,24 +34,10 @@ var ( ErrQuit = standardErrors.New("quit") ) -const ( - // OutputNormal provides 8-colors terminal mode. - OutputNormal = OutputMode(termbox.OutputNormal) - - // Output256 provides 256-colors terminal mode. - Output256 = OutputMode(termbox.Output256) - - // OutputGrayScale provides greyscale terminal mode. - OutputGrayScale = OutputMode(termbox.OutputGrayscale) - - // Output216 provides greyscale terminal mode. - Output216 = OutputMode(termbox.Output216) -) - // Gui represents the whole User Interface, including the views, layouts // and keybindings. type Gui struct { - tbEvents chan termbox.Event + tbEvents chan Event userEvents chan userEvent views []*View currentView *View @@ -98,7 +81,7 @@ type Gui struct { // NewGui returns a new Gui object with a given output mode. func NewGui(mode OutputMode, supportOverlaps bool) (*Gui, error) { - err := termbox.Init() + err := Init() if err != nil { return nil, err } @@ -106,11 +89,11 @@ func NewGui(mode OutputMode, supportOverlaps bool) (*Gui, error) { g := &Gui{} g.outputMode = mode - termbox.SetOutputMode(termbox.OutputMode(mode)) + SetOutputMode(OutputMode(mode)) g.stop = make(chan struct{}) - g.tbEvents = make(chan termbox.Event, 20) + g.tbEvents = make(chan Event, 20) g.userEvents = make(chan userEvent, 20) if runtime.GOOS != "windows" { @@ -119,7 +102,7 @@ func NewGui(mode OutputMode, supportOverlaps bool) (*Gui, error) { return nil, err } } else { - g.maxX, g.maxY = termbox.Size() + g.maxX, g.maxY = Size() } g.BgColor, g.FgColor = ColorDefault, ColorDefault @@ -138,7 +121,7 @@ func (g *Gui) Close() { go func() { g.stop <- struct{}{} }() - termbox.Close() + Close() } // Size returns the terminal's size. @@ -153,7 +136,7 @@ func (g *Gui) SetRune(x, y int, ch rune, fgColor, bgColor Attribute) 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)) + SetCell(x, y, ch, Attribute(fgColor), Attribute(bgColor)) return nil } @@ -163,7 +146,7 @@ func (g *Gui) Rune(x, y int) (rune, error) { if x < 0 || y < 0 || x >= g.maxX || y >= g.maxY { return ' ', errors.New("invalid point") } - // c := termbox.CellBuffer()[y*g.maxX+x] + // c := CellBuffer()[y*g.maxX+x] // return c.Ch, nil return ' ', errors.New("invalid point") } @@ -429,7 +412,7 @@ func (g *Gui) SetManager(managers ...Manager) { g.views = nil g.keybindings = nil - go func() { g.tbEvents <- termbox.Event{Type: termbox.EventResize} }() + go func() { g.tbEvents <- Event{Type: EventResize} }() } // SetManagerFunc sets the given manager function. It deletes all views and @@ -452,19 +435,19 @@ func (g *Gui) MainLoop() error { case <-g.stop: return default: - g.tbEvents <- termbox.PollEvent() + g.tbEvents <- PollEvent() } } }() - inputMode := termbox.InputAlt + inputMode := InputAlt if true { // previously g.InputEsc, but didn't seem to work - inputMode = termbox.InputEsc + inputMode = InputEsc } if g.Mouse { - inputMode |= termbox.InputMouse + inputMode |= InputMouse } - termbox.SetInputMode(inputMode) + SetInputMode(inputMode) if err := g.flush(); err != nil { return err @@ -509,11 +492,11 @@ 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 { +func (g *Gui) handleEvent(ev *Event) error { switch ev.Type { - case termbox.EventKey, termbox.EventMouse: + case EventKey, EventMouse: return g.onKey(ev) - case termbox.EventError: + case EventError: return ev.Err default: return nil @@ -522,9 +505,9 @@ 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)) + Clear(Attribute(g.FgColor), Attribute(g.BgColor)) - maxX, maxY := termbox.Size() + maxX, maxY := 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 { @@ -583,7 +566,7 @@ func (g *Gui) flush() error { return err } } - termbox.Flush() + Flush() return nil } @@ -798,13 +781,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) + SetCursor(cx, cy) } else { - termbox.HideCursor() + HideCursor() } } } else { - termbox.HideCursor() + HideCursor() } v.clearRunes() @@ -817,9 +800,9 @@ func (g *Gui) draw(v *View) error { // onKey manages key-press events. A keybinding handler is called when // a key-press or mouse event satisfies a configured keybinding. Furthermore, // currentView's internal buffer is modified if currentView.Editable is true. -func (g *Gui) onKey(ev *termbox.Event) error { +func (g *Gui) onKey(ev *Event) error { switch ev.Type { - case termbox.EventKey: + case EventKey: matched, err := g.execKeybindings(g.currentView, ev) if err != nil { return err @@ -830,7 +813,7 @@ func (g *Gui) onKey(ev *termbox.Event) error { 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)) } - case termbox.EventMouse: + case EventMouse: mx, my := ev.MouseX, ev.MouseY v, err := g.ViewByPosition(mx, my) if err != nil { @@ -849,7 +832,7 @@ func (g *Gui) onKey(ev *termbox.Event) error { // execKeybindings executes the keybinding 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) { +func (g *Gui) execKeybindings(v *View, ev *Event) (matched bool, err error) { var globalKb *keybinding for _, kb := range g.keybindings { diff --git a/keybinding.go b/keybinding.go index 17f638b6..59498ebd 100644 --- a/keybinding.go +++ b/keybinding.go @@ -6,18 +6,8 @@ package gocui import ( "strings" - - // "github.com/awesome-gocui/termbox-go" - "github.com/gdamore/tcell/termbox" ) -// Key represents special keys or keys combinations. -type Key termbox.Key - -// 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 - // Keybidings are used to link a given key-press event with a handler. type keybinding struct { viewName string @@ -195,92 +185,3 @@ var translate = map[string]Key{ "MousewheelUp": MouseWheelUp, "MousewheelDown": MouseWheelDown, } - -// 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) -) - -// Modifiers. -const ( - ModNone Modifier = Modifier(0) - ModAlt = Modifier(termbox.ModAlt) -) diff --git a/tcell_compat.go b/tcell_compat.go new file mode 100644 index 00000000..fe061724 --- /dev/null +++ b/tcell_compat.go @@ -0,0 +1,385 @@ +// 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. + +// This is copy from original github.com/gdamore/tcell/termbox package +// for easier adoption of tcell. +// There are some changes made, to make it work with termbox keys (Ctrl modifier) + +package gocui + +import ( + "errors" + + "github.com/gdamore/tcell" +) + +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.Color(16) + case Output256: + c %= tcell.Color(256) + case Output216: + c %= tcell.Color(216) + c += tcell.Color(16) + case OutputGrayscale: + c %= tcell.Color(24) + c += tcell.Color(232) + default: + c = tcell.ColorDefault + } + return c +} + +func mkStyle(fg, bg Attribute) tcell.Style { + st := tcell.StyleDefault + + f := tcell.Color(int(fg)&0x1ff) - 1 + b := tcell.Color(int(bg)&0x1ff) - 1 + + f = fixColor(f) + b = fixColor(b) + st = st.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 does not do anything in this version. +func SetInputMode(mode InputMode) InputMode { + // 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 +} + +// 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) + 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('~')) + + // 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) + KeyCtrl2 = Key(tcell.KeyNUL) // termbox defines theses + KeyCtrl3 = Key(tcell.KeyEscape) + KeyCtrl4 = Key(tcell.KeyCtrlBackslash) + KeyCtrl5 = Key(tcell.KeyCtrlRightSq) + KeyCtrl6 = Key(tcell.KeyCtrlCarat) + KeyCtrl7 = Key(tcell.KeyCtrlUnderscore) + KeyCtrlSlash = Key(tcell.KeyCtrlUnderscore) + KeyCtrlRsqBracket = Key(tcell.KeyCtrlRightSq) + KeyCtrlBackslash = Key(tcell.KeyCtrlBackslash) + KeyCtrlLsqBracket = Key(tcell.KeyCtrlLeftSq) +) + +// Modifiers. +const ( + ModAlt = Modifier(tcell.ModAlt) + ModNone = Modifier(0) +) + +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(' ') + } + } + mod := tev.Modifiers() + if mod == tcell.ModCtrl { + mod = 0 + } + return Event{ + Type: EventKey, + Key: Key(k), + Ch: ch, + Mod: Modifier(mod), + } + 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 +} diff --git a/view.go b/view.go index 0f69cb5e..b5181923 100644 --- a/view.go +++ b/view.go @@ -12,9 +12,6 @@ import ( "unicode/utf8" "github.com/go-errors/errors" - - // "github.com/awesome-gocui/termbox-go" - "github.com/gdamore/tcell/termbox" "github.com/mattn/go-runewidth" ) @@ -229,8 +226,8 @@ func (v *View) setRune(x, y int, ch rune, fgColor, bgColor Attribute) error { ch = ' ' } - termbox.SetCell(v.x0+x+1, v.y0+y+1, ch, - termbox.Attribute(fgColor), termbox.Attribute(bgColor)) + SetCell(v.x0+x+1, v.y0+y+1, ch, + Attribute(fgColor), Attribute(bgColor)) return nil } @@ -631,8 +628,8 @@ 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)) + SetCell(v.x0+x+1, v.y0+y+1, ' ', + Attribute(v.FgColor), Attribute(v.BgColor)) } } } From 83d33e6bd9d0f68054a86c3e3a46e0e21133c565 Mon Sep 17 00:00:00 2001 From: danko Date: Wed, 4 Nov 2020 11:34:22 +0000 Subject: [PATCH 23/50] add debug configuration --- .vscode/launch.json | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..50f2c01b --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,31 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + + "version": "0.2.0", + "configurations": [ + { + // for remote run this command in terminal: + // dlv debug --headless --listen=:2345 --log --api-version 2 + "name": "Connect to server", + "type": "go", + "request": "launch", + "mode": "remote", + "program": "${workspaceRoot}", + "remotePath": "${workspaceFolder}", + "port": 2345, + "host": "127.0.0.1" + }, + { + "name": "Launch", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${fileDirname}", + // "program": "${workspaceFolder}/_examples/colors256.go", + "env": {}, + "args": [] + } + ] +} \ No newline at end of file From a4fe1ef86a804c00c72b63b6135b4dcafc8b6204 Mon Sep 17 00:00:00 2001 From: danko Date: Wed, 4 Nov 2020 11:52:31 +0000 Subject: [PATCH 24/50] return back CtrlC (it works now) --- _examples/colors256.go | 4 ---- _examples/dynamic.go | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/_examples/colors256.go b/_examples/colors256.go index 1056679d..245b7f7e 100644 --- a/_examples/colors256.go +++ b/_examples/colors256.go @@ -25,10 +25,6 @@ func main() { log.Panicln(err) } - if err := g.SetKeybinding("", gocui.KeyF10, gocui.ModNone, quit); err != nil { - log.Panicln(err) - } - if err := g.MainLoop(); err != nil && !gocui.IsQuit(err) { log.Panicln(err) } diff --git a/_examples/dynamic.go b/_examples/dynamic.go index 9f3bc670..d73d1178 100644 --- a/_examples/dynamic.go +++ b/_examples/dynamic.go @@ -64,7 +64,7 @@ func layout(g *gocui.Gui) error { } func initKeybindings(g *gocui.Gui) error { - if err := g.SetKeybinding("", gocui.KeyF10, gocui.ModNone, + if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error { return gocui.ErrQuit }); err != nil { From 5f6ae5b9caa1143296ece8cf3a45d2406a1e1a85 Mon Sep 17 00:00:00 2001 From: danko Date: Wed, 4 Nov 2020 12:19:52 +0000 Subject: [PATCH 25/50] fix CtrlSpace and Spacebar keys --- tcell_compat.go | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/tcell_compat.go b/tcell_compat.go index fe061724..b970389b 100644 --- a/tcell_compat.go +++ b/tcell_compat.go @@ -292,8 +292,11 @@ const ( KeyEsc = Key(tcell.KeyEscape) KeyPgdn = Key(tcell.KeyPgDn) KeyPgup = Key(tcell.KeyPgUp) - KeySpace = Key(tcell.Key(' ')) - KeyTilde = Key(tcell.Key('~')) + KeyCtrlSpace = Key(tcell.KeyCtrlSpace) + // KeySpace = Key(tcell.Key(' ')) + KeySpace = ' ' + // KeyTilde = Key(tcell.Key('~')) + KeyTilde = '~' // The following assignments are provided for termbox // compatibility. Their use in applications is discouraged. @@ -334,13 +337,15 @@ func makeEvent(tev tcell.Event) Event { k := tev.Key() ch := rune(0) if k == tcell.KeyRune { + k = 0 // if rune remove key (so it can match, for example spacebar) ch = tev.Rune() - if ch == ' ' { - k = tcell.Key(' ') - } } mod := tev.Modifiers() - if mod == tcell.ModCtrl { + // remove control modifier and setup special handling of ctrl+spacebar, etc. + if mod == tcell.ModCtrl && k == 0 && ch == 0 { + mod = 0 + k = tcell.KeyCtrlSpace + } else if mod == tcell.ModCtrl { mod = 0 } return Event{ From db74815b815e16c1860ed82227f91bd589f1f31d Mon Sep 17 00:00:00 2001 From: danko Date: Wed, 4 Nov 2020 12:39:55 +0000 Subject: [PATCH 26/50] fix widget example (coloring) --- _examples/widgets.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_examples/widgets.go b/_examples/widgets.go index 331bfc28..aaabaa1b 100644 --- a/_examples/widgets.go +++ b/_examples/widgets.go @@ -121,7 +121,7 @@ func main() { defer g.Close() g.Highlight = true - g.SelFgColor = gocui.ColorRed + g.SelFrameColor = gocui.ColorRed help := NewHelpWidget("help", 1, 1, helpText) status := NewStatusbarWidget("status", 1, 7, 50) From 462a239cc00ec971ad1d449a74e740a2f7039586 Mon Sep 17 00:00:00 2001 From: danko Date: Thu, 5 Nov 2020 21:02:26 +0000 Subject: [PATCH 27/50] True colors implementation with example OutputTrue mode implemented to use tcell full color option. Color values redefined according to tcell. Constant names remains, values and type is changed. This may break compatibility if somebody was using Attribute as int, because now it's int64, so converting to int might lose attributes. Example with true color display added. --- _examples/colorstrue.go | 95 +++++++++++++++++++++++++++++++++++++++++ escape.go | 86 ++++++++++++++++++++++++++++++++++--- go.mod | 1 - gui.go | 4 +- tcell_compat.go | 66 +++++++++++++++++++++------- view.go | 4 ++ 6 files changed, 231 insertions(+), 25 deletions(-) create mode 100644 _examples/colorstrue.go diff --git a/_examples/colorstrue.go b/_examples/colorstrue.go new file mode 100644 index 00000000..70aa5901 --- /dev/null +++ b/_examples/colorstrue.go @@ -0,0 +1,95 @@ +// 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 main + +import ( + "fmt" + "log" + + "github.com/awesome-gocui/gocui" + colorful "github.com/lucasb-eyer/go-colorful" +) + +var dark = false + +func main() { + g, err := gocui.NewGui(gocui.OutputTrue, true) + + if err != nil { + log.Panicln(err) + } + defer g.Close() + + g.SetManagerFunc(layout) + + if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil { + log.Panicln(err) + } + + if err := g.SetKeybinding("", gocui.KeyCtrlR, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error { + if dark { + dark = false + } else { + dark = true + } + displayHsv(v) + + return nil + }); err != nil { + log.Panicln(err) + } + + if err := g.MainLoop(); err != nil && !gocui.IsQuit(err) { + log.Panicln(err) + } +} + +func layout(g *gocui.Gui) error { + maxX, maxY := g.Size() + if v, err := g.SetView("colors", -1, -1, maxX, maxY, 0); err != nil { + if !gocui.IsUnknownView(err) { + return err + } + + displayHsv(v) + + if _, err := g.SetCurrentView("colors"); err != nil { + return err + } + } + return nil +} + +func displayHsv(v *gocui.View) { + v.Clear() + str := "" + // HSV color space (lines are value or saturation) + for i := 50; i > 0; i -= 2 { + // Hue + for j := 0; j < 360; j += 2 { + ir, ig, ib := hsv(j, i-1) + ir2, ig2, ib2 := hsv(j, i) + str += fmt.Sprintf("\x1b[48;2;%d;%d;%dm\x1b[38;2;%d;%d;%dm▀\x1b[0m", ir, ig, ib, ir2, ig2, ib2) + } + str += "\n" + fmt.Fprint(v, str) + str = "" + } + + fmt.Fprint(v, "\nCtrl + R - Switch light/dark mode\n") +} + +func hsv(hue, sv int) (uint32, uint32, uint32) { + if !dark { + ir, ig, ib, _ := colorful.Hsv(float64(hue), float64(sv)/50, float64(1)).RGBA() + return ir >> 8, ig >> 8, ib >> 8 + } + ir, ig, ib, _ := colorful.Hsv(float64(hue), float64(1), float64(sv)/50).RGBA() + return ir >> 8, ig >> 8, ib >> 8 +} + +func quit(g *gocui.Gui, v *gocui.View) error { + return gocui.ErrQuit +} diff --git a/escape.go b/escape.go index 64360802..55436d33 100644 --- a/escape.go +++ b/escape.go @@ -7,6 +7,7 @@ package gocui import ( "strconv" + "github.com/gdamore/tcell" "github.com/go-errors/errors" ) @@ -135,6 +136,8 @@ func (ei *escapeInterpreter) parseOne(ch rune) (isEscape bool, err error) { err = ei.outputNormal() case Output256: err = ei.output256() + case OutputTrue: + err = ei.outputTrue() } if err != nil { return false, errCSIParseError @@ -161,11 +164,11 @@ func (ei *escapeInterpreter) outputNormal() error { switch { case p >= 30 && p <= 37: - ei.curFgColor = Attribute(p - 30 + 1) + ei.curFgColor = Attribute(p - 30) case p == 39: ei.curFgColor = ColorDefault case p >= 40 && p <= 47: - ei.curBgColor = Attribute(p - 40 + 1) + ei.curBgColor = Attribute(p - 40) case p == 49: ei.curBgColor = ColorDefault case p == 1: @@ -201,7 +204,7 @@ func (ei *escapeInterpreter) output256() error { return ei.outputNormal() } - for _, param := range splitFgBg(ei.csiParam) { + for _, param := range splitFgBg(ei.csiParam, 3) { fgbg, err := strconv.Atoi(param[0]) if err != nil { return errCSIParseError @@ -213,7 +216,7 @@ func (ei *escapeInterpreter) output256() error { switch fontEffect(fgbg) { case setForegroundColor: - ei.curFgColor = Attribute(color + 1) + ei.curFgColor = Attribute(color) for _, s := range param[3:] { p, err := strconv.Atoi(s) @@ -232,7 +235,7 @@ func (ei *escapeInterpreter) output256() error { } } case setBackgroundColor: - ei.curBgColor = Attribute(color + 1) + ei.curBgColor = Attribute(color) default: return errCSIParseError } @@ -240,11 +243,80 @@ func (ei *escapeInterpreter) output256() error { return nil } -func splitFgBg(params []string) [][]string { +// outputTrue allows you to leverage the true-color terminal mode. +// +// Works with rgb ANSI sequence: `\x1b[38;2;;;m`, `\x1b[48;2;;;m` +func (ei *escapeInterpreter) outputTrue() error { + if len(ei.csiParam) < 5 { + return ei.output256() + } + + mode, err := strconv.Atoi(ei.csiParam[1]) + if err != nil { + return errCSIParseError + } + if mode != 2 { + return ei.output256() + } + + for _, param := range splitFgBg(ei.csiParam, 5) { + fgbg, err := strconv.Atoi(param[0]) + if err != nil { + return errCSIParseError + } + colr, err := strconv.Atoi(param[2]) + if err != nil { + return errCSIParseError + } + colg, err := strconv.Atoi(param[3]) + if err != nil { + return errCSIParseError + } + colb, err := strconv.Atoi(param[4]) + if err != nil { + return errCSIParseError + } + color := tcell.NewRGBColor(int32(colr), int32(colg), int32(colb)).Hex() + + switch fontEffect(fgbg) { + case setForegroundColor: + if color != 0 { + } + ei.curFgColor = Attribute(color) | AttrIsRGBColor + + for _, s := range param[5:] { + p, err := strconv.Atoi(s) + if err != nil { + return errCSIParseError + } + + switch fontEffect(p) { + case bold: + ei.curFgColor |= AttrBold + case underline: + ei.curFgColor |= AttrUnderline + case reverse: + ei.curFgColor |= AttrReverse + + } + } + case setBackgroundColor: + ei.curBgColor = Attribute(color) | AttrIsRGBColor + default: + return errCSIParseError + } + } + return nil +} + +// splitFgBg splits foreground and background color according to ANSI sequence. +// +// num (number of segments in ansi) is used to determine if it's 256 mode or rgb mode (3 - 256-color, 5 - rgb-color) +func splitFgBg(params []string, num int) [][]string { var out [][]string var current []string for _, p := range params { - if len(current) == 3 && (p == "48" || p == "38") { + if len(current) == num && (p == "48" || p == "38") { out = append(out, current) current = []string{} } diff --git a/go.mod b/go.mod index 257ef2bb..2aa9d219 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/awesome-gocui/gocui go 1.12 require ( - github.com/awesome-gocui/termbox-go v0.0.0-20190427202837-c0aef3d18bcc github.com/gdamore/tcell v1.4.0 github.com/go-errors/errors v1.0.2 github.com/mattn/go-runewidth v0.0.9 diff --git a/gui.go b/gui.go index c6d70987..8c524686 100644 --- a/gui.go +++ b/gui.go @@ -105,8 +105,8 @@ func NewGui(mode OutputMode, supportOverlaps bool) (*Gui, error) { g.maxX, g.maxY = Size() } - g.BgColor, g.FgColor = ColorDefault, ColorDefault - g.SelBgColor, g.SelFgColor = ColorDefault, ColorDefault + g.BgColor, g.FgColor, g.FrameColor = ColorDefault, ColorDefault, ColorDefault + g.SelBgColor, g.SelFgColor, g.SelFrameColor = ColorDefault, ColorDefault, ColorDefault // SupportOverlaps is true when we allow for view edges to overlap with other // view edges diff --git a/tcell_compat.go b/tcell_compat.go index b970389b..5a82d7c1 100644 --- a/tcell_compat.go +++ b/tcell_compat.go @@ -68,12 +68,22 @@ func Size() (int, int) { // Attribute affects the presentation of characters, such as color, boldness, // and so forth. -type Attribute uint16 +type Attribute int64 + +const ( + // ColorDefault is used to leave the Color unchanged from whatever system or teminal default may exist. + ColorDefault = Attribute(tcell.ColorDefault) + // AttrIsRGBColor is used to indicate that the Attribute value is RGB value of color. + // The lower order 3 bytes are RGB. + // (It's not a color in basic ANSI range 256). + AttrIsRGBColor = Attribute(tcell.ColorIsRGB) + // AttrColorBits is a mask where color is located in Attribute (unless it's -1 => default) + AttrColorBits = 0x1ffffff +) // Colors first. The order here is significant. const ( - ColorDefault Attribute = iota - ColorBlack + ColorBlack Attribute = iota ColorRed ColorGreen ColorYellow @@ -83,13 +93,21 @@ const ( ColorWhite ) -// Other attributes. +// Attributes are not colors, but affect the display of text. They can +// be combined. const ( - AttrBold Attribute = 1 << (9 + iota) - AttrUnderline + AttrBold Attribute = 1 << (25 + iota) + AttrBlink AttrReverse + AttrUnderline + AttrDim + AttrItalic + AttrNone Attribute = 0 // Just normal text. ) +// AttrAll is all the attributes turned on +const AttrAll = AttrBold | AttrBlink | AttrReverse | AttrUnderline | AttrDim | AttrItalic + func fixColor(c tcell.Color) tcell.Color { if c == tcell.ColorDefault { return c @@ -114,21 +132,38 @@ func fixColor(c tcell.Color) tcell.Color { func mkStyle(fg, bg Attribute) tcell.Style { st := tcell.StyleDefault - f := tcell.Color(int(fg)&0x1ff) - 1 - b := tcell.Color(int(bg)&0x1ff) - 1 + // extract colors and attributes + if fg != ColorDefault { + st = st.Foreground(tcell.Color(fg & AttrColorBits)) + st = setAttr(st, fg) + } + if bg != ColorDefault { + st = st.Background(tcell.Color(bg & AttrColorBits)) + st = setAttr(st, bg) + } + + return st +} - f = fixColor(f) - b = fixColor(b) - st = st.Foreground(f).Background(b) - if (fg|bg)&AttrBold != 0 { +func setAttr(st tcell.Style, attr Attribute) tcell.Style { + if attr&AttrBold != 0 { st = st.Bold(true) } - if (fg|bg)&AttrUnderline != 0 { + if attr&AttrUnderline != 0 { st = st.Underline(true) } - if (fg|bg)&AttrReverse != 0 { + if attr&AttrReverse != 0 { st = st.Reverse(true) } + if attr&AttrBlink != 0 { + st = st.Blink(true) + } + if attr&AttrDim != 0 { + st = st.Dim(true) + } + if attr&AttrItalic != 0 { + st = st.Italic(true) + } return st } @@ -171,6 +206,7 @@ const ( Output256 Output216 OutputGrayscale + OutputTrue ) // SetOutputMode is used to set the color palette used. @@ -181,7 +217,7 @@ func SetOutputMode(mode OutputMode) OutputMode { switch mode { case OutputCurrent: return outMode - case OutputNormal, Output256, Output216, OutputGrayscale: + case OutputNormal, Output256, Output216, OutputGrayscale, OutputTrue: outMode = mode return mode default: diff --git a/view.go b/view.go index b5181923..ab7da031 100644 --- a/view.go +++ b/view.go @@ -171,6 +171,10 @@ func newView(name string, x0, y0, x1, y1 int, mode OutputMode) *View { tainted: true, ei: newEscapeInterpreter(mode), } + + v.FgColor, v.BgColor = ColorDefault, ColorDefault + v.SelFgColor, v.SelBgColor = ColorDefault, ColorDefault + v.TitleColor, v.FrameColor = ColorDefault, ColorDefault return v } From 8e73c554e4fa326e32496c17928c255d9c151d5c Mon Sep 17 00:00:00 2001 From: danko Date: Thu, 5 Nov 2020 21:55:40 +0000 Subject: [PATCH 28/50] fix colors for lower output modes some minor fixes in examples --- _examples/colorstrue.go | 4 +++- _examples/dynamic.go | 1 + tcell_compat.go | 6 ++++-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/_examples/colorstrue.go b/_examples/colorstrue.go index 70aa5901..87edb751 100644 --- a/_examples/colorstrue.go +++ b/_examples/colorstrue.go @@ -78,7 +78,9 @@ func displayHsv(v *gocui.View) { str = "" } - fmt.Fprint(v, "\nCtrl + R - Switch light/dark mode\n") + fmt.Fprintln(v, "\n\x1b[38;5;245mCtrl + R - Switch light/dark mode") + fmt.Fprintln(v, "\nCtrl + C - Exit\n") + fmt.Fprint(v, "To enable true colors in terminal run this command: \x1b[0mexport COLORTERM=truecolor") } func hsv(hue, sv int) (uint32, uint32, uint32) { diff --git a/_examples/dynamic.go b/_examples/dynamic.go index d73d1178..b1a39d49 100644 --- a/_examples/dynamic.go +++ b/_examples/dynamic.go @@ -29,6 +29,7 @@ func main() { g.Highlight = true g.SelFgColor = gocui.ColorRed + g.SelFrameColor = gocui.ColorRed g.SetManagerFunc(layout) diff --git a/tcell_compat.go b/tcell_compat.go index 5a82d7c1..3360e200 100644 --- a/tcell_compat.go +++ b/tcell_compat.go @@ -113,6 +113,8 @@ func fixColor(c tcell.Color) tcell.Color { return c } switch outMode { + case OutputTrue: + return c case OutputNormal: c %= tcell.Color(16) case Output256: @@ -134,11 +136,11 @@ func mkStyle(fg, bg Attribute) tcell.Style { // extract colors and attributes if fg != ColorDefault { - st = st.Foreground(tcell.Color(fg & AttrColorBits)) + st = st.Foreground(fixColor(tcell.Color(fg & AttrColorBits))) st = setAttr(st, fg) } if bg != ColorDefault { - st = st.Background(tcell.Color(bg & AttrColorBits)) + st = st.Background(fixColor(tcell.Color(bg & AttrColorBits))) st = setAttr(st, bg) } From a78cd5e3103163573e3d48f7dcf821abd2b4472c Mon Sep 17 00:00:00 2001 From: danko Date: Sat, 7 Nov 2020 15:51:42 +0000 Subject: [PATCH 29/50] Fix colorstrue example terminal settings Added new function to easily create color attribute. --- _examples/colorstrue.go | 16 ++++++++++++++-- tcell_compat.go | 6 ++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/_examples/colorstrue.go b/_examples/colorstrue.go index 87edb751..865a1b4e 100644 --- a/_examples/colorstrue.go +++ b/_examples/colorstrue.go @@ -7,6 +7,7 @@ package main import ( "fmt" "log" + "os" "github.com/awesome-gocui/gocui" colorful "github.com/lucasb-eyer/go-colorful" @@ -15,6 +16,7 @@ import ( var dark = false func main() { + os.Setenv("COLORTERM", "truecolor") g, err := gocui.NewGui(gocui.OutputTrue, true) if err != nil { @@ -48,11 +50,21 @@ func main() { func layout(g *gocui.Gui) error { maxX, maxY := g.Size() - if v, err := g.SetView("colors", -1, -1, maxX, maxY, 0); err != nil { + rows := 33 + cols := 182 + if maxY < rows { + rows = maxY + } + if maxX < cols { + cols = maxX + } + + if v, err := g.SetView("colors", 0, 0, cols-1, rows-1, 0); err != nil { if !gocui.IsUnknownView(err) { return err } + v.FrameColor = gocui.GetColor("#FFAA55") displayHsv(v) if _, err := g.SetCurrentView("colors"); err != nil { @@ -80,7 +92,7 @@ func displayHsv(v *gocui.View) { fmt.Fprintln(v, "\n\x1b[38;5;245mCtrl + R - Switch light/dark mode") fmt.Fprintln(v, "\nCtrl + C - Exit\n") - fmt.Fprint(v, "To enable true colors in terminal run this command: \x1b[0mexport COLORTERM=truecolor") + fmt.Fprint(v, "Example should enable true color, but if it doesn't work run this command: \x1b[0mexport COLORTERM=truecolor") } func hsv(hue, sv int) (uint32, uint32, uint32) { diff --git a/tcell_compat.go b/tcell_compat.go index 3360e200..6fee675a 100644 --- a/tcell_compat.go +++ b/tcell_compat.go @@ -169,6 +169,12 @@ func setAttr(st tcell.Style, attr Attribute) tcell.Style { return st } +// GetColor creates a Color from a color name (W3C name). A hex value may +// be supplied as a string in the format "#ffffff". +func GetColor(color string) Attribute { + return Attribute(tcell.GetColor(color)) +} + // Clear clears the screen with the given attributes. func Clear(fg, bg Attribute) { st := mkStyle(fg, bg) From 7a7f847b0e2f11c337347462c4d48a223a2d3119 Mon Sep 17 00:00:00 2001 From: danko Date: Sat, 7 Nov 2020 16:05:54 +0000 Subject: [PATCH 30/50] Add mouse event support --- _examples/mouse.go | 11 +++++++- tcell_compat.go | 69 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 76 insertions(+), 4 deletions(-) diff --git a/_examples/mouse.go b/_examples/mouse.go index 7226281d..e397e11a 100644 --- a/_examples/mouse.go +++ b/_examples/mouse.go @@ -72,6 +72,14 @@ func keybindings(g *gocui.Gui) error { if err := g.SetKeybinding("msg", gocui.MouseLeft, gocui.ModNone, delMsg); err != nil { return err } + // These keys are global, but they work only when you click them while hovering over View. + // ModCtrl is actually `ALT` key, not `CTRL` key. Tcell generates it like that, not sure why. + if err := g.SetKeybinding("", gocui.MouseRight, gocui.ModCtrl, delMsg); err != nil { + return err + } + if err := g.SetKeybinding("", gocui.MouseMiddle, gocui.ModNone, delMsg); err != nil { + return err + } return nil } @@ -104,7 +112,8 @@ func showMsg(g *gocui.Gui, v *gocui.View) error { func delMsg(g *gocui.Gui, v *gocui.View) error { if err := g.DeleteView("msg"); err != nil { - return err + // Error removed for testing purpose, because delete could be called multiple times with the above keybindings + // return err } return nil } diff --git a/tcell_compat.go b/tcell_compat.go index 6fee675a..15fcfbc4 100644 --- a/tcell_compat.go +++ b/tcell_compat.go @@ -191,14 +191,18 @@ type InputMode int // Unused input modes; here for compatibility. const ( - InputCurrent InputMode = iota - InputEsc + InputEsc InputMode = 1 << iota InputAlt InputMouse + InputCurrent InputMode = 0 ) // SetInputMode does not do anything in this version. func SetInputMode(mode InputMode) InputMode { + if mode&InputMouse != 0 { + screen.EnableMouse() + return InputEsc | InputMouse + } // We don't do anything else right now return InputEsc } @@ -352,6 +356,8 @@ const ( MouseRelease = Key(tcell.KeyF60) MouseWheelUp = Key(tcell.KeyF59) MouseWheelDown = Key(tcell.KeyF58) + MouseWheelLeft = Key(tcell.KeyF57) + MouseWheelRight = Key(tcell.KeyF56) KeyCtrl2 = Key(tcell.KeyNUL) // termbox defines theses KeyCtrl3 = Key(tcell.KeyEscape) KeyCtrl4 = Key(tcell.KeyCtrlBackslash) @@ -364,9 +370,16 @@ const ( KeyCtrlLsqBracket = Key(tcell.KeyCtrlLeftSq) ) +var ( + lastMouseKey tcell.ButtonMask = tcell.ButtonNone + lastMouseMod tcell.ModMask = tcell.ModNone +) + // Modifiers. const ( - ModAlt = Modifier(tcell.ModAlt) + ModAlt = Modifier(tcell.ModAlt) + // ModCtrl doesn't work with keyboard keys. Use CtrlKey in Key and ModNone. This is for mouse clicks only + ModCtrl = Modifier(tcell.ModCtrl) ModNone = Modifier(0) ) @@ -398,6 +411,56 @@ func makeEvent(tev tcell.Event) Event { Ch: ch, Mod: Modifier(mod), } + case *tcell.EventMouse: + x, y := tev.Position() + button := tev.Buttons() + mouseKey := MouseRelease + mouseMod := ModNone + if button&tcell.WheelUp != 0 { + mouseKey = MouseWheelUp + } + if button&tcell.WheelDown != 0 { + mouseKey = MouseWheelDown + } + if button&tcell.WheelLeft != 0 { + mouseKey = MouseWheelLeft + } + if button&tcell.WheelRight != 0 { + mouseKey = MouseWheelRight + } + + // Only process button events, not wheel events + button &= tcell.ButtonMask(0xff) + if button != tcell.ButtonNone && lastMouseKey == tcell.ButtonNone { + lastMouseKey = button + lastMouseMod = tev.Modifiers() + } + + switch tev.Buttons() { + case tcell.ButtonNone: + if lastMouseKey != tcell.ButtonNone { + switch lastMouseKey { + case tcell.Button1: + mouseKey = MouseLeft + case tcell.Button2: + mouseKey = MouseMiddle + case tcell.Button3: + mouseKey = MouseRight + } + mouseMod = Modifier(lastMouseMod) + lastMouseMod = tcell.ModNone + lastMouseKey = tcell.ButtonNone + } + } + + return Event{ + Type: EventMouse, + MouseX: x, + MouseY: y, + Key: mouseKey, + Ch: 0, + Mod: mouseMod, + } default: return Event{Type: EventNone} } From a81de49b43c16c3f9ce67b208888c481fed20329 Mon Sep 17 00:00:00 2001 From: danko Date: Sat, 7 Nov 2020 19:28:23 +0000 Subject: [PATCH 31/50] Change to tcell v2 Change to newer tcell, which has some breaking changes. Color representation is different, so it needed to be adjusted. New mouse events to better represent correct mouse buttons. Some minor addition to ANSI attributes (dim, blink, italic, etc.). --- _examples/mouse.go | 4 +- escape.go | 58 ++++++++++++------------ go.mod | 2 +- go.sum | 4 +- gui.go | 3 ++ tcell_compat.go | 108 ++++++++++++++++++++------------------------- 6 files changed, 88 insertions(+), 91 deletions(-) diff --git a/_examples/mouse.go b/_examples/mouse.go index e397e11a..e06676c8 100644 --- a/_examples/mouse.go +++ b/_examples/mouse.go @@ -73,8 +73,8 @@ func keybindings(g *gocui.Gui) error { return err } // These keys are global, but they work only when you click them while hovering over View. - // ModCtrl is actually `ALT` key, not `CTRL` key. Tcell generates it like that, not sure why. - if err := g.SetKeybinding("", gocui.MouseRight, gocui.ModCtrl, delMsg); err != nil { + // ModAlt is actually `CTRL` key, not `ALT` key. Tcell generates it like that, not sure why. + if err := g.SetKeybinding("", gocui.MouseRight, gocui.ModAlt, delMsg); err != nil { return err } if err := g.SetKeybinding("", gocui.MouseMiddle, gocui.ModNone, delMsg); err != nil { diff --git a/escape.go b/escape.go index 55436d33..e391acd8 100644 --- a/escape.go +++ b/escape.go @@ -7,7 +7,7 @@ package gocui import ( "strconv" - "github.com/gdamore/tcell" + "github.com/gdamore/tcell/v2" "github.com/go-errors/errors" ) @@ -31,7 +31,10 @@ const ( stateParams bold fontEffect = 1 + faint fontEffect = 2 + italic fontEffect = 3 underline fontEffect = 4 + blink fontEffect = 5 reverse fontEffect = 7 setForegroundColor fontEffect = 38 setBackgroundColor fontEffect = 48 @@ -171,15 +174,11 @@ func (ei *escapeInterpreter) outputNormal() error { ei.curBgColor = Attribute(p - 40) case p == 49: ei.curBgColor = ColorDefault - case p == 1: - ei.curFgColor |= AttrBold - case p == 4: - ei.curFgColor |= AttrUnderline - case p == 7: - ei.curFgColor |= AttrReverse case p == 0: ei.curFgColor = ColorDefault ei.curBgColor = ColorDefault + default: + ei.curFgColor |= getFontEffect(p) } } @@ -224,15 +223,7 @@ func (ei *escapeInterpreter) output256() error { return errCSIParseError } - switch fontEffect(p) { - case bold: - ei.curFgColor |= AttrBold - case underline: - ei.curFgColor |= AttrUnderline - case reverse: - ei.curFgColor |= AttrReverse - - } + ei.curFgColor |= getFontEffect(p) } case setBackgroundColor: ei.curBgColor = Attribute(color) @@ -280,9 +271,12 @@ func (ei *escapeInterpreter) outputTrue() error { switch fontEffect(fgbg) { case setForegroundColor: - if color != 0 { + if color == -1 { + // This shouldn't happen ever, but Hex() could return it, so rather safe than sorry + ei.curFgColor = ColorDefault + } else { + ei.curFgColor = Attribute(color) | AttrIsRGBColor } - ei.curFgColor = Attribute(color) | AttrIsRGBColor for _, s := range param[5:] { p, err := strconv.Atoi(s) @@ -290,15 +284,7 @@ func (ei *escapeInterpreter) outputTrue() error { return errCSIParseError } - switch fontEffect(p) { - case bold: - ei.curFgColor |= AttrBold - case underline: - ei.curFgColor |= AttrUnderline - case reverse: - ei.curFgColor |= AttrReverse - - } + ei.curFgColor |= getFontEffect(p) } case setBackgroundColor: ei.curBgColor = Attribute(color) | AttrIsRGBColor @@ -329,3 +315,21 @@ func splitFgBg(params []string, num int) [][]string { return out } + +func getFontEffect(f int) Attribute { + switch fontEffect(f) { + case bold: + return AttrBold + case faint: + return AttrDim + case italic: + return AttrItalic + case underline: + return AttrUnderline + case blink: + return AttrBlink + case reverse: + return AttrReverse + } + return AttrNone +} diff --git a/go.mod b/go.mod index 2aa9d219..cc2fd06c 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/awesome-gocui/gocui go 1.12 require ( - github.com/gdamore/tcell v1.4.0 + github.com/gdamore/tcell/v2 v2.0.0 github.com/go-errors/errors v1.0.2 github.com/mattn/go-runewidth v0.0.9 ) diff --git a/go.sum b/go.sum index 12ed2e68..5fed2788 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,8 @@ github.com/awesome-gocui/termbox-go v0.0.0-20190427202837-c0aef3d18bcc h1:wGNpKc github.com/awesome-gocui/termbox-go v0.0.0-20190427202837-c0aef3d18bcc/go.mod h1:tOy3o5Nf1bA17mnK4W41gD7PS3u4Cv0P0pqFcoWMy8s= github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= -github.com/gdamore/tcell v1.4.0 h1:vUnHwJRvcPQa3tzi+0QI4U9JINXYJlOz9yiaiPQ2wMU= -github.com/gdamore/tcell v1.4.0/go.mod h1:vxEiSDZdW3L+Uhjii9c3375IlDmR05bzxY404ZVSMo0= +github.com/gdamore/tcell/v2 v2.0.0 h1:GRWG8aLfWAlekj9Q6W29bVvkHENc6hp79XOqG4AWDOs= +github.com/gdamore/tcell/v2 v2.0.0/go.mod h1:vSVL/GV5mCSlPC6thFP5kfOFdM9MGZcalipmpTxTgQA= github.com/go-errors/errors v1.0.2 h1:xMxH9j2fNg/L4hLn/4y3M0IUsn0M6Wbu/Uh9QlOfBh4= github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs= github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac= diff --git a/gui.go b/gui.go index 8c524686..861eae39 100644 --- a/gui.go +++ b/gui.go @@ -498,6 +498,9 @@ func (g *Gui) handleEvent(ev *Event) error { return g.onKey(ev) case EventError: return ev.Err + // Not sure if this should be handled. It acts weirder when it's here + // case EventResize: + // return Sync() default: return nil } diff --git a/tcell_compat.go b/tcell_compat.go index 15fcfbc4..8f4f6944 100644 --- a/tcell_compat.go +++ b/tcell_compat.go @@ -19,9 +19,7 @@ package gocui import ( - "errors" - - "github.com/gdamore/tcell" + "github.com/gdamore/tcell/v2" ) var screen tcell.Screen @@ -68,17 +66,22 @@ func Size() (int, int) { // Attribute affects the presentation of characters, such as color, boldness, // and so forth. -type Attribute int64 +type Attribute uint64 const ( // ColorDefault is used to leave the Color unchanged from whatever system or teminal default may exist. - ColorDefault = Attribute(tcell.ColorDefault) + ColorDefault = Attribute(tcell.ColorValid) // We are going to use reverse method to see if color is setup than tcell does + // AttrIsRGBColor is used to indicate that the Attribute value is RGB value of color. // The lower order 3 bytes are RGB. // (It's not a color in basic ANSI range 256). AttrIsRGBColor = Attribute(tcell.ColorIsRGB) - // AttrColorBits is a mask where color is located in Attribute (unless it's -1 => default) - AttrColorBits = 0x1ffffff + + // AttrColorBits is a mask where color is located in Attribute + AttrColorBits = 0xffffffffff // roughly 5 bytes, tcell uses 4 bytes and half-byte as a special flags for color (rest is reserved for future) + + // AttrStyleBits is a mask where character attributes (e.g.: bold, italic, underline) are located in Attribute + AttrStyleBits = 0xffffff0000000000 // remaining 3 bytes in the 8 bytes Attribute (tcell is not using it, so we should be fine) ) // Colors first. The order here is significant. @@ -96,7 +99,7 @@ const ( // Attributes are not colors, but affect the display of text. They can // be combined. const ( - AttrBold Attribute = 1 << (25 + iota) + AttrBold Attribute = 1 << (40 + iota) AttrBlink AttrReverse AttrUnderline @@ -108,27 +111,31 @@ const ( // AttrAll is all the attributes turned on const AttrAll = AttrBold | AttrBlink | AttrReverse | AttrUnderline | AttrDim | AttrItalic -func fixColor(c tcell.Color) tcell.Color { - if c == tcell.ColorDefault { - return c +// fixColor transform Attribute into tcell.Color +func fixColor(c Attribute) tcell.Color { + c = c & AttrColorBits + if c&ColorDefault != 0 { + return tcell.ColorDefault } + + tc := tcell.Color(c) switch outMode { case OutputTrue: - return c + return tc | tcell.ColorValid case OutputNormal: - c %= tcell.Color(16) + tc %= tcell.Color(16) case Output256: - c %= tcell.Color(256) + tc %= tcell.Color(256) case Output216: - c %= tcell.Color(216) - c += tcell.Color(16) + tc %= tcell.Color(216) + tc += tcell.Color(16) case OutputGrayscale: - c %= tcell.Color(24) - c += tcell.Color(232) + tc %= tcell.Color(24) + tc += tcell.Color(232) default: - c = tcell.ColorDefault + return tcell.ColorDefault } - return c + return tc | tcell.ColorValid } func mkStyle(fg, bg Attribute) tcell.Style { @@ -136,11 +143,11 @@ func mkStyle(fg, bg Attribute) tcell.Style { // extract colors and attributes if fg != ColorDefault { - st = st.Foreground(fixColor(tcell.Color(fg & AttrColorBits))) + st = st.Foreground(fixColor(fg)) st = setAttr(st, fg) } if bg != ColorDefault { - st = st.Background(fixColor(tcell.Color(bg & AttrColorBits))) + st = st.Background(fixColor(bg)) st = setAttr(st, bg) } @@ -172,7 +179,14 @@ func setAttr(st tcell.Style, attr Attribute) tcell.Style { // GetColor creates a Color from a color name (W3C name). A hex value may // be supplied as a string in the format "#ffffff". func GetColor(color string) Attribute { - return Attribute(tcell.GetColor(color)) + c := tcell.GetColor(color) + if c&tcell.ColorValid != 0 { + // reverse ColorValid for Attribute + c &= ^tcell.ColorValid + return Attribute(c) + } + + return ColorDefault } // Clear clears the screen with the given attributes. @@ -346,10 +360,9 @@ const ( // KeyTilde = Key(tcell.Key('~')) KeyTilde = '~' - // 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. + // The following assignments were used in termbox implementation. + // In tcell, these are not keys per se. But in gocui we have them + // mapped to the keys so we have to use placeholder keys. MouseLeft = Key(tcell.KeyF63) // arbitrary assignments MouseRight = Key(tcell.KeyF62) MouseMiddle = Key(tcell.KeyF61) @@ -377,10 +390,10 @@ var ( // Modifiers. const ( - ModAlt = Modifier(tcell.ModAlt) - // ModCtrl doesn't work with keyboard keys. Use CtrlKey in Key and ModNone. This is for mouse clicks only - ModCtrl = Modifier(tcell.ModCtrl) + ModAlt = Modifier(tcell.ModAlt) ModNone = Modifier(0) + // ModCtrl doesn't work with keyboard keys. Use CtrlKey in Key and ModNone. This is for mouse clicks only (tcell.v1) + // ModCtrl = Modifier(tcell.ModCtrl) ) func makeEvent(tev tcell.Event) Event { @@ -416,6 +429,7 @@ func makeEvent(tev tcell.Event) Event { button := tev.Buttons() mouseKey := MouseRelease mouseMod := ModNone + // process mouse wheel if button&tcell.WheelUp != 0 { mouseKey = MouseWheelUp } @@ -429,7 +443,7 @@ func makeEvent(tev tcell.Event) Event { mouseKey = MouseWheelRight } - // Only process button events, not wheel events + // process button events (not wheel events) button &= tcell.ButtonMask(0xff) if button != tcell.ButtonNone && lastMouseKey == tcell.ButtonNone { lastMouseKey = button @@ -440,12 +454,12 @@ func makeEvent(tev tcell.Event) Event { case tcell.ButtonNone: if lastMouseKey != tcell.ButtonNone { switch lastMouseKey { - case tcell.Button1: + case tcell.ButtonPrimary: mouseKey = MouseLeft - case tcell.Button2: - mouseKey = MouseMiddle - case tcell.Button3: + case tcell.ButtonSecondary: mouseKey = MouseRight + case tcell.ButtonMiddle: + mouseKey = MouseMiddle } mouseMod = Modifier(lastMouseMod) lastMouseMod = tcell.ModNone @@ -466,32 +480,8 @@ func makeEvent(tev tcell.Event) Event { } } -// 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 -} From 765f78058f23c81b565ebdeea3b0b389b74c08a5 Mon Sep 17 00:00:00 2001 From: dankox Date: Sun, 8 Nov 2020 08:04:36 +0100 Subject: [PATCH 32/50] Change tcell calls to private --- gui.go | 26 ++++++++++++------------- tcell_compat.go | 50 ++++++++++++++++++++++++------------------------- view.go | 6 ++---- 3 files changed, 40 insertions(+), 42 deletions(-) diff --git a/gui.go b/gui.go index 861eae39..bd31b117 100644 --- a/gui.go +++ b/gui.go @@ -81,7 +81,7 @@ type Gui struct { // NewGui returns a new Gui object with a given output mode. func NewGui(mode OutputMode, supportOverlaps bool) (*Gui, error) { - err := Init() + err := tcellInit() if err != nil { return nil, err } @@ -89,7 +89,7 @@ func NewGui(mode OutputMode, supportOverlaps bool) (*Gui, error) { g := &Gui{} g.outputMode = mode - SetOutputMode(OutputMode(mode)) + tcellSetOutputMode(OutputMode(mode)) g.stop = make(chan struct{}) @@ -102,7 +102,7 @@ func NewGui(mode OutputMode, supportOverlaps bool) (*Gui, error) { return nil, err } } else { - g.maxX, g.maxY = Size() + g.maxX, g.maxY = tcellSize() } g.BgColor, g.FgColor, g.FrameColor = ColorDefault, ColorDefault, ColorDefault @@ -121,7 +121,7 @@ func (g *Gui) Close() { go func() { g.stop <- struct{}{} }() - Close() + tcellClose() } // Size returns the terminal's size. @@ -136,7 +136,7 @@ func (g *Gui) SetRune(x, y int, ch rune, fgColor, bgColor Attribute) error { if x < 0 || y < 0 || x >= g.maxX || y >= g.maxY { return errors.New("invalid point") } - SetCell(x, y, ch, Attribute(fgColor), Attribute(bgColor)) + tcellSetCell(x, y, ch, fgColor, bgColor) return nil } @@ -435,7 +435,7 @@ func (g *Gui) MainLoop() error { case <-g.stop: return default: - g.tbEvents <- PollEvent() + g.tbEvents <- tcellPollEvent() } } }() @@ -447,7 +447,7 @@ func (g *Gui) MainLoop() error { if g.Mouse { inputMode |= InputMouse } - SetInputMode(inputMode) + tcellSetInputMode(inputMode) if err := g.flush(); err != nil { return err @@ -508,9 +508,9 @@ func (g *Gui) handleEvent(ev *Event) error { // flush updates the gui, re-drawing frames and buffers. func (g *Gui) flush() error { - Clear(Attribute(g.FgColor), Attribute(g.BgColor)) + tcellClear(Attribute(g.FgColor), Attribute(g.BgColor)) - maxX, maxY := Size() + maxX, maxY := tcellSize() // if GUI's size has changed, we need to redraw all views if maxX != g.maxX || maxY != g.maxY { for _, v := range g.views { @@ -569,7 +569,7 @@ func (g *Gui) flush() error { return err } } - Flush() + tcellFlush() return nil } @@ -784,13 +784,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 { - SetCursor(cx, cy) + tcellSetCursor(cx, cy) } else { - HideCursor() + tcellHideCursor() } } } else { - HideCursor() + tcellHideCursor() } v.clearRunes() diff --git a/tcell_compat.go b/tcell_compat.go index 8f4f6944..8961b984 100644 --- a/tcell_compat.go +++ b/tcell_compat.go @@ -25,8 +25,8 @@ import ( var screen tcell.Screen var outMode OutputMode -// Init initializes the screen for use. -func Init() error { +// tcellInit initializes the screen for use. +func tcellInit() error { outMode = OutputNormal if s, e := tcell.NewScreen(); e != nil { return e @@ -38,29 +38,29 @@ func Init() error { } } -// Close cleans up the terminal, restoring terminal modes, etc. -func Close() { +// tcellClose cleans up the terminal, restoring terminal modes, etc. +func tcellClose() { screen.Fini() } -// Flush updates the screen. -func Flush() error { +// tcellFlush updates the screen. +func tcellFlush() error { screen.Show() return nil } -// SetCursor displays the terminal cursor at the given location. -func SetCursor(x, y int) { +// tcellSetCursor displays the terminal cursor at the given location. +func tcellSetCursor(x, y int) { screen.ShowCursor(x, y) } -// HideCursor hides the terminal cursor. -func HideCursor() { - SetCursor(-1, -1) +// tcellHideCursor hides the terminal cursor. +func tcellHideCursor() { + tcellSetCursor(-1, -1) } -// Size returns the screen size as width, height in character cells. -func Size() (int, int) { +// tcellSize returns the screen size as width, height in character cells. +func tcellSize() (int, int) { return screen.Size() } @@ -189,8 +189,8 @@ func GetColor(color string) Attribute { return ColorDefault } -// Clear clears the screen with the given attributes. -func Clear(fg, bg Attribute) { +// tcellClear clears the screen with the given attributes. +func tcellClear(fg, bg Attribute) { st := mkStyle(fg, bg) w, h := screen.Size() for row := 0; row < h; row++ { @@ -211,8 +211,8 @@ const ( InputCurrent InputMode = 0 ) -// SetInputMode does not do anything in this version. -func SetInputMode(mode InputMode) InputMode { +// tcellSetInputMode does not do anything in this version. +func tcellSetInputMode(mode InputMode) InputMode { if mode&InputMouse != 0 { screen.EnableMouse() return InputEsc | InputMouse @@ -235,8 +235,8 @@ const ( OutputTrue ) -// SetOutputMode is used to set the color palette used. -func SetOutputMode(mode OutputMode) OutputMode { +// tcellSetOutputMode is used to set the color palette used. +func tcellSetOutputMode(mode OutputMode) OutputMode { if screen.Colors() < 256 { mode = OutputNormal } @@ -251,15 +251,15 @@ func SetOutputMode(mode OutputMode) OutputMode { } } -// Sync forces a resync of the screen. -func Sync() error { +// tcellSync forces a resync of the screen. +func tcellSync() error { screen.Sync() return nil } -// SetCell sets the character cell at a given location to the given +// tcellSetCell 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) { +func tcellSetCell(x, y int, ch rune, fg, bg Attribute) { st := mkStyle(fg, bg) screen.SetContent(x, y, ch, nil, st) } @@ -480,8 +480,8 @@ func makeEvent(tev tcell.Event) Event { } } -// PollEvent blocks until an event is ready, and then returns it. -func PollEvent() Event { +// tcellPollEvent blocks until an event is ready, and then returns it. +func tcellPollEvent() Event { ev := screen.PollEvent() return makeEvent(ev) } diff --git a/view.go b/view.go index ab7da031..d8ee7f08 100644 --- a/view.go +++ b/view.go @@ -230,8 +230,7 @@ func (v *View) setRune(x, y int, ch rune, fgColor, bgColor Attribute) error { ch = ' ' } - SetCell(v.x0+x+1, v.y0+y+1, ch, - Attribute(fgColor), Attribute(bgColor)) + tcellSetCell(v.x0+x+1, v.y0+y+1, ch, fgColor, bgColor) return nil } @@ -632,8 +631,7 @@ func (v *View) clearRunes() { maxX, maxY := v.Size() for x := 0; x < maxX; x++ { for y := 0; y < maxY; y++ { - SetCell(v.x0+x+1, v.y0+y+1, ' ', - Attribute(v.FgColor), Attribute(v.BgColor)) + tcellSetCell(v.x0+x+1, v.y0+y+1, ' ', v.FgColor, v.BgColor) } } } From 0414b0727bdd7410224f0b9a34f9cf81ad0e0e7a Mon Sep 17 00:00:00 2001 From: dankox Date: Sun, 8 Nov 2020 12:53:36 +0100 Subject: [PATCH 33/50] minor correction in mouse example --- _examples/mouse.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/_examples/mouse.go b/_examples/mouse.go index e06676c8..80473823 100644 --- a/_examples/mouse.go +++ b/_examples/mouse.go @@ -111,9 +111,7 @@ func showMsg(g *gocui.Gui, v *gocui.View) error { } func delMsg(g *gocui.Gui, v *gocui.View) error { - if err := g.DeleteView("msg"); err != nil { - // Error removed for testing purpose, because delete could be called multiple times with the above keybindings - // return err - } + // Error check removed, because delete could be called multiple times with the above keybindings + g.DeleteView("msg") return nil } From eaa6f5af783126bc788c96103f2a5cc05c92a131 Mon Sep 17 00:00:00 2001 From: dankox Date: Sun, 8 Nov 2020 15:08:44 +0100 Subject: [PATCH 34/50] Change colors to mimic tcell/v2 colors Colors are changed to be represented exactly as tcell/v2 colors. User can use tcell functions and just wrap them in Attribute() to create colors. Backward compatibility for application using termbox-go coloring system. If colors are in 256 range and are not marked as valid colors (which would be default for previously build applications), they will be translated to tcell, by subtracting 1 from it. --- escape.go | 20 +++++-------- tcell_compat.go | 77 ++++++++++++++++++++++++++++++++++++------------- 2 files changed, 64 insertions(+), 33 deletions(-) diff --git a/escape.go b/escape.go index e391acd8..9ef60af9 100644 --- a/escape.go +++ b/escape.go @@ -7,7 +7,6 @@ package gocui import ( "strconv" - "github.com/gdamore/tcell/v2" "github.com/go-errors/errors" ) @@ -167,11 +166,11 @@ func (ei *escapeInterpreter) outputNormal() error { switch { case p >= 30 && p <= 37: - ei.curFgColor = Attribute(p - 30) + ei.curFgColor = Get256Color(int32(p) - 30) case p == 39: ei.curFgColor = ColorDefault case p >= 40 && p <= 47: - ei.curBgColor = Attribute(p - 40) + ei.curBgColor = Get256Color(int32(p) - 40) case p == 49: ei.curBgColor = ColorDefault case p == 0: @@ -215,7 +214,7 @@ func (ei *escapeInterpreter) output256() error { switch fontEffect(fgbg) { case setForegroundColor: - ei.curFgColor = Attribute(color) + ei.curFgColor = Get256Color(int32(color)) for _, s := range param[3:] { p, err := strconv.Atoi(s) @@ -226,7 +225,7 @@ func (ei *escapeInterpreter) output256() error { ei.curFgColor |= getFontEffect(p) } case setBackgroundColor: - ei.curBgColor = Attribute(color) + ei.curBgColor = Get256Color(int32(color)) default: return errCSIParseError } @@ -267,16 +266,11 @@ func (ei *escapeInterpreter) outputTrue() error { if err != nil { return errCSIParseError } - color := tcell.NewRGBColor(int32(colr), int32(colg), int32(colb)).Hex() + color := NewRGBColor(int32(colr), int32(colg), int32(colb)) switch fontEffect(fgbg) { case setForegroundColor: - if color == -1 { - // This shouldn't happen ever, but Hex() could return it, so rather safe than sorry - ei.curFgColor = ColorDefault - } else { - ei.curFgColor = Attribute(color) | AttrIsRGBColor - } + ei.curFgColor = color for _, s := range param[5:] { p, err := strconv.Atoi(s) @@ -287,7 +281,7 @@ func (ei *escapeInterpreter) outputTrue() error { ei.curFgColor |= getFontEffect(p) } case setBackgroundColor: - ei.curBgColor = Attribute(color) | AttrIsRGBColor + ei.curBgColor = color default: return errCSIParseError } diff --git a/tcell_compat.go b/tcell_compat.go index 8961b984..18492f42 100644 --- a/tcell_compat.go +++ b/tcell_compat.go @@ -70,7 +70,12 @@ type Attribute uint64 const ( // ColorDefault is used to leave the Color unchanged from whatever system or teminal default may exist. - ColorDefault = Attribute(tcell.ColorValid) // We are going to use reverse method to see if color is setup than tcell does + ColorDefault = Attribute(tcell.ColorDefault) + + // AttrIsValidColor is used to indicate the color value is actually + // valid (initialized). This is useful to permit the zero value + // to be treated as the default. + AttrIsValidColor = Attribute(tcell.ColorValid) // AttrIsRGBColor is used to indicate that the Attribute value is RGB value of color. // The lower order 3 bytes are RGB. @@ -84,9 +89,9 @@ const ( AttrStyleBits = 0xffffff0000000000 // remaining 3 bytes in the 8 bytes Attribute (tcell is not using it, so we should be fine) ) -// Colors first. The order here is significant. +// Colors compatible with tcell colors const ( - ColorBlack Attribute = iota + ColorBlack Attribute = AttrIsValidColor + iota ColorRed ColorGreen ColorYellow @@ -96,6 +101,12 @@ const ( ColorWhite ) +// grayscale indexes (for backward compatibility with termbox-go original grayscale) +var grayscale = []tcell.Color{ + 16, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, + 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 231, +} + // Attributes are not colors, but affect the display of text. They can // be combined. const ( @@ -114,28 +125,45 @@ const AttrAll = AttrBold | AttrBlink | AttrReverse | AttrUnderline | AttrDim | A // fixColor transform Attribute into tcell.Color func fixColor(c Attribute) tcell.Color { c = c & AttrColorBits - if c&ColorDefault != 0 { + // Default color is 0 in tcell/v2 and was 0 in termbox-go, so we are good here + if c == ColorDefault { return tcell.ColorDefault } - tc := tcell.Color(c) + tc := tcell.ColorDefault + // Check if we have valid color + if c&AttrIsValidColor != 0 { + tc = tcell.Color(c) + } else if c > 0 && c <= 256 { + // It's not valid color, but it has value in range 1-256 + // This is old Attribute style of color from termbox-go (black=1, etc.) + // convert to tcell color (black=0|ColorValid) + tc = tcell.Color(c-1) | tcell.ColorValid + } + switch outMode { case OutputTrue: - return tc | tcell.ColorValid + return tc case OutputNormal: - tc %= tcell.Color(16) + tc &= tcell.Color(0xf) | tcell.ColorValid case Output256: - tc %= tcell.Color(256) + tc &= tcell.Color(0xff) | tcell.ColorValid case Output216: - tc %= tcell.Color(216) - tc += tcell.Color(16) + tc &= tcell.Color(0xff) + if tc > 215 { + return tcell.ColorDefault + } + tc += tcell.Color(16) | tcell.ColorValid case OutputGrayscale: - tc %= tcell.Color(24) - tc += tcell.Color(232) + tc &= tcell.Color(0x1f) + if tc > 26 { + return tcell.ColorDefault + } + tc = grayscale[tc] | tcell.ColorValid default: return tcell.ColorDefault } - return tc | tcell.ColorValid + return tc } func mkStyle(fg, bg Attribute) tcell.Style { @@ -179,14 +207,23 @@ func setAttr(st tcell.Style, attr Attribute) tcell.Style { // GetColor creates a Color from a color name (W3C name). A hex value may // be supplied as a string in the format "#ffffff". func GetColor(color string) Attribute { - c := tcell.GetColor(color) - if c&tcell.ColorValid != 0 { - // reverse ColorValid for Attribute - c &= ^tcell.ColorValid - return Attribute(c) - } + return Attribute(tcell.GetColor(color)) +} + +// Get256Color creates Attribute which stores ANSI color (0-255) +func Get256Color(color int32) Attribute { + return Attribute(color) | AttrIsValidColor +} + +// GetRGBColor creates Attribute which stores RGB color. +// Color is passed as 24bit RGB value, where R << 16 | G << 8 | B +func GetRGBColor(color int32) Attribute { + return Attribute(color) | AttrIsValidColor | AttrIsRGBColor +} - return ColorDefault +// NewRGBColor creates Attribute which stores RGB color. +func NewRGBColor(r, g, b int32) Attribute { + return Attribute(tcell.NewRGBColor(r, g, b)) } // tcellClear clears the screen with the given attributes. From 4377f3d8b4a241f09ff8b2a14ae06f73dd48b70d Mon Sep 17 00:00:00 2001 From: dankox Date: Sun, 8 Nov 2020 15:28:07 +0100 Subject: [PATCH 35/50] remove .vscode/launch.json and update gitignore --- .gitignore | 1 + .vscode/launch.json | 31 ------------------------------- 2 files changed, 1 insertion(+), 31 deletions(-) delete mode 100644 .vscode/launch.json diff --git a/.gitignore b/.gitignore index a505ae07..51f2ba78 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *.swp .idea +.vscode \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 50f2c01b..00000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - - "version": "0.2.0", - "configurations": [ - { - // for remote run this command in terminal: - // dlv debug --headless --listen=:2345 --log --api-version 2 - "name": "Connect to server", - "type": "go", - "request": "launch", - "mode": "remote", - "program": "${workspaceRoot}", - "remotePath": "${workspaceFolder}", - "port": 2345, - "host": "127.0.0.1" - }, - { - "name": "Launch", - "type": "go", - "request": "launch", - "mode": "auto", - "program": "${fileDirname}", - // "program": "${workspaceFolder}/_examples/colors256.go", - "env": {}, - "args": [] - } - ] -} \ No newline at end of file From 7e477c776092f43fa0b9b53b5e63781cd421f970 Mon Sep 17 00:00:00 2001 From: dankox Date: Sun, 8 Nov 2020 15:29:30 +0100 Subject: [PATCH 36/50] update go.sum --- go.sum | 3 --- 1 file changed, 3 deletions(-) diff --git a/go.sum b/go.sum index 5fed2788..10db7422 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -github.com/awesome-gocui/termbox-go v0.0.0-20190427202837-c0aef3d18bcc h1:wGNpKcHU8Aadr9yOzsT3GEsFLS7HQu8HxQIomnekqf0= -github.com/awesome-gocui/termbox-go v0.0.0-20190427202837-c0aef3d18bcc/go.mod h1:tOy3o5Nf1bA17mnK4W41gD7PS3u4Cv0P0pqFcoWMy8s= github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= github.com/gdamore/tcell/v2 v2.0.0 h1:GRWG8aLfWAlekj9Q6W29bVvkHENc6hp79XOqG4AWDOs= @@ -11,7 +9,6 @@ github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756 h1:9nuHUbU8dRnRRfj9KjWUVrJeoexdbeMjttk6Oh1rD10= golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= From f84d3fddda5a9274efa7df02774e440d6db2a7ac Mon Sep 17 00:00:00 2001 From: dankox Date: Sun, 8 Nov 2020 15:35:44 +0100 Subject: [PATCH 37/50] Update mouse example and add comment to SetKeybinding about mouse keys --- _examples/mouse.go | 4 +--- gui.go | 5 +++++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/_examples/mouse.go b/_examples/mouse.go index 80473823..f82ede75 100644 --- a/_examples/mouse.go +++ b/_examples/mouse.go @@ -72,9 +72,7 @@ func keybindings(g *gocui.Gui) error { if err := g.SetKeybinding("msg", gocui.MouseLeft, gocui.ModNone, delMsg); err != nil { return err } - // These keys are global, but they work only when you click them while hovering over View. - // ModAlt is actually `CTRL` key, not `ALT` key. Tcell generates it like that, not sure why. - if err := g.SetKeybinding("", gocui.MouseRight, gocui.ModAlt, delMsg); err != nil { + if err := g.SetKeybinding("", gocui.MouseRight, gocui.ModNone, delMsg); err != nil { return err } if err := g.SetKeybinding("", gocui.MouseMiddle, gocui.ModNone, delMsg); err != nil { diff --git a/gui.go b/gui.go index bd31b117..29795171 100644 --- a/gui.go +++ b/gui.go @@ -287,6 +287,11 @@ func (g *Gui) CurrentView() *View { // SetKeybinding creates a new keybinding. If viewname equals to "" // (empty string) then the keybinding will apply to all views. key must // be a rune or a Key. +// +// When mouse keys are used (MouseLeft, MouseRight, ...), modifier might not work correctly. +// It behaves differently on different platforms. Somewhere it doesn't register Alt key press, +// on others it might report Ctrl as Alt. It's not consistent and therefore it's not recommended +// to use with mouse keys. func (g *Gui) SetKeybinding(viewname string, key interface{}, mod Modifier, handler func(*Gui, *View) error) error { var kb *keybinding From ea044ebc9ac3ec75228aa951db87da19475312de Mon Sep 17 00:00:00 2001 From: mjarkk Date: Sun, 8 Nov 2020 21:04:40 +0100 Subject: [PATCH 38/50] Update readme and hello example --- README.md | 11 +++++++---- _examples/hello.go | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index be212c58..d109a200 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![GoDoc](https://godoc.org/github.com/awesome-gocui/gocui?status.svg)](https://godoc.org/github.com/awesome-gocui/gocui) ![GitHub tag (latest SemVer)](https://img.shields.io/github/tag/awesome-gocui/gocui.svg) -Minimalist Go package aimed at creating Console User Interfaces. +Minimalist Go package aimed at creating Console User Interfaces. A community fork based on the amazing work of [jroimartin](https://github.com/jroimartin/gocui) ## Features @@ -23,7 +23,7 @@ A community fork based on the amazing work of [jroimartin](https://github.com/jr ## About fork -This fork has many improvements over the original work from [jroimartin](https://github.com/jroimartin/gocui). +This fork has many improvements over the original work from [jroimartin](https://github.com/jroimartin/gocui). * Better wide character support * Support for 1 Line height views @@ -68,7 +68,7 @@ import ( ) func main() { - g, err := gocui.NewGui(gocui.OutputNormal, false) + g, err := gocui.NewGui(gocui.OutputNormal, true) if err != nil { log.Panicln(err) } @@ -91,11 +91,14 @@ func layout(g *gocui.Gui) error { if !gocui.IsUnknownView(err) { return err } - fmt.Fprintln(v, "Hello world!") + if _, err := g.SetCurrentView("hello"); err != nil { return err } + + fmt.Fprintln(v, "Hello world!") } + return nil } diff --git a/_examples/hello.go b/_examples/hello.go index 8e30bbf8..7649efcf 100644 --- a/_examples/hello.go +++ b/_examples/hello.go @@ -25,7 +25,7 @@ func main() { } if err := g.MainLoop(); err != nil && !gocui.IsQuit(err) { - log.Panicln(err.Error()) + log.Panicln(err) } } From ae9b323f78571eb13dcd966ce865c47ac98cfb3c Mon Sep 17 00:00:00 2001 From: mjarkk Date: Sun, 8 Nov 2020 21:07:37 +0100 Subject: [PATCH 39/50] Add new feature to readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d109a200..3e86480b 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ A community fork based on the amazing work of [jroimartin](https://github.com/jr This fork has many improvements over the original work from [jroimartin](https://github.com/jroimartin/gocui). +* Written ontop of TCell * Better wide character support * Support for 1 Line height views * Better support for running in docker container From e19a29b8d652cdbd81c20bf891c2efe1b54b6bb5 Mon Sep 17 00:00:00 2001 From: dankox Date: Sun, 8 Nov 2020 23:30:16 +0100 Subject: [PATCH 40/50] Move attributes and keys to respective files Attributes and keybindings returned to their original location with updated version. --- attribute.go | 196 +++++++++++++++++++++++++++++++++++ gui.go | 2 +- keybinding.go | 158 +++++++++++++++++++++++----- tcell_compat.go | 267 ------------------------------------------------ 4 files changed, 329 insertions(+), 294 deletions(-) create mode 100644 attribute.go diff --git a/attribute.go b/attribute.go new file mode 100644 index 00000000..f680ee23 --- /dev/null +++ b/attribute.go @@ -0,0 +1,196 @@ +// Copyright 2020 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" + +// Attribute affects the presentation of characters, such as color, boldness, +// and so forth. +type Attribute uint64 + +const ( + // ColorDefault is used to leave the Color unchanged from whatever system or teminal default may exist. + ColorDefault = Attribute(tcell.ColorDefault) + + // AttrIsValidColor is used to indicate the color value is actually + // valid (initialized). This is useful to permit the zero value + // to be treated as the default. + AttrIsValidColor = Attribute(tcell.ColorValid) + + // AttrIsRGBColor is used to indicate that the Attribute value is RGB value of color. + // The lower order 3 bytes are RGB. + // (It's not a color in basic ANSI range 256). + AttrIsRGBColor = Attribute(tcell.ColorIsRGB) + + // AttrColorBits is a mask where color is located in Attribute + AttrColorBits = 0xffffffffff // roughly 5 bytes, tcell uses 4 bytes and half-byte as a special flags for color (rest is reserved for future) + + // AttrStyleBits is a mask where character attributes (e.g.: bold, italic, underline) are located in Attribute + AttrStyleBits = 0xffffff0000000000 // remaining 3 bytes in the 8 bytes Attribute (tcell is not using it, so we should be fine) +) + +// Color attributes. These colors are compatible with tcell.Color type and can be expanded like: +// g.FgColor := gocui.Attribute(tcell.ColorLime) +const ( + ColorBlack Attribute = AttrIsValidColor + iota + ColorRed + ColorGreen + ColorYellow + ColorBlue + ColorMagenta + ColorCyan + ColorWhite +) + +// grayscale indexes (for backward compatibility with termbox-go original grayscale) +var grayscale = []tcell.Color{ + 16, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, + 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 231, +} + +// Attributes are not colors, but affect the display of text. They can +// be combined. +const ( + AttrBold Attribute = 1 << (40 + iota) + AttrBlink + AttrReverse + AttrUnderline + AttrDim + AttrItalic + AttrNone Attribute = 0 // Just normal text. +) + +// AttrAll is all the attributes turned on +const AttrAll = AttrBold | AttrBlink | AttrReverse | AttrUnderline | AttrDim | AttrItalic + +// Hex returns the color's hexadecimal RGB 24-bit value with each component +// consisting of a single byte, ala R << 16 | G << 8 | B. If the color +// is unknown or unset, -1 is returned. +// +// This function produce the same output as `tcell.Hex()` with additional +// support for `termbox-go` colors (to 256). +func (a Attribute) Hex() int32 { + fixa := fixColor(a, OutputTrue) + return fixa.Hex() +} + +// RGB returns the red, green, and blue components of the color, with +// each component represented as a value 0-255. If the color +// is unknown or unset, -1 is returned for each component. +// +// This function produce the same output as `tcell.RGB()` with additional +// support for `termbox-go` colors (to 256). +func (a Attribute) RGB() (int32, int32, int32) { + v := a.Hex() + if v < 0 { + return -1, -1, -1 + } + return (v >> 16) & 0xff, (v >> 8) & 0xff, v & 0xff +} + +// GetColor creates a Color from a color name (W3C name). A hex value may +// be supplied as a string in the format "#ffffff". +func GetColor(color string) Attribute { + return Attribute(tcell.GetColor(color)) +} + +// Get256Color creates Attribute which stores ANSI color (0-255) +func Get256Color(color int32) Attribute { + return Attribute(color) | AttrIsValidColor +} + +// GetRGBColor creates Attribute which stores RGB color. +// Color is passed as 24bit RGB value, where R << 16 | G << 8 | B +func GetRGBColor(color int32) Attribute { + return Attribute(color) | AttrIsValidColor | AttrIsRGBColor +} + +// NewRGBColor creates Attribute which stores RGB color. +func NewRGBColor(r, g, b int32) Attribute { + return Attribute(tcell.NewRGBColor(r, g, b)) +} + +// fixColor transform Attribute into tcell.Color +func fixColor(c Attribute, omode OutputMode) tcell.Color { + c = c & AttrColorBits + // Default color is 0 in tcell/v2 and was 0 in termbox-go, so we are good here + if c == ColorDefault { + return tcell.ColorDefault + } + + tc := tcell.ColorDefault + // Check if we have valid color + if c&AttrIsValidColor != 0 { + tc = tcell.Color(c) + } else if c > 0 && c <= 256 { + // It's not valid color, but it has value in range 1-256 + // This is old Attribute style of color from termbox-go (black=1, etc.) + // convert to tcell color (black=0|ColorValid) + tc = tcell.Color(c-1) | tcell.ColorValid + } + + switch omode { + case OutputTrue: + return tc + case OutputNormal: + tc &= tcell.Color(0xf) | tcell.ColorValid + case Output256: + tc &= tcell.Color(0xff) | tcell.ColorValid + case Output216: + tc &= tcell.Color(0xff) + if tc > 215 { + return tcell.ColorDefault + } + tc += tcell.Color(16) | tcell.ColorValid + case OutputGrayscale: + tc &= tcell.Color(0x1f) + if tc > 26 { + return tcell.ColorDefault + } + tc = grayscale[tc] | tcell.ColorValid + default: + return tcell.ColorDefault + } + return tc +} + +// mkStyle creates tcell.Style from Attributes +func mkStyle(fg, bg Attribute) tcell.Style { + st := tcell.StyleDefault + + // extract colors and attributes + if fg != ColorDefault { + st = st.Foreground(fixColor(fg, outMode)) + st = setAttr(st, fg) + } + if bg != ColorDefault { + st = st.Background(fixColor(bg, outMode)) + st = setAttr(st, bg) + } + + return st +} + +func setAttr(st tcell.Style, attr Attribute) tcell.Style { + if attr&AttrBold != 0 { + st = st.Bold(true) + } + if attr&AttrUnderline != 0 { + st = st.Underline(true) + } + if attr&AttrReverse != 0 { + st = st.Reverse(true) + } + if attr&AttrBlink != 0 { + st = st.Blink(true) + } + if attr&AttrDim != 0 { + st = st.Dim(true) + } + if attr&AttrItalic != 0 { + st = st.Italic(true) + } + return st +} diff --git a/gui.go b/gui.go index 29795171..5190e020 100644 --- a/gui.go +++ b/gui.go @@ -440,7 +440,7 @@ func (g *Gui) MainLoop() error { case <-g.stop: return default: - g.tbEvents <- tcellPollEvent() + g.tbEvents <- makeEvent(screen.PollEvent()) } } }() diff --git a/keybinding.go b/keybinding.go index 59498ebd..b48d7b79 100644 --- a/keybinding.go +++ b/keybinding.go @@ -6,8 +6,17 @@ package gocui import ( "strings" + + "github.com/gdamore/tcell/v2" ) +// Key represents special keys or keys combinations. +type Key tcell.Key + +// 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 tcell.ModMask + // Keybidings are used to link a given key-press event with a handler. type keybinding struct { viewName string @@ -110,31 +119,31 @@ func (kb *keybinding) matchView(v *View) bool { // translations for strings to keys var translate = map[string]Key{ - "F1": KeyF1, - "F2": KeyF2, - "F3": KeyF3, - "F4": KeyF4, - "F5": KeyF5, - "F6": KeyF6, - "F7": KeyF7, - "F8": KeyF8, - "F9": KeyF9, - "F10": KeyF10, - "F11": KeyF11, - "F12": KeyF12, - "Insert": KeyInsert, - "Delete": KeyDelete, - "Home": KeyHome, - "End": KeyEnd, - "Pgup": KeyPgup, - "Pgdn": KeyPgdn, - "ArrowUp": KeyArrowUp, - "ArrowDown": KeyArrowDown, - "ArrowLeft": KeyArrowLeft, - "ArrowRight": KeyArrowRight, - // "CtrlTilde": KeyCtrlTilde, - "Ctrl2": KeyCtrl2, - // "CtrlSpace": KeyCtrlSpace, + "F1": KeyF1, + "F2": KeyF2, + "F3": KeyF3, + "F4": KeyF4, + "F5": KeyF5, + "F6": KeyF6, + "F7": KeyF7, + "F8": KeyF8, + "F9": KeyF9, + "F10": KeyF10, + "F11": KeyF11, + "F12": KeyF12, + "Insert": KeyInsert, + "Delete": KeyDelete, + "Home": KeyHome, + "End": KeyEnd, + "Pgup": KeyPgup, + "Pgdn": KeyPgdn, + "ArrowUp": KeyArrowUp, + "ArrowDown": KeyArrowDown, + "ArrowLeft": KeyArrowLeft, + "ArrowRight": KeyArrowRight, + "CtrlTilde": KeyCtrlTilde, + "Ctrl2": KeyCtrl2, + "CtrlSpace": KeyCtrlSpace, "CtrlA": KeyCtrlA, "CtrlB": KeyCtrlB, "CtrlC": KeyCtrlC, @@ -177,7 +186,7 @@ var translate = map[string]Key{ "CtrlUnderscore": KeyCtrlUnderscore, "Space": KeySpace, "Backspace2": KeyBackspace2, - // "Ctrl8": KeyCtrl8, + "Ctrl8": KeyCtrl8, "Mouseleft": MouseLeft, "Mousemiddle": MouseMiddle, "Mouseright": MouseRight, @@ -185,3 +194,100 @@ var translate = map[string]Key{ "MousewheelUp": MouseWheelUp, "MousewheelDown": MouseWheelDown, } + +// Special keys. +const ( + KeyF1 Key = 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) + KeyPgdn = Key(tcell.KeyPgDn) + KeyPgup = Key(tcell.KeyPgUp) + KeyArrowUp = Key(tcell.KeyUp) + KeyArrowDown = Key(tcell.KeyDown) + KeyArrowLeft = Key(tcell.KeyLeft) + KeyArrowRight = Key(tcell.KeyRight) +) + +// Keys combinations. +const ( + KeyCtrlTilde = Key(tcell.KeyF64) // arbitrary assignment + KeyCtrlSpace = Key(tcell.KeyCtrlSpace) + 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) + KeyBackspace = Key(tcell.KeyBackspace) + KeyCtrlH = Key(tcell.KeyCtrlH) + KeyTab = Key(tcell.KeyTab) + KeyCtrlI = Key(tcell.KeyCtrlI) + KeyCtrlJ = Key(tcell.KeyCtrlJ) + KeyCtrlK = Key(tcell.KeyCtrlK) + KeyCtrlL = Key(tcell.KeyCtrlL) + KeyEnter = Key(tcell.KeyEnter) + 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) + KeyEsc = Key(tcell.KeyEscape) + KeyCtrlUnderscore = Key(tcell.KeyCtrlUnderscore) + KeySpace = ' ' + KeyBackspace2 = Key(tcell.KeyBackspace2) + KeyCtrl8 = Key(tcell.KeyBackspace2) // same key as in termbox-go + + // The following assignments were used in termbox implementation. + // In tcell, these are not keys per se. But in gocui we have them + // mapped to the keys so we have to use placeholder keys. + + 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) + MouseWheelLeft = Key(tcell.KeyF57) + MouseWheelRight = Key(tcell.KeyF56) + KeyCtrl2 = Key(tcell.KeyNUL) // termbox defines theses + KeyCtrl3 = Key(tcell.KeyEscape) + KeyCtrl4 = Key(tcell.KeyCtrlBackslash) + KeyCtrl5 = Key(tcell.KeyCtrlRightSq) + KeyCtrl6 = Key(tcell.KeyCtrlCarat) + KeyCtrl7 = Key(tcell.KeyCtrlUnderscore) + KeyCtrlSlash = Key(tcell.KeyCtrlUnderscore) + KeyCtrlRsqBracket = Key(tcell.KeyCtrlRightSq) + KeyCtrlBackslash = Key(tcell.KeyCtrlBackslash) + KeyCtrlLsqBracket = Key(tcell.KeyCtrlLeftSq) +) + +// Modifiers. +const ( + ModNone Modifier = Modifier(0) + ModAlt = Modifier(tcell.ModAlt) + // ModCtrl doesn't work with keyboard keys. Use CtrlKey in Key and ModNone. This is was for mouse clicks only (tcell.v1) + // ModCtrl = Modifier(tcell.ModCtrl) +) diff --git a/tcell_compat.go b/tcell_compat.go index 18492f42..e5d334e6 100644 --- a/tcell_compat.go +++ b/tcell_compat.go @@ -64,168 +64,6 @@ func tcellSize() (int, int) { return screen.Size() } -// Attribute affects the presentation of characters, such as color, boldness, -// and so forth. -type Attribute uint64 - -const ( - // ColorDefault is used to leave the Color unchanged from whatever system or teminal default may exist. - ColorDefault = Attribute(tcell.ColorDefault) - - // AttrIsValidColor is used to indicate the color value is actually - // valid (initialized). This is useful to permit the zero value - // to be treated as the default. - AttrIsValidColor = Attribute(tcell.ColorValid) - - // AttrIsRGBColor is used to indicate that the Attribute value is RGB value of color. - // The lower order 3 bytes are RGB. - // (It's not a color in basic ANSI range 256). - AttrIsRGBColor = Attribute(tcell.ColorIsRGB) - - // AttrColorBits is a mask where color is located in Attribute - AttrColorBits = 0xffffffffff // roughly 5 bytes, tcell uses 4 bytes and half-byte as a special flags for color (rest is reserved for future) - - // AttrStyleBits is a mask where character attributes (e.g.: bold, italic, underline) are located in Attribute - AttrStyleBits = 0xffffff0000000000 // remaining 3 bytes in the 8 bytes Attribute (tcell is not using it, so we should be fine) -) - -// Colors compatible with tcell colors -const ( - ColorBlack Attribute = AttrIsValidColor + iota - ColorRed - ColorGreen - ColorYellow - ColorBlue - ColorMagenta - ColorCyan - ColorWhite -) - -// grayscale indexes (for backward compatibility with termbox-go original grayscale) -var grayscale = []tcell.Color{ - 16, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, - 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 231, -} - -// Attributes are not colors, but affect the display of text. They can -// be combined. -const ( - AttrBold Attribute = 1 << (40 + iota) - AttrBlink - AttrReverse - AttrUnderline - AttrDim - AttrItalic - AttrNone Attribute = 0 // Just normal text. -) - -// AttrAll is all the attributes turned on -const AttrAll = AttrBold | AttrBlink | AttrReverse | AttrUnderline | AttrDim | AttrItalic - -// fixColor transform Attribute into tcell.Color -func fixColor(c Attribute) tcell.Color { - c = c & AttrColorBits - // Default color is 0 in tcell/v2 and was 0 in termbox-go, so we are good here - if c == ColorDefault { - return tcell.ColorDefault - } - - tc := tcell.ColorDefault - // Check if we have valid color - if c&AttrIsValidColor != 0 { - tc = tcell.Color(c) - } else if c > 0 && c <= 256 { - // It's not valid color, but it has value in range 1-256 - // This is old Attribute style of color from termbox-go (black=1, etc.) - // convert to tcell color (black=0|ColorValid) - tc = tcell.Color(c-1) | tcell.ColorValid - } - - switch outMode { - case OutputTrue: - return tc - case OutputNormal: - tc &= tcell.Color(0xf) | tcell.ColorValid - case Output256: - tc &= tcell.Color(0xff) | tcell.ColorValid - case Output216: - tc &= tcell.Color(0xff) - if tc > 215 { - return tcell.ColorDefault - } - tc += tcell.Color(16) | tcell.ColorValid - case OutputGrayscale: - tc &= tcell.Color(0x1f) - if tc > 26 { - return tcell.ColorDefault - } - tc = grayscale[tc] | tcell.ColorValid - default: - return tcell.ColorDefault - } - return tc -} - -func mkStyle(fg, bg Attribute) tcell.Style { - st := tcell.StyleDefault - - // extract colors and attributes - if fg != ColorDefault { - st = st.Foreground(fixColor(fg)) - st = setAttr(st, fg) - } - if bg != ColorDefault { - st = st.Background(fixColor(bg)) - st = setAttr(st, bg) - } - - return st -} - -func setAttr(st tcell.Style, attr Attribute) tcell.Style { - if attr&AttrBold != 0 { - st = st.Bold(true) - } - if attr&AttrUnderline != 0 { - st = st.Underline(true) - } - if attr&AttrReverse != 0 { - st = st.Reverse(true) - } - if attr&AttrBlink != 0 { - st = st.Blink(true) - } - if attr&AttrDim != 0 { - st = st.Dim(true) - } - if attr&AttrItalic != 0 { - st = st.Italic(true) - } - return st -} - -// GetColor creates a Color from a color name (W3C name). A hex value may -// be supplied as a string in the format "#ffffff". -func GetColor(color string) Attribute { - return Attribute(tcell.GetColor(color)) -} - -// Get256Color creates Attribute which stores ANSI color (0-255) -func Get256Color(color int32) Attribute { - return Attribute(color) | AttrIsValidColor -} - -// GetRGBColor creates Attribute which stores RGB color. -// Color is passed as 24bit RGB value, where R << 16 | G << 8 | B -func GetRGBColor(color int32) Attribute { - return Attribute(color) | AttrIsValidColor | AttrIsRGBColor -} - -// NewRGBColor creates Attribute which stores RGB color. -func NewRGBColor(r, g, b int32) Attribute { - return Attribute(tcell.NewRGBColor(r, g, b)) -} - // tcellClear clears the screen with the given attributes. func tcellClear(fg, bg Attribute) { st := mkStyle(fg, bg) @@ -304,12 +142,6 @@ func tcellSetCell(x, y int, ch rune, fg, bg Attribute) { // 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 @@ -335,104 +167,11 @@ const ( 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) - KeyCtrlSpace = Key(tcell.KeyCtrlSpace) - // KeySpace = Key(tcell.Key(' ')) - KeySpace = ' ' - // KeyTilde = Key(tcell.Key('~')) - KeyTilde = '~' - - // The following assignments were used in termbox implementation. - // In tcell, these are not keys per se. But in gocui we have them - // mapped to the keys so we have to use placeholder keys. - 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) - MouseWheelLeft = Key(tcell.KeyF57) - MouseWheelRight = Key(tcell.KeyF56) - KeyCtrl2 = Key(tcell.KeyNUL) // termbox defines theses - KeyCtrl3 = Key(tcell.KeyEscape) - KeyCtrl4 = Key(tcell.KeyCtrlBackslash) - KeyCtrl5 = Key(tcell.KeyCtrlRightSq) - KeyCtrl6 = Key(tcell.KeyCtrlCarat) - KeyCtrl7 = Key(tcell.KeyCtrlUnderscore) - KeyCtrlSlash = Key(tcell.KeyCtrlUnderscore) - KeyCtrlRsqBracket = Key(tcell.KeyCtrlRightSq) - KeyCtrlBackslash = Key(tcell.KeyCtrlBackslash) - KeyCtrlLsqBracket = Key(tcell.KeyCtrlLeftSq) -) - var ( lastMouseKey tcell.ButtonMask = tcell.ButtonNone lastMouseMod tcell.ModMask = tcell.ModNone ) -// Modifiers. -const ( - ModAlt = Modifier(tcell.ModAlt) - ModNone = Modifier(0) - // ModCtrl doesn't work with keyboard keys. Use CtrlKey in Key and ModNone. This is for mouse clicks only (tcell.v1) - // ModCtrl = Modifier(tcell.ModCtrl) -) - func makeEvent(tev tcell.Event) Event { switch tev := tev.(type) { case *tcell.EventInterrupt: @@ -516,9 +255,3 @@ func makeEvent(tev tcell.Event) Event { return Event{Type: EventNone} } } - -// tcellPollEvent blocks until an event is ready, and then returns it. -func tcellPollEvent() Event { - ev := screen.PollEvent() - return makeEvent(ev) -} From 99abdfd7868870526ded5c49930fd4bfd5889dde Mon Sep 17 00:00:00 2001 From: dankox Date: Sun, 8 Nov 2020 23:37:09 +0100 Subject: [PATCH 41/50] Fix some keybindings KeyCtrlSpace - incorrectly translated ModShift - removed, this is translated into the rune code --- tcell_compat.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tcell_compat.go b/tcell_compat.go index e5d334e6..a4d6c65b 100644 --- a/tcell_compat.go +++ b/tcell_compat.go @@ -188,10 +188,14 @@ func makeEvent(tev tcell.Event) Event { } mod := tev.Modifiers() // remove control modifier and setup special handling of ctrl+spacebar, etc. - if mod == tcell.ModCtrl && k == 0 && ch == 0 { + if mod == tcell.ModCtrl && k == 0 && ch == ' ' { mod = 0 + ch = rune(0) k = tcell.KeyCtrlSpace - } else if mod == tcell.ModCtrl { + } else if mod == tcell.ModCtrl || mod == tcell.ModShift { + // remove Ctrl or Shift if specified + // - shift - will be translated to the final code of rune + // - ctrl - is translated in the key mod = 0 } return Event{ From da677454054257c7580ab723e00b1d3a28e61e82 Mon Sep 17 00:00:00 2001 From: dankox Date: Mon, 9 Nov 2020 00:11:48 +0000 Subject: [PATCH 42/50] refactoring tcell functions part 1 --- attribute.go | 6 +++--- gui.go | 41 ++++++++++++++++++++++++++++++++++++++--- tcell_compat.go | 45 ++------------------------------------------- view.go | 6 ++++-- 4 files changed, 47 insertions(+), 51 deletions(-) diff --git a/attribute.go b/attribute.go index f680ee23..658c7bfd 100644 --- a/attribute.go +++ b/attribute.go @@ -157,16 +157,16 @@ func fixColor(c Attribute, omode OutputMode) tcell.Color { } // mkStyle creates tcell.Style from Attributes -func mkStyle(fg, bg Attribute) tcell.Style { +func mkStyle(fg, bg Attribute, omode OutputMode) tcell.Style { st := tcell.StyleDefault // extract colors and attributes if fg != ColorDefault { - st = st.Foreground(fixColor(fg, outMode)) + st = st.Foreground(fixColor(fg, omode)) st = setAttr(st, fg) } if bg != ColorDefault { - st = st.Background(fixColor(bg, outMode)) + st = st.Background(fixColor(bg, omode)) st = setAttr(st, bg) } diff --git a/gui.go b/gui.go index 5190e020..9aa3fc65 100644 --- a/gui.go +++ b/gui.go @@ -34,6 +34,31 @@ var ( ErrQuit = standardErrors.New("quit") ) +// OutputMode represents an output mode, which determines how colors +// are used. +type OutputMode int + +const ( + // OutputNormal provides 8-colors terminal mode. + OutputNormal OutputMode = iota + + // Output256 provides 256-colors terminal mode. + Output256 + + // Output216 provides 216 ansi color terminal mode. + Output216 + + // OutputGrayscale provides greyscale terminal mode. + OutputGrayscale + + // OutputTrue provides 24bit color terminal mode. + // This mode is recommended even if your terminal doesn't support + // such mode. The colors are represented exactly as you + // write them (no clamping or truncating). `tcell` will take care + // of what your terminal can do. + OutputTrue +) + // Gui represents the whole User Interface, including the views, layouts // and keybindings. type Gui struct { @@ -89,7 +114,6 @@ func NewGui(mode OutputMode, supportOverlaps bool) (*Gui, error) { g := &Gui{} g.outputMode = mode - tcellSetOutputMode(OutputMode(mode)) g.stop = make(chan struct{}) @@ -136,7 +160,7 @@ func (g *Gui) SetRune(x, y int, ch rune, fgColor, bgColor Attribute) error { if x < 0 || y < 0 || x >= g.maxX || y >= g.maxY { return errors.New("invalid point") } - tcellSetCell(x, y, ch, fgColor, bgColor) + tcellSetCell(x, y, ch, fgColor, bgColor, g.outputMode) return nil } @@ -513,7 +537,7 @@ func (g *Gui) handleEvent(ev *Event) error { // flush updates the gui, re-drawing frames and buffers. func (g *Gui) flush() error { - tcellClear(Attribute(g.FgColor), Attribute(g.BgColor)) + g.clear(g.FgColor, g.BgColor) maxX, maxY := tcellSize() // if GUI's size has changed, we need to redraw all views @@ -578,6 +602,17 @@ func (g *Gui) flush() error { return nil } +func (g *Gui) clear(fg, bg Attribute) (int, int) { + st := mkStyle(fg, bg, g.outputMode) + w, h := screen.Size() + for row := 0; row < h; row++ { + for col := 0; col < w; col++ { + screen.SetContent(col, row, ' ', nil, st) + } + } + return w, h +} + // drawFrameEdges draws the horizontal and vertical edges of a view. func (g *Gui) drawFrameEdges(v *View, fgColor, bgColor Attribute) error { runeH, runeV := '─', '│' diff --git a/tcell_compat.go b/tcell_compat.go index a4d6c65b..b4f47260 100644 --- a/tcell_compat.go +++ b/tcell_compat.go @@ -64,17 +64,6 @@ func tcellSize() (int, int) { return screen.Size() } -// tcellClear clears the screen with the given attributes. -func tcellClear(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 @@ -96,36 +85,6 @@ func tcellSetInputMode(mode InputMode) InputMode { 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 - OutputTrue -) - -// tcellSetOutputMode is used to set the color palette used. -func tcellSetOutputMode(mode OutputMode) OutputMode { - if screen.Colors() < 256 { - mode = OutputNormal - } - switch mode { - case OutputCurrent: - return outMode - case OutputNormal, Output256, Output216, OutputGrayscale, OutputTrue: - outMode = mode - return mode - default: - return outMode - } -} - // tcellSync forces a resync of the screen. func tcellSync() error { screen.Sync() @@ -134,8 +93,8 @@ func tcellSync() error { // tcellSetCell sets the character cell at a given location to the given // content (rune) and attributes. -func tcellSetCell(x, y int, ch rune, fg, bg Attribute) { - st := mkStyle(fg, bg) +func tcellSetCell(x, y int, ch rune, fg, bg Attribute, omode OutputMode) { + st := mkStyle(fg, bg, omode) screen.SetContent(x, y, ch, nil, st) } diff --git a/view.go b/view.go index d8ee7f08..4faa4209 100644 --- a/view.go +++ b/view.go @@ -39,6 +39,7 @@ type View struct { rx, ry int // Read() offsets wx, wy int // Write() offsets lines [][]cell // All the data + outMode OutputMode // readBuffer is used for storing unread bytes readBuffer []byte @@ -169,6 +170,7 @@ func newView(name string, x0, y0, x1, y1 int, mode OutputMode) *View { Frame: true, Editor: DefaultEditor, tainted: true, + outMode: mode, ei: newEscapeInterpreter(mode), } @@ -230,7 +232,7 @@ func (v *View) setRune(x, y int, ch rune, fgColor, bgColor Attribute) error { ch = ' ' } - tcellSetCell(v.x0+x+1, v.y0+y+1, ch, fgColor, bgColor) + tcellSetCell(v.x0+x+1, v.y0+y+1, ch, fgColor, bgColor, v.outMode) return nil } @@ -631,7 +633,7 @@ func (v *View) clearRunes() { maxX, maxY := v.Size() for x := 0; x < maxX; x++ { for y := 0; y < maxY; y++ { - tcellSetCell(v.x0+x+1, v.y0+y+1, ' ', v.FgColor, v.BgColor) + tcellSetCell(v.x0+x+1, v.y0+y+1, ' ', v.FgColor, v.BgColor, v.outMode) } } } From 5c8be1dbe9bf195102c771d442b51ed1c32e34ab Mon Sep 17 00:00:00 2001 From: danko Date: Mon, 9 Nov 2020 22:55:01 +0000 Subject: [PATCH 43/50] Add strikethrough attribute --- attribute.go | 4 ++++ escape.go | 3 +++ 2 files changed, 7 insertions(+) diff --git a/attribute.go b/attribute.go index 658c7bfd..2ebd5d7f 100644 --- a/attribute.go +++ b/attribute.go @@ -59,6 +59,7 @@ const ( AttrUnderline AttrDim AttrItalic + AttrStrikeThrough AttrNone Attribute = 0 // Just normal text. ) @@ -192,5 +193,8 @@ func setAttr(st tcell.Style, attr Attribute) tcell.Style { if attr&AttrItalic != 0 { st = st.Italic(true) } + if attr&AttrStrikeThrough != 0 { + st = st.StrikeThrough(true) + } return st } diff --git a/escape.go b/escape.go index 9ef60af9..d0a439bf 100644 --- a/escape.go +++ b/escape.go @@ -35,6 +35,7 @@ const ( underline fontEffect = 4 blink fontEffect = 5 reverse fontEffect = 7 + strike fontEffect = 9 setForegroundColor fontEffect = 38 setBackgroundColor fontEffect = 48 ) @@ -324,6 +325,8 @@ func getFontEffect(f int) Attribute { return AttrBlink case reverse: return AttrReverse + case strike: + return AttrStrikeThrough } return AttrNone } From 5921dae3e5268b980d60035590c323c60d87001d Mon Sep 17 00:00:00 2001 From: danko Date: Mon, 9 Nov 2020 23:34:12 +0000 Subject: [PATCH 44/50] fix KeySpace --- keybinding.go | 2 +- tcell_compat.go | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/keybinding.go b/keybinding.go index b48d7b79..5f70dd06 100644 --- a/keybinding.go +++ b/keybinding.go @@ -256,7 +256,7 @@ const ( KeyCtrlZ = Key(tcell.KeyCtrlZ) KeyEsc = Key(tcell.KeyEscape) KeyCtrlUnderscore = Key(tcell.KeyCtrlUnderscore) - KeySpace = ' ' + KeySpace = Key(32) KeyBackspace2 = Key(tcell.KeyBackspace2) KeyCtrl8 = Key(tcell.KeyBackspace2) // same key as in termbox-go diff --git a/tcell_compat.go b/tcell_compat.go index b4f47260..9973aae7 100644 --- a/tcell_compat.go +++ b/tcell_compat.go @@ -142,12 +142,17 @@ func makeEvent(tev tcell.Event) Event { k := tev.Key() ch := rune(0) if k == tcell.KeyRune { - k = 0 // if rune remove key (so it can match, for example spacebar) + k = 0 // if rune remove key (so it can match rune instead of key) ch = tev.Rune() + if ch == ' ' { + // special handling for spacebar + k = 32 // tcell keys ends at 31 or starts at 256 + ch = rune(0) + } } mod := tev.Modifiers() // remove control modifier and setup special handling of ctrl+spacebar, etc. - if mod == tcell.ModCtrl && k == 0 && ch == ' ' { + if mod == tcell.ModCtrl && k == 32 { mod = 0 ch = rune(0) k = tcell.KeyCtrlSpace From 8b345d02c1fc278293cc08e012e3e4cc4957b7ea Mon Sep 17 00:00:00 2001 From: danko Date: Tue, 10 Nov 2020 01:13:47 +0000 Subject: [PATCH 45/50] refactoring tcell functions finished tcell functions were moved around and included in gocui code directly. `tcell_driver.go` file was limited to only specific tcell functions which needs special transform (like Poll or SetCell). --- attribute.go | 69 +++--------- go.sum | 1 + gui.go | 56 +++++----- tcell_compat.go => tcell_driver.go | 162 +++++++++++++---------------- 4 files changed, 116 insertions(+), 172 deletions(-) rename tcell_compat.go => tcell_driver.go (51%) diff --git a/attribute.go b/attribute.go index 2ebd5d7f..adc933ff 100644 --- a/attribute.go +++ b/attribute.go @@ -6,8 +6,7 @@ package gocui import "github.com/gdamore/tcell/v2" -// Attribute affects the presentation of characters, such as color, boldness, -// and so forth. +// Attribute affects the presentation of characters, such as color, boldness, etc. type Attribute uint64 const ( @@ -50,8 +49,8 @@ var grayscale = []tcell.Color{ 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 231, } -// Attributes are not colors, but affect the display of text. They can -// be combined. +// Attributes are not colors, but effects (e.g.: bold, dim) which affect the display of text. +// They can be combined. const ( AttrBold Attribute = 1 << (40 + iota) AttrBlink @@ -63,9 +62,14 @@ const ( AttrNone Attribute = 0 // Just normal text. ) -// AttrAll is all the attributes turned on +// AttrAll represents all the text effect attributes turned on const AttrAll = AttrBold | AttrBlink | AttrReverse | AttrUnderline | AttrDim | AttrItalic +// IsValidColor indicates if the Attribute is a valid color value (has been set). +func (a Attribute) IsValidColor() bool { + return a&AttrIsValidColor != 0 +} + // Hex returns the color's hexadecimal RGB 24-bit value with each component // consisting of a single byte, ala R << 16 | G << 8 | B. If the color // is unknown or unset, -1 is returned. @@ -73,8 +77,11 @@ const AttrAll = AttrBold | AttrBlink | AttrReverse | AttrUnderline | AttrDim | A // This function produce the same output as `tcell.Hex()` with additional // support for `termbox-go` colors (to 256). func (a Attribute) Hex() int32 { - fixa := fixColor(a, OutputTrue) - return fixa.Hex() + if !a.IsValidColor() { + return -1 + } + tc := getTcellColor(a, OutputTrue) + return tc.Hex() } // RGB returns the red, green, and blue components of the color, with @@ -113,8 +120,8 @@ func NewRGBColor(r, g, b int32) Attribute { return Attribute(tcell.NewRGBColor(r, g, b)) } -// fixColor transform Attribute into tcell.Color -func fixColor(c Attribute, omode OutputMode) tcell.Color { +// getTcellColor transform Attribute into tcell.Color +func getTcellColor(c Attribute, omode OutputMode) tcell.Color { c = c & AttrColorBits // Default color is 0 in tcell/v2 and was 0 in termbox-go, so we are good here if c == ColorDefault { @@ -123,7 +130,7 @@ func fixColor(c Attribute, omode OutputMode) tcell.Color { tc := tcell.ColorDefault // Check if we have valid color - if c&AttrIsValidColor != 0 { + if c.IsValidColor() { tc = tcell.Color(c) } else if c > 0 && c <= 256 { // It's not valid color, but it has value in range 1-256 @@ -156,45 +163,3 @@ func fixColor(c Attribute, omode OutputMode) tcell.Color { } return tc } - -// mkStyle creates tcell.Style from Attributes -func mkStyle(fg, bg Attribute, omode OutputMode) tcell.Style { - st := tcell.StyleDefault - - // extract colors and attributes - if fg != ColorDefault { - st = st.Foreground(fixColor(fg, omode)) - st = setAttr(st, fg) - } - if bg != ColorDefault { - st = st.Background(fixColor(bg, omode)) - st = setAttr(st, bg) - } - - return st -} - -func setAttr(st tcell.Style, attr Attribute) tcell.Style { - if attr&AttrBold != 0 { - st = st.Bold(true) - } - if attr&AttrUnderline != 0 { - st = st.Underline(true) - } - if attr&AttrReverse != 0 { - st = st.Reverse(true) - } - if attr&AttrBlink != 0 { - st = st.Blink(true) - } - if attr&AttrDim != 0 { - st = st.Dim(true) - } - if attr&AttrItalic != 0 { - st = st.Italic(true) - } - if attr&AttrStrikeThrough != 0 { - st = st.StrikeThrough(true) - } - return st -} diff --git a/go.sum b/go.sum index 10db7422..2a892bec 100644 --- a/go.sum +++ b/go.sum @@ -9,6 +9,7 @@ github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756 h1:9nuHUbU8dRnRRfj9KjWUVrJeoexdbeMjttk6Oh1rD10= golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/gui.go b/gui.go index 9aa3fc65..69657169 100644 --- a/gui.go +++ b/gui.go @@ -54,7 +54,7 @@ const ( // OutputTrue provides 24bit color terminal mode. // This mode is recommended even if your terminal doesn't support // such mode. The colors are represented exactly as you - // write them (no clamping or truncating). `tcell` will take care + // write them (no clamping or truncating). `tcell` should take care // of what your terminal can do. OutputTrue ) @@ -62,7 +62,7 @@ const ( // Gui represents the whole User Interface, including the views, layouts // and keybindings. type Gui struct { - tbEvents chan Event + gEvents chan gocuiEvent userEvents chan userEvent views []*View currentView *View @@ -117,7 +117,7 @@ func NewGui(mode OutputMode, supportOverlaps bool) (*Gui, error) { g.stop = make(chan struct{}) - g.tbEvents = make(chan Event, 20) + g.gEvents = make(chan gocuiEvent, 20) g.userEvents = make(chan userEvent, 20) if runtime.GOOS != "windows" { @@ -126,7 +126,7 @@ func NewGui(mode OutputMode, supportOverlaps bool) (*Gui, error) { return nil, err } } else { - g.maxX, g.maxY = tcellSize() + g.maxX, g.maxY = screen.Size() } g.BgColor, g.FgColor, g.FrameColor = ColorDefault, ColorDefault, ColorDefault @@ -145,7 +145,7 @@ func (g *Gui) Close() { go func() { g.stop <- struct{}{} }() - tcellClose() + screen.Fini() } // Size returns the terminal's size. @@ -441,7 +441,7 @@ func (g *Gui) SetManager(managers ...Manager) { g.views = nil g.keybindings = nil - go func() { g.tbEvents <- Event{Type: EventResize} }() + go func() { g.gEvents <- gocuiEvent{Type: eventResize} }() } // SetManagerFunc sets the given manager function. It deletes all views and @@ -464,26 +464,21 @@ func (g *Gui) MainLoop() error { case <-g.stop: return default: - g.tbEvents <- makeEvent(screen.PollEvent()) + g.gEvents <- pollEvent() } } }() - inputMode := InputAlt - if true { // previously g.InputEsc, but didn't seem to work - inputMode = InputEsc - } if g.Mouse { - inputMode |= InputMouse + screen.EnableMouse() } - tcellSetInputMode(inputMode) if err := g.flush(); err != nil { return err } for { select { - case ev := <-g.tbEvents: + case ev := <-g.gEvents: if err := g.handleEvent(&ev); err != nil { return err } @@ -505,7 +500,7 @@ func (g *Gui) MainLoop() error { func (g *Gui) consumeevents() error { for { select { - case ev := <-g.tbEvents: + case ev := <-g.gEvents: if err := g.handleEvent(&ev); err != nil { return err } @@ -521,14 +516,14 @@ func (g *Gui) consumeevents() error { // handleEvent handles an event, based on its type (key-press, error, // etc.) -func (g *Gui) handleEvent(ev *Event) error { +func (g *Gui) handleEvent(ev *gocuiEvent) error { switch ev.Type { - case EventKey, EventMouse: + case eventKey, eventMouse: return g.onKey(ev) - case EventError: + case eventError: return ev.Err // Not sure if this should be handled. It acts weirder when it's here - // case EventResize: + // case eventResize: // return Sync() default: return nil @@ -539,7 +534,7 @@ func (g *Gui) handleEvent(ev *Event) error { func (g *Gui) flush() error { g.clear(g.FgColor, g.BgColor) - maxX, maxY := tcellSize() + maxX, maxY := 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 { @@ -598,12 +593,12 @@ func (g *Gui) flush() error { return err } } - tcellFlush() + screen.Show() return nil } func (g *Gui) clear(fg, bg Attribute) (int, int) { - st := mkStyle(fg, bg, g.outputMode) + st := getTcellStyle(fg, bg, g.outputMode) w, h := screen.Size() for row := 0; row < h; row++ { for col := 0; col < w; col++ { @@ -823,14 +818,17 @@ func (g *Gui) draw(v *View) error { gMaxX, gMaxY := g.Size() cx, cy := curview.x0+curview.cx+1, curview.y0+curview.cy+1 + // This test probably doesn't need to be here. + // tcell is hiding cursor by setting coordinates outside of screen. + // Keeping it here for now, as I'm not 100% sure :) if cx >= 0 && cx < gMaxX && cy >= 0 && cy < gMaxY { - tcellSetCursor(cx, cy) + screen.ShowCursor(cx, cy) } else { - tcellHideCursor() + screen.HideCursor() } } } else { - tcellHideCursor() + screen.HideCursor() } v.clearRunes() @@ -843,9 +841,9 @@ func (g *Gui) draw(v *View) error { // onKey manages key-press events. A keybinding handler is called when // a key-press or mouse event satisfies a configured keybinding. Furthermore, // currentView's internal buffer is modified if currentView.Editable is true. -func (g *Gui) onKey(ev *Event) error { +func (g *Gui) onKey(ev *gocuiEvent) error { switch ev.Type { - case EventKey: + case eventKey: matched, err := g.execKeybindings(g.currentView, ev) if err != nil { return err @@ -856,7 +854,7 @@ func (g *Gui) onKey(ev *Event) error { 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)) } - case EventMouse: + case eventMouse: mx, my := ev.MouseX, ev.MouseY v, err := g.ViewByPosition(mx, my) if err != nil { @@ -875,7 +873,7 @@ func (g *Gui) onKey(ev *Event) error { // execKeybindings executes the keybinding 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 *Event) (matched bool, err error) { +func (g *Gui) execKeybindings(v *View, ev *gocuiEvent) (matched bool, err error) { var globalKb *keybinding for _, kb := range g.keybindings { diff --git a/tcell_compat.go b/tcell_driver.go similarity index 51% rename from tcell_compat.go rename to tcell_driver.go index 9973aae7..cea0da90 100644 --- a/tcell_compat.go +++ b/tcell_driver.go @@ -1,20 +1,6 @@ -// 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. - -// This is copy from original github.com/gdamore/tcell/termbox package -// for easier adoption of tcell. -// There are some changes made, to make it work with termbox keys (Ctrl modifier) +// Copyright 2020 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 @@ -23,11 +9,9 @@ import ( ) var screen tcell.Screen -var outMode OutputMode -// tcellInit initializes the screen for use. +// tcellInit initializes tcell screen for use. func tcellInit() error { - outMode = OutputNormal if s, e := tcell.NewScreen(); e != nil { return e } else if e = s.Init(); e != nil { @@ -38,72 +22,66 @@ func tcellInit() error { } } -// tcellClose cleans up the terminal, restoring terminal modes, etc. -func tcellClose() { - screen.Fini() -} - -// tcellFlush updates the screen. -func tcellFlush() error { - screen.Show() - return nil -} - -// tcellSetCursor displays the terminal cursor at the given location. -func tcellSetCursor(x, y int) { - screen.ShowCursor(x, y) -} - -// tcellHideCursor hides the terminal cursor. -func tcellHideCursor() { - tcellSetCursor(-1, -1) -} - -// tcellSize returns the screen size as width, height in character cells. -func tcellSize() (int, int) { - return screen.Size() +// tcellSetCell sets the character cell at a given location to the given +// content (rune) and attributes using provided OutputMode +func tcellSetCell(x, y int, ch rune, fg, bg Attribute, omode OutputMode) { + st := getTcellStyle(fg, bg, omode) + screen.SetContent(x, y, ch, nil, st) } -// InputMode is not used. -type InputMode int - -// Unused input modes; here for compatibility. -const ( - InputEsc InputMode = 1 << iota - InputAlt - InputMouse - InputCurrent InputMode = 0 -) +// getTcellStyle creates tcell.Style from Attributes +func getTcellStyle(fg, bg Attribute, omode OutputMode) tcell.Style { + st := tcell.StyleDefault -// tcellSetInputMode does not do anything in this version. -func tcellSetInputMode(mode InputMode) InputMode { - if mode&InputMouse != 0 { - screen.EnableMouse() - return InputEsc | InputMouse + // extract colors and attributes + if fg != ColorDefault { + st = st.Foreground(getTcellColor(fg, omode)) + st = setTcellFontEffectStyle(st, fg) + } + if bg != ColorDefault { + st = st.Background(getTcellColor(bg, omode)) + st = setTcellFontEffectStyle(st, bg) } - // We don't do anything else right now - return InputEsc -} -// tcellSync forces a resync of the screen. -func tcellSync() error { - screen.Sync() - return nil + return st } -// tcellSetCell sets the character cell at a given location to the given -// content (rune) and attributes. -func tcellSetCell(x, y int, ch rune, fg, bg Attribute, omode OutputMode) { - st := mkStyle(fg, bg, omode) - screen.SetContent(x, y, ch, nil, st) +// setTcellFontEffectStyle add additional attributes to tcell.Style +func setTcellFontEffectStyle(st tcell.Style, attr Attribute) tcell.Style { + if attr&AttrBold != 0 { + st = st.Bold(true) + } + if attr&AttrUnderline != 0 { + st = st.Underline(true) + } + if attr&AttrReverse != 0 { + st = st.Reverse(true) + } + if attr&AttrBlink != 0 { + st = st.Blink(true) + } + if attr&AttrDim != 0 { + st = st.Dim(true) + } + if attr&AttrItalic != 0 { + st = st.Italic(true) + } + if attr&AttrStrikeThrough != 0 { + st = st.StrikeThrough(true) + } + return st } -// EventType represents the type of event. -type EventType uint8 +// gocuiEventType represents the type of event. +type gocuiEventType uint8 -// Event represents an event like a key press, mouse action, or window resize. -type Event struct { - Type EventType +// gocuiEvent represents events like a keys, mouse actions, or window resize. +// The 'Mod', 'Key' and 'Ch' fields are valid if 'Type' is 'eventKey'. +// The 'MouseX' and 'MouseY' fields are valid if 'Type' is 'eventMouse'. +// The 'Width' and 'Height' fields are valid if 'Type' is 'eventResize'. +// The 'Err' field is valid if 'Type' is 'eventError'. +type gocuiEvent struct { + Type gocuiEventType Mod Modifier Key Key Ch rune @@ -117,13 +95,13 @@ type Event struct { // Event types. const ( - EventNone EventType = iota - EventKey - EventResize - EventMouse - EventInterrupt - EventError - EventRaw + eventNone gocuiEventType = iota + eventKey + eventResize + eventMouse + eventInterrupt + eventError + eventRaw ) var ( @@ -131,13 +109,15 @@ var ( lastMouseMod tcell.ModMask = tcell.ModNone ) -func makeEvent(tev tcell.Event) Event { +// pollEvent get tcell.Event and transform it into gocuiEvent +func pollEvent() gocuiEvent { + tev := screen.PollEvent() switch tev := tev.(type) { case *tcell.EventInterrupt: - return Event{Type: EventInterrupt} + return gocuiEvent{Type: eventInterrupt} case *tcell.EventResize: w, h := tev.Size() - return Event{Type: EventResize, Width: w, Height: h} + return gocuiEvent{Type: eventResize, Width: w, Height: h} case *tcell.EventKey: k := tev.Key() ch := rune(0) @@ -162,8 +142,8 @@ func makeEvent(tev tcell.Event) Event { // - ctrl - is translated in the key mod = 0 } - return Event{ - Type: EventKey, + return gocuiEvent{ + Type: eventKey, Key: Key(k), Ch: ch, Mod: Modifier(mod), @@ -211,8 +191,8 @@ func makeEvent(tev tcell.Event) Event { } } - return Event{ - Type: EventMouse, + return gocuiEvent{ + Type: eventMouse, MouseX: x, MouseY: y, Key: mouseKey, @@ -220,6 +200,6 @@ func makeEvent(tev tcell.Event) Event { Mod: mouseMod, } default: - return Event{Type: EventNone} + return gocuiEvent{Type: eventNone} } } From e283cd1c956003d2195982b726df825a4062ec74 Mon Sep 17 00:00:00 2001 From: danko Date: Tue, 10 Nov 2020 19:40:43 +0000 Subject: [PATCH 46/50] fix g.Rune() function --- gui.go | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/gui.go b/gui.go index 69657169..604da365 100644 --- a/gui.go +++ b/gui.go @@ -11,8 +11,9 @@ import ( "github.com/go-errors/errors" ) -// OutputMode represents the terminal's output mode (8 or 256 colors). -// type OutputMode OutputMode +// OutputMode represents an output mode, which determines how colors +// are used. +type OutputMode int var ( // ErrAlreadyBlacklisted is returned when the keybinding is already blacklisted. @@ -34,10 +35,6 @@ var ( ErrQuit = standardErrors.New("quit") ) -// OutputMode represents an output mode, which determines how colors -// are used. -type OutputMode int - const ( // OutputNormal provides 8-colors terminal mode. OutputNormal OutputMode = iota @@ -170,9 +167,8 @@ func (g *Gui) Rune(x, y int) (rune, error) { if x < 0 || y < 0 || x >= g.maxX || y >= g.maxY { return ' ', errors.New("invalid point") } - // c := CellBuffer()[y*g.maxX+x] - // return c.Ch, nil - return ' ', errors.New("invalid point") + c, _, _, _ := screen.GetContent(x, y) + return c, nil } // SetView creates a new view with its top-left corner at (x0, y0) From ab1c2311e43daf39daacd504aba724761e1242c8 Mon Sep 17 00:00:00 2001 From: danko Date: Tue, 10 Nov 2020 21:12:49 +0000 Subject: [PATCH 47/50] Add CHANGES documentation --- CHANGES_tcell.md | 56 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 CHANGES_tcell.md diff --git a/CHANGES_tcell.md b/CHANGES_tcell.md new file mode 100644 index 00000000..492afa37 --- /dev/null +++ b/CHANGES_tcell.md @@ -0,0 +1,56 @@ +# Change from termbox to tcell + +Original GOCUI was written on top of [termbox](https://github.com/nsf/termbox-go) package. This document describes changes which were done to be able to use to [tcell/v2](https://github.com/gdamore/tcell) package. + +## Attribute color + +Attribute type represents a terminal attribute like color and font effects. Color and font effects can be combined using bitwise OR (`|`). + +In `termbox` colors were represented by range 1 to 256. `0` was default color which uses the terminal default setting. + +In `tcell` colors can be represented in 24bit, and all of them starts from 0. Valid colors have special flag which gives them real value starting from 4294967296. `0` is a default similart to `termbox`. +The change to support all these colors was made in a way, that original colors from 1 to 256 are backward compatible and if user has color specified as +`Attribute(ansicolor+1)` without the valid color flag, it will be translated to `tcell` color by subtracting 1 and making the color valid by adding the flag. This should ensure backward compatibility. + +All the color constants are the same with different underlying values. From user perspective, this should be fine unless some arithmetic is done with it. For example `gocui.ColorBlack` was `1` in original version but is `4294967296` in new version. + +GOCUI provides a few helper functions which could be used to get the real color value or to create a color attribute. + +- `(a Attribute).Hex()` - returns `int32` value of the color represented as `Red << 16 | Green << 8 | Blue` +- `(a Attribute).RGB()` - returns 3 `int32` values for red, green and blue color. +- `gocui.GetColor(string)` - creates `Attribute` from color passed as a string. This can be hex value or color name (W3C name). +- `gocui.Get256Color(int32)` - creates `Attribute` from color number (ANSI colors). +- `gocui.GetRGBColor(int32)` - creates `Attribute` from color number created the same way as `Hex()` function returns. +- `gocui.NewRGBColor(int32, int32, int32)` - creates `Attribute` from color numbers for red, green and blue values. + +## Attribute font effect + +There were 3 attributes for font effect, `AttrBold`, `AttrUnderline` and `AttrReverse`. + +`tcell` supports more attributes, so they were added. All of these attributes have different values from before. However they can be used in the same way as before. + +All the font effect attributes: +- `AttrBold` +- `AttrBlink` +- `AttrReverse` +- `AttrUnderline` +- `AttrDim` +- `AttrItalic` +- `AttrStrikeThrough` + +## OutputMode + +`OutputMode` in `termbox` was used to translate colors into the correct range. So for example in `OutputGrayscale` you had colors from 1 - 24 all representing gray colors in range 232 - 255, and white and black color. + +`tcell` colors are 24bit and they are translated by the library into the color which can be read by terminal. + +The original translation from `termbox` was included in GOCUI to be backward compatible. This is enabled in all the original modes: `OutputNormal`, `Output216`, `OutputGrayscale` and `Output256`. + +`OutputTrue` is a new mode. It is recomended, because in this mode GOCUI doesn't do any kind of translation of the colors and pass them directly to `tcell`. If user wants to use true color in terminal and this mode doesn't work, it might be because of the terminal setup. `tcell` has a documentation what needs to be done, but in short `COLORTERM=truecolor` environment variable should help (see [_examples/colorstrue.go](./_examples/colorstrue.go)). Other way would be to have `TERM` environment variable having value with suffix `-truecolor`. To disable true color set `TCELL_TRUECOLOR=disable`. + +## Keybinding + +`termbox` had different way of handling input from terminal than `tcell`. This leads to some adjustement on how the keys are represented. +In general, all the keys in GOCUI should be presented from before, but the underlying values might be different. This could lead to some problems if a user uses different parser to create the `Key` for the keybinding. If using GOCUI parser, everything should be ok. + +Mouse is handled differently in `tcell`, but translation was done to keep it in the same way as it was before. However this was harder to test due to different behaviour across the platforms, so if anything is missing or not working, please report. \ No newline at end of file From d20d4521a849df96f7b2df864d17ff374b442fc2 Mon Sep 17 00:00:00 2001 From: mjarkk Date: Sat, 14 Nov 2020 15:24:03 +0100 Subject: [PATCH 48/50] Remove all gocui. prefixes in changes_tcell --- CHANGES_tcell.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/CHANGES_tcell.md b/CHANGES_tcell.md index 492afa37..7aa55234 100644 --- a/CHANGES_tcell.md +++ b/CHANGES_tcell.md @@ -8,24 +8,24 @@ Attribute type represents a terminal attribute like color and font effects. Colo In `termbox` colors were represented by range 1 to 256. `0` was default color which uses the terminal default setting. -In `tcell` colors can be represented in 24bit, and all of them starts from 0. Valid colors have special flag which gives them real value starting from 4294967296. `0` is a default similart to `termbox`. +In `tcell` colors can be represented in 24bit, and all of them starts from 0. Valid colors have special flag which gives them real value starting from 4294967296. `0` is a default similart to `termbox`. The change to support all these colors was made in a way, that original colors from 1 to 256 are backward compatible and if user has color specified as `Attribute(ansicolor+1)` without the valid color flag, it will be translated to `tcell` color by subtracting 1 and making the color valid by adding the flag. This should ensure backward compatibility. -All the color constants are the same with different underlying values. From user perspective, this should be fine unless some arithmetic is done with it. For example `gocui.ColorBlack` was `1` in original version but is `4294967296` in new version. +All the color constants are the same with different underlying values. From user perspective, this should be fine unless some arithmetic is done with it. For example `ColorBlack` was `1` in original version but is `4294967296` in new version. GOCUI provides a few helper functions which could be used to get the real color value or to create a color attribute. - `(a Attribute).Hex()` - returns `int32` value of the color represented as `Red << 16 | Green << 8 | Blue` - `(a Attribute).RGB()` - returns 3 `int32` values for red, green and blue color. -- `gocui.GetColor(string)` - creates `Attribute` from color passed as a string. This can be hex value or color name (W3C name). -- `gocui.Get256Color(int32)` - creates `Attribute` from color number (ANSI colors). -- `gocui.GetRGBColor(int32)` - creates `Attribute` from color number created the same way as `Hex()` function returns. -- `gocui.NewRGBColor(int32, int32, int32)` - creates `Attribute` from color numbers for red, green and blue values. +- `GetColor(string)` - creates `Attribute` from color passed as a string. This can be hex value or color name (W3C name). +- `Get256Color(int32)` - creates `Attribute` from color number (ANSI colors). +- `GetRGBColor(int32)` - creates `Attribute` from color number created the same way as `Hex()` function returns. +- `NewRGBColor(int32, int32, int32)` - creates `Attribute` from color numbers for red, green and blue values. ## Attribute font effect -There were 3 attributes for font effect, `AttrBold`, `AttrUnderline` and `AttrReverse`. +There were 3 attributes for font effect, `AttrBold`, `AttrUnderline` and `AttrReverse`. `tcell` supports more attributes, so they were added. All of these attributes have different values from before. However they can be used in the same way as before. @@ -42,7 +42,7 @@ All the font effect attributes: `OutputMode` in `termbox` was used to translate colors into the correct range. So for example in `OutputGrayscale` you had colors from 1 - 24 all representing gray colors in range 232 - 255, and white and black color. -`tcell` colors are 24bit and they are translated by the library into the color which can be read by terminal. +`tcell` colors are 24bit and they are translated by the library into the color which can be read by terminal. The original translation from `termbox` was included in GOCUI to be backward compatible. This is enabled in all the original modes: `OutputNormal`, `Output216`, `OutputGrayscale` and `Output256`. @@ -50,7 +50,7 @@ The original translation from `termbox` was included in GOCUI to be backward com ## Keybinding -`termbox` had different way of handling input from terminal than `tcell`. This leads to some adjustement on how the keys are represented. +`termbox` had different way of handling input from terminal than `tcell`. This leads to some adjustement on how the keys are represented. In general, all the keys in GOCUI should be presented from before, but the underlying values might be different. This could lead to some problems if a user uses different parser to create the `Key` for the keybinding. If using GOCUI parser, everything should be ok. -Mouse is handled differently in `tcell`, but translation was done to keep it in the same way as it was before. However this was harder to test due to different behaviour across the platforms, so if anything is missing or not working, please report. \ No newline at end of file +Mouse is handled differently in `tcell`, but translation was done to keep it in the same way as it was before. However this was harder to test due to different behaviour across the platforms, so if anything is missing or not working, please report. From 84a78cc106db59d4e28a197222dd3e582abc0b48 Mon Sep 17 00:00:00 2001 From: mjarkk Date: Wed, 23 Dec 2020 17:53:30 +0100 Subject: [PATCH 49/50] Fix circleci build error --- .circleci/config.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 89d5f0ad..e275579a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,10 +1,10 @@ version: 2 jobs: build: - working_directory: /go/src/github.com/glvr182/git-profile - + working_directory: /go/src/github.com/awesome-gocui/gocui + docker: - - image: circleci/golang:1.12 + - image: circleci/golang:1.15 steps: - checkout @@ -13,7 +13,7 @@ jobs: command: go mod tidy - run: name: go fmt - command: | + command: | if [ $(find . ! -path "./vendor/*" -name "*.go" -exec gofmt -s -d {} \;|wc -l) -gt 0 ]; then find . ! -path "./vendor/*" -name "*.go" -exec gofmt -s -d {} \; exit 1; @@ -24,11 +24,11 @@ jobs: cd _examples/ for file in *.go do - go build $file + go build $file done workflows: version: 2 build: jobs: - - build \ No newline at end of file + - build From 05b9329b5b69579d0ea5e9b05d93a2e7a3e83c45 Mon Sep 17 00:00:00 2001 From: mjarkk Date: Wed, 23 Dec 2020 18:56:51 +0100 Subject: [PATCH 50/50] Ran gofmt over the whole project --- _examples/custom_frame.go | 282 ++++++++++++++++---------------- attribute.go | 330 +++++++++++++++++++------------------- 2 files changed, 306 insertions(+), 306 deletions(-) diff --git a/_examples/custom_frame.go b/_examples/custom_frame.go index 6087bac8..ef4ae9f2 100644 --- a/_examples/custom_frame.go +++ b/_examples/custom_frame.go @@ -1,141 +1,141 @@ -package main - -import ( - "fmt" - "log" - - "github.com/awesome-gocui/gocui" -) - -var ( - viewArr = []string{"v1", "v2", "v3", "v4"} - active = 0 -) - -func setCurrentViewOnTop(g *gocui.Gui, name string) (*gocui.View, error) { - if _, err := g.SetCurrentView(name); err != nil { - return nil, err - } - return g.SetViewOnTop(name) -} - -func nextView(g *gocui.Gui, v *gocui.View) error { - nextIndex := (active + 1) % len(viewArr) - name := viewArr[nextIndex] - - out, err := g.View("v1") - if err != nil { - return err - } - fmt.Fprintln(out, "Going from view "+v.Name()+" to "+name) - - if _, err := setCurrentViewOnTop(g, name); err != nil { - return err - } - - if nextIndex == 3 { - g.Cursor = true - } else { - g.Cursor = false - } - - active = nextIndex - return nil -} - -func layout(g *gocui.Gui) error { - maxX, maxY := g.Size() - if v, err := g.SetView("v1", 0, 0, maxX/2-1, maxY/2-1, gocui.RIGHT); err != nil { - if !gocui.IsUnknownView(err) { - return err - } - v.Title = "v1" - v.Autoscroll = true - fmt.Fprintln(v, "View with default frame color") - fmt.Fprintln(v, "It's connected to v2 with overlay RIGHT.\n") - if _, err = setCurrentViewOnTop(g, "v1"); err != nil { - return err - } - } - - if v, err := g.SetView("v2", maxX/2-1, 0, maxX-1, maxY/2-1, gocui.LEFT); err != nil { - if !gocui.IsUnknownView(err) { - return err - } - v.Title = "v2" - v.Wrap = true - v.FrameColor = gocui.ColorMagenta - v.FrameRunes = []rune{'═', '│'} - fmt.Fprintln(v, "View with minimum frame customization and colored frame.") - fmt.Fprintln(v, "It's connected to v1 with overlay LEFT.\n") - fmt.Fprintln(v, "\033[35;1mInstructions:\033[0m") - fmt.Fprintln(v, "Press TAB to change current view") - fmt.Fprintln(v, "Press Ctrl+O to toggle gocui.SupportOverlap\n") - fmt.Fprintln(v, "\033[32;2mSelected frame is highlighted with green color\033[0m") - } - if v, err := g.SetView("v3", 0, maxY/2, maxX/2-1, maxY-1, 0); err != nil { - if !gocui.IsUnknownView(err) { - return err - } - v.Title = "v3" - v.Wrap = true - v.Autoscroll = true - v.FrameColor = gocui.ColorCyan - v.TitleColor = gocui.ColorCyan - v.FrameRunes = []rune{'═', '║', '╔', '╗', '╚', '╝'} - fmt.Fprintln(v, "View with basic frame customization and colored frame and title") - fmt.Fprintln(v, "It's not connected to any view.") - } - if v, err := g.SetView("v4", maxX/2, maxY/2, maxX-1, maxY-1, gocui.LEFT); err != nil { - if !gocui.IsUnknownView(err) { - return err - } - v.Title = "v4" - v.Subtitle = "(editable)" - v.Editable = true - v.TitleColor = gocui.ColorYellow - v.FrameColor = gocui.ColorRed - v.FrameRunes = []rune{'═', '║', '╔', '╗', '╚', '╝', '╠', '╣', '╦', '╩', '╬'} - fmt.Fprintln(v, "View with fully customized frame and colored title differently.") - fmt.Fprintln(v, "It's connected to v3 with overlay LEFT.\n") - v.SetCursor(0, 3) - } - return nil -} - -func quit(g *gocui.Gui, v *gocui.View) error { - return gocui.ErrQuit -} - -func toggleOverlap(g *gocui.Gui, v *gocui.View) error { - g.SupportOverlaps = !g.SupportOverlaps - return nil -} - -func main() { - g, err := gocui.NewGui(gocui.OutputNormal, true) - if err != nil { - log.Panicln(err) - } - defer g.Close() - - g.Highlight = true - g.SelFgColor = gocui.ColorGreen - g.SelFrameColor = gocui.ColorGreen - - g.SetManagerFunc(layout) - - if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil { - log.Panicln(err) - } - if err := g.SetKeybinding("", gocui.KeyCtrlO, gocui.ModNone, toggleOverlap); err != nil { - log.Panicln(err) - } - if err := g.SetKeybinding("", gocui.KeyTab, gocui.ModNone, nextView); err != nil { - log.Panicln(err) - } - - if err := g.MainLoop(); err != nil && !gocui.IsQuit(err) { - log.Panicln(err) - } -} +package main + +import ( + "fmt" + "log" + + "github.com/awesome-gocui/gocui" +) + +var ( + viewArr = []string{"v1", "v2", "v3", "v4"} + active = 0 +) + +func setCurrentViewOnTop(g *gocui.Gui, name string) (*gocui.View, error) { + if _, err := g.SetCurrentView(name); err != nil { + return nil, err + } + return g.SetViewOnTop(name) +} + +func nextView(g *gocui.Gui, v *gocui.View) error { + nextIndex := (active + 1) % len(viewArr) + name := viewArr[nextIndex] + + out, err := g.View("v1") + if err != nil { + return err + } + fmt.Fprintln(out, "Going from view "+v.Name()+" to "+name) + + if _, err := setCurrentViewOnTop(g, name); err != nil { + return err + } + + if nextIndex == 3 { + g.Cursor = true + } else { + g.Cursor = false + } + + active = nextIndex + return nil +} + +func layout(g *gocui.Gui) error { + maxX, maxY := g.Size() + if v, err := g.SetView("v1", 0, 0, maxX/2-1, maxY/2-1, gocui.RIGHT); err != nil { + if !gocui.IsUnknownView(err) { + return err + } + v.Title = "v1" + v.Autoscroll = true + fmt.Fprintln(v, "View with default frame color") + fmt.Fprintln(v, "It's connected to v2 with overlay RIGHT.\n") + if _, err = setCurrentViewOnTop(g, "v1"); err != nil { + return err + } + } + + if v, err := g.SetView("v2", maxX/2-1, 0, maxX-1, maxY/2-1, gocui.LEFT); err != nil { + if !gocui.IsUnknownView(err) { + return err + } + v.Title = "v2" + v.Wrap = true + v.FrameColor = gocui.ColorMagenta + v.FrameRunes = []rune{'═', '│'} + fmt.Fprintln(v, "View with minimum frame customization and colored frame.") + fmt.Fprintln(v, "It's connected to v1 with overlay LEFT.\n") + fmt.Fprintln(v, "\033[35;1mInstructions:\033[0m") + fmt.Fprintln(v, "Press TAB to change current view") + fmt.Fprintln(v, "Press Ctrl+O to toggle gocui.SupportOverlap\n") + fmt.Fprintln(v, "\033[32;2mSelected frame is highlighted with green color\033[0m") + } + if v, err := g.SetView("v3", 0, maxY/2, maxX/2-1, maxY-1, 0); err != nil { + if !gocui.IsUnknownView(err) { + return err + } + v.Title = "v3" + v.Wrap = true + v.Autoscroll = true + v.FrameColor = gocui.ColorCyan + v.TitleColor = gocui.ColorCyan + v.FrameRunes = []rune{'═', '║', '╔', '╗', '╚', '╝'} + fmt.Fprintln(v, "View with basic frame customization and colored frame and title") + fmt.Fprintln(v, "It's not connected to any view.") + } + if v, err := g.SetView("v4", maxX/2, maxY/2, maxX-1, maxY-1, gocui.LEFT); err != nil { + if !gocui.IsUnknownView(err) { + return err + } + v.Title = "v4" + v.Subtitle = "(editable)" + v.Editable = true + v.TitleColor = gocui.ColorYellow + v.FrameColor = gocui.ColorRed + v.FrameRunes = []rune{'═', '║', '╔', '╗', '╚', '╝', '╠', '╣', '╦', '╩', '╬'} + fmt.Fprintln(v, "View with fully customized frame and colored title differently.") + fmt.Fprintln(v, "It's connected to v3 with overlay LEFT.\n") + v.SetCursor(0, 3) + } + return nil +} + +func quit(g *gocui.Gui, v *gocui.View) error { + return gocui.ErrQuit +} + +func toggleOverlap(g *gocui.Gui, v *gocui.View) error { + g.SupportOverlaps = !g.SupportOverlaps + return nil +} + +func main() { + g, err := gocui.NewGui(gocui.OutputNormal, true) + if err != nil { + log.Panicln(err) + } + defer g.Close() + + g.Highlight = true + g.SelFgColor = gocui.ColorGreen + g.SelFrameColor = gocui.ColorGreen + + g.SetManagerFunc(layout) + + if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil { + log.Panicln(err) + } + if err := g.SetKeybinding("", gocui.KeyCtrlO, gocui.ModNone, toggleOverlap); err != nil { + log.Panicln(err) + } + if err := g.SetKeybinding("", gocui.KeyTab, gocui.ModNone, nextView); err != nil { + log.Panicln(err) + } + + if err := g.MainLoop(); err != nil && !gocui.IsQuit(err) { + log.Panicln(err) + } +} diff --git a/attribute.go b/attribute.go index adc933ff..54e39fc2 100644 --- a/attribute.go +++ b/attribute.go @@ -1,165 +1,165 @@ -// Copyright 2020 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" - -// Attribute affects the presentation of characters, such as color, boldness, etc. -type Attribute uint64 - -const ( - // ColorDefault is used to leave the Color unchanged from whatever system or teminal default may exist. - ColorDefault = Attribute(tcell.ColorDefault) - - // AttrIsValidColor is used to indicate the color value is actually - // valid (initialized). This is useful to permit the zero value - // to be treated as the default. - AttrIsValidColor = Attribute(tcell.ColorValid) - - // AttrIsRGBColor is used to indicate that the Attribute value is RGB value of color. - // The lower order 3 bytes are RGB. - // (It's not a color in basic ANSI range 256). - AttrIsRGBColor = Attribute(tcell.ColorIsRGB) - - // AttrColorBits is a mask where color is located in Attribute - AttrColorBits = 0xffffffffff // roughly 5 bytes, tcell uses 4 bytes and half-byte as a special flags for color (rest is reserved for future) - - // AttrStyleBits is a mask where character attributes (e.g.: bold, italic, underline) are located in Attribute - AttrStyleBits = 0xffffff0000000000 // remaining 3 bytes in the 8 bytes Attribute (tcell is not using it, so we should be fine) -) - -// Color attributes. These colors are compatible with tcell.Color type and can be expanded like: -// g.FgColor := gocui.Attribute(tcell.ColorLime) -const ( - ColorBlack Attribute = AttrIsValidColor + iota - ColorRed - ColorGreen - ColorYellow - ColorBlue - ColorMagenta - ColorCyan - ColorWhite -) - -// grayscale indexes (for backward compatibility with termbox-go original grayscale) -var grayscale = []tcell.Color{ - 16, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, - 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 231, -} - -// Attributes are not colors, but effects (e.g.: bold, dim) which affect the display of text. -// They can be combined. -const ( - AttrBold Attribute = 1 << (40 + iota) - AttrBlink - AttrReverse - AttrUnderline - AttrDim - AttrItalic - AttrStrikeThrough - AttrNone Attribute = 0 // Just normal text. -) - -// AttrAll represents all the text effect attributes turned on -const AttrAll = AttrBold | AttrBlink | AttrReverse | AttrUnderline | AttrDim | AttrItalic - -// IsValidColor indicates if the Attribute is a valid color value (has been set). -func (a Attribute) IsValidColor() bool { - return a&AttrIsValidColor != 0 -} - -// Hex returns the color's hexadecimal RGB 24-bit value with each component -// consisting of a single byte, ala R << 16 | G << 8 | B. If the color -// is unknown or unset, -1 is returned. -// -// This function produce the same output as `tcell.Hex()` with additional -// support for `termbox-go` colors (to 256). -func (a Attribute) Hex() int32 { - if !a.IsValidColor() { - return -1 - } - tc := getTcellColor(a, OutputTrue) - return tc.Hex() -} - -// RGB returns the red, green, and blue components of the color, with -// each component represented as a value 0-255. If the color -// is unknown or unset, -1 is returned for each component. -// -// This function produce the same output as `tcell.RGB()` with additional -// support for `termbox-go` colors (to 256). -func (a Attribute) RGB() (int32, int32, int32) { - v := a.Hex() - if v < 0 { - return -1, -1, -1 - } - return (v >> 16) & 0xff, (v >> 8) & 0xff, v & 0xff -} - -// GetColor creates a Color from a color name (W3C name). A hex value may -// be supplied as a string in the format "#ffffff". -func GetColor(color string) Attribute { - return Attribute(tcell.GetColor(color)) -} - -// Get256Color creates Attribute which stores ANSI color (0-255) -func Get256Color(color int32) Attribute { - return Attribute(color) | AttrIsValidColor -} - -// GetRGBColor creates Attribute which stores RGB color. -// Color is passed as 24bit RGB value, where R << 16 | G << 8 | B -func GetRGBColor(color int32) Attribute { - return Attribute(color) | AttrIsValidColor | AttrIsRGBColor -} - -// NewRGBColor creates Attribute which stores RGB color. -func NewRGBColor(r, g, b int32) Attribute { - return Attribute(tcell.NewRGBColor(r, g, b)) -} - -// getTcellColor transform Attribute into tcell.Color -func getTcellColor(c Attribute, omode OutputMode) tcell.Color { - c = c & AttrColorBits - // Default color is 0 in tcell/v2 and was 0 in termbox-go, so we are good here - if c == ColorDefault { - return tcell.ColorDefault - } - - tc := tcell.ColorDefault - // Check if we have valid color - if c.IsValidColor() { - tc = tcell.Color(c) - } else if c > 0 && c <= 256 { - // It's not valid color, but it has value in range 1-256 - // This is old Attribute style of color from termbox-go (black=1, etc.) - // convert to tcell color (black=0|ColorValid) - tc = tcell.Color(c-1) | tcell.ColorValid - } - - switch omode { - case OutputTrue: - return tc - case OutputNormal: - tc &= tcell.Color(0xf) | tcell.ColorValid - case Output256: - tc &= tcell.Color(0xff) | tcell.ColorValid - case Output216: - tc &= tcell.Color(0xff) - if tc > 215 { - return tcell.ColorDefault - } - tc += tcell.Color(16) | tcell.ColorValid - case OutputGrayscale: - tc &= tcell.Color(0x1f) - if tc > 26 { - return tcell.ColorDefault - } - tc = grayscale[tc] | tcell.ColorValid - default: - return tcell.ColorDefault - } - return tc -} +// Copyright 2020 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" + +// Attribute affects the presentation of characters, such as color, boldness, etc. +type Attribute uint64 + +const ( + // ColorDefault is used to leave the Color unchanged from whatever system or teminal default may exist. + ColorDefault = Attribute(tcell.ColorDefault) + + // AttrIsValidColor is used to indicate the color value is actually + // valid (initialized). This is useful to permit the zero value + // to be treated as the default. + AttrIsValidColor = Attribute(tcell.ColorValid) + + // AttrIsRGBColor is used to indicate that the Attribute value is RGB value of color. + // The lower order 3 bytes are RGB. + // (It's not a color in basic ANSI range 256). + AttrIsRGBColor = Attribute(tcell.ColorIsRGB) + + // AttrColorBits is a mask where color is located in Attribute + AttrColorBits = 0xffffffffff // roughly 5 bytes, tcell uses 4 bytes and half-byte as a special flags for color (rest is reserved for future) + + // AttrStyleBits is a mask where character attributes (e.g.: bold, italic, underline) are located in Attribute + AttrStyleBits = 0xffffff0000000000 // remaining 3 bytes in the 8 bytes Attribute (tcell is not using it, so we should be fine) +) + +// Color attributes. These colors are compatible with tcell.Color type and can be expanded like: +// g.FgColor := gocui.Attribute(tcell.ColorLime) +const ( + ColorBlack Attribute = AttrIsValidColor + iota + ColorRed + ColorGreen + ColorYellow + ColorBlue + ColorMagenta + ColorCyan + ColorWhite +) + +// grayscale indexes (for backward compatibility with termbox-go original grayscale) +var grayscale = []tcell.Color{ + 16, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, + 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 231, +} + +// Attributes are not colors, but effects (e.g.: bold, dim) which affect the display of text. +// They can be combined. +const ( + AttrBold Attribute = 1 << (40 + iota) + AttrBlink + AttrReverse + AttrUnderline + AttrDim + AttrItalic + AttrStrikeThrough + AttrNone Attribute = 0 // Just normal text. +) + +// AttrAll represents all the text effect attributes turned on +const AttrAll = AttrBold | AttrBlink | AttrReverse | AttrUnderline | AttrDim | AttrItalic + +// IsValidColor indicates if the Attribute is a valid color value (has been set). +func (a Attribute) IsValidColor() bool { + return a&AttrIsValidColor != 0 +} + +// Hex returns the color's hexadecimal RGB 24-bit value with each component +// consisting of a single byte, ala R << 16 | G << 8 | B. If the color +// is unknown or unset, -1 is returned. +// +// This function produce the same output as `tcell.Hex()` with additional +// support for `termbox-go` colors (to 256). +func (a Attribute) Hex() int32 { + if !a.IsValidColor() { + return -1 + } + tc := getTcellColor(a, OutputTrue) + return tc.Hex() +} + +// RGB returns the red, green, and blue components of the color, with +// each component represented as a value 0-255. If the color +// is unknown or unset, -1 is returned for each component. +// +// This function produce the same output as `tcell.RGB()` with additional +// support for `termbox-go` colors (to 256). +func (a Attribute) RGB() (int32, int32, int32) { + v := a.Hex() + if v < 0 { + return -1, -1, -1 + } + return (v >> 16) & 0xff, (v >> 8) & 0xff, v & 0xff +} + +// GetColor creates a Color from a color name (W3C name). A hex value may +// be supplied as a string in the format "#ffffff". +func GetColor(color string) Attribute { + return Attribute(tcell.GetColor(color)) +} + +// Get256Color creates Attribute which stores ANSI color (0-255) +func Get256Color(color int32) Attribute { + return Attribute(color) | AttrIsValidColor +} + +// GetRGBColor creates Attribute which stores RGB color. +// Color is passed as 24bit RGB value, where R << 16 | G << 8 | B +func GetRGBColor(color int32) Attribute { + return Attribute(color) | AttrIsValidColor | AttrIsRGBColor +} + +// NewRGBColor creates Attribute which stores RGB color. +func NewRGBColor(r, g, b int32) Attribute { + return Attribute(tcell.NewRGBColor(r, g, b)) +} + +// getTcellColor transform Attribute into tcell.Color +func getTcellColor(c Attribute, omode OutputMode) tcell.Color { + c = c & AttrColorBits + // Default color is 0 in tcell/v2 and was 0 in termbox-go, so we are good here + if c == ColorDefault { + return tcell.ColorDefault + } + + tc := tcell.ColorDefault + // Check if we have valid color + if c.IsValidColor() { + tc = tcell.Color(c) + } else if c > 0 && c <= 256 { + // It's not valid color, but it has value in range 1-256 + // This is old Attribute style of color from termbox-go (black=1, etc.) + // convert to tcell color (black=0|ColorValid) + tc = tcell.Color(c-1) | tcell.ColorValid + } + + switch omode { + case OutputTrue: + return tc + case OutputNormal: + tc &= tcell.Color(0xf) | tcell.ColorValid + case Output256: + tc &= tcell.Color(0xff) | tcell.ColorValid + case Output216: + tc &= tcell.Color(0xff) + if tc > 215 { + return tcell.ColorDefault + } + tc += tcell.Color(16) | tcell.ColorValid + case OutputGrayscale: + tc &= tcell.Color(0x1f) + if tc > 26 { + return tcell.ColorDefault + } + tc = grayscale[tc] | tcell.ColorValid + default: + return tcell.ColorDefault + } + return tc +}