Have added prompting.

This commit is contained in:
Leon Mika 2017-08-26 09:48:22 +10:00
parent 665bc8cdae
commit 33847a78c1
10 changed files with 847 additions and 774 deletions

View file

@ -1,33 +1,27 @@
package main
import (
"./ui"
"bitbucket.org/lmika/ted-v2/ui"
)
const (
ModAlt rune = 1 << 31 - 1
ModAlt rune = 1<<31 - 1
)
// A command
type Command struct {
Name string
Doc string
Action func(ctx CommandContext) error
// TODO: Add argument mapping which will fetch properties from the environment
Action func(ctx *CommandContext) error
}
// Execute the command
func (cmd *Command) Do(ctx CommandContext) error {
func (cmd *Command) Do(ctx *CommandContext) error {
return cmd.Action(ctx)
}
// The command context
type CommandContext interface {
// Returns the current frame. If no frame is defined, returns nil.
Frame() *Frame
}
// A command mapping
type CommandMapping struct {
Commands map[string]*Command
@ -40,7 +34,7 @@ func NewCommandMapping() *CommandMapping {
}
// Adds a new command
func (cm *CommandMapping) Define(name string, doc string, opts string, fn func(ctx CommandContext) error) {
func (cm *CommandMapping) Define(name string, doc string, opts string, fn func(ctx *CommandContext) error) {
cm.Commands[name] = &Command{name, doc, fn}
}
@ -79,7 +73,7 @@ func (cm *CommandMapping) RegisterViewCommands() {
cm.Define("row-bottom", "Moves the cursor to the bottom of the row", "", gridNavOperation(func(grid *ui.Grid) {
cellX, _ := grid.CellPosition()
_, dimY := grid.Model().Dimensions()
grid.MoveTo(cellX, dimY - 1)
grid.MoveTo(cellX, dimY-1)
}))
cm.Define("col-left", "Moves the cursor to the left-most column", "", gridNavOperation(func(grid *ui.Grid) {
_, cellY := grid.CellPosition()
@ -89,8 +83,31 @@ func (cm *CommandMapping) RegisterViewCommands() {
cm.Define("col-right", "Moves the cursor to the right-most column", "", gridNavOperation(func(grid *ui.Grid) {
_, cellY := grid.CellPosition()
dimX, _ := grid.Model().Dimensions()
grid.MoveTo(dimX - 1, cellY)
grid.MoveTo(dimX-1, cellY)
}))
cm.Define("enter-command", "Enter command", "", func(ctx *CommandContext) error {
ctx.Frame().Prompt(": ", func(res string) {
ctx.Frame().Message("Command = " + res)
})
return nil
})
cm.Define("set-cell", "Change the value of the selected cell", "", func(ctx *CommandContext) error {
grid := ctx.Frame().Grid()
cellX, cellY := grid.CellPosition()
if rwModel, isRwModel := ctx.Session().Model.(RWModel); isRwModel {
ctx.Frame().Prompt("> ", func(res string) {
rwModel.SetCellValue(cellY, cellX, res)
})
}
return nil
})
cm.Define("quit", "Quit TED", "", func(ctx *CommandContext) error {
ctx.Session().UIManager.Shutdown()
return nil
})
}
// Registers the standard view key bindings. These commands require the frame
@ -113,14 +130,17 @@ func (cm *CommandMapping) RegisterViewKeyBindings() {
cm.MapKey(ui.KeyArrowLeft, cm.Command("move-left"))
cm.MapKey(ui.KeyArrowRight, cm.Command("move-right"))
cm.MapKey(':', cm.Command("enter-command"))
}
cm.MapKey('e', cm.Command("set-cell"))
cm.MapKey(':', cm.Command("enter-command"))
cm.MapKey('q', cm.Command("quit"))
}
// A nativation command factory. This will perform the passed in operation with the current grid and
// will display the cell value in the message box.
func gridNavOperation(op func(grid *ui.Grid)) func(ctx CommandContext) error {
return func(ctx CommandContext) error {
func gridNavOperation(op func(grid *ui.Grid)) func(ctx *CommandContext) error {
return func(ctx *CommandContext) error {
op(ctx.Frame().Grid())
ctx.Frame().ShowCellValue()
return nil

View file

@ -1,20 +1,27 @@
package main
import (
"./ui"
"bitbucket.org/lmika/ted-v2/ui"
)
type Mode int
const (
NilMode Mode = iota
// The grid is selectable
GridMode Mode = iota
GridMode
// EntryMode is when the text entry is selected
EntryMode
)
// A frame is a UI instance.
type Frame struct {
Session *Session
mode Mode
uiManager *ui.Ui
clientArea *ui.RelativeLayout
grid *ui.Grid
@ -41,7 +48,7 @@ func NewFrame(uiManager *ui.Ui) *Frame {
statusLayout.Append(frame.statusBar)
statusLayout.Append(frame.textEntrySwitch)
frame.clientArea = &ui.RelativeLayout{ Client: frame.grid, South: statusLayout }
frame.clientArea = &ui.RelativeLayout{Client: frame.grid, South: statusLayout}
return frame
}
@ -60,14 +67,51 @@ func (frame *Frame) Grid() *ui.Grid {
return frame.grid
}
// Sets the specific mode.
func (frame *Frame) EnterMode(mode Mode) {
// Enter the specific mode.
func (frame *Frame) enterMode(mode Mode) {
switch mode {
case GridMode:
frame.uiManager.SetFocusedComponent(frame)
case EntryMode:
frame.textEntrySwitch.Component = frame.textEntry
frame.uiManager.SetFocusedComponent(frame.textEntry)
}
}
// Exit the specific mode.
func (frame *Frame) exitMode(mode Mode) {
switch mode {
case EntryMode:
frame.textEntrySwitch.Component = frame.messageView
}
}
func (frame *Frame) setMode(mode Mode) {
frame.exitMode(frame.mode)
frame.mode = mode
frame.enterMode(frame.mode)
}
// Message sets the message view's message
func (frame *Frame) Message(s string) {
frame.messageView.Text = s
}
// Prompt the user for input. This switches the mode to entry mode.
func (frame *Frame) Prompt(prompt string, callback func(res string)) {
frame.textEntry.Prompt = prompt
frame.textEntry.SetValue("")
frame.textEntry.OnEntry = func(res string) {
frame.textEntry.OnEntry = nil
frame.setMode(GridMode)
callback(res)
}
frame.setMode(EntryMode)
}
// Show a message. This will switch the bottom to the messageView and select the frame
func (frame *Frame) ShowMessage(msg string) {
frame.messageView.Text = msg

View file

@ -1,7 +1,7 @@
package main
import (
"./ui"
"bitbucket.org/lmika/ted-v2/ui"
)
func main() {
@ -12,12 +12,13 @@ func main() {
defer uiManager.Close()
model := &StdModel{}
model.Resize(5, 5)
frame := NewFrame(uiManager)
NewSession(frame, model)
NewSession(uiManager, frame, model)
uiManager.SetRootComponent(frame.RootComponent())
frame.EnterMode(GridMode)
frame.enterMode(GridMode)
uiManager.Loop()
}

View file

@ -1,6 +1,6 @@
package main
import "./ui"
import "bitbucket.org/lmika/ted-v2/ui"
// The session is responsible for managing the UI and the model and handling
// the interaction between the two and the user.
@ -8,13 +8,15 @@ type Session struct {
Model Model
Frame *Frame
Commands *CommandMapping
UIManager *ui.Ui
}
func NewSession(frame *Frame, model Model) *Session {
func NewSession(uiManager *ui.Ui, frame *Frame, model Model) *Session {
session := &Session{
Model: model,
Frame: frame,
Commands: NewCommandMapping(),
UIManager: uiManager,
}
frame.SetModel(&SessionGridModel{session})
@ -31,30 +33,31 @@ func NewSession(frame *Frame, model Model) *Session {
// Input from the frame
func (session *Session) KeyPressed(key rune, mod int) {
// Add the mod key modifier
if (mod & ui.ModKeyAlt != 0) {
if mod&ui.ModKeyAlt != 0 {
key |= ModAlt
}
cmd := session.Commands.KeyMapping(key)
if cmd != nil {
err := cmd.Do(SessionCommandContext{session})
err := cmd.Do(&CommandContext{session})
if err != nil {
session.Frame.ShowMessage(err.Error())
}
}
}
// The command context used by the session
type SessionCommandContext struct {
Session *Session
type CommandContext struct {
session *Session
}
func (scc SessionCommandContext) Frame() *Frame {
return scc.Session.Frame
func (scc *CommandContext) Session() *Session {
return scc.session
}
func (scc *CommandContext) Frame() *Frame {
return scc.session.Frame
}
// Session grid model
type SessionGridModel struct {

View file

@ -19,12 +19,11 @@ type DrawContext struct {
fa, ba Attribute
}
// Returns a new subcontext. The sub-context must be an area within the current context.
func (dc *DrawContext) NewSubContext(offsetX, offsetY, width, height int) *DrawContext {
return &DrawContext{
X: intMax(dc.X + offsetX, dc.X),
Y: intMax(dc.Y + offsetY, dc.Y),
X: intMax(dc.X+offsetX, dc.X),
Y: intMax(dc.Y+offsetY, dc.Y),
W: intMax(width, 0),
H: intMax(height, 0),
@ -42,7 +41,6 @@ func (dc *DrawContext) SetBgAttr(attr Attribute) {
dc.ba = attr
}
// Draws a horizontal rule with a specific rune.
func (dc *DrawContext) HorizRule(y int, ch rune) {
for x := 0; x < dc.W; x++ {
@ -61,7 +59,7 @@ func (dc *DrawContext) Print(x, y int, str string) {
// Prints a right-justified string at a specific offset. This will be bounded by the size of the drawing context.
func (dc *DrawContext) PrintRight(x, y int, str string) {
l := len(str)
dc.Print(x - l, y, str)
dc.Print(x-l, y, str)
}
// Draws a rune at a local point X, Y with the current foreground and background attributes
@ -85,8 +83,13 @@ func (dc *DrawContext) SetCursorPosition(x, y int) {
}
}
// HideCursor hides the cursor
func (dc *DrawContext) HideCursor() {
dc.driver.HideCursor()
}
// Converts a local point to a real point. If the point is within the context, also returns true
func (dc *DrawContext) localPointToRealPoint(x, y int) (int, int, bool) {
rx, ry := x + dc.X, y + dc.Y
return rx, ry, (rx >= dc.X) && (ry >= dc.Y) && (rx < dc.X + dc.W) && (ry < dc.Y + dc.H)
rx, ry := x+dc.X, y+dc.Y
return rx, ry, (rx >= dc.X) && (ry >= dc.Y) && (rx < dc.X+dc.W) && (ry < dc.Y+dc.H)
}

View file

@ -25,7 +25,6 @@ const (
AttrReverse
)
// Special keys
const (
KeyCtrlSpace rune = 0x8000 + iota
@ -115,7 +114,6 @@ type Event struct {
Ch rune
}
// The terminal driver interface.
type Driver interface {
@ -139,4 +137,7 @@ type Driver interface {
// Move the position of the cursor
SetCursor(x, y int)
// Hide the cursor
HideCursor()
}

View file

@ -5,7 +5,6 @@ package ui
import "strconv"
/**
* An abstract display model.
*/
@ -31,7 +30,6 @@ type GridModel interface {
CellValue(int, int) string
}
type gridPoint int
/**
@ -65,7 +63,6 @@ func newGridRect(x1, y1, x2, y2 int) gridRect {
return gridRect{gridPoint(x1), gridPoint(y1), gridPoint(x2), gridPoint(y2)}
}
/**
* Creates a new grid.
*/
@ -91,7 +88,6 @@ func (grid *Grid) ShiftBy(x int, y int) {
grid.viewCellY += y
}
// Returns the display value of the currently selected cell.
func (grid *Grid) CurrentCellDisplayValue() string {
if grid.isCellValid(grid.selCellX, grid.selCellY) {
@ -104,15 +100,15 @@ func (grid *Grid) CurrentCellDisplayValue() string {
// Moves the currently selected cell by a delta. This will be implemented as single stepped
// moveTo calls to handle invalid cells.
func (grid *Grid) MoveBy(x int, y int) {
grid.MoveTo(grid.selCellX + x, grid.selCellY + y)
grid.MoveTo(grid.selCellX+x, grid.selCellY+y)
}
// Moves the currently selected cell to a specific row. The row must be valid, otherwise the
// currently selected cell will not be changed. Returns true if the move was successful
func (grid *Grid) MoveTo(newX, newY int) {
maxX, maxY := grid.model.Dimensions()
newX = intMinMax(newX, 0, maxX - 1)
newY = intMinMax(newY, 0, maxY - 1)
newX = intMinMax(newX, 0, maxX-1)
newY = intMinMax(newY, 0, maxY-1)
if grid.isCellValid(newX, newY) {
grid.selCellX = newX
@ -132,7 +128,6 @@ func (grid *Grid) isCellValid(x int, y int) bool {
return (x >= 0) && (y >= 0) && (x < maxX) && (y < maxY)
}
// Determine the topmost cell based on the location of the currently selected cell
func (grid *Grid) reposition() {
@ -154,7 +149,6 @@ func (grid *Grid) reposition() {
}
}
// Gets the cell value and attributes of a particular cell
func (grid *Grid) getCellData(cellX, cellY int) (text string, fg, bg Attribute) {
// The fixed cells
@ -164,17 +158,17 @@ func (grid *Grid) getCellData(cellX, cellY int) (text string, fg, bg Attribute)
if (cellX == 0) && (cellY == 0) {
return "", AttrBold, AttrBold
} else if (cellX == 0) {
if (modelCellY == grid.selCellY) {
return strconv.Itoa(modelCellY), AttrBold | AttrReverse, AttrBold | AttrReverse
} else if cellX == 0 {
if modelCellY == grid.selCellY {
return strconv.Itoa(modelCellY), AttrBold | AttrReverse, AttrReverse
} else {
return strconv.Itoa(modelCellY), AttrBold, AttrBold
return strconv.Itoa(modelCellY), AttrBold, 0
}
} else if (cellY == 0) {
if (modelCellX == grid.selCellX) {
return strconv.Itoa(modelCellX), AttrBold | AttrReverse, AttrBold | AttrReverse
} else if cellY == 0 {
if modelCellX == grid.selCellX {
return strconv.Itoa(modelCellX), AttrBold | AttrReverse, AttrReverse
} else {
return strconv.Itoa(modelCellX), AttrBold, AttrBold
return strconv.Itoa(modelCellX), AttrBold, 0
}
} else {
// The data from the model
@ -214,9 +208,9 @@ func (grid *Grid) getCellDimensions(cellX, cellY int) (width, height int) {
if (cellX == 0) && (cellY == 0) {
return 8, 1
} else if (cellX == 0) {
} else if cellX == 0 {
return 8, cellHeight
} else if (cellY == 0) {
} else if cellY == 0 {
return cellWidth, 1
} else {
return cellWidth, cellHeight
@ -227,7 +221,6 @@ func (grid *Grid) getCellDimensions(cellX, cellY int) (width, height int) {
return 0, 0
}
/**
* Renders a cell which contains text. The clip rectangle defines the size of the cell, as well as the left offset
* of the cell. The sx and sy determine the screen position of the cell top-left.
@ -236,7 +229,7 @@ func (grid *Grid) renderCell(ctx *DrawContext, cellClipRect gridRect, sx int, sy
for x := cellClipRect.x1; x <= cellClipRect.x2; x++ {
for y := cellClipRect.y1; y <= cellClipRect.y2; y++ {
currRune := ' '
if (y == 0) {
if y == 0 {
textPos := int(x)
if textPos < len(text) {
currRune = rune(text[textPos])
@ -244,12 +237,11 @@ func (grid *Grid) renderCell(ctx *DrawContext, cellClipRect gridRect, sx int, sy
}
// TODO: This might be better if this wasn't so low-level
ctx.DrawRuneWithAttrs(int(x - cellClipRect.x1) + sx, int(y - cellClipRect.y1) + sy, currRune, fg, bg)
ctx.DrawRuneWithAttrs(int(x-cellClipRect.x1)+sx, int(y-cellClipRect.y1)+sy, currRune, fg, bg)
}
}
}
// Renders a column. The viewport determines the maximum position of the rendered cell. CellX and CellY are the
// cell indicies to render, cellOffset are the LOCAL offset of the cell.
// This function will return the new X position (gridRect.x1 + colWidth)
@ -276,13 +268,13 @@ func (grid *Grid) renderColumn(ctx *DrawContext, screenViewPort gridRect, cellX
// Cap the row height if it will go beyond the edge of the viewport.
_, rowHeight := grid.getCellDimensions(cellX, cellY)
if screenY + rowHeight > maxScreenY {
if screenY+rowHeight > maxScreenY {
rowHeight = maxScreenY - screenY
}
cellText, cellFg, cellBg := grid.getCellData(cellX, cellY)
grid.renderCell(ctx, newGridRect(cellOffsetX, cellOffsetY, colWidth - cellOffsetX, rowHeight),
grid.renderCell(ctx, newGridRect(cellOffsetX, cellOffsetY, colWidth-cellOffsetX, rowHeight),
screenX, screenY, cellText, cellFg, cellBg) // termbox.AttrReverse, termbox.AttrReverse
cellY++
@ -294,7 +286,6 @@ func (grid *Grid) renderColumn(ctx *DrawContext, screenViewPort gridRect, cellX
return gridPoint(screenX + colWidth), cellsHigh
}
// Renders the grid. Returns the number of cells in the X and Y direction were rendered.
//
func (grid *Grid) renderGrid(ctx *DrawContext, screenViewPort gridRect, cellX int, cellY int, cellOffsetX int, cellOffsetY int) (int, int) {
@ -312,7 +303,6 @@ func (grid *Grid) renderGrid(ctx *DrawContext, screenViewPort gridRect, cellX in
return cellsWide, cellsHigh
}
/**
* Returns the cell of the particular point, along with the top-left position of the cell.
*/
@ -326,7 +316,7 @@ func (grid *Grid) pointToCell(x int, y int) (cellX int, cellY int, posX int, pos
// Go through columns to locate the particular cellX
for cx := 0; cx < wid; cx++ {
if (x >= posX) && (x < posX + grid.model.ColWidth(cx)) {
if (x >= posX) && (x < posX+grid.model.ColWidth(cx)) {
// We found the X position
cellX = int(cx)
break
@ -334,7 +324,7 @@ func (grid *Grid) pointToCell(x int, y int) (cellX int, cellY int, posX int, pos
}
for cy := 0; cy < hei; cy++ {
if (y >= posY) && (y < posY + grid.model.RowHeight(cy)) {
if (y >= posY) && (y < posY+grid.model.RowHeight(cy)) {
// And the Y position
cellY = int(cy)
break
@ -374,7 +364,6 @@ func (grid *Grid) KeyPressed(key rune, mod int) {
}
}
// --------------------------------------------------------------------------------------------
// Test Model

View file

@ -3,7 +3,6 @@
package ui
// The UI manager
type Ui struct {
// The root component
@ -12,9 +11,9 @@ type Ui struct {
drawContext *DrawContext
driver Driver
shutdown bool
}
// Creates a new UI context. This also initializes the UI state.
// Returns the context and an error.
func NewUI() (*Ui, error) {
@ -25,13 +24,12 @@ func NewUI() (*Ui, error) {
return nil, err
}
drawContext := &DrawContext{ driver: driver }
ui := &Ui{ drawContext: drawContext, driver: driver }
drawContext := &DrawContext{driver: driver}
ui := &Ui{drawContext: drawContext, driver: driver}
return ui, nil
}
// Closes the UI context.
func (ui *Ui) Close() {
ui.driver.Close()
@ -65,10 +63,14 @@ func (ui *Ui) Redraw() {
ui.driver.Sync()
}
// Quit indicates to the UI that it should shutdown
func (ui *Ui) Shutdown() {
ui.shutdown = true
}
// Enter the UI loop
func (ui *Ui) Loop() {
for {
for !ui.shutdown {
ui.Redraw()
event := ui.driver.WaitForEvent()

View file

@ -4,7 +4,6 @@ package ui
import "unicode"
// A text component. This simply renders a text string.
type TextView struct {
@ -24,10 +23,9 @@ func (tv *TextView) Redraw(context *DrawContext) {
context.HorizRule(0, ' ')
context.Print(0, 0, tv.Text)
context.HideCursor()
}
// Status bar component. This component displays text on the left and right of it's
// allocated space.
type StatusBar struct {
@ -50,7 +48,6 @@ func (sbar *StatusBar) Redraw(context *DrawContext) {
context.PrintRight(context.W, 0, sbar.Right)
}
// A single-text entry component.
type TextEntry struct {
Prompt string
@ -58,6 +55,9 @@ type TextEntry struct {
value string
cursorOffset int
displayOffset int
// Called when the user presses Enter
OnEntry func(val string)
}
func (te *TextEntry) Remeasure(w, h int) (int, int) {
@ -77,8 +77,8 @@ func (te *TextEntry) Redraw(context *DrawContext) {
valueOffsetX = len(te.Prompt)
}
context.Print(valueOffsetX, 0, te.value[displayOffsetX:intMin(displayOffsetX + context.W,len(te.value))])
context.SetCursorPosition(te.cursorOffset + valueOffsetX - displayOffsetX, 0)
context.Print(valueOffsetX, 0, te.value[displayOffsetX:intMin(displayOffsetX+context.W, len(te.value))])
context.SetCursorPosition(te.cursorOffset+valueOffsetX-displayOffsetX, 0)
//context.Print(0, 0, fmt.Sprintf("%d,%d", te.cursorOffset, displayOffsetX))
}
@ -91,37 +91,46 @@ func (te *TextEntry) calculateDisplayOffset(displayWidth int) int {
if virtualCursorOffset >= displayWidth {
te.displayOffset = te.cursorOffset - displayWidth + 10
} else if (virtualCursorOffset < 0) {
te.displayOffset = intMax(te.cursorOffset - displayWidth + 1, 0)
} else if virtualCursorOffset < 0 {
te.displayOffset = intMax(te.cursorOffset-displayWidth+1, 0)
}
return te.displayOffset
}
// SetValue sets the value of the text entry
func (te *TextEntry) SetValue(val string) {
te.value = val
te.cursorOffset = len(val)
}
func (te *TextEntry) KeyPressed(key rune, mod int) {
if (key >= ' ') && (key <= '~') {
te.insertRune(key)
} else if (key == KeyArrowLeft) {
} else if key == KeyArrowLeft {
te.moveCursorBy(-1)
} else if (key == KeyArrowRight) {
} else if key == KeyArrowRight {
te.moveCursorBy(1)
} else if (key == KeyHome) {
} else if key == KeyHome {
te.moveCursorTo(0)
} else if (key == KeyEnd) {
} else if key == KeyEnd {
te.moveCursorTo(len(te.value))
} else if (key == KeyBackspace) || (key == KeyBackspace2) {
if (mod & ModKeyAlt != 0) {
if mod&ModKeyAlt != 0 {
te.backspaceWhile(unicode.IsSpace)
te.backspaceWhile(func(r rune) bool { return !unicode.IsSpace(r) })
} else {
te.backspace()
}
} else if (key == KeyCtrlK) {
} else if key == KeyCtrlK {
te.killLine()
} else if (key == KeyDelete) {
} else if key == KeyDelete {
te.removeCharAtPos(te.cursorOffset)
} else if (key == KeyEnter) {
panic("Entered text: '" + te.value + "'")
} else if key == KeyEnter {
//panic("Entered text: '" + te.value + "'")
if te.OnEntry != nil {
te.OnEntry(te.value)
}
}
}
@ -133,8 +142,8 @@ func (te *TextEntry) backspace() {
// Backspace while the character underneith the cursor matches the guard
func (te *TextEntry) backspaceWhile(guard func(r rune) bool) {
for (te.cursorOffset > 0) {
ch := rune(te.value[te.cursorOffset - 1])
for te.cursorOffset > 0 {
ch := rune(te.value[te.cursorOffset-1])
if guard(ch) {
te.backspace()
} else {
@ -146,7 +155,7 @@ func (te *TextEntry) backspaceWhile(guard func(r rune) bool) {
// Kill the line. If the cursor is at the end of the line, kill to the start.
// Otherwise, trim the line.
func (te *TextEntry) killLine() {
if (te.cursorOffset < len(te.value)) {
if te.cursorOffset < len(te.value) {
te.value = te.value[:te.cursorOffset]
} else {
te.value = ""
@ -156,7 +165,7 @@ func (te *TextEntry) killLine() {
// Inserts a rune at the cursor position
func (te *TextEntry) insertRune(key rune) {
if (te.cursorOffset >= len(te.value)) {
if te.cursorOffset >= len(te.value) {
te.value += string(key)
} else {
te.value = te.value[:te.cursorOffset] + string(key) + te.value[te.cursorOffset:]

View file

@ -6,11 +6,9 @@ import (
"github.com/nsf/termbox-go"
)
type TermboxDriver struct {
}
// Initializes the driver. Returns an error if there was an error
func (td *TermboxDriver) Init() error {
err := termbox.Init()
@ -37,6 +35,11 @@ func (td *TermboxDriver) SetCell(x, y int, ch rune, fg, bg Attribute) {
termbox.SetCell(x, y, ch, termbox.Attribute(fg), termbox.Attribute(bg))
}
// Hide the cursor
func (td *TermboxDriver) HideCursor() {
termbox.HideCursor()
}
// Synchronizes the internal buffer with the real buffer
func (td *TermboxDriver) Sync() {
termbox.Flush()
@ -51,12 +54,12 @@ func (td *TermboxDriver) WaitForEvent() Event {
return Event{EventResize, 0, 0}
case termbox.EventKey:
mod := 0
if tev.Mod & termbox.ModAlt != 0 {
if tev.Mod&termbox.ModAlt != 0 {
mod = ModKeyAlt
}
if tev.Ch != 0 {
return Event{EventKeyPress, mod, tev.Ch}
} else if spec, hasSpec := termboxKeysToSpecialKeys[tev.Key] ; hasSpec {
} else if spec, hasSpec := termboxKeysToSpecialKeys[tev.Key]; hasSpec {
return Event{EventKeyPress, mod, spec}
} else {
return Event{EventNone, mod, 0}
@ -71,10 +74,8 @@ func (td *TermboxDriver) SetCursor(x, y int) {
termbox.SetCursor(x, y)
}
// Map from termbox Keys to driver key runes
var termboxKeysToSpecialKeys = map[termbox.Key]rune {
var termboxKeysToSpecialKeys = map[termbox.Key]rune{
termbox.KeySpace: ' ',
termbox.KeyF1: KeyF1,