From faa12e105a1f0da348ec3ece5cf9380f1268baa9 Mon Sep 17 00:00:00 2001 From: Roi Martin Date: Sat, 23 Jan 2016 16:07:42 +0100 Subject: [PATCH] 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. --- README.md | 6 +-- _examples/delete.go | 11 +++--- _examples/demo.go | 10 ++--- _examples/layout.go | 10 ++--- _examples/mouse.go | 94 ++++++++++++++++++++++++++++++++++++++++++++ _examples/overlap.go | 22 +++++------ _examples/stdin.go | 8 ++-- _examples/wrap.go | 6 +-- attribute.go | 5 ++- doc.go | 6 +-- edit.go | 14 +++---- gui.go | 71 ++++++++++++++++++++------------- keybinding.go | 17 ++++---- 13 files changed, 198 insertions(+), 82 deletions(-) create mode 100644 _examples/mouse.go diff --git a/README.md b/README.md index addbaea..6d30109 100644 --- a/README.md +++ b/README.md @@ -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) } } diff --git a/_examples/delete.go b/_examples/delete.go index c7b1bfc..025bc28 100644 --- a/_examples/delete.go +++ b/_examples/delete.go @@ -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 diff --git a/_examples/demo.go b/_examples/demo.go index 755323d..6a52c9e 100644 --- a/_examples/demo.go +++ b/_examples/demo.go @@ -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) } } diff --git a/_examples/layout.go b/_examples/layout.go index 1afe70f..62d34d4 100644 --- a/_examples/layout.go +++ b/_examples/layout.go @@ -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) } } diff --git a/_examples/mouse.go b/_examples/mouse.go new file mode 100644 index 0000000..7c96eed --- /dev/null +++ b/_examples/mouse.go @@ -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 +} diff --git a/_examples/overlap.go b/_examples/overlap.go index 77da819..f2b8f0f 100644 --- a/_examples/overlap.go +++ b/_examples/overlap.go @@ -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) } } diff --git a/_examples/stdin.go b/_examples/stdin.go index 6334ec3..33725b3 100644 --- a/_examples/stdin.go +++ b/_examples/stdin.go @@ -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 { diff --git a/_examples/wrap.go b/_examples/wrap.go index 05c2a2b..b9b4055 100644 --- a/_examples/wrap.go +++ b/_examples/wrap.go @@ -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) } } diff --git a/attribute.go b/attribute.go index 595b2b1..bad758a 100644 --- a/attribute.go +++ b/attribute.go @@ -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. diff --git a/doc.go b/doc.go index 69061ce..2be9865 100644 --- a/doc.go +++ b/doc.go @@ -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) } } diff --git a/edit.go b/edit.go index ab64a6b..a49bfda 100644 --- a/edit.go +++ b/edit.go @@ -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 diff --git a/gui.go b/gui.go index d96a768..db37e18 100644 --- a/gui.go +++ b/gui.go @@ -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 { - if g.currentView != nil && g.currentView.Editable && Edit != nil { - Edit(g.currentView, Key(ev.Key), ev.Ch, Modifier(ev.Mod)) + 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)) + } + curView = g.currentView + case termbox.EventMouse: + if v, err := g.ViewByPosition(ev.MouseX, ev.MouseY); err == nil { + curView = v + } } - var cv string - if g.currentView != nil { - cv = g.currentView.name - } 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 } } diff --git a/keybinding.go b/keybinding.go index bfc4e93..376e9a6 100644 --- a/keybinding.go +++ b/keybinding.go @@ -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 }