Have added prompting.
This commit is contained in:
parent
665bc8cdae
commit
33847a78c1
|
|
@ -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
|
||||
|
|
|
|||
54
frame.go
54
frame.go
|
|
@ -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
|
||||
|
|
|
|||
7
main.go
7
main.go
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
23
session.go
23
session.go
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
49
ui/grid.go
49
ui/grid.go
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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:]
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in a new issue