mirror of https://github.com/miguelmota/cointop
closes #24
parent
b7ea461a07
commit
6eb6236104
@ -1,7 +1,7 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
cmc "github.com/miguelmota/cointop/pkg/api/impl/coinmarketcap"
|
||||
cmc "github.com/miguelmota/cointop/cointop/common/api/impl/coinmarketcap"
|
||||
)
|
||||
|
||||
// NewCMC new CoinMarketCap API
|
@ -1,7 +1,7 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
types "github.com/miguelmota/cointop/pkg/api/types"
|
||||
types "github.com/miguelmota/cointop/cointop/common/api/types"
|
||||
)
|
||||
|
||||
// Interface interface
|
@ -1,4 +1,4 @@
|
||||
package fcache
|
||||
package filecache
|
||||
|
||||
import (
|
||||
"bytes"
|
@ -0,0 +1,24 @@
|
||||
package timeutil
|
||||
|
||||
import "time"
|
||||
|
||||
// Now now struct
|
||||
type Now struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
// New initialize Now with time
|
||||
func New(t time.Time) *Now {
|
||||
return &Now{t}
|
||||
}
|
||||
|
||||
// BeginningOfYear beginning of year
|
||||
func BeginningOfYear() time.Time {
|
||||
return New(time.Now()).BeginningOfYear()
|
||||
}
|
||||
|
||||
// BeginningOfYear BeginningOfYear beginning of year
|
||||
func (now *Now) BeginningOfYear() time.Time {
|
||||
y, _, _ := now.Date()
|
||||
return time.Date(y, time.January, 1, 0, 0, 0, 0, now.Location())
|
||||
}
|
@ -1,25 +1,36 @@
|
||||
github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/anaskhan96/soup v0.0.0-20180328123631-cd07662ec930/go.mod h1:pT5vs4HXDwA5y4KQCsKvnkpQd3D+joP7IqpiGskfWW0=
|
||||
github.com/anaskhan96/soup v1.1.1 h1:Duux/0htS2Va7XLJ9qIakCSey790hg9OFRm2FwlMTy0=
|
||||
github.com/anaskhan96/soup v1.1.1/go.mod h1:pT5vs4HXDwA5y4KQCsKvnkpQd3D+joP7IqpiGskfWW0=
|
||||
github.com/fatih/color v1.6.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/gizak/termui v2.3.0+incompatible h1:S8wJoNumYfc/rR5UezUM4HsPEo3RJh0LKdiuDWQpjqw=
|
||||
github.com/gizak/termui v2.3.0+incompatible/go.mod h1:PkJoWUt/zacQKysNfQtcw1RW+eK2SxkieVBtl+4ovLA=
|
||||
github.com/jroimartin/gocui v0.4.0 h1:52jnalstgmc25FmtGcWqa0tcbMEWS6RpFLsOIO+I+E8=
|
||||
github.com/jroimartin/gocui v0.4.0/go.mod h1:7i7bbj99OgFHzo7kB2zPb8pXLqMBSQegY7azfqXMkyY=
|
||||
github.com/maruel/panicparse v1.1.1 h1:k62YPcEoLncEEpjMt92GtG5ugb8WL/510Ys3/h5IkRc=
|
||||
github.com/maruel/panicparse v1.1.1/go.mod h1:nty42YY5QByNC5MM7q/nj938VbgPU7avs45z6NClpxI=
|
||||
github.com/maruel/panicparse v1.1.2-0.20180806203336-f20d4c4d746f h1:mtX2D0ta3lWxCvv276VVIH6mMYzm4jhSfYP70ZJSOQU=
|
||||
github.com/maruel/panicparse v1.1.2-0.20180806203336-f20d4c4d746f/go.mod h1:nty42YY5QByNC5MM7q/nj938VbgPU7avs45z6NClpxI=
|
||||
github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg=
|
||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.6 h1:SrwhHcpV4nWrMGdNcC2kXpMfcBVYGDuTArqyhocJgvA=
|
||||
github.com/mattn/go-isatty v0.0.6/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
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=
|
||||
golang.org/x/net v0.0.0-20180420171651-5f9ae10d9af5/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4=
|
||||
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
|
||||
github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d h1:x3S6kxmy49zXVVyhcnrFqxvNVCBPb2KZ9hV2RBdS840=
|
||||
github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3 h1:eH6Eip3UpmR+yM/qI9Ijluzb1bNv/cAU/n+6l8tRSis=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sys v0.0.0-20180425194835-bb9c189858d9/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6 h1:IcgEB62HYgAhX0Nd/QrVgZlxlcyxbGQHElLUhW2X4Fo=
|
||||
golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
@ -1,138 +0,0 @@
|
||||
// Package now is a time toolkit for golang.
|
||||
//
|
||||
// More details README here: https://github.com/jinzhu/now
|
||||
//
|
||||
// import "github.com/jinzhu/now"
|
||||
//
|
||||
// now.BeginningOfMinute() // 2013-11-18 17:51:00 Mon
|
||||
// now.BeginningOfDay() // 2013-11-18 00:00:00 Mon
|
||||
// now.EndOfDay() // 2013-11-18 23:59:59.999999999 Mon
|
||||
package now
|
||||
|
||||
import "time"
|
||||
|
||||
// WeekStartDay set week start day, default is sunday
|
||||
var WeekStartDay = time.Sunday
|
||||
|
||||
// TimeFormats default time formats will be parsed as
|
||||
var TimeFormats = []string{"1/2/2006", "1/2/2006 15:4:5", "2006", "2006-1", "2006-1-2", "2006-1-2 15", "2006-1-2 15:4", "2006-1-2 15:4:5", "1-2", "15:4:5", "15:4", "15", "15:4:5 Jan 2, 2006 MST", "2006-01-02 15:04:05.999999999 -0700 MST"}
|
||||
|
||||
// Now now struct
|
||||
type Now struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
// New initialize Now with time
|
||||
func New(t time.Time) *Now {
|
||||
return &Now{t}
|
||||
}
|
||||
|
||||
// BeginningOfMinute beginning of minute
|
||||
func BeginningOfMinute() time.Time {
|
||||
return New(time.Now()).BeginningOfMinute()
|
||||
}
|
||||
|
||||
// BeginningOfHour beginning of hour
|
||||
func BeginningOfHour() time.Time {
|
||||
return New(time.Now()).BeginningOfHour()
|
||||
}
|
||||
|
||||
// BeginningOfDay beginning of day
|
||||
func BeginningOfDay() time.Time {
|
||||
return New(time.Now()).BeginningOfDay()
|
||||
}
|
||||
|
||||
// BeginningOfWeek beginning of week
|
||||
func BeginningOfWeek() time.Time {
|
||||
return New(time.Now()).BeginningOfWeek()
|
||||
}
|
||||
|
||||
// BeginningOfMonth beginning of month
|
||||
func BeginningOfMonth() time.Time {
|
||||
return New(time.Now()).BeginningOfMonth()
|
||||
}
|
||||
|
||||
// BeginningOfQuarter beginning of quarter
|
||||
func BeginningOfQuarter() time.Time {
|
||||
return New(time.Now()).BeginningOfQuarter()
|
||||
}
|
||||
|
||||
// BeginningOfYear beginning of year
|
||||
func BeginningOfYear() time.Time {
|
||||
return New(time.Now()).BeginningOfYear()
|
||||
}
|
||||
|
||||
// EndOfMinute end of minute
|
||||
func EndOfMinute() time.Time {
|
||||
return New(time.Now()).EndOfMinute()
|
||||
}
|
||||
|
||||
// EndOfHour end of hour
|
||||
func EndOfHour() time.Time {
|
||||
return New(time.Now()).EndOfHour()
|
||||
}
|
||||
|
||||
// EndOfDay end of day
|
||||
func EndOfDay() time.Time {
|
||||
return New(time.Now()).EndOfDay()
|
||||
}
|
||||
|
||||
// EndOfWeek end of week
|
||||
func EndOfWeek() time.Time {
|
||||
return New(time.Now()).EndOfWeek()
|
||||
}
|
||||
|
||||
// EndOfMonth end of month
|
||||
func EndOfMonth() time.Time {
|
||||
return New(time.Now()).EndOfMonth()
|
||||
}
|
||||
|
||||
// EndOfQuarter end of quarter
|
||||
func EndOfQuarter() time.Time {
|
||||
return New(time.Now()).EndOfQuarter()
|
||||
}
|
||||
|
||||
// EndOfYear end of year
|
||||
func EndOfYear() time.Time {
|
||||
return New(time.Now()).EndOfYear()
|
||||
}
|
||||
|
||||
// Monday monday
|
||||
func Monday() time.Time {
|
||||
return New(time.Now()).Monday()
|
||||
}
|
||||
|
||||
// Sunday sunday
|
||||
func Sunday() time.Time {
|
||||
return New(time.Now()).Sunday()
|
||||
}
|
||||
|
||||
// EndOfSunday end of sunday
|
||||
func EndOfSunday() time.Time {
|
||||
return New(time.Now()).EndOfSunday()
|
||||
}
|
||||
|
||||
// Parse parse string to time
|
||||
func Parse(strs ...string) (time.Time, error) {
|
||||
return New(time.Now()).Parse(strs...)
|
||||
}
|
||||
|
||||
// ParseInLocation parse string to time in location
|
||||
func ParseInLocation(loc *time.Location, strs ...string) (time.Time, error) {
|
||||
return New(time.Now().In(loc)).Parse(strs...)
|
||||
}
|
||||
|
||||
// MustParse must parse string to time or will panic
|
||||
func MustParse(strs ...string) time.Time {
|
||||
return New(time.Now()).MustParse(strs...)
|
||||
}
|
||||
|
||||
// MustParseInLocation must parse string to time in location or will panic
|
||||
func MustParseInLocation(loc *time.Location, strs ...string) time.Time {
|
||||
return New(time.Now().In(loc)).MustParse(strs...)
|
||||
}
|
||||
|
||||
// Between check now between the begin, end time or not
|
||||
func Between(time1, time2 string) bool {
|
||||
return New(time.Now()).Between(time1, time2)
|
||||
}
|
@ -1,204 +0,0 @@
|
||||
package now
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"regexp"
|
||||
"time"
|
||||
)
|
||||
|
||||
// BeginningOfMinute beginning of minute
|
||||
func (now *Now) BeginningOfMinute() time.Time {
|
||||
return now.Truncate(time.Minute)
|
||||
}
|
||||
|
||||
// BeginningOfHour beginning of hour
|
||||
func (now *Now) BeginningOfHour() time.Time {
|
||||
y, m, d := now.Date()
|
||||
return time.Date(y, m, d, now.Time.Hour(), 0, 0, 0, now.Time.Location())
|
||||
}
|
||||
|
||||
// BeginningOfDay beginning of day
|
||||
func (now *Now) BeginningOfDay() time.Time {
|
||||
y, m, d := now.Date()
|
||||
return time.Date(y, m, d, 0, 0, 0, 0, now.Time.Location())
|
||||
}
|
||||
|
||||
// BeginningOfWeek beginning of week
|
||||
func (now *Now) BeginningOfWeek() time.Time {
|
||||
t := now.BeginningOfDay()
|
||||
weekday := int(t.Weekday())
|
||||
|
||||
if WeekStartDay != time.Sunday {
|
||||
weekStartDayInt := int(WeekStartDay)
|
||||
|
||||
if weekday < weekStartDayInt {
|
||||
weekday = weekday + 7 - weekStartDayInt
|
||||
} else {
|
||||
weekday = weekday - weekStartDayInt
|
||||
}
|
||||
}
|
||||
return t.AddDate(0, 0, -weekday)
|
||||
}
|
||||
|
||||
// BeginningOfMonth beginning of month
|
||||
func (now *Now) BeginningOfMonth() time.Time {
|
||||
y, m, _ := now.Date()
|
||||
return time.Date(y, m, 1, 0, 0, 0, 0, now.Location())
|
||||
}
|
||||
|
||||
// BeginningOfQuarter beginning of quarter
|
||||
func (now *Now) BeginningOfQuarter() time.Time {
|
||||
month := now.BeginningOfMonth()
|
||||
offset := (int(month.Month()) - 1) % 3
|
||||
return month.AddDate(0, -offset, 0)
|
||||
}
|
||||
|
||||
// BeginningOfYear BeginningOfYear beginning of year
|
||||
func (now *Now) BeginningOfYear() time.Time {
|
||||
y, _, _ := now.Date()
|
||||
return time.Date(y, time.January, 1, 0, 0, 0, 0, now.Location())
|
||||
}
|
||||
|
||||
// EndOfMinute end of minute
|
||||
func (now *Now) EndOfMinute() time.Time {
|
||||
return now.BeginningOfMinute().Add(time.Minute - time.Nanosecond)
|
||||
}
|
||||
|
||||
// EndOfHour end of hour
|
||||
func (now *Now) EndOfHour() time.Time {
|
||||
return now.BeginningOfHour().Add(time.Hour - time.Nanosecond)
|
||||
}
|
||||
|
||||
// EndOfDay end of day
|
||||
func (now *Now) EndOfDay() time.Time {
|
||||
y, m, d := now.Date()
|
||||
return time.Date(y, m, d, 23, 59, 59, int(time.Second-time.Nanosecond), now.Location())
|
||||
}
|
||||
|
||||
// EndOfWeek end of week
|
||||
func (now *Now) EndOfWeek() time.Time {
|
||||
return now.BeginningOfWeek().AddDate(0, 0, 7).Add(-time.Nanosecond)
|
||||
}
|
||||
|
||||
// EndOfMonth end of month
|
||||
func (now *Now) EndOfMonth() time.Time {
|
||||
return now.BeginningOfMonth().AddDate(0, 1, 0).Add(-time.Nanosecond)
|
||||
}
|
||||
|
||||
// EndOfQuarter end of quarter
|
||||
func (now *Now) EndOfQuarter() time.Time {
|
||||
return now.BeginningOfQuarter().AddDate(0, 3, 0).Add(-time.Nanosecond)
|
||||
}
|
||||
|
||||
// EndOfYear end of year
|
||||
func (now *Now) EndOfYear() time.Time {
|
||||
return now.BeginningOfYear().AddDate(1, 0, 0).Add(-time.Nanosecond)
|
||||
}
|
||||
|
||||
// Monday monday
|
||||
func (now *Now) Monday() time.Time {
|
||||
t := now.BeginningOfDay()
|
||||
weekday := int(t.Weekday())
|
||||
if weekday == 0 {
|
||||
weekday = 7
|
||||
}
|
||||
return t.AddDate(0, 0, -weekday+1)
|
||||
}
|
||||
|
||||
// Sunday sunday
|
||||
func (now *Now) Sunday() time.Time {
|
||||
t := now.BeginningOfDay()
|
||||
weekday := int(t.Weekday())
|
||||
if weekday == 0 {
|
||||
return t
|
||||
}
|
||||
return t.AddDate(0, 0, (7 - weekday))
|
||||
}
|
||||
|
||||
// EndOfSunday end of sunday
|
||||
func (now *Now) EndOfSunday() time.Time {
|
||||
return New(now.Sunday()).EndOfDay()
|
||||
}
|
||||
|
||||
func parseWithFormat(str string) (t time.Time, err error) {
|
||||
for _, format := range TimeFormats {
|
||||
t, err = time.Parse(format, str)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
err = errors.New("Can't parse string as time: " + str)
|
||||
return
|
||||
}
|
||||
|
||||
var hasTimeRegexp = regexp.MustCompile(`(\s+|^\s*)\d{1,2}(:\d{1,2})*\s*$`) // match 15:04:05, 15, 2017-01-01 15:04
|
||||
var onlyTimeRegexp = regexp.MustCompile(`^\s*\d{1,2}(:\d{1,2})*\s*$`) // match 15:04:05, 15
|
||||
|
||||
// Parse parse string to time
|
||||
func (now *Now) Parse(strs ...string) (t time.Time, err error) {
|
||||
var (
|
||||
setCurrentTime bool
|
||||
parseTime []int
|
||||
currentTime = []int{now.Second(), now.Minute(), now.Hour(), now.Day(), int(now.Month()), now.Year()}
|
||||
currentLocation = now.Location()
|
||||
onlyTimeInStr = true
|
||||
)
|
||||
|
||||
for _, str := range strs {
|
||||
hasTimeInStr := hasTimeRegexp.MatchString(str) // match 15:04:05, 15
|
||||
onlyTimeInStr = hasTimeInStr && onlyTimeInStr && onlyTimeRegexp.MatchString(str)
|
||||
|
||||
if t, err = parseWithFormat(str); err == nil {
|
||||
location := t.Location()
|
||||
if location.String() == "UTC" {
|
||||
location = currentLocation
|
||||
}
|
||||
|
||||
parseTime = []int{t.Second(), t.Minute(), t.Hour(), t.Day(), int(t.Month()), t.Year()}
|
||||
|
||||
for i, v := range parseTime {
|
||||
// Don't reset hour, minute, second if current time str including time
|
||||
if hasTimeInStr && i <= 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
// If value is zero, replace it with current time
|
||||
if v == 0 {
|
||||
if setCurrentTime {
|
||||
parseTime[i] = currentTime[i]
|
||||
}
|
||||
} else {
|
||||
setCurrentTime = true
|
||||
}
|
||||
|
||||
// if current time only includes time, should change day, month to current time
|
||||
if onlyTimeInStr {
|
||||
if i == 3 || i == 4 {
|
||||
parseTime[i] = currentTime[i]
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
t = time.Date(parseTime[5], time.Month(parseTime[4]), parseTime[3], parseTime[2], parseTime[1], parseTime[0], 0, location)
|
||||
currentTime = []int{t.Second(), t.Minute(), t.Hour(), t.Day(), int(t.Month()), t.Year()}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// MustParse must parse string to time or it will panic
|
||||
func (now *Now) MustParse(strs ...string) (t time.Time) {
|
||||
t, err := now.Parse(strs...)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// Between check time between the begin, end time or not
|
||||
func (now *Now) Between(begin, end string) bool {
|
||||
beginTime := now.MustParse(begin)
|
||||
endTime := now.MustParse(end)
|
||||
return now.After(beginTime) && now.Before(endTime)
|
||||
}
|
@ -1,300 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/mattn/go-runewidth"
|
||||
"github.com/nsf/termbox-go"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
func tbprint(x, y int, fg, bg termbox.Attribute, msg string) {
|
||||
for _, c := range msg {
|
||||
termbox.SetCell(x, y, c, fg, bg)
|
||||
x += runewidth.RuneWidth(c)
|
||||
}
|
||||
}
|
||||
|
||||
func fill(x, y, w, h int, cell termbox.Cell) {
|
||||
for ly := 0; ly < h; ly++ {
|
||||
for lx := 0; lx < w; lx++ {
|
||||
termbox.SetCell(x+lx, y+ly, cell.Ch, cell.Fg, cell.Bg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func rune_advance_len(r rune, pos int) int {
|
||||
if r == '\t' {
|
||||
return tabstop_length - pos%tabstop_length
|
||||
}
|
||||
return runewidth.RuneWidth(r)
|
||||
}
|
||||
|
||||
func voffset_coffset(text []byte, boffset int) (voffset, coffset int) {
|
||||
text = text[:boffset]
|
||||
for len(text) > 0 {
|
||||
r, size := utf8.DecodeRune(text)
|
||||
text = text[size:]
|
||||
coffset += 1
|
||||
voffset += rune_advance_len(r, voffset)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func byte_slice_grow(s []byte, desired_cap int) []byte {
|
||||
if cap(s) < desired_cap {
|
||||
ns := make([]byte, len(s), desired_cap)
|
||||
copy(ns, s)
|
||||
return ns
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func byte_slice_remove(text []byte, from, to int) []byte {
|
||||
size := to - from
|
||||
copy(text[from:], text[to:])
|
||||
text = text[:len(text)-size]
|
||||
return text
|
||||
}
|
||||
|
||||
func byte_slice_insert(text []byte, offset int, what []byte) []byte {
|
||||
n := len(text) + len(what)
|
||||
text = byte_slice_grow(text, n)
|
||||
text = text[:n]
|
||||
copy(text[offset+len(what):], text[offset:])
|
||||
copy(text[offset:], what)
|
||||
return text
|
||||
}
|
||||
|
||||
const preferred_horizontal_threshold = 5
|
||||
const tabstop_length = 8
|
||||
|
||||
type EditBox struct {
|
||||
text []byte
|
||||
line_voffset int
|
||||
cursor_boffset int // cursor offset in bytes
|
||||
cursor_voffset int // visual cursor offset in termbox cells
|
||||
cursor_coffset int // cursor offset in unicode code points
|
||||
}
|
||||
|
||||
// Draws the EditBox in the given location, 'h' is not used at the moment
|
||||
func (eb *EditBox) Draw(x, y, w, h int) {
|
||||
eb.AdjustVOffset(w)
|
||||
|
||||
const coldef = termbox.ColorDefault
|
||||
fill(x, y, w, h, termbox.Cell{Ch: ' '})
|
||||
|
||||
t := eb.text
|
||||
lx := 0
|
||||
tabstop := 0
|
||||
for {
|
||||
rx := lx - eb.line_voffset
|
||||
if len(t) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
if lx == tabstop {
|
||||
tabstop += tabstop_length
|
||||
}
|
||||
|
||||
if rx >= w {
|
||||
termbox.SetCell(x+w-1, y, '→',
|
||||
coldef, coldef)
|
||||
break
|
||||
}
|
||||
|
||||
r, size := utf8.DecodeRune(t)
|
||||
if r == '\t' {
|
||||
for ; lx < tabstop; lx++ {
|
||||
rx = lx - eb.line_voffset
|
||||
if rx >= w {
|
||||
goto next
|
||||
}
|
||||
|
||||
if rx >= 0 {
|
||||
termbox.SetCell(x+rx, y, ' ', coldef, coldef)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if rx >= 0 {
|
||||
termbox.SetCell(x+rx, y, r, coldef, coldef)
|
||||
}
|
||||
lx += runewidth.RuneWidth(r)
|
||||
}
|
||||
next:
|
||||
t = t[size:]
|
||||
}
|
||||
|
||||
if eb.line_voffset != 0 {
|
||||
termbox.SetCell(x, y, '←', coldef, coldef)
|
||||
}
|
||||
}
|
||||
|
||||
// Adjusts line visual offset to a proper value depending on width
|
||||
func (eb *EditBox) AdjustVOffset(width int) {
|
||||
ht := preferred_horizontal_threshold
|
||||
max_h_threshold := (width - 1) / 2
|
||||
if ht > max_h_threshold {
|
||||
ht = max_h_threshold
|
||||
}
|
||||
|
||||
threshold := width - 1
|
||||
if eb.line_voffset != 0 {
|
||||
threshold = width - ht
|
||||
}
|
||||
if eb.cursor_voffset-eb.line_voffset >= threshold {
|
||||
eb.line_voffset = eb.cursor_voffset + (ht - width + 1)
|
||||
}
|
||||
|
||||
if eb.line_voffset != 0 && eb.cursor_voffset-eb.line_voffset < ht {
|
||||
eb.line_voffset = eb.cursor_voffset - ht
|
||||
if eb.line_voffset < 0 {
|
||||
eb.line_voffset = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (eb *EditBox) MoveCursorTo(boffset int) {
|
||||
eb.cursor_boffset = boffset
|
||||
eb.cursor_voffset, eb.cursor_coffset = voffset_coffset(eb.text, boffset)
|
||||
}
|
||||
|
||||
func (eb *EditBox) RuneUnderCursor() (rune, int) {
|
||||
return utf8.DecodeRune(eb.text[eb.cursor_boffset:])
|
||||
}
|
||||
|
||||
func (eb *EditBox) RuneBeforeCursor() (rune, int) {
|
||||
return utf8.DecodeLastRune(eb.text[:eb.cursor_boffset])
|
||||
}
|
||||
|
||||
func (eb *EditBox) MoveCursorOneRuneBackward() {
|
||||
if eb.cursor_boffset == 0 {
|
||||
return
|
||||
}
|
||||
_, size := eb.RuneBeforeCursor()
|
||||
eb.MoveCursorTo(eb.cursor_boffset - size)
|
||||
}
|
||||
|
||||
func (eb *EditBox) MoveCursorOneRuneForward() {
|
||||
if eb.cursor_boffset == len(eb.text) {
|
||||
return
|
||||
}
|
||||
_, size := eb.RuneUnderCursor()
|
||||
eb.MoveCursorTo(eb.cursor_boffset + size)
|
||||
}
|
||||
|
||||
func (eb *EditBox) MoveCursorToBeginningOfTheLine() {
|
||||
eb.MoveCursorTo(0)
|
||||
}
|
||||
|
||||
func (eb *EditBox) MoveCursorToEndOfTheLine() {
|
||||
eb.MoveCursorTo(len(eb.text))
|
||||
}
|
||||
|
||||
func (eb *EditBox) DeleteRuneBackward() {
|
||||
if eb.cursor_boffset == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
eb.MoveCursorOneRuneBackward()
|
||||
_, size := eb.RuneUnderCursor()
|
||||
eb.text = byte_slice_remove(eb.text, eb.cursor_boffset, eb.cursor_boffset+size)
|
||||
}
|
||||
|
||||
func (eb *EditBox) DeleteRuneForward() {
|
||||
if eb.cursor_boffset == len(eb.text) {
|
||||
return
|
||||
}
|
||||
_, size := eb.RuneUnderCursor()
|
||||
eb.text = byte_slice_remove(eb.text, eb.cursor_boffset, eb.cursor_boffset+size)
|
||||
}
|
||||
|
||||
func (eb *EditBox) DeleteTheRestOfTheLine() {
|
||||
eb.text = eb.text[:eb.cursor_boffset]
|
||||
}
|
||||
|
||||
func (eb *EditBox) InsertRune(r rune) {
|
||||
var buf [utf8.UTFMax]byte
|
||||
n := utf8.EncodeRune(buf[:], r)
|
||||
eb.text = byte_slice_insert(eb.text, eb.cursor_boffset, buf[:n])
|
||||
eb.MoveCursorOneRuneForward()
|
||||
}
|
||||
|
||||
// Please, keep in mind that cursor depends on the value of line_voffset, which
|
||||
// is being set on Draw() call, so.. call this method after Draw() one.
|
||||
func (eb *EditBox) CursorX() int {
|
||||
return eb.cursor_voffset - eb.line_voffset
|
||||
}
|
||||
|
||||
var edit_box EditBox
|
||||
|
||||
const edit_box_width = 30
|
||||
|
||||
func redraw_all() {
|
||||
const coldef = termbox.ColorDefault
|
||||
termbox.Clear(coldef, coldef)
|
||||
w, h := termbox.Size()
|
||||
|
||||
midy := h / 2
|
||||
midx := (w - edit_box_width) / 2
|
||||
|
||||
// unicode box drawing chars around the edit box
|
||||
termbox.SetCell(midx-1, midy, '│', coldef, coldef)
|
||||
termbox.SetCell(midx+edit_box_width, midy, '│', coldef, coldef)
|
||||
termbox.SetCell(midx-1, midy-1, '┌', coldef, coldef)
|
||||
termbox.SetCell(midx-1, midy+1, '└', coldef, coldef)
|
||||
termbox.SetCell(midx+edit_box_width, midy-1, '┐', coldef, coldef)
|
||||
termbox.SetCell(midx+edit_box_width, midy+1, '┘', coldef, coldef)
|
||||
fill(midx, midy-1, edit_box_width, 1, termbox.Cell{Ch: '─'})
|
||||
fill(midx, midy+1, edit_box_width, 1, termbox.Cell{Ch: '─'})
|
||||
|
||||
edit_box.Draw(midx, midy, edit_box_width, 1)
|
||||
termbox.SetCursor(midx+edit_box.CursorX(), midy)
|
||||
|
||||
tbprint(midx+6, midy+3, coldef, coldef, "Press ESC to quit")
|
||||
termbox.Flush()
|
||||
}
|
||||
|
||||
func main() {
|
||||
err := termbox.Init()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer termbox.Close()
|
||||
termbox.SetInputMode(termbox.InputEsc)
|
||||
|
||||
redraw_all()
|
||||
mainloop:
|
||||
for {
|
||||
switch ev := termbox.PollEvent(); ev.Type {
|
||||
case termbox.EventKey:
|
||||
switch ev.Key {
|
||||
case termbox.KeyEsc:
|
||||
break mainloop
|
||||
case termbox.KeyArrowLeft, termbox.KeyCtrlB:
|
||||
edit_box.MoveCursorOneRuneBackward()
|
||||
case termbox.KeyArrowRight, termbox.KeyCtrlF:
|
||||
edit_box.MoveCursorOneRuneForward()
|
||||
case termbox.KeyBackspace, termbox.KeyBackspace2:
|
||||
edit_box.DeleteRuneBackward()
|
||||
case termbox.KeyDelete, termbox.KeyCtrlD:
|
||||
edit_box.DeleteRuneForward()
|
||||
case termbox.KeyTab:
|
||||
edit_box.InsertRune('\t')
|
||||
case termbox.KeySpace:
|
||||
edit_box.InsertRune(' ')
|
||||
case termbox.KeyCtrlK:
|
||||
edit_box.DeleteTheRestOfTheLine()
|
||||
case termbox.KeyHome, termbox.KeyCtrlA:
|
||||
edit_box.MoveCursorToBeginningOfTheLine()
|
||||
case termbox.KeyEnd, termbox.KeyCtrlE:
|
||||
edit_box.MoveCursorToEndOfTheLine()
|
||||
default:
|
||||
if ev.Ch != 0 {
|
||||
edit_box.InsertRune(ev.Ch)
|
||||
}
|
||||
}
|
||||
case termbox.EventError:
|
||||
panic(ev.Err)
|
||||
}
|
||||
redraw_all()
|
||||
}
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/nsf/termbox-go"
|
||||
"time"
|
||||
)
|
||||
|
||||
func tbPrint(x, y int, fg, bg termbox.Attribute, msg string) {
|
||||
for _, c := range msg {
|
||||
termbox.SetCell(x, y, c, fg, bg)
|
||||
x++
|
||||
}
|
||||
}
|
||||
|
||||
func draw(i int) {
|
||||
termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)
|
||||
defer termbox.Flush()
|
||||
|
||||
w, h := termbox.Size()
|
||||
s := fmt.Sprintf("count = %d", i)
|
||||
|
||||
tbPrint((w/2)-(len(s)/2), h/2, termbox.ColorRed, termbox.ColorDefault, s)
|
||||
}
|
||||
|
||||
func main() {
|
||||
err := termbox.Init()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
termbox.SetInputMode(termbox.InputEsc)
|
||||
|
||||
go func() {
|
||||
time.Sleep(5 * time.Second)
|
||||
termbox.Interrupt()
|
||||
|
||||
// This should never run - the Interrupt(), above, should cause the event
|
||||
// loop below to exit, which then exits the process. If something goes
|
||||
// wrong, this panic will trigger and show what happened.
|
||||
time.Sleep(1 * time.Second)
|
||||
panic("this should never run")
|
||||
}()
|
||||
|
||||
var count int
|
||||
|
||||
draw(count)
|
||||
mainloop:
|
||||
for {
|
||||
switch ev := termbox.PollEvent(); ev.Type {
|
||||
case termbox.EventKey:
|
||||
if ev.Ch == '+' {
|
||||
count++
|
||||
} else if ev.Ch == '-' {
|
||||
count--
|
||||
}
|
||||
|
||||
case termbox.EventError:
|
||||
panic(ev.Err)
|
||||
|
||||
case termbox.EventInterrupt:
|
||||
break mainloop
|
||||
}
|
||||
|
||||
draw(count)
|
||||
}
|
||||
termbox.Close()
|
||||
|
||||
fmt.Println("Finished")
|
||||
}
|
@ -1,722 +0,0 @@
|
||||
package main
|
||||
|
||||
import "github.com/nsf/termbox-go"
|
||||
import "fmt"
|
||||
|
||||
type key struct {
|
||||
x int
|
||||
y int
|
||||
ch rune
|
||||
}
|
||||
|
||||
var K_ESC = []key{{1, 1, 'E'}, {2, 1, 'S'}, {3, 1, 'C'}}
|
||||
var K_F1 = []key{{6, 1, 'F'}, {7, 1, '1'}}
|
||||
var K_F2 = []key{{9, 1, 'F'}, {10, 1, '2'}}
|
||||
var K_F3 = []key{{12, 1, 'F'}, {13, 1, '3'}}
|
||||
var K_F4 = []key{{15, 1, 'F'}, {16, 1, '4'}}
|
||||
var K_F5 = []key{{19, 1, 'F'}, {20, 1, '5'}}
|
||||
var K_F6 = []key{{22, 1, 'F'}, {23, 1, '6'}}
|
||||
var K_F7 = []key{{25, 1, 'F'}, {26, 1, '7'}}
|
||||
var K_F8 = []key{{28, 1, 'F'}, {29, 1, '8'}}
|
||||
var K_F9 = []key{{33, 1, 'F'}, {34, 1, '9'}}
|
||||
var K_F10 = []key{{36, 1, 'F'}, {37, 1, '1'}, {38, 1, '0'}}
|
||||
var K_F11 = []key{{40, 1, 'F'}, {41, 1, '1'}, {42, 1, '1'}}
|
||||
var K_F12 = []key{{44, 1, 'F'}, {45, 1, '1'}, {46, 1, '2'}}
|
||||
var K_PRN = []key{{50, 1, 'P'}, {51, 1, 'R'}, {52, 1, 'N'}}
|
||||
var K_SCR = []key{{54, 1, 'S'}, {55, 1, 'C'}, {56, 1, 'R'}}
|
||||
var K_BRK = []key{{58, 1, 'B'}, {59, 1, 'R'}, {60, 1, 'K'}}
|
||||
var K_LED1 = []key{{66, 1, '-'}}
|
||||
var K_LED2 = []key{{70, 1, '-'}}
|
||||
var K_LED3 = []key{{74, 1, '-'}}
|
||||
var K_TILDE = []key{{1, 4, '`'}}
|
||||
var K_TILDE_SHIFT = []key{{1, 4, '~'}}
|
||||
var K_1 = []key{{4, 4, '1'}}
|
||||
var K_1_SHIFT = []key{{4, 4, '!'}}
|
||||
var K_2 = []key{{7, 4, '2'}}
|
||||
var K_2_SHIFT = []key{{7, 4, '@'}}
|
||||
var K_3 = []key{{10, 4, '3'}}
|
||||
var K_3_SHIFT = []key{{10, 4, '#'}}
|
||||
var K_4 = []key{{13, 4, '4'}}
|
||||
var K_4_SHIFT = []key{{13, 4, '$'}}
|
||||
var K_5 = []key{{16, 4, '5'}}
|
||||
var K_5_SHIFT = []key{{16, 4, '%'}}
|
||||
var K_6 = []key{{19, 4, '6'}}
|
||||
var K_6_SHIFT = []key{{19, 4, '^'}}
|
||||
var K_7 = []key{{22, 4, '7'}}
|
||||
var K_7_SHIFT = []key{{22, 4, '&'}}
|
||||
var K_8 = []key{{25, 4, '8'}}
|
||||
var K_8_SHIFT = []key{{25, 4, '*'}}
|
||||
var K_9 = []key{{28, 4, '9'}}
|
||||
var K_9_SHIFT = []key{{28, 4, '('}}
|
||||
var K_0 = []key{{31, 4, '0'}}
|
||||
var K_0_SHIFT = []key{{31, 4, ')'}}
|
||||
var K_MINUS = []key{{34, 4, '-'}}
|
||||
var K_MINUS_SHIFT = []key{{34, 4, '_'}}
|
||||
var K_EQUALS = []key{{37, 4, '='}}
|
||||
var K_EQUALS_SHIFT = []key{{37, 4, '+'}}
|
||||
var K_BACKSLASH = []key{{40, 4, '\\'}}
|
||||
var K_BACKSLASH_SHIFT = []key{{40, 4, '|'}}
|
||||
var K_BACKSPACE = []key{{44, 4, 0x2190}, {45, 4, 0x2500}, {46, 4, 0x2500}}
|
||||
var K_INS = []key{{50, 4, 'I'}, {51, 4, 'N'}, {52, 4, 'S'}}
|
||||
var K_HOM = []key{{54, 4, 'H'}, {55, 4, 'O'}, {56, 4, 'M'}}
|
||||
var K_PGU = []key{{58, 4, 'P'}, {59, 4, 'G'}, {60, 4, 'U'}}
|
||||
var K_K_NUMLOCK = []key{{65, 4, 'N'}}
|
||||
var K_K_SLASH = []key{{68, 4, '/'}}
|
||||
var K_K_STAR = []key{{71, 4, '*'}}
|
||||
var K_K_MINUS = []key{{74, 4, '-'}}
|
||||
var K_TAB = []key{{1, 6, 'T'}, {2, 6, 'A'}, {3, 6, 'B'}}
|
||||
var K_q = []key{{6, 6, 'q'}}
|
||||
var K_Q = []key{{6, 6, 'Q'}}
|
||||
var K_w = []key{{9, 6, 'w'}}
|
||||
var K_W = []key{{9, 6, 'W'}}
|
||||
var K_e = []key{{12, 6, 'e'}}
|
||||
var K_E = []key{{12, 6, 'E'}}
|
||||
var K_r = []key{{15, 6, 'r'}}
|
||||
var K_R = []key{{15, 6, 'R'}}
|
||||
var K_t = []key{{18, 6, 't'}}
|
||||
var K_T = []key{{18, 6, 'T'}}
|
||||
var K_y = []key{{21, 6, 'y'}}
|
||||
var K_Y = []key{{21, 6, 'Y'}}
|
||||
var K_u = []key{{24, 6, 'u'}}
|
||||
var K_U = []key{{24, 6, 'U'}}
|
||||
var K_i = []key{{27, 6, 'i'}}
|
||||
var K_I = []key{{27, 6, 'I'}}
|
||||
var K_o = []key{{30, 6, 'o'}}
|
||||
var K_O = []key{{30, 6, 'O'}}
|
||||
var K_p = []key{{33, 6, 'p'}}
|
||||
var K_P = []key{{33, 6, 'P'}}
|
||||
var K_LSQB = []key{{36, 6, '['}}
|
||||
var K_LCUB = []key{{36, 6, '{'}}
|
||||
var K_RSQB = []key{{39, 6, ']'}}
|
||||
var K_RCUB = []key{{39, 6, '}'}}
|
||||
var K_ENTER = []key{
|
||||
{43, 6, 0x2591}, {44, 6, 0x2591}, {45, 6, 0x2591}, {46, 6, 0x2591},
|
||||
{43, 7, 0x2591}, {44, 7, 0x2591}, {45, 7, 0x21B5}, {46, 7, 0x2591},
|
||||
{41, 8, 0x2591}, {42, 8, 0x2591}, {43, 8, 0x2591}, {44, 8, 0x2591},
|
||||
{45, 8, 0x2591}, {46, 8, 0x2591},
|
||||
}
|
||||
var K_DEL = []key{{50, 6, 'D'}, {51, 6, 'E'}, {52, 6, 'L'}}
|
||||
var K_END = []key{{54, 6, 'E'}, {55, 6, 'N'}, {56, 6, 'D'}}
|
||||
var K_PGD = []key{{58, 6, 'P'}, {59, 6, 'G'}, {60, 6, 'D'}}
|
||||
var K_K_7 = []key{{65, 6, '7'}}
|
||||
var K_K_8 = []key{{68, 6, '8'}}
|
||||
var K_K_9 = []key{{71, 6, '9'}}
|
||||
var K_K_PLUS = []key{{74, 6, ' '}, {74, 7, '+'}, {74, 8, ' '}}
|
||||
var K_CAPS = []key{{1, 8, 'C'}, {2, 8, 'A'}, {3, 8, 'P'}, {4, 8, 'S'}}
|
||||
var K_a = []key{{7, 8, 'a'}}
|
||||
var K_A = []key{{7, 8, 'A'}}
|
||||
var K_s = []key{{10, 8, 's'}}
|
||||
var K_S = []key{{10, 8, 'S'}}
|
||||
var K_d = []key{{13, 8, 'd'}}
|
||||
var K_D = []key{{13, 8, 'D'}}
|
||||
var K_f = []key{{16, 8, 'f'}}
|
||||
var K_F = []key{{16, 8, 'F'}}
|
||||
var K_g = []key{{19, 8, 'g'}}
|
||||
var K_G = []key{{19, 8, 'G'}}
|
||||
var K_h = []key{{22, 8, 'h'}}
|
||||
var K_H = []key{{22, 8, 'H'}}
|
||||
var K_j = []key{{25, 8, 'j'}}
|
||||
var K_J = []key{{25, 8, 'J'}}
|
||||
var K_k = []key{{28, 8, 'k'}}
|
||||
var K_K = []key{{28, 8, 'K'}}
|
||||
var K_l = []key{{31, 8, 'l'}}
|
||||
var K_L = []key{{31, 8, 'L'}}
|
||||
var K_SEMICOLON = []key{{34, 8, ';'}}
|
||||
var K_PARENTHESIS = []key{{34, 8, ':'}}
|
||||
var K_QUOTE = []key{{37, 8, '\''}}
|
||||
var K_DOUBLEQUOTE = []key{{37, 8, '"'}}
|
||||
var K_K_4 = []key{{65, 8, '4'}}
|
||||
var K_K_5 = []key{{68, 8, '5'}}
|
||||
var K_K_6 = []key{{71, 8, '6'}}
|
||||
var K_LSHIFT = []key{{1, 10, 'S'}, {2, 10, 'H'}, {3, 10, 'I'}, {4, 10, 'F'}, {5, 10, 'T'}}
|
||||
var K_z = []key{{9, 10, 'z'}}
|
||||
var K_Z = []key{{9, 10, 'Z'}}
|
||||
var K_x = []key{{12, 10, 'x'}}
|
||||
var K_X = []key{{12, 10, 'X'}}
|
||||
var K_c = []key{{15, 10, 'c'}}
|
||||
var K_C = []key{{15, 10, 'C'}}
|
||||
var K_v = []key{{18, 10, 'v'}}
|
||||
var K_V = []key{{18, 10, 'V'}}
|
||||
var K_b = []key{{21, 10, 'b'}}
|
||||
var K_B = []key{{21, 10, 'B'}}
|
||||
var K_n = []key{{24, 10, 'n'}}
|
||||
var K_N = []key{{24, 10, 'N'}}
|
||||
var K_m = []key{{27, 10, 'm'}}
|
||||
var K_M = []key{{27, 10, 'M'}}
|
||||
var K_COMMA = []key{{30, 10, ','}}
|
||||
var K_LANB = []key{{30, 10, '<'}}
|
||||
var K_PERIOD = []key{{33, 10, '.'}}
|
||||
var K_RANB = []key{{33, 10, '>'}}
|
||||
var K_SLASH = []key{{36, 10, '/'}}
|
||||
var K_QUESTION = []key{{36, 10, '?'}}
|
||||
var K_RSHIFT = []key{{42, 10, 'S'}, {43, 10, 'H'}, {44, 10, 'I'}, {45, 10, 'F'}, {46, 10, 'T'}}
|
||||
var K_ARROW_UP = []key{{54, 10, '('}, {55, 10, 0x2191}, {56, 10, ')'}}
|
||||
var K_K_1 = []key{{65, 10, '1'}}
|
||||
var K_K_2 = []key{{68, 10, '2'}}
|
||||
var K_K_3 = []key{{71, 10, '3'}}
|
||||
var K_K_ENTER = []key{{74, 10, 0x2591}, {74, 11, 0x2591}, {74, 12, 0x2591}}
|
||||
var K_LCTRL = []key{{1, 12, 'C'}, {2, 12, 'T'}, {3, 12, 'R'}, {4, 12, 'L'}}
|
||||
var K_LWIN = []key{{6, 12, 'W'}, {7, 12, 'I'}, {8, 12, 'N'}}
|
||||
var K_LALT = []key{{10, 12, 'A'}, {11, 12, 'L'}, {12, 12, 'T'}}
|
||||
var K_SPACE = []key{
|
||||
{14, 12, ' '}, {15, 12, ' '}, {16, 12, ' '}, {17, 12, ' '}, {18, 12, ' '},
|
||||
{19, 12, 'S'}, {20, 12, 'P'}, {21, 12, 'A'}, {22, 12, 'C'}, {23, 12, 'E'},
|
||||
{24, 12, ' '}, {25, 12, ' '}, {26, 12, ' '}, {27, 12, ' '}, {28, 12, ' '},
|
||||
}
|
||||
var K_RALT = []key{{30, 12, 'A'}, {31, 12, 'L'}, {32, 12, 'T'}}
|
||||
var K_RWIN = []key{{34, 12, 'W'}, {35, 12, 'I'}, {36, 12, 'N'}}
|
||||
var K_RPROP = []key{{38, 12, 'P'}, {39, 12, 'R'}, {40, 12, 'O'}, {41, 12, 'P'}}
|
||||
var K_RCTRL = []key{{43, 12, 'C'}, {44, 12, 'T'}, {45, 12, 'R'}, {46, 12, 'L'}}
|
||||
var K_ARROW_LEFT = []key{{50, 12, '('}, {51, 12, 0x2190}, {52, 12, ')'}}
|
||||
var K_ARROW_DOWN = []key{{54, 12, '('}, {55, 12, 0x2193}, {56, 12, ')'}}
|
||||
var K_ARROW_RIGHT = []key{{58, 12, '('}, {59, 12, 0x2192}, {60, 12, ')'}}
|
||||
var K_K_0 = []key{{65, 12, ' '}, {66, 12, '0'}, {67, 12, ' '}, {68, 12, ' '}}
|
||||
var K_K_PERIOD = []key{{71, 12, '.'}}
|
||||
|
||||
type combo struct {
|
||||
keys [][]key
|
||||
}
|
||||
|
||||
var combos = []combo{
|
||||
{[][]key{K_TILDE, K_2, K_SPACE, K_LCTRL, K_RCTRL}},
|
||||
{[][]key{K_A, K_LCTRL, K_RCTRL}},
|
||||
{[][]key{K_B, K_LCTRL, K_RCTRL}},
|
||||
{[][]key{K_C, K_LCTRL, K_RCTRL}},
|
||||
{[][]key{K_D, K_LCTRL, K_RCTRL}},
|
||||
{[][]key{K_E, K_LCTRL, K_RCTRL}},
|
||||
{[][]key{K_F, K_LCTRL, K_RCTRL}},
|
||||
{[][]key{K_G, K_LCTRL, K_RCTRL}},
|
||||
{[][]key{K_H, K_BACKSPACE, K_LCTRL, K_RCTRL}},
|
||||
{[][]key{K_I, K_TAB, K_LCTRL, K_RCTRL}},
|
||||
{[][]key{K_J, K_LCTRL, K_RCTRL}},
|
||||
{[][]key{K_K, K_LCTRL, K_RCTRL}},
|
||||
{[][]key{K_L, K_LCTRL, K_RCTRL}},
|
||||
{[][]key{K_M, K_ENTER, K_K_ENTER, K_LCTRL, K_RCTRL}},
|
||||
{[][]key{K_N, K_LCTRL, K_RCTRL}},
|
||||
{[][]key{K_O, K_LCTRL, K_RCTRL}},
|
||||
{[][]key{K_P, K_LCTRL, K_RCTRL}},
|
||||
{[][]key{K_Q, K_LCTRL, K_RCTRL}},
|
||||
{[][]key{K_R, K_LCTRL, K_RCTRL}},
|
||||
{[][]key{K_S, K_LCTRL, K_RCTRL}},
|
||||
{[][]key{K_T, K_LCTRL, K_RCTRL}},
|
||||
{[][]key{K_U, K_LCTRL, K_RCTRL}},
|
||||
{[][]key{K_V, K_LCTRL, K_RCTRL}},
|
||||
{[][]key{K_W, K_LCTRL, K_RCTRL}},
|
||||
{[][]key{K_X, K_LCTRL, K_RCTRL}},
|
||||
{[][]key{K_Y, K_LCTRL, K_RCTRL}},
|
||||
{[][]key{K_Z, K_LCTRL, K_RCTRL}},
|
||||
{[][]key{K_LSQB, K_ESC, K_3, K_LCTRL, K_RCTRL}},
|
||||
{[][]key{K_4, K_BACKSLASH, K_LCTRL, K_RCTRL}},
|
||||
{[][]key{K_RSQB, K_5, K_LCTRL, K_RCTRL}},
|
||||
{[][]key{K_6, K_LCTRL, K_RCTRL}},
|
||||
{[][]key{K_7, K_SLASH, K_MINUS_SHIFT, K_LCTRL, K_RCTRL}},
|
||||
{[][]key{K_SPACE}},
|
||||
{[][]key{K_1_SHIFT, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_DOUBLEQUOTE, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_3_SHIFT, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_4_SHIFT, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_5_SHIFT, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_7_SHIFT, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_QUOTE}},
|
||||
{[][]key{K_9_SHIFT, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_0_SHIFT, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_8_SHIFT, K_K_STAR, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_EQUALS_SHIFT, K_K_PLUS, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_COMMA}},
|
||||
{[][]key{K_MINUS, K_K_MINUS}},
|
||||
{[][]key{K_PERIOD, K_K_PERIOD}},
|
||||
{[][]key{K_SLASH, K_K_SLASH}},
|
||||
{[][]key{K_0, K_K_0}},
|
||||
{[][]key{K_1, K_K_1}},
|
||||
{[][]key{K_2, K_K_2}},
|
||||
{[][]key{K_3, K_K_3}},
|
||||
{[][]key{K_4, K_K_4}},
|
||||
{[][]key{K_5, K_K_5}},
|
||||
{[][]key{K_6, K_K_6}},
|
||||
{[][]key{K_7, K_K_7}},
|
||||
{[][]key{K_8, K_K_8}},
|
||||
{[][]key{K_9, K_K_9}},
|
||||
{[][]key{K_PARENTHESIS, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_SEMICOLON}},
|
||||
{[][]key{K_LANB, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_EQUALS}},
|
||||
{[][]key{K_RANB, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_QUESTION, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_2_SHIFT, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_A, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_B, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_C, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_D, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_E, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_F, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_G, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_H, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_I, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_J, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_K, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_L, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_M, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_N, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_O, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_P, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_Q, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_R, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_S, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_T, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_U, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_V, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_W, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_X, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_Y, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_Z, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_LSQB}},
|
||||
{[][]key{K_BACKSLASH}},
|
||||
{[][]key{K_RSQB}},
|
||||
{[][]key{K_6_SHIFT, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_MINUS_SHIFT, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_TILDE}},
|
||||
{[][]key{K_a}},
|
||||
{[][]key{K_b}},
|
||||
{[][]key{K_c}},
|
||||
{[][]key{K_d}},
|
||||
{[][]key{K_e}},
|
||||
{[][]key{K_f}},
|
||||
{[][]key{K_g}},
|
||||
{[][]key{K_h}},
|
||||
{[][]key{K_i}},
|
||||
{[][]key{K_j}},
|
||||
{[][]key{K_k}},
|
||||
{[][]key{K_l}},
|
||||
{[][]key{K_m}},
|
||||
{[][]key{K_n}},
|
||||
{[][]key{K_o}},
|
||||
{[][]key{K_p}},
|
||||
{[][]key{K_q}},
|
||||
{[][]key{K_r}},
|
||||
{[][]key{K_s}},
|
||||
{[][]key{K_t}},
|
||||
{[][]key{K_u}},
|
||||
{[][]key{K_v}},
|
||||
{[][]key{K_w}},
|
||||
{[][]key{K_x}},
|
||||
{[][]key{K_y}},
|
||||
{[][]key{K_z}},
|
||||
{[][]key{K_LCUB, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_BACKSLASH_SHIFT, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_RCUB, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_TILDE_SHIFT, K_LSHIFT, K_RSHIFT}},
|
||||
{[][]key{K_8, K_BACKSPACE, K_LCTRL, K_RCTRL}},
|
||||
}
|
||||
|
||||
var func_combos = []combo{
|
||||
{[][]key{K_F1}},
|
||||
{[][]key{K_F2}},
|
||||
{[][]key{K_F3}},
|
||||
{[][]key{K_F4}},
|
||||
{[][]key{K_F5}},
|
||||
{[][]key{K_F6}},
|
||||
{[][]key{K_F7}},
|
||||
{[][]key{K_F8}},
|
||||
{[][]key{K_F9}},
|
||||
{[][]key{K_F10}},
|
||||
{[][]key{K_F11}},
|
||||
{[][]key{K_F12}},
|
||||
{[][]key{K_INS}},
|
||||
{[][]key{K_DEL}},
|
||||
{[][]key{K_HOM}},
|
||||
{[][]key{K_END}},
|
||||
{[][]key{K_PGU}},
|
||||
{[][]key{K_PGD}},
|
||||
{[][]key{K_ARROW_UP}},
|
||||
{[][]key{K_ARROW_DOWN}},
|
||||
{[][]key{K_ARROW_LEFT}},
|
||||
{[][]key{K_ARROW_RIGHT}},
|
||||
}
|
||||
|
||||
func print_tb(x, y int, fg, bg termbox.Attribute, msg string) {
|
||||
for _, c := range msg {
|
||||
termbox.SetCell(x, y, c, fg, bg)
|
||||
x++
|
||||
}
|
||||
}
|
||||
|
||||
func printf_tb(x, y int, fg, bg termbox.Attribute, format string, args ...interface{}) {
|
||||
s := fmt.Sprintf(format, args...)
|
||||
print_tb(x, y, fg, bg, s)
|
||||
}
|
||||
|
||||
func draw_key(k []key, fg, bg termbox.Attribute) {
|
||||
for _, k := range k {
|
||||
termbox.SetCell(k.x+2, k.y+4, k.ch, fg, bg)
|
||||
}
|
||||
}
|
||||
|
||||
func draw_keyboard() {
|
||||
termbox.SetCell(0, 0, 0x250C, termbox.ColorWhite, termbox.ColorBlack)
|
||||
termbox.SetCell(79, 0, 0x2510, termbox.ColorWhite, termbox.ColorBlack)
|
||||
termbox.SetCell(0, 23, 0x2514, termbox.ColorWhite, termbox.ColorBlack)
|
||||
termbox.SetCell(79, 23, 0x2518, termbox.ColorWhite, termbox.ColorBlack)
|
||||
|
||||
for i := 1; i < 79; i++ {
|
||||
termbox.SetCell(i, 0, 0x2500, termbox.ColorWhite, termbox.ColorBlack)
|
||||
termbox.SetCell(i, 23, 0x2500, termbox.ColorWhite, termbox.ColorBlack)
|
||||
termbox.SetCell(i, 17, 0x2500, termbox.ColorWhite, termbox.ColorBlack)
|
||||
termbox.SetCell(i, 4, 0x2500, termbox.ColorWhite, termbox.ColorBlack)
|
||||
}
|
||||
for i := 1; i < 23; i++ {
|
||||
termbox.SetCell(0, i, 0x2502, termbox.ColorWhite, termbox.ColorBlack)
|
||||
termbox.SetCell(79, i, 0x2502, termbox.ColorWhite, termbox.ColorBlack)
|
||||
}
|
||||
termbox.SetCell(0, 17, 0x251C, termbox.ColorWhite, termbox.ColorBlack)
|
||||
termbox.SetCell(79, 17, 0x2524, termbox.ColorWhite, termbox.ColorBlack)
|
||||
termbox.SetCell(0, 4, 0x251C, termbox.ColorWhite, termbox.ColorBlack)
|
||||
termbox.SetCell(79, 4, 0x2524, termbox.ColorWhite, termbox.ColorBlack)
|
||||
for i := 5; i < 17; i++ {
|
||||
termbox.SetCell(1, i, 0x2588, termbox.ColorYellow, termbox.ColorYellow)
|
||||
termbox.SetCell(78, i, 0x2588, termbox.ColorYellow, termbox.ColorYellow)
|
||||
}
|
||||
|
||||
draw_key(K_ESC, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_F1, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_F2, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_F3, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_F4, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_F5, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_F6, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_F7, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_F8, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_F9, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_F10, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_F11, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_F12, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_PRN, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_SCR, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_BRK, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_LED1, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_LED2, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_LED3, termbox.ColorWhite, termbox.ColorBlue)
|
||||
|
||||
draw_key(K_TILDE, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_1, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_2, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_3, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_4, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_5, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_6, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_7, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_8, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_9, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_0, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_MINUS, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_EQUALS, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_BACKSLASH, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_BACKSPACE, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_INS, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_HOM, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_PGU, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_K_NUMLOCK, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_K_SLASH, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_K_STAR, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_K_MINUS, termbox.ColorWhite, termbox.ColorBlue)
|
||||
|
||||
draw_key(K_TAB, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_q, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_w, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_e, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_r, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_t, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_y, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_u, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_i, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_o, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_p, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_LSQB, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_RSQB, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_ENTER, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_DEL, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_END, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_PGD, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_K_7, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_K_8, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_K_9, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_K_PLUS, termbox.ColorWhite, termbox.ColorBlue)
|
||||
|
||||
draw_key(K_CAPS, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_a, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_s, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_d, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_f, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_g, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_h, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_j, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_k, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_l, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_SEMICOLON, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_QUOTE, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_K_4, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_K_5, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_K_6, termbox.ColorWhite, termbox.ColorBlue)
|
||||
|
||||
draw_key(K_LSHIFT, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_z, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_x, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_c, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_v, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_b, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_n, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_m, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_COMMA, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_PERIOD, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_SLASH, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_RSHIFT, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_ARROW_UP, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_K_1, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_K_2, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_K_3, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_K_ENTER, termbox.ColorWhite, termbox.ColorBlue)
|
||||
|
||||
draw_key(K_LCTRL, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_LWIN, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_LALT, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_SPACE, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_RCTRL, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_RPROP, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_RWIN, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_RALT, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_ARROW_LEFT, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_ARROW_DOWN, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_ARROW_RIGHT, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_K_0, termbox.ColorWhite, termbox.ColorBlue)
|
||||
draw_key(K_K_PERIOD, termbox.ColorWhite, termbox.ColorBlue)
|
||||
|
||||
printf_tb(33, 1, termbox.ColorMagenta|termbox.AttrBold, termbox.ColorBlack, "Keyboard demo!")
|
||||
printf_tb(21, 2, termbox.ColorMagenta, termbox.ColorBlack, "(press CTRL+X and then CTRL+Q to exit)")
|
||||
printf_tb(15, 3, termbox.ColorMagenta, termbox.ColorBlack, "(press CTRL+X and then CTRL+C to change input mode)")
|
||||
|
||||
inputmode := termbox.SetInputMode(termbox.InputCurrent)
|
||||
inputmode_str := ""
|
||||
switch {
|
||||
case inputmode&termbox.InputEsc != 0:
|
||||
inputmode_str = "termbox.InputEsc"
|
||||
case inputmode&termbox.InputAlt != 0:
|
||||
inputmode_str = "termbox.InputAlt"
|
||||
}
|
||||
|
||||
if inputmode&termbox.InputMouse != 0 {
|
||||
inputmode_str += " | termbox.InputMouse"
|
||||
}
|
||||
printf_tb(3, 18, termbox.ColorWhite, termbox.ColorBlack, "Input mode: %s", inputmode_str)
|
||||
}
|
||||
|
||||
var fcmap = []string{
|
||||
"CTRL+2, CTRL+~",
|
||||
"CTRL+A",
|
||||
"CTRL+B",
|
||||
"CTRL+C",
|
||||
"CTRL+D",
|
||||
"CTRL+E",
|
||||
"CTRL+F",
|
||||
"CTRL+G",
|
||||
"CTRL+H, BACKSPACE",
|
||||
"CTRL+I, TAB",
|
||||
"CTRL+J",
|
||||
"CTRL+K",
|
||||
"CTRL+L",
|
||||
"CTRL+M, ENTER",
|
||||
"CTRL+N",
|
||||
"CTRL+O",
|
||||
"CTRL+P",
|
||||
"CTRL+Q",
|
||||
"CTRL+R",
|
||||
"CTRL+S",
|
||||
"CTRL+T",
|
||||
"CTRL+U",
|
||||
"CTRL+V",
|
||||
"CTRL+W",
|
||||
"CTRL+X",
|
||||
"CTRL+Y",
|
||||
"CTRL+Z",
|
||||
"CTRL+3, ESC, CTRL+[",
|
||||
"CTRL+4, CTRL+\\",
|
||||
"CTRL+5, CTRL+]",
|
||||
"CTRL+6",
|
||||
"CTRL+7, CTRL+/, CTRL+_",
|
||||
"SPACE",
|
||||
}
|
||||
|
||||
var fkmap = []string{
|
||||
"F1",
|
||||
"F2",
|
||||
"F3",
|
||||
"F4",
|
||||
"F5",
|
||||
"F6",
|
||||
"F7",
|
||||
"F8",
|
||||
"F9",
|
||||
"F10",
|
||||
"F11",
|
||||
"F12",
|
||||
"INSERT",
|
||||
"DELETE",
|
||||
"HOME",
|
||||
"END",
|
||||
"PGUP",
|
||||
"PGDN",
|
||||
"ARROW UP",
|
||||
"ARROW DOWN",
|
||||
"ARROW LEFT",
|
||||
"ARROW RIGHT",
|
||||
}
|
||||
|
||||
func funckeymap(k termbox.Key) string {
|
||||
if k == termbox.KeyCtrl8 {
|
||||
return "CTRL+8, BACKSPACE 2" /* 0x7F */
|
||||
} else if k >= termbox.KeyArrowRight && k <= 0xFFFF {
|
||||
return fkmap[0xFFFF-k]
|
||||
} else if k <= termbox.KeySpace {
|
||||
return fcmap[k]
|
||||
}
|
||||
return "UNKNOWN"
|
||||
}
|
||||
|
||||
func pretty_print_press(ev *termbox.Event) {
|
||||
printf_tb(3, 19, termbox.ColorWhite, termbox.ColorBlack, "Key: ")
|
||||
printf_tb(8, 19, termbox.ColorYellow, termbox.ColorBlack, "decimal: %d", ev.Key)
|
||||
printf_tb(8, 20, termbox.ColorGreen, termbox.ColorBlack, "hex: 0x%X", ev.Key)
|
||||
printf_tb(8, 21, termbox.ColorCyan, termbox.ColorBlack, "octal: 0%o", ev.Key)
|
||||
printf_tb(8, 22, termbox.ColorRed, termbox.ColorBlack, "string: %s", funckeymap(ev.Key))
|
||||
|
||||
printf_tb(54, 19, termbox.ColorWhite, termbox.ColorBlack, "Char: ")
|
||||
printf_tb(60, 19, termbox.ColorYellow, termbox.ColorBlack, "decimal: %d", ev.Ch)
|
||||
printf_tb(60, 20, termbox.ColorGreen, termbox.ColorBlack, "hex: 0x%X", ev.Ch)
|
||||
printf_tb(60, 21, termbox.ColorCyan, termbox.ColorBlack, "octal: 0%o", ev.Ch)
|
||||
printf_tb(60, 22, termbox.ColorRed, termbox.ColorBlack, "string: %s", string(ev.Ch))
|
||||
|
||||
modifier := "none"
|
||||
if ev.Mod != 0 {
|
||||
modifier = "termbox.ModAlt"
|
||||
}
|
||||
printf_tb(54, 18, termbox.ColorWhite, termbox.ColorBlack, "Modifier: %s", modifier)
|
||||
}
|
||||
|
||||
func pretty_print_resize(ev *termbox.Event) {
|
||||
printf_tb(3, 19, termbox.ColorWhite, termbox.ColorBlack, "Resize event: %d x %d", ev.Width, ev.Height)
|
||||
}
|
||||
|
||||
var counter = 0
|
||||
|
||||
func pretty_print_mouse(ev *termbox.Event) {
|
||||
printf_tb(3, 19, termbox.ColorWhite, termbox.ColorBlack, "Mouse event: %d x %d", ev.MouseX, ev.MouseY)
|
||||
button := ""
|
||||
switch ev.Key {
|
||||
case termbox.MouseLeft:
|
||||
button = "MouseLeft: %d"
|
||||
case termbox.MouseMiddle:
|
||||
button = "MouseMiddle: %d"
|
||||
case termbox.MouseRight:
|
||||
button = "MouseRight: %d"
|
||||
case termbox.MouseWheelUp:
|
||||
button = "MouseWheelUp: %d"
|
||||
case termbox.MouseWheelDown:
|
||||
button = "MouseWheelDown: %d"
|
||||
case termbox.MouseRelease:
|
||||
button = "MouseRelease: %d"
|
||||
}
|
||||
if ev.Mod&termbox.ModMotion != 0 {
|
||||
button += "*"
|
||||
}
|
||||
counter++
|
||||
printf_tb(43, 19, termbox.ColorWhite, termbox.ColorBlack, "Key: ")
|
||||
printf_tb(48, 19, termbox.ColorYellow, termbox.ColorBlack, button, counter)
|
||||
}
|
||||
|
||||
func dispatch_press(ev *termbox.Event) {
|
||||
if ev.Mod&termbox.ModAlt != 0 {
|
||||
draw_key(K_LALT, termbox.ColorWhite, termbox.ColorRed)
|
||||
draw_key(K_RALT, termbox.ColorWhite, termbox.ColorRed)
|
||||
}
|
||||
|
||||
var k *combo
|
||||
if ev.Key >= termbox.KeyArrowRight {
|
||||
k = &func_combos[0xFFFF-ev.Key]
|
||||
} else if ev.Ch < 128 {
|
||||
if ev.Ch == 0 && ev.Key < 128 {
|
||||
k = &combos[ev.Key]
|
||||
} else {
|
||||
k = &combos[ev.Ch]
|
||||
}
|
||||
}
|
||||
if k == nil {
|
||||
return
|
||||
}
|
||||
|
||||
keys := k.keys
|
||||
for _, k := range keys {
|
||||
draw_key(k, termbox.ColorWhite, termbox.ColorRed)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
err := termbox.Init()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer termbox.Close()
|
||||
|
||||
termbox.SetInputMode(termbox.InputEsc | termbox.InputMouse)
|
||||
|
||||
termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)
|
||||
draw_keyboard()
|
||||
termbox.Flush()
|
||||
inputmode := 0
|
||||
ctrlxpressed := false
|
||||
loop:
|
||||
for {
|
||||
switch ev := termbox.PollEvent(); ev.Type {
|
||||
case termbox.EventKey:
|
||||
if ev.Key == termbox.KeyCtrlS && ctrlxpressed {
|
||||
termbox.Sync()
|
||||
}
|
||||
if ev.Key == termbox.KeyCtrlQ && ctrlxpressed {
|
||||
break loop
|
||||
}
|
||||
if ev.Key == termbox.KeyCtrlC && ctrlxpressed {
|
||||
chmap := []termbox.InputMode{
|
||||
termbox.InputEsc | termbox.InputMouse,
|
||||
termbox.InputAlt | termbox.InputMouse,
|
||||
termbox.InputEsc,
|
||||
termbox.InputAlt,
|
||||
}
|
||||
inputmode++
|
||||
if inputmode >= len(chmap) {
|
||||
inputmode = 0
|
||||
}
|
||||
termbox.SetInputMode(chmap[inputmode])
|
||||
}
|
||||
if ev.Key == termbox.KeyCtrlX {
|
||||
ctrlxpressed = true
|
||||
} else {
|
||||
ctrlxpressed = false
|
||||
}
|
||||
|
||||
termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)
|
||||
draw_keyboard()
|
||||
dispatch_press(&ev)
|
||||
pretty_print_press(&ev)
|
||||
termbox.Flush()
|
||||
case termbox.EventResize:
|
||||
termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)
|
||||
draw_keyboard()
|
||||
pretty_print_resize(&ev)
|
||||
termbox.Flush()
|
||||
case termbox.EventMouse:
|
||||
termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)
|
||||
draw_keyboard()
|
||||
pretty_print_mouse(&ev)
|
||||
termbox.Flush()
|
||||
case termbox.EventError:
|
||||
panic(ev.Err)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,228 +0,0 @@
|
||||
package main
|
||||
|
||||
import "github.com/mattn/go-runewidth"
|
||||
import "github.com/nsf/termbox-go"
|
||||
|
||||
const chars = "nnnnnnnnnbbbbbbbbbuuuuuuuuuBBBBBBBBB"
|
||||
|
||||
var output_mode = termbox.OutputNormal
|
||||
|
||||
func next_char(current int) int {
|
||||
current++
|
||||
if current >= len(chars) {
|
||||
return 0
|
||||
}
|
||||
return current
|
||||
}
|
||||
|
||||
func print_combinations_table(sx, sy int, attrs []termbox.Attribute) {
|
||||
var bg termbox.Attribute
|
||||
current_char := 0
|
||||
y := sy
|
||||
|
||||
all_attrs := []termbox.Attribute{
|
||||
0,
|
||||
termbox.AttrBold,
|
||||
termbox.AttrUnderline,
|
||||
termbox.AttrBold | termbox.AttrUnderline,
|
||||
}
|
||||
|
||||
draw_line := func() {
|
||||
x := sx
|
||||
for _, a := range all_attrs {
|
||||
for c := termbox.ColorDefault; c <= termbox.ColorWhite; c++ {
|
||||
fg := a | c
|
||||
termbox.SetCell(x, y, rune(chars[current_char]), fg, bg)
|
||||
current_char = next_char(current_char)
|
||||
x++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, a := range attrs {
|
||||
for c := termbox.ColorDefault; c <= termbox.ColorWhite; c++ {
|
||||
bg = a | c
|
||||
draw_line()
|
||||
y++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func print_wide(x, y int, s string) {
|
||||
red := false
|
||||
for _, r := range s {
|
||||
c := termbox.ColorDefault
|
||||
if red {
|
||||
c = termbox.ColorRed
|
||||
}
|
||||
termbox.SetCell(x, y, r, termbox.ColorDefault, c)
|
||||
w := runewidth.RuneWidth(r)
|
||||
if w == 0 || (w == 2 && runewidth.IsAmbiguousWidth(r)) {
|
||||
w = 1
|
||||
}
|
||||
x += w
|
||||
|
||||
red = !red
|
||||
}
|
||||
}
|
||||
|
||||
const hello_world = "こんにちは世界"
|
||||
|
||||
func draw_all() {
|
||||
termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)
|
||||
|
||||
switch output_mode {
|
||||
|
||||
case termbox.OutputNormal:
|
||||
print_combinations_table(1, 1, []termbox.Attribute{
|
||||
0,
|
||||
termbox.AttrBold,
|
||||
})
|
||||
print_combinations_table(2+len(chars), 1, []termbox.Attribute{
|
||||
termbox.AttrReverse,
|
||||
})
|
||||
print_wide(2+len(chars), 11, hello_world)
|
||||
|
||||
case termbox.OutputGrayscale:
|
||||
for y := 0; y < 26; y++ {
|
||||
for x := 0; x < 26; x++ {
|
||||
termbox.SetCell(x, y, 'n',
|
||||
termbox.Attribute(x+1),
|
||||
termbox.Attribute(y+1))
|
||||
termbox.SetCell(x+27, y, 'b',
|
||||
termbox.Attribute(x+1)|termbox.AttrBold,
|
||||
termbox.Attribute(26-y))
|
||||
termbox.SetCell(x+54, y, 'u',
|
||||
termbox.Attribute(x+1)|termbox.AttrUnderline,
|
||||
termbox.Attribute(y+1))
|
||||
}
|
||||
termbox.SetCell(82, y, 'd',
|
||||
termbox.Attribute(y+1),
|
||||
termbox.ColorDefault)
|
||||
termbox.SetCell(83, y, 'd',
|
||||
termbox.ColorDefault,
|
||||
termbox.Attribute(26-y))
|
||||
}
|
||||
|
||||
case termbox.Output216:
|
||||
for r := 0; r < 6; r++ {
|
||||
for g := 0; g < 6; g++ {
|
||||
for b := 0; b < 6; b++ {
|
||||
y := r
|
||||
x := g + 6*b
|
||||
c1 := termbox.Attribute(1 + r*36 + g*6 + b)
|
||||
bg := termbox.Attribute(1 + g*36 + b*6 + r)
|
||||
c2 := termbox.Attribute(1 + b*36 + r*6 + g)
|
||||
bc1 := c1 | termbox.AttrBold
|
||||
uc1 := c1 | termbox.AttrUnderline
|
||||
bc2 := c2 | termbox.AttrBold
|
||||
uc2 := c2 | termbox.AttrUnderline
|
||||
termbox.SetCell(x, y, 'n', c1, bg)
|
||||
termbox.SetCell(x, y+6, 'b', bc1, bg)
|
||||
termbox.SetCell(x, y+12, 'u', uc1, bg)
|
||||
termbox.SetCell(x, y+18, 'B', bc1|uc1, bg)
|
||||
termbox.SetCell(x+37, y, 'n', c2, bg)
|
||||
termbox.SetCell(x+37, y+6, 'b', bc2, bg)
|
||||
termbox.SetCell(x+37, y+12, 'u', uc2, bg)
|
||||
termbox.SetCell(x+37, y+18, 'B', bc2|uc2, bg)
|
||||
}
|
||||
c1 := termbox.Attribute(1 + g*6 + r*36)
|
||||
c2 := termbox.Attribute(6 + g*6 + r*36)
|
||||
termbox.SetCell(74+g, r, 'd', c1, termbox.ColorDefault)
|
||||
termbox.SetCell(74+g, r+6, 'd', c2, termbox.ColorDefault)
|
||||
termbox.SetCell(74+g, r+12, 'd', termbox.ColorDefault, c1)
|
||||
termbox.SetCell(74+g, r+18, 'd', termbox.ColorDefault, c2)
|
||||
}
|
||||
}
|
||||
|
||||
case termbox.Output256:
|
||||
for y := 0; y < 4; y++ {
|
||||
for x := 0; x < 8; x++ {
|
||||
for z := 0; z < 8; z++ {
|
||||
bg := termbox.Attribute(1 + y*64 + x*8 + z)
|
||||
c1 := termbox.Attribute(256 - y*64 - x*8 - z)
|
||||
c2 := termbox.Attribute(1 + y*64 + z*8 + x)
|
||||
c3 := termbox.Attribute(256 - y*64 - z*8 - x)
|
||||
c4 := termbox.Attribute(1 + y*64 + x*4 + z*4)
|
||||
bold := c2 | termbox.AttrBold
|
||||
under := c3 | termbox.AttrUnderline
|
||||
both := c1 | termbox.AttrBold | termbox.AttrUnderline
|
||||
termbox.SetCell(z+8*x, y, ' ', 0, bg)
|
||||
termbox.SetCell(z+8*x, y+5, 'n', c4, bg)
|
||||
termbox.SetCell(z+8*x, y+10, 'b', bold, bg)
|
||||
termbox.SetCell(z+8*x, y+15, 'u', under, bg)
|
||||
termbox.SetCell(z+8*x, y+20, 'B', both, bg)
|
||||
}
|
||||
}
|
||||
}
|
||||
for x := 0; x < 12; x++ {
|
||||
for y := 0; y < 2; y++ {
|
||||
c1 := termbox.Attribute(233 + y*12 + x)
|
||||
termbox.SetCell(66+x, y, 'd', c1, termbox.ColorDefault)
|
||||
termbox.SetCell(66+x, 2+y, 'd', termbox.ColorDefault, c1)
|
||||
}
|
||||
}
|
||||
for x := 0; x < 6; x++ {
|
||||
for y := 0; y < 6; y++ {
|
||||
c1 := termbox.Attribute(17 + x*6 + y*36)
|
||||
c2 := termbox.Attribute(17 + 5 + x*6 + y*36)
|
||||
termbox.SetCell(66+x, 6+y, 'd', c1, termbox.ColorDefault)
|
||||
termbox.SetCell(66+x, 12+y, 'd', c2, termbox.ColorDefault)
|
||||
termbox.SetCell(72+x, 6+y, 'd', termbox.ColorDefault, c1)
|
||||
termbox.SetCell(72+x, 12+y, 'd', termbox.ColorDefault, c2)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
termbox.Flush()
|
||||
}
|
||||
|
||||
var available_modes = []termbox.OutputMode{
|
||||
termbox.OutputNormal,
|
||||
termbox.OutputGrayscale,
|
||||
termbox.Output216,
|
||||
termbox.Output256,
|
||||
}
|
||||
|
||||
var output_mode_index = 0
|
||||
|
||||
func switch_output_mode(direction int) {
|
||||
output_mode_index += direction
|
||||
if output_mode_index < 0 {
|
||||
output_mode_index = len(available_modes) - 1
|
||||
} else if output_mode_index >= len(available_modes) {
|
||||
output_mode_index = 0
|
||||
}
|
||||
output_mode = termbox.SetOutputMode(available_modes[output_mode_index])
|
||||
termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)
|
||||
termbox.Sync()
|
||||
}
|
||||
|
||||
func main() {
|
||||
err := termbox.Init()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer termbox.Close()
|
||||
|
||||
draw_all()
|
||||
loop:
|
||||
for {
|
||||
switch ev := termbox.PollEvent(); ev.Type {
|
||||
case termbox.EventKey:
|
||||
switch ev.Key {
|
||||
case termbox.KeyEsc:
|
||||
break loop
|
||||
case termbox.KeyArrowUp, termbox.KeyArrowRight:
|
||||
switch_output_mode(1)
|
||||
draw_all()
|
||||
case termbox.KeyArrowDown, termbox.KeyArrowLeft:
|
||||
switch_output_mode(-1)
|
||||
draw_all()
|
||||
}
|
||||
case termbox.EventResize:
|
||||
draw_all()
|
||||
}
|
||||
}
|
||||
}
|
@ -1,105 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/nsf/termbox-go"
|
||||
)
|
||||
|
||||
var curCol = 0
|
||||
var curRune = 0
|
||||
var backbuf []termbox.Cell
|
||||
var bbw, bbh int
|
||||
|
||||
var runes = []rune{' ', '░', '▒', '▓', '█'}
|
||||
var colors = []termbox.Attribute{
|
||||
termbox.ColorBlack,
|
||||
termbox.ColorRed,
|
||||
termbox.ColorGreen,
|
||||
termbox.ColorYellow,
|
||||
termbox.ColorBlue,
|
||||
termbox.ColorMagenta,
|
||||
termbox.ColorCyan,
|
||||
termbox.ColorWhite,
|
||||
}
|
||||
|
||||
type attrFunc func(int) (rune, termbox.Attribute, termbox.Attribute)
|
||||
|
||||
func updateAndDrawButtons(current *int, x, y int, mx, my int, n int, attrf attrFunc) {
|
||||
lx, ly := x, y
|
||||
for i := 0; i < n; i++ {
|
||||
if lx <= mx && mx <= lx+3 && ly <= my && my <= ly+1 {
|
||||
*current = i
|
||||
}
|
||||
r, fg, bg := attrf(i)
|
||||
termbox.SetCell(lx+0, ly+0, r, fg, bg)
|
||||
termbox.SetCell(lx+1, ly+0, r, fg, bg)
|
||||
termbox.SetCell(lx+2, ly+0, r, fg, bg)
|
||||
termbox.SetCell(lx+3, ly+0, r, fg, bg)
|
||||
termbox.SetCell(lx+0, ly+1, r, fg, bg)
|
||||
termbox.SetCell(lx+1, ly+1, r, fg, bg)
|
||||
termbox.SetCell(lx+2, ly+1, r, fg, bg)
|
||||
termbox.SetCell(lx+3, ly+1, r, fg, bg)
|
||||
lx += 4
|
||||
}
|
||||
lx, ly = x, y
|
||||
for i := 0; i < n; i++ {
|
||||
if *current == i {
|
||||
fg := termbox.ColorRed | termbox.AttrBold
|
||||
bg := termbox.ColorDefault
|
||||
termbox.SetCell(lx+0, ly+2, '^', fg, bg)
|
||||
termbox.SetCell(lx+1, ly+2, '^', fg, bg)
|
||||
termbox.SetCell(lx+2, ly+2, '^', fg, bg)
|
||||
termbox.SetCell(lx+3, ly+2, '^', fg, bg)
|
||||
}
|
||||
lx += 4
|
||||
}
|
||||
}
|
||||
|
||||
func update_and_redraw_all(mx, my int) {
|
||||
termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)
|
||||
if mx != -1 && my != -1 {
|
||||
backbuf[bbw*my+mx] = termbox.Cell{Ch: runes[curRune], Fg: colors[curCol]}
|
||||
}
|
||||
copy(termbox.CellBuffer(), backbuf)
|
||||
_, h := termbox.Size()
|
||||
updateAndDrawButtons(&curRune, 0, 0, mx, my, len(runes), func(i int) (rune, termbox.Attribute, termbox.Attribute) {
|
||||
return runes[i], termbox.ColorDefault, termbox.ColorDefault
|
||||
})
|
||||
updateAndDrawButtons(&curCol, 0, h-3, mx, my, len(colors), func(i int) (rune, termbox.Attribute, termbox.Attribute) {
|
||||
return ' ', termbox.ColorDefault, colors[i]
|
||||
})
|
||||
termbox.Flush()
|
||||
}
|
||||
|
||||
func reallocBackBuffer(w, h int) {
|
||||
bbw, bbh = w, h
|
||||
backbuf = make([]termbox.Cell, w*h)
|
||||
}
|
||||
|
||||
func main() {
|
||||
err := termbox.Init()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer termbox.Close()
|
||||
termbox.SetInputMode(termbox.InputEsc | termbox.InputMouse)
|
||||
reallocBackBuffer(termbox.Size())
|
||||
update_and_redraw_all(-1, -1)
|
||||
|
||||
mainloop:
|
||||
for {
|
||||
mx, my := -1, -1
|
||||
switch ev := termbox.PollEvent(); ev.Type {
|
||||
case termbox.EventKey:
|
||||
if ev.Key == termbox.KeyEsc {
|
||||
break mainloop
|
||||
}
|
||||
case termbox.EventMouse:
|
||||
if ev.Key == termbox.MouseLeft {
|
||||
mx, my = ev.MouseX, ev.MouseY
|
||||
}
|
||||
case termbox.EventResize:
|
||||
reallocBackBuffer(ev.Width, ev.Height)
|
||||
}
|
||||
update_and_redraw_all(mx, my)
|
||||
}
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
package main
|
||||
|
||||
import "github.com/nsf/termbox-go"
|
||||
import "math/rand"
|
||||
import "time"
|
||||
|
||||
func draw() {
|
||||
w, h := termbox.Size()
|
||||
termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)
|
||||
for y := 0; y < h; y++ {
|
||||
for x := 0; x < w; x++ {
|
||||
termbox.SetCell(x, y, ' ', termbox.ColorDefault,
|
||||
termbox.Attribute(rand.Int()%8)+1)
|
||||
}
|
||||
}
|
||||
termbox.Flush()
|
||||
}
|
||||
|
||||
func main() {
|
||||
err := termbox.Init()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer termbox.Close()
|
||||
|
||||
event_queue := make(chan termbox.Event)
|
||||
go func() {
|
||||
for {
|
||||
event_queue <- termbox.PollEvent()
|
||||
}
|
||||
}()
|
||||
|
||||
draw()
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case ev := <-event_queue:
|
||||
if ev.Type == termbox.EventKey && ev.Key == termbox.KeyEsc {
|
||||
break loop
|
||||
}
|
||||
default:
|
||||
draw()
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,109 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/nsf/termbox-go"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func tbprint(x, y int, fg, bg termbox.Attribute, msg string) {
|
||||
for _, c := range msg {
|
||||
termbox.SetCell(x, y, c, fg, bg)
|
||||
x++
|
||||
}
|
||||
}
|
||||
|
||||
var current string
|
||||
var curev termbox.Event
|
||||
|
||||
func mouse_button_str(k termbox.Key) string {
|
||||
switch k {
|
||||
case termbox.MouseLeft:
|
||||
return "MouseLeft"
|
||||
case termbox.MouseMiddle:
|
||||
return "MouseMiddle"
|
||||
case termbox.MouseRight:
|
||||
return "MouseRight"
|
||||
case termbox.MouseRelease:
|
||||
return "MouseRelease"
|
||||
case termbox.MouseWheelUp:
|
||||
return "MouseWheelUp"
|
||||
case termbox.MouseWheelDown:
|
||||
return "MouseWheelDown"
|
||||
}
|
||||
return "Key"
|
||||
}
|
||||
|
||||
func mod_str(m termbox.Modifier) string {
|
||||
var out []string
|
||||
if m&termbox.ModAlt != 0 {
|
||||
out = append(out, "ModAlt")
|
||||
}
|
||||
if m&termbox.ModMotion != 0 {
|
||||
out = append(out, "ModMotion")
|
||||
}
|
||||
return strings.Join(out, " | ")
|
||||
}
|
||||
|
||||
func redraw_all() {
|
||||
const coldef = termbox.ColorDefault
|
||||
termbox.Clear(coldef, coldef)
|
||||
tbprint(0, 0, termbox.ColorMagenta, coldef, "Press 'q' to quit")
|
||||
tbprint(0, 1, coldef, coldef, current)
|
||||
switch curev.Type {
|
||||
case termbox.EventKey:
|
||||
tbprint(0, 2, coldef, coldef,
|
||||
fmt.Sprintf("EventKey: k: %d, c: %c, mod: %s", curev.Key, curev.Ch, mod_str(curev.Mod)))
|
||||
case termbox.EventMouse:
|
||||
tbprint(0, 2, coldef, coldef,
|
||||
fmt.Sprintf("EventMouse: x: %d, y: %d, b: %s, mod: %s",
|
||||
curev.MouseX, curev.MouseY, mouse_button_str(curev.Key), mod_str(curev.Mod)))
|
||||
case termbox.EventNone:
|
||||
tbprint(0, 2, coldef, coldef, "EventNone")
|
||||
}
|
||||
tbprint(0, 3, coldef, coldef, fmt.Sprintf("%d", curev.N))
|
||||
termbox.Flush()
|
||||
}
|
||||
|
||||
func main() {
|
||||
err := termbox.Init()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer termbox.Close()
|
||||
termbox.SetInputMode(termbox.InputAlt | termbox.InputMouse)
|
||||
redraw_all()
|
||||
|
||||
data := make([]byte, 0, 64)
|
||||
mainloop:
|
||||
for {
|
||||
if cap(data)-len(data) < 32 {
|
||||
newdata := make([]byte, len(data), len(data)+32)
|
||||
copy(newdata, data)
|
||||
data = newdata
|
||||
}
|
||||
beg := len(data)
|
||||
d := data[beg : beg+32]
|
||||
switch ev := termbox.PollRawEvent(d); ev.Type {
|
||||
case termbox.EventRaw:
|
||||
data = data[:beg+ev.N]
|
||||
current = fmt.Sprintf("%q", data)
|
||||
if current == `"q"` {
|
||||
break mainloop
|
||||
}
|
||||
|
||||
for {
|
||||
ev := termbox.ParseEvent(data)
|
||||
if ev.N == 0 {
|
||||
break
|
||||
}
|
||||
curev = ev
|
||||
copy(data, data[curev.N:])
|
||||
data = data[:len(data)-curev.N]
|
||||
}
|
||||
case termbox.EventError:
|
||||
panic(ev.Err)
|
||||
}
|
||||
redraw_all()
|
||||
}
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
package termui
|
||||
|
||||
import (
|
||||
"image"
|
||||
"sync"
|
||||
|
||||
tm "github.com/miguelmota/cointop/pkg/termbox"
|
||||
)
|
||||
|
||||
// Bufferer should be implemented by all renderable components.
|
||||
type Bufferer interface {
|
||||
Buffer() Buffer
|
||||
}
|
||||
|
||||
// Close finalizes termui library,
|
||||
// should be called after successful initialization when termui's functionality isn't required anymore.
|
||||
func Close() {
|
||||
tm.Close()
|
||||
}
|
||||
|
||||
var renderLock sync.Mutex
|
||||
|
||||
func termSync() {
|
||||
renderLock.Lock()
|
||||
tm.Sync()
|
||||
termWidth, termHeight = tm.Size()
|
||||
renderLock.Unlock()
|
||||
}
|
||||
|
||||
// TermWidth returns the current terminal's width.
|
||||
func TermWidth() int {
|
||||
termSync()
|
||||
return termWidth
|
||||
}
|
||||
|
||||
// TermHeight returns the current terminal's height.
|
||||
func TermHeight() int {
|
||||
termSync()
|
||||
return termHeight
|
||||
}
|
||||
|
||||
func Clear() {
|
||||
tm.Clear(tm.ColorDefault, toTmAttr(ThemeAttr("bg")))
|
||||
}
|
||||
|
||||
func clearArea(r image.Rectangle, bg Attribute) {
|
||||
for i := r.Min.X; i < r.Max.X; i++ {
|
||||
for j := r.Min.Y; j < r.Max.Y; j++ {
|
||||
tm.SetCell(i, j, ' ', tm.ColorDefault, toTmAttr(bg))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ClearArea(r image.Rectangle, bg Attribute) {
|
||||
clearArea(r, bg)
|
||||
tm.Flush()
|
||||
}
|
||||
|
||||
var renderJobs chan []Bufferer
|
||||
|
||||
func Render(bs ...Bufferer) {
|
||||
//go func() { renderJobs <- bs }()
|
||||
renderJobs <- bs
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
.DS_Store
|
||||
/vendor
|
@ -0,0 +1,6 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- tip
|
||||
|
||||
script: go test -v ./
|
@ -0,0 +1,81 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
digest = "1:a2c1d0e43bd3baaa071d1b9ed72c27d78169b2b269f71c105ac4ba34b1be4a39"
|
||||
name = "github.com/davecgh/go-spew"
|
||||
packages = ["spew"]
|
||||
pruneopts = "UT"
|
||||
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:2eeeebccad4f052e6037527e86b8114c6bfd184a97f84d4449a5ea6ad202c216"
|
||||
name = "github.com/maruel/panicparse"
|
||||
packages = ["stack"]
|
||||
pruneopts = "UT"
|
||||
revision = "4417700b5a8d33fbb4416bb04359d20329f60008"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:e2d1d410fb367567c2b53ed9e2d719d3c1f0891397bb2fa49afd747cfbf1e8e4"
|
||||
name = "github.com/mattn/go-runewidth"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "9e777a8366cce605130a531d2cd6363d07ad7317"
|
||||
version = "v0.0.2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:e68cd472b96cdf7c9f6971ac41bcc1d4d3b23d67c2a31d2399446e295bc88ae9"
|
||||
name = "github.com/mitchellh/go-wordwrap"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "ad45545899c7b13c020ea92b2072220eefad42b8"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:c9b6e36dbd23f8403a04493376916ca5dad8c01b2da5ae0a05e6a468eb0b6f24"
|
||||
name = "github.com/nsf/termbox-go"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "5c94acc5e6eb520f1bcd183974e01171cc4c23b3"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe"
|
||||
name = "github.com/pmezard/go-difflib"
|
||||
packages = ["difflib"]
|
||||
pruneopts = "UT"
|
||||
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:18752d0b95816a1b777505a97f71c7467a8445b8ffb55631a7bf779f6ba4fa83"
|
||||
name = "github.com/stretchr/testify"
|
||||
packages = ["assert"]
|
||||
pruneopts = "UT"
|
||||
revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686"
|
||||
version = "v1.2.2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:7427036b6d926b3d1fa0773771820735b35554a98296f1074df440ee30a97296"
|
||||
name = "golang.org/x/net"
|
||||
packages = ["websocket"]
|
||||
pruneopts = "UT"
|
||||
revision = "f4c29de78a2a91c00474a2e689954305c350adf9"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
input-imports = [
|
||||
"github.com/davecgh/go-spew/spew",
|
||||
"github.com/maruel/panicparse/stack",
|
||||
"github.com/mattn/go-runewidth",
|
||||
"github.com/mitchellh/go-wordwrap",
|
||||
"github.com/nsf/termbox-go",
|
||||
"github.com/stretchr/testify/assert",
|
||||
"golang.org/x/net/websocket",
|
||||
]
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
@ -0,0 +1,15 @@
|
||||
[[constraint]]
|
||||
name = "github.com/maruel/panicparse"
|
||||
branch = "master"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/davecgh/go-spew"
|
||||
version = "1.1.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/mitchellh/go-wordwrap"
|
||||
branch = "master"
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Zack Guo
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
@ -0,0 +1,151 @@
|
||||
# termui [![Build Status](https://travis-ci.org/gizak/termui.svg?branch=master)](https://travis-ci.org/gizak/termui) [![Doc Status](https://godoc.org/github.com/gizak/termui?status.png)](https://godoc.org/github.com/gizak/termui)
|
||||
|
||||
<img src="./_example/dashboard.gif" alt="demo cast under osx 10.10; Terminal.app; Menlo Regular 12pt.)" width="80%">
|
||||
|
||||
`termui` is a cross-platform, easy-to-compile, and fully-customizable terminal dashboard. It is inspired by [blessed-contrib](https://github.com/yaronn/blessed-contrib), but purely in Go.
|
||||
|
||||
Now version v2 has arrived! It brings new event system, new theme system, new `Buffer` interface and specific colour text rendering. (some docs are missing, but it will be completed soon!)
|
||||
|
||||
## Installation
|
||||
|
||||
`master` mirrors v2 branch, to install:
|
||||
|
||||
go get -u github.com/gizak/termui
|
||||
|
||||
It is recommanded to use locked deps by using [dep](https://golang.github.io/dep/): move to `termui` src directory then run `dep ensure`.
|
||||
|
||||
For the compatible reason, you can choose to install the legacy version of `termui`:
|
||||
|
||||
go get gopkg.in/gizak/termui.v1
|
||||
|
||||
## Usage
|
||||
|
||||
### Layout
|
||||
|
||||
To use `termui`, the very first thing you may want to know is how to manage layout. `termui` offers two ways of doing this, known as absolute layout and grid layout.
|
||||
|
||||
__Absolute layout__
|
||||
|
||||
Each widget has an underlying block structure which basically is a box model. It has border, label and padding properties. A border of a widget can be chosen to hide or display (with its border label), you can pick a different front/back colour for the border as well. To display such a widget at a specific location in terminal window, you need to assign `.X`, `.Y`, `.Height`, `.Width` values for each widget before sending it to `.Render`. Let's demonstrate these by a code snippet:
|
||||
|
||||
`````go
|
||||
import ui "github.com/gizak/termui" // <- ui shortcut, optional
|
||||
|
||||
func main() {
|
||||
err := ui.Init()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer ui.Close()
|
||||
|
||||
p := ui.NewPar(":PRESS q TO QUIT DEMO")
|
||||
p.Height = 3
|
||||
p.Width = 50
|
||||
p.TextFgColor = ui.ColorWhite
|
||||
p.BorderLabel = "Text Box"
|
||||
p.BorderFg = ui.ColorCyan
|
||||
|
||||
g := ui.NewGauge()
|
||||
g.Percent = 50
|
||||
g.Width = 50
|
||||
g.Height = 3
|
||||
g.Y = 11
|
||||
g.BorderLabel = "Gauge"
|
||||
g.BarColor = ui.ColorRed
|
||||
g.BorderFg = ui.ColorWhite
|
||||
g.BorderLabelFg = ui.ColorCyan
|
||||
|
||||
ui.Render(p, g) // feel free to call Render, it's async and non-block
|
||||
|
||||
// event handler...
|
||||
}
|
||||
`````
|
||||
|
||||
Note that components can be overlapped (I'd rather call this a feature...), `Render(rs ...Renderer)` renders its args from left to right (i.e. each component's weight is arising from left to right).
|
||||
|
||||
__Grid layout:__
|
||||
|
||||
<img src="./_example/grid.gif" alt="grid" width="60%">
|
||||
|
||||
Grid layout uses [12 columns grid system](http://www.w3schools.com/bootstrap/bootstrap_grid_system.asp) with expressive syntax. To use `Grid`, all we need to do is build a widget tree consisting of `Row`s and `Col`s (Actually a `Col` is also a `Row` but with a widget endpoint attached).
|
||||
|
||||
```go
|
||||
import ui "github.com/gizak/termui"
|
||||
// init and create widgets...
|
||||
|
||||
// build
|
||||
ui.Body.AddRows(
|
||||
ui.NewRow(
|
||||
ui.NewCol(6, 0, widget0),
|
||||
ui.NewCol(6, 0, widget1)),
|
||||
ui.NewRow(
|
||||
ui.NewCol(3, 0, widget2),
|
||||
ui.NewCol(3, 0, widget30, widget31, widget32),
|
||||
ui.NewCol(6, 0, widget4)))
|
||||
|
||||
// calculate layout
|
||||
ui.Body.Align()
|
||||
|
||||
ui.Render(ui.Body)
|
||||
```
|
||||
|
||||
### Events
|
||||
|
||||
`termui` ships with a http-like event mux handling system. All events are channeled up from different sources (typing, click, windows resize, custom event) and then encoded as universal `Event` object. `Event.Path` indicates the event type and `Event.Data` stores the event data struct. Add a handler to a certain event is easy as below:
|
||||
|
||||
```go
|
||||
// handle key q pressing
|
||||
ui.Handle("/sys/kbd/q", func(ui.Event) {
|
||||
// press q to quit
|
||||
ui.StopLoop()
|
||||
})
|
||||
|
||||
ui.Handle("/sys/kbd/C-x", func(ui.Event) {
|
||||
// handle Ctrl + x combination
|
||||
})
|
||||
|
||||
ui.Handle("/sys/kbd", func(ui.Event) {
|
||||
// handle all other key pressing
|
||||
})
|
||||
|
||||
// handle a 1s timer
|
||||
ui.Handle("/timer/1s", func(e ui.Event) {
|
||||
t := e.Data.(ui.EvtTimer)
|
||||
// t is a EvtTimer
|
||||
if t.Count%2 ==0 {
|
||||
// do something
|
||||
}
|
||||
})
|
||||
|
||||
ui.Loop() // block until StopLoop is called
|
||||
```
|
||||
|
||||
### Widgets
|
||||
|
||||
Click image to see the corresponding demo codes.
|
||||
|
||||
[<img src="./_example/par.png" alt="par" type="image/png" width="45%">](https://github.com/gizak/termui/blob/master/_example/par.go)
|
||||
[<img src="./_example/list.png" alt="list" type="image/png" width="45%">](https://github.com/gizak/termui/blob/master/_example/list.go)
|
||||
[<img src="./_example/gauge.png" alt="gauge" type="image/png" width="45%">](https://github.com/gizak/termui/blob/master/_example/gauge.go)
|
||||
[<img src="./_example/linechart.png" alt="linechart" type="image/png" width="45%">](https://github.com/gizak/termui/blob/master/_example/linechart.go)
|
||||
[<img src="./_example/barchart.png" alt="barchart" type="image/png" width="45%">](https://github.com/gizak/termui/blob/master/_example/barchart.go)
|
||||
[<img src="./_example/mbarchart.png" alt="barchart" type="image/png" width="45%">](https://github.com/gizak/termui/blob/master/_example/mbarchart.go)
|
||||
[<img src="./_example/sparklines.png" alt="sparklines" type="image/png" width="45%">](https://github.com/gizak/termui/blob/master/_example/sparklines.go)
|
||||
[<img src="./_example/table.png" alt="table" type="image/png" width="45%">](https://github.com/gizak/termui/blob/master/_example/table.go)
|
||||
|
||||
## GoDoc
|
||||
|
||||
[godoc](https://godoc.org/github.com/gizak/termui)
|
||||
|
||||
## TODO
|
||||
|
||||
- [x] Grid layout
|
||||
- [x] Event system
|
||||
- [x] Canvas widget
|
||||
- [x] Refine APIs
|
||||
- [ ] Focusable widgets
|
||||
|
||||
## Changelog
|
||||
|
||||
## License
|
||||
This library is under the [MIT License](http://opensource.org/licenses/MIT)
|
@ -0,0 +1,149 @@
|
||||
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
package termui
|
||||
|
||||
import "fmt"
|
||||
|
||||
// BarChart creates multiple bars in a widget:
|
||||
/*
|
||||
bc := termui.NewBarChart()
|
||||
data := []int{3, 2, 5, 3, 9, 5}
|
||||
bclabels := []string{"S0", "S1", "S2", "S3", "S4", "S5"}
|
||||
bc.BorderLabel = "Bar Chart"
|
||||
bc.Data = data
|
||||
bc.Width = 26
|
||||
bc.Height = 10
|
||||
bc.DataLabels = bclabels
|
||||
bc.TextColor = termui.ColorGreen
|
||||
bc.BarColor = termui.ColorRed
|
||||
bc.NumColor = termui.ColorYellow
|
||||
*/
|
||||
type BarChart struct {
|
||||
Block
|
||||
BarColor Attribute
|
||||
TextColor Attribute
|
||||
NumColor Attribute
|
||||
Data []int
|
||||
DataLabels []string
|
||||
BarWidth int
|
||||
BarGap int
|
||||
CellChar rune
|
||||
labels [][]rune
|
||||
dataNum [][]rune
|
||||
numBar int
|
||||
scale float64
|
||||
max int
|
||||
}
|
||||
|
||||
// NewBarChart returns a new *BarChart with current theme.
|
||||
func NewBarChart() *BarChart {
|
||||
bc := &BarChart{Block: *NewBlock()}
|
||||
bc.BarColor = ThemeAttr("barchart.bar.bg")
|
||||
bc.NumColor = ThemeAttr("barchart.num.fg")
|
||||
bc.TextColor = ThemeAttr("barchart.text.fg")
|
||||
bc.BarGap = 1
|
||||
bc.BarWidth = 3
|
||||
bc.CellChar = ' '
|
||||
return bc
|
||||
}
|
||||
|
||||
func (bc *BarChart) layout() {
|
||||
bc.numBar = bc.innerArea.Dx() / (bc.BarGap + bc.BarWidth)
|
||||
bc.labels = make([][]rune, bc.numBar)
|
||||
bc.dataNum = make([][]rune, len(bc.Data))
|
||||
|
||||
for i := 0; i < bc.numBar && i < len(bc.DataLabels) && i < len(bc.Data); i++ {
|
||||
bc.labels[i] = trimStr2Runes(bc.DataLabels[i], bc.BarWidth)
|
||||
n := bc.Data[i]
|
||||
s := fmt.Sprint(n)
|
||||
bc.dataNum[i] = trimStr2Runes(s, bc.BarWidth)
|
||||
}
|
||||
|
||||
//bc.max = bc.Data[0] // what if Data is nil? Sometimes when bar graph is nill it produces panic with panic: runtime error: index out of range
|
||||
// Assign a negative value to get maxvalue auto-populates
|
||||
if bc.max == 0 {
|
||||
bc.max = -1
|
||||
}
|
||||
for i := 0; i < len(bc.Data); i++ {
|
||||
if bc.max < bc.Data[i] {
|
||||
bc.max = bc.Data[i]
|
||||
}
|
||||
}
|
||||
bc.scale = float64(bc.max) / float64(bc.innerArea.Dy()-1)
|
||||
}
|
||||
|
||||
func (bc *BarChart) SetMax(max int) {
|
||||
|
||||
if max > 0 {
|
||||
bc.max = max
|
||||
}
|
||||
}
|
||||
|
||||
// Buffer implements Bufferer interface.
|
||||
func (bc *BarChart) Buffer() Buffer {
|
||||
buf := bc.Block.Buffer()
|
||||
bc.layout()
|
||||
|
||||
for i := 0; i < bc.numBar && i < len(bc.Data) && i < len(bc.DataLabels); i++ {
|
||||
h := int(float64(bc.Data[i]) / bc.scale)
|
||||
oftX := i * (bc.BarWidth + bc.BarGap)
|
||||
|
||||
barBg := bc.Bg
|
||||
barFg := bc.BarColor
|
||||
|
||||
if bc.CellChar == ' ' {
|
||||
barBg = bc.BarColor
|
||||
barFg = ColorDefault
|
||||
if bc.BarColor == ColorDefault { // the same as above
|
||||
barBg |= AttrReverse
|
||||
}
|
||||
}
|
||||
|
||||
// plot bar
|
||||
for j := 0; j < bc.BarWidth; j++ {
|
||||
for k := 0; k < h; k++ {
|
||||
c := Cell{
|
||||
Ch: bc.CellChar,
|
||||
Bg: barBg,
|
||||
Fg: barFg,
|
||||
}
|
||||
|
||||
x := bc.innerArea.Min.X + i*(bc.BarWidth+bc.BarGap) + j
|
||||
y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 2 - k
|
||||
buf.Set(x, y, c)
|
||||
}
|
||||
}
|
||||
// plot text
|
||||
for j, k := 0, 0; j < len(bc.labels[i]); j++ {
|
||||
w := charWidth(bc.labels[i][j])
|
||||
c := Cell{
|
||||
Ch: bc.labels[i][j],
|
||||
Bg: bc.Bg,
|
||||
Fg: bc.TextColor,
|
||||
}
|
||||
y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 1
|
||||
x := bc.innerArea.Min.X + oftX + k
|
||||
buf.Set(x, y, c)
|
||||
k += w
|
||||
}
|
||||
// plot num
|
||||
for j := 0; j < len(bc.dataNum[i]); j++ {
|
||||
c := Cell{
|
||||
Ch: bc.dataNum[i][j],
|
||||
Fg: bc.NumColor,
|
||||
Bg: barBg,
|
||||
}
|
||||
|
||||
if h == 0 {
|
||||
c.Bg = bc.Bg
|
||||
}
|
||||
x := bc.innerArea.Min.X + oftX + (bc.BarWidth-len(bc.dataNum[i]))/2 + j
|
||||
y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 2
|
||||
buf.Set(x, y, c)
|
||||
}
|
||||
}
|
||||
|
||||
return buf
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package termui is a library designed for creating command line UI. For more info, goto http://github.com/gizak/termui
|
||||
|
||||
A simplest example:
|
||||
package main
|
||||
|
||||
import ui "github.com/gizak/termui"
|
||||
|
||||
func main() {
|
||||
if err:=ui.Init(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer ui.Close()
|
||||
|
||||
g := ui.NewGauge()
|
||||
g.Percent = 50
|
||||
g.Width = 50
|
||||
g.BorderLabel = "Gauge"
|
||||
|
||||
ui.Render(g)
|
||||
|
||||
ui.Loop()
|
||||
}
|
||||
*/
|
||||
package termui
|
@ -0,0 +1,109 @@
|
||||
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
package termui
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Gauge is a progress bar like widget.
|
||||
// A simple example:
|
||||
/*
|
||||
g := termui.NewGauge()
|
||||
g.Percent = 40
|
||||
g.Width = 50
|
||||
g.Height = 3
|
||||
g.BorderLabel = "Slim Gauge"
|
||||
g.BarColor = termui.ColorRed
|
||||
g.PercentColor = termui.ColorBlue
|
||||
*/
|
||||
|
||||
const ColorUndef Attribute = Attribute(^uint16(0))
|
||||
|
||||
type Gauge struct {
|
||||
Block
|
||||
Percent int
|
||||
BarColor Attribute
|
||||
PercentColor Attribute
|
||||
PercentColorHighlighted Attribute
|
||||
Label string
|
||||
LabelAlign Align
|
||||
}
|
||||
|
||||
// NewGauge return a new gauge with current theme.
|
||||
func NewGauge() *Gauge {
|
||||
g := &Gauge{
|
||||
Block: *NewBlock(),
|
||||
PercentColor: ThemeAttr("gauge.percent.fg"),
|
||||
BarColor: ThemeAttr("gauge.bar.bg"),
|
||||
Label: "{{percent}}%",
|
||||
LabelAlign: AlignCenter,
|
||||
PercentColorHighlighted: ColorUndef,
|
||||
}
|
||||
|
||||
g.Width = 12
|
||||
g.Height = 5
|
||||
return g
|
||||
}
|
||||
|
||||
// Buffer implements Bufferer interface.
|
||||
func (g *Gauge) Buffer() Buffer {
|
||||
buf := g.Block.Buffer()
|
||||
|
||||
// plot bar
|
||||
w := g.Percent * g.innerArea.Dx() / 100
|
||||
for i := 0; i < g.innerArea.Dy(); i++ {
|
||||
for j := 0; j < w; j++ {
|
||||
c := Cell{}
|
||||
c.Ch = ' '
|
||||
c.Bg = g.BarColor
|
||||
if c.Bg == ColorDefault {
|
||||
c.Bg |= AttrReverse
|
||||
}
|
||||
buf.Set(g.innerArea.Min.X+j, g.innerArea.Min.Y+i, c)
|
||||
}
|
||||
}
|
||||
|
||||
// plot percentage
|
||||
s := strings.Replace(g.Label, "{{percent}}", strconv.Itoa(g.Percent), -1)
|
||||
pry := g.innerArea.Min.Y + g.innerArea.Dy()/2
|
||||
rs := str2runes(s)
|
||||
var pos int
|
||||
switch g.LabelAlign {
|
||||
case AlignLeft:
|
||||
pos = 0
|
||||
|
||||
case AlignCenter:
|
||||
pos = (g.innerArea.Dx() - strWidth(s)) / 2
|
||||
|
||||
case AlignRight:
|
||||
pos = g.innerArea.Dx() - strWidth(s) - 1
|
||||
}
|
||||
pos += g.innerArea.Min.X
|
||||
|
||||
for i, v := range rs {
|
||||
c := Cell{
|
||||
Ch: v,
|
||||
Fg: g.PercentColor,
|
||||
}
|
||||
|
||||
if w+g.innerArea.Min.X > pos+i {
|
||||
c.Bg = g.BarColor
|
||||
if c.Bg == ColorDefault {
|
||||
c.Bg |= AttrReverse
|
||||
}
|
||||
|
||||
if g.PercentColorHighlighted != ColorUndef {
|
||||
c.Fg = g.PercentColorHighlighted
|
||||
}
|
||||
} else {
|
||||
c.Bg = g.Block.Bg
|
||||
}
|
||||
|
||||
buf.Set(1+pos+i, pry, c)
|
||||
}
|
||||
return buf
|
||||
}
|
0
pkg/termui/linechart_others.go → vendor/github.com/gizak/termui/linechart_others.go
generated
vendored
0
pkg/termui/linechart_others.go → vendor/github.com/gizak/termui/linechart_others.go
generated
vendored
@ -0,0 +1,89 @@
|
||||
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
package termui
|
||||
|
||||
import "strings"
|
||||
|
||||
// List displays []string as its items,
|
||||
// it has a Overflow option (default is "hidden"), when set to "hidden",
|
||||
// the item exceeding List's width is truncated, but when set to "wrap",
|
||||
// the overflowed text breaks into next line.
|
||||
/*
|
||||
strs := []string{
|
||||
"[0] github.com/gizak/termui",
|
||||
"[1] editbox.go",
|
||||
"[2] interrupt.go",
|
||||
"[3] keyboard.go",
|
||||
"[4] output.go",
|
||||
"[5] random_out.go",
|
||||
"[6] dashboard.go",
|
||||
"[7] nsf/termbox-go"}
|
||||
|
||||
ls := termui.NewList()
|
||||
ls.Items = strs
|
||||
ls.ItemFgColor = termui.ColorYellow
|
||||
ls.BorderLabel = "List"
|
||||
ls.Height = 7
|
||||
ls.Width = 25
|
||||
ls.Y = 0
|
||||
*/
|
||||
type List struct {
|
||||
Block
|
||||
Items []string
|
||||
Overflow string
|
||||
ItemFgColor Attribute
|
||||
ItemBgColor Attribute
|
||||
}
|
||||
|
||||
// NewList returns a new *List with current theme.
|
||||
func NewList() *List {
|
||||
l := &List{Block: *NewBlock()}
|
||||
l.Overflow = "hidden"
|
||||
l.ItemFgColor = ThemeAttr("list.item.fg")
|
||||
l.ItemBgColor = ThemeAttr("list.item.bg")
|
||||
return l
|
||||
}
|
||||
|
||||
// Buffer implements Bufferer interface.
|
||||
func (l *List) Buffer() Buffer {
|
||||
buf := l.Block.Buffer()
|
||||
|
||||
switch l.Overflow {
|
||||
case "wrap":
|
||||
cs := DefaultTxBuilder.Build(strings.Join(l.Items, "\n"), l.ItemFgColor, l.ItemBgColor)
|
||||
i, j, k := 0, 0, 0
|
||||
for i < l.innerArea.Dy() && k < len(cs) {
|
||||
w := cs[k].Width()
|
||||
if cs[k].Ch == '\n' || j+w > l.innerArea.Dx() {
|
||||
i++
|
||||
j = 0
|
||||
if cs[k].Ch == '\n' {
|
||||
k++
|
||||
}
|
||||
continue
|
||||
}
|
||||
buf.Set(l.innerArea.Min.X+j, l.innerArea.Min.Y+i, cs[k])
|
||||
|
||||
k++
|
||||
j++
|
||||
}
|
||||
|
||||
case "hidden":
|
||||
trimItems := l.Items
|
||||
if len(trimItems) > l.innerArea.Dy() {
|
||||
trimItems = trimItems[:l.innerArea.Dy()]
|
||||
}
|
||||
for i, v := range trimItems {
|
||||
cs := DTrimTxCls(DefaultTxBuilder.Build(v, l.ItemFgColor, l.ItemBgColor), l.innerArea.Dx())
|
||||
j := 0
|
||||
for _, vv := range cs {
|
||||
w := vv.Width()
|
||||
buf.Set(l.innerArea.Min.X+j, l.innerArea.Min.Y+i, vv)
|
||||
j += w
|
||||
}
|
||||
}
|
||||
}
|
||||
return buf
|
||||
}
|
@ -0,0 +1,242 @@
|
||||
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
package termui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// This is the implementation of multi-colored or stacked bar graph. This is different from default barGraph which is implemented in bar.go
|
||||
// Multi-Colored-BarChart creates multiple bars in a widget:
|
||||
/*
|
||||
bc := termui.NewMBarChart()
|
||||
data := make([][]int, 2)
|
||||
data[0] := []int{3, 2, 5, 7, 9, 4}
|
||||
data[1] := []int{7, 8, 5, 3, 1, 6}
|
||||
bclabels := []string{"S0", "S1", "S2", "S3", "S4", "S5"}
|
||||
bc.BorderLabel = "Bar Chart"
|
||||
bc.Data = data
|
||||
bc.Width = 26
|
||||
bc.Height = 10
|
||||
bc.DataLabels = bclabels
|
||||
bc.TextColor = termui.ColorGreen
|
||||
bc.BarColor = termui.ColorRed
|
||||
bc.NumColor = termui.ColorYellow
|
||||
*/
|
||||
type MBarChart struct {
|
||||
Block
|
||||
BarColor [NumberofColors]Attribute
|
||||
TextColor Attribute
|
||||
NumColor [NumberofColors]Attribute
|
||||
Data [NumberofColors][]int
|
||||
DataLabels []string
|
||||
BarWidth int
|
||||
BarGap int
|
||||
labels [][]rune
|
||||
dataNum [NumberofColors][][]rune
|
||||
numBar int
|
||||
scale float64
|
||||
max int
|
||||
minDataLen int
|
||||
numStack int
|
||||
ShowScale bool
|
||||
maxScale []rune
|
||||
}
|
||||
|
||||
// NewBarChart returns a new *BarChart with current theme.
|
||||
func NewMBarChart() *MBarChart {
|
||||
bc := &MBarChart{Block: *NewBlock()}
|
||||
bc.BarColor[0] = ThemeAttr("mbarchart.bar.bg")
|
||||
bc.NumColor[0] = ThemeAttr("mbarchart.num.fg")
|
||||
bc.TextColor = ThemeAttr("mbarchart.text.fg")
|
||||
bc.BarGap = 1
|
||||
bc.BarWidth = 3
|
||||
return bc
|
||||
}
|
||||
|
||||
func (bc *MBarChart) layout() {
|
||||
bc.numBar = bc.innerArea.Dx() / (bc.BarGap + bc.BarWidth)
|
||||
bc.labels = make([][]rune, bc.numBar)
|
||||
DataLen := 0
|
||||
LabelLen := len(bc.DataLabels)
|
||||
bc.minDataLen = 9999 //Set this to some very hight value so that we find the minimum one We want to know which array among data[][] has got the least length
|
||||
|
||||
// We need to know how many stack/data array data[0] , data[1] are there
|
||||
for i := 0; i < len(bc.Data); i++ {
|
||||
if bc.Data[i] == nil {
|
||||
break
|
||||
}
|
||||
DataLen++
|
||||
}
|
||||
bc.numStack = DataLen
|
||||
|
||||
//We need to know what is the minimum size of data array data[0] could have 10 elements data[1] could have only 5, so we plot only 5 bar graphs
|
||||
|
||||
for i := 0; i < DataLen; i++ {
|
||||
if bc.minDataLen > len(bc.Data[i]) {
|
||||
bc.minDataLen = len(bc.Data[i])
|
||||
}
|
||||
}
|
||||
|
||||
if LabelLen > bc.minDataLen {
|
||||
LabelLen = bc.minDataLen
|
||||
}
|
||||
|
||||
for i := 0; i < LabelLen && i < bc.numBar; i++ {
|
||||
bc.labels[i] = trimStr2Runes(bc.DataLabels[i], bc.BarWidth)
|
||||
}
|
||||
|
||||
for i := 0; i < bc.numStack; i++ {
|
||||
bc.dataNum[i] = make([][]rune, len(bc.Data[i]))
|
||||
//For each stack of bar calculate the rune
|
||||
for j := 0; j < LabelLen && i < bc.numBar; j++ {
|
||||
n := bc.Data[i][j]
|
||||
s := fmt.Sprint(n)
|
||||
bc.dataNum[i][j] = trimStr2Runes(s, bc.BarWidth)
|
||||
}
|
||||
//If color is not defined by default then populate a color that is different from the previous bar
|
||||
if bc.BarColor[i] == ColorDefault && bc.NumColor[i] == ColorDefault {
|
||||
if i == 0 {
|
||||
bc.BarColor[i] = ColorBlack
|
||||
} else {
|
||||
bc.BarColor[i] = bc.BarColor[i-1] + 1
|
||||
if bc.BarColor[i] > NumberofColors {
|
||||
bc.BarColor[i] = ColorBlack
|
||||
}
|
||||
}
|
||||
bc.NumColor[i] = (NumberofColors + 1) - bc.BarColor[i] //Make NumColor opposite of barColor for visibility
|
||||
}
|
||||
}
|
||||
|
||||
//If Max value is not set then we have to populate, this time the max value will be max(sum(d1[0],d2[0],d3[0]) .... sum(d1[n], d2[n], d3[n]))
|
||||
|
||||
if bc.max == 0 {
|
||||
bc.max = -1
|
||||
}
|
||||
for i := 0; i < bc.minDataLen && i < LabelLen; i++ {
|
||||
var dsum int
|
||||
for j := 0; j < bc.numStack; j++ {
|
||||
dsum += bc.Data[j][i]
|
||||
}
|
||||
if dsum > bc.max {
|
||||
bc.max = dsum
|
||||
}
|
||||
}
|
||||
|
||||
//Finally Calculate max sale
|
||||
if bc.ShowScale {
|
||||
s := fmt.Sprintf("%d", bc.max)
|
||||
bc.maxScale = trimStr2Runes(s, len(s))
|
||||
bc.scale = float64(bc.max) / float64(bc.innerArea.Dy()-2)
|
||||
} else {
|
||||
bc.scale = float64(bc.max) / float64(bc.innerArea.Dy()-1)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (bc *MBarChart) SetMax(max int) {
|
||||
|
||||
if max > 0 {
|
||||
bc.max = max
|
||||
}
|
||||
}
|
||||
|
||||
// Buffer implements Bufferer interface.
|
||||
func (bc *MBarChart) Buffer() Buffer {
|
||||
buf := bc.Block.Buffer()
|
||||
bc.layout()
|
||||
var oftX int
|
||||
|
||||
for i := 0; i < bc.numBar && i < bc.minDataLen && i < len(bc.DataLabels); i++ {
|
||||
ph := 0 //Previous Height to stack up
|
||||
oftX = i * (bc.BarWidth + bc.BarGap)
|
||||
for i1 := 0; i1 < bc.numStack; i1++ {
|
||||
h := int(float64(bc.Data[i1][i]) / bc.scale)
|
||||
// plot bars
|
||||
for j := 0; j < bc.BarWidth; j++ {
|
||||
for k := 0; k < h; k++ {
|
||||
c := Cell{
|
||||
Ch: ' ',
|
||||
Bg: bc.BarColor[i1],
|
||||
}
|
||||
if bc.BarColor[i1] == ColorDefault { // when color is default, space char treated as transparent!
|
||||
c.Bg |= AttrReverse
|
||||
}
|
||||
x := bc.innerArea.Min.X + i*(bc.BarWidth+bc.BarGap) + j
|
||||
y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 2 - k - ph
|
||||
buf.Set(x, y, c)
|
||||
|
||||
}
|
||||
}
|
||||
ph += h
|
||||
}
|
||||
// plot text
|
||||
for j, k := 0, 0; j < len(bc.labels[i]); j++ {
|
||||
w := charWidth(bc.labels[i][j])
|
||||
c := Cell{
|
||||
Ch: bc.labels[i][j],
|
||||
Bg: bc.Bg,
|
||||
Fg: bc.TextColor,
|
||||
}
|
||||
y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 1
|
||||
x := bc.innerArea.Max.X + oftX + ((bc.BarWidth - len(bc.labels[i])) / 2) + k
|
||||
buf.Set(x, y, c)
|
||||
k += w
|
||||
}
|
||||
// plot num
|
||||
ph = 0 //re-initialize previous height
|
||||
for i1 := 0; i1 < bc.numStack; i1++ {
|
||||
h := int(float64(bc.Data[i1][i]) / bc.scale)
|
||||
for j := 0; j < len(bc.dataNum[i1][i]) && h > 0; j++ {
|
||||
c := Cell{
|
||||
Ch: bc.dataNum[i1][i][j],
|
||||
Fg: bc.NumColor[i1],
|
||||
Bg: bc.BarColor[i1],
|
||||
}
|
||||
if bc.BarColor[i1] == ColorDefault { // the same as above
|
||||
c.Bg |= AttrReverse
|
||||
}
|
||||
if h == 0 {
|
||||
c.Bg = bc.Bg
|
||||
}
|
||||
x := bc.innerArea.Min.X + oftX + (bc.BarWidth-len(bc.dataNum[i1][i]))/2 + j
|
||||
y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 2 - ph
|
||||
buf.Set(x, y, c)
|
||||
}
|
||||
ph += h
|
||||
}
|
||||
}
|
||||
|
||||
if bc.ShowScale {
|
||||
//Currently bar graph only supprts data range from 0 to MAX
|
||||
//Plot 0
|
||||
c := Cell{
|
||||
Ch: '0',
|
||||
Bg: bc.Bg,
|
||||
Fg: bc.TextColor,
|
||||
}
|
||||
|
||||
y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 2
|
||||
x := bc.X
|
||||
buf.Set(x, y, c)
|
||||
|
||||
//Plot the maximum sacle value
|
||||
for i := 0; i < len(bc.maxScale); i++ {
|
||||
c := Cell{
|
||||
Ch: bc.maxScale[i],
|
||||
Bg: bc.Bg,
|
||||
Fg: bc.TextColor,
|
||||
}
|
||||
|
||||
y := bc.innerArea.Min.Y
|
||||
x := bc.X + i
|
||||
|
||||
buf.Set(x, y, c)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return buf
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
pages:
|
||||
- Home: 'index.md'
|
||||
- Quickstart: 'quickstart.md'
|
||||
- Recipes: 'recipes.md'
|
||||
- References:
|
||||
- Layouts: 'layouts.md'
|
||||
- Components: 'components.md'
|
||||
- Events: 'events.md'
|
||||
- Themes: 'themes.md'
|
||||
- Versions: 'versions.md'
|
||||
- About: 'about.md'
|
||||
|
||||
site_name: termui
|
||||
repo_url: https://github.com/gizak/termui/
|
||||
site_description: 'termui user guide'
|
||||
site_author: gizak
|
||||
|
||||
docs_dir: '_docs'
|
||||
|
||||
theme: readthedocs
|
||||
|
||||
markdown_extensions:
|
||||
- smarty
|
||||
- admonition
|
||||
- toc
|
||||
|
||||
extra:
|
||||
version: 1.0
|
@ -0,0 +1,73 @@
|
||||
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
package termui
|
||||
|
||||
// Par displays a paragraph.
|
||||
/*
|
||||
par := termui.NewPar("Simple Text")
|
||||
par.Height = 3
|
||||
par.Width = 17
|
||||
par.BorderLabel = "Label"
|
||||
*/
|
||||
type Par struct {
|
||||
Block
|
||||
Text string
|
||||
TextFgColor Attribute
|
||||
TextBgColor Attribute
|
||||
WrapLength int // words wrap limit. Note it may not work properly with multi-width char
|
||||
}
|
||||
|
||||
// NewPar returns a new *Par with given text as its content.
|
||||
func NewPar(s string) *Par {
|
||||
return &Par{
|
||||
Block: *NewBlock(),
|
||||
Text: s,
|
||||
TextFgColor: ThemeAttr("par.text.fg"),
|
||||
TextBgColor: ThemeAttr("par.text.bg"),
|
||||
WrapLength: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// Buffer implements Bufferer interface.
|
||||
func (p *Par) Buffer() Buffer {
|
||||
buf := p.Block.Buffer()
|
||||
|
||||
fg, bg := p.TextFgColor, p.TextBgColor
|
||||
cs := DefaultTxBuilder.Build(p.Text, fg, bg)
|
||||
|
||||
// wrap if WrapLength set
|
||||
if p.WrapLength < 0 {
|
||||
cs = wrapTx(cs, p.Width-2)
|
||||
} else if p.WrapLength > 0 {
|
||||
cs = wrapTx(cs, p.WrapLength)
|
||||
}
|
||||
|
||||
y, x, n := 0, 0, 0
|
||||
for y < p.innerArea.Dy() && n < len(cs) {
|
||||
w := cs[n].Width()
|
||||
if cs[n].Ch == '\n' || x+w > p.innerArea.Dx() {
|
||||
y++
|
||||
x = 0 // set x = 0
|
||||
if cs[n].Ch == '\n' {
|
||||
n++
|
||||
}
|
||||
|
||||
if y >= p.innerArea.Dy() {
|
||||
buf.Set(p.innerArea.Min.X+p.innerArea.Dx()-1,
|
||||
p.innerArea.Min.Y+p.innerArea.Dy()-1,
|
||||
Cell{Ch: '…', Fg: p.TextFgColor, Bg: p.TextBgColor})
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
buf.Set(p.innerArea.Min.X+x, p.innerArea.Min.Y+y, cs[n])
|
||||
|
||||
n++
|
||||
x += w
|
||||
}
|
||||
|
||||
return buf
|
||||
}
|
@ -0,0 +1,190 @@
|
||||
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
package termui
|
||||
|
||||
import (
|
||||
"image"
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"fmt"
|
||||
|
||||
"os"
|
||||
|
||||
"runtime/debug"
|
||||
|
||||
"bytes"
|
||||
|
||||
"github.com/maruel/panicparse/stack"
|
||||
tm "github.com/nsf/termbox-go"
|
||||
)
|
||||
|
||||
// Bufferer should be implemented by all renderable components.
|
||||
type Bufferer interface {
|
||||
Buffer() Buffer
|
||||
}
|
||||
|
||||
// Init initializes termui library. This function should be called before any others.
|
||||
// After initialization, the library must be finalized by 'Close' function.
|
||||
func Init() error {
|
||||
if err := tm.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sysEvtChs = make([]chan Event, 0)
|
||||
go hookTermboxEvt()
|
||||
|
||||
renderJobs = make(chan []Bufferer)
|
||||
//renderLock = new(sync.RWMutex)
|
||||
|
||||
Body = NewGrid()
|
||||
Body.X = 0
|
||||
Body.Y = 0
|
||||
Body.BgColor = ThemeAttr("bg")
|
||||
Body.Width = TermWidth()
|
||||
|
||||
DefaultEvtStream.Init()
|
||||
DefaultEvtStream.Merge("termbox", NewSysEvtCh())
|
||||
DefaultEvtStream.Merge("timer", NewTimerCh(time.Second))
|
||||
DefaultEvtStream.Merge("custom", usrEvtCh)
|
||||
|
||||
DefaultEvtStream.Handle("/", DefaultHandler)
|
||||
DefaultEvtStream.Handle("/sys/wnd/resize", func(e Event) {
|
||||
w := e.Data.(EvtWnd)
|
||||
Body.Width = w.Width
|
||||
})
|
||||
|
||||
DefaultWgtMgr = NewWgtMgr()
|
||||
DefaultEvtStream.Hook(DefaultWgtMgr.WgtHandlersHook())
|
||||
|
||||
go func() {
|
||||
for bs := range renderJobs {
|
||||
render(bs...)
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close finalizes termui library,
|
||||
// should be called after successful initialization when termui's functionality isn't required anymore.
|
||||
func Close() {
|
||||
tm.Close()
|
||||
}
|
||||
|
||||
var renderLock sync.Mutex
|
||||
|
||||
func termSync() {
|
||||
renderLock.Lock()
|
||||
tm.Sync()
|
||||
termWidth, termHeight = tm.Size()
|
||||
renderLock.Unlock()
|
||||
}
|
||||
|
||||
// TermWidth returns the current terminal's width.
|
||||
func TermWidth() int {
|
||||
termSync()
|
||||
return termWidth
|
||||
}
|
||||
|
||||
// TermHeight returns the current terminal's height.
|
||||
func TermHeight() int {
|
||||
termSync()
|
||||
return termHeight
|
||||
}
|
||||
|
||||
// Render renders all Bufferer in the given order from left to right,
|
||||
// right could overlap on left ones.
|
||||
func render(bs ...Bufferer) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
Close()
|
||||
fmt.Fprintf(os.Stderr, "Captured a panic(value=%v) when rendering Bufferer. Exit termui and clean terminal...\nPrint stack trace:\n\n", e)
|
||||
//debug.PrintStack()
|
||||
gs, err := stack.ParseDump(bytes.NewReader(debug.Stack()), os.Stderr, false)
|
||||
if err != nil {
|
||||
debug.PrintStack()
|
||||
os.Exit(1)
|
||||
}
|
||||
buckets := stack.Aggregate(gs.Goroutines, stack.AnyValue)
|
||||
for _, bucket := range buckets {
|
||||
srcLen, pkgLen := 0, 0
|
||||
for _, line := range bucket.Signature.Stack.Calls {
|
||||
if l := len(line.SrcLine()); l > srcLen {
|
||||
srcLen = l
|
||||
}
|
||||
if l := len(line.Func.PkgName()); l > pkgLen {
|
||||
pkgLen = l
|
||||
}
|
||||
}
|
||||
extra := ""
|
||||
if s := bucket.SleepString(); s != "" {
|
||||
extra += " [" + s + "]"
|
||||
}
|
||||
if bucket.Locked {
|
||||
extra += " [locked]"
|
||||
}
|
||||
if c := bucket.CreatedByString(false); c != "" {
|
||||
extra += " [Created by " + c + "]"
|
||||
}
|
||||
io.WriteString(os.Stdout, fmt.Sprintf("%d: %s%s\n", len(bucket.IDs), bucket.State, extra))
|
||||
|
||||
for _, line := range bucket.Stack.Calls {
|
||||
io.WriteString(os.Stdout, fmt.Sprintf(
|
||||
" %-*s %-*s %s(%s)\n",
|
||||
pkgLen, line.Func.PkgName(), srcLen, line.SrcLine(),
|
||||
line.Func.Name(), &line.Args))
|
||||
}
|
||||
if bucket.Stack.Elided {
|
||||
io.WriteString(os.Stdout, " (...)\n")
|
||||
}
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
for _, b := range bs {
|
||||
|
||||
buf := b.Buffer()
|
||||
// set cels in buf
|
||||
for p, c := range buf.CellMap {
|
||||
if p.In(buf.Area) {
|
||||
|
||||
tm.SetCell(p.X, p.Y, c.Ch, toTmAttr(c.Fg), toTmAttr(c.Bg))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
renderLock.Lock()
|
||||
// render
|
||||
tm.Flush()
|
||||
renderLock.Unlock()
|
||||
}
|
||||
|
||||
func Clear() {
|
||||
tm.Clear(tm.ColorDefault, toTmAttr(ThemeAttr("bg")))
|
||||
}
|
||||
|
||||
func clearArea(r image.Rectangle, bg Attribute) {
|
||||
for i := r.Min.X; i < r.Max.X; i++ {
|
||||
for j := r.Min.Y; j < r.Max.Y; j++ {
|
||||
tm.SetCell(i, j, ' ', tm.ColorDefault, toTmAttr(bg))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ClearArea(r image.Rectangle, bg Attribute) {
|
||||
clearArea(r, bg)
|
||||
tm.Flush()
|
||||
}
|
||||
|
||||
var renderJobs chan []Bufferer
|
||||
|
||||
func Render(bs ...Bufferer) {
|
||||
//go func() { renderJobs <- bs }()
|
||||
renderJobs <- bs
|
||||
}
|
@ -0,0 +1,185 @@
|
||||
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
package termui
|
||||
|
||||
import "strings"
|
||||
|
||||
/* Table is like:
|
||||
|
||||
┌Awesome Table ────────────────────────────────────────────────┐
|
||||
│ Col0 | Col1 | Col2 | Col3 | Col4 | Col5 | Col6 |
|
||||
│──────────────────────────────────────────────────────────────│
|
||||
│ Some Item #1 | AAA | 123 | CCCCC | EEEEE | GGGGG | IIIII |
|
||||
│──────────────────────────────────────────────────────────────│
|
||||
│ Some Item #2 | BBB | 456 | DDDDD | FFFFF | HHHHH | JJJJJ |
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
|
||||
Datapoints are a two dimensional array of strings: [][]string
|
||||
|
||||
Example:
|
||||
data := [][]string{
|
||||
{"Col0", "Col1", "Col3", "Col4", "Col5", "Col6"},
|
||||
{"Some Item #1", "AAA", "123", "CCCCC", "EEEEE", "GGGGG", "IIIII"},
|
||||
{"Some Item #2", "BBB", "456", "DDDDD", "FFFFF", "HHHHH", "JJJJJ"},
|
||||
}
|
||||
|
||||
table := termui.NewTable()
|
||||
table.Rows = data // type [][]string
|
||||
table.FgColor = termui.ColorWhite
|
||||
table.BgColor = termui.ColorDefault
|
||||
table.Height = 7
|
||||
table.Width = 62
|
||||
table.Y = 0
|
||||
table.X = 0
|
||||
table.Border = true
|
||||
*/
|
||||
|
||||
// Table tracks all the attributes of a Table instance
|
||||
type Table struct {
|
||||
Block
|
||||
Rows [][]string
|
||||
CellWidth []int
|
||||
FgColor Attribute
|
||||
BgColor Attribute
|
||||
FgColors []Attribute
|
||||
BgColors []Attribute
|
||||
Separator bool
|
||||
TextAlign Align
|
||||
}
|
||||
|
||||
// NewTable returns a new Table instance
|
||||
func NewTable() *Table {
|
||||
table := &Table{Block: *NewBlock()}
|
||||
table.FgColor = ColorWhite
|
||||
table.BgColor = ColorDefault
|
||||
table.Separator = true
|
||||
return table
|
||||
}
|
||||
|
||||
// CellsWidth calculates the width of a cell array and returns an int
|
||||
func cellsWidth(cells []Cell) int {
|
||||
width := 0
|
||||
for _, c := range cells {
|
||||
width += c.Width()
|
||||
}
|
||||
return width
|
||||
}
|
||||
|
||||
// Analysis generates and returns an array of []Cell that represent all columns in the Table
|
||||
func (table *Table) Analysis() [][]Cell {
|
||||
var rowCells [][]Cell
|
||||
length := len(table.Rows)
|
||||
if length < 1 {
|
||||
return rowCells
|
||||
}
|
||||
|
||||
if len(table.FgColors) == 0 {
|
||||
table.FgColors = make([]Attribute, len(table.Rows))
|
||||
}
|
||||
if len(table.BgColors) == 0 {
|
||||
table.BgColors = make([]Attribute, len(table.Rows))
|
||||
}
|
||||
|
||||
cellWidths := make([]int, len(table.Rows[0]))
|
||||
|
||||
for y, row := range table.Rows {
|
||||
if table.FgColors[y] == 0 {
|
||||
table.FgColors[y] = table.FgColor
|
||||
}
|
||||
if table.BgColors[y] == 0 {
|
||||
table.BgColors[y] = table.BgColor
|
||||
}
|
||||
for x, str := range row {
|
||||
cells := DefaultTxBuilder.Build(str, table.FgColors[y], table.BgColors[y])
|
||||
cw := cellsWidth(cells)
|
||||
if cellWidths[x] < cw {
|
||||
cellWidths[x] = cw
|
||||
}
|
||||
rowCells = append(rowCells, cells)
|
||||
}
|
||||
}
|
||||
table.CellWidth = cellWidths
|
||||
return rowCells
|
||||
}
|
||||
|
||||
// SetSize calculates the table size and sets the internal value
|
||||
func (table *Table) SetSize() {
|
||||
length := len(table.Rows)
|
||||
if table.Separator {
|
||||
table.Height = length*2 + 1
|
||||
} else {
|
||||
table.Height = length + 2
|
||||
}
|
||||
table.Width = 2
|
||||
if length != 0 {
|
||||
for _, cellWidth := range table.CellWidth {
|
||||
table.Width += cellWidth + 3
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CalculatePosition ...
|
||||
func (table *Table) CalculatePosition(x int, y int, coordinateX *int, coordinateY *int, cellStart *int) {
|
||||
if table.Separator {
|
||||
*coordinateY = table.innerArea.Min.Y + y*2
|
||||
} else {
|
||||
*coordinateY = table.innerArea.Min.Y + y
|
||||
}
|
||||
if x == 0 {
|
||||
*cellStart = table.innerArea.Min.X
|
||||
} else {
|
||||
*cellStart += table.CellWidth[x-1] + 3
|
||||
}
|
||||
|
||||
switch table.TextAlign {
|
||||
case AlignRight:
|
||||
*coordinateX = *cellStart + (table.CellWidth[x] - len(table.Rows[y][x])) + 2
|
||||
case AlignCenter:
|
||||
*coordinateX = *cellStart + (table.CellWidth[x]-len(table.Rows[y][x]))/2 + 2
|
||||
default:
|
||||
*coordinateX = *cellStart + 2
|
||||
}
|
||||
}
|
||||
|
||||
// Buffer ...
|
||||
func (table *Table) Buffer() Buffer {
|
||||
buffer := table.Block.Buffer()
|
||||
rowCells := table.Analysis()
|
||||
pointerX := table.innerArea.Min.X + 2
|
||||
pointerY := table.innerArea.Min.Y
|
||||
borderPointerX := table.innerArea.Min.X
|
||||
for y, row := range table.Rows {
|
||||
for x := range row {
|
||||
table.CalculatePosition(x, y, &pointerX, &pointerY, &borderPointerX)
|
||||
background := DefaultTxBuilder.Build(strings.Repeat(" ", table.CellWidth[x]+3), table.BgColors[y], table.BgColors[y])
|
||||
cells := rowCells[y*len(row)+x]
|
||||
for i, back := range background {
|
||||
buffer.Set(borderPointerX+i, pointerY, back)
|
||||
}
|
||||
|
||||
coordinateX := pointerX
|
||||
for _, printer := range cells {
|
||||
buffer.Set(coordinateX, pointerY, printer)
|
||||
coordinateX += printer.Width()
|
||||
}
|
||||
|
||||
if x != 0 {
|
||||
dividors := DefaultTxBuilder.Build("|", table.FgColors[y], table.BgColors[y])
|
||||
for _, dividor := range dividors {
|
||||
buffer.Set(borderPointerX, pointerY, dividor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if table.Separator {
|
||||
border := DefaultTxBuilder.Build(strings.Repeat("─", table.Width-2), table.FgColor, table.BgColor)
|
||||
for i, cell := range border {
|
||||
buffer.Set(i+1, pointerY+1, cell)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buffer
|
||||
}
|
@ -0,0 +1,278 @@
|
||||
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
package termui
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/go-wordwrap"
|
||||
)
|
||||
|
||||
// TextBuilder is a minimal interface to produce text []Cell using specific syntax (markdown).
|
||||
type TextBuilder interface {
|
||||
Build(s string, fg, bg Attribute) []Cell
|
||||
}
|
||||
|
||||
// DefaultTxBuilder is set to be MarkdownTxBuilder.
|
||||
var DefaultTxBuilder = NewMarkdownTxBuilder()
|
||||
|
||||
// MarkdownTxBuilder implements TextBuilder interface, using markdown syntax.
|
||||
type MarkdownTxBuilder struct {
|
||||
baseFg Attribute
|
||||
baseBg Attribute
|
||||
plainTx []rune
|
||||
markers []marker
|
||||
}
|
||||
|
||||
type marker struct {
|
||||
st int
|
||||
ed int
|
||||
fg Attribute
|
||||
bg Attribute
|
||||
}
|
||||
|
||||
var colorMap = map[string]Attribute{
|
||||
"red": ColorRed,
|
||||
"blue": ColorBlue,
|
||||
"black": ColorBlack,
|
||||
"cyan": ColorCyan,
|
||||
"yellow": ColorYellow,
|
||||
"white": ColorWhite,
|
||||
"default": ColorDefault,
|
||||
"green": ColorGreen,
|
||||
"magenta": ColorMagenta,
|
||||
}
|
||||
|
||||
var attrMap = map[string]Attribute{
|
||||
"bold": AttrBold,
|
||||
"underline": AttrUnderline,
|
||||
"reverse": AttrReverse,
|
||||
}
|
||||
|
||||
func rmSpc(s string) string {
|
||||
reg := regexp.MustCompile(`\s+`)
|
||||
return reg.ReplaceAllString(s, "")
|
||||
}
|
||||
|
||||
// readAttr translates strings like `fg-red,fg-bold,bg-white` to fg and bg Attribute
|
||||
func (mtb MarkdownTxBuilder) readAttr(s string) (Attribute, Attribute) {
|
||||
fg := mtb.baseFg
|
||||
bg := mtb.baseBg
|
||||
|
||||
updateAttr := func(a Attribute, attrs []string) Attribute {
|
||||
for _, s := range attrs {
|
||||
// replace the color
|
||||
if c, ok := colorMap[s]; ok {
|
||||
a &= 0xFF00 // erase clr 0 ~ 8 bits
|
||||
a |= c // set clr
|
||||
}
|
||||
// add attrs
|
||||
if c, ok := attrMap[s]; ok {
|
||||
a |= c
|
||||
}
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
ss := strings.Split(s, ",")
|
||||
fgs := []string{}
|
||||
bgs := []string{}
|
||||
for _, v := range ss {
|
||||
subs := strings.Split(v, "-")
|
||||
if len(subs) > 1 {
|
||||
if subs[0] == "fg" {
|
||||
fgs = append(fgs, subs[1])
|
||||
}
|
||||
if subs[0] == "bg" {
|
||||
bgs = append(bgs, subs[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fg = updateAttr(fg, fgs)
|
||||
bg = updateAttr(bg, bgs)
|
||||
return fg, bg
|
||||
}
|
||||
|
||||
func (mtb *MarkdownTxBuilder) reset() {
|
||||
mtb.plainTx = []rune{}
|
||||
mtb.markers = []marker{}
|
||||
}
|
||||
|
||||
// parse streams and parses text into normalized text and render sequence.
|
||||
func (mtb *MarkdownTxBuilder) parse(str string) {
|
||||
rs := str2runes(str)
|
||||
normTx := []rune{}
|
||||
square := []rune{}
|
||||
brackt := []rune{}
|
||||
accSquare := false
|
||||
accBrackt := false
|
||||
cntSquare := 0
|
||||
|
||||
reset := func() {
|
||||
square = []rune{}
|
||||
brackt = []rune{}
|
||||
accSquare = false
|
||||
accBrackt = false
|
||||
cntSquare = 0
|
||||
}
|
||||
// pipe stacks into normTx and clear
|
||||
rollback := func() {
|
||||
normTx = append(normTx, square...)
|
||||
normTx = append(normTx, brackt...)
|
||||
reset()
|
||||
}
|
||||
// chop first and last
|
||||
chop := func(s []rune) []rune {
|
||||
return s[1 : len(s)-1]
|
||||
}
|
||||
|
||||
for i, r := range rs {
|
||||
switch {
|
||||
// stacking brackt
|
||||
case accBrackt:
|
||||
brackt = append(brackt, r)
|
||||
if ')' == r {
|
||||
fg, bg := mtb.readAttr(string(chop(brackt)))
|
||||
st := len(normTx)
|
||||
ed := len(normTx) + len(square) - 2
|
||||
mtb.markers = append(mtb.markers, marker{st, ed, fg, bg})
|
||||
normTx = append(normTx, chop(square)...)
|
||||
reset()
|
||||
} else if i+1 == len(rs) {
|
||||
rollback()
|
||||
}
|
||||
// stacking square
|
||||
case accSquare:
|
||||
switch {
|
||||
// squares closed and followed by a '('
|
||||
case cntSquare == 0 && '(' == r:
|
||||
accBrackt = true
|
||||
brackt = append(brackt, '(')
|
||||
// squares closed but not followed by a '('
|
||||
case cntSquare == 0:
|
||||
rollback()
|
||||
if '[' == r {
|
||||
accSquare = true
|
||||
cntSquare = 1
|
||||
brackt = append(brackt, '[')
|
||||
} else {
|
||||
normTx = append(normTx, r)
|
||||
}
|
||||
// hit the end
|
||||
case i+1 == len(rs):
|
||||
square = append(square, r)
|
||||
rollback()
|
||||
case '[' == r:
|
||||
cntSquare++
|
||||
square = append(square, '[')
|
||||
case ']' == r:
|
||||
cntSquare--
|
||||
square = append(square, ']')
|
||||
// normal char
|
||||
default:
|
||||
square = append(square, r)
|
||||
}
|
||||
// stacking normTx
|
||||
default:
|
||||
if '[' == r {
|
||||
accSquare = true
|
||||
cntSquare = 1
|
||||
square = append(square, '[')
|
||||
} else {
|
||||
normTx = append(normTx, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mtb.plainTx = normTx
|
||||
}
|
||||
|
||||
func wrapTx(cs []Cell, wl int) []Cell {
|
||||
tmpCell := make([]Cell, len(cs))
|
||||
copy(tmpCell, cs)
|
||||
|
||||
// get the plaintext
|
||||
plain := CellsToStr(cs)
|
||||
|
||||
// wrap
|
||||
plainWrapped := wordwrap.WrapString(plain, uint(wl))
|
||||
|
||||
// find differences and insert
|
||||
finalCell := tmpCell // finalcell will get the inserts and is what is returned
|
||||
|
||||
plainRune := []rune(plain)
|
||||
plainWrappedRune := []rune(plainWrapped)
|
||||
trigger := "go"
|
||||
plainRuneNew := plainRune
|
||||
|
||||
for trigger != "stop" {
|
||||
plainRune = plainRuneNew
|
||||
for i := range plainRune {
|
||||
if plainRune[i] == plainWrappedRune[i] {
|
||||
trigger = "stop"
|
||||
} else if plainRune[i] != plainWrappedRune[i] && plainWrappedRune[i] == 10 {
|
||||
trigger = "go"
|
||||
cell := Cell{10, 0, 0}
|
||||
j := i - 0
|
||||
|
||||
// insert a cell into the []Cell in correct position
|
||||
tmpCell[i] = cell
|
||||
|
||||
// insert the newline into plain so we avoid indexing errors
|
||||
plainRuneNew = append(plainRune, 10)
|
||||
copy(plainRuneNew[j+1:], plainRuneNew[j:])
|
||||
plainRuneNew[j] = plainWrappedRune[j]
|
||||
|
||||
// restart the inner for loop until plain and plain wrapped are
|
||||
// the same; yeah, it's inefficient, but the text amounts
|
||||
// should be small
|
||||
break
|
||||
|
||||
} else if plainRune[i] != plainWrappedRune[i] &&
|
||||
plainWrappedRune[i-1] == 10 && // if the prior rune is a newline
|
||||
plainRune[i] == 32 { // and this rune is a space
|
||||
trigger = "go"
|
||||
// need to delete plainRune[i] because it gets rid of an extra
|
||||
// space
|
||||
plainRuneNew = append(plainRune[:i], plainRune[i+1:]...)
|
||||
break
|
||||
|
||||
} else {
|
||||
trigger = "stop" // stops the outer for loop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
finalCell = tmpCell
|
||||
|
||||
return finalCell
|
||||
}
|
||||
|
||||
// Build implements TextBuilder interface.
|
||||
func (mtb MarkdownTxBuilder) Build(s string, fg, bg Attribute) []Cell {
|
||||
mtb.baseFg = fg
|
||||
mtb.baseBg = bg
|
||||
mtb.reset()
|
||||
mtb.parse(s)
|
||||
cs := make([]Cell, len(mtb.plainTx))
|
||||
for i := range cs {
|
||||
cs[i] = Cell{Ch: mtb.plainTx[i], Fg: fg, Bg: bg}
|
||||
}
|
||||
for _, mrk := range mtb.markers {
|
||||
for i := mrk.st; i < mrk.ed; i++ {
|
||||
cs[i].Fg = mrk.fg
|
||||
cs[i].Bg = mrk.bg
|
||||
}
|
||||
}
|
||||
|
||||
return cs
|
||||
}
|
||||
|
||||
// NewMarkdownTxBuilder returns a TextBuilder employing markdown syntax.
|
||||
func NewMarkdownTxBuilder() TextBuilder {
|
||||
return MarkdownTxBuilder{}
|
||||
}
|
@ -0,0 +1 @@
|
||||
*.swp
|
@ -0,0 +1,30 @@
|
||||
# This is the official list of gocui authors for copyright purposes.
|
||||
|
||||
# Names should be added to this file as
|
||||
# Name or Organization <email address> contribution
|
||||
# Contribution
|
||||
# The email address is not required for organizations.
|
||||
|
||||
Roi Martin <jroi.martin@gmail.com>
|
||||
Main developer
|
||||
|
||||
Ryan Sullivan <kayoticsully@gmail.com>
|
||||
Toggleable view frames
|
||||
|
||||
Matthieu Rakotojaona <matthieu.rakotojaona@gmail.com>
|
||||
Wrapped views
|
||||
|
||||
Harry Lawrence <hazbo@gmx.com>
|
||||
Basic mouse support
|
||||
|
||||
Danny Tylman <dtylman@gmail.com>
|
||||
Masked views
|
||||
|
||||
Frederik Deweerdt <frederik.deweerdt@gmail.com>
|
||||
Colored fonts
|
||||
|
||||
Henri Koski <henri.t.koski@gmail.com>
|
||||
Custom current view color
|
||||
|
||||
Dustin Willis Webber <dustin.webber@gmail.com>
|
||||
256-colors output mode support
|
@ -0,0 +1,23 @@
|
||||
Copyright (c) 2014 The gocui Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the gocui Authors nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
@ -0,0 +1,110 @@
|
||||
# GOCUI - Go Console User Interface
|
||||
|
||||
[![GoDoc](https://godoc.org/github.com/jroimartin/gocui?status.svg)](https://godoc.org/github.com/jroimartin/gocui)
|
||||
|
||||
Minimalist Go package aimed at creating Console User Interfaces.
|
||||
|
||||
## Features
|
||||
|
||||
* Minimalist API.
|
||||
* Views (the "windows" in the GUI) implement the interface io.ReadWriter.
|
||||
* Support for overlapping views.
|
||||
* The GUI can be modified at runtime (concurrent-safe).
|
||||
* Global and view-level keybindings.
|
||||
* Mouse support.
|
||||
* Colored text.
|
||||
* Customizable edition mode.
|
||||
* Easy to build reusable widgets, complex layouts...
|
||||
|
||||
## Installation
|
||||
|
||||
Execute:
|
||||
|
||||
```
|
||||
$ go get github.com/jroimartin/gocui
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
Execute:
|
||||
|
||||
```
|
||||
$ go doc github.com/jroimartin/gocui
|
||||
```
|
||||
|
||||
Or visit [godoc.org](https://godoc.org/github.com/jroimartin/gocui) to read it
|
||||
online.
|
||||
|
||||
## Example
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/jroimartin/gocui"
|
||||
)
|
||||
|
||||
func main() {
|
||||
g, err := gocui.NewGui(gocui.OutputNormal)
|
||||
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.MainLoop(); err != nil && err != gocui.ErrQuit {
|
||||
log.Panicln(err)
|
||||
}
|
||||
}
|
||||
|
||||
func layout(g *gocui.Gui) error {
|
||||
maxX, maxY := g.Size()
|
||||
if v, err := g.SetView("hello", maxX/2-7, maxY/2, maxX/2+7, maxY/2+2); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(v, "Hello world!")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func quit(g *gocui.Gui, v *gocui.View) error {
|
||||
return gocui.ErrQuit
|
||||
}
|
||||
```
|
||||
|
||||
## Screenshots
|
||||
|
||||
![r2cui](https://cloud.githubusercontent.com/assets/1223476/19418932/63645052-93ce-11e6-867c-da5e97e37237.png)
|
||||
|
||||
![_examples/demo.go](https://cloud.githubusercontent.com/assets/1223476/5992750/720b84f0-aa36-11e4-88ec-296fa3247b52.png)
|
||||
|
||||
![_examples/dynamic.go](https://cloud.githubusercontent.com/assets/1223476/5992751/76ad5cc2-aa36-11e4-8204-6a90269db827.png)
|
||||
|
||||
## Projects using gocui
|
||||
|
||||
* [komanda-cli](https://github.com/mephux/komanda-cli): IRC Client For Developers.
|
||||
* [vuls](https://github.com/future-architect/vuls): Agentless vulnerability scanner for Linux/FreeBSD.
|
||||
* [wuzz](https://github.com/asciimoo/wuzz): Interactive cli tool for HTTP inspection.
|
||||
* [httplab](https://github.com/gchaincl/httplab): Interactive web server.
|
||||
* [domainr](https://github.com/MichaelThessel/domainr): Tool that checks the availability of domains based on keywords.
|
||||
* [gotime](https://github.com/nanohard/gotime): Time tracker for projects and tasks.
|
||||
* [claws](https://github.com/thehowl/claws): Interactive command line client for testing websockets.
|
||||
* [terminews](http://github.com/antavelos/terminews): Terminal based RSS reader.
|
||||
* [diagram](https://github.com/esimov/diagram): Tool to convert ascii arts into hand drawn diagrams.
|
||||
* [pody](https://github.com/JulienBreux/pody): CLI app to manage Pods in a Kubernetes cluster.
|
||||
* [kubexp](https://github.com/alitari/kubexp): Kubernetes client.
|
||||
* [kcli](https://github.com/cswank/kcli): Tool for inspecting kafka topics/partitions/messages.
|
||||
* [fac](https://github.com/mkchoi212/fac): git merge conflict resolver
|
||||
* [jsonui](https://github.com/gulyasm/jsonui): Interactive JSON explorer for your terminal.
|
||||
* [cointop](https://github.com/miguelmota/cointop): Interactive terminal based UI application for tracking cryptocurrencies.
|
||||
|
||||
Note: if your project is not listed here, let us know! :)
|
@ -0,0 +1,118 @@
|
||||
// Copyright 2014 The gocui Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package gocui allows to create console user interfaces.
|
||||
|
||||
Create a new GUI:
|
||||
|
||||
g, err := gocui.NewGui(gocui.OutputNormal)
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
// Set GUI managers and key bindings
|
||||
// ...
|
||||
|
||||
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
|
||||
// handle error
|
||||
}
|
||||
|
||||
Set GUI managers:
|
||||
|
||||
g.SetManager(mgr1, mgr2)
|
||||
|
||||
Managers are in charge of GUI's layout and can be used to build widgets. On
|
||||
each iteration of the GUI's main loop, the Layout function of each configured
|
||||
manager is executed. Managers are used to set-up and update the application's
|
||||
main views, being possible to freely change them during execution. Also, it is
|
||||
important to mention that a main loop iteration is executed on each reported
|
||||
event (key-press, mouse event, window resize, etc).
|
||||
|
||||
GUIs are composed by Views, you can think of it as buffers. Views implement the
|
||||
io.ReadWriter interface, so you can just write to them if you want to modify
|
||||
their content. The same is valid for reading.
|
||||
|
||||
Create and initialize a view with absolute coordinates:
|
||||
|
||||
if v, err := g.SetView("viewname", 2, 2, 22, 7); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
// handle error
|
||||
}
|
||||
fmt.Fprintln(v, "This is a new view")
|
||||
// ...
|
||||
}
|
||||
|
||||
Views can also be created using relative coordinates:
|
||||
|
||||
maxX, maxY := g.Size()
|
||||
if v, err := g.SetView("viewname", maxX/2-30, maxY/2, maxX/2+30, maxY/2+2); err != nil {
|
||||
// ...
|
||||
}
|
||||
|
||||
Configure keybindings:
|
||||
|
||||
if err := g.SetKeybinding("viewname", gocui.KeyEnter, gocui.ModNone, fcn); err != nil {
|
||||
// handle error
|
||||
}
|
||||
|
||||
gocui implements full mouse support that can be enabled with:
|
||||
|
||||
g.Mouse = true
|
||||
|
||||
Mouse events are handled like any other keybinding:
|
||||
|
||||
if err := g.SetKeybinding("viewname", gocui.MouseLeft, gocui.ModNone, fcn); err != nil {
|
||||
// handle error
|
||||
}
|
||||
|
||||
IMPORTANT: Views can only be created, destroyed or updated in three ways: from
|
||||
the Layout function within managers, from keybinding callbacks or via
|
||||
*Gui.Update(). The reason for this is that it allows gocui to be
|
||||
concurrent-safe. So, if you want to update your GUI from a goroutine, you must
|
||||
use *Gui.Update(). For example:
|
||||
|
||||
g.Update(func(g *gocui.Gui) error {
|
||||
v, err := g.View("viewname")
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
v.Clear()
|
||||
fmt.Fprintln(v, "Writing from different goroutines")
|
||||
return nil
|
||||
})
|
||||
|
||||
By default, gocui provides a basic edition mode. This mode can be extended
|
||||
and customized creating a new Editor and assigning it to *View.Editor:
|
||||
|
||||
type Editor interface {
|
||||
Edit(v *View, key Key, ch rune, mod Modifier)
|
||||
}
|
||||
|
||||
DefaultEditor can be taken as example to create your own custom Editor:
|
||||
|
||||
var DefaultEditor Editor = EditorFunc(simpleEditor)
|
||||
|
||||
func simpleEditor(v *View, key Key, ch rune, mod Modifier) {
|
||||
switch {
|
||||
case ch != 0 && mod == 0:
|
||||
v.EditWrite(ch)
|
||||
case key == KeySpace:
|
||||
v.EditWrite(' ')
|
||||
case key == KeyBackspace || key == KeyBackspace2:
|
||||
v.EditDelete(true)
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
Colored text:
|
||||
|
||||
Views allow to add colored text using ANSI colors. For example:
|
||||
|
||||
fmt.Fprintln(v, "\x1b[0;31mHello world")
|
||||
|
||||
For more information, see the examples in folder "_examples/".
|
||||
*/
|
||||
package gocui
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue