Skip to content

Commit

Permalink
Merge branch 'master' into fix-text-insert
Browse files Browse the repository at this point in the history
mjarkk committed Dec 23, 2020
2 parents d9bfd68 + 0450a61 commit d755bd2
Showing 19 changed files with 1,144 additions and 215 deletions.
12 changes: 6 additions & 6 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -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
- build
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
*.swp
.idea
.vscode
56 changes: 56 additions & 0 deletions CHANGES_tcell.md
Original file line number Diff line number Diff line change
@@ -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 `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.
- `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`.

`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.
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -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,8 +23,9 @@ 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).

* Written ontop of TCell
* Better wide character support
* Support for 1 Line height views
* Support for running in docker container
@@ -69,7 +70,7 @@ import (
)

func main() {
g, err := gocui.NewGui(gocui.OutputNormal, false)
g, err := gocui.NewGui(gocui.OutputNormal, true)
if err != nil {
log.Panicln(err)
}
@@ -92,11 +93,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
}

109 changes: 109 additions & 0 deletions _examples/colorstrue.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// 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"
"os"

"github.com/awesome-gocui/gocui"
colorful "github.com/lucasb-eyer/go-colorful"
)

var dark = false

func main() {
os.Setenv("COLORTERM", "truecolor")
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()
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 {
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.Fprintln(v, "\n\x1b[38;5;245mCtrl + R - Switch light/dark mode")
fmt.Fprintln(v, "\nCtrl + C - Exit\n")
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) {
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
}
141 changes: 141 additions & 0 deletions _examples/custom_frame.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
1 change: 1 addition & 0 deletions _examples/dynamic.go
Original file line number Diff line number Diff line change
@@ -29,6 +29,7 @@ func main() {

g.Highlight = true
g.SelFgColor = gocui.ColorRed
g.SelFrameColor = gocui.ColorRed

g.SetManagerFunc(layout)

2 changes: 1 addition & 1 deletion _examples/hello.go
Original file line number Diff line number Diff line change
@@ -25,7 +25,7 @@ func main() {
}

if err := g.MainLoop(); err != nil && !gocui.IsQuit(err) {
log.Panicln(err.Error())
log.Panicln(err)
}
}

11 changes: 8 additions & 3 deletions _examples/mouse.go
Original file line number Diff line number Diff line change
@@ -72,6 +72,12 @@ func keybindings(g *gocui.Gui) error {
if err := g.SetKeybinding("msg", gocui.MouseLeft, gocui.ModNone, delMsg); err != nil {
return err
}
if err := g.SetKeybinding("", gocui.MouseRight, gocui.ModNone, delMsg); err != nil {
return err
}
if err := g.SetKeybinding("", gocui.MouseMiddle, gocui.ModNone, delMsg); err != nil {
return err
}
return nil
}

@@ -103,8 +109,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 {
return err
}
// Error check removed, because delete could be called multiple times with the above keybindings
g.DeleteView("msg")
return nil
}
2 changes: 1 addition & 1 deletion _examples/widgets.go
Original file line number Diff line number Diff line change
@@ -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)
173 changes: 153 additions & 20 deletions attribute.go
Original file line number Diff line number Diff line change
@@ -1,32 +1,165 @@
// Copyright 2014 The gocui Authors. All rights reserved.
// 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/awesome-gocui/termbox-go"
import "github.com/gdamore/tcell/v2"

// 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
// Attribute affects the presentation of characters, such as color, boldness, etc.
type Attribute uint64

// 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)
// 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
)

// Text style attributes.
// 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 = Attribute(termbox.AttrBold)
AttrUnderline = Attribute(termbox.AttrUnderline)
AttrReverse = Attribute(termbox.AttrReverse)
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
}
6 changes: 3 additions & 3 deletions doc.go
Original file line number Diff line number Diff line change
@@ -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
}
@@ -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 {
// ...
}
171 changes: 137 additions & 34 deletions escape.go
Original file line number Diff line number Diff line change
@@ -5,8 +5,9 @@
package gocui

import (
"github.com/go-errors/errors"
"strconv"

"github.com/go-errors/errors"
)

type escapeInterpreter struct {
@@ -17,13 +18,26 @@ type escapeInterpreter struct {
mode OutputMode
}

type escapeState int
type (
escapeState int
fontEffect int
)

const (
stateNone escapeState = iota
stateEscape
stateCSI
stateParams

bold fontEffect = 1
faint fontEffect = 2
italic fontEffect = 3
underline fontEffect = 4
blink fontEffect = 5
reverse fontEffect = 7
strike fontEffect = 9
setForegroundColor fontEffect = 38
setBackgroundColor fontEffect = 48
)

var (
@@ -125,6 +139,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
@@ -151,22 +167,18 @@ func (ei *escapeInterpreter) outputNormal() error {

switch {
case p >= 30 && p <= 37:
ei.curFgColor = Attribute(p - 30 + 1)
ei.curFgColor = Get256Color(int32(p) - 30)
case p == 39:
ei.curFgColor = ColorDefault
case p >= 40 && p <= 47:
ei.curBgColor = Attribute(p - 40 + 1)
ei.curBgColor = Get256Color(int32(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)
}
}

@@ -191,39 +203,130 @@ func (ei *escapeInterpreter) output256() error {
return ei.outputNormal()
}

fgbg, err := strconv.Atoi(ei.csiParam[0])
if err != nil {
return errCSIParseError
for _, param := range splitFgBg(ei.csiParam, 3) {
fgbg, err := strconv.Atoi(param[0])
if err != nil {
return errCSIParseError
}
color, err := strconv.Atoi(param[2])
if err != nil {
return errCSIParseError
}

switch fontEffect(fgbg) {
case setForegroundColor:
ei.curFgColor = Get256Color(int32(color))

for _, s := range param[3:] {
p, err := strconv.Atoi(s)
if err != nil {
return errCSIParseError
}

ei.curFgColor |= getFontEffect(p)
}
case setBackgroundColor:
ei.curBgColor = Get256Color(int32(color))
default:
return errCSIParseError
}
}
return nil
}

// outputTrue allows you to leverage the true-color terminal mode.
//
// Works with rgb ANSI sequence: `\x1b[38;2;<r>;<g>;<b>m`, `\x1b[48;2;<r>;<g>;<b>m`
func (ei *escapeInterpreter) outputTrue() error {
if len(ei.csiParam) < 5 {
return ei.output256()
}
color, err := strconv.Atoi(ei.csiParam[2])

mode, err := strconv.Atoi(ei.csiParam[1])
if err != nil {
return errCSIParseError
}
if mode != 2 {
return ei.output256()
}

switch fgbg {
case 38:
ei.curFgColor = Attribute(color + 1)
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 := NewRGBColor(int32(colr), int32(colg), int32(colb))

for _, param := range ei.csiParam[3:] {
p, err := strconv.Atoi(param)
if err != nil {
return errCSIParseError
}
switch fontEffect(fgbg) {
case setForegroundColor:
ei.curFgColor = color

for _, s := range param[5:] {
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
ei.curFgColor |= getFontEffect(p)
}
case setBackgroundColor:
ei.curBgColor = color
default:
return errCSIParseError
}
case 48:
ei.curBgColor = Attribute(color + 1)
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) == num && (p == "48" || p == "38") {
out = append(out, current)
current = []string{}
}
current = append(current, p)
}

if len(current) > 0 {
out = append(out, current)
}

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
case strike:
return AttrStrikeThrough
}
return AttrNone
}
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@ module github.com/awesome-gocui/gocui
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/gdamore/tcell/v2 v2.0.0
github.com/go-errors/errors v1.0.2
github.com/mattn/go-runewidth v0.0.9
)
21 changes: 15 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
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/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=
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=
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 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=
217 changes: 171 additions & 46 deletions gui.go

Large diffs are not rendered by default.

168 changes: 88 additions & 80 deletions keybinding.go
Original file line number Diff line number Diff line change
@@ -7,15 +7,15 @@ package gocui
import (
"strings"

"github.com/awesome-gocui/termbox-go"
"github.com/gdamore/tcell/v2"
)

// Key represents special keys or keys combinations.
type Key termbox.Key
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 termbox.Modifier
type Modifier tcell.ModMask

// Keybidings are used to link a given key-press event with a handler.
type keybinding struct {
@@ -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
@@ -197,89 +197,97 @@ var translate = map[string]Key{

// Special keys.
const (
KeyF1 Key = Key(termbox.KeyF1)
KeyF2 = Key(termbox.KeyF2)
KeyF3 = Key(termbox.KeyF3)
KeyF4 = Key(termbox.KeyF4)
KeyF5 = Key(termbox.KeyF5)
KeyF6 = Key(termbox.KeyF6)
KeyF7 = Key(termbox.KeyF7)
KeyF8 = Key(termbox.KeyF8)
KeyF9 = Key(termbox.KeyF9)
KeyF10 = Key(termbox.KeyF10)
KeyF11 = Key(termbox.KeyF11)
KeyF12 = Key(termbox.KeyF12)
KeyInsert = Key(termbox.KeyInsert)
KeyDelete = Key(termbox.KeyDelete)
KeyHome = Key(termbox.KeyHome)
KeyEnd = Key(termbox.KeyEnd)
KeyPgup = Key(termbox.KeyPgup)
KeyPgdn = Key(termbox.KeyPgdn)
KeyArrowUp = Key(termbox.KeyArrowUp)
KeyArrowDown = Key(termbox.KeyArrowDown)
KeyArrowLeft = Key(termbox.KeyArrowLeft)
KeyArrowRight = Key(termbox.KeyArrowRight)

MouseLeft = Key(termbox.MouseLeft)
MouseMiddle = Key(termbox.MouseMiddle)
MouseRight = Key(termbox.MouseRight)
MouseRelease = Key(termbox.MouseRelease)
MouseWheelUp = Key(termbox.MouseWheelUp)
MouseWheelDown = Key(termbox.MouseWheelDown)
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 = 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(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 = Key(32)
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(termbox.ModAlt)
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)
)
205 changes: 205 additions & 0 deletions tcell_driver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
// 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"
)

var screen tcell.Screen

// tcellInit initializes tcell screen for use.
func tcellInit() error {
if s, e := tcell.NewScreen(); e != nil {
return e
} else if e = s.Init(); e != nil {
return e
} else {
screen = s
return nil
}
}

// 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)
}

// getTcellStyle creates tcell.Style from Attributes
func getTcellStyle(fg, bg Attribute, omode OutputMode) tcell.Style {
st := tcell.StyleDefault

// 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)
}

return 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
}

// gocuiEventType represents the type of event.
type gocuiEventType uint8

// 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
Width int
Height int
Err error
MouseX int
MouseY int
N int
}

// Event types.
const (
eventNone gocuiEventType = iota
eventKey
eventResize
eventMouse
eventInterrupt
eventError
eventRaw
)

var (
lastMouseKey tcell.ButtonMask = tcell.ButtonNone
lastMouseMod tcell.ModMask = tcell.ModNone
)

// pollEvent get tcell.Event and transform it into gocuiEvent
func pollEvent() gocuiEvent {
tev := screen.PollEvent()
switch tev := tev.(type) {
case *tcell.EventInterrupt:
return gocuiEvent{Type: eventInterrupt}
case *tcell.EventResize:
w, h := tev.Size()
return gocuiEvent{Type: eventResize, Width: w, Height: h}
case *tcell.EventKey:
k := tev.Key()
ch := rune(0)
if k == tcell.KeyRune {
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 == 32 {
mod = 0
ch = rune(0)
k = tcell.KeyCtrlSpace
} 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 gocuiEvent{
Type: eventKey,
Key: Key(k),
Ch: ch,
Mod: Modifier(mod),
}
case *tcell.EventMouse:
x, y := tev.Position()
button := tev.Buttons()
mouseKey := MouseRelease
mouseMod := ModNone
// process mouse wheel
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
}

// 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.ButtonPrimary:
mouseKey = MouseLeft
case tcell.ButtonSecondary:
mouseKey = MouseRight
case tcell.ButtonMiddle:
mouseKey = MouseMiddle
}
mouseMod = Modifier(lastMouseMod)
lastMouseMod = tcell.ModNone
lastMouseKey = tcell.ButtonNone
}
}

return gocuiEvent{
Type: eventMouse,
MouseX: x,
MouseY: y,
Key: mouseKey,
Ch: 0,
Mod: mouseMod,
}
default:
return gocuiEvent{Type: eventNone}
}
}
44 changes: 36 additions & 8 deletions view.go
Original file line number Diff line number Diff line change
@@ -12,8 +12,6 @@ import (
"unicode/utf8"

"github.com/go-errors/errors"

"github.com/awesome-gocui/termbox-go"
"github.com/mattn/go-runewidth"
)

@@ -41,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
@@ -88,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.
@@ -100,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

@@ -113,6 +133,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

// gui contains the view it's gui
gui *Gui
}
@@ -151,9 +175,14 @@ func (g *Gui) newView(name string, x0, y0, x1, y1 int, mode OutputMode) *View {
Frame: true,
Editor: DefaultEditor,
tainted: true,
outMode: mode,
ei: newEscapeInterpreter(mode),
gui: g,
}

v.FgColor, v.BgColor = ColorDefault, ColorDefault
v.SelFgColor, v.SelBgColor = ColorDefault, ColorDefault
v.TitleColor, v.FrameColor = ColorDefault, ColorDefault
return v
}

@@ -185,17 +214,17 @@ func (v *View) setRune(x, y int, ch rune, fgColor, bgColor Attribute) error {
fgColor = v.FgColor
bgColor = v.BgColor
ch = v.Mask
} else if v.Highlight && y == v.cy {
fgColor = fgColor | AttrBold
} else if v.Highlight && ry == rcy {
fgColor = v.SelFgColor | AttrBold
bgColor = v.SelBgColor | AttrBold
}

// Don't display NUL characters
if ch == 0 {
ch = ' '
}

termbox.SetCell(v.x0+x+1, v.y0+y+1, ch,
termbox.Attribute(fgColor), termbox.Attribute(bgColor))
tcellSetCell(v.x0+x+1, v.y0+y+1, ch, fgColor, bgColor, v.outMode)

return nil
}
@@ -653,8 +682,7 @@ func (v *View) clearRunes() {
maxX, maxY := v.Size()
for x := 0; x < maxX; x++ {
for y := 0; y < maxY; y++ {
termbox.SetCell(v.x0+x+1, v.y0+y+1, ' ',
termbox.Attribute(v.FgColor), termbox.Attribute(v.BgColor))
tcellSetCell(v.x0+x+1, v.y0+y+1, ' ', v.FgColor, v.BgColor, v.outMode)
}
}
}

0 comments on commit d755bd2

Please sign in to comment.