2013-06-04 06:17:48 +00:00
|
|
|
/**
|
|
|
|
|
* The grid component. This is used for displaying the model.
|
|
|
|
|
*/
|
2015-01-03 08:05:04 +00:00
|
|
|
package ui
|
2013-06-04 06:17:48 +00:00
|
|
|
|
|
|
|
|
import "strconv"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* An abstract display model.
|
|
|
|
|
*/
|
|
|
|
|
type GridModel interface {
|
|
|
|
|
/**
|
|
|
|
|
* Returns the size of the grid model (width x height)
|
|
|
|
|
*/
|
|
|
|
|
GetDimensions() (int, int)
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the size of the particular column. If the size is 0, this indicates that the column is hidden.
|
|
|
|
|
*/
|
|
|
|
|
GetColWidth(int) int
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the size of the particular row. If the size is 0, this indicates that the row is hidden.
|
|
|
|
|
*/
|
|
|
|
|
GetRowHeight(int) int
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the value of the cell a position X, Y
|
|
|
|
|
*/
|
|
|
|
|
GetCellValue(int, int) string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
type gridPoint int
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The grid component.
|
|
|
|
|
*/
|
|
|
|
|
type Grid struct {
|
|
|
|
|
model GridModel
|
2013-06-05 03:26:10 +00:00
|
|
|
viewCellX int // Left most cell
|
|
|
|
|
viewCellY int // Top most cell
|
|
|
|
|
selCellX int // The currently selected cell
|
|
|
|
|
selCellY int
|
|
|
|
|
cellsWide int // Measured number of cells. Recalculated on redraw.
|
|
|
|
|
cellsHigh int
|
2013-06-04 06:17:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Clipping rectangle
|
|
|
|
|
*/
|
|
|
|
|
type gridRect struct {
|
|
|
|
|
x1 gridPoint
|
|
|
|
|
y1 gridPoint
|
|
|
|
|
x2 gridPoint
|
|
|
|
|
y2 gridPoint
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Creates a new gridRect from integers.
|
|
|
|
|
*/
|
|
|
|
|
func newGridRect(x1, y1, x2, y2 int) gridRect {
|
|
|
|
|
return gridRect{gridPoint(x1), gridPoint(y1), gridPoint(x2), gridPoint(y2)}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Creates a new grid.
|
|
|
|
|
*/
|
|
|
|
|
func NewGrid(model GridModel) *Grid {
|
2013-06-05 03:26:10 +00:00
|
|
|
return &Grid{model, 0, 0, 0, 0, -1, -1}
|
2013-06-04 06:17:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the requested dimensions of a grid (as required by UiComponent)
|
|
|
|
|
*/
|
2015-01-03 08:05:04 +00:00
|
|
|
func (grid *Grid) Remeasure(w, h int) (int, int) {
|
|
|
|
|
return w, h
|
2013-06-04 06:17:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Shifts the viewport of the grid.
|
|
|
|
|
*/
|
|
|
|
|
func (grid *Grid) ShiftBy(x int, y int) {
|
2013-06-05 03:26:10 +00:00
|
|
|
grid.viewCellX += x
|
|
|
|
|
grid.viewCellY += y
|
2013-06-04 13:25:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2013-06-05 03:26:10 +00:00
|
|
|
// Moves the currently selected cell by a delta.
|
|
|
|
|
func (grid *Grid) MoveBy(x int, y int) {
|
|
|
|
|
grid.selCellX += x
|
|
|
|
|
grid.selCellY += y
|
|
|
|
|
grid.reposition()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Determine the topmost cell based on the location of the currently selected cell
|
|
|
|
|
func (grid *Grid) reposition() {
|
|
|
|
|
|
|
|
|
|
// If we have no measurement information, forget it.
|
|
|
|
|
if (grid.cellsWide == -1) || (grid.cellsHigh == -1) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if grid.selCellX < grid.viewCellX {
|
|
|
|
|
grid.viewCellX = grid.selCellX
|
|
|
|
|
} else if grid.selCellX >= (grid.viewCellX + grid.cellsWide - 3) {
|
|
|
|
|
grid.viewCellX = grid.selCellX - (grid.cellsWide - 3)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if grid.selCellY < grid.viewCellY {
|
|
|
|
|
grid.viewCellY = grid.selCellY
|
|
|
|
|
} else if grid.selCellY >= (grid.viewCellY + grid.cellsHigh - 3) {
|
|
|
|
|
grid.viewCellY = grid.selCellY - (grid.cellsHigh - 3)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2013-06-04 13:25:02 +00:00
|
|
|
// Gets the cell value and attributes of a particular cell
|
2015-01-03 08:05:04 +00:00
|
|
|
func (grid *Grid) getCellData(cellX, cellY int) (text string, fg, bg Attribute) {
|
2013-06-04 13:25:02 +00:00
|
|
|
// The fixed cells
|
2013-06-05 03:26:10 +00:00
|
|
|
modelCellX := cellX - 1 + grid.viewCellX
|
|
|
|
|
modelCellY := cellY - 1 + grid.viewCellY
|
2013-06-04 13:25:02 +00:00
|
|
|
modelMaxX, modelMaxY := grid.model.GetDimensions()
|
|
|
|
|
|
|
|
|
|
if (cellX == 0) && (cellY == 0) {
|
2015-01-03 08:05:04 +00:00
|
|
|
return strconv.Itoa(grid.cellsWide), AttrBold, AttrBold
|
2013-06-04 13:25:02 +00:00
|
|
|
} else if (cellX == 0) {
|
2013-06-05 03:26:10 +00:00
|
|
|
if (modelCellY == grid.selCellY) {
|
2015-01-03 08:05:04 +00:00
|
|
|
return strconv.Itoa(modelCellY), AttrBold | AttrReverse, AttrBold | AttrReverse
|
2013-06-05 03:26:10 +00:00
|
|
|
} else {
|
2015-01-03 08:05:04 +00:00
|
|
|
return strconv.Itoa(modelCellY), AttrBold, AttrBold
|
2013-06-05 03:26:10 +00:00
|
|
|
}
|
2013-06-04 13:25:02 +00:00
|
|
|
} else if (cellY == 0) {
|
2013-06-05 03:26:10 +00:00
|
|
|
if (modelCellX == grid.selCellX) {
|
2015-01-03 08:05:04 +00:00
|
|
|
return strconv.Itoa(modelCellX), AttrBold | AttrReverse, AttrBold | AttrReverse
|
2013-06-05 03:26:10 +00:00
|
|
|
} else {
|
2015-01-03 08:05:04 +00:00
|
|
|
return strconv.Itoa(modelCellX), AttrBold, AttrBold
|
2013-06-05 03:26:10 +00:00
|
|
|
}
|
2013-06-04 13:25:02 +00:00
|
|
|
} else {
|
|
|
|
|
// The data from the model
|
2013-06-05 03:26:10 +00:00
|
|
|
if (modelCellX >= 0) && (modelCellY >= 0) && (modelCellX < modelMaxX) && (modelCellY < modelMaxY) {
|
|
|
|
|
if (modelCellX == grid.selCellX) && (modelCellY == grid.selCellY) {
|
2015-01-03 08:05:04 +00:00
|
|
|
return grid.model.GetCellValue(modelCellX, modelCellY), AttrReverse, AttrReverse
|
2013-06-05 03:26:10 +00:00
|
|
|
} else {
|
|
|
|
|
return grid.model.GetCellValue(modelCellX, modelCellY), 0, 0
|
|
|
|
|
}
|
2013-06-04 13:25:02 +00:00
|
|
|
} else {
|
|
|
|
|
return "~", 0, 0
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// XXX: Workaround for bug in compiler
|
|
|
|
|
panic("Unreachable code")
|
|
|
|
|
return "", 0, 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Gets the cell dimensions
|
|
|
|
|
func (grid *Grid) getCellDimensions(cellX, cellY int) (width, height int) {
|
|
|
|
|
|
|
|
|
|
var cellWidth, cellHeight int
|
|
|
|
|
|
2013-06-05 03:26:10 +00:00
|
|
|
modelCellX := cellX - 1 + grid.viewCellX
|
|
|
|
|
modelCellY := cellY - 1 + grid.viewCellY
|
2013-06-04 13:25:02 +00:00
|
|
|
modelMaxX, modelMaxY := grid.model.GetDimensions()
|
|
|
|
|
|
|
|
|
|
// Get the cell width & height from model (if within range)
|
|
|
|
|
if (modelCellX >= 0) && (modelCellX < modelMaxX) {
|
|
|
|
|
cellWidth = grid.model.GetColWidth(modelCellX)
|
|
|
|
|
} else {
|
|
|
|
|
cellWidth = 8
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (modelCellY >= 0) && (modelCellY < modelMaxY) {
|
|
|
|
|
cellHeight = grid.model.GetRowHeight(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
|
2013-06-04 06:17:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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.
|
|
|
|
|
*/
|
2015-01-03 08:05:04 +00:00
|
|
|
func (grid *Grid) renderCell(ctx *DrawContext, cellClipRect gridRect, sx int, sy int, text string, fg, bg Attribute) {
|
2013-06-04 06:17:48 +00:00
|
|
|
for x := cellClipRect.x1; x <= cellClipRect.x2; x++ {
|
|
|
|
|
for y := cellClipRect.y1; y <= cellClipRect.y2; y++ {
|
|
|
|
|
currRune := ' '
|
|
|
|
|
if (y == 0) {
|
|
|
|
|
textPos := int(x)
|
|
|
|
|
if textPos < len(text) {
|
|
|
|
|
currRune = rune(text[textPos])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-03 08:05:04 +00:00
|
|
|
//termbox.SetCell(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)
|
2013-06-04 06:17:48 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 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)
|
2015-01-03 08:05:04 +00:00
|
|
|
func (grid *Grid) renderColumn(ctx *DrawContext, screenViewPort gridRect, cellX int, cellY int, cellOffsetX int, cellOffsetY int) (gridPoint, int) {
|
2013-06-04 06:17:48 +00:00
|
|
|
|
|
|
|
|
// The top-left position of the column
|
|
|
|
|
screenX := int(screenViewPort.x1)
|
|
|
|
|
screenY := int(screenViewPort.y1)
|
|
|
|
|
screenWidth := int(screenViewPort.x2 - screenViewPort.x1)
|
|
|
|
|
screenHeight := int(screenViewPort.y2 - screenViewPort.y1)
|
|
|
|
|
|
|
|
|
|
// Work out the column width and cap it if it will spill over the edge of the viewport
|
2013-06-04 13:25:02 +00:00
|
|
|
colWidth, _ := grid.getCellDimensions(cellX, cellY)
|
|
|
|
|
colWidth -= cellOffsetX
|
2013-06-04 06:17:48 +00:00
|
|
|
if colWidth > screenWidth {
|
|
|
|
|
colWidth = screenHeight
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The maximum
|
|
|
|
|
maxScreenY := screenY + screenHeight
|
2013-06-05 03:26:10 +00:00
|
|
|
cellsHigh := 0
|
2013-06-04 06:17:48 +00:00
|
|
|
|
|
|
|
|
for screenY < maxScreenY {
|
|
|
|
|
|
|
|
|
|
// Cap the row height if it will go beyond the edge of the viewport.
|
2013-06-04 13:25:02 +00:00
|
|
|
_, rowHeight := grid.getCellDimensions(cellX, cellY)
|
2013-06-04 06:17:48 +00:00
|
|
|
if screenY + rowHeight > maxScreenY {
|
|
|
|
|
rowHeight = maxScreenY - screenY
|
|
|
|
|
}
|
2013-06-04 13:25:02 +00:00
|
|
|
|
|
|
|
|
cellText, cellFg, cellBg := grid.getCellData(cellX, cellY)
|
2013-06-04 06:17:48 +00:00
|
|
|
|
2015-01-03 08:05:04 +00:00
|
|
|
grid.renderCell(ctx, newGridRect(cellOffsetX, cellOffsetY, colWidth - cellOffsetX, rowHeight),
|
2013-06-04 13:25:02 +00:00
|
|
|
screenX, screenY, cellText, cellFg, cellBg) // termbox.AttrReverse, termbox.AttrReverse
|
2013-06-04 06:17:48 +00:00
|
|
|
|
2013-06-05 03:26:10 +00:00
|
|
|
cellY++
|
|
|
|
|
cellsHigh++
|
2013-06-04 06:17:48 +00:00
|
|
|
screenY = screenY + rowHeight - cellOffsetY
|
|
|
|
|
cellOffsetY = 0
|
|
|
|
|
}
|
|
|
|
|
|
2013-06-05 03:26:10 +00:00
|
|
|
return gridPoint(screenX + colWidth), cellsHigh
|
2013-06-04 06:17:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2013-06-05 03:26:10 +00:00
|
|
|
// Renders the grid. Returns the number of cells in the X and Y direction were rendered.
|
|
|
|
|
//
|
2015-01-03 08:05:04 +00:00
|
|
|
func (grid *Grid) renderGrid(ctx *DrawContext, screenViewPort gridRect, cellX int, cellY int, cellOffsetX int, cellOffsetY int) (int, int) {
|
2013-06-04 06:17:48 +00:00
|
|
|
|
2013-06-05 03:26:10 +00:00
|
|
|
var cellsHigh = 0
|
|
|
|
|
var cellsWide = 0
|
|
|
|
|
|
2013-06-04 06:17:48 +00:00
|
|
|
for screenViewPort.x1 < screenViewPort.x2 {
|
2015-01-03 08:05:04 +00:00
|
|
|
screenViewPort.x1, cellsHigh = grid.renderColumn(ctx, screenViewPort, cellX, cellY, cellOffsetX, cellOffsetY)
|
2013-06-04 06:17:48 +00:00
|
|
|
cellX = cellX + 1
|
2013-06-05 03:26:10 +00:00
|
|
|
cellsWide++
|
2013-06-04 06:17:48 +00:00
|
|
|
cellOffsetX = 0
|
|
|
|
|
}
|
|
|
|
|
|
2013-06-05 03:26:10 +00:00
|
|
|
return cellsWide, cellsHigh
|
|
|
|
|
}
|
2013-06-04 06:17:48 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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) {
|
|
|
|
|
var wid, hei int = grid.model.GetDimensions()
|
|
|
|
|
posX = 0
|
|
|
|
|
posY = 0
|
|
|
|
|
|
|
|
|
|
cellX = -1
|
|
|
|
|
cellY = -1
|
|
|
|
|
|
|
|
|
|
// Go through columns to locate the particular cellX
|
|
|
|
|
for cx := 0; cx < wid; cx++ {
|
|
|
|
|
if (x >= posX) && (x < posX + grid.model.GetColWidth(cx)) {
|
|
|
|
|
// We found the X position
|
|
|
|
|
cellX = int(cx)
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for cy := 0; cy < hei; cy++ {
|
|
|
|
|
if (y >= posY) && (y < posY + grid.model.GetRowHeight(cy)) {
|
|
|
|
|
// And the Y position
|
|
|
|
|
cellY = int(cy)
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Redraws the grid.
|
|
|
|
|
*/
|
2015-01-03 08:05:04 +00:00
|
|
|
func (grid *Grid) Redraw(ctx *DrawContext) {
|
|
|
|
|
viewportRect := newGridRect(0, 0, ctx.W, ctx.H)
|
|
|
|
|
grid.cellsWide, grid.cellsHigh = grid.renderGrid(ctx, viewportRect, 0, 0, 0, 0)
|
2013-06-04 06:17:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --------------------------------------------------------------------------------------------
|
|
|
|
|
// Test Model
|
|
|
|
|
|
|
|
|
|
type TestModel struct {
|
|
|
|
|
thing int
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the size of the grid model (width x height)
|
|
|
|
|
*/
|
|
|
|
|
func (model *TestModel) GetDimensions() (int, int) {
|
|
|
|
|
return 100, 100
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the size of the particular column. If the size is 0, this indicates that the column is hidden.
|
|
|
|
|
*/
|
|
|
|
|
func (model *TestModel) GetColWidth(int) int {
|
|
|
|
|
return 16
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the size of the particular row. If the size is 0, this indicates that the row is hidden.
|
|
|
|
|
*/
|
|
|
|
|
func (model *TestModel) GetRowHeight(int) int {
|
|
|
|
|
return 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the value of the cell a position X, Y
|
|
|
|
|
*/
|
|
|
|
|
func (model *TestModel) GetCellValue(x int, y int) string {
|
|
|
|
|
return strconv.Itoa(x) + "," + strconv.Itoa(y)
|
|
|
|
|
}
|