Have added prompting.
This commit is contained in:
parent
665bc8cdae
commit
33847a78c1
170
commandmap.go
170
commandmap.go
|
|
@ -1,128 +1,148 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"./ui"
|
"bitbucket.org/lmika/ted-v2/ui"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ModAlt rune = 1 << 31 - 1
|
ModAlt rune = 1<<31 - 1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
// A command
|
// A command
|
||||||
type Command struct {
|
type Command struct {
|
||||||
Name string
|
Name string
|
||||||
Doc 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
|
// Execute the command
|
||||||
func (cmd *Command) Do(ctx CommandContext) error {
|
func (cmd *Command) Do(ctx *CommandContext) error {
|
||||||
return cmd.Action(ctx)
|
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
|
// A command mapping
|
||||||
type CommandMapping struct {
|
type CommandMapping struct {
|
||||||
Commands map[string]*Command
|
Commands map[string]*Command
|
||||||
KeyMappings map[rune]*Command
|
KeyMappings map[rune]*Command
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates a new, empty command mapping
|
// Creates a new, empty command mapping
|
||||||
func NewCommandMapping() *CommandMapping {
|
func NewCommandMapping() *CommandMapping {
|
||||||
return &CommandMapping{make(map[string]*Command), make(map[rune]*Command)}
|
return &CommandMapping{make(map[string]*Command), make(map[rune]*Command)}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds a new command
|
// 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}
|
cm.Commands[name] = &Command{name, doc, fn}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds a key mapping
|
// Adds a key mapping
|
||||||
func (cm *CommandMapping) MapKey(key rune, cmd *Command) {
|
func (cm *CommandMapping) MapKey(key rune, cmd *Command) {
|
||||||
cm.KeyMappings[key] = cmd
|
cm.KeyMappings[key] = cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
// Searches for a command by name. Returns the command or null
|
// Searches for a command by name. Returns the command or null
|
||||||
func (cm *CommandMapping) Command(name string) *Command {
|
func (cm *CommandMapping) Command(name string) *Command {
|
||||||
return cm.Commands[name]
|
return cm.Commands[name]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Searches for a command by key mapping
|
// Searches for a command by key mapping
|
||||||
func (cm *CommandMapping) KeyMapping(key rune) *Command {
|
func (cm *CommandMapping) KeyMapping(key rune) *Command {
|
||||||
return cm.KeyMappings[key]
|
return cm.KeyMappings[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Registers the standard view navigation commands. These commands require the frame
|
// Registers the standard view navigation commands. These commands require the frame
|
||||||
func (cm *CommandMapping) RegisterViewCommands() {
|
func (cm *CommandMapping) RegisterViewCommands() {
|
||||||
cm.Define("move-down", "Moves the cursor down one row", "", gridNavOperation(func(grid *ui.Grid) { grid.MoveBy(0, 1) }))
|
cm.Define("move-down", "Moves the cursor down one row", "", gridNavOperation(func(grid *ui.Grid) { grid.MoveBy(0, 1) }))
|
||||||
cm.Define("move-up", "Moves the cursor up one row", "", gridNavOperation(func(grid *ui.Grid) { grid.MoveBy(0, -1) }))
|
cm.Define("move-up", "Moves the cursor up one row", "", gridNavOperation(func(grid *ui.Grid) { grid.MoveBy(0, -1) }))
|
||||||
cm.Define("move-left", "Moves the cursor left one column", "", gridNavOperation(func(grid *ui.Grid) { grid.MoveBy(-1, 0) }))
|
cm.Define("move-left", "Moves the cursor left one column", "", gridNavOperation(func(grid *ui.Grid) { grid.MoveBy(-1, 0) }))
|
||||||
cm.Define("move-right", "Moves the cursor right one column", "", gridNavOperation(func(grid *ui.Grid) { grid.MoveBy(1, 0) }))
|
cm.Define("move-right", "Moves the cursor right one column", "", gridNavOperation(func(grid *ui.Grid) { grid.MoveBy(1, 0) }))
|
||||||
|
|
||||||
// TODO: Pages are just 25 rows and 15 columns at the moment
|
// TODO: Pages are just 25 rows and 15 columns at the moment
|
||||||
cm.Define("page-down", "Moves the cursor down one page", "", gridNavOperation(func(grid *ui.Grid) { grid.MoveBy(0, 25) }))
|
cm.Define("page-down", "Moves the cursor down one page", "", gridNavOperation(func(grid *ui.Grid) { grid.MoveBy(0, 25) }))
|
||||||
cm.Define("page-up", "Moves the cursor up one page", "", gridNavOperation(func(grid *ui.Grid) { grid.MoveBy(0, -25) }))
|
cm.Define("page-up", "Moves the cursor up one page", "", gridNavOperation(func(grid *ui.Grid) { grid.MoveBy(0, -25) }))
|
||||||
cm.Define("page-left", "Moves the cursor left one page", "", gridNavOperation(func(grid *ui.Grid) { grid.MoveBy(-15, 0) }))
|
cm.Define("page-left", "Moves the cursor left one page", "", gridNavOperation(func(grid *ui.Grid) { grid.MoveBy(-15, 0) }))
|
||||||
cm.Define("page-right", "Moves the cursor right one page", "", gridNavOperation(func(grid *ui.Grid) { grid.MoveBy(15, 0) }))
|
cm.Define("page-right", "Moves the cursor right one page", "", gridNavOperation(func(grid *ui.Grid) { grid.MoveBy(15, 0) }))
|
||||||
|
|
||||||
cm.Define("row-top", "Moves the cursor to the top of the row", "", gridNavOperation(func(grid *ui.Grid) {
|
cm.Define("row-top", "Moves the cursor to the top of the row", "", gridNavOperation(func(grid *ui.Grid) {
|
||||||
cellX, _ := grid.CellPosition()
|
cellX, _ := grid.CellPosition()
|
||||||
grid.MoveTo(cellX, 0)
|
grid.MoveTo(cellX, 0)
|
||||||
}))
|
}))
|
||||||
cm.Define("row-bottom", "Moves the cursor to the bottom of the row", "", gridNavOperation(func(grid *ui.Grid) {
|
cm.Define("row-bottom", "Moves the cursor to the bottom of the row", "", gridNavOperation(func(grid *ui.Grid) {
|
||||||
cellX, _ := grid.CellPosition()
|
cellX, _ := grid.CellPosition()
|
||||||
_, dimY := grid.Model().Dimensions()
|
_, 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) {
|
cm.Define("col-left", "Moves the cursor to the left-most column", "", gridNavOperation(func(grid *ui.Grid) {
|
||||||
_, cellY := grid.CellPosition()
|
_, cellY := grid.CellPosition()
|
||||||
grid.MoveTo(0, cellY)
|
grid.MoveTo(0, cellY)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
cm.Define("col-right", "Moves the cursor to the right-most column", "", gridNavOperation(func(grid *ui.Grid) {
|
cm.Define("col-right", "Moves the cursor to the right-most column", "", gridNavOperation(func(grid *ui.Grid) {
|
||||||
_, cellY := grid.CellPosition()
|
_, cellY := grid.CellPosition()
|
||||||
dimX, _ := grid.Model().Dimensions()
|
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
|
// Registers the standard view key bindings. These commands require the frame
|
||||||
func (cm *CommandMapping) RegisterViewKeyBindings() {
|
func (cm *CommandMapping) RegisterViewKeyBindings() {
|
||||||
cm.MapKey('i', cm.Command("move-up"))
|
cm.MapKey('i', cm.Command("move-up"))
|
||||||
cm.MapKey('k', cm.Command("move-down"))
|
cm.MapKey('k', cm.Command("move-down"))
|
||||||
cm.MapKey('j', cm.Command("move-left"))
|
cm.MapKey('j', cm.Command("move-left"))
|
||||||
cm.MapKey('l', cm.Command("move-right"))
|
cm.MapKey('l', cm.Command("move-right"))
|
||||||
cm.MapKey('I', cm.Command("page-up"))
|
cm.MapKey('I', cm.Command("page-up"))
|
||||||
cm.MapKey('K', cm.Command("page-down"))
|
cm.MapKey('K', cm.Command("page-down"))
|
||||||
cm.MapKey('J', cm.Command("page-left"))
|
cm.MapKey('J', cm.Command("page-left"))
|
||||||
cm.MapKey('L', cm.Command("page-right"))
|
cm.MapKey('L', cm.Command("page-right"))
|
||||||
cm.MapKey(ui.KeyCtrlI, cm.Command("row-top"))
|
cm.MapKey(ui.KeyCtrlI, cm.Command("row-top"))
|
||||||
cm.MapKey(ui.KeyCtrlK, cm.Command("row-bottom"))
|
cm.MapKey(ui.KeyCtrlK, cm.Command("row-bottom"))
|
||||||
cm.MapKey(ui.KeyCtrlJ, cm.Command("col-left"))
|
cm.MapKey(ui.KeyCtrlJ, cm.Command("col-left"))
|
||||||
cm.MapKey(ui.KeyCtrlL, cm.Command("col-right"))
|
cm.MapKey(ui.KeyCtrlL, cm.Command("col-right"))
|
||||||
|
|
||||||
cm.MapKey(ui.KeyArrowUp, cm.Command("move-up"))
|
cm.MapKey(ui.KeyArrowUp, cm.Command("move-up"))
|
||||||
cm.MapKey(ui.KeyArrowDown, cm.Command("move-down"))
|
cm.MapKey(ui.KeyArrowDown, cm.Command("move-down"))
|
||||||
cm.MapKey(ui.KeyArrowLeft, cm.Command("move-left"))
|
cm.MapKey(ui.KeyArrowLeft, cm.Command("move-left"))
|
||||||
cm.MapKey(ui.KeyArrowRight, cm.Command("move-right"))
|
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
|
// 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.
|
// will display the cell value in the message box.
|
||||||
func gridNavOperation(op func(grid *ui.Grid)) func(ctx CommandContext) error {
|
func gridNavOperation(op func(grid *ui.Grid)) func(ctx *CommandContext) error {
|
||||||
return func(ctx CommandContext) error {
|
return func(ctx *CommandContext) error {
|
||||||
op(ctx.Frame().Grid())
|
op(ctx.Frame().Grid())
|
||||||
ctx.Frame().ShowCellValue()
|
ctx.Frame().ShowCellValue()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
132
frame.go
132
frame.go
|
|
@ -1,89 +1,133 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"./ui"
|
"bitbucket.org/lmika/ted-v2/ui"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Mode int
|
type Mode int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// The grid is selectable
|
NilMode Mode = iota
|
||||||
GridMode Mode = iota
|
|
||||||
|
// The grid is selectable
|
||||||
|
GridMode
|
||||||
|
|
||||||
|
// EntryMode is when the text entry is selected
|
||||||
|
EntryMode
|
||||||
)
|
)
|
||||||
|
|
||||||
// A frame is a UI instance.
|
// A frame is a UI instance.
|
||||||
type Frame struct {
|
type Frame struct {
|
||||||
Session *Session
|
Session *Session
|
||||||
|
|
||||||
uiManager *ui.Ui
|
mode Mode
|
||||||
clientArea *ui.RelativeLayout
|
|
||||||
grid *ui.Grid
|
uiManager *ui.Ui
|
||||||
messageView *ui.TextView
|
clientArea *ui.RelativeLayout
|
||||||
textEntry *ui.TextEntry
|
grid *ui.Grid
|
||||||
statusBar *ui.StatusBar
|
messageView *ui.TextView
|
||||||
textEntrySwitch *ui.ProxyLayout
|
textEntry *ui.TextEntry
|
||||||
|
statusBar *ui.StatusBar
|
||||||
|
textEntrySwitch *ui.ProxyLayout
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates the UI and returns a new frame
|
// Creates the UI and returns a new frame
|
||||||
func NewFrame(uiManager *ui.Ui) *Frame {
|
func NewFrame(uiManager *ui.Ui) *Frame {
|
||||||
frame := &Frame{
|
frame := &Frame{
|
||||||
uiManager: uiManager,
|
uiManager: uiManager,
|
||||||
}
|
}
|
||||||
|
|
||||||
frame.grid = ui.NewGrid(nil)
|
frame.grid = ui.NewGrid(nil)
|
||||||
frame.messageView = &ui.TextView{"Hello"}
|
frame.messageView = &ui.TextView{"Hello"}
|
||||||
frame.statusBar = &ui.StatusBar{"Test", "Status"}
|
frame.statusBar = &ui.StatusBar{"Test", "Status"}
|
||||||
frame.textEntrySwitch = &ui.ProxyLayout{frame.messageView}
|
frame.textEntrySwitch = &ui.ProxyLayout{frame.messageView}
|
||||||
frame.textEntry = &ui.TextEntry{}
|
frame.textEntry = &ui.TextEntry{}
|
||||||
|
|
||||||
// Build the UI frame
|
// Build the UI frame
|
||||||
statusLayout := &ui.VertLinearLayout{}
|
statusLayout := &ui.VertLinearLayout{}
|
||||||
statusLayout.Append(frame.statusBar)
|
statusLayout.Append(frame.statusBar)
|
||||||
statusLayout.Append(frame.textEntrySwitch)
|
statusLayout.Append(frame.textEntrySwitch)
|
||||||
|
|
||||||
frame.clientArea = &ui.RelativeLayout{ Client: frame.grid, South: statusLayout }
|
frame.clientArea = &ui.RelativeLayout{Client: frame.grid, South: statusLayout}
|
||||||
return frame
|
return frame
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the root component of the frame
|
// Returns the root component of the frame
|
||||||
func (frame *Frame) RootComponent() ui.UiComponent {
|
func (frame *Frame) RootComponent() ui.UiComponent {
|
||||||
return frame.clientArea
|
return frame.clientArea
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sets the current model of the frame
|
// Sets the current model of the frame
|
||||||
func (frame *Frame) SetModel(model ui.GridModel) {
|
func (frame *Frame) SetModel(model ui.GridModel) {
|
||||||
frame.grid.SetModel(model)
|
frame.grid.SetModel(model)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the grid component
|
// Returns the grid component
|
||||||
func (frame *Frame) Grid() *ui.Grid {
|
func (frame *Frame) Grid() *ui.Grid {
|
||||||
return frame.grid
|
return frame.grid
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sets the specific mode.
|
// Enter the specific mode.
|
||||||
func (frame *Frame) EnterMode(mode Mode) {
|
func (frame *Frame) enterMode(mode Mode) {
|
||||||
switch mode {
|
switch mode {
|
||||||
case GridMode:
|
case GridMode:
|
||||||
frame.uiManager.SetFocusedComponent(frame)
|
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
|
// Show a message. This will switch the bottom to the messageView and select the frame
|
||||||
func (frame *Frame) ShowMessage(msg string) {
|
func (frame *Frame) ShowMessage(msg string) {
|
||||||
frame.messageView.Text = msg
|
frame.messageView.Text = msg
|
||||||
frame.textEntrySwitch.Component = frame.messageView
|
frame.textEntrySwitch.Component = frame.messageView
|
||||||
//frame.EnterMode(GridMode)
|
//frame.EnterMode(GridMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shows the value of the currently select grid cell
|
// Shows the value of the currently select grid cell
|
||||||
func (frame *Frame) ShowCellValue() {
|
func (frame *Frame) ShowCellValue() {
|
||||||
displayValue := frame.grid.CurrentCellDisplayValue()
|
displayValue := frame.grid.CurrentCellDisplayValue()
|
||||||
frame.ShowMessage(displayValue)
|
frame.ShowMessage(displayValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle the main grid input as this is the "component" that handles command input.
|
// Handle the main grid input as this is the "component" that handles command input.
|
||||||
func (frame *Frame) KeyPressed(key rune, mod int) {
|
func (frame *Frame) KeyPressed(key rune, mod int) {
|
||||||
if frame.Session != nil {
|
if frame.Session != nil {
|
||||||
frame.Session.KeyPressed(key, mod)
|
frame.Session.KeyPressed(key, mod)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
25
main.go
25
main.go
|
|
@ -1,23 +1,24 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"./ui"
|
"bitbucket.org/lmika/ted-v2/ui"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
uiManager, err := ui.NewUI()
|
uiManager, err := ui.NewUI()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
defer uiManager.Close()
|
defer uiManager.Close()
|
||||||
|
|
||||||
model := &StdModel{}
|
model := &StdModel{}
|
||||||
|
model.Resize(5, 5)
|
||||||
|
|
||||||
frame := NewFrame(uiManager)
|
frame := NewFrame(uiManager)
|
||||||
NewSession(frame, model)
|
NewSession(uiManager, frame, model)
|
||||||
|
|
||||||
uiManager.SetRootComponent(frame.RootComponent())
|
uiManager.SetRootComponent(frame.RootComponent())
|
||||||
frame.EnterMode(GridMode)
|
frame.enterMode(GridMode)
|
||||||
|
|
||||||
uiManager.Loop()
|
uiManager.Loop()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
81
session.go
81
session.go
|
|
@ -1,83 +1,86 @@
|
||||||
package main
|
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 session is responsible for managing the UI and the model and handling
|
||||||
// the interaction between the two and the user.
|
// the interaction between the two and the user.
|
||||||
type Session struct {
|
type Session struct {
|
||||||
Model Model
|
Model Model
|
||||||
Frame *Frame
|
Frame *Frame
|
||||||
Commands *CommandMapping
|
Commands *CommandMapping
|
||||||
|
UIManager *ui.Ui
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSession(frame *Frame, model Model) *Session {
|
func NewSession(uiManager *ui.Ui, frame *Frame, model Model) *Session {
|
||||||
session := &Session{
|
session := &Session{
|
||||||
Model: model,
|
Model: model,
|
||||||
Frame: frame,
|
Frame: frame,
|
||||||
Commands: NewCommandMapping(),
|
Commands: NewCommandMapping(),
|
||||||
}
|
UIManager: uiManager,
|
||||||
|
}
|
||||||
|
|
||||||
frame.SetModel(&SessionGridModel{session})
|
frame.SetModel(&SessionGridModel{session})
|
||||||
|
|
||||||
session.Commands.RegisterViewCommands()
|
session.Commands.RegisterViewCommands()
|
||||||
session.Commands.RegisterViewKeyBindings()
|
session.Commands.RegisterViewKeyBindings()
|
||||||
|
|
||||||
// Also assign this session with the frame
|
// Also assign this session with the frame
|
||||||
frame.Session = session
|
frame.Session = session
|
||||||
|
|
||||||
return session
|
return session
|
||||||
}
|
}
|
||||||
|
|
||||||
// Input from the frame
|
// Input from the frame
|
||||||
func (session *Session) KeyPressed(key rune, mod int) {
|
func (session *Session) KeyPressed(key rune, mod int) {
|
||||||
// Add the mod key modifier
|
// Add the mod key modifier
|
||||||
if (mod & ui.ModKeyAlt != 0) {
|
if mod&ui.ModKeyAlt != 0 {
|
||||||
key |= ModAlt
|
key |= ModAlt
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := session.Commands.KeyMapping(key)
|
cmd := session.Commands.KeyMapping(key)
|
||||||
if cmd != nil {
|
if cmd != nil {
|
||||||
err := cmd.Do(SessionCommandContext{session})
|
err := cmd.Do(&CommandContext{session})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
session.Frame.ShowMessage(err.Error())
|
session.Frame.ShowMessage(err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// The command context used by the session
|
// The command context used by the session
|
||||||
type SessionCommandContext struct {
|
type CommandContext struct {
|
||||||
Session *Session
|
session *Session
|
||||||
}
|
}
|
||||||
|
|
||||||
func (scc SessionCommandContext) Frame() *Frame {
|
func (scc *CommandContext) Session() *Session {
|
||||||
return scc.Session.Frame
|
return scc.session
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (scc *CommandContext) Frame() *Frame {
|
||||||
|
return scc.session.Frame
|
||||||
|
}
|
||||||
|
|
||||||
// Session grid model
|
// Session grid model
|
||||||
type SessionGridModel struct {
|
type SessionGridModel struct {
|
||||||
Session *Session
|
Session *Session
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the size of the grid model (width x height)
|
// Returns the size of the grid model (width x height)
|
||||||
func (sgm *SessionGridModel) Dimensions() (int, int) {
|
func (sgm *SessionGridModel) Dimensions() (int, int) {
|
||||||
rs, cs := sgm.Session.Model.Dimensions()
|
rs, cs := sgm.Session.Model.Dimensions()
|
||||||
return cs, rs
|
return cs, rs
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the size of the particular column. If the size is 0, this indicates that the column is hidden.
|
// Returns the size of the particular column. If the size is 0, this indicates that the column is hidden.
|
||||||
func (sgm *SessionGridModel) ColWidth(int) int {
|
func (sgm *SessionGridModel) ColWidth(int) int {
|
||||||
return 24
|
return 24
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the size of the particular row. If the size is 0, this indicates that the row is hidden.
|
// Returns the size of the particular row. If the size is 0, this indicates that the row is hidden.
|
||||||
func (sgm *SessionGridModel) RowHeight(int) int {
|
func (sgm *SessionGridModel) RowHeight(int) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the value of the cell a position X, Y
|
// Returns the value of the cell a position X, Y
|
||||||
func (sgm *SessionGridModel) CellValue(x int, y int) string {
|
func (sgm *SessionGridModel) CellValue(x int, y int) string {
|
||||||
return sgm.Session.Model.CellValue(y, x)
|
return sgm.Session.Model.CellValue(y, x)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,87 +6,90 @@ package ui
|
||||||
// drawing within it's context.
|
// drawing within it's context.
|
||||||
type DrawContext struct {
|
type DrawContext struct {
|
||||||
|
|
||||||
// The left and top position of the context.
|
// The left and top position of the context.
|
||||||
X, Y int
|
X, Y int
|
||||||
|
|
||||||
// The width and height position of the context.
|
// The width and height position of the context.
|
||||||
W, H int
|
W, H int
|
||||||
|
|
||||||
// The current driver
|
// The current driver
|
||||||
driver Driver
|
driver Driver
|
||||||
|
|
||||||
// The current foregound and background attributes
|
// The current foregound and background attributes
|
||||||
fa, ba Attribute
|
fa, ba Attribute
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Returns a new subcontext. The sub-context must be an area within the current context.
|
// 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 {
|
func (dc *DrawContext) NewSubContext(offsetX, offsetY, width, height int) *DrawContext {
|
||||||
return &DrawContext{
|
return &DrawContext{
|
||||||
X: intMax(dc.X + offsetX, dc.X),
|
X: intMax(dc.X+offsetX, dc.X),
|
||||||
Y: intMax(dc.Y + offsetY, dc.Y),
|
Y: intMax(dc.Y+offsetY, dc.Y),
|
||||||
W: intMax(width, 0),
|
W: intMax(width, 0),
|
||||||
H: intMax(height, 0),
|
H: intMax(height, 0),
|
||||||
|
|
||||||
driver: dc.driver,
|
driver: dc.driver,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sets the foreground attribute
|
// Sets the foreground attribute
|
||||||
func (dc *DrawContext) SetFgAttr(attr Attribute) {
|
func (dc *DrawContext) SetFgAttr(attr Attribute) {
|
||||||
dc.fa = attr
|
dc.fa = attr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sets the background attribute
|
// Sets the background attribute
|
||||||
func (dc *DrawContext) SetBgAttr(attr Attribute) {
|
func (dc *DrawContext) SetBgAttr(attr Attribute) {
|
||||||
dc.ba = attr
|
dc.ba = attr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Draws a horizontal rule with a specific rune.
|
// Draws a horizontal rule with a specific rune.
|
||||||
func (dc *DrawContext) HorizRule(y int, ch rune) {
|
func (dc *DrawContext) HorizRule(y int, ch rune) {
|
||||||
for x := 0; x < dc.W; x++ {
|
for x := 0; x < dc.W; x++ {
|
||||||
dc.DrawRune(x, y, ch)
|
dc.DrawRune(x, y, ch)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prints a string at a specific offset. This will be bounded by the size of the drawing context.
|
// Prints a string at a specific offset. This will be bounded by the size of the drawing context.
|
||||||
func (dc *DrawContext) Print(x, y int, str string) {
|
func (dc *DrawContext) Print(x, y int, str string) {
|
||||||
for _, ch := range str {
|
for _, ch := range str {
|
||||||
dc.DrawRune(x, y, ch)
|
dc.DrawRune(x, y, ch)
|
||||||
x++
|
x++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prints a right-justified string at a specific offset. This will be bounded by the size of the drawing context.
|
// 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) {
|
func (dc *DrawContext) PrintRight(x, y int, str string) {
|
||||||
l := len(str)
|
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
|
// Draws a rune at a local point X, Y with the current foreground and background attributes
|
||||||
func (dc *DrawContext) DrawRune(x, y int, ch rune) {
|
func (dc *DrawContext) DrawRune(x, y int, ch rune) {
|
||||||
if rx, ry, isWithinContext := dc.localPointToRealPoint(x, y); isWithinContext {
|
if rx, ry, isWithinContext := dc.localPointToRealPoint(x, y); isWithinContext {
|
||||||
dc.driver.SetCell(rx, ry, ch, dc.fa, dc.ba)
|
dc.driver.SetCell(rx, ry, ch, dc.fa, dc.ba)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draws a rune at a local point X, Y with specific foreground and background attributes
|
// Draws a rune at a local point X, Y with specific foreground and background attributes
|
||||||
func (dc *DrawContext) DrawRuneWithAttrs(x, y int, ch rune, fa, ba Attribute) {
|
func (dc *DrawContext) DrawRuneWithAttrs(x, y int, ch rune, fa, ba Attribute) {
|
||||||
if rx, ry, isWithinContext := dc.localPointToRealPoint(x, y); isWithinContext {
|
if rx, ry, isWithinContext := dc.localPointToRealPoint(x, y); isWithinContext {
|
||||||
dc.driver.SetCell(rx, ry, ch, fa, ba)
|
dc.driver.SetCell(rx, ry, ch, fa, ba)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the position of the cursor
|
// Set the position of the cursor
|
||||||
func (dc *DrawContext) SetCursorPosition(x, y int) {
|
func (dc *DrawContext) SetCursorPosition(x, y int) {
|
||||||
if rx, ry, isWithinContext := dc.localPointToRealPoint(x, y); isWithinContext {
|
if rx, ry, isWithinContext := dc.localPointToRealPoint(x, y); isWithinContext {
|
||||||
dc.driver.SetCursor(rx, ry)
|
dc.driver.SetCursor(rx, ry)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
// 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) {
|
func (dc *DrawContext) localPointToRealPoint(x, y int) (int, int, bool) {
|
||||||
rx, ry := x + dc.X, y + dc.Y
|
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)
|
return rx, ry, (rx >= dc.X) && (ry >= dc.Y) && (rx < dc.X+dc.W) && (ry < dc.Y+dc.H)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
201
ui/driver.go
201
ui/driver.go
|
|
@ -3,140 +3,141 @@
|
||||||
package ui
|
package ui
|
||||||
|
|
||||||
// The set of attributes a specific cell can have
|
// The set of attributes a specific cell can have
|
||||||
type Attribute uint16
|
type Attribute uint16
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Can have only one of these
|
// Can have only one of these
|
||||||
ColorDefault Attribute = iota
|
ColorDefault Attribute = iota
|
||||||
ColorBlack
|
ColorBlack
|
||||||
ColorRed
|
ColorRed
|
||||||
ColorGreen
|
ColorGreen
|
||||||
ColorYellow
|
ColorYellow
|
||||||
ColorBlue
|
ColorBlue
|
||||||
ColorMagenta
|
ColorMagenta
|
||||||
ColorCyan
|
ColorCyan
|
||||||
ColorWhite
|
ColorWhite
|
||||||
)
|
)
|
||||||
|
|
||||||
// and zero or more of these (combined using OR '|')
|
// and zero or more of these (combined using OR '|')
|
||||||
const (
|
const (
|
||||||
AttrBold Attribute = 1 << (iota + 9)
|
AttrBold Attribute = 1 << (iota + 9)
|
||||||
AttrUnderline
|
AttrUnderline
|
||||||
AttrReverse
|
AttrReverse
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
// Special keys
|
// Special keys
|
||||||
const (
|
const (
|
||||||
KeyCtrlSpace rune = 0x8000 + iota
|
KeyCtrlSpace rune = 0x8000 + iota
|
||||||
KeyCtrlA
|
KeyCtrlA
|
||||||
KeyCtrlB
|
KeyCtrlB
|
||||||
KeyCtrlC
|
KeyCtrlC
|
||||||
KeyCtrlD
|
KeyCtrlD
|
||||||
KeyCtrlE
|
KeyCtrlE
|
||||||
KeyCtrlF
|
KeyCtrlF
|
||||||
KeyCtrlG
|
KeyCtrlG
|
||||||
KeyCtrlH
|
KeyCtrlH
|
||||||
KeyCtrlI
|
KeyCtrlI
|
||||||
KeyCtrlJ
|
KeyCtrlJ
|
||||||
KeyCtrlK
|
KeyCtrlK
|
||||||
KeyCtrlL
|
KeyCtrlL
|
||||||
KeyCtrlM
|
KeyCtrlM
|
||||||
KeyCtrlN
|
KeyCtrlN
|
||||||
KeyCtrlO
|
KeyCtrlO
|
||||||
KeyCtrlP
|
KeyCtrlP
|
||||||
KeyCtrlQ
|
KeyCtrlQ
|
||||||
KeyCtrlR
|
KeyCtrlR
|
||||||
KeyCtrlS
|
KeyCtrlS
|
||||||
KeyCtrlT
|
KeyCtrlT
|
||||||
KeyCtrlU
|
KeyCtrlU
|
||||||
KeyCtrlV
|
KeyCtrlV
|
||||||
KeyCtrlW
|
KeyCtrlW
|
||||||
KeyCtrlX
|
KeyCtrlX
|
||||||
KeyCtrlY
|
KeyCtrlY
|
||||||
KeyCtrlZ
|
KeyCtrlZ
|
||||||
KeyCtrl3
|
KeyCtrl3
|
||||||
KeyCtrl4
|
KeyCtrl4
|
||||||
KeyCtrl5
|
KeyCtrl5
|
||||||
KeyCtrl6
|
KeyCtrl6
|
||||||
KeyCtrl7
|
KeyCtrl7
|
||||||
KeyCtrl8
|
KeyCtrl8
|
||||||
|
|
||||||
KeyF1
|
KeyF1
|
||||||
KeyF2
|
KeyF2
|
||||||
KeyF3
|
KeyF3
|
||||||
KeyF4
|
KeyF4
|
||||||
KeyF5
|
KeyF5
|
||||||
KeyF6
|
KeyF6
|
||||||
KeyF7
|
KeyF7
|
||||||
KeyF8
|
KeyF8
|
||||||
KeyF9
|
KeyF9
|
||||||
KeyF10
|
KeyF10
|
||||||
KeyF11
|
KeyF11
|
||||||
KeyF12
|
KeyF12
|
||||||
KeyInsert
|
KeyInsert
|
||||||
KeyDelete
|
KeyDelete
|
||||||
KeyHome
|
KeyHome
|
||||||
KeyEnd
|
KeyEnd
|
||||||
KeyPgup
|
KeyPgup
|
||||||
KeyPgdn
|
KeyPgdn
|
||||||
KeyArrowUp
|
KeyArrowUp
|
||||||
KeyArrowDown
|
KeyArrowDown
|
||||||
KeyArrowLeft
|
KeyArrowLeft
|
||||||
KeyArrowRight
|
KeyArrowRight
|
||||||
|
|
||||||
KeyBackspace = KeyCtrlH
|
KeyBackspace = KeyCtrlH
|
||||||
KeyBackspace2 = KeyCtrl8
|
KeyBackspace2 = KeyCtrl8
|
||||||
KeyEnter = KeyCtrlM
|
KeyEnter = KeyCtrlM
|
||||||
)
|
)
|
||||||
|
|
||||||
// The type of events supported by the driver
|
// The type of events supported by the driver
|
||||||
type EventType int
|
type EventType int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
EventNone EventType = iota
|
EventNone EventType = iota
|
||||||
|
|
||||||
// Event when the window is resized
|
// Event when the window is resized
|
||||||
EventResize
|
EventResize
|
||||||
|
|
||||||
// Event indicating a key press. The key is set in Ch and modifications
|
// Event indicating a key press. The key is set in Ch and modifications
|
||||||
// are set in Or
|
// are set in Or
|
||||||
EventKeyPress
|
EventKeyPress
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ModKeyAlt int = (1 << iota)
|
ModKeyAlt int = (1 << iota)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Data from an event callback.
|
// Data from an event callback.
|
||||||
type Event struct {
|
type Event struct {
|
||||||
Type EventType
|
Type EventType
|
||||||
Par int
|
Par int
|
||||||
Ch rune
|
Ch rune
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// The terminal driver interface.
|
// The terminal driver interface.
|
||||||
type Driver interface {
|
type Driver interface {
|
||||||
|
|
||||||
// Initializes the driver. Returns an error if there was an error
|
// Initializes the driver. Returns an error if there was an error
|
||||||
Init() error
|
Init() error
|
||||||
|
|
||||||
// Closes the driver
|
// Closes the driver
|
||||||
Close()
|
Close()
|
||||||
|
|
||||||
// Returns the size of the window.
|
// Returns the size of the window.
|
||||||
Size() (int, int)
|
Size() (int, int)
|
||||||
|
|
||||||
// Sets the value of a specific cell
|
// Sets the value of a specific cell
|
||||||
SetCell(x, y int, ch rune, fg, bg Attribute)
|
SetCell(x, y int, ch rune, fg, bg Attribute)
|
||||||
|
|
||||||
// Synchronizes the internal buffer with the real buffer
|
// Synchronizes the internal buffer with the real buffer
|
||||||
Sync()
|
Sync()
|
||||||
|
|
||||||
// Wait for an event
|
// Wait for an event
|
||||||
WaitForEvent() Event
|
WaitForEvent() Event
|
||||||
|
|
||||||
// Move the position of the cursor
|
// Move the position of the cursor
|
||||||
SetCursor(x, y int)
|
SetCursor(x, y int)
|
||||||
|
|
||||||
|
// Hide the cursor
|
||||||
|
HideCursor()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
455
ui/grid.go
455
ui/grid.go
|
|
@ -5,407 +5,396 @@ package ui
|
||||||
|
|
||||||
import "strconv"
|
import "strconv"
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An abstract display model.
|
* An abstract display model.
|
||||||
*/
|
*/
|
||||||
type GridModel interface {
|
type GridModel interface {
|
||||||
/**
|
/**
|
||||||
* Returns the size of the grid model (width x height)
|
* Returns the size of the grid model (width x height)
|
||||||
*/
|
*/
|
||||||
Dimensions() (int, int)
|
Dimensions() (int, int)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the size of the particular column. If the size is 0, this indicates that the column is hidden.
|
* Returns the size of the particular column. If the size is 0, this indicates that the column is hidden.
|
||||||
*/
|
*/
|
||||||
ColWidth(int) int
|
ColWidth(int) int
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the size of the particular row. If the size is 0, this indicates that the row is hidden.
|
* Returns the size of the particular row. If the size is 0, this indicates that the row is hidden.
|
||||||
*/
|
*/
|
||||||
RowHeight(int) int
|
RowHeight(int) int
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the value of the cell a position X, Y
|
* Returns the value of the cell a position X, Y
|
||||||
*/
|
*/
|
||||||
CellValue(int, int) string
|
CellValue(int, int) string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type gridPoint int
|
||||||
type gridPoint int
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The grid component.
|
* The grid component.
|
||||||
*/
|
*/
|
||||||
type Grid struct {
|
type Grid struct {
|
||||||
model GridModel // The grid model
|
model GridModel // The grid model
|
||||||
|
|
||||||
viewCellX int // Left most cell
|
viewCellX int // Left most cell
|
||||||
viewCellY int // Top most cell
|
viewCellY int // Top most cell
|
||||||
selCellX int // The currently selected cell
|
selCellX int // The currently selected cell
|
||||||
selCellY int
|
selCellY int
|
||||||
cellsWide int // Measured number of cells. Recalculated on redraw.
|
cellsWide int // Measured number of cells. Recalculated on redraw.
|
||||||
cellsHigh int
|
cellsHigh int
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clipping rectangle
|
* Clipping rectangle
|
||||||
*/
|
*/
|
||||||
type gridRect struct {
|
type gridRect struct {
|
||||||
x1 gridPoint
|
x1 gridPoint
|
||||||
y1 gridPoint
|
y1 gridPoint
|
||||||
x2 gridPoint
|
x2 gridPoint
|
||||||
y2 gridPoint
|
y2 gridPoint
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new gridRect from integers.
|
* Creates a new gridRect from integers.
|
||||||
*/
|
*/
|
||||||
func newGridRect(x1, y1, x2, y2 int) gridRect {
|
func newGridRect(x1, y1, x2, y2 int) gridRect {
|
||||||
return gridRect{gridPoint(x1), gridPoint(y1), gridPoint(x2), gridPoint(y2)}
|
return gridRect{gridPoint(x1), gridPoint(y1), gridPoint(x2), gridPoint(y2)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new grid.
|
* Creates a new grid.
|
||||||
*/
|
*/
|
||||||
func NewGrid(model GridModel) *Grid {
|
func NewGrid(model GridModel) *Grid {
|
||||||
return &Grid{model, 0, 0, 0, 0, -1, -1}
|
return &Grid{model, 0, 0, 0, 0, -1, -1}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the model
|
// Returns the model
|
||||||
func (grid *Grid) Model() GridModel {
|
func (grid *Grid) Model() GridModel {
|
||||||
return grid.model
|
return grid.model
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sets the model
|
// Sets the model
|
||||||
func (grid *Grid) SetModel(model GridModel) {
|
func (grid *Grid) SetModel(model GridModel) {
|
||||||
grid.model = model
|
grid.model = model
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shifts the viewport of the grid.
|
* Shifts the viewport of the grid.
|
||||||
*/
|
*/
|
||||||
func (grid *Grid) ShiftBy(x int, y int) {
|
func (grid *Grid) ShiftBy(x int, y int) {
|
||||||
grid.viewCellX += x
|
grid.viewCellX += x
|
||||||
grid.viewCellY += y
|
grid.viewCellY += y
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Returns the display value of the currently selected cell.
|
// Returns the display value of the currently selected cell.
|
||||||
func (grid *Grid) CurrentCellDisplayValue() string {
|
func (grid *Grid) CurrentCellDisplayValue() string {
|
||||||
if grid.isCellValid(grid.selCellX, grid.selCellY) {
|
if grid.isCellValid(grid.selCellX, grid.selCellY) {
|
||||||
return grid.model.CellValue(grid.selCellX, grid.selCellY)
|
return grid.model.CellValue(grid.selCellX, grid.selCellY)
|
||||||
} else {
|
} else {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Moves the currently selected cell by a delta. This will be implemented as single stepped
|
// Moves the currently selected cell by a delta. This will be implemented as single stepped
|
||||||
// moveTo calls to handle invalid cells.
|
// moveTo calls to handle invalid cells.
|
||||||
func (grid *Grid) MoveBy(x int, y int) {
|
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
|
// 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
|
// currently selected cell will not be changed. Returns true if the move was successful
|
||||||
func (grid *Grid) MoveTo(newX, newY int) {
|
func (grid *Grid) MoveTo(newX, newY int) {
|
||||||
maxX, maxY := grid.model.Dimensions()
|
maxX, maxY := grid.model.Dimensions()
|
||||||
newX = intMinMax(newX, 0, maxX - 1)
|
newX = intMinMax(newX, 0, maxX-1)
|
||||||
newY = intMinMax(newY, 0, maxY - 1)
|
newY = intMinMax(newY, 0, maxY-1)
|
||||||
|
|
||||||
if grid.isCellValid(newX, newY) {
|
if grid.isCellValid(newX, newY) {
|
||||||
grid.selCellX = newX
|
grid.selCellX = newX
|
||||||
grid.selCellY = newY
|
grid.selCellY = newY
|
||||||
grid.reposition()
|
grid.reposition()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the currently selected cell position.
|
// Returns the currently selected cell position.
|
||||||
func (grid *Grid) CellPosition() (int, int) {
|
func (grid *Grid) CellPosition() (int, int) {
|
||||||
return grid.selCellX, grid.selCellY
|
return grid.selCellX, grid.selCellY
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if the user can enter the specific cell
|
// Returns true if the user can enter the specific cell
|
||||||
func (grid *Grid) isCellValid(x int, y int) bool {
|
func (grid *Grid) isCellValid(x int, y int) bool {
|
||||||
maxX, maxY := grid.model.Dimensions()
|
maxX, maxY := grid.model.Dimensions()
|
||||||
return (x >= 0) && (y >= 0) && (x < maxX) && (y < maxY)
|
return (x >= 0) && (y >= 0) && (x < maxX) && (y < maxY)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Determine the topmost cell based on the location of the currently selected cell
|
// Determine the topmost cell based on the location of the currently selected cell
|
||||||
func (grid *Grid) reposition() {
|
func (grid *Grid) reposition() {
|
||||||
|
|
||||||
// If we have no measurement information, forget it.
|
// If we have no measurement information, forget it.
|
||||||
if (grid.cellsWide == -1) || (grid.cellsHigh == -1) {
|
if (grid.cellsWide == -1) || (grid.cellsHigh == -1) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if grid.selCellX < grid.viewCellX {
|
if grid.selCellX < grid.viewCellX {
|
||||||
grid.viewCellX = grid.selCellX
|
grid.viewCellX = grid.selCellX
|
||||||
} else if grid.selCellX >= (grid.viewCellX + grid.cellsWide - 3) {
|
} else if grid.selCellX >= (grid.viewCellX + grid.cellsWide - 3) {
|
||||||
grid.viewCellX = grid.selCellX - (grid.cellsWide - 3)
|
grid.viewCellX = grid.selCellX - (grid.cellsWide - 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
if grid.selCellY < grid.viewCellY {
|
if grid.selCellY < grid.viewCellY {
|
||||||
grid.viewCellY = grid.selCellY
|
grid.viewCellY = grid.selCellY
|
||||||
} else if grid.selCellY >= (grid.viewCellY + grid.cellsHigh - 3) {
|
} else if grid.selCellY >= (grid.viewCellY + grid.cellsHigh - 3) {
|
||||||
grid.viewCellY = grid.selCellY - (grid.cellsHigh - 3)
|
grid.viewCellY = grid.selCellY - (grid.cellsHigh - 3)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Gets the cell value and attributes of a particular cell
|
// Gets the cell value and attributes of a particular cell
|
||||||
func (grid *Grid) getCellData(cellX, cellY int) (text string, fg, bg Attribute) {
|
func (grid *Grid) getCellData(cellX, cellY int) (text string, fg, bg Attribute) {
|
||||||
// The fixed cells
|
// The fixed cells
|
||||||
modelCellX := cellX - 1 + grid.viewCellX
|
modelCellX := cellX - 1 + grid.viewCellX
|
||||||
modelCellY := cellY - 1 + grid.viewCellY
|
modelCellY := cellY - 1 + grid.viewCellY
|
||||||
modelMaxX, modelMaxY := grid.model.Dimensions()
|
modelMaxX, modelMaxY := grid.model.Dimensions()
|
||||||
|
|
||||||
if (cellX == 0) && (cellY == 0) {
|
if (cellX == 0) && (cellY == 0) {
|
||||||
return "", AttrBold, AttrBold
|
return "", AttrBold, AttrBold
|
||||||
} else if (cellX == 0) {
|
} else if cellX == 0 {
|
||||||
if (modelCellY == grid.selCellY) {
|
if modelCellY == grid.selCellY {
|
||||||
return strconv.Itoa(modelCellY), AttrBold | AttrReverse, AttrBold | AttrReverse
|
return strconv.Itoa(modelCellY), AttrBold | AttrReverse, AttrReverse
|
||||||
} else {
|
} else {
|
||||||
return strconv.Itoa(modelCellY), AttrBold, AttrBold
|
return strconv.Itoa(modelCellY), AttrBold, 0
|
||||||
}
|
}
|
||||||
} else if (cellY == 0) {
|
} else if cellY == 0 {
|
||||||
if (modelCellX == grid.selCellX) {
|
if modelCellX == grid.selCellX {
|
||||||
return strconv.Itoa(modelCellX), AttrBold | AttrReverse, AttrBold | AttrReverse
|
return strconv.Itoa(modelCellX), AttrBold | AttrReverse, AttrReverse
|
||||||
} else {
|
} else {
|
||||||
return strconv.Itoa(modelCellX), AttrBold, AttrBold
|
return strconv.Itoa(modelCellX), AttrBold, 0
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// The data from the model
|
// The data from the model
|
||||||
if (modelCellX >= 0) && (modelCellY >= 0) && (modelCellX < modelMaxX) && (modelCellY < modelMaxY) {
|
if (modelCellX >= 0) && (modelCellY >= 0) && (modelCellX < modelMaxX) && (modelCellY < modelMaxY) {
|
||||||
if (modelCellX == grid.selCellX) && (modelCellY == grid.selCellY) {
|
if (modelCellX == grid.selCellX) && (modelCellY == grid.selCellY) {
|
||||||
return grid.model.CellValue(modelCellX, modelCellY), AttrReverse, AttrReverse
|
return grid.model.CellValue(modelCellX, modelCellY), AttrReverse, AttrReverse
|
||||||
} else {
|
} else {
|
||||||
return grid.model.CellValue(modelCellX, modelCellY), 0, 0
|
return grid.model.CellValue(modelCellX, modelCellY), 0, 0
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return "~", ColorBlue | AttrBold, 0
|
return "~", ColorBlue | AttrBold, 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets the cell dimensions
|
// Gets the cell dimensions
|
||||||
func (grid *Grid) getCellDimensions(cellX, cellY int) (width, height int) {
|
func (grid *Grid) getCellDimensions(cellX, cellY int) (width, height int) {
|
||||||
|
|
||||||
var cellWidth, cellHeight int
|
var cellWidth, cellHeight int
|
||||||
|
|
||||||
modelCellX := cellX - 1 + grid.viewCellX
|
|
||||||
modelCellY := cellY - 1 + grid.viewCellY
|
|
||||||
modelMaxX, modelMaxY := grid.model.Dimensions()
|
|
||||||
|
|
||||||
// Get the cell width & height from model (if within range)
|
|
||||||
if (modelCellX >= 0) && (modelCellX < modelMaxX) {
|
|
||||||
cellWidth = grid.model.ColWidth(modelCellX)
|
|
||||||
} else {
|
|
||||||
cellWidth = 8
|
|
||||||
}
|
|
||||||
|
|
||||||
if (modelCellY >= 0) && (modelCellY < modelMaxY) {
|
|
||||||
cellHeight = grid.model.RowHeight(modelCellY)
|
|
||||||
} else {
|
|
||||||
cellHeight = 2
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cellX == 0) && (cellY == 0) {
|
|
||||||
return 8, 1
|
|
||||||
} else if (cellX == 0) {
|
|
||||||
return 8, cellHeight
|
|
||||||
} else if (cellY == 0) {
|
|
||||||
return cellWidth, 1
|
|
||||||
} else {
|
|
||||||
return cellWidth, cellHeight
|
|
||||||
}
|
|
||||||
|
|
||||||
// XXX: Workaround for bug in compiler
|
|
||||||
panic("Unreachable code")
|
|
||||||
return 0, 0
|
|
||||||
}
|
|
||||||
|
|
||||||
|
modelCellX := cellX - 1 + grid.viewCellX
|
||||||
|
modelCellY := cellY - 1 + grid.viewCellY
|
||||||
|
modelMaxX, modelMaxY := grid.model.Dimensions()
|
||||||
|
|
||||||
|
// Get the cell width & height from model (if within range)
|
||||||
|
if (modelCellX >= 0) && (modelCellX < modelMaxX) {
|
||||||
|
cellWidth = grid.model.ColWidth(modelCellX)
|
||||||
|
} else {
|
||||||
|
cellWidth = 8
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modelCellY >= 0) && (modelCellY < modelMaxY) {
|
||||||
|
cellHeight = grid.model.RowHeight(modelCellY)
|
||||||
|
} else {
|
||||||
|
cellHeight = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cellX == 0) && (cellY == 0) {
|
||||||
|
return 8, 1
|
||||||
|
} else if cellX == 0 {
|
||||||
|
return 8, cellHeight
|
||||||
|
} else if cellY == 0 {
|
||||||
|
return cellWidth, 1
|
||||||
|
} else {
|
||||||
|
return cellWidth, cellHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
// XXX: Workaround for bug in compiler
|
||||||
|
panic("Unreachable code")
|
||||||
|
return 0, 0
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders a cell which contains text. The clip rectangle defines the size of the cell, as well as the left offset
|
* 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.
|
* of the cell. The sx and sy determine the screen position of the cell top-left.
|
||||||
*/
|
*/
|
||||||
func (grid *Grid) renderCell(ctx *DrawContext, cellClipRect gridRect, sx int, sy int, text string, fg, bg Attribute) {
|
func (grid *Grid) renderCell(ctx *DrawContext, cellClipRect gridRect, sx int, sy int, text string, fg, bg Attribute) {
|
||||||
for x := cellClipRect.x1; x <= cellClipRect.x2; x++ {
|
for x := cellClipRect.x1; x <= cellClipRect.x2; x++ {
|
||||||
for y := cellClipRect.y1; y <= cellClipRect.y2; y++ {
|
for y := cellClipRect.y1; y <= cellClipRect.y2; y++ {
|
||||||
currRune := ' '
|
currRune := ' '
|
||||||
if (y == 0) {
|
if y == 0 {
|
||||||
textPos := int(x)
|
textPos := int(x)
|
||||||
if textPos < len(text) {
|
if textPos < len(text) {
|
||||||
currRune = rune(text[textPos])
|
currRune = rune(text[textPos])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Renders a column. The viewport determines the maximum position of the rendered cell. CellX and CellY are the
|
// 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.
|
// cell indicies to render, cellOffset are the LOCAL offset of the cell.
|
||||||
// This function will return the new X position (gridRect.x1 + colWidth)
|
// This function will return the new X position (gridRect.x1 + colWidth)
|
||||||
func (grid *Grid) renderColumn(ctx *DrawContext, screenViewPort gridRect, cellX int, cellY int, cellOffsetX int, cellOffsetY int) (gridPoint, int) {
|
func (grid *Grid) renderColumn(ctx *DrawContext, screenViewPort gridRect, cellX int, cellY int, cellOffsetX int, cellOffsetY int) (gridPoint, int) {
|
||||||
|
|
||||||
// The top-left position of the column
|
// The top-left position of the column
|
||||||
screenX := int(screenViewPort.x1)
|
screenX := int(screenViewPort.x1)
|
||||||
screenY := int(screenViewPort.y1)
|
screenY := int(screenViewPort.y1)
|
||||||
screenWidth := int(screenViewPort.x2 - screenViewPort.x1)
|
screenWidth := int(screenViewPort.x2 - screenViewPort.x1)
|
||||||
screenHeight := int(screenViewPort.y2 - screenViewPort.y1)
|
screenHeight := int(screenViewPort.y2 - screenViewPort.y1)
|
||||||
|
|
||||||
// Work out the column width and cap it if it will spill over the edge of the viewport
|
// Work out the column width and cap it if it will spill over the edge of the viewport
|
||||||
colWidth, _ := grid.getCellDimensions(cellX, cellY)
|
colWidth, _ := grid.getCellDimensions(cellX, cellY)
|
||||||
colWidth -= cellOffsetX
|
colWidth -= cellOffsetX
|
||||||
if colWidth > screenWidth {
|
if colWidth > screenWidth {
|
||||||
colWidth = screenHeight
|
colWidth = screenHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
// The maximum
|
// The maximum
|
||||||
maxScreenY := screenY + screenHeight
|
maxScreenY := screenY + screenHeight
|
||||||
cellsHigh := 0
|
cellsHigh := 0
|
||||||
|
|
||||||
for screenY < maxScreenY {
|
for screenY < maxScreenY {
|
||||||
|
|
||||||
// Cap the row height if it will go beyond the edge of the viewport.
|
// Cap the row height if it will go beyond the edge of the viewport.
|
||||||
_, rowHeight := grid.getCellDimensions(cellX, cellY)
|
_, rowHeight := grid.getCellDimensions(cellX, cellY)
|
||||||
if screenY + rowHeight > maxScreenY {
|
if screenY+rowHeight > maxScreenY {
|
||||||
rowHeight = maxScreenY - screenY
|
rowHeight = maxScreenY - screenY
|
||||||
}
|
}
|
||||||
|
|
||||||
cellText, cellFg, cellBg := grid.getCellData(cellX, cellY)
|
|
||||||
|
|
||||||
grid.renderCell(ctx, newGridRect(cellOffsetX, cellOffsetY, colWidth - cellOffsetX, rowHeight),
|
cellText, cellFg, cellBg := grid.getCellData(cellX, cellY)
|
||||||
screenX, screenY, cellText, cellFg, cellBg) // termbox.AttrReverse, termbox.AttrReverse
|
|
||||||
|
|
||||||
cellY++
|
grid.renderCell(ctx, newGridRect(cellOffsetX, cellOffsetY, colWidth-cellOffsetX, rowHeight),
|
||||||
cellsHigh++
|
screenX, screenY, cellText, cellFg, cellBg) // termbox.AttrReverse, termbox.AttrReverse
|
||||||
screenY = screenY + rowHeight - cellOffsetY
|
|
||||||
cellOffsetY = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return gridPoint(screenX + colWidth), cellsHigh
|
cellY++
|
||||||
|
cellsHigh++
|
||||||
|
screenY = screenY + rowHeight - cellOffsetY
|
||||||
|
cellOffsetY = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return gridPoint(screenX + colWidth), cellsHigh
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Renders the grid. Returns the number of cells in the X and Y direction were rendered.
|
// 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) {
|
func (grid *Grid) renderGrid(ctx *DrawContext, screenViewPort gridRect, cellX int, cellY int, cellOffsetX int, cellOffsetY int) (int, int) {
|
||||||
|
|
||||||
var cellsHigh = 0
|
|
||||||
var cellsWide = 0
|
|
||||||
|
|
||||||
for screenViewPort.x1 < screenViewPort.x2 {
|
var cellsHigh = 0
|
||||||
screenViewPort.x1, cellsHigh = grid.renderColumn(ctx, screenViewPort, cellX, cellY, cellOffsetX, cellOffsetY)
|
var cellsWide = 0
|
||||||
cellX = cellX + 1
|
|
||||||
cellsWide++
|
|
||||||
cellOffsetX = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return cellsWide, cellsHigh
|
for screenViewPort.x1 < screenViewPort.x2 {
|
||||||
|
screenViewPort.x1, cellsHigh = grid.renderColumn(ctx, screenViewPort, cellX, cellY, cellOffsetX, cellOffsetY)
|
||||||
|
cellX = cellX + 1
|
||||||
|
cellsWide++
|
||||||
|
cellOffsetX = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return cellsWide, cellsHigh
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the cell of the particular point, along with the top-left position of the cell.
|
* Returns the cell of the particular point, along with the top-left position of the cell.
|
||||||
*/
|
*/
|
||||||
func (grid *Grid) pointToCell(x int, y int) (cellX int, cellY int, posX int, posY int) {
|
func (grid *Grid) pointToCell(x int, y int) (cellX int, cellY int, posX int, posY int) {
|
||||||
var wid, hei int = grid.model.Dimensions()
|
var wid, hei int = grid.model.Dimensions()
|
||||||
posX = 0
|
posX = 0
|
||||||
posY = 0
|
posY = 0
|
||||||
|
|
||||||
cellX = -1
|
cellX = -1
|
||||||
cellY = -1
|
cellY = -1
|
||||||
|
|
||||||
// Go through columns to locate the particular cellX
|
// Go through columns to locate the particular cellX
|
||||||
for cx := 0; cx < wid; cx++ {
|
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
|
// We found the X position
|
||||||
cellX = int(cx)
|
cellX = int(cx)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for cy := 0; cy < hei; cy++ {
|
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
|
// And the Y position
|
||||||
cellY = int(cy)
|
cellY = int(cy)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the requested dimensions of a grid (as required by UiComponent)
|
* Returns the requested dimensions of a grid (as required by UiComponent)
|
||||||
*/
|
*/
|
||||||
func (grid *Grid) Remeasure(w, h int) (int, int) {
|
func (grid *Grid) Remeasure(w, h int) (int, int) {
|
||||||
return w, h
|
return w, h
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Redraws the grid.
|
* Redraws the grid.
|
||||||
*/
|
*/
|
||||||
func (grid *Grid) Redraw(ctx *DrawContext) {
|
func (grid *Grid) Redraw(ctx *DrawContext) {
|
||||||
viewportRect := newGridRect(0, 0, ctx.W, ctx.H)
|
viewportRect := newGridRect(0, 0, ctx.W, ctx.H)
|
||||||
grid.cellsWide, grid.cellsHigh = grid.renderGrid(ctx, viewportRect, 0, 0, 0, 0)
|
grid.cellsWide, grid.cellsHigh = grid.renderGrid(ctx, viewportRect, 0, 0, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called when the component has focus and a key has been pressed.
|
// Called when the component has focus and a key has been pressed.
|
||||||
// This is the default behaviour of the grid, but it is not used by the main grid.
|
// This is the default behaviour of the grid, but it is not used by the main grid.
|
||||||
func (grid *Grid) KeyPressed(key rune, mod int) {
|
func (grid *Grid) KeyPressed(key rune, mod int) {
|
||||||
// TODO: Not sure if this would be better handled using commands
|
// TODO: Not sure if this would be better handled using commands
|
||||||
if (key == 'i') || (key == KeyArrowUp) {
|
if (key == 'i') || (key == KeyArrowUp) {
|
||||||
grid.MoveBy(0, -1)
|
grid.MoveBy(0, -1)
|
||||||
} else if (key == 'k') || (key == KeyArrowDown) {
|
} else if (key == 'k') || (key == KeyArrowDown) {
|
||||||
grid.MoveBy(0, 1)
|
grid.MoveBy(0, 1)
|
||||||
} else if (key == 'j') || (key == KeyArrowLeft) {
|
} else if (key == 'j') || (key == KeyArrowLeft) {
|
||||||
grid.MoveBy(-1, 0)
|
grid.MoveBy(-1, 0)
|
||||||
} else if (key == 'l') || (key == KeyArrowRight) {
|
} else if (key == 'l') || (key == KeyArrowRight) {
|
||||||
grid.MoveBy(1, 0)
|
grid.MoveBy(1, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// --------------------------------------------------------------------------------------------
|
// --------------------------------------------------------------------------------------------
|
||||||
// Test Model
|
// Test Model
|
||||||
|
|
||||||
type TestModel struct {
|
type TestModel struct {
|
||||||
thing int
|
thing int
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the size of the grid model (width x height)
|
* Returns the size of the grid model (width x height)
|
||||||
*/
|
*/
|
||||||
func (model *TestModel) Dimensions() (int, int) {
|
func (model *TestModel) Dimensions() (int, int) {
|
||||||
return 100, 100
|
return 100, 100
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the size of the particular column. If the size is 0, this indicates that the column is hidden.
|
* Returns the size of the particular column. If the size is 0, this indicates that the column is hidden.
|
||||||
*/
|
*/
|
||||||
func (model *TestModel) ColWidth(int) int {
|
func (model *TestModel) ColWidth(int) int {
|
||||||
return 16
|
return 16
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the size of the particular row. If the size is 0, this indicates that the row is hidden.
|
* Returns the size of the particular row. If the size is 0, this indicates that the row is hidden.
|
||||||
*/
|
*/
|
||||||
func (model *TestModel) RowHeight(int) int {
|
func (model *TestModel) RowHeight(int) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the value of the cell a position X, Y
|
* Returns the value of the cell a position X, Y
|
||||||
*/
|
*/
|
||||||
func (model *TestModel) CellValue(x int, y int) string {
|
func (model *TestModel) CellValue(x int, y int) string {
|
||||||
return strconv.Itoa(x) + "," + strconv.Itoa(y)
|
return strconv.Itoa(x) + "," + strconv.Itoa(y)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,84 +3,86 @@
|
||||||
|
|
||||||
package ui
|
package ui
|
||||||
|
|
||||||
|
|
||||||
// The UI manager
|
// The UI manager
|
||||||
type Ui struct {
|
type Ui struct {
|
||||||
// The root component
|
// The root component
|
||||||
rootComponent UiComponent
|
rootComponent UiComponent
|
||||||
focusedComponent FocusableComponent
|
focusedComponent FocusableComponent
|
||||||
|
|
||||||
drawContext *DrawContext
|
drawContext *DrawContext
|
||||||
driver Driver
|
driver Driver
|
||||||
|
shutdown bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Creates a new UI context. This also initializes the UI state.
|
// Creates a new UI context. This also initializes the UI state.
|
||||||
// Returns the context and an error.
|
// Returns the context and an error.
|
||||||
func NewUI() (*Ui, error) {
|
func NewUI() (*Ui, error) {
|
||||||
driver := &TermboxDriver{}
|
driver := &TermboxDriver{}
|
||||||
err := driver.Init()
|
err := driver.Init()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
drawContext := &DrawContext{ driver: driver }
|
drawContext := &DrawContext{driver: driver}
|
||||||
ui := &Ui{ drawContext: drawContext, driver: driver }
|
ui := &Ui{drawContext: drawContext, driver: driver}
|
||||||
|
|
||||||
return ui, nil
|
return ui, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Closes the UI context.
|
// Closes the UI context.
|
||||||
func (ui *Ui) Close() {
|
func (ui *Ui) Close() {
|
||||||
ui.driver.Close()
|
ui.driver.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sets the root component
|
// Sets the root component
|
||||||
func (ui *Ui) SetRootComponent(comp UiComponent) {
|
func (ui *Ui) SetRootComponent(comp UiComponent) {
|
||||||
ui.rootComponent = comp
|
ui.rootComponent = comp
|
||||||
ui.Remeasure()
|
ui.Remeasure()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sets the focused component
|
// Sets the focused component
|
||||||
func (ui *Ui) SetFocusedComponent(newFocused FocusableComponent) {
|
func (ui *Ui) SetFocusedComponent(newFocused FocusableComponent) {
|
||||||
ui.focusedComponent = newFocused
|
ui.focusedComponent = newFocused
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remeasures the UI
|
// Remeasures the UI
|
||||||
func (ui *Ui) Remeasure() {
|
func (ui *Ui) Remeasure() {
|
||||||
ui.drawContext.X = 0
|
ui.drawContext.X = 0
|
||||||
ui.drawContext.Y = 0
|
ui.drawContext.Y = 0
|
||||||
ui.drawContext.W, ui.drawContext.H = ui.driver.Size()
|
ui.drawContext.W, ui.drawContext.H = ui.driver.Size()
|
||||||
|
|
||||||
ui.rootComponent.Remeasure(ui.drawContext.W, ui.drawContext.H)
|
ui.rootComponent.Remeasure(ui.drawContext.W, ui.drawContext.H)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redraws the UI.
|
// Redraws the UI.
|
||||||
func (ui *Ui) Redraw() {
|
func (ui *Ui) Redraw() {
|
||||||
ui.Remeasure()
|
ui.Remeasure()
|
||||||
|
|
||||||
ui.rootComponent.Redraw(ui.drawContext)
|
ui.rootComponent.Redraw(ui.drawContext)
|
||||||
ui.driver.Sync()
|
ui.driver.Sync()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Quit indicates to the UI that it should shutdown
|
||||||
|
func (ui *Ui) Shutdown() {
|
||||||
|
ui.shutdown = true
|
||||||
|
}
|
||||||
|
|
||||||
// Enter the UI loop
|
// Enter the UI loop
|
||||||
func (ui *Ui) Loop() {
|
func (ui *Ui) Loop() {
|
||||||
for {
|
for !ui.shutdown {
|
||||||
ui.Redraw()
|
ui.Redraw()
|
||||||
event := ui.driver.WaitForEvent()
|
event := ui.driver.WaitForEvent()
|
||||||
|
|
||||||
// TODO: If the event is a key-press, do something.
|
// TODO: If the event is a key-press, do something.
|
||||||
if event.Type == EventKeyPress {
|
if event.Type == EventKeyPress {
|
||||||
if ui.focusedComponent != nil {
|
if ui.focusedComponent != nil {
|
||||||
ui.focusedComponent.KeyPressed(event.Ch, event.Par)
|
ui.focusedComponent.KeyPressed(event.Ch, event.Par)
|
||||||
}
|
}
|
||||||
} else if event.Type == EventResize {
|
} else if event.Type == EventResize {
|
||||||
|
|
||||||
// HACK: Find another way to refresh the size of the screen to prevent a full redraw.
|
// HACK: Find another way to refresh the size of the screen to prevent a full redraw.
|
||||||
ui.driver.Sync()
|
ui.driver.Sync()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
205
ui/stdcomps.go
205
ui/stdcomps.go
|
|
@ -4,178 +4,187 @@ package ui
|
||||||
|
|
||||||
import "unicode"
|
import "unicode"
|
||||||
|
|
||||||
|
|
||||||
// A text component. This simply renders a text string.
|
// A text component. This simply renders a text string.
|
||||||
type TextView struct {
|
type TextView struct {
|
||||||
|
|
||||||
// The string to render
|
// The string to render
|
||||||
Text string
|
Text string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Minimum dimensions
|
// Minimum dimensions
|
||||||
func (tv *TextView) Remeasure(w, h int) (int, int) {
|
func (tv *TextView) Remeasure(w, h int) (int, int) {
|
||||||
return w, 1
|
return w, 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Status bar redraw
|
// Status bar redraw
|
||||||
func (tv *TextView) Redraw(context *DrawContext) {
|
func (tv *TextView) Redraw(context *DrawContext) {
|
||||||
context.SetFgAttr(0)
|
context.SetFgAttr(0)
|
||||||
context.SetBgAttr(0)
|
context.SetBgAttr(0)
|
||||||
|
|
||||||
context.HorizRule(0, ' ')
|
context.HorizRule(0, ' ')
|
||||||
context.Print(0, 0, tv.Text)
|
context.Print(0, 0, tv.Text)
|
||||||
|
context.HideCursor()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Status bar component. This component displays text on the left and right of it's
|
// Status bar component. This component displays text on the left and right of it's
|
||||||
// allocated space.
|
// allocated space.
|
||||||
type StatusBar struct {
|
type StatusBar struct {
|
||||||
Left string // Left aligned string
|
Left string // Left aligned string
|
||||||
Right string // Right aligned string
|
Right string // Right aligned string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Minimum dimensions
|
// Minimum dimensions
|
||||||
func (sbar *StatusBar) Remeasure(w, h int) (int, int) {
|
func (sbar *StatusBar) Remeasure(w, h int) (int, int) {
|
||||||
return w, 1
|
return w, 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Status bar redraw
|
// Status bar redraw
|
||||||
func (sbar *StatusBar) Redraw(context *DrawContext) {
|
func (sbar *StatusBar) Redraw(context *DrawContext) {
|
||||||
context.SetFgAttr(AttrReverse)
|
context.SetFgAttr(AttrReverse)
|
||||||
context.SetBgAttr(AttrReverse)
|
context.SetBgAttr(AttrReverse)
|
||||||
|
|
||||||
context.HorizRule(0, ' ')
|
context.HorizRule(0, ' ')
|
||||||
context.Print(0, 0, sbar.Left)
|
context.Print(0, 0, sbar.Left)
|
||||||
context.PrintRight(context.W, 0, sbar.Right)
|
context.PrintRight(context.W, 0, sbar.Right)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// A single-text entry component.
|
// A single-text entry component.
|
||||||
type TextEntry struct {
|
type TextEntry struct {
|
||||||
Prompt string
|
Prompt string
|
||||||
|
|
||||||
value string
|
value string
|
||||||
cursorOffset int
|
cursorOffset int
|
||||||
displayOffset int
|
displayOffset int
|
||||||
|
|
||||||
|
// Called when the user presses Enter
|
||||||
|
OnEntry func(val string)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (te *TextEntry) Remeasure(w, h int) (int, int) {
|
func (te *TextEntry) Remeasure(w, h int) (int, int) {
|
||||||
return w, 1
|
return w, 1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (te *TextEntry) Redraw(context *DrawContext) {
|
func (te *TextEntry) Redraw(context *DrawContext) {
|
||||||
context.HorizRule(0, ' ')
|
context.HorizRule(0, ' ')
|
||||||
valueOffsetX := 0
|
valueOffsetX := 0
|
||||||
displayOffsetX := te.calculateDisplayOffset(context.W)
|
displayOffsetX := te.calculateDisplayOffset(context.W)
|
||||||
|
|
||||||
if te.Prompt != "" {
|
if te.Prompt != "" {
|
||||||
context.SetFgAttr(ColorDefault | AttrBold)
|
context.SetFgAttr(ColorDefault | AttrBold)
|
||||||
context.Print(0, 0, te.Prompt)
|
context.Print(0, 0, te.Prompt)
|
||||||
context.SetFgAttr(ColorDefault)
|
context.SetFgAttr(ColorDefault)
|
||||||
|
|
||||||
valueOffsetX = len(te.Prompt)
|
valueOffsetX = len(te.Prompt)
|
||||||
}
|
}
|
||||||
|
|
||||||
context.Print(valueOffsetX, 0, te.value[displayOffsetX:intMin(displayOffsetX + context.W,len(te.value))])
|
context.Print(valueOffsetX, 0, te.value[displayOffsetX:intMin(displayOffsetX+context.W, len(te.value))])
|
||||||
context.SetCursorPosition(te.cursorOffset + valueOffsetX - displayOffsetX, 0)
|
context.SetCursorPosition(te.cursorOffset+valueOffsetX-displayOffsetX, 0)
|
||||||
|
|
||||||
//context.Print(0, 0, fmt.Sprintf("%d,%d", te.cursorOffset, displayOffsetX))
|
//context.Print(0, 0, fmt.Sprintf("%d,%d", te.cursorOffset, displayOffsetX))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (te *TextEntry) calculateDisplayOffset(displayWidth int) int {
|
func (te *TextEntry) calculateDisplayOffset(displayWidth int) int {
|
||||||
if te.Prompt != "" {
|
if te.Prompt != "" {
|
||||||
displayWidth -= len(te.Prompt)
|
displayWidth -= len(te.Prompt)
|
||||||
}
|
}
|
||||||
virtualCursorOffset := te.cursorOffset - te.displayOffset
|
virtualCursorOffset := te.cursorOffset - te.displayOffset
|
||||||
|
|
||||||
if virtualCursorOffset >= displayWidth {
|
if virtualCursorOffset >= displayWidth {
|
||||||
te.displayOffset = te.cursorOffset - displayWidth + 10
|
te.displayOffset = te.cursorOffset - displayWidth + 10
|
||||||
} else if (virtualCursorOffset < 0) {
|
} else if virtualCursorOffset < 0 {
|
||||||
te.displayOffset = intMax(te.cursorOffset - displayWidth + 1, 0)
|
te.displayOffset = intMax(te.cursorOffset-displayWidth+1, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
return te.displayOffset
|
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) {
|
func (te *TextEntry) KeyPressed(key rune, mod int) {
|
||||||
if (key >= ' ') && (key <= '~') {
|
if (key >= ' ') && (key <= '~') {
|
||||||
te.insertRune(key)
|
te.insertRune(key)
|
||||||
} else if (key == KeyArrowLeft) {
|
} else if key == KeyArrowLeft {
|
||||||
te.moveCursorBy(-1)
|
te.moveCursorBy(-1)
|
||||||
} else if (key == KeyArrowRight) {
|
} else if key == KeyArrowRight {
|
||||||
te.moveCursorBy(1)
|
te.moveCursorBy(1)
|
||||||
} else if (key == KeyHome) {
|
} else if key == KeyHome {
|
||||||
te.moveCursorTo(0)
|
te.moveCursorTo(0)
|
||||||
} else if (key == KeyEnd) {
|
} else if key == KeyEnd {
|
||||||
te.moveCursorTo(len(te.value))
|
te.moveCursorTo(len(te.value))
|
||||||
} else if (key == KeyBackspace) || (key == KeyBackspace2) {
|
} else if (key == KeyBackspace) || (key == KeyBackspace2) {
|
||||||
if (mod & ModKeyAlt != 0) {
|
if mod&ModKeyAlt != 0 {
|
||||||
te.backspaceWhile(unicode.IsSpace)
|
te.backspaceWhile(unicode.IsSpace)
|
||||||
te.backspaceWhile(func(r rune) bool { return !unicode.IsSpace(r) })
|
te.backspaceWhile(func(r rune) bool { return !unicode.IsSpace(r) })
|
||||||
} else {
|
} else {
|
||||||
te.backspace()
|
te.backspace()
|
||||||
}
|
}
|
||||||
} else if (key == KeyCtrlK) {
|
} else if key == KeyCtrlK {
|
||||||
te.killLine()
|
te.killLine()
|
||||||
} else if (key == KeyDelete) {
|
} else if key == KeyDelete {
|
||||||
te.removeCharAtPos(te.cursorOffset)
|
te.removeCharAtPos(te.cursorOffset)
|
||||||
} else if (key == KeyEnter) {
|
} else if key == KeyEnter {
|
||||||
panic("Entered text: '" + te.value + "'")
|
//panic("Entered text: '" + te.value + "'")
|
||||||
}
|
if te.OnEntry != nil {
|
||||||
|
te.OnEntry(te.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Backspace
|
// Backspace
|
||||||
func (te *TextEntry) backspace() {
|
func (te *TextEntry) backspace() {
|
||||||
te.removeCharAtPos(te.cursorOffset - 1)
|
te.removeCharAtPos(te.cursorOffset - 1)
|
||||||
te.moveCursorBy(-1)
|
te.moveCursorBy(-1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Backspace while the character underneith the cursor matches the guard
|
// Backspace while the character underneith the cursor matches the guard
|
||||||
func (te *TextEntry) backspaceWhile(guard func(r rune) bool) {
|
func (te *TextEntry) backspaceWhile(guard func(r rune) bool) {
|
||||||
for (te.cursorOffset > 0) {
|
for te.cursorOffset > 0 {
|
||||||
ch := rune(te.value[te.cursorOffset - 1])
|
ch := rune(te.value[te.cursorOffset-1])
|
||||||
if guard(ch) {
|
if guard(ch) {
|
||||||
te.backspace()
|
te.backspace()
|
||||||
} else {
|
} else {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Kill the line. If the cursor is at the end of the line, kill to the start.
|
// Kill the line. If the cursor is at the end of the line, kill to the start.
|
||||||
// Otherwise, trim the line.
|
// Otherwise, trim the line.
|
||||||
func (te *TextEntry) killLine() {
|
func (te *TextEntry) killLine() {
|
||||||
if (te.cursorOffset < len(te.value)) {
|
if te.cursorOffset < len(te.value) {
|
||||||
te.value = te.value[:te.cursorOffset]
|
te.value = te.value[:te.cursorOffset]
|
||||||
} else {
|
} else {
|
||||||
te.value = ""
|
te.value = ""
|
||||||
te.cursorOffset = 0
|
te.cursorOffset = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inserts a rune at the cursor position
|
// Inserts a rune at the cursor position
|
||||||
func (te *TextEntry) insertRune(key rune) {
|
func (te *TextEntry) insertRune(key rune) {
|
||||||
if (te.cursorOffset >= len(te.value)) {
|
if te.cursorOffset >= len(te.value) {
|
||||||
te.value += string(key)
|
te.value += string(key)
|
||||||
} else {
|
} else {
|
||||||
te.value = te.value[:te.cursorOffset] + string(key) + te.value[te.cursorOffset:]
|
te.value = te.value[:te.cursorOffset] + string(key) + te.value[te.cursorOffset:]
|
||||||
}
|
}
|
||||||
te.moveCursorBy(1)
|
te.moveCursorBy(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the character at a specific position
|
// Remove the character at a specific position
|
||||||
func (te *TextEntry) removeCharAtPos(pos int) {
|
func (te *TextEntry) removeCharAtPos(pos int) {
|
||||||
if (pos >= 0) && (pos < len(te.value)) {
|
if (pos >= 0) && (pos < len(te.value)) {
|
||||||
te.value = te.value[:pos] + te.value[pos+1:]
|
te.value = te.value[:pos] + te.value[pos+1:]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move the cursor
|
// Move the cursor
|
||||||
func (te *TextEntry) moveCursorBy(byX int) {
|
func (te *TextEntry) moveCursorBy(byX int) {
|
||||||
te.moveCursorTo(te.cursorOffset + byX)
|
te.moveCursorTo(te.cursorOffset + byX)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (te *TextEntry) moveCursorTo(toX int) {
|
func (te *TextEntry) moveCursorTo(toX int) {
|
||||||
te.cursorOffset = intMinMax(toX, 0, len(te.value))
|
te.cursorOffset = intMinMax(toX, 0, len(te.value))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,134 +3,135 @@
|
||||||
package ui
|
package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/nsf/termbox-go"
|
"github.com/nsf/termbox-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
type TermboxDriver struct {
|
type TermboxDriver struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Initializes the driver. Returns an error if there was an error
|
// Initializes the driver. Returns an error if there was an error
|
||||||
func (td *TermboxDriver) Init() error {
|
func (td *TermboxDriver) Init() error {
|
||||||
err := termbox.Init()
|
err := termbox.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
termbox.SetInputMode(termbox.InputAlt)
|
termbox.SetInputMode(termbox.InputAlt)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Closes the driver
|
// Closes the driver
|
||||||
func (td *TermboxDriver) Close() {
|
func (td *TermboxDriver) Close() {
|
||||||
termbox.Close()
|
termbox.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the size of the window.
|
// Returns the size of the window.
|
||||||
func (td *TermboxDriver) Size() (int, int) {
|
func (td *TermboxDriver) Size() (int, int) {
|
||||||
return termbox.Size()
|
return termbox.Size()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sets the value of a specific cell
|
// Sets the value of a specific cell
|
||||||
func (td *TermboxDriver) SetCell(x, y int, ch rune, fg, bg Attribute) {
|
func (td *TermboxDriver) SetCell(x, y int, ch rune, fg, bg Attribute) {
|
||||||
termbox.SetCell(x, y, ch, termbox.Attribute(fg), termbox.Attribute(bg))
|
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
|
// Synchronizes the internal buffer with the real buffer
|
||||||
func (td *TermboxDriver) Sync() {
|
func (td *TermboxDriver) Sync() {
|
||||||
termbox.Flush()
|
termbox.Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for an event
|
// Wait for an event
|
||||||
func (td *TermboxDriver) WaitForEvent() Event {
|
func (td *TermboxDriver) WaitForEvent() Event {
|
||||||
tev := termbox.PollEvent()
|
tev := termbox.PollEvent()
|
||||||
|
|
||||||
switch tev.Type {
|
switch tev.Type {
|
||||||
case termbox.EventResize:
|
case termbox.EventResize:
|
||||||
return Event{EventResize, 0, 0}
|
return Event{EventResize, 0, 0}
|
||||||
case termbox.EventKey:
|
case termbox.EventKey:
|
||||||
mod := 0
|
mod := 0
|
||||||
if tev.Mod & termbox.ModAlt != 0 {
|
if tev.Mod&termbox.ModAlt != 0 {
|
||||||
mod = ModKeyAlt
|
mod = ModKeyAlt
|
||||||
}
|
}
|
||||||
if tev.Ch != 0 {
|
if tev.Ch != 0 {
|
||||||
return Event{EventKeyPress, mod, tev.Ch}
|
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}
|
return Event{EventKeyPress, mod, spec}
|
||||||
} else {
|
} else {
|
||||||
return Event{EventNone, mod, 0}
|
return Event{EventNone, mod, 0}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return Event{EventNone, 0, 0}
|
return Event{EventNone, 0, 0}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move the position of the cursor
|
// Move the position of the cursor
|
||||||
func (td *TermboxDriver) SetCursor(x, y int) {
|
func (td *TermboxDriver) SetCursor(x, y int) {
|
||||||
termbox.SetCursor(x, y)
|
termbox.SetCursor(x, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Map from termbox Keys to driver key runes
|
// Map from termbox Keys to driver key runes
|
||||||
var termboxKeysToSpecialKeys = map[termbox.Key]rune {
|
var termboxKeysToSpecialKeys = map[termbox.Key]rune{
|
||||||
termbox.KeySpace: ' ',
|
termbox.KeySpace: ' ',
|
||||||
|
|
||||||
termbox.KeyF1: KeyF1,
|
termbox.KeyF1: KeyF1,
|
||||||
termbox.KeyF2: KeyF2,
|
termbox.KeyF2: KeyF2,
|
||||||
termbox.KeyF3: KeyF3,
|
termbox.KeyF3: KeyF3,
|
||||||
termbox.KeyF4: KeyF4,
|
termbox.KeyF4: KeyF4,
|
||||||
termbox.KeyF5: KeyF5,
|
termbox.KeyF5: KeyF5,
|
||||||
termbox.KeyF6: KeyF6,
|
termbox.KeyF6: KeyF6,
|
||||||
termbox.KeyF7: KeyF7,
|
termbox.KeyF7: KeyF7,
|
||||||
termbox.KeyF8: KeyF8,
|
termbox.KeyF8: KeyF8,
|
||||||
termbox.KeyF9: KeyF9,
|
termbox.KeyF9: KeyF9,
|
||||||
termbox.KeyF10: KeyF10,
|
termbox.KeyF10: KeyF10,
|
||||||
termbox.KeyF11: KeyF11,
|
termbox.KeyF11: KeyF11,
|
||||||
termbox.KeyF12: KeyF12,
|
termbox.KeyF12: KeyF12,
|
||||||
termbox.KeyInsert: KeyInsert,
|
termbox.KeyInsert: KeyInsert,
|
||||||
termbox.KeyDelete: KeyDelete,
|
termbox.KeyDelete: KeyDelete,
|
||||||
termbox.KeyHome: KeyHome,
|
termbox.KeyHome: KeyHome,
|
||||||
termbox.KeyEnd: KeyEnd,
|
termbox.KeyEnd: KeyEnd,
|
||||||
termbox.KeyPgup: KeyPgup,
|
termbox.KeyPgup: KeyPgup,
|
||||||
termbox.KeyPgdn: KeyPgdn,
|
termbox.KeyPgdn: KeyPgdn,
|
||||||
termbox.KeyArrowUp: KeyArrowUp,
|
termbox.KeyArrowUp: KeyArrowUp,
|
||||||
termbox.KeyArrowDown: KeyArrowDown,
|
termbox.KeyArrowDown: KeyArrowDown,
|
||||||
termbox.KeyArrowLeft: KeyArrowLeft,
|
termbox.KeyArrowLeft: KeyArrowLeft,
|
||||||
termbox.KeyArrowRight: KeyArrowRight,
|
termbox.KeyArrowRight: KeyArrowRight,
|
||||||
|
|
||||||
termbox.KeyCtrlSpace: KeyCtrlSpace,
|
termbox.KeyCtrlSpace: KeyCtrlSpace,
|
||||||
termbox.KeyCtrlA: KeyCtrlA,
|
termbox.KeyCtrlA: KeyCtrlA,
|
||||||
termbox.KeyCtrlB: KeyCtrlB,
|
termbox.KeyCtrlB: KeyCtrlB,
|
||||||
termbox.KeyCtrlC: KeyCtrlC,
|
termbox.KeyCtrlC: KeyCtrlC,
|
||||||
termbox.KeyCtrlD: KeyCtrlD,
|
termbox.KeyCtrlD: KeyCtrlD,
|
||||||
termbox.KeyCtrlE: KeyCtrlE,
|
termbox.KeyCtrlE: KeyCtrlE,
|
||||||
termbox.KeyCtrlF: KeyCtrlF,
|
termbox.KeyCtrlF: KeyCtrlF,
|
||||||
termbox.KeyCtrlG: KeyCtrlG,
|
termbox.KeyCtrlG: KeyCtrlG,
|
||||||
termbox.KeyCtrlH: KeyCtrlH,
|
termbox.KeyCtrlH: KeyCtrlH,
|
||||||
termbox.KeyCtrlI: KeyCtrlI,
|
termbox.KeyCtrlI: KeyCtrlI,
|
||||||
termbox.KeyCtrlJ: KeyCtrlJ,
|
termbox.KeyCtrlJ: KeyCtrlJ,
|
||||||
termbox.KeyCtrlK: KeyCtrlK,
|
termbox.KeyCtrlK: KeyCtrlK,
|
||||||
termbox.KeyCtrlL: KeyCtrlL,
|
termbox.KeyCtrlL: KeyCtrlL,
|
||||||
termbox.KeyCtrlM: KeyCtrlM,
|
termbox.KeyCtrlM: KeyCtrlM,
|
||||||
termbox.KeyCtrlN: KeyCtrlN,
|
termbox.KeyCtrlN: KeyCtrlN,
|
||||||
termbox.KeyCtrlO: KeyCtrlO,
|
termbox.KeyCtrlO: KeyCtrlO,
|
||||||
termbox.KeyCtrlP: KeyCtrlP,
|
termbox.KeyCtrlP: KeyCtrlP,
|
||||||
termbox.KeyCtrlQ: KeyCtrlQ,
|
termbox.KeyCtrlQ: KeyCtrlQ,
|
||||||
termbox.KeyCtrlR: KeyCtrlR,
|
termbox.KeyCtrlR: KeyCtrlR,
|
||||||
termbox.KeyCtrlS: KeyCtrlS,
|
termbox.KeyCtrlS: KeyCtrlS,
|
||||||
termbox.KeyCtrlT: KeyCtrlT,
|
termbox.KeyCtrlT: KeyCtrlT,
|
||||||
termbox.KeyCtrlU: KeyCtrlU,
|
termbox.KeyCtrlU: KeyCtrlU,
|
||||||
termbox.KeyCtrlV: KeyCtrlV,
|
termbox.KeyCtrlV: KeyCtrlV,
|
||||||
termbox.KeyCtrlW: KeyCtrlW,
|
termbox.KeyCtrlW: KeyCtrlW,
|
||||||
termbox.KeyCtrlX: KeyCtrlX,
|
termbox.KeyCtrlX: KeyCtrlX,
|
||||||
termbox.KeyCtrlY: KeyCtrlY,
|
termbox.KeyCtrlY: KeyCtrlY,
|
||||||
termbox.KeyCtrlZ: KeyCtrlZ,
|
termbox.KeyCtrlZ: KeyCtrlZ,
|
||||||
termbox.KeyCtrl3: KeyCtrl3,
|
termbox.KeyCtrl3: KeyCtrl3,
|
||||||
termbox.KeyCtrl4: KeyCtrl4,
|
termbox.KeyCtrl4: KeyCtrl4,
|
||||||
termbox.KeyCtrl5: KeyCtrl5,
|
termbox.KeyCtrl5: KeyCtrl5,
|
||||||
termbox.KeyCtrl6: KeyCtrl6,
|
termbox.KeyCtrl6: KeyCtrl6,
|
||||||
termbox.KeyCtrl7: KeyCtrl7,
|
termbox.KeyCtrl7: KeyCtrl7,
|
||||||
termbox.KeyCtrl8: KeyCtrl8,
|
termbox.KeyCtrl8: KeyCtrl8,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue