termui/events.go

255 lines
5.2 KiB
Go
Raw Normal View History

2017-01-13 23:07:43 -07:00
// 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
2015-08-19 13:22:53 -06:00
import (
2015-09-18 09:41:44 -06:00
"strconv"
2015-08-30 21:03:47 -06:00
"sync"
tb "github.com/nsf/termbox-go"
)
/*
Here's the list of events which can be assigned handlers using Handle():
mouse events:
<MouseLeft> <MouseRight> <MouseMiddle>
<MouseWheelUp> <MouseWheelDown>
keyboard events:
any uppercase or lowercase letter or a set of two letters like j or jj or J or JJ
<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 eventStream struct {
sync.RWMutex
handlers map[string]func(Event)
stopLoop chan bool
eventQueue chan tb.Event // list of events from termbox
hook func(Event)
}
var defaultES = eventStream{
handlers: make(map[string]func(Event)),
stopLoop: make(chan bool, 1),
eventQueue: make(chan tb.Event),
hook: DefaultHandler,
}
// Event contains an ID used for Handle() and an optional payload.
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
}
// handleEvent calls the approriate callback function if there is one.
func handleEvent(e Event) {
if val, ok := defaultES.handlers[e.ID]; ok {
val(e)
}
}
// Loop gets events from termbox and passes them off to handleEvent.
// Stops when StopLoop is called.
func Loop() {
go func() {
for {
defaultES.eventQueue <- tb.PollEvent()
}
}()
for {
select {
case <-defaultES.stopLoop:
return
case e := <-defaultES.eventQueue:
ne := convertTermboxEvent(e)
defaultES.RLock()
handleEvent(ne)
defaultES.hook(ne)
defaultES.RUnlock()
}
}
2015-08-30 21:03:47 -06:00
}
// StopLoop stops the event loop.
func StopLoop() {
defaultES.stopLoop <- true
}
// Handle assigns event names to their handlers. Takes a string, strings, or a slice of strings, and a function.
func Handle(things ...interface{}) {
function := things[len(things)-1].(func(Event))
for _, thing := range things {
if value, ok := thing.(string); ok {
defaultES.Lock()
defaultES.handlers[value] = function
defaultES.Unlock()
}
if value, ok := thing.([]string); ok {
defaultES.Lock()
for _, name := range value {
defaultES.handlers[name] = function
}
defaultES.Unlock()
}
}
2015-08-30 21:03:47 -06:00
}
func EventHook(f func(Event)) {
defaultES.Lock()
defaultES.hook = f
defaultES.Unlock()
}
2015-09-18 09:41:44 -06:00
// 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 {
2015-09-18 09:41:44 -06:00
k := string(e.Ch)
pre := ""
mod := ""
if e.Mod == tb.ModAlt {
mod = "<M-"
2015-09-18 09:41:44 -06:00
}
if e.Ch == 0 {
if e.Key > 0xFFFF-12 {
k = "<f" + strconv.Itoa(0xFFFF-int(e.Key)+1) + ">"
} else if e.Key > 0xFFFF-25 {
ks := []string{"<Insert>", "<Delete>", "<Home>", "<End>", "<Previous>", "<Next>", "<Up>", "<Down>", "<Left>", "<Right>"}
2015-09-18 09:41:44 -06:00
k = ks[0xFFFF-int(e.Key)-12]
}
if e.Key <= 0x7F {
pre = "<C-"
2015-09-18 09:41:44 -06:00
k = string('a' - 1 + int(e.Key))
kmap := map[tb.Key][2]string{
tb.KeyCtrlSpace: {"C-", "<Space>"},
tb.KeyBackspace: {"", "<Backspace>"},
tb.KeyTab: {"", "<Tab>"},
tb.KeyEnter: {"", "<Enter>"},
tb.KeyEsc: {"", "<Escape>"},
tb.KeyCtrlBackslash: {"C-", "\\"},
tb.KeyCtrlSlash: {"C-", "/"},
tb.KeySpace: {"", "<Space>"},
tb.KeyCtrl8: {"C-", "8"},
2015-09-18 09:41:44 -06:00
}
if sk, ok := kmap[e.Key]; ok {
pre = sk[0]
k = sk[1]
}
}
}
if pre != "" {
k += ">"
2015-09-18 09:41:44 -06:00
}
id := pre + mod + k
2017-05-27 04:11:46 -06:00
return Event{
Type: KeyboardEvent,
ID: id,
2018-01-11 11:01:45 -07:00
}
2017-05-27 04:11:46 -06:00
}
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>",
2015-08-30 21:03:47 -06:00
}
2015-09-18 09:41:44 -06:00
converted, ok := mouseButtonMap[e.Key]
if !ok {
converted = "Unknown_Mouse_Button"
2015-09-18 09:41:44 -06:00
}
Drag := false
if e.Mod == tb.ModMotion {
Drag = true
2015-08-19 13:22:53 -06:00
}
return Event{
Type: MouseEvent,
ID: converted,
Payload: Mouse{
X: e.MouseX,
Y: e.MouseY,
Drag: Drag,
},
2015-09-18 09:41:44 -06:00
}
2015-10-07 12:25:59 -06:00
}
// convertTermboxEvent turns a termbox event into a termui event.
func convertTermboxEvent(e tb.Event) Event {
if e.Type == tb.EventError {
panic(e.Err)
}
2015-08-08 17:07:32 -06:00
switch e.Type {
case tb.EventKey:
return convertTermboxKeyboardEvent(e)
case tb.EventMouse:
return convertTermboxMouseEvent(e)
case tb.EventResize:
return Event{
Type: ResizeEvent,
ID: "<Resize>",
Payload: Resize{
Width: e.Width,
Height: e.Height,
},
2015-10-07 12:25:59 -06:00
}
2015-09-18 09:41:44 -06:00
}
return Event{}
2015-09-18 09:41:44 -06:00
}
var DefaultHandler = func(e Event) {
}
2015-10-13 10:45:03 -06:00
func ResetHandlers() {
defaultES.Lock()
defaultES.handlers = make(map[string]func(Event))
defaultES.Unlock()
}
2015-10-13 10:45:03 -06:00
func ResetHandler(handle string) {
defaultES.Lock()
delete(defaultES.handlers, handle)
defaultES.Unlock()
2015-10-13 10:45:03 -06:00
}