Full mouse support. Add mouse example. Make golint happy.

- Full mouse support based on hazbo's initial work.
- Add example to show mouse support.
- Fix comments, naming conventions, etc. to make golint happy.
This commit is contained in:
Roi Martin 2016-01-23 16:07:42 +01:00
parent c36dfefa9b
commit faa12e105a
13 changed files with 198 additions and 82 deletions

View File

@ -17,7 +17,7 @@ Minimalist Go package aimed at creating Console User Interfaces.
func layout(g *gocui.Gui) error {
maxX, maxY := g.Size()
if v, err := g.SetView("center", maxX/2-10, maxY/2, maxX/2+10, maxY/2+2); err != nil {
if err != gocui.ErrorUnkView {
if err != gocui.ErrUnknownView {
return err
}
fmt.Fprintln(v, "This is an example")
@ -25,7 +25,7 @@ func layout(g *gocui.Gui) error {
return nil
}
func quit(g *gocui.Gui, v *gocui.View) error {
return gocui.Quit
return gocui.ErrQuit
}
func main() {
var err error
@ -39,7 +39,7 @@ func main() {
log.Panicln(err)
}
err = g.MainLoop()
if err != nil && err != gocui.Quit {
if err != nil && err != gocui.ErrQuit {
log.Panicln(err)
}
}

View File

@ -38,22 +38,23 @@ func main() {
}
err = g.MainLoop()
if err != nil && err != gocui.Quit {
if err != nil && err != gocui.ErrQuit {
log.Panicln(err)
}
}
func layout(g *gocui.Gui) error {
maxX, _ := g.Size()
v, err := g.SetView("legend", maxX-22, 0, maxX-1, 6)
v, err := g.SetView("legend", maxX-25, 0, maxX-1, 7)
if err != nil {
if err != gocui.ErrorUnkView {
if err != gocui.ErrUnknownView {
return err
}
fmt.Fprintln(v, "KEYBINDINGS")
fmt.Fprintln(v, "Space: New View")
fmt.Fprintln(v, "Tab: Next View")
fmt.Fprintln(v, "← ↑ → ↓: Move View")
fmt.Fprintln(v, "Backspace: Delete View")
fmt.Fprintln(v, "^C: Exit")
}
return nil
@ -109,7 +110,7 @@ func initKeybindings(g *gocui.Gui) error {
}
func quit(g *gocui.Gui, v *gocui.View) error {
return gocui.Quit
return gocui.ErrQuit
}
func newView(g *gocui.Gui) error {
@ -117,7 +118,7 @@ func newView(g *gocui.Gui) error {
name := fmt.Sprintf("v%v", idxView)
v, err := g.SetView(name, maxX/2-5, maxY/2-5, maxX/2+5, maxY/2+5)
if err != nil {
if err != gocui.ErrorUnkView {
if err != gocui.ErrUnknownView {
return err
}
v.Wrap = true

View File

@ -58,7 +58,7 @@ func getLine(g *gocui.Gui, v *gocui.View) error {
maxX, maxY := g.Size()
if v, err := g.SetView("msg", maxX/2-30, maxY/2, maxX/2+30, maxY/2+2); err != nil {
if err != gocui.ErrorUnkView {
if err != gocui.ErrUnknownView {
return err
}
fmt.Fprintln(v, l)
@ -80,7 +80,7 @@ func delMsg(g *gocui.Gui, v *gocui.View) error {
}
func quit(g *gocui.Gui, v *gocui.View) error {
return gocui.Quit
return gocui.ErrQuit
}
func keybindings(g *gocui.Gui) error {
@ -158,7 +158,7 @@ func saveVisualMain(g *gocui.Gui, v *gocui.View) error {
func layout(g *gocui.Gui) error {
maxX, maxY := g.Size()
if v, err := g.SetView("side", -1, -1, 30, maxY); err != nil {
if err != gocui.ErrorUnkView {
if err != gocui.ErrUnknownView {
return err
}
v.Highlight = true
@ -169,7 +169,7 @@ func layout(g *gocui.Gui) error {
fmt.Fprint(v, "deleted\rItem 4\nItem 5")
}
if v, err := g.SetView("main", 30, -1, maxX, maxY); err != nil {
if err != gocui.ErrorUnkView {
if err != gocui.ErrUnknownView {
return err
}
b, err := ioutil.ReadFile("Mark.Twain-Tom.Sawyer.txt")
@ -204,7 +204,7 @@ func main() {
g.ShowCursor = true
err = g.MainLoop()
if err != nil && err != gocui.Quit {
if err != nil && err != gocui.ErrQuit {
log.Panicln(err)
}
}

View File

@ -13,22 +13,22 @@ import (
func layout(g *gocui.Gui) error {
maxX, maxY := g.Size()
if _, err := g.SetView("side", -1, -1, int(0.2*float32(maxX)), maxY-5); err != nil &&
err != gocui.ErrorUnkView {
err != gocui.ErrUnknownView {
return err
}
if _, err := g.SetView("main", int(0.2*float32(maxX)), -1, maxX, maxY-5); err != nil &&
err != gocui.ErrorUnkView {
err != gocui.ErrUnknownView {
return err
}
if _, err := g.SetView("cmdline", -1, maxY-5, maxX, maxY); err != nil &&
err != gocui.ErrorUnkView {
err != gocui.ErrUnknownView {
return err
}
return nil
}
func quit(g *gocui.Gui, v *gocui.View) error {
return gocui.Quit
return gocui.ErrQuit
}
func main() {
@ -47,7 +47,7 @@ func main() {
}
err = g.MainLoop()
if err != nil && err != gocui.Quit {
if err != nil && err != gocui.ErrQuit {
log.Panicln(err)
}
}

94
_examples/mouse.go Normal file
View File

@ -0,0 +1,94 @@
// 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() {
var err error
g := gocui.NewGui()
if err := g.Init(); err != nil {
log.Panicln(err)
}
defer g.Close()
g.SetLayout(layout)
if err := keybindings(g); err != nil {
log.Panicln(err)
}
g.EnableMouse = true
err = g.MainLoop()
if err != nil && err != gocui.ErrQuit {
log.Panicln(err)
}
}
func layout(g *gocui.Gui) error {
if v, err := g.SetView("but1", 2, 2, 12, 4); err != nil {
if err != gocui.ErrUnknownView {
return err
}
fmt.Fprintln(v, "Button 1")
}
if v, err := g.SetView("but2", 14, 2, 24, 4); err != nil {
if err != gocui.ErrUnknownView {
return err
}
fmt.Fprintln(v, "Button 2")
}
return nil
}
func keybindings(g *gocui.Gui) error {
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
return err
}
for _, n := range []string{"but1", "but2"} {
if err := g.SetKeybinding(n, gocui.MouseLeft, gocui.ModNone, showMsg); err != nil {
return err
}
}
if err := g.SetKeybinding("msg", gocui.MouseLeft, gocui.ModNone, delMsg); err != nil {
return err
}
return nil
}
func quit(g *gocui.Gui, v *gocui.View) error {
return gocui.ErrQuit
}
func showMsg(g *gocui.Gui, v *gocui.View) error {
var l string
var err error
_, cy := v.Cursor()
if l, err = v.Line(cy); err != nil {
l = ""
}
maxX, maxY := g.Size()
if v, err := g.SetView("msg", maxX/2-5, maxY/2, maxX/2+5, maxY/2+2); err != nil {
if err != gocui.ErrUnknownView {
return err
}
fmt.Fprintln(v, l)
}
return nil
}
func delMsg(g *gocui.Gui, v *gocui.View) error {
if err := g.DeleteView("msg"); err != nil {
return err
}
return nil
}

View File

@ -13,46 +13,46 @@ import (
func layout(g *gocui.Gui) error {
maxX, maxY := g.Size()
if _, err := g.SetView("v1", -1, -1, 10, 10); err != nil &&
err != gocui.ErrorUnkView {
err != gocui.ErrUnknownView {
return err
}
if _, err := g.SetView("v2", maxX-10, -1, maxX, 10); err != nil &&
err != gocui.ErrorUnkView {
err != gocui.ErrUnknownView {
return err
}
if _, err := g.SetView("v3", maxX/2-5, -1, maxX/2+5, 10); err != nil &&
err != gocui.ErrorUnkView {
err != gocui.ErrUnknownView {
return err
}
if _, err := g.SetView("v4", -1, maxY/2-5, 10, maxY/2+5); err != nil &&
err != gocui.ErrorUnkView {
err != gocui.ErrUnknownView {
return err
}
if _, err := g.SetView("v5", maxX-10, maxY/2-5, maxX, maxY/2+5); err != nil &&
err != gocui.ErrorUnkView {
err != gocui.ErrUnknownView {
return err
}
if _, err := g.SetView("v6", -1, maxY-10, 10, maxY); err != nil &&
err != gocui.ErrorUnkView {
err != gocui.ErrUnknownView {
return err
}
if _, err := g.SetView("v7", maxX-10, maxY-10, maxX, maxY); err != nil &&
err != gocui.ErrorUnkView {
err != gocui.ErrUnknownView {
return err
}
if _, err := g.SetView("v8", maxX/2-5, maxY-10, maxX/2+5, maxY); err != nil &&
err != gocui.ErrorUnkView {
err != gocui.ErrUnknownView {
return err
}
if _, err := g.SetView("v9", maxX/2-5, maxY/2-5, maxX/2+5, maxY/2+5); err != nil &&
err != gocui.ErrorUnkView {
err != gocui.ErrUnknownView {
return err
}
return nil
}
func quit(g *gocui.Gui, v *gocui.View) error {
return gocui.Quit
return gocui.ErrQuit
}
func main() {
@ -70,7 +70,7 @@ func main() {
}
err = g.MainLoop()
if err != nil && err != gocui.Quit {
if err != nil && err != gocui.ErrQuit {
log.Panicln(err)
}
}

View File

@ -28,7 +28,7 @@ func main() {
g.ShowCursor = true
err := g.MainLoop()
if err != nil && err != gocui.Quit {
if err != nil && err != gocui.ErrQuit {
log.Fatalln(err)
}
}
@ -37,7 +37,7 @@ func layout(g *gocui.Gui) error {
maxX, _ := g.Size()
if v, err := g.SetView("legend", maxX-23, 0, maxX-1, 5); err != nil {
if err != gocui.ErrorUnkView {
if err != gocui.ErrUnknownView {
return err
}
fmt.Fprintln(v, "KEYBINDINGS")
@ -47,7 +47,7 @@ func layout(g *gocui.Gui) error {
}
if v, err := g.SetView("stdin", 0, 0, 80, 35); err != nil {
if err != gocui.ErrorUnkView {
if err != gocui.ErrUnknownView {
return err
}
if err := g.SetCurrentView("stdin"); err != nil {
@ -88,7 +88,7 @@ func initKeybindings(g *gocui.Gui) error {
}
func quit(g *gocui.Gui, v *gocui.View) error {
return gocui.Quit
return gocui.ErrQuit
}
func autoscroll(g *gocui.Gui, v *gocui.View) error {

View File

@ -15,7 +15,7 @@ import (
func layout(g *gocui.Gui) error {
maxX, maxY := g.Size()
if v, err := g.SetView("main", 1, 1, maxX-1, maxY-1); err != nil {
if err != gocui.ErrorUnkView {
if err != gocui.ErrUnknownView {
return err
}
v.Wrap = true
@ -28,7 +28,7 @@ func layout(g *gocui.Gui) error {
}
func quit(g *gocui.Gui, v *gocui.View) error {
return gocui.Quit
return gocui.ErrQuit
}
func main() {
@ -46,7 +46,7 @@ func main() {
}
err = g.MainLoop()
if err != nil && err != gocui.Quit {
if err != nil && err != gocui.ErrQuit {
log.Panicln(err)
}
}

View File

@ -6,8 +6,9 @@ package gocui
import "github.com/nsf/termbox-go"
// Attributes can be combined using bitwise OR (|). Note that it is not
// possible to combine multiple color attributes.
// Attribute represents a terminal attribute, like color, font style, etc. They
// can be combined using bitwise OR (|). Note that it is not possible to
// combine multiple color attributes.
type Attribute termbox.Attribute
// Color attributes.

6
doc.go
View File

@ -10,7 +10,7 @@ Example:
func layout(g *gocui.Gui) error {
maxX, maxY := g.Size()
if v, err := g.SetView("center", maxX/2-10, maxY/2, maxX/2+10, maxY/2+2); err != nil {
if err != gocui.ErrorUnkView {
if err != gocui.ErrUnknownView {
return err
}
fmt.Fprintln(v, "This is an example")
@ -18,7 +18,7 @@ Example:
return nil
}
func quit(g *gocui.Gui, v *gocui.View) error {
return gocui.Quit
return gocui.ErrQuit
}
func main() {
var err error
@ -32,7 +32,7 @@ Example:
log.Panicln(err)
}
err = g.MainLoop()
if err != nil && err != gocui.Quit {
if err != nil && err != gocui.ErrQuit {
log.Panicln(err)
}
}

14
edit.go
View File

@ -11,7 +11,7 @@ const maxInt = int(^uint(0) >> 1)
// default.
var Edit = EditorFunc(DefaultEditor)
// Objects implementing the Editor interface can be used as gocui editors.
// Editor interface must be satisfied by gocui editors.
type Editor interface {
Edit(v *View, key Key, ch rune, mod Modifier)
}
@ -159,7 +159,7 @@ func (v *View) MoveCursor(dx, dy int, writeMode bool) {
v.ox = 0
}
v.cx = 0
cy += 1
cy++
} else { // vertical movement
if curLineWidth > 0 { // move cursor to the EOL
if v.Wrap {
@ -185,7 +185,7 @@ func (v *View) MoveCursor(dx, dy int, writeMode bool) {
}
} else if cx < 0 {
if !v.Wrap && v.ox > 0 { // move origin to the left
v.ox -= 1
v.ox--
} else { // move to previous line
if prevLineWidth > 0 {
if !v.Wrap { // set origin so the EOL is visible
@ -203,14 +203,14 @@ func (v *View) MoveCursor(dx, dy int, writeMode bool) {
}
v.cx = 0
}
cy -= 1
cy--
}
} else { // stay on the same line
if v.Wrap {
v.cx = cx
} else {
if cx >= maxX {
v.ox += 1
v.ox++
} else {
v.cx = cx
}
@ -219,10 +219,10 @@ func (v *View) MoveCursor(dx, dy int, writeMode bool) {
// adjust cursor's y position and view's y origin
if cy >= maxY {
v.oy += 1
v.oy++
} else if cy < 0 {
if v.oy > 0 {
v.oy -= 1
v.oy--
}
} else {
v.cy = cy

67
gui.go
View File

@ -12,11 +12,11 @@ import (
)
var (
// Quit is used to decide if the MainLoop finished succesfully.
Quit error = errors.New("quit")
// ErrQuit is used to decide if the MainLoop finished succesfully.
ErrQuit = errors.New("quit")
// ErrorUnkView allows to assert if a View must be initialized.
ErrorUnkView error = errors.New("unknown view")
// ErrUnknownView allows to assert if a View must be initialized.
ErrUnknownView = errors.New("unknown view")
)
// Gui represents the whole User Interface, including the views, layouts
@ -43,7 +43,7 @@ type Gui struct {
// If ShowCursor is true then the cursor is enabled.
ShowCursor bool
// If EnableMouse is true then mouse clicks will be recognized.
// If EnableMouse is true then mouse events will be enabled.
EnableMouse bool
}
@ -100,7 +100,7 @@ func (g *Gui) Rune(x, y int) (rune, error) {
// SetView creates a new view with its top-left corner at (x0, y0)
// and the bottom-right one at (x1, y1). If a view with the same name
// already exists, its dimensions are updated; otherwise, the error
// ErrorUnkView is returned, which allows to assert if the View must
// ErrUnknownView is returned, which allows to assert if the View must
// be initialized. It checks if the position is valid.
func (g *Gui) SetView(name string, x0, y0, x1, y1 int) (*View, error) {
if x0 >= x1 || y0 >= y1 {
@ -123,29 +123,40 @@ func (g *Gui) SetView(name string, x0, y0, x1, y1 int) (*View, error) {
v.BgColor, v.FgColor = g.BgColor, g.FgColor
v.SelBgColor, v.SelFgColor = g.SelBgColor, g.SelFgColor
g.views = append(g.views, v)
return v, ErrorUnkView
return v, ErrUnknownView
}
// View returns a pointer to the view with the given name, or error
// ErrorUnkView 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) {
for _, v := range g.views {
if v.name == name {
return v, nil
}
}
return nil, ErrorUnkView
return nil, ErrUnknownView
}
// Position returns the coordinates of the view with the given name,
// or error ErrorUnkView if a view with that name does not exist.
// ViewByPosition returns a pointer to a view matching the given position, or
// error ErrUnknownView if a view in that position does not exist.
func (g *Gui) ViewByPosition(x, y int) (*View, error) {
for _, v := range g.views {
if x >= v.x0 && x <= v.x1 && y >= v.y0 && y <= v.y1 {
return v, nil
}
}
return nil, ErrUnknownView
}
// ViewPosition returns the coordinates of the view with the given name, or
// error ErrUnknownView if a view with that name does not exist.
func (g *Gui) ViewPosition(name string) (x0, y0, x1, y1 int, err error) {
for _, v := range g.views {
if v.name == name {
return v.x0, v.y0, v.x1, v.y1, nil
}
}
return 0, 0, 0, 0, ErrorUnkView
return 0, 0, 0, 0, ErrUnknownView
}
// DeleteView deletes a view by name.
@ -156,7 +167,7 @@ func (g *Gui) DeleteView(name string) error {
return nil
}
}
return ErrorUnkView
return ErrUnknownView
}
// SetCurrentView gives the focus to a given view.
@ -167,7 +178,7 @@ func (g *Gui) SetCurrentView(name string) error {
return nil
}
}
return ErrorUnkView
return ErrUnknownView
}
// CurrentView returns the currently focused view, or nil if no view
@ -205,7 +216,7 @@ func (g *Gui) SetLayout(layout func(*Gui) error) {
}
// MainLoop runs the main loop until an error is returned. A successful
// finish should return Quit.
// finish should return ErrQuit.
func (g *Gui) MainLoop() error {
go func() {
for {
@ -213,11 +224,11 @@ func (g *Gui) MainLoop() error {
}
}()
termbox.SetInputMode(termbox.InputAlt)
inputMode := termbox.InputAlt
if g.EnableMouse == true {
termbox.SetInputMode(termbox.InputMouse)
inputMode |= termbox.InputMouse
}
termbox.SetInputMode(inputMode)
if err := g.Flush(); err != nil {
return err
@ -459,23 +470,29 @@ func horizontalRune(ch rune) bool {
}
// onKey manages key-press events. A keybinding handler is called when
// a key-press 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.
func (g *Gui) onKey(ev *termbox.Event) error {
var curView *View
switch ev.Type {
case termbox.EventKey:
if g.currentView != nil && g.currentView.Editable && Edit != nil {
Edit(g.currentView, Key(ev.Key), ev.Ch, Modifier(ev.Mod))
}
var cv string
if g.currentView != nil {
cv = g.currentView.name
curView = g.currentView
case termbox.EventMouse:
if v, err := g.ViewByPosition(ev.MouseX, ev.MouseY); err == nil {
curView = v
}
}
for _, kb := range g.keybindings {
if kb.h == nil {
continue
}
if kb.matchKeypress(Key(ev.Key), ev.Ch, Modifier(ev.Mod)) && kb.matchView(cv) {
if err := kb.h(g, g.currentView); err != nil {
if kb.matchKeypress(Key(ev.Key), ev.Ch, Modifier(ev.Mod)) && kb.matchView(curView) {
if err := kb.h(g, curView); err != nil {
return err
}
}

View File

@ -7,12 +7,12 @@ package gocui
import "github.com/nsf/termbox-go"
type (
// Keys represent special keys or keys combinations.
// Key represents special keys or keys combinations.
Key termbox.Key
// Modifiers allow to define special keys combinations. They can be used
// 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
// KeybindingHandlers represent the actions linked to keybindings. The
// KeybindingHandler represents the actions linked to keybindings. The
// handler is called when a key-press event satisfies a configured
// keybinding.
KeybindingHandler func(*Gui, *View) error
@ -125,12 +125,15 @@ func newKeybinding(viewname string, key Key, ch rune, mod Modifier, h Keybinding
return kb
}
// match returns if the keybinding matches the keypress
// 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
}
// match returns if the keybinding matches the current view
func (kb *keybinding) matchView(viewname string) bool {
return kb.viewName == "" || kb.viewName == viewname
// 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
}