diff --git a/src/grid.go b/src/grid.go new file mode 100644 index 0000000..aee2137 --- /dev/null +++ b/src/grid.go @@ -0,0 +1,243 @@ +/** + * The grid component. This is used for displaying the model. + */ +package main + +import "github.com/nsf/termbox-go" +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 + offsetX int // Offset of the viewport (REAL characters, not cells) + offsetY int // Offset of the viewport +} + +/** + * 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 { + return &Grid{model, 0, 0} +} + +/** + * Returns the requested dimensions of a grid (as required by UiComponent) + */ +func (grid *Grid) RequestDims() (int, int) { + return -1, -1 +} + +/** + * Shifts the viewport of the grid. + */ +func (grid *Grid) ShiftBy(x int, y int) { + grid.offsetX += x + grid.offsetY += y +} + + +/** + * 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. + */ +func (grid *Grid) renderCell(cellClipRect gridRect, sx int, sy int, text string, fg, bg termbox.Attribute) { + 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]) + } + } + + termbox.SetCell(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) +func (grid *Grid) renderColumn(screenViewPort gridRect, cellX int, cellY int, cellOffsetX int, cellOffsetY int) (int) { + + // 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 + colWidth := grid.model.GetColWidth(cellX) - cellOffsetX + if colWidth > screenWidth { + colWidth = screenHeight + } + + // The maximum + maxScreenY := screenY + screenHeight + + for screenY < maxScreenY { + + // Cap the row height if it will go beyond the edge of the viewport. + rowHeight := grid.model.GetRowHeight(cellY) + if screenY + rowHeight > maxScreenY { + rowHeight = maxScreenY - screenY + } + + grid.renderCell(newGridRect(cellOffsetX, cellOffsetY, colWidth - cellOffsetX, rowHeight), + screenX, screenY, grid.model.GetCellValue(cellX, cellY), 0, 0) // termbox.AttrReverse, termbox.AttrReverse + + cellY = cellY + 1 + screenY = screenY + rowHeight - cellOffsetY + cellOffsetY = 0 + } + + return screenX + colWidth +} + + +// Renders the grid. +func (grid *Grid) renderGrid(screenViewPort gridRect, cellX int, cellY int, cellOffsetX int, cellOffsetY int) { + + for screenViewPort.x1 < screenViewPort.x2 { + screenViewPort.x1 = gridPoint(grid.renderColumn(screenViewPort, cellX, cellY, cellOffsetX, cellOffsetY)) + cellX = cellX + 1 + cellOffsetX = 0 + } +} + + + +/** + * 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. + */ +func (grid *Grid) Redraw(x int, y int, w int, h int) { + viewportRect := newGridRect(x, y, x + w, y + h) + + cellX, cellY, posX, posY := grid.pointToCell(grid.offsetX, grid.offsetY) +/* + grid.renderCell(gridRect{0, 0, 14, 0}, 0, 0, "Hello", termbox.AttrReverse, termbox.AttrReverse) + grid.renderCell(gridRect{1, 0, 14, 0}, 0, 1, "Hello", termbox.AttrReverse, termbox.AttrReverse) + grid.renderCell(gridRect{2, 0, 14, 0}, 0, 2, "Hello", termbox.AttrReverse, termbox.AttrReverse) + grid.renderCell(gridRect{3, 0, 14, 0}, 0, 3, "Hello", termbox.AttrReverse, termbox.AttrReverse) + grid.renderCell(gridRect{4, 0, 14, 0}, 0, 4, "Hello", termbox.AttrReverse, termbox.AttrReverse) + grid.renderCell(gridRect{5, 0, 14, 0}, 0, 5, "Hello", termbox.AttrReverse, termbox.AttrReverse) +*/ + grid.renderGrid(viewportRect, cellX, cellY, grid.offsetX - posX, grid.offsetY - posY) +} + +// -------------------------------------------------------------------------------------------- +// 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) +} diff --git a/src/makefile b/src/makefile index 2fe2d25..04d68cc 100644 --- a/src/makefile +++ b/src/makefile @@ -1,8 +1,12 @@ +GO = go -GOFILES = main.go ui.go +GOFILES = main.go ui.go grid.go ted: $(GOFILES) - go build -o ted $(GOFILES) + $(GO) build -o ted $(GOFILES) clean: -rm ted + +dependencies: + $(GO) get "github.com/nsf/termbox-go" diff --git a/src/model.go b/src/model.go new file mode 100644 index 0000000..e4fb153 --- /dev/null +++ b/src/model.go @@ -0,0 +1,16 @@ +/** + * The model. + */ +package main + + +/** + * An abstract model interface. + */ +type IModel interface ( + + /** + * The dimensions of the model (width, height). + */ + GetDimensions() (int, int) +) diff --git a/src/ui.go b/src/ui.go index 49c26b9..5a764fb 100644 --- a/src/ui.go +++ b/src/ui.go @@ -49,7 +49,8 @@ type UiComponent interface { * Ui context type. */ type Ui struct { - statusBar UiComponent + grid *Grid + statusBar *UiStatusBar } @@ -63,7 +64,9 @@ func NewUI() (*Ui, error) { if termboxError != nil { return nil, termboxError } else { - uiCtx := &Ui{&UiStatusBar{"Hello", "World"}} + uiCtx := new(Ui) // &Ui{&UiStatusBar{"Hello", "World"}} + uiCtx.grid = NewGrid(&TestModel{}) + uiCtx.statusBar = &UiStatusBar{"Hello", "World"} return uiCtx, nil } } @@ -92,6 +95,7 @@ func (ui *Ui) redrawInternal(width, height int) { termbox.Clear(termbox.ColorDefault, termbox.ColorDefault) // TODO: This will eventually offload to UI "components" + ui.grid.Redraw(0, 0, width, height - 2) // Draws the status bar ui.statusBar.Redraw(0, height - 2, width, 2) @@ -109,7 +113,23 @@ func (ui *Ui) NextEvent() UiEvent { if event.Type == termbox.EventResize { ui.redrawInternal(event.Width, event.Height) } else { - return UiEvent{EventKeyPress, 0} + + // !!TEMP!! + if (event.Ch == 'i') { + ui.grid.ShiftBy(0, -1) + } else if (event.Ch == 'k') { + ui.grid.ShiftBy(0, 1) + } else if (event.Ch == 'j') { + ui.grid.ShiftBy(1, 0) + } else if (event.Ch == 'l') { + ui.grid.ShiftBy(-1, 0) + } else { + return UiEvent{EventKeyPress, 0} + } + // !!END TEMP!! + + ui.Redraw() + //return UiEvent{EventKeyPress, 0} } } }