termui/events.go
2019-01-24 05:23:30 -08:00

276 lines
5.5 KiB
Go

// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.
package termui
import (
"fmt"
tb "github.com/nsf/termbox-go"
)
/*
List of events:
mouse events:
<MouseLeft> <MouseRight> <MouseMiddle>
<MouseWheelUp> <MouseWheelDown>
keyboard events:
any uppercase or lowercase letter like j or J
<C-d> etc
<M-d> etc
<Up> <Down> <Left> <Right>
<Insert> <Delete> <Home> <End> <Previous> <Next>
<Backspace> <Tab> <Enter> <Escape> <Space>
<C-<Space>> etc
terminal events:
<Resize>
*/
type EventType int
const (
KeyboardEvent EventType = iota
MouseEvent
ResizeEvent
)
type Event struct {
Type EventType
ID string
Payload interface{}
}
// Mouse payload.
type Mouse struct {
Drag bool
X int
Y int
}
// Resize payload.
type Resize struct {
Width int
Height int
}
// PollEvents gets events from termbox, converts them, then sends them to each of its channels.
func PollEvents() <-chan Event {
ch := make(chan Event)
go func() {
for {
ch <- convertTermboxEvent(tb.PollEvent())
}
}()
return ch
}
// convertTermboxKeyboardEvent converts a termbox keyboard event to a more friendly string format.
// Combines modifiers into the string instead of having them as additional fields in an event.
func convertTermboxKeyboardEvent(e tb.Event) Event {
ID := "%s"
if e.Mod == tb.ModAlt {
ID = "<M-%s>"
}
if e.Ch != 0 {
ID = fmt.Sprintf(ID, string(e.Ch))
} else {
switchExpression := func() string {
switch e.Key {
case tb.KeyF1:
return "<F1>"
case tb.KeyF2:
return "<F2>"
case tb.KeyF3:
return "<F3>"
case tb.KeyF4:
return "<F4>"
case tb.KeyF5:
return "<F5>"
case tb.KeyF6:
return "<F6>"
case tb.KeyF7:
return "<F7>"
case tb.KeyF8:
return "<F8>"
case tb.KeyF9:
return "<F9>"
case tb.KeyF10:
return "<F10>"
case tb.KeyF11:
return "<F11>"
case tb.KeyF12:
return "<F12>"
case tb.KeyInsert:
return "<Insert>"
case tb.KeyDelete:
return "<Delete>"
case tb.KeyHome:
return "<Home>"
case tb.KeyEnd:
return "<End>"
case tb.KeyPgup:
return "<PageUp>"
case tb.KeyPgdn:
return "<PageDown>"
case tb.KeyArrowUp:
return "<Up>"
case tb.KeyArrowDown:
return "<Down>"
case tb.KeyArrowLeft:
return "<Left>"
case tb.KeyArrowRight:
return "<Right>"
case tb.KeyCtrlTilde: // tb.KeyCtrl2 tb.KeyCtrlSpace
// <C-~> doesn't work
// <C-2> doesn't work
return "<C-<Space>>"
case tb.KeyCtrlA:
return "<C-a>"
case tb.KeyCtrlB:
return "<C-b>"
case tb.KeyCtrlC:
return "<C-c>"
case tb.KeyCtrlD:
return "<C-d>"
case tb.KeyCtrlE:
return "<C-e>"
case tb.KeyCtrlF:
return "<C-f>"
case tb.KeyCtrlG:
return "<C-g>"
case tb.KeyBackspace: // tb.KeyCtrlH
// <C-h> doesn't work
return "<C-<Backspace>>"
case tb.KeyTab: // tb.KeyCtrlI
// <C-i> doesn't work
return "<Tab>"
case tb.KeyCtrlJ:
return "<C-j>"
case tb.KeyCtrlK:
return "<C-k>"
case tb.KeyCtrlL:
return "<C-l>"
case tb.KeyEnter: // tb.KeyCtrlM
// <C-m> doesn't work
return "<Enter>"
case tb.KeyCtrlN:
return "<C-n>"
case tb.KeyCtrlO:
return "<C-o>"
case tb.KeyCtrlP:
return "<C-p>"
case tb.KeyCtrlQ:
return "<C-q>"
case tb.KeyCtrlR:
return "<C-r>"
case tb.KeyCtrlS:
return "<C-s>"
case tb.KeyCtrlT:
return "<C-t>"
case tb.KeyCtrlU:
return "<C-u>"
case tb.KeyCtrlV:
return "<C-v>"
case tb.KeyCtrlW:
return "<C-w>"
case tb.KeyCtrlX:
return "<C-x>"
case tb.KeyCtrlY:
return "<C-y>"
case tb.KeyCtrlZ:
return "<C-z>"
case tb.KeyEsc: // tb.KeyCtrlLsqBracket tb.KeyCtrl3
// <C-[> doesn't work
// <C-3> doesn't work
return "<Escape>"
case tb.KeyCtrl4: // tb.KeyCtrlBackslash
// <C-\\> doesn't work
return "<C-4>"
case tb.KeyCtrl5: // tb.KeyCtrlRsqBracket
// <C-]> doesn't work
return "<C-5>"
case tb.KeyCtrl6:
return "<C-6>"
case tb.KeyCtrl7: // tb.KeyCtrlSlash tb.KeyCtrlUnderscore
// <C-/> doesn't work
// <C-_> doesn't work
return "<C-7>"
case tb.KeySpace:
return "<Space>"
case tb.KeyBackspace2: // tb.KeyCtrl8:
// <C-8> doesn't work
return "<Backspace>"
}
// <C--> doesn't work
return ""
}
ID = fmt.Sprintf(ID, switchExpression())
}
return Event{
Type: KeyboardEvent,
ID: ID,
}
}
func convertTermboxMouseEvent(e tb.Event) Event {
mouseButtonMap := map[tb.Key]string{
tb.MouseLeft: "<MouseLeft>",
tb.MouseMiddle: "<MouseMiddle>",
tb.MouseRight: "<MouseRight>",
tb.MouseRelease: "<MouseRelease>",
tb.MouseWheelUp: "<MouseWheelUp>",
tb.MouseWheelDown: "<MouseWheelDown>",
}
converted, ok := mouseButtonMap[e.Key]
if !ok {
converted = "Unknown_Mouse_Button"
}
Drag := false
if e.Mod == tb.ModMotion {
Drag = true
}
return Event{
Type: MouseEvent,
ID: converted,
Payload: Mouse{
X: e.MouseX,
Y: e.MouseY,
Drag: Drag,
},
}
}
// convertTermboxEvent turns a termbox event into a termui event.
func convertTermboxEvent(e tb.Event) Event {
if e.Type == tb.EventError {
panic(e.Err)
}
var event Event
switch e.Type {
case tb.EventKey:
event = convertTermboxKeyboardEvent(e)
case tb.EventMouse:
event = convertTermboxMouseEvent(e)
case tb.EventResize:
event = Event{
Type: ResizeEvent,
ID: "<Resize>",
Payload: Resize{
Width: e.Width,
Height: e.Height,
},
}
}
return event
}