mirror of
https://github.com/jroimartin/gocui.git
synced 2025-04-30 13:48:54 +08:00
Compare commits
81 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
fc12c654f2 | ||
![]() |
031962f8d6 | ||
![]() |
de100503e6 | ||
![]() |
0214e0e872 | ||
![]() |
c055c87ae8 | ||
![]() |
2cda4f9f05 | ||
![]() |
491cd051cf | ||
![]() |
f2f6a1fab8 | ||
![]() |
df0b3dc8df | ||
![]() |
247e437c8e | ||
![]() |
1ec4d5dd73 | ||
![]() |
dc8ca8416c | ||
![]() |
25c0c27f2e | ||
![]() |
4f518eddb0 | ||
![]() |
eb73455bec | ||
![]() |
a251da5a9f | ||
![]() |
70497a1d13 | ||
![]() |
881edc4380 | ||
![]() |
6564cfcacb | ||
![]() |
75a7ad2750 | ||
![]() |
a4d0b30476 | ||
![]() |
ad91aa2b83 | ||
![]() |
2677ad0445 | ||
![]() |
c64aff6dc2 | ||
![]() |
7ba3ea9d2c | ||
![]() |
7e8dafc560 | ||
![]() |
e18bedd405 | ||
![]() |
70a276872a | ||
![]() |
0f8c8e3c9e | ||
![]() |
d1b6db5a49 | ||
![]() |
4316bb79d4 | ||
![]() |
c48e902a26 | ||
![]() |
a984617410 | ||
![]() |
93acb816a8 | ||
![]() |
612b0b2987 | ||
![]() |
5424b93b3b | ||
![]() |
ed41d1bd2c | ||
![]() |
139487f578 | ||
![]() |
e27f247a3e | ||
![]() |
88d2b471d4 | ||
![]() |
cfc4e0298f | ||
![]() |
aff177f6e3 | ||
![]() |
c690b943b6 | ||
![]() |
7ac95c981b | ||
![]() |
78f1aee25c | ||
![]() |
1ad1e27a52 | ||
![]() |
aacdc2698f | ||
![]() |
ba396278de | ||
![]() |
fced1c672f | ||
![]() |
f95667af1a | ||
![]() |
64e8d44eee | ||
![]() |
6b3dc12c32 | ||
![]() |
adef0057a9 | ||
![]() |
56a1633c76 | ||
![]() |
ff841413b6 | ||
![]() |
0975ddb2a8 | ||
![]() |
e41f0e8947 | ||
![]() |
308833a2cf | ||
![]() |
aa4ac778d3 | ||
![]() |
717fd3eaae | ||
![]() |
19125a0a67 | ||
![]() |
357a541add | ||
![]() |
fb08594c69 | ||
![]() |
6a590932dd | ||
![]() |
89ecdb537d | ||
![]() |
e5517b913e | ||
![]() |
df63f19bd6 | ||
![]() |
e38ba07224 | ||
![]() |
40dbad569f | ||
![]() |
fc121d98fd | ||
![]() |
ddbc9be671 | ||
![]() |
550f04e523 | ||
![]() |
d5cb1ac216 | ||
![]() |
a7019c8547 | ||
![]() |
c0ae071931 | ||
![]() |
16db12db96 | ||
![]() |
a109a3641c | ||
![]() |
463428abda | ||
![]() |
7779534f95 | ||
![]() |
ec14132e67 | ||
![]() |
1724fbebc8 |
6
AUTHORS
6
AUTHORS
@ -22,3 +22,9 @@ Danny Tylman <dtylman@gmail.com>
|
|||||||
|
|
||||||
Frederik Deweerdt <frederik.deweerdt@gmail.com>
|
Frederik Deweerdt <frederik.deweerdt@gmail.com>
|
||||||
Colored fonts
|
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
|
||||||
|
45
README.md
45
README.md
@ -1,6 +1,6 @@
|
|||||||
# GOCUI - Go Console User Interface
|
# GOCUI - Go Console User Interface
|
||||||
|
|
||||||
[](https://godoc.org/github.com/jroimartin/gocui)
|
[](https://pkg.go.dev/github.com/jroimartin/gocui)
|
||||||
|
|
||||||
Minimalist Go package aimed at creating Console User Interfaces.
|
Minimalist Go package aimed at creating Console User Interfaces.
|
||||||
|
|
||||||
@ -14,25 +14,26 @@ Minimalist Go package aimed at creating Console User Interfaces.
|
|||||||
* Mouse support.
|
* Mouse support.
|
||||||
* Colored text.
|
* Colored text.
|
||||||
* Customizable edition mode.
|
* Customizable edition mode.
|
||||||
|
* Easy to build reusable widgets, complex layouts...
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
Execute:
|
Execute:
|
||||||
|
|
||||||
```
|
```sh
|
||||||
$ go get github.com/jroimartin/gocui
|
go get github.com/jroimartin/gocui
|
||||||
```
|
```
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
Execute:
|
Execute:
|
||||||
|
|
||||||
```
|
```sh
|
||||||
$ go doc github.com/jroimartin/gocui
|
go doc github.com/jroimartin/gocui
|
||||||
```
|
```
|
||||||
|
|
||||||
Or visit [godoc.org](https://godoc.org/github.com/jroimartin/gocui) to read it
|
Or visit [pkg.go.dev](https://pkg.go.dev/github.com/jroimartin/gocui) to read
|
||||||
online.
|
it online.
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
@ -47,13 +48,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
g := gocui.NewGui()
|
g, err := gocui.NewGui(gocui.OutputNormal)
|
||||||
if err := g.Init(); err != nil {
|
if err != nil {
|
||||||
log.Panicln(err)
|
log.Panicln(err)
|
||||||
}
|
}
|
||||||
defer g.Close()
|
defer g.Close()
|
||||||
|
|
||||||
g.SetLayout(layout)
|
g.SetManagerFunc(layout)
|
||||||
|
|
||||||
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
||||||
log.Panicln(err)
|
log.Panicln(err)
|
||||||
@ -82,10 +83,28 @@ func quit(g *gocui.Gui, v *gocui.View) error {
|
|||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
_examples/demo.go:
|

|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
_examples/dynamic.go:
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
## 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! :)
|
||||||
|
119
_examples/active.go
Normal file
119
_examples/active.go
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
// Copyright 2014 The gocui Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/jroimartin/gocui"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
viewArr = []string{"v1", "v2", "v3", "v4"}
|
||||||
|
active = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
func setCurrentViewOnTop(g *gocui.Gui, name string) (*gocui.View, error) {
|
||||||
|
if _, err := g.SetCurrentView(name); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return g.SetViewOnTop(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func nextView(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
nextIndex := (active + 1) % len(viewArr)
|
||||||
|
name := viewArr[nextIndex]
|
||||||
|
|
||||||
|
out, err := g.View("v2")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Fprintln(out, "Going from view "+v.Name()+" to "+name)
|
||||||
|
|
||||||
|
if _, err := setCurrentViewOnTop(g, name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if nextIndex == 0 || nextIndex == 3 {
|
||||||
|
g.Cursor = true
|
||||||
|
} else {
|
||||||
|
g.Cursor = false
|
||||||
|
}
|
||||||
|
|
||||||
|
active = nextIndex
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func layout(g *gocui.Gui) error {
|
||||||
|
maxX, maxY := g.Size()
|
||||||
|
if v, err := g.SetView("v1", 0, 0, maxX/2-1, maxY/2-1); err != nil {
|
||||||
|
if err != gocui.ErrUnknownView {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.Title = "v1 (editable)"
|
||||||
|
v.Editable = true
|
||||||
|
v.Wrap = true
|
||||||
|
|
||||||
|
if _, err = setCurrentViewOnTop(g, "v1"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, err := g.SetView("v2", maxX/2-1, 0, maxX-1, maxY/2-1); err != nil {
|
||||||
|
if err != gocui.ErrUnknownView {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.Title = "v2"
|
||||||
|
v.Wrap = true
|
||||||
|
v.Autoscroll = true
|
||||||
|
}
|
||||||
|
if v, err := g.SetView("v3", 0, maxY/2-1, maxX/2-1, maxY-1); err != nil {
|
||||||
|
if err != gocui.ErrUnknownView {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.Title = "v3"
|
||||||
|
v.Wrap = true
|
||||||
|
v.Autoscroll = true
|
||||||
|
fmt.Fprint(v, "Press TAB to change current view")
|
||||||
|
}
|
||||||
|
if v, err := g.SetView("v4", maxX/2, maxY/2, maxX-1, maxY-1); err != nil {
|
||||||
|
if err != gocui.ErrUnknownView {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.Title = "v4 (editable)"
|
||||||
|
v.Editable = true
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func quit(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
return gocui.ErrQuit
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
g, err := gocui.NewGui(gocui.OutputNormal)
|
||||||
|
if err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
defer g.Close()
|
||||||
|
|
||||||
|
g.Highlight = true
|
||||||
|
g.Cursor = true
|
||||||
|
g.SelFgColor = gocui.ColorGreen
|
||||||
|
|
||||||
|
g.SetManagerFunc(layout)
|
||||||
|
|
||||||
|
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
if err := g.SetKeybinding("", gocui.KeyTab, gocui.ModNone, nextView); err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,7 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// WARNING: tricky code just for testing purposes, do not use as example.
|
// WARNING: tricky code just for testing purposes, do not use as reference.
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
@ -34,7 +34,7 @@ func layout(g *gocui.Gui) error {
|
|||||||
}
|
}
|
||||||
v.Editable = true
|
v.Editable = true
|
||||||
v.Wrap = true
|
v.Wrap = true
|
||||||
if err := g.SetCurrentView("main"); err != nil {
|
if _, err := g.SetCurrentView("main"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -42,21 +42,22 @@ func layout(g *gocui.Gui) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
g := gocui.NewGui()
|
g, err := gocui.NewGui(gocui.OutputNormal)
|
||||||
if err := g.Init(); err != nil {
|
if err != nil {
|
||||||
log.Panicln(err)
|
log.Panicln(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
g.SetLayout(layout)
|
g.Cursor = true
|
||||||
|
g.Mouse = true
|
||||||
|
|
||||||
|
g.SetManagerFunc(layout)
|
||||||
|
|
||||||
if err := g.SetKeybinding("main", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
if err := g.SetKeybinding("main", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
||||||
log.Panicln(err)
|
log.Panicln(err)
|
||||||
}
|
}
|
||||||
g.SetLayout(layout)
|
|
||||||
if err := g.SetKeybinding("main", gocui.KeyCtrlI, gocui.ModNone, overwrite); err != nil {
|
if err := g.SetKeybinding("main", gocui.KeyCtrlI, gocui.ModNone, overwrite); err != nil {
|
||||||
log.Panicln(err)
|
log.Panicln(err)
|
||||||
}
|
}
|
||||||
g.Cursor = true
|
|
||||||
g.Mouse = true
|
|
||||||
|
|
||||||
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
|
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
|
||||||
log.Panicln(err)
|
log.Panicln(err)
|
||||||
|
@ -12,13 +12,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
g := gocui.NewGui()
|
g, err := gocui.NewGui(gocui.OutputNormal)
|
||||||
if err := g.Init(); err != nil {
|
if err != nil {
|
||||||
log.Panicln(err)
|
log.Panicln(err)
|
||||||
}
|
}
|
||||||
defer g.Close()
|
defer g.Close()
|
||||||
|
|
||||||
g.SetLayout(layout)
|
g.SetManagerFunc(layout)
|
||||||
|
|
||||||
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
||||||
log.Panicln(err)
|
log.Panicln(err)
|
||||||
|
74
_examples/colors256.go
Normal file
74
_examples/colors256.go
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
// Copyright 2014 The gocui Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/jroimartin/gocui"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
g, err := gocui.NewGui(gocui.Output256)
|
||||||
|
|
||||||
|
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("colors", -1, -1, maxX, maxY); err != nil {
|
||||||
|
if err != gocui.ErrUnknownView {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 256-colors escape codes
|
||||||
|
for i := 0; i < 256; i++ {
|
||||||
|
str := fmt.Sprintf("\x1b[48;5;%dm\x1b[30m%3d\x1b[0m ", i, i)
|
||||||
|
str += fmt.Sprintf("\x1b[38;5;%dm%3d\x1b[0m ", i, i)
|
||||||
|
|
||||||
|
if (i+1)%10 == 0 {
|
||||||
|
str += "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprint(v, str)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprint(v, "\n\n")
|
||||||
|
|
||||||
|
// 8-colors escape codes
|
||||||
|
ctr := 0
|
||||||
|
for i := 0; i <= 7; i++ {
|
||||||
|
for _, j := range []int{1, 4, 7} {
|
||||||
|
str := fmt.Sprintf("\x1b[3%d;%dm%d:%d\x1b[0m ", i, j, i, j)
|
||||||
|
if (ctr+1)%20 == 0 {
|
||||||
|
str += "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprint(v, str)
|
||||||
|
|
||||||
|
ctr++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func quit(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
return gocui.ErrQuit
|
||||||
|
}
|
@ -16,9 +16,11 @@ import (
|
|||||||
|
|
||||||
func nextView(g *gocui.Gui, v *gocui.View) error {
|
func nextView(g *gocui.Gui, v *gocui.View) error {
|
||||||
if v == nil || v.Name() == "side" {
|
if v == nil || v.Name() == "side" {
|
||||||
return g.SetCurrentView("main")
|
_, err := g.SetCurrentView("main")
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
return g.SetCurrentView("side")
|
_, err := g.SetCurrentView("side")
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func cursorDown(g *gocui.Gui, v *gocui.View) error {
|
func cursorDown(g *gocui.Gui, v *gocui.View) error {
|
||||||
@ -62,7 +64,7 @@ func getLine(g *gocui.Gui, v *gocui.View) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Fprintln(v, l)
|
fmt.Fprintln(v, l)
|
||||||
if err := g.SetCurrentView("msg"); err != nil {
|
if _, err := g.SetCurrentView("msg"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -73,7 +75,7 @@ func delMsg(g *gocui.Gui, v *gocui.View) error {
|
|||||||
if err := g.DeleteView("msg"); err != nil {
|
if err := g.DeleteView("msg"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := g.SetCurrentView("side"); err != nil {
|
if _, err := g.SetCurrentView("side"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -162,6 +164,8 @@ func layout(g *gocui.Gui) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
v.Highlight = true
|
v.Highlight = true
|
||||||
|
v.SelBgColor = gocui.ColorGreen
|
||||||
|
v.SelFgColor = gocui.ColorBlack
|
||||||
fmt.Fprintln(v, "Item 1")
|
fmt.Fprintln(v, "Item 1")
|
||||||
fmt.Fprintln(v, "Item 2")
|
fmt.Fprintln(v, "Item 2")
|
||||||
fmt.Fprintln(v, "Item 3")
|
fmt.Fprintln(v, "Item 3")
|
||||||
@ -179,7 +183,7 @@ func layout(g *gocui.Gui) error {
|
|||||||
fmt.Fprintf(v, "%s", b)
|
fmt.Fprintf(v, "%s", b)
|
||||||
v.Editable = true
|
v.Editable = true
|
||||||
v.Wrap = true
|
v.Wrap = true
|
||||||
if err := g.SetCurrentView("main"); err != nil {
|
if _, err := g.SetCurrentView("main"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -187,19 +191,19 @@ func layout(g *gocui.Gui) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
g := gocui.NewGui()
|
g, err := gocui.NewGui(gocui.OutputNormal)
|
||||||
if err := g.Init(); err != nil {
|
if err != nil {
|
||||||
log.Panicln(err)
|
log.Panicln(err)
|
||||||
}
|
}
|
||||||
defer g.Close()
|
defer g.Close()
|
||||||
|
|
||||||
g.SetLayout(layout)
|
g.Cursor = true
|
||||||
|
|
||||||
|
g.SetManagerFunc(layout)
|
||||||
|
|
||||||
if err := keybindings(g); err != nil {
|
if err := keybindings(g); err != nil {
|
||||||
log.Panicln(err)
|
log.Panicln(err)
|
||||||
}
|
}
|
||||||
g.SelBgColor = gocui.ColorGreen
|
|
||||||
g.SelFgColor = gocui.ColorBlack
|
|
||||||
g.Cursor = true
|
|
||||||
|
|
||||||
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
|
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
|
||||||
log.Panicln(err)
|
log.Panicln(err)
|
||||||
|
@ -21,13 +21,17 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
g := gocui.NewGui()
|
g, err := gocui.NewGui(gocui.OutputNormal)
|
||||||
if err := g.Init(); err != nil {
|
if err != nil {
|
||||||
log.Panicln(err)
|
log.Panicln(err)
|
||||||
}
|
}
|
||||||
defer g.Close()
|
defer g.Close()
|
||||||
|
|
||||||
g.SetLayout(layout)
|
g.Highlight = true
|
||||||
|
g.SelFgColor = gocui.ColorRed
|
||||||
|
|
||||||
|
g.SetManagerFunc(layout)
|
||||||
|
|
||||||
if err := initKeybindings(g); err != nil {
|
if err := initKeybindings(g); err != nil {
|
||||||
log.Panicln(err)
|
log.Panicln(err)
|
||||||
}
|
}
|
||||||
@ -42,7 +46,7 @@ func main() {
|
|||||||
|
|
||||||
func layout(g *gocui.Gui) error {
|
func layout(g *gocui.Gui) error {
|
||||||
maxX, _ := g.Size()
|
maxX, _ := g.Size()
|
||||||
v, err := g.SetView("legend", maxX-25, 0, maxX-1, 8)
|
v, err := g.SetView("help", maxX-25, 0, maxX-1, 9)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != gocui.ErrUnknownView {
|
if err != gocui.ErrUnknownView {
|
||||||
return err
|
return err
|
||||||
@ -53,13 +57,17 @@ func layout(g *gocui.Gui) error {
|
|||||||
fmt.Fprintln(v, "← ↑ → ↓: Move View")
|
fmt.Fprintln(v, "← ↑ → ↓: Move View")
|
||||||
fmt.Fprintln(v, "Backspace: Delete View")
|
fmt.Fprintln(v, "Backspace: Delete View")
|
||||||
fmt.Fprintln(v, "t: Set view on top")
|
fmt.Fprintln(v, "t: Set view on top")
|
||||||
|
fmt.Fprintln(v, "b: Set view on bottom")
|
||||||
fmt.Fprintln(v, "^C: Exit")
|
fmt.Fprintln(v, "^C: Exit")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func initKeybindings(g *gocui.Gui) error {
|
func initKeybindings(g *gocui.Gui) error {
|
||||||
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone,
|
||||||
|
func(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
return gocui.ErrQuit
|
||||||
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := g.SetKeybinding("", gocui.KeySpace, gocui.ModNone,
|
if err := g.SetKeybinding("", gocui.KeySpace, gocui.ModNone,
|
||||||
@ -104,16 +112,23 @@ func initKeybindings(g *gocui.Gui) error {
|
|||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := g.SetKeybinding("", 't', gocui.ModNone, ontop); err != nil {
|
if err := g.SetKeybinding("", 't', gocui.ModNone,
|
||||||
|
func(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
_, err := g.SetViewOnTop(views[curView])
|
||||||
|
return err
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := g.SetKeybinding("", 'b', gocui.ModNone,
|
||||||
|
func(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
_, err := g.SetViewOnBottom(views[curView])
|
||||||
|
return err
|
||||||
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func quit(g *gocui.Gui, v *gocui.View) error {
|
|
||||||
return gocui.ErrQuit
|
|
||||||
}
|
|
||||||
|
|
||||||
func newView(g *gocui.Gui) error {
|
func newView(g *gocui.Gui) error {
|
||||||
maxX, maxY := g.Size()
|
maxX, maxY := g.Size()
|
||||||
name := fmt.Sprintf("v%v", idxView)
|
name := fmt.Sprintf("v%v", idxView)
|
||||||
@ -125,18 +140,9 @@ func newView(g *gocui.Gui) error {
|
|||||||
v.Wrap = true
|
v.Wrap = true
|
||||||
fmt.Fprintln(v, strings.Repeat(name+" ", 30))
|
fmt.Fprintln(v, strings.Repeat(name+" ", 30))
|
||||||
}
|
}
|
||||||
if err := g.SetCurrentView(name); err != nil {
|
if _, err := g.SetCurrentView(name); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
v.BgColor = gocui.ColorRed
|
|
||||||
|
|
||||||
if curView >= 0 {
|
|
||||||
cv, err := g.View(views[curView])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
cv.BgColor = g.BgColor
|
|
||||||
}
|
|
||||||
|
|
||||||
views = append(views, name)
|
views = append(views, name)
|
||||||
curView = len(views) - 1
|
curView = len(views) - 1
|
||||||
@ -163,22 +169,9 @@ func nextView(g *gocui.Gui, disableCurrent bool) error {
|
|||||||
next = 0
|
next = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
nv, err := g.View(views[next])
|
if _, err := g.SetCurrentView(views[next]); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := g.SetCurrentView(views[next]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
nv.BgColor = gocui.ColorRed
|
|
||||||
|
|
||||||
if disableCurrent && len(views) > 1 {
|
|
||||||
cv, err := g.View(views[curView])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
cv.BgColor = g.BgColor
|
|
||||||
}
|
|
||||||
|
|
||||||
curView = next
|
curView = next
|
||||||
return nil
|
return nil
|
||||||
@ -195,8 +188,3 @@ func moveView(g *gocui.Gui, v *gocui.View, dx, dy int) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ontop(g *gocui.Gui, v *gocui.View) error {
|
|
||||||
_, err := g.SetViewOnTop(views[curView])
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
87
_examples/flow_layout.go
Normal file
87
_examples/flow_layout.go
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
// Copyright 2014 The gocui Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/jroimartin/gocui"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Label struct {
|
||||||
|
name string
|
||||||
|
w, h int
|
||||||
|
body string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLabel(name string, body string) *Label {
|
||||||
|
lines := strings.Split(body, "\n")
|
||||||
|
|
||||||
|
w := 0
|
||||||
|
for _, l := range lines {
|
||||||
|
if len(l) > w {
|
||||||
|
w = len(l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h := len(lines) + 1
|
||||||
|
w = w + 1
|
||||||
|
|
||||||
|
return &Label{name: name, w: w, h: h, body: body}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Label) Layout(g *gocui.Gui) error {
|
||||||
|
v, err := g.SetView(w.name, 0, 0, w.w, w.h)
|
||||||
|
if err != nil {
|
||||||
|
if err != gocui.ErrUnknownView {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Fprint(v, w.body)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func flowLayout(g *gocui.Gui) error {
|
||||||
|
views := g.Views()
|
||||||
|
x := 0
|
||||||
|
for _, v := range views {
|
||||||
|
w, h := v.Size()
|
||||||
|
_, err := g.SetView(v.Name(), x, 0, x+w+1, h+1)
|
||||||
|
if err != nil && err != gocui.ErrUnknownView {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
x += w + 2
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
g, err := gocui.NewGui(gocui.OutputNormal)
|
||||||
|
if err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
defer g.Close()
|
||||||
|
|
||||||
|
l1 := NewLabel("l1", "This")
|
||||||
|
l2 := NewLabel("l2", "is")
|
||||||
|
l3 := NewLabel("l3", "a")
|
||||||
|
l4 := NewLabel("l4", "flow\nlayout")
|
||||||
|
l5 := NewLabel("l5", "!")
|
||||||
|
fl := gocui.ManagerFunc(flowLayout)
|
||||||
|
g.SetManager(l1, l2, l3, l4, l5, fl)
|
||||||
|
|
||||||
|
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 quit(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
return gocui.ErrQuit
|
||||||
|
}
|
@ -16,7 +16,7 @@ import (
|
|||||||
const NumGoroutines = 10
|
const NumGoroutines = 10
|
||||||
|
|
||||||
var (
|
var (
|
||||||
done = make(chan bool)
|
done = make(chan struct{})
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
|
|
||||||
mu sync.Mutex // protects ctr
|
mu sync.Mutex // protects ctr
|
||||||
@ -24,13 +24,14 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
g := gocui.NewGui()
|
g, err := gocui.NewGui(gocui.OutputNormal)
|
||||||
if err := g.Init(); err != nil {
|
if err != nil {
|
||||||
log.Panicln(err)
|
log.Panicln(err)
|
||||||
}
|
}
|
||||||
defer g.Close()
|
defer g.Close()
|
||||||
|
|
||||||
g.SetLayout(layout)
|
g.SetManagerFunc(layout)
|
||||||
|
|
||||||
if err := keybindings(g); err != nil {
|
if err := keybindings(g); err != nil {
|
||||||
log.Panicln(err)
|
log.Panicln(err)
|
||||||
}
|
}
|
||||||
@ -65,9 +66,7 @@ func keybindings(g *gocui.Gui) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func quit(g *gocui.Gui, v *gocui.View) error {
|
func quit(g *gocui.Gui, v *gocui.View) error {
|
||||||
for i := 0; i < NumGoroutines; i++ {
|
close(done)
|
||||||
done <- true
|
|
||||||
}
|
|
||||||
return gocui.ErrQuit
|
return gocui.ErrQuit
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,7 +83,7 @@ func counter(g *gocui.Gui) {
|
|||||||
ctr++
|
ctr++
|
||||||
mu.Unlock()
|
mu.Unlock()
|
||||||
|
|
||||||
g.Execute(func(g *gocui.Gui) error {
|
g.Update(func(g *gocui.Gui) error {
|
||||||
v, err := g.View("ctr")
|
v, err := g.View("ctr")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -12,13 +12,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
g := gocui.NewGui()
|
g, err := gocui.NewGui(gocui.OutputNormal)
|
||||||
if err := g.Init(); err != nil {
|
if err != nil {
|
||||||
log.Panicln(err)
|
log.Panicln(err)
|
||||||
}
|
}
|
||||||
defer g.Close()
|
defer g.Close()
|
||||||
|
|
||||||
g.SetLayout(layout)
|
g.SetManagerFunc(layout)
|
||||||
|
|
||||||
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
||||||
log.Panicln(err)
|
log.Panicln(err)
|
||||||
|
@ -32,13 +32,13 @@ func quit(g *gocui.Gui, v *gocui.View) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
g := gocui.NewGui()
|
g, err := gocui.NewGui(gocui.OutputNormal)
|
||||||
if err := g.Init(); err != nil {
|
if err != nil {
|
||||||
log.Panicln(err)
|
log.Panicln(err)
|
||||||
}
|
}
|
||||||
defer g.Close()
|
defer g.Close()
|
||||||
|
|
||||||
g.SetLayout(layout)
|
g.SetManagerFunc(layout)
|
||||||
|
|
||||||
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
||||||
log.Panicln(err)
|
log.Panicln(err)
|
||||||
|
@ -12,17 +12,19 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
g := gocui.NewGui()
|
g, err := gocui.NewGui(gocui.OutputNormal)
|
||||||
if err := g.Init(); err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
}
|
}
|
||||||
defer g.Close()
|
defer g.Close()
|
||||||
|
|
||||||
g.SetLayout(layout)
|
g.Cursor = true
|
||||||
|
|
||||||
|
g.SetManagerFunc(layout)
|
||||||
|
|
||||||
if err := initKeybindings(g); err != nil {
|
if err := initKeybindings(g); err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
}
|
}
|
||||||
g.Cursor = true
|
|
||||||
|
|
||||||
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
|
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
@ -32,7 +34,7 @@ func main() {
|
|||||||
func layout(g *gocui.Gui) error {
|
func layout(g *gocui.Gui) error {
|
||||||
maxX, maxY := g.Size()
|
maxX, maxY := g.Size()
|
||||||
|
|
||||||
if v, err := g.SetView("legend", maxX-23, 0, maxX-1, 3); err != nil {
|
if v, err := g.SetView("help", maxX-23, 0, maxX-1, 3); err != nil {
|
||||||
if err != gocui.ErrUnknownView {
|
if err != gocui.ErrUnknownView {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -45,7 +47,7 @@ func layout(g *gocui.Gui) error {
|
|||||||
if err != gocui.ErrUnknownView {
|
if err != gocui.ErrUnknownView {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := g.SetCurrentView("input"); err != nil {
|
if _, err := g.SetCurrentView("input"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
v.Editable = true
|
v.Editable = true
|
||||||
|
@ -12,20 +12,20 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
g := gocui.NewGui()
|
g, err := gocui.NewGui(gocui.OutputNormal)
|
||||||
if err := g.Init(); err != nil {
|
if err != nil {
|
||||||
log.Panicln(err)
|
log.Panicln(err)
|
||||||
}
|
}
|
||||||
defer g.Close()
|
defer g.Close()
|
||||||
|
|
||||||
g.SetLayout(layout)
|
g.Cursor = true
|
||||||
|
g.Mouse = true
|
||||||
|
|
||||||
|
g.SetManagerFunc(layout)
|
||||||
|
|
||||||
if err := keybindings(g); err != nil {
|
if err := keybindings(g); err != nil {
|
||||||
log.Panicln(err)
|
log.Panicln(err)
|
||||||
}
|
}
|
||||||
g.SelBgColor = gocui.ColorGreen
|
|
||||||
g.SelFgColor = gocui.ColorBlack
|
|
||||||
g.Cursor = true
|
|
||||||
g.Mouse = true
|
|
||||||
|
|
||||||
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
|
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
|
||||||
log.Panicln(err)
|
log.Panicln(err)
|
||||||
@ -38,6 +38,8 @@ func layout(g *gocui.Gui) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
v.Highlight = true
|
v.Highlight = true
|
||||||
|
v.SelBgColor = gocui.ColorGreen
|
||||||
|
v.SelFgColor = gocui.ColorBlack
|
||||||
fmt.Fprintln(v, "Button 1 - line 1")
|
fmt.Fprintln(v, "Button 1 - line 1")
|
||||||
fmt.Fprintln(v, "Button 1 - line 2")
|
fmt.Fprintln(v, "Button 1 - line 2")
|
||||||
fmt.Fprintln(v, "Button 1 - line 3")
|
fmt.Fprintln(v, "Button 1 - line 3")
|
||||||
@ -48,6 +50,8 @@ func layout(g *gocui.Gui) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
v.Highlight = true
|
v.Highlight = true
|
||||||
|
v.SelBgColor = gocui.ColorGreen
|
||||||
|
v.SelFgColor = gocui.ColorBlack
|
||||||
fmt.Fprintln(v, "Button 2 - line 1")
|
fmt.Fprintln(v, "Button 2 - line 1")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -76,7 +80,7 @@ func showMsg(g *gocui.Gui, v *gocui.View) error {
|
|||||||
var l string
|
var l string
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if err := g.SetCurrentView(v.Name()); err != nil {
|
if _, err := g.SetCurrentView(v.Name()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,13 +12,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
g := gocui.NewGui()
|
g, err := gocui.NewGui(gocui.OutputNormal)
|
||||||
if err := g.Init(); err != nil {
|
if err != nil {
|
||||||
log.Panicln(err)
|
log.Panicln(err)
|
||||||
}
|
}
|
||||||
defer g.Close()
|
defer g.Close()
|
||||||
|
|
||||||
g.SetLayout(layout)
|
g.SetManagerFunc(layout)
|
||||||
|
|
||||||
if err := keybindings(g); err != nil {
|
if err := keybindings(g); err != nil {
|
||||||
log.Panicln(err)
|
log.Panicln(err)
|
||||||
}
|
}
|
||||||
|
@ -56,13 +56,14 @@ func quit(g *gocui.Gui, v *gocui.View) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
g := gocui.NewGui()
|
g, err := gocui.NewGui(gocui.OutputNormal)
|
||||||
if err := g.Init(); err != nil {
|
if err != nil {
|
||||||
log.Panicln(err)
|
log.Panicln(err)
|
||||||
}
|
}
|
||||||
defer g.Close()
|
defer g.Close()
|
||||||
|
|
||||||
g.SetLayout(layout)
|
g.SetManagerFunc(layout)
|
||||||
|
|
||||||
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
||||||
log.Panicln(err)
|
log.Panicln(err)
|
||||||
}
|
}
|
||||||
|
45
_examples/size.go
Normal file
45
_examples/size.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
// Copyright 2014 The gocui Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/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()
|
||||||
|
v, err := g.SetView("size", maxX/2-7, maxY/2, maxX/2+7, maxY/2+2)
|
||||||
|
if err != nil && err != gocui.ErrUnknownView {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.Clear()
|
||||||
|
fmt.Fprintf(v, "%d, %d", maxX, maxY)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func quit(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
return gocui.ErrQuit
|
||||||
|
}
|
@ -15,17 +15,19 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
g := gocui.NewGui()
|
g, err := gocui.NewGui(gocui.OutputNormal)
|
||||||
if err := g.Init(); err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
}
|
}
|
||||||
defer g.Close()
|
defer g.Close()
|
||||||
|
|
||||||
g.SetLayout(layout)
|
g.Cursor = true
|
||||||
|
|
||||||
|
g.SetManagerFunc(layout)
|
||||||
|
|
||||||
if err := initKeybindings(g); err != nil {
|
if err := initKeybindings(g); err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
}
|
}
|
||||||
g.Cursor = true
|
|
||||||
|
|
||||||
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
|
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
@ -35,13 +37,13 @@ func main() {
|
|||||||
func layout(g *gocui.Gui) error {
|
func layout(g *gocui.Gui) error {
|
||||||
maxX, _ := g.Size()
|
maxX, _ := g.Size()
|
||||||
|
|
||||||
if v, err := g.SetView("legend", maxX-23, 0, maxX-1, 5); err != nil {
|
if v, err := g.SetView("help", maxX-23, 0, maxX-1, 5); err != nil {
|
||||||
if err != gocui.ErrUnknownView {
|
if err != gocui.ErrUnknownView {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Fprintln(v, "KEYBINDINGS")
|
fmt.Fprintln(v, "KEYBINDINGS")
|
||||||
fmt.Fprintln(v, "↑ ↓: Seek input")
|
fmt.Fprintln(v, "↑ ↓: Seek input")
|
||||||
fmt.Fprintln(v, "A: Enable autoscroll")
|
fmt.Fprintln(v, "a: Enable autoscroll")
|
||||||
fmt.Fprintln(v, "^C: Exit")
|
fmt.Fprintln(v, "^C: Exit")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,7 +51,7 @@ func layout(g *gocui.Gui) error {
|
|||||||
if err != gocui.ErrUnknownView {
|
if err != gocui.ErrUnknownView {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := g.SetCurrentView("stdin"); err != nil {
|
if _, err := g.SetCurrentView("stdin"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
dumper := hex.Dumper(v)
|
dumper := hex.Dumper(v)
|
||||||
|
@ -11,13 +11,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
g := gocui.NewGui()
|
g, err := gocui.NewGui(gocui.OutputNormal)
|
||||||
if err := g.Init(); err != nil {
|
if err != nil {
|
||||||
log.Panicln(err)
|
log.Panicln(err)
|
||||||
}
|
}
|
||||||
defer g.Close()
|
defer g.Close()
|
||||||
|
|
||||||
g.SetLayout(layout)
|
g.SetManagerFunc(layout)
|
||||||
|
|
||||||
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
||||||
log.Panicln(err)
|
log.Panicln(err)
|
||||||
}
|
}
|
||||||
|
179
_examples/widgets.go
Normal file
179
_examples/widgets.go
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
// Copyright 2014 The gocui Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/jroimartin/gocui"
|
||||||
|
)
|
||||||
|
|
||||||
|
const delta = 0.2
|
||||||
|
|
||||||
|
type HelpWidget struct {
|
||||||
|
name string
|
||||||
|
x, y int
|
||||||
|
w, h int
|
||||||
|
body string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHelpWidget(name string, x, y int, body string) *HelpWidget {
|
||||||
|
lines := strings.Split(body, "\n")
|
||||||
|
|
||||||
|
w := 0
|
||||||
|
for _, l := range lines {
|
||||||
|
if len(l) > w {
|
||||||
|
w = len(l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h := len(lines) + 1
|
||||||
|
w = w + 1
|
||||||
|
|
||||||
|
return &HelpWidget{name: name, x: x, y: y, w: w, h: h, body: body}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *HelpWidget) Layout(g *gocui.Gui) error {
|
||||||
|
v, err := g.SetView(w.name, w.x, w.y, w.x+w.w, w.y+w.h)
|
||||||
|
if err != nil {
|
||||||
|
if err != gocui.ErrUnknownView {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Fprint(v, w.body)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type StatusbarWidget struct {
|
||||||
|
name string
|
||||||
|
x, y int
|
||||||
|
w int
|
||||||
|
val float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStatusbarWidget(name string, x, y, w int) *StatusbarWidget {
|
||||||
|
return &StatusbarWidget{name: name, x: x, y: y, w: w}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *StatusbarWidget) SetVal(val float64) error {
|
||||||
|
if val < 0 || val > 1 {
|
||||||
|
return errors.New("invalid value")
|
||||||
|
}
|
||||||
|
w.val = val
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *StatusbarWidget) Val() float64 {
|
||||||
|
return w.val
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *StatusbarWidget) Layout(g *gocui.Gui) error {
|
||||||
|
v, err := g.SetView(w.name, w.x, w.y, w.x+w.w, w.y+2)
|
||||||
|
if err != nil && err != gocui.ErrUnknownView {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.Clear()
|
||||||
|
|
||||||
|
rep := int(w.val * float64(w.w-1))
|
||||||
|
fmt.Fprint(v, strings.Repeat("▒", rep))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ButtonWidget struct {
|
||||||
|
name string
|
||||||
|
x, y int
|
||||||
|
w int
|
||||||
|
label string
|
||||||
|
handler func(g *gocui.Gui, v *gocui.View) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewButtonWidget(name string, x, y int, label string, handler func(g *gocui.Gui, v *gocui.View) error) *ButtonWidget {
|
||||||
|
return &ButtonWidget{name: name, x: x, y: y, w: len(label) + 1, label: label, handler: handler}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *ButtonWidget) Layout(g *gocui.Gui) error {
|
||||||
|
v, err := g.SetView(w.name, w.x, w.y, w.x+w.w, w.y+2)
|
||||||
|
if err != nil {
|
||||||
|
if err != gocui.ErrUnknownView {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := g.SetCurrentView(w.name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := g.SetKeybinding(w.name, gocui.KeyEnter, gocui.ModNone, w.handler); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Fprint(v, w.label)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
g, err := gocui.NewGui(gocui.OutputNormal)
|
||||||
|
if err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
defer g.Close()
|
||||||
|
|
||||||
|
g.Highlight = true
|
||||||
|
g.SelFgColor = gocui.ColorRed
|
||||||
|
|
||||||
|
help := NewHelpWidget("help", 1, 1, helpText)
|
||||||
|
status := NewStatusbarWidget("status", 1, 7, 50)
|
||||||
|
butdown := NewButtonWidget("butdown", 52, 7, "DOWN", statusDown(status))
|
||||||
|
butup := NewButtonWidget("butup", 58, 7, "UP", statusUp(status))
|
||||||
|
g.SetManager(help, status, butdown, butup)
|
||||||
|
|
||||||
|
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
if err := g.SetKeybinding("", gocui.KeyTab, gocui.ModNone, toggleButton); err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func quit(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
return gocui.ErrQuit
|
||||||
|
}
|
||||||
|
|
||||||
|
func toggleButton(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
nextview := "butdown"
|
||||||
|
if v != nil && v.Name() == "butdown" {
|
||||||
|
nextview = "butup"
|
||||||
|
}
|
||||||
|
_, err := g.SetCurrentView(nextview)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func statusUp(status *StatusbarWidget) func(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
return func(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
return statusSet(status, delta)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func statusDown(status *StatusbarWidget) func(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
return func(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
return statusSet(status, -delta)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func statusSet(sw *StatusbarWidget, inc float64) error {
|
||||||
|
val := sw.Val() + inc
|
||||||
|
if val < 0 || val > 1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return sw.SetVal(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
const helpText = `KEYBINDINGS
|
||||||
|
Tab: Move between buttons
|
||||||
|
Enter: Push button
|
||||||
|
^C: Exit`
|
@ -32,13 +32,14 @@ func quit(g *gocui.Gui, v *gocui.View) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
g := gocui.NewGui()
|
g, err := gocui.NewGui(gocui.OutputNormal)
|
||||||
if err := g.Init(); err != nil {
|
if err != nil {
|
||||||
log.Panicln(err)
|
log.Panicln(err)
|
||||||
}
|
}
|
||||||
defer g.Close()
|
defer g.Close()
|
||||||
|
|
||||||
g.SetLayout(layout)
|
g.SetManagerFunc(layout)
|
||||||
|
|
||||||
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
||||||
log.Panicln(err)
|
log.Panicln(err)
|
||||||
}
|
}
|
||||||
|
32
doc.go
32
doc.go
@ -7,28 +7,29 @@ Package gocui allows to create console user interfaces.
|
|||||||
|
|
||||||
Create a new GUI:
|
Create a new GUI:
|
||||||
|
|
||||||
g := gocui.NewGui()
|
g, err := gocui.NewGui(gocui.OutputNormal)
|
||||||
if err := g.Init(); err != nil {
|
if err != nil {
|
||||||
// handle error
|
// handle error
|
||||||
}
|
}
|
||||||
defer g.Close()
|
defer g.Close()
|
||||||
|
|
||||||
// Set layout and key bindings
|
// Set GUI managers and key bindings
|
||||||
// ...
|
// ...
|
||||||
|
|
||||||
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
|
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
|
||||||
// handle error
|
// handle error
|
||||||
}
|
}
|
||||||
|
|
||||||
Set the layout function:
|
Set GUI managers:
|
||||||
|
|
||||||
g.SetLayout(fcn)
|
g.SetManager(mgr1, mgr2)
|
||||||
|
|
||||||
On each iteration of the GUI's main loop, the "layout function" is executed.
|
Managers are in charge of GUI's layout and can be used to build widgets. On
|
||||||
These layout functions can be used to set-up and update the application's main
|
each iteration of the GUI's main loop, the Layout function of each configured
|
||||||
views, being possible to freely switch between them. Also, it is important to
|
manager is executed. Managers are used to set-up and update the application's
|
||||||
mention that a main loop iteration is executed on each reported event
|
main views, being possible to freely change them during execution. Also, it is
|
||||||
(key-press, mouse event, window resize, etc).
|
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
|
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
|
io.ReadWriter interface, so you can just write to them if you want to modify
|
||||||
@ -68,11 +69,12 @@ Mouse events are handled like any other keybinding:
|
|||||||
}
|
}
|
||||||
|
|
||||||
IMPORTANT: Views can only be created, destroyed or updated in three ways: from
|
IMPORTANT: Views can only be created, destroyed or updated in three ways: from
|
||||||
layout functions, from keybinding callbacks or via *Gui.Execute(). The reason
|
the Layout function within managers, from keybinding callbacks or via
|
||||||
for this is that it allows gocui to be conccurent-safe. So, if you want to
|
*Gui.Update(). The reason for this is that it allows gocui to be
|
||||||
update your GUI from a goroutine, you must use *Gui.Execute(). For example:
|
concurrent-safe. So, if you want to update your GUI from a goroutine, you must
|
||||||
|
use *Gui.Update(). For example:
|
||||||
|
|
||||||
g.Execute(func(g *gocui.Gui) error {
|
g.Update(func(g *gocui.Gui) error {
|
||||||
v, err := g.View("viewname")
|
v, err := g.View("viewname")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// handle error
|
// handle error
|
||||||
@ -83,7 +85,7 @@ update your GUI from a goroutine, you must use *Gui.Execute(). For example:
|
|||||||
})
|
})
|
||||||
|
|
||||||
By default, gocui provides a basic edition mode. This mode can be extended
|
By default, gocui provides a basic edition mode. This mode can be extended
|
||||||
and customized creating a new Editor and assigning it to *Gui.Editor:
|
and customized creating a new Editor and assigning it to *View.Editor:
|
||||||
|
|
||||||
type Editor interface {
|
type Editor interface {
|
||||||
Edit(v *View, key Key, ch rune, mod Modifier)
|
Edit(v *View, key Key, ch rune, mod Modifier)
|
||||||
|
53
edit.go
53
edit.go
@ -108,17 +108,10 @@ func (v *View) EditDelete(back bool) {
|
|||||||
// EditNewLine inserts a new line under the cursor.
|
// EditNewLine inserts a new line under the cursor.
|
||||||
func (v *View) EditNewLine() {
|
func (v *View) EditNewLine() {
|
||||||
v.breakLine(v.cx, v.cy)
|
v.breakLine(v.cx, v.cy)
|
||||||
|
|
||||||
y := v.oy + v.cy
|
|
||||||
if y >= len(v.viewLines) || (y >= 0 && y < len(v.viewLines) &&
|
|
||||||
!(v.Wrap && v.cx == 0 && v.viewLines[y].linesX > 0)) {
|
|
||||||
// new line at the end of the buffer or
|
|
||||||
// cursor is not at the beginning of a wrapped line
|
|
||||||
v.ox = 0
|
v.ox = 0
|
||||||
v.cx = 0
|
v.cx = 0
|
||||||
v.MoveCursor(0, 1, true)
|
v.MoveCursor(0, 1, true)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// MoveCursor moves the cursor taking into account the width of the line/view,
|
// MoveCursor moves the cursor taking into account the width of the line/view,
|
||||||
// displacing the origin if necessary.
|
// displacing the origin if necessary.
|
||||||
@ -155,11 +148,13 @@ func (v *View) MoveCursor(dx, dy int, writeMode bool) {
|
|||||||
// adjust cursor's x position and view's x origin
|
// adjust cursor's x position and view's x origin
|
||||||
if x > curLineWidth { // move to next line
|
if x > curLineWidth { // move to next line
|
||||||
if dx > 0 { // horizontal movement
|
if dx > 0 { // horizontal movement
|
||||||
|
cy++
|
||||||
|
if writeMode || v.oy+cy < len(v.viewLines) {
|
||||||
if !v.Wrap {
|
if !v.Wrap {
|
||||||
v.ox = 0
|
v.ox = 0
|
||||||
}
|
}
|
||||||
v.cx = 0
|
v.cx = 0
|
||||||
cy++
|
}
|
||||||
} else { // vertical movement
|
} else { // vertical movement
|
||||||
if curLineWidth > 0 { // move cursor to the EOL
|
if curLineWidth > 0 { // move cursor to the EOL
|
||||||
if v.Wrap {
|
if v.Wrap {
|
||||||
@ -177,16 +172,20 @@ func (v *View) MoveCursor(dx, dy int, writeMode bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if writeMode || v.oy+cy < len(v.viewLines) {
|
||||||
if !v.Wrap {
|
if !v.Wrap {
|
||||||
v.ox = 0
|
v.ox = 0
|
||||||
}
|
}
|
||||||
v.cx = 0
|
v.cx = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else if cx < 0 {
|
} else if cx < 0 {
|
||||||
if !v.Wrap && v.ox > 0 { // move origin to the left
|
if !v.Wrap && v.ox > 0 { // move origin to the left
|
||||||
v.ox--
|
v.ox += cx
|
||||||
|
v.cx = 0
|
||||||
} else { // move to previous line
|
} else { // move to previous line
|
||||||
|
cy--
|
||||||
if prevLineWidth > 0 {
|
if prevLineWidth > 0 {
|
||||||
if !v.Wrap { // set origin so the EOL is visible
|
if !v.Wrap { // set origin so the EOL is visible
|
||||||
nox := prevLineWidth - maxX + 1
|
nox := prevLineWidth - maxX + 1
|
||||||
@ -203,14 +202,14 @@ func (v *View) MoveCursor(dx, dy int, writeMode bool) {
|
|||||||
}
|
}
|
||||||
v.cx = 0
|
v.cx = 0
|
||||||
}
|
}
|
||||||
cy--
|
|
||||||
}
|
}
|
||||||
} else { // stay on the same line
|
} else { // stay on the same line
|
||||||
if v.Wrap {
|
if v.Wrap {
|
||||||
v.cx = cx
|
v.cx = cx
|
||||||
} else {
|
} else {
|
||||||
if cx >= maxX {
|
if cx >= maxX {
|
||||||
v.ox++
|
v.ox += cx - maxX + 1
|
||||||
|
v.cx = maxX
|
||||||
} else {
|
} else {
|
||||||
v.cx = cx
|
v.cx = cx
|
||||||
}
|
}
|
||||||
@ -218,16 +217,18 @@ func (v *View) MoveCursor(dx, dy int, writeMode bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// adjust cursor's y position and view's y origin
|
// adjust cursor's y position and view's y origin
|
||||||
if cy >= maxY {
|
if cy < 0 {
|
||||||
v.oy++
|
|
||||||
} else if cy < 0 {
|
|
||||||
if v.oy > 0 {
|
if v.oy > 0 {
|
||||||
v.oy--
|
v.oy--
|
||||||
}
|
}
|
||||||
|
} else if writeMode || v.oy+cy < len(v.viewLines) {
|
||||||
|
if cy >= maxY {
|
||||||
|
v.oy++
|
||||||
} else {
|
} else {
|
||||||
v.cy = cy
|
v.cy = cy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// writeRune writes a rune into the view's internal buffer, at the
|
// writeRune writes a rune into the view's internal buffer, at the
|
||||||
// position corresponding to the point (x, y). The length of the internal
|
// position corresponding to the point (x, y). The length of the internal
|
||||||
@ -251,22 +252,24 @@ func (v *View) writeRune(x, y int, ch rune) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
olen := len(v.lines[y])
|
olen := len(v.lines[y])
|
||||||
if x >= len(v.lines[y]) {
|
|
||||||
s := make([]cell, x-len(v.lines[y])+1)
|
|
||||||
v.lines[y] = append(v.lines[y], s...)
|
|
||||||
}
|
|
||||||
|
|
||||||
c := cell{
|
var s []cell
|
||||||
fgColor: v.FgColor,
|
if x >= len(v.lines[y]) {
|
||||||
bgColor: v.BgColor,
|
s = make([]cell, x-len(v.lines[y])+1)
|
||||||
|
} else if !v.Overwrite {
|
||||||
|
s = make([]cell, 1)
|
||||||
}
|
}
|
||||||
|
v.lines[y] = append(v.lines[y], s...)
|
||||||
|
|
||||||
if !v.Overwrite || (v.Overwrite && x >= olen-1) {
|
if !v.Overwrite || (v.Overwrite && x >= olen-1) {
|
||||||
c.chr = '\x00'
|
|
||||||
v.lines[y] = append(v.lines[y], c)
|
|
||||||
copy(v.lines[y][x+1:], v.lines[y][x:])
|
copy(v.lines[y][x+1:], v.lines[y][x:])
|
||||||
}
|
}
|
||||||
c.chr = ch
|
v.lines[y][x] = cell{
|
||||||
v.lines[y][x] = c
|
fgColor: v.FgColor,
|
||||||
|
bgColor: v.BgColor,
|
||||||
|
chr: ch,
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
145
escape.go
145
escape.go
@ -14,6 +14,7 @@ type escapeInterpreter struct {
|
|||||||
curch rune
|
curch rune
|
||||||
csiParam []string
|
csiParam []string
|
||||||
curFgColor, curBgColor Attribute
|
curFgColor, curBgColor Attribute
|
||||||
|
mode OutputMode
|
||||||
}
|
}
|
||||||
|
|
||||||
type escapeState int
|
type escapeState int
|
||||||
@ -27,7 +28,6 @@ const (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
errNotCSI = errors.New("Not a CSI escape sequence")
|
errNotCSI = errors.New("Not a CSI escape sequence")
|
||||||
errCSINotANumber = errors.New("CSI escape sequence was expecting a number or a ;")
|
|
||||||
errCSIParseError = errors.New("CSI escape sequence parsing error")
|
errCSIParseError = errors.New("CSI escape sequence parsing error")
|
||||||
errCSITooLong = errors.New("CSI escape sequence is too long")
|
errCSITooLong = errors.New("CSI escape sequence is too long")
|
||||||
)
|
)
|
||||||
@ -54,11 +54,12 @@ func (ei *escapeInterpreter) runes() []rune {
|
|||||||
|
|
||||||
// newEscapeInterpreter returns an escapeInterpreter that will be able to parse
|
// newEscapeInterpreter returns an escapeInterpreter that will be able to parse
|
||||||
// terminal escape sequences.
|
// terminal escape sequences.
|
||||||
func newEscapeInterpreter() *escapeInterpreter {
|
func newEscapeInterpreter(mode OutputMode) *escapeInterpreter {
|
||||||
ei := &escapeInterpreter{
|
ei := &escapeInterpreter{
|
||||||
state: stateNone,
|
state: stateNone,
|
||||||
curFgColor: ColorDefault,
|
curFgColor: ColorDefault,
|
||||||
curBgColor: ColorDefault,
|
curBgColor: ColorDefault,
|
||||||
|
mode: mode,
|
||||||
}
|
}
|
||||||
return ei
|
return ei
|
||||||
}
|
}
|
||||||
@ -71,41 +72,20 @@ func (ei *escapeInterpreter) reset() {
|
|||||||
ei.csiParam = nil
|
ei.csiParam = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// paramToColor returns an attribute given a terminfo coloring.
|
|
||||||
func paramToColor(p int) Attribute {
|
|
||||||
switch p {
|
|
||||||
case 0:
|
|
||||||
return ColorBlack
|
|
||||||
case 1:
|
|
||||||
return ColorRed
|
|
||||||
case 2:
|
|
||||||
return ColorGreen
|
|
||||||
case 3:
|
|
||||||
return ColorYellow
|
|
||||||
case 4:
|
|
||||||
return ColorBlue
|
|
||||||
case 5:
|
|
||||||
return ColorMagenta
|
|
||||||
case 6:
|
|
||||||
return ColorCyan
|
|
||||||
case 7:
|
|
||||||
return ColorWhite
|
|
||||||
}
|
|
||||||
return ColorDefault
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseOne parses a rune. If isEscape is true, it means that the rune is part
|
// parseOne parses a rune. If isEscape is true, it means that the rune is part
|
||||||
// of an escape sequence, and as such should not be printed verbatim. Otherwise,
|
// of an escape sequence, and as such should not be printed verbatim. Otherwise,
|
||||||
// it's not an escape sequence.
|
// it's not an escape sequence.
|
||||||
func (ei *escapeInterpreter) parseOne(ch rune) (isEscape bool, err error) {
|
func (ei *escapeInterpreter) parseOne(ch rune) (isEscape bool, err error) {
|
||||||
// Sanity checks to make sure we're not parsing something totally bogus.
|
// Sanity checks
|
||||||
if len(ei.csiParam) > 20 {
|
if len(ei.csiParam) > 20 {
|
||||||
return false, errCSITooLong
|
return false, errCSITooLong
|
||||||
}
|
}
|
||||||
if len(ei.csiParam) > 0 && len(ei.csiParam[len(ei.csiParam)-1]) > 255 {
|
if len(ei.csiParam) > 0 && len(ei.csiParam[len(ei.csiParam)-1]) > 255 {
|
||||||
return false, errCSITooLong
|
return false, errCSITooLong
|
||||||
}
|
}
|
||||||
|
|
||||||
ei.curch = ch
|
ei.curch = ch
|
||||||
|
|
||||||
switch ei.state {
|
switch ei.state {
|
||||||
case stateNone:
|
case stateNone:
|
||||||
if ch == 0x1b {
|
if ch == 0x1b {
|
||||||
@ -120,12 +100,16 @@ func (ei *escapeInterpreter) parseOne(ch rune) (isEscape bool, err error) {
|
|||||||
}
|
}
|
||||||
return false, errNotCSI
|
return false, errNotCSI
|
||||||
case stateCSI:
|
case stateCSI:
|
||||||
if ch >= '0' && ch <= '9' {
|
switch {
|
||||||
ei.state = stateParams
|
case ch >= '0' && ch <= '9':
|
||||||
ei.csiParam = append(ei.csiParam, string(ch))
|
ei.csiParam = append(ei.csiParam, "")
|
||||||
return true, nil
|
case ch == 'm':
|
||||||
|
ei.csiParam = append(ei.csiParam, "0")
|
||||||
|
default:
|
||||||
|
return false, errCSIParseError
|
||||||
}
|
}
|
||||||
return false, errCSINotANumber
|
ei.state = stateParams
|
||||||
|
fallthrough
|
||||||
case stateParams:
|
case stateParams:
|
||||||
switch {
|
switch {
|
||||||
case ch >= '0' && ch <= '9':
|
case ch >= '0' && ch <= '9':
|
||||||
@ -135,34 +119,111 @@ func (ei *escapeInterpreter) parseOne(ch rune) (isEscape bool, err error) {
|
|||||||
ei.csiParam = append(ei.csiParam, "")
|
ei.csiParam = append(ei.csiParam, "")
|
||||||
return true, nil
|
return true, nil
|
||||||
case ch == 'm':
|
case ch == 'm':
|
||||||
if len(ei.csiParam) < 1 {
|
var err error
|
||||||
return false, errCSIParseError
|
switch ei.mode {
|
||||||
|
case OutputNormal:
|
||||||
|
err = ei.outputNormal()
|
||||||
|
case Output256:
|
||||||
|
err = ei.output256()
|
||||||
}
|
}
|
||||||
for _, param := range ei.csiParam {
|
|
||||||
p, err := strconv.Atoi(param)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, errCSIParseError
|
return false, errCSIParseError
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ei.state = stateNone
|
||||||
|
ei.csiParam = nil
|
||||||
|
return true, nil
|
||||||
|
default:
|
||||||
|
return false, errCSIParseError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// outputNormal provides 8 different colors:
|
||||||
|
// black, red, green, yellow, blue, magenta, cyan, white
|
||||||
|
func (ei *escapeInterpreter) outputNormal() error {
|
||||||
|
for _, param := range ei.csiParam {
|
||||||
|
p, err := strconv.Atoi(param)
|
||||||
|
if err != nil {
|
||||||
|
return errCSIParseError
|
||||||
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case p >= 30 && p <= 37:
|
case p >= 30 && p <= 37:
|
||||||
ei.curFgColor = paramToColor(p - 30)
|
ei.curFgColor = Attribute(p - 30 + 1)
|
||||||
|
case p == 39:
|
||||||
|
ei.curFgColor = ColorDefault
|
||||||
case p >= 40 && p <= 47:
|
case p >= 40 && p <= 47:
|
||||||
ei.curBgColor = paramToColor(p - 40)
|
ei.curBgColor = Attribute(p - 40 + 1)
|
||||||
|
case p == 49:
|
||||||
|
ei.curBgColor = ColorDefault
|
||||||
case p == 1:
|
case p == 1:
|
||||||
ei.curFgColor |= AttrBold
|
ei.curFgColor |= AttrBold
|
||||||
case p == 4:
|
case p == 4:
|
||||||
ei.curFgColor |= AttrUnderline
|
ei.curFgColor |= AttrUnderline
|
||||||
case p == 7:
|
case p == 7:
|
||||||
ei.curFgColor |= AttrReverse
|
ei.curFgColor |= AttrReverse
|
||||||
case p == 0 || p == 39:
|
case p == 0:
|
||||||
ei.curFgColor = ColorDefault
|
ei.curFgColor = ColorDefault
|
||||||
ei.curBgColor = ColorDefault
|
ei.curBgColor = ColorDefault
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ei.state = stateNone
|
|
||||||
ei.csiParam = nil
|
return nil
|
||||||
return true, nil
|
}
|
||||||
|
|
||||||
|
// output256 allows you to leverage the 256-colors terminal mode:
|
||||||
|
// 0x01 - 0x08: the 8 colors as in OutputNormal
|
||||||
|
// 0x09 - 0x10: Color* | AttrBold
|
||||||
|
// 0x11 - 0xe8: 216 different colors
|
||||||
|
// 0xe9 - 0x1ff: 24 different shades of grey
|
||||||
|
func (ei *escapeInterpreter) output256() error {
|
||||||
|
if len(ei.csiParam) < 3 {
|
||||||
|
return ei.outputNormal()
|
||||||
|
}
|
||||||
|
|
||||||
|
mode, err := strconv.Atoi(ei.csiParam[1])
|
||||||
|
if err != nil {
|
||||||
|
return errCSIParseError
|
||||||
|
}
|
||||||
|
if mode != 5 {
|
||||||
|
return ei.outputNormal()
|
||||||
|
}
|
||||||
|
|
||||||
|
fgbg, err := strconv.Atoi(ei.csiParam[0])
|
||||||
|
if err != nil {
|
||||||
|
return errCSIParseError
|
||||||
|
}
|
||||||
|
color, err := strconv.Atoi(ei.csiParam[2])
|
||||||
|
if err != nil {
|
||||||
|
return errCSIParseError
|
||||||
|
}
|
||||||
|
|
||||||
|
switch fgbg {
|
||||||
|
case 38:
|
||||||
|
ei.curFgColor = Attribute(color + 1)
|
||||||
|
|
||||||
|
for _, param := range ei.csiParam[3:] {
|
||||||
|
p, err := strconv.Atoi(param)
|
||||||
|
if err != nil {
|
||||||
|
return errCSIParseError
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case p == 1:
|
||||||
|
ei.curFgColor |= AttrBold
|
||||||
|
case p == 4:
|
||||||
|
ei.curFgColor |= AttrUnderline
|
||||||
|
case p == 7:
|
||||||
|
ei.curFgColor |= AttrReverse
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false, nil
|
case 48:
|
||||||
|
ei.curBgColor = Attribute(color + 1)
|
||||||
|
default:
|
||||||
|
return errCSIParseError
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
5
go.mod
Normal file
5
go.mod
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
module github.com/jroimartin/gocui
|
||||||
|
|
||||||
|
go 1.16
|
||||||
|
|
||||||
|
require github.com/nsf/termbox-go v1.1.1
|
4
go.sum
Normal file
4
go.sum
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||||
|
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||||
|
github.com/nsf/termbox-go v1.1.1 h1:nksUPLCb73Q++DwbYUBEglYBRPZyoXJdrj5L+TkjyZY=
|
||||||
|
github.com/nsf/termbox-go v1.1.1/go.mod h1:T0cTdVuOwf7pHQNtfhnEbzHbcNyCEcVU4YPpouCbVxo=
|
370
gui.go
370
gui.go
@ -10,14 +10,6 @@ import (
|
|||||||
"github.com/nsf/termbox-go"
|
"github.com/nsf/termbox-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Handler represents a handler that can be used to update or modify the GUI.
|
|
||||||
type Handler func(*Gui) error
|
|
||||||
|
|
||||||
// userEvent represents an event triggered by the user.
|
|
||||||
type userEvent struct {
|
|
||||||
h Handler
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// ErrQuit is used to decide if the MainLoop finished successfully.
|
// ErrQuit is used to decide if the MainLoop finished successfully.
|
||||||
ErrQuit = errors.New("quit")
|
ErrQuit = errors.New("quit")
|
||||||
@ -26,6 +18,17 @@ var (
|
|||||||
ErrUnknownView = errors.New("unknown view")
|
ErrUnknownView = errors.New("unknown view")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// OutputMode represents the terminal's output mode (8 or 256 colors).
|
||||||
|
type OutputMode termbox.OutputMode
|
||||||
|
|
||||||
|
const (
|
||||||
|
// OutputNormal provides 8-colors terminal mode.
|
||||||
|
OutputNormal = OutputMode(termbox.OutputNormal)
|
||||||
|
|
||||||
|
// Output256 provides 256-colors terminal mode.
|
||||||
|
Output256 = OutputMode(termbox.Output256)
|
||||||
|
)
|
||||||
|
|
||||||
// Gui represents the whole User Interface, including the views, layouts
|
// Gui represents the whole User Interface, including the views, layouts
|
||||||
// and keybindings.
|
// and keybindings.
|
||||||
type Gui struct {
|
type Gui struct {
|
||||||
@ -33,18 +36,23 @@ type Gui struct {
|
|||||||
userEvents chan userEvent
|
userEvents chan userEvent
|
||||||
views []*View
|
views []*View
|
||||||
currentView *View
|
currentView *View
|
||||||
layout Handler
|
managers []Manager
|
||||||
keybindings []*keybinding
|
keybindings []*keybinding
|
||||||
maxX, maxY int
|
maxX, maxY int
|
||||||
|
outputMode OutputMode
|
||||||
|
|
||||||
// BgColor and FgColor allow to configure the background and foreground
|
// BgColor and FgColor allow to configure the background and foreground
|
||||||
// colors of the GUI.
|
// colors of the GUI.
|
||||||
BgColor, FgColor Attribute
|
BgColor, FgColor Attribute
|
||||||
|
|
||||||
// SelBgColor and SelFgColor are used to configure the background and
|
// SelBgColor and SelFgColor allow to configure the background and
|
||||||
// foreground colors of the selected line, when it is highlighted.
|
// foreground colors of the frame of the current view.
|
||||||
SelBgColor, SelFgColor Attribute
|
SelBgColor, SelFgColor Attribute
|
||||||
|
|
||||||
|
// If Highlight is true, Sel{Bg,Fg}Colors will be used to draw the
|
||||||
|
// frame of the current view.
|
||||||
|
Highlight bool
|
||||||
|
|
||||||
// If Cursor is true then the cursor is enabled.
|
// If Cursor is true then the cursor is enabled.
|
||||||
Cursor bool
|
Cursor bool
|
||||||
|
|
||||||
@ -55,30 +63,31 @@ type Gui struct {
|
|||||||
// match any known sequence, ESC means KeyEsc.
|
// match any known sequence, ESC means KeyEsc.
|
||||||
InputEsc bool
|
InputEsc bool
|
||||||
|
|
||||||
// Editor allows to define the editor that manages the edition mode,
|
// If ASCII is true then use ASCII instead of unicode to draw the
|
||||||
// including keybindings or cursor behaviour. DefaultEditor is used by
|
// interface. Using ASCII is more portable.
|
||||||
// default.
|
ASCII bool
|
||||||
Editor Editor
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGui returns a new Gui object.
|
// NewGui returns a new Gui object with a given output mode.
|
||||||
func NewGui() *Gui {
|
func NewGui(mode OutputMode) (*Gui, error) {
|
||||||
return &Gui{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init initializes the library. This function must be called before
|
|
||||||
// any other functions.
|
|
||||||
func (g *Gui) Init() error {
|
|
||||||
if err := termbox.Init(); err != nil {
|
if err := termbox.Init(); err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
g := &Gui{}
|
||||||
|
|
||||||
|
g.outputMode = mode
|
||||||
|
termbox.SetOutputMode(termbox.OutputMode(mode))
|
||||||
|
|
||||||
g.tbEvents = make(chan termbox.Event, 20)
|
g.tbEvents = make(chan termbox.Event, 20)
|
||||||
g.userEvents = make(chan userEvent, 20)
|
g.userEvents = make(chan userEvent, 20)
|
||||||
|
|
||||||
g.maxX, g.maxY = termbox.Size()
|
g.maxX, g.maxY = termbox.Size()
|
||||||
g.BgColor = ColorBlack
|
|
||||||
g.FgColor = ColorWhite
|
g.BgColor, g.FgColor = ColorDefault, ColorDefault
|
||||||
g.Editor = DefaultEditor
|
g.SelBgColor, g.SelFgColor = ColorDefault, ColorDefault
|
||||||
return nil
|
|
||||||
|
return g, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close finalizes the library. It should be called after a successful
|
// Close finalizes the library. It should be called after a successful
|
||||||
@ -94,12 +103,12 @@ func (g *Gui) Size() (x, y int) {
|
|||||||
|
|
||||||
// SetRune writes a rune at the given point, relative to the top-left
|
// SetRune writes a rune at the given point, relative to the top-left
|
||||||
// corner of the terminal. It checks if the position is valid and applies
|
// corner of the terminal. It checks if the position is valid and applies
|
||||||
// the gui's colors.
|
// the given colors.
|
||||||
func (g *Gui) SetRune(x, y int, ch rune) error {
|
func (g *Gui) SetRune(x, y int, ch rune, fgColor, bgColor Attribute) error {
|
||||||
if x < 0 || y < 0 || x >= g.maxX || y >= g.maxY {
|
if x < 0 || y < 0 || x >= g.maxX || y >= g.maxY {
|
||||||
return errors.New("invalid point")
|
return errors.New("invalid point")
|
||||||
}
|
}
|
||||||
termbox.SetCell(x, y, ch, termbox.Attribute(g.FgColor), termbox.Attribute(g.BgColor))
|
termbox.SetCell(x, y, ch, termbox.Attribute(fgColor), termbox.Attribute(bgColor))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,7 +144,7 @@ func (g *Gui) SetView(name string, x0, y0, x1, y1 int) (*View, error) {
|
|||||||
return v, nil
|
return v, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
v := newView(name, x0, y0, x1, y1)
|
v := newView(name, x0, y0, x1, y1, g.outputMode)
|
||||||
v.BgColor, v.FgColor = g.BgColor, g.FgColor
|
v.BgColor, v.FgColor = g.BgColor, g.FgColor
|
||||||
v.SelBgColor, v.SelFgColor = g.SelBgColor, g.SelFgColor
|
v.SelBgColor, v.SelFgColor = g.SelBgColor, g.SelFgColor
|
||||||
g.views = append(g.views, v)
|
g.views = append(g.views, v)
|
||||||
@ -154,6 +163,23 @@ func (g *Gui) SetViewOnTop(name string) (*View, error) {
|
|||||||
return nil, ErrUnknownView
|
return nil, ErrUnknownView
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetViewOnBottom sets the given view on bottom of the existing ones.
|
||||||
|
func (g *Gui) SetViewOnBottom(name string) (*View, error) {
|
||||||
|
for i, v := range g.views {
|
||||||
|
if v.name == name {
|
||||||
|
s := append(g.views[:i], g.views[i+1:]...)
|
||||||
|
g.views = append([]*View{v}, s...)
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, ErrUnknownView
|
||||||
|
}
|
||||||
|
|
||||||
|
// Views returns all the views in the GUI.
|
||||||
|
func (g *Gui) Views() []*View {
|
||||||
|
return g.views
|
||||||
|
}
|
||||||
|
|
||||||
// View returns a pointer to the view with the given name, or error
|
// View returns a pointer to the view with the given name, or error
|
||||||
// ErrUnknownView if a view with that name does not exist.
|
// ErrUnknownView if a view with that name does not exist.
|
||||||
func (g *Gui) View(name string) (*View, error) {
|
func (g *Gui) View(name string) (*View, error) {
|
||||||
@ -168,7 +194,9 @@ func (g *Gui) View(name string) (*View, error) {
|
|||||||
// ViewByPosition returns a pointer to a view matching the given position, or
|
// ViewByPosition returns a pointer to a view matching the given position, or
|
||||||
// error ErrUnknownView if a view in that position does not exist.
|
// error ErrUnknownView if a view in that position does not exist.
|
||||||
func (g *Gui) ViewByPosition(x, y int) (*View, error) {
|
func (g *Gui) ViewByPosition(x, y int) (*View, error) {
|
||||||
for _, v := range g.views {
|
// traverse views in reverse order checking top views first
|
||||||
|
for i := len(g.views); i > 0; i-- {
|
||||||
|
v := g.views[i-1]
|
||||||
if x > v.x0 && x < v.x1 && y > v.y0 && y < v.y1 {
|
if x > v.x0 && x < v.x1 && y > v.y0 && y < v.y1 {
|
||||||
return v, nil
|
return v, nil
|
||||||
}
|
}
|
||||||
@ -199,14 +227,14 @@ func (g *Gui) DeleteView(name string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetCurrentView gives the focus to a given view.
|
// SetCurrentView gives the focus to a given view.
|
||||||
func (g *Gui) SetCurrentView(name string) error {
|
func (g *Gui) SetCurrentView(name string) (*View, error) {
|
||||||
for _, v := range g.views {
|
for _, v := range g.views {
|
||||||
if v.name == name {
|
if v.name == name {
|
||||||
g.currentView = v
|
g.currentView = v
|
||||||
return nil
|
return v, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ErrUnknownView
|
return nil, ErrUnknownView
|
||||||
}
|
}
|
||||||
|
|
||||||
// CurrentView returns the currently focused view, or nil if no view
|
// CurrentView returns the currently focused view, or nil if no view
|
||||||
@ -218,14 +246,14 @@ func (g *Gui) CurrentView() *View {
|
|||||||
// SetKeybinding creates a new keybinding. If viewname equals to ""
|
// SetKeybinding creates a new keybinding. If viewname equals to ""
|
||||||
// (empty string) then the keybinding will apply to all views. key must
|
// (empty string) then the keybinding will apply to all views. key must
|
||||||
// be a rune or a Key.
|
// be a rune or a Key.
|
||||||
func (g *Gui) SetKeybinding(viewname string, key interface{}, mod Modifier, h KeybindingHandler) error {
|
func (g *Gui) SetKeybinding(viewname string, key interface{}, mod Modifier, handler func(*Gui, *View) error) error {
|
||||||
var kb *keybinding
|
var kb *keybinding
|
||||||
|
|
||||||
k, ch, err := getKey(key)
|
k, ch, err := getKey(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
kb = newKeybinding(viewname, k, ch, mod, h)
|
kb = newKeybinding(viewname, k, ch, mod, handler)
|
||||||
g.keybindings = append(g.keybindings, kb)
|
g.keybindings = append(g.keybindings, kb)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -270,24 +298,54 @@ func getKey(key interface{}) (Key, rune, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute executes the given handler. This function can be called safely from
|
// userEvent represents an event triggered by the user.
|
||||||
// a goroutine in order to update the GUI. It is important to note that it
|
type userEvent struct {
|
||||||
// won't be executed immediately, instead it will be added to the user events
|
f func(*Gui) error
|
||||||
// queue.
|
|
||||||
func (g *Gui) Execute(h Handler) {
|
|
||||||
go func() { g.userEvents <- userEvent{h: h} }()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetLayout sets the current layout. A layout is a function that
|
// Update executes the passed function. This method can be called safely from a
|
||||||
// will be called every time the gui is redrawn, it must contain
|
// goroutine in order to update the GUI. It is important to note that the
|
||||||
// the base views and its initializations.
|
// passed function won't be executed immediately, instead it will be added to
|
||||||
func (g *Gui) SetLayout(layout Handler) {
|
// the user events queue. Given that Update spawns a goroutine, the order in
|
||||||
g.layout = layout
|
// which the user events will be handled is not guaranteed.
|
||||||
|
func (g *Gui) Update(f func(*Gui) error) {
|
||||||
|
go func() { g.userEvents <- userEvent{f: f} }()
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Manager is in charge of GUI's layout and can be used to build widgets.
|
||||||
|
type Manager interface {
|
||||||
|
// Layout is called every time the GUI is redrawn, it must contain the
|
||||||
|
// base views and its initializations.
|
||||||
|
Layout(*Gui) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// The ManagerFunc type is an adapter to allow the use of ordinary functions as
|
||||||
|
// Managers. If f is a function with the appropriate signature, ManagerFunc(f)
|
||||||
|
// is an Manager object that calls f.
|
||||||
|
type ManagerFunc func(*Gui) error
|
||||||
|
|
||||||
|
// Layout calls f(g)
|
||||||
|
func (f ManagerFunc) Layout(g *Gui) error {
|
||||||
|
return f(g)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetManager sets the given GUI managers. It deletes all views and
|
||||||
|
// keybindings.
|
||||||
|
func (g *Gui) SetManager(managers ...Manager) {
|
||||||
|
g.managers = managers
|
||||||
g.currentView = nil
|
g.currentView = nil
|
||||||
g.views = nil
|
g.views = nil
|
||||||
|
g.keybindings = nil
|
||||||
|
|
||||||
go func() { g.tbEvents <- termbox.Event{Type: termbox.EventResize} }()
|
go func() { g.tbEvents <- termbox.Event{Type: termbox.EventResize} }()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetManagerFunc sets the given manager function. It deletes all views and
|
||||||
|
// keybindings.
|
||||||
|
func (g *Gui) SetManagerFunc(manager func(*Gui) error) {
|
||||||
|
g.SetManager(ManagerFunc(manager))
|
||||||
|
}
|
||||||
|
|
||||||
// MainLoop runs the main loop until an error is returned. A successful
|
// MainLoop runs the main loop until an error is returned. A successful
|
||||||
// finish should return ErrQuit.
|
// finish should return ErrQuit.
|
||||||
func (g *Gui) MainLoop() error {
|
func (g *Gui) MainLoop() error {
|
||||||
@ -316,7 +374,7 @@ func (g *Gui) MainLoop() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case ev := <-g.userEvents:
|
case ev := <-g.userEvents:
|
||||||
if err := ev.h(g); err != nil {
|
if err := ev.f(g); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -338,7 +396,7 @@ func (g *Gui) consumeevents() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case ev := <-g.userEvents:
|
case ev := <-g.userEvents:
|
||||||
if err := ev.h(g); err != nil {
|
if err := ev.f(g); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@ -362,10 +420,6 @@ func (g *Gui) handleEvent(ev *termbox.Event) error {
|
|||||||
|
|
||||||
// flush updates the gui, re-drawing frames and buffers.
|
// flush updates the gui, re-drawing frames and buffers.
|
||||||
func (g *Gui) flush() error {
|
func (g *Gui) flush() error {
|
||||||
if g.layout == nil {
|
|
||||||
return errors.New("Null layout")
|
|
||||||
}
|
|
||||||
|
|
||||||
termbox.Clear(termbox.Attribute(g.FgColor), termbox.Attribute(g.BgColor))
|
termbox.Clear(termbox.Attribute(g.FgColor), termbox.Attribute(g.BgColor))
|
||||||
|
|
||||||
maxX, maxY := termbox.Size()
|
maxX, maxY := termbox.Size()
|
||||||
@ -377,49 +431,60 @@ func (g *Gui) flush() error {
|
|||||||
}
|
}
|
||||||
g.maxX, g.maxY = maxX, maxY
|
g.maxX, g.maxY = maxX, maxY
|
||||||
|
|
||||||
if err := g.layout(g); err != nil {
|
for _, m := range g.managers {
|
||||||
|
if err := m.Layout(g); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
for _, v := range g.views {
|
for _, v := range g.views {
|
||||||
if v.Frame {
|
if v.Frame {
|
||||||
if err := g.drawFrame(v); err != nil {
|
var fgColor, bgColor Attribute
|
||||||
|
if g.Highlight && v == g.currentView {
|
||||||
|
fgColor = g.SelFgColor
|
||||||
|
bgColor = g.SelBgColor
|
||||||
|
} else {
|
||||||
|
fgColor = g.FgColor
|
||||||
|
bgColor = g.BgColor
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := g.drawFrameEdges(v, fgColor, bgColor); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := g.drawCorners(v); err != nil {
|
if err := g.drawFrameCorners(v, fgColor, bgColor); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if v.Title != "" {
|
if v.Title != "" {
|
||||||
if err := g.drawTitle(v); err != nil {
|
if err := g.drawTitle(v, fgColor, bgColor); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := g.draw(v); err != nil {
|
if err := g.draw(v); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := g.drawIntersections(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
termbox.Flush()
|
termbox.Flush()
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// drawFrame draws the horizontal and vertical edges of a view.
|
// drawFrameEdges draws the horizontal and vertical edges of a view.
|
||||||
func (g *Gui) drawFrame(v *View) error {
|
func (g *Gui) drawFrameEdges(v *View, fgColor, bgColor Attribute) error {
|
||||||
|
runeH, runeV := '─', '│'
|
||||||
|
if g.ASCII {
|
||||||
|
runeH, runeV = '-', '|'
|
||||||
|
}
|
||||||
|
|
||||||
for x := v.x0 + 1; x < v.x1 && x < g.maxX; x++ {
|
for x := v.x0 + 1; x < v.x1 && x < g.maxX; x++ {
|
||||||
if x < 0 {
|
if x < 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if v.y0 > -1 && v.y0 < g.maxY {
|
if v.y0 > -1 && v.y0 < g.maxY {
|
||||||
if err := g.SetRune(x, v.y0, '─'); err != nil {
|
if err := g.SetRune(x, v.y0, runeH, fgColor, bgColor); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if v.y1 > -1 && v.y1 < g.maxY {
|
if v.y1 > -1 && v.y1 < g.maxY {
|
||||||
if err := g.SetRune(x, v.y1, '─'); err != nil {
|
if err := g.SetRune(x, v.y1, runeH, fgColor, bgColor); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -429,12 +494,12 @@ func (g *Gui) drawFrame(v *View) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if v.x0 > -1 && v.x0 < g.maxX {
|
if v.x0 > -1 && v.x0 < g.maxX {
|
||||||
if err := g.SetRune(v.x0, y, '│'); err != nil {
|
if err := g.SetRune(v.x0, y, runeV, fgColor, bgColor); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if v.x1 > -1 && v.x1 < g.maxX {
|
if v.x1 > -1 && v.x1 < g.maxX {
|
||||||
if err := g.SetRune(v.x1, y, '│'); err != nil {
|
if err := g.SetRune(v.x1, y, runeV, fgColor, bgColor); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -442,33 +507,30 @@ func (g *Gui) drawFrame(v *View) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// drawCorners draws the corners of the view.
|
// drawFrameCorners draws the corners of the view.
|
||||||
func (g *Gui) drawCorners(v *View) error {
|
func (g *Gui) drawFrameCorners(v *View, fgColor, bgColor Attribute) error {
|
||||||
if v.x0 >= 0 && v.y0 >= 0 && v.x0 < g.maxX && v.y0 < g.maxY {
|
runeTL, runeTR, runeBL, runeBR := '┌', '┐', '└', '┘'
|
||||||
if err := g.SetRune(v.x0, v.y0, '┌'); err != nil {
|
if g.ASCII {
|
||||||
|
runeTL, runeTR, runeBL, runeBR = '+', '+', '+', '+'
|
||||||
|
}
|
||||||
|
|
||||||
|
corners := []struct {
|
||||||
|
x, y int
|
||||||
|
ch rune
|
||||||
|
}{{v.x0, v.y0, runeTL}, {v.x1, v.y0, runeTR}, {v.x0, v.y1, runeBL}, {v.x1, v.y1, runeBR}}
|
||||||
|
|
||||||
|
for _, c := range corners {
|
||||||
|
if c.x >= 0 && c.y >= 0 && c.x < g.maxX && c.y < g.maxY {
|
||||||
|
if err := g.SetRune(c.x, c.y, c.ch, fgColor, bgColor); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if v.x1 >= 0 && v.y0 >= 0 && v.x1 < g.maxX && v.y0 < g.maxY {
|
|
||||||
if err := g.SetRune(v.x1, v.y0, '┐'); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if v.x0 >= 0 && v.y1 >= 0 && v.x0 < g.maxX && v.y1 < g.maxY {
|
|
||||||
if err := g.SetRune(v.x0, v.y1, '└'); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if v.x1 >= 0 && v.y1 >= 0 && v.x1 < g.maxX && v.y1 < g.maxY {
|
|
||||||
if err := g.SetRune(v.x1, v.y1, '┘'); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// drawTitle draws the title of the view.
|
// drawTitle draws the title of the view.
|
||||||
func (g *Gui) drawTitle(v *View) error {
|
func (g *Gui) drawTitle(v *View, fgColor, bgColor Attribute) error {
|
||||||
if v.y0 < 0 || v.y0 >= g.maxY {
|
if v.y0 < 0 || v.y0 >= g.maxY {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -480,7 +542,7 @@ func (g *Gui) drawTitle(v *View) error {
|
|||||||
} else if x > v.x1-2 || x >= g.maxX {
|
} else if x > v.x1-2 || x >= g.maxX {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if err := g.SetRune(x, v.y0, ch); err != nil {
|
if err := g.SetRune(x, v.y0, ch, fgColor, bgColor); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -490,21 +552,21 @@ func (g *Gui) drawTitle(v *View) error {
|
|||||||
// draw manages the cursor and calls the draw function of a view.
|
// draw manages the cursor and calls the draw function of a view.
|
||||||
func (g *Gui) draw(v *View) error {
|
func (g *Gui) draw(v *View) error {
|
||||||
if g.Cursor {
|
if g.Cursor {
|
||||||
if v := g.currentView; v != nil {
|
if curview := g.currentView; curview != nil {
|
||||||
vMaxX, vMaxY := v.Size()
|
vMaxX, vMaxY := curview.Size()
|
||||||
if v.cx < 0 {
|
if curview.cx < 0 {
|
||||||
v.cx = 0
|
curview.cx = 0
|
||||||
} else if v.cx >= vMaxX {
|
} else if curview.cx >= vMaxX {
|
||||||
v.cx = vMaxX - 1
|
curview.cx = vMaxX - 1
|
||||||
}
|
}
|
||||||
if v.cy < 0 {
|
if curview.cy < 0 {
|
||||||
v.cy = 0
|
curview.cy = 0
|
||||||
} else if v.cy >= vMaxY {
|
} else if curview.cy >= vMaxY {
|
||||||
v.cy = vMaxY - 1
|
curview.cy = vMaxY - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
gMaxX, gMaxY := g.Size()
|
gMaxX, gMaxY := g.Size()
|
||||||
cx, cy := v.x0+v.cx+1, v.y0+v.cy+1
|
cx, cy := curview.x0+curview.cx+1, curview.y0+curview.cy+1
|
||||||
if cx >= 0 && cx < gMaxX && cy >= 0 && cy < gMaxY {
|
if cx >= 0 && cx < gMaxX && cy >= 0 && cy < gMaxY {
|
||||||
termbox.SetCursor(cx, cy)
|
termbox.SetCursor(cx, cy)
|
||||||
} else {
|
} else {
|
||||||
@ -522,95 +584,21 @@ func (g *Gui) draw(v *View) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// drawIntersections draws the corners of each view, based on the type
|
|
||||||
// of the edges that converge at these points.
|
|
||||||
func (g *Gui) drawIntersections() error {
|
|
||||||
for _, v := range g.views {
|
|
||||||
if ch, ok := g.intersectionRune(v.x0, v.y0); ok {
|
|
||||||
if err := g.SetRune(v.x0, v.y0, ch); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ch, ok := g.intersectionRune(v.x0, v.y1); ok {
|
|
||||||
if err := g.SetRune(v.x0, v.y1, ch); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ch, ok := g.intersectionRune(v.x1, v.y0); ok {
|
|
||||||
if err := g.SetRune(v.x1, v.y0, ch); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ch, ok := g.intersectionRune(v.x1, v.y1); ok {
|
|
||||||
if err := g.SetRune(v.x1, v.y1, ch); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// intersectionRune returns the correct intersection rune at a given
|
|
||||||
// point.
|
|
||||||
func (g *Gui) intersectionRune(x, y int) (rune, bool) {
|
|
||||||
if x < 0 || y < 0 || x >= g.maxX || y >= g.maxY {
|
|
||||||
return ' ', false
|
|
||||||
}
|
|
||||||
|
|
||||||
chTop, _ := g.Rune(x, y-1)
|
|
||||||
top := verticalRune(chTop)
|
|
||||||
chBottom, _ := g.Rune(x, y+1)
|
|
||||||
bottom := verticalRune(chBottom)
|
|
||||||
chLeft, _ := g.Rune(x-1, y)
|
|
||||||
left := horizontalRune(chLeft)
|
|
||||||
chRight, _ := g.Rune(x+1, y)
|
|
||||||
right := horizontalRune(chRight)
|
|
||||||
|
|
||||||
var ch rune
|
|
||||||
switch {
|
|
||||||
case top && bottom && left && right:
|
|
||||||
ch = '┼'
|
|
||||||
case top && bottom && !left && right:
|
|
||||||
ch = '├'
|
|
||||||
case top && bottom && left && !right:
|
|
||||||
ch = '┤'
|
|
||||||
case !top && bottom && left && right:
|
|
||||||
ch = '┬'
|
|
||||||
case top && !bottom && left && right:
|
|
||||||
ch = '┴'
|
|
||||||
default:
|
|
||||||
return ' ', false
|
|
||||||
}
|
|
||||||
return ch, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// verticalRune returns if the given character is a vertical rune.
|
|
||||||
func verticalRune(ch rune) bool {
|
|
||||||
if ch == '│' || ch == '┼' || ch == '├' || ch == '┤' {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// verticalRune returns if the given character is a horizontal rune.
|
|
||||||
func horizontalRune(ch rune) bool {
|
|
||||||
if ch == '─' || ch == '┼' || ch == '┬' || ch == '┴' {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// onKey manages key-press events. A keybinding handler is called when
|
// onKey manages key-press events. A keybinding handler is called when
|
||||||
// a key-press or mouse event satisfies a configured keybinding. Furthermore,
|
// a key-press or mouse event satisfies a configured keybinding. Furthermore,
|
||||||
// currentView's internal buffer is modified if currentView.Editable is true.
|
// currentView's internal buffer is modified if currentView.Editable is true.
|
||||||
func (g *Gui) onKey(ev *termbox.Event) error {
|
func (g *Gui) onKey(ev *termbox.Event) error {
|
||||||
switch ev.Type {
|
switch ev.Type {
|
||||||
case termbox.EventKey:
|
case termbox.EventKey:
|
||||||
if err := g.execKeybindings(g.currentView, ev); err != nil {
|
matched, err := g.execKeybindings(g.currentView, ev)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if g.currentView != nil && g.currentView.Editable && g.Editor != nil {
|
if matched {
|
||||||
g.Editor.Edit(g.currentView, Key(ev.Key), ev.Ch, Modifier(ev.Mod))
|
break
|
||||||
|
}
|
||||||
|
if g.currentView != nil && g.currentView.Editable && g.currentView.Editor != nil {
|
||||||
|
g.currentView.Editor.Edit(g.currentView, Key(ev.Key), ev.Ch, Modifier(ev.Mod))
|
||||||
}
|
}
|
||||||
case termbox.EventMouse:
|
case termbox.EventMouse:
|
||||||
mx, my := ev.MouseX, ev.MouseY
|
mx, my := ev.MouseX, ev.MouseY
|
||||||
@ -621,7 +609,7 @@ func (g *Gui) onKey(ev *termbox.Event) error {
|
|||||||
if err := v.SetCursor(mx-v.x0-1, my-v.y0-1); err != nil {
|
if err := v.SetCursor(mx-v.x0-1, my-v.y0-1); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := g.execKeybindings(v, ev); err != nil {
|
if _, err := g.execKeybindings(v, ev); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -630,17 +618,19 @@ func (g *Gui) onKey(ev *termbox.Event) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// execKeybindings executes the keybinding handlers that match the passed view
|
// execKeybindings executes the keybinding handlers that match the passed view
|
||||||
// and event.
|
// and event. The value of matched is true if there is a match and no errors.
|
||||||
func (g *Gui) execKeybindings(v *View, ev *termbox.Event) error {
|
func (g *Gui) execKeybindings(v *View, ev *termbox.Event) (matched bool, err error) {
|
||||||
|
matched = false
|
||||||
for _, kb := range g.keybindings {
|
for _, kb := range g.keybindings {
|
||||||
if kb.h == nil {
|
if kb.handler == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if kb.matchKeypress(Key(ev.Key), ev.Ch, Modifier(ev.Mod)) && kb.matchView(v) {
|
if kb.matchKeypress(Key(ev.Key), ev.Ch, Modifier(ev.Mod)) && kb.matchView(v) {
|
||||||
if err := kb.h(g, v); err != nil {
|
if err := kb.handler(g, v); err != nil {
|
||||||
return err
|
return false, err
|
||||||
|
}
|
||||||
|
matched = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
return matched, nil
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
@ -6,19 +6,42 @@ package gocui
|
|||||||
|
|
||||||
import "github.com/nsf/termbox-go"
|
import "github.com/nsf/termbox-go"
|
||||||
|
|
||||||
type (
|
// Keybidings are used to link a given key-press event with a handler.
|
||||||
|
type keybinding struct {
|
||||||
|
viewName string
|
||||||
|
key Key
|
||||||
|
ch rune
|
||||||
|
mod Modifier
|
||||||
|
handler func(*Gui, *View) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// newKeybinding returns a new Keybinding object.
|
||||||
|
func newKeybinding(viewname string, key Key, ch rune, mod Modifier, handler func(*Gui, *View) error) (kb *keybinding) {
|
||||||
|
kb = &keybinding{
|
||||||
|
viewName: viewname,
|
||||||
|
key: key,
|
||||||
|
ch: ch,
|
||||||
|
mod: mod,
|
||||||
|
handler: handler,
|
||||||
|
}
|
||||||
|
return kb
|
||||||
|
}
|
||||||
|
|
||||||
|
// matchKeypress returns if the keybinding matches the keypress.
|
||||||
|
func (kb *keybinding) matchKeypress(key Key, ch rune, mod Modifier) bool {
|
||||||
|
return kb.key == key && kb.ch == ch && kb.mod == mod
|
||||||
|
}
|
||||||
|
|
||||||
|
// matchView returns if the keybinding matches the current view.
|
||||||
|
func (kb *keybinding) matchView(v *View) bool {
|
||||||
|
if kb.viewName == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return v != nil && kb.viewName == v.name
|
||||||
|
}
|
||||||
|
|
||||||
// Key represents special keys or keys combinations.
|
// Key represents special keys or keys combinations.
|
||||||
Key termbox.Key
|
type Key termbox.Key
|
||||||
|
|
||||||
// Modifier allows to define special keys combinations. They can be used
|
|
||||||
// in combination with Keys or Runes when a new keybinding is defined.
|
|
||||||
Modifier termbox.Modifier
|
|
||||||
|
|
||||||
// KeybindingHandler represents the handler linked to a specific
|
|
||||||
// keybindings. The handler is called when a key-press event satisfies a
|
|
||||||
// configured keybinding.
|
|
||||||
KeybindingHandler func(*Gui, *View) error
|
|
||||||
)
|
|
||||||
|
|
||||||
// Special keys.
|
// Special keys.
|
||||||
const (
|
const (
|
||||||
@ -48,6 +71,9 @@ const (
|
|||||||
MouseLeft = Key(termbox.MouseLeft)
|
MouseLeft = Key(termbox.MouseLeft)
|
||||||
MouseMiddle = Key(termbox.MouseMiddle)
|
MouseMiddle = Key(termbox.MouseMiddle)
|
||||||
MouseRight = Key(termbox.MouseRight)
|
MouseRight = Key(termbox.MouseRight)
|
||||||
|
MouseRelease = Key(termbox.MouseRelease)
|
||||||
|
MouseWheelUp = Key(termbox.MouseWheelUp)
|
||||||
|
MouseWheelDown = Key(termbox.MouseWheelDown)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Keys combinations.
|
// Keys combinations.
|
||||||
@ -100,42 +126,12 @@ const (
|
|||||||
KeyCtrl8 = Key(termbox.KeyCtrl8)
|
KeyCtrl8 = Key(termbox.KeyCtrl8)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Modifier allows to define special keys combinations. They can be used
|
||||||
|
// in combination with Keys or Runes when a new keybinding is defined.
|
||||||
|
type Modifier termbox.Modifier
|
||||||
|
|
||||||
// Modifiers.
|
// Modifiers.
|
||||||
const (
|
const (
|
||||||
ModNone Modifier = Modifier(0)
|
ModNone Modifier = Modifier(0)
|
||||||
ModAlt = Modifier(termbox.ModAlt)
|
ModAlt = Modifier(termbox.ModAlt)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Keybidings are used to link a given key-press event with a handler.
|
|
||||||
type keybinding struct {
|
|
||||||
viewName string
|
|
||||||
key Key
|
|
||||||
ch rune
|
|
||||||
mod Modifier
|
|
||||||
h KeybindingHandler
|
|
||||||
}
|
|
||||||
|
|
||||||
// newKeybinding returns a new Keybinding object.
|
|
||||||
func newKeybinding(viewname string, key Key, ch rune, mod Modifier, h KeybindingHandler) (kb *keybinding) {
|
|
||||||
kb = &keybinding{
|
|
||||||
viewName: viewname,
|
|
||||||
key: key,
|
|
||||||
ch: ch,
|
|
||||||
mod: mod,
|
|
||||||
h: h,
|
|
||||||
}
|
|
||||||
return kb
|
|
||||||
}
|
|
||||||
|
|
||||||
// matchKeypress returns if the keybinding matches the keypress.
|
|
||||||
func (kb *keybinding) matchKeypress(key Key, ch rune, mod Modifier) bool {
|
|
||||||
return kb.key == key && kb.ch == ch && kb.mod == mod
|
|
||||||
}
|
|
||||||
|
|
||||||
// matchView returns if the keybinding matches the current view.
|
|
||||||
func (kb *keybinding) matchView(v *View) bool {
|
|
||||||
if kb.viewName == "" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return v != nil && kb.viewName == v.name
|
|
||||||
}
|
|
||||||
|
45
view.go
45
view.go
@ -41,6 +41,11 @@ type View struct {
|
|||||||
// buffer at the cursor position.
|
// buffer at the cursor position.
|
||||||
Editable bool
|
Editable bool
|
||||||
|
|
||||||
|
// Editor allows to define the editor that manages the edition mode,
|
||||||
|
// including keybindings or cursor behaviour. DefaultEditor is used by
|
||||||
|
// default.
|
||||||
|
Editor Editor
|
||||||
|
|
||||||
// Overwrite enables or disables the overwrite mode of the view.
|
// Overwrite enables or disables the overwrite mode of the view.
|
||||||
Overwrite bool
|
Overwrite bool
|
||||||
|
|
||||||
@ -90,7 +95,7 @@ func (l lineType) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// newView returns a new View object.
|
// newView returns a new View object.
|
||||||
func newView(name string, x0, y0, x1, y1 int) *View {
|
func newView(name string, x0, y0, x1, y1 int, mode OutputMode) *View {
|
||||||
v := &View{
|
v := &View{
|
||||||
name: name,
|
name: name,
|
||||||
x0: x0,
|
x0: x0,
|
||||||
@ -98,8 +103,9 @@ func newView(name string, x0, y0, x1, y1 int) *View {
|
|||||||
x1: x1,
|
x1: x1,
|
||||||
y1: y1,
|
y1: y1,
|
||||||
Frame: true,
|
Frame: true,
|
||||||
|
Editor: DefaultEditor,
|
||||||
tainted: true,
|
tainted: true,
|
||||||
ei: newEscapeInterpreter(),
|
ei: newEscapeInterpreter(mode),
|
||||||
}
|
}
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
@ -292,16 +298,12 @@ func (v *View) draw() error {
|
|||||||
v.viewLines = nil
|
v.viewLines = nil
|
||||||
for i, line := range v.lines {
|
for i, line := range v.lines {
|
||||||
if v.Wrap {
|
if v.Wrap {
|
||||||
if len(line) <= maxX {
|
if len(line) < maxX {
|
||||||
vline := viewLine{linesX: 0, linesY: i, line: line}
|
vline := viewLine{linesX: 0, linesY: i, line: line}
|
||||||
v.viewLines = append(v.viewLines, vline)
|
v.viewLines = append(v.viewLines, vline)
|
||||||
continue
|
continue
|
||||||
} else {
|
} else {
|
||||||
vline := viewLine{linesX: 0, linesY: i, line: line[:maxX]}
|
for n := 0; n <= len(line); n += maxX {
|
||||||
v.viewLines = append(v.viewLines, vline)
|
|
||||||
}
|
|
||||||
// Append remaining lines
|
|
||||||
for n := maxX; n < len(line); n += maxX {
|
|
||||||
if len(line[n:]) <= maxX {
|
if len(line[n:]) <= maxX {
|
||||||
vline := viewLine{linesX: n, linesY: i, line: line[n:]}
|
vline := viewLine{linesX: n, linesY: i, line: line[n:]}
|
||||||
v.viewLines = append(v.viewLines, vline)
|
v.viewLines = append(v.viewLines, vline)
|
||||||
@ -310,6 +312,7 @@ func (v *View) draw() error {
|
|||||||
v.viewLines = append(v.viewLines, vline)
|
v.viewLines = append(v.viewLines, vline)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
vline := viewLine{linesX: 0, linesY: i, line: line}
|
vline := viewLine{linesX: 0, linesY: i, line: line}
|
||||||
v.viewLines = append(v.viewLines, vline)
|
v.viewLines = append(v.viewLines, vline)
|
||||||
@ -389,6 +392,8 @@ func (v *View) Clear() {
|
|||||||
v.tainted = true
|
v.tainted = true
|
||||||
|
|
||||||
v.lines = nil
|
v.lines = nil
|
||||||
|
v.viewLines = nil
|
||||||
|
v.readOffset = 0
|
||||||
v.clearRunes()
|
v.clearRunes()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -403,6 +408,18 @@ func (v *View) clearRunes() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BufferLines returns the lines in the view's internal
|
||||||
|
// buffer.
|
||||||
|
func (v *View) BufferLines() []string {
|
||||||
|
lines := make([]string, len(v.lines))
|
||||||
|
for i, l := range v.lines {
|
||||||
|
str := lineType(l).String()
|
||||||
|
str = strings.Replace(str, "\x00", " ", -1)
|
||||||
|
lines[i] = str
|
||||||
|
}
|
||||||
|
return lines
|
||||||
|
}
|
||||||
|
|
||||||
// Buffer returns a string with the contents of the view's internal
|
// Buffer returns a string with the contents of the view's internal
|
||||||
// buffer.
|
// buffer.
|
||||||
func (v *View) Buffer() string {
|
func (v *View) Buffer() string {
|
||||||
@ -413,6 +430,18 @@ func (v *View) Buffer() string {
|
|||||||
return strings.Replace(str, "\x00", " ", -1)
|
return strings.Replace(str, "\x00", " ", -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ViewBufferLines returns the lines in the view's internal
|
||||||
|
// buffer that is shown to the user.
|
||||||
|
func (v *View) ViewBufferLines() []string {
|
||||||
|
lines := make([]string, len(v.viewLines))
|
||||||
|
for i, l := range v.viewLines {
|
||||||
|
str := lineType(l.line).String()
|
||||||
|
str = strings.Replace(str, "\x00", " ", -1)
|
||||||
|
lines[i] = str
|
||||||
|
}
|
||||||
|
return lines
|
||||||
|
}
|
||||||
|
|
||||||
// ViewBuffer returns a string with the contents of the view's buffer that is
|
// ViewBuffer returns a string with the contents of the view's buffer that is
|
||||||
// shown to the user.
|
// shown to the user.
|
||||||
func (v *View) ViewBuffer() string {
|
func (v *View) ViewBuffer() string {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user