2d50065a8299867854f3e24c6b565da7152c71fa — Trevor Slocum 18 days ago 0e4db5d
Migrate from tview to cview
M README.md => README.md +2 -5
@@ 28,10 28,7 @@ Windows and Linux binaries are available.
 ### Compile
 
 ```
-GO111MODULE=on
-go get -u git.sr.ht/~tslocum/netris
-go get -u git.sr.ht/~tslocum/netris/cmd/netris
-go get -u git.sr.ht/~tslocum/netris/cmd/netris-server
+GO111MODULE=on go get git.sr.ht/~tslocum/netris/...
 ```
 
 ## Configure


@@ 50,8 47,8 @@ Please share suggestions/issues [here](https://todo.sr.ht/~tslocum/netris).
 
 The following libraries are used to build netris:
 
+* [cview](https://git.sr.ht/~tslocum/cview) - User interface
 * [tcell](https://github.com/gdamore/tcell) - User interface
-* [tview](https://github.com/rivo/tview) - User interface
 * [ssh](https://github.com/gliderlabs/ssh) - SSH server
 * [pty](https://github.com/creack/pty) - Pseudo-terminal interface
 * [go-isatty](https://github.com/mattn/go-isatty) - Terminal detection

M cmd/netris/gui.go => cmd/netris/gui.go +19 -19
@@ 10,11 10,11 @@ import (
 	"sync"
 	"time"
 
+	"git.sr.ht/~tslocum/cview"
 	"git.sr.ht/~tslocum/netris/pkg/event"
 	"git.sr.ht/~tslocum/netris/pkg/game"
 	"git.sr.ht/~tslocum/netris/pkg/mino"
 	"github.com/gdamore/tcell"
-	"github.com/tslocum/tview"
 )
 
 var (


@@ 24,12 24,12 @@ var (
 	capturingKeybind bool
 	showDetails      bool
 
-	app       *tview.Application
-	inputView *tview.InputField
-	mtx       *tview.TextView
-	side      *tview.TextView
-	buffer    *tview.TextView
-	recent    *tview.TextView
+	app       *cview.Application
+	inputView *cview.InputField
+	mtx       *cview.TextView
+	side      *cview.TextView
+	buffer    *cview.TextView
+	recent    *cview.TextView
 
 	joinedGame bool
 


@@ 55,18 55,18 @@ var (
 
 	profileCPU *os.File
 
-	buttonGhostPiece       *tview.Button
-	buttonKeybindRotateCCW *tview.Button
-	buttonKeybindRotateCW  *tview.Button
-	buttonKeybindMoveLeft  *tview.Button
-	buttonKeybindMoveRight *tview.Button
-	buttonKeybindSoftDrop  *tview.Button
-	buttonKeybindHardDrop  *tview.Button
-	buttonKeybindCancel    *tview.Button
-	buttonKeybindSave      *tview.Button
-
-	buttonCancel *tview.Button
-	buttonStart  *tview.Button
+	buttonGhostPiece       *cview.Button
+	buttonKeybindRotateCCW *cview.Button
+	buttonKeybindRotateCW  *cview.Button
+	buttonKeybindMoveLeft  *cview.Button
+	buttonKeybindMoveRight *cview.Button
+	buttonKeybindSoftDrop  *cview.Button
+	buttonKeybindHardDrop  *cview.Button
+	buttonKeybindCancel    *cview.Button
+	buttonKeybindSave      *cview.Button
+
+	buttonCancel *cview.Button
+	buttonStart  *cview.Button
 )
 
 const DefaultStatusText = "Press Enter to chat, Z/X to rotate, arrow keys or HJKL to move/drop"

M cmd/netris/gui_init.go => cmd/netris/gui_init.go +97 -97
@@ 4,19 4,19 @@ import (
 	"log"
 	"unicode"
 
+	"git.sr.ht/~tslocum/cview"
 	"git.sr.ht/~tslocum/netris/pkg/event"
 	"git.sr.ht/~tslocum/netris/pkg/game"
 	"git.sr.ht/~tslocum/netris/pkg/mino"
 	"github.com/gdamore/tcell"
-	"github.com/tslocum/tview"
 )
 
-func initGUI(skipTitle bool) (*tview.Application, error) {
-	app = tview.NewApplication()
+func initGUI(skipTitle bool) (*cview.Application, error) {
+	app = cview.NewApplication()
 
 	app.SetAfterResizeFunc(handleResize)
 
-	inputView = tview.NewInputField().
+	inputView = cview.NewInputField().
 		SetText(DefaultStatusText).
 		SetLabel("> ").
 		SetFieldWidth(0).


@@ 31,38 31,38 @@ func initGUI(skipTitle bool) (*tview.Application, error) {
 		return event
 	})
 
-	gameGrid = tview.NewGrid().
+	gameGrid = cview.NewGrid().
 		SetBorders(false)
 
-	mtx = tview.NewTextView().
+	mtx = cview.NewTextView().
 		SetScrollable(false).
-		SetTextAlign(tview.AlignLeft).
+		SetTextAlign(cview.AlignLeft).
 		SetWrap(false).
 		SetWordWrap(false)
 
 	mtx.SetDynamicColors(true)
 
-	side = tview.NewTextView().
+	side = cview.NewTextView().
 		SetScrollable(false).
-		SetTextAlign(tview.AlignLeft).
+		SetTextAlign(cview.AlignLeft).
 		SetWrap(false).
 		SetWordWrap(false)
 
 	side.SetDynamicColors(true)
 
-	buffer = tview.NewTextView().
+	buffer = cview.NewTextView().
 		SetScrollable(false).
-		SetTextAlign(tview.AlignLeft).
+		SetTextAlign(cview.AlignLeft).
 		SetWrap(false).
 		SetWordWrap(false)
 
 	buffer.SetDynamicColors(true)
 
-	pad := tview.NewBox()
+	pad := cview.NewBox()
 
-	recent = tview.NewTextView().
+	recent = cview.NewTextView().
 		SetScrollable(true).
-		SetTextAlign(tview.AlignLeft).
+		SetTextAlign(cview.AlignLeft).
 		SetWrap(true).
 		SetWordWrap(true)
 


@@ 115,40 115,40 @@ func initGUI(skipTitle bool) (*tview.Application, error) {
 		}
 	}
 
-	titleName = tview.NewTextView().
+	titleName = cview.NewTextView().
 		SetScrollable(false).
-		SetTextAlign(tview.AlignLeft).
+		SetTextAlign(cview.AlignLeft).
 		SetWrap(false).
 		SetWordWrap(false).SetDynamicColors(true)
 
-	titleL = tview.NewTextView().
+	titleL = cview.NewTextView().
 		SetScrollable(false).
-		SetTextAlign(tview.AlignLeft).
+		SetTextAlign(cview.AlignLeft).
 		SetWrap(false).
 		SetWordWrap(false).SetDynamicColors(true)
 
-	titleR = tview.NewTextView().
+	titleR = cview.NewTextView().
 		SetScrollable(false).
-		SetTextAlign(tview.AlignLeft).
+		SetTextAlign(cview.AlignLeft).
 		SetWrap(false).
 		SetWordWrap(false).SetDynamicColors(true)
 
 	go handleTitle()
 
-	buttonA = tview.NewButton("A")
-	buttonLabelA = tview.NewTextView().SetTextAlign(tview.AlignCenter)
+	buttonA = cview.NewButton("A")
+	buttonLabelA = cview.NewTextView().SetTextAlign(cview.AlignCenter)
 
-	buttonB = tview.NewButton("B")
-	buttonLabelB = tview.NewTextView().SetTextAlign(tview.AlignCenter)
+	buttonB = cview.NewButton("B")
+	buttonLabelB = cview.NewTextView().SetTextAlign(cview.AlignCenter)
 
-	buttonC = tview.NewButton("C")
-	buttonLabelC = tview.NewTextView().SetTextAlign(tview.AlignCenter)
+	buttonC = cview.NewButton("C")
+	buttonLabelC = cview.NewTextView().SetTextAlign(cview.AlignCenter)
 
-	titleNameGrid := tview.NewGrid().SetRows(3, 2).
+	titleNameGrid := cview.NewGrid().SetRows(3, 2).
 		AddItem(titleName, 0, 0, 1, 1, 0, 0, false).
-		AddItem(tview.NewTextView().SetText(SubTitle+game.Version), 1, 0, 1, 1, 0, 0, false)
+		AddItem(cview.NewTextView().SetText(SubTitle+game.Version), 1, 0, 1, 1, 0, 0, false)
 
-	titleGrid = tview.NewGrid().
+	titleGrid = cview.NewGrid().
 		SetRows(5, 3, 3, 3, 3, 3, 3).
 		SetColumns(-1, 34, -1).
 		AddItem(titleL, 0, 0, 8, 1, 0, 0, false).


@@ 162,9 162,9 @@ func initGUI(skipTitle bool) (*tview.Application, error) {
 		AddItem(buttonLabelC, 6, 1, 1, 1, 0, 0, false).
 		AddItem(pad, 7, 1, 1, 1, 0, 0, false)
 
-	gameListView = tview.NewTextView().SetDynamicColors(true)
+	gameListView = cview.NewTextView().SetDynamicColors(true)
 
-	gameListButtonsGrid := tview.NewGrid().
+	gameListButtonsGrid := cview.NewGrid().
 		SetColumns(-1, 1, -1, 1, -1).
 		AddItem(buttonA, 0, 0, 1, 1, 0, 0, false).
 		AddItem(pad, 0, 1, 1, 1, 0, 0, false).


@@ 172,9 172,9 @@ func initGUI(skipTitle bool) (*tview.Application, error) {
 		AddItem(pad, 0, 3, 1, 1, 0, 0, false).
 		AddItem(buttonC, 0, 4, 1, 1, 0, 0, false)
 
-	gameListHeader = tview.NewTextView().SetTextAlign(tview.AlignCenter)
+	gameListHeader = cview.NewTextView().SetTextAlign(cview.AlignCenter)
 
-	gameListGrid = tview.NewGrid().
+	gameListGrid = cview.NewGrid().
 		SetRows(5, 1, 14, 1, 3).
 		SetColumns(-1, 34, -1).
 		AddItem(titleL, 0, 0, 5, 1, 0, 0, false).


@@ 183,15 183,15 @@ func initGUI(skipTitle bool) (*tview.Application, error) {
 		AddItem(gameListHeader, 1, 1, 1, 1, 0, 0, true).
 		AddItem(gameListView, 2, 1, 1, 1, 0, 0, true).
 		AddItem(gameListButtonsGrid, 3, 1, 1, 1, 0, 0, true).
-		AddItem(tview.NewTextView().
-			SetTextAlign(tview.AlignCenter).
+		AddItem(cview.NewTextView().
+			SetTextAlign(cview.AlignCenter).
 			SetWrap(false).
 			SetWordWrap(false).SetText("\nRefresh: R\nPrevious: Shift+Tab - Next: Tab"), 4, 1, 1, 1, 0, 0, true)
 
-	buttonCancel = tview.NewButton("Cancel")
-	buttonStart = tview.NewButton("Start")
+	buttonCancel = cview.NewButton("Cancel")
+	buttonStart = cview.NewButton("Start")
 
-	newGameSubmitGrid := tview.NewGrid().
+	newGameSubmitGrid := cview.NewGrid().
 		SetColumns(-1, 10, 1, 10, -1).
 		AddItem(pad, 0, 0, 1, 1, 0, 0, false).
 		AddItem(buttonCancel, 0, 1, 1, 1, 0, 0, false).


@@ 199,34 199,34 @@ func initGUI(skipTitle bool) (*tview.Application, error) {
 		AddItem(buttonStart, 0, 3, 1, 1, 0, 0, false).
 		AddItem(pad, 0, 4, 1, 1, 0, 0, false)
 
-	newGameNameInput = tview.NewInputField().SetText("netris")
-	newGameMaxPlayersInput = tview.NewInputField().SetFieldWidth(3).SetAcceptanceFunc(func(textToCheck string, lastChar rune) bool {
+	newGameNameInput = cview.NewInputField().SetText("netris")
+	newGameMaxPlayersInput = cview.NewInputField().SetFieldWidth(3).SetAcceptanceFunc(func(textToCheck string, lastChar rune) bool {
 		return unicode.IsDigit(lastChar) && len(textToCheck) <= 3
 	})
-	newGameSpeedLimitInput = tview.NewInputField().SetFieldWidth(3).SetAcceptanceFunc(func(textToCheck string, lastChar rune) bool {
+	newGameSpeedLimitInput = cview.NewInputField().SetFieldWidth(3).SetAcceptanceFunc(func(textToCheck string, lastChar rune) bool {
 		return unicode.IsDigit(lastChar) && len(textToCheck) <= 3
 	})
 
 	resetNewGameInputs()
 
-	newGameNameGrid := tview.NewGrid().
-		AddItem(tview.NewTextView().SetText("Name"), 0, 0, 1, 1, 0, 0, false).
+	newGameNameGrid := cview.NewGrid().
+		AddItem(cview.NewTextView().SetText("Name"), 0, 0, 1, 1, 0, 0, false).
 		AddItem(newGameNameInput, 0, 1, 1, 1, 0, 0, false)
 
-	newGameMaxPlayersGrid := tview.NewGrid().
-		AddItem(tview.NewTextView().SetText("Player Limit"), 0, 0, 1, 1, 0, 0, false).
+	newGameMaxPlayersGrid := cview.NewGrid().
+		AddItem(cview.NewTextView().SetText("Player Limit"), 0, 0, 1, 1, 0, 0, false).
 		AddItem(newGameMaxPlayersInput, 0, 1, 1, 1, 0, 0, false)
 
-	newGameSpeedLimitGrid := tview.NewGrid().
-		AddItem(tview.NewTextView().SetText("Speed Limit"), 0, 0, 1, 1, 0, 0, false).
+	newGameSpeedLimitGrid := cview.NewGrid().
+		AddItem(cview.NewTextView().SetText("Speed Limit"), 0, 0, 1, 1, 0, 0, false).
 		AddItem(newGameSpeedLimitInput, 0, 1, 1, 1, 0, 0, false)
 
-	newGameHeader := tview.NewTextView().
-		SetTextAlign(tview.AlignCenter).
+	newGameHeader := cview.NewTextView().
+		SetTextAlign(cview.AlignCenter).
 		SetWrap(false).
 		SetWordWrap(false).SetText("New Game")
 
-	newGameGrid = tview.NewGrid().
+	newGameGrid = cview.NewGrid().
 		SetRows(5, 2, 1, 1, 1, 1, 1, 1, 1, -1, 3).
 		SetColumns(-1, 34, -1).
 		AddItem(titleL, 0, 0, 11, 1, 0, 0, false).


@@ 241,19 241,19 @@ func initGUI(skipTitle bool) (*tview.Application, error) {
 		AddItem(pad, 7, 1, 1, 1, 0, 0, false).
 		AddItem(newGameSubmitGrid, 8, 1, 1, 1, 0, 0, false).
 		AddItem(pad, 9, 1, 1, 1, 0, 0, false).
-		AddItem(tview.NewTextView().
-			SetTextAlign(tview.AlignCenter).
+		AddItem(cview.NewTextView().
+			SetTextAlign(cview.AlignCenter).
 			SetWrap(false).
 			SetWordWrap(false).SetText("\nLimits set to zero are disabled\nPrevious: Shift+Tab - Next: Tab"), 10, 1, 1, 1, 0, 0, false)
 
-	playerSettingsTitle := tview.NewTextView().
-		SetTextAlign(tview.AlignCenter).
+	playerSettingsTitle := cview.NewTextView().
+		SetTextAlign(cview.AlignCenter).
 		SetWrap(false).
 		SetWordWrap(false).SetText("Player Settings")
 
-	playerSettingsForm = tview.NewForm().SetButtonsAlign(tview.AlignCenter)
+	playerSettingsForm = cview.NewForm().SetButtonsAlign(cview.AlignCenter)
 
-	playerSettingsGrid = tview.NewGrid().
+	playerSettingsGrid = cview.NewGrid().
 		SetRows(5, 2, -1, 1).
 		SetColumns(-1, 34, -1).
 		AddItem(titleL, 0, 0, 4, 1, 0, 0, false).


@@ 261,56 261,56 @@ func initGUI(skipTitle bool) (*tview.Application, error) {
 		AddItem(titleR, 0, 2, 4, 1, 0, 0, false).
 		AddItem(playerSettingsTitle, 1, 1, 1, 1, 0, 0, true).
 		AddItem(playerSettingsForm, 2, 1, 1, 1, 0, 0, true).
-		AddItem(tview.NewTextView().
-			SetTextAlign(tview.AlignCenter).
+		AddItem(cview.NewTextView().
+			SetTextAlign(cview.AlignCenter).
 			SetWrap(false).
 			SetWordWrap(false).SetText("Previous: Shift+Tab - Next: Tab"), 3, 1, 1, 1, 0, 0, true)
 
-	gameSettingsTitle := tview.NewTextView().
-		SetTextAlign(tview.AlignCenter).
+	gameSettingsTitle := cview.NewTextView().
+		SetTextAlign(cview.AlignCenter).
 		SetWrap(false).
 		SetWordWrap(false).SetText("Game Settings")
 
-	buttonGhostPiece = tview.NewButton("Enabled")
+	buttonGhostPiece = cview.NewButton("Enabled")
 
-	ghostPieceGrid := tview.NewGrid().SetColumns(19, -1).
-		AddItem(tview.NewTextView().SetText("Ghost Piece"), 0, 0, 1, 1, 0, 0, false).
+	ghostPieceGrid := cview.NewGrid().SetColumns(19, -1).
+		AddItem(cview.NewTextView().SetText("Ghost Piece"), 0, 0, 1, 1, 0, 0, false).
 		AddItem(buttonGhostPiece, 0, 1, 1, 1, 0, 0, false)
 
-	buttonKeybindRotateCCW = tview.NewButton("Set")
-	buttonKeybindRotateCW = tview.NewButton("Set")
-	buttonKeybindMoveLeft = tview.NewButton("Set")
-	buttonKeybindMoveRight = tview.NewButton("Set")
-	buttonKeybindSoftDrop = tview.NewButton("Set")
-	buttonKeybindHardDrop = tview.NewButton("Set")
-	buttonKeybindCancel = tview.NewButton("Cancel")
-	buttonKeybindSave = tview.NewButton("Save")
-
-	rotateCCWGrid := tview.NewGrid().SetColumns(27, -1).
-		AddItem(tview.NewTextView().SetText("Rotate CCW"), 0, 0, 1, 1, 0, 0, false).
+	buttonKeybindRotateCCW = cview.NewButton("Set")
+	buttonKeybindRotateCW = cview.NewButton("Set")
+	buttonKeybindMoveLeft = cview.NewButton("Set")
+	buttonKeybindMoveRight = cview.NewButton("Set")
+	buttonKeybindSoftDrop = cview.NewButton("Set")
+	buttonKeybindHardDrop = cview.NewButton("Set")
+	buttonKeybindCancel = cview.NewButton("Cancel")
+	buttonKeybindSave = cview.NewButton("Save")
+
+	rotateCCWGrid := cview.NewGrid().SetColumns(27, -1).
+		AddItem(cview.NewTextView().SetText("Rotate CCW"), 0, 0, 1, 1, 0, 0, false).
 		AddItem(buttonKeybindRotateCCW, 0, 1, 1, 1, 0, 0, false)
 
-	rotateCWGrid := tview.NewGrid().SetColumns(27, -1).
-		AddItem(tview.NewTextView().SetText("Rotate CW"), 0, 0, 1, 1, 0, 0, false).
+	rotateCWGrid := cview.NewGrid().SetColumns(27, -1).
+		AddItem(cview.NewTextView().SetText("Rotate CW"), 0, 0, 1, 1, 0, 0, false).
 		AddItem(buttonKeybindRotateCW, 0, 1, 1, 1, 0, 0, false)
 
-	moveLeftGrid := tview.NewGrid().SetColumns(27, -1).
-		AddItem(tview.NewTextView().SetText("Move Left"), 0, 0, 1, 1, 0, 0, false).
+	moveLeftGrid := cview.NewGrid().SetColumns(27, -1).
+		AddItem(cview.NewTextView().SetText("Move Left"), 0, 0, 1, 1, 0, 0, false).
 		AddItem(buttonKeybindMoveLeft, 0, 1, 1, 1, 0, 0, false)
 
-	moveRightGrid := tview.NewGrid().SetColumns(27, -1).
-		AddItem(tview.NewTextView().SetText("Move Right"), 0, 0, 1, 1, 0, 0, false).
+	moveRightGrid := cview.NewGrid().SetColumns(27, -1).
+		AddItem(cview.NewTextView().SetText("Move Right"), 0, 0, 1, 1, 0, 0, false).
 		AddItem(buttonKeybindMoveRight, 0, 1, 1, 1, 0, 0, false)
 
-	softDropGrid := tview.NewGrid().SetColumns(27, -1).
-		AddItem(tview.NewTextView().SetText("Soft Drop"), 0, 0, 1, 1, 0, 0, false).
+	softDropGrid := cview.NewGrid().SetColumns(27, -1).
+		AddItem(cview.NewTextView().SetText("Soft Drop"), 0, 0, 1, 1, 0, 0, false).
 		AddItem(buttonKeybindSoftDrop, 0, 1, 1, 1, 0, 0, false)
 
-	hardDropGrid := tview.NewGrid().SetColumns(27, -1).
-		AddItem(tview.NewTextView().SetText("Hard Drop"), 0, 0, 1, 1, 0, 0, false).
+	hardDropGrid := cview.NewGrid().SetColumns(27, -1).
+		AddItem(cview.NewTextView().SetText("Hard Drop"), 0, 0, 1, 1, 0, 0, false).
 		AddItem(buttonKeybindHardDrop, 0, 1, 1, 1, 0, 0, false)
 
-	gameSettingsSubmitGrid := tview.NewGrid().
+	gameSettingsSubmitGrid := cview.NewGrid().
 		SetColumns(-1, 10, 1, 10, -1).
 		AddItem(pad, 0, 0, 1, 1, 0, 0, false).
 		AddItem(buttonKeybindCancel, 0, 1, 1, 1, 0, 0, false).


@@ 318,7 318,7 @@ func initGUI(skipTitle bool) (*tview.Application, error) {
 		AddItem(buttonKeybindSave, 0, 3, 1, 1, 0, 0, false).
 		AddItem(pad, 0, 4, 1, 1, 0, 0, false)
 
-	gameSettingsGrid = tview.NewGrid().
+	gameSettingsGrid = cview.NewGrid().
 		SetRows(5, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1).
 		SetColumns(-1, 34, -1).
 		AddItem(titleL, 0, 0, 18, 1, 0, 0, false).


@@ 326,15 326,15 @@ func initGUI(skipTitle bool) (*tview.Application, error) {
 		AddItem(titleR, 0, 2, 18, 1, 0, 0, false).
 		AddItem(gameSettingsTitle, 1, 1, 1, 1, 0, 0, false).
 		AddItem(pad, 2, 1, 1, 1, 0, 0, false).
-		AddItem(tview.NewTextView().
-			SetTextAlign(tview.AlignCenter).
+		AddItem(cview.NewTextView().
+			SetTextAlign(cview.AlignCenter).
 			SetWrap(false).
 			SetWordWrap(false).SetText("Options"), 3, 1, 1, 1, 0, 0, false).
 		AddItem(ghostPieceGrid, 4, 1, 1, 1, 0, 0, false).
 		AddItem(ghostPieceGrid, 5, 1, 1, 1, 0, 0, false).
 		AddItem(pad, 6, 1, 1, 1, 0, 0, false).
-		AddItem(tview.NewTextView().
-			SetTextAlign(tview.AlignCenter).
+		AddItem(cview.NewTextView().
+			SetTextAlign(cview.AlignCenter).
 			SetWrap(false).
 			SetWordWrap(false).SetText("Keybindings"), 7, 1, 1, 1, 0, 0, false).
 		AddItem(pad, 8, 1, 1, 1, 0, 0, false).


@@ 346,40 346,40 @@ func initGUI(skipTitle bool) (*tview.Application, error) {
 		AddItem(hardDropGrid, 14, 1, 1, 1, 0, 0, false).
 		AddItem(pad, 15, 1, 1, 1, 0, 0, false).
 		AddItem(gameSettingsSubmitGrid, 16, 1, 1, 1, 0, 0, false).
-		AddItem(tview.NewTextView().
-			SetTextAlign(tview.AlignCenter).
+		AddItem(cview.NewTextView().
+			SetTextAlign(cview.AlignCenter).
 			SetWrap(false).
 			SetWordWrap(false).SetText("\nPrevious: Shift+Tab - Next: Tab"), 17, 1, 1, 1, 0, 0, false)
 
-	titleContainerGrid = tview.NewGrid().SetColumns(-1, 80, -1).SetRows(-1, 24, -1).
+	titleContainerGrid = cview.NewGrid().SetColumns(-1, 80, -1).SetRows(-1, 24, -1).
 		AddItem(pad, 0, 0, 1, 3, 0, 0, false).
 		AddItem(pad, 1, 0, 1, 1, 0, 0, false).
 		AddItem(titleGrid, 1, 1, 1, 1, 0, 0, true).
 		AddItem(pad, 1, 2, 1, 1, 0, 0, false).
 		AddItem(pad, 0, 0, 1, 3, 0, 0, false)
 
-	gameListContainerGrid = tview.NewGrid().SetColumns(-1, 80, -1).SetRows(-1, 24, -1).
+	gameListContainerGrid = cview.NewGrid().SetColumns(-1, 80, -1).SetRows(-1, 24, -1).
 		AddItem(pad, 0, 0, 1, 3, 0, 0, false).
 		AddItem(pad, 1, 0, 1, 1, 0, 0, false).
 		AddItem(gameListGrid, 1, 1, 1, 1, 0, 0, true).
 		AddItem(pad, 1, 2, 1, 1, 0, 0, false).
 		AddItem(pad, 0, 0, 1, 3, 0, 0, false)
 
-	newGameContainerGrid = tview.NewGrid().SetColumns(-1, 80, -1).SetRows(-1, 24, -1).
+	newGameContainerGrid = cview.NewGrid().SetColumns(-1, 80, -1).SetRows(-1, 24, -1).
 		AddItem(pad, 0, 0, 1, 3, 0, 0, false).
 		AddItem(pad, 1, 0, 1, 1, 0, 0, false).
 		AddItem(newGameGrid, 1, 1, 1, 1, 0, 0, false).
 		AddItem(pad, 1, 2, 1, 1, 0, 0, false).
 		AddItem(pad, 0, 0, 1, 3, 0, 0, false)
 
-	playerSettingsContainerGrid = tview.NewGrid().SetColumns(-1, 80, -1).SetRows(-1, 24, -1).
+	playerSettingsContainerGrid = cview.NewGrid().SetColumns(-1, 80, -1).SetRows(-1, 24, -1).
 		AddItem(pad, 0, 0, 1, 3, 0, 0, false).
 		AddItem(pad, 1, 0, 1, 1, 0, 0, false).
 		AddItem(playerSettingsGrid, 1, 1, 1, 1, 0, 0, true).
 		AddItem(pad, 1, 2, 1, 1, 0, 0, false).
 		AddItem(pad, 0, 0, 1, 3, 0, 0, false)
 
-	gameSettingsContainerGrid = tview.NewGrid().SetColumns(-1, 80, -1).SetRows(-1, 24, -1).
+	gameSettingsContainerGrid = cview.NewGrid().SetColumns(-1, 80, -1).SetRows(-1, 24, -1).
 		AddItem(pad, 0, 0, 1, 3, 0, 0, false).
 		AddItem(pad, 1, 0, 1, 1, 0, 0, false).
 		AddItem(gameSettingsGrid, 1, 1, 1, 1, 0, 0, false).

M cmd/netris/gui_input.go => cmd/netris/gui_input.go +3 -3
@@ 7,10 7,10 @@ import (
 	"runtime/pprof"
 	"strings"
 
+	"git.sr.ht/~tslocum/cview"
 	"git.sr.ht/~tslocum/netris/pkg/event"
 	"git.sr.ht/~tslocum/netris/pkg/game"
 	"github.com/gdamore/tcell"
-	"github.com/tslocum/tview"
 )
 
 type Keybinding struct {


@@ 173,7 173,7 @@ func handleKeypress(ev *tcell.EventKey) *tcell.EventKey {
 						return nil
 					}
 
-					modal := tview.NewModal().SetText("Press desired key(s) to set keybinding or press Escape to cancel.").ClearButtons()
+					modal := cview.NewModal().SetText("Press desired key(s) to set keybinding or press Escape to cancel.").ClearButtons()
 					app.SetRoot(modal, true)
 
 					capturingKeybind = true


@@ 223,7 223,7 @@ func handleKeypress(ev *tcell.EventKey) *tcell.EventKey {
 						titleScreen = 5
 						titleSelectedButton = 0
 
-						modal := tview.NewModal().SetText("Joining another server by IP via GUI is not yet implemented.\nPlease re-launch netris with the --connect argument instead.\n\nPress Escape to return.").ClearButtons()
+						modal := cview.NewModal().SetText("Joining another server by IP via GUI is not yet implemented.\nPlease re-launch netris with the --connect argument instead.\n\nPress Escape to return.").ClearButtons()
 						app.SetRoot(modal, true)
 					} else if titleSelectedButton == 3 {
 						titleScreen = 0

M cmd/netris/gui_title.go => cmd/netris/gui_title.go +27 -27
@@ 6,9 6,9 @@ import (
 	"strconv"
 	"time"
 
+	"git.sr.ht/~tslocum/cview"
 	"git.sr.ht/~tslocum/netris/pkg/game"
 	"git.sr.ht/~tslocum/netris/pkg/mino"
-	"github.com/tslocum/tview"
 )
 
 const (


@@ 22,34 22,34 @@ var (
 	gameSettingsSelectedButton int
 	drawTitle                  = make(chan struct{}, game.CommandQueueSize)
 
-	titleGrid          *tview.Grid
-	titleContainerGrid *tview.Grid
+	titleGrid          *cview.Grid
+	titleContainerGrid *cview.Grid
 
 	gameListSelected int
 
-	newGameGrid            *tview.Grid
-	newGameNameInput       *tview.InputField
-	newGameMaxPlayersInput *tview.InputField
-	newGameSpeedLimitInput *tview.InputField
+	newGameGrid            *cview.Grid
+	newGameNameInput       *cview.InputField
+	newGameMaxPlayersInput *cview.InputField
+	newGameSpeedLimitInput *cview.InputField
 
-	playerSettingsForm          *tview.Form
-	playerSettingsGrid          *tview.Grid
-	playerSettingsContainerGrid *tview.Grid
+	playerSettingsForm          *cview.Form
+	playerSettingsGrid          *cview.Grid
+	playerSettingsContainerGrid *cview.Grid
 
 	gameList              []*game.ListedGame
-	gameListHeader        *tview.TextView
-	gameListView          *tview.TextView
-	gameListGrid          *tview.Grid
-	gameListContainerGrid *tview.Grid
-	newGameContainerGrid  *tview.Grid
+	gameListHeader        *cview.TextView
+	gameListView          *cview.TextView
+	gameListGrid          *cview.Grid
+	gameListContainerGrid *cview.Grid
+	newGameContainerGrid  *cview.Grid
 
-	gameSettingsGrid          *tview.Grid
-	gameSettingsContainerGrid *tview.Grid
-	gameGrid                  *tview.Grid
+	gameSettingsGrid          *cview.Grid
+	gameSettingsContainerGrid *cview.Grid
+	gameGrid                  *cview.Grid
 
-	titleName *tview.TextView
-	titleL    *tview.TextView
-	titleR    *tview.TextView
+	titleName *cview.TextView
+	titleL    *cview.TextView
+	titleR    *cview.TextView
 
 	titleMatrixL = newTitleMatrixSide()
 	titleMatrix  = newTitleMatrixName()


@@ 57,13 57,13 @@ var (
 	titlePiecesL []*mino.Piece
 	titlePiecesR []*mino.Piece
 
-	buttonA *tview.Button
-	buttonB *tview.Button
-	buttonC *tview.Button
+	buttonA *cview.Button
+	buttonB *cview.Button
+	buttonC *cview.Button
 
-	buttonLabelA *tview.TextView
-	buttonLabelB *tview.TextView
-	buttonLabelC *tview.TextView
+	buttonLabelA *cview.TextView
+	buttonLabelB *cview.TextView
+	buttonLabelC *cview.TextView
 )
 
 func previousTitleButton() {

M go.mod => go.mod +6 -5
@@ 3,14 3,15 @@ module git.sr.ht/~tslocum/netris
 go 1.13
 
 require (
+	git.sr.ht/~tslocum/cview v0.2.1-0.20191231040847-3c1736c9c849
 	github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 // indirect
 	github.com/creack/pty v1.1.9
 	github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
 	github.com/gdamore/tcell v1.3.0
 	github.com/gliderlabs/ssh v0.2.2
-	github.com/mattn/go-isatty v0.0.10
-	github.com/mattn/go-runewidth v0.0.6 // indirect
-	github.com/tslocum/tview v0.0.0-20191018041445-09b275a4b660
-	golang.org/x/crypto v0.0.0-20191107222254-f4817d981bb6
-	golang.org/x/sys v0.0.0-20191105231009-c1f44814a5cd // indirect
+	github.com/lucasb-eyer/go-colorful v1.0.3 // indirect
+	github.com/mattn/go-isatty v0.0.11
+	github.com/mattn/go-runewidth v0.0.7 // indirect
+	golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876
+	golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8 // indirect
 )

M go.sum => go.sum +14 -13
@@ 1,3 1,5 @@
+git.sr.ht/~tslocum/cview v0.2.1-0.20191231040847-3c1736c9c849 h1:t18Zd/LG2cxJwAQVSsncquNjAgcn4RpRkCq7EV++Ba8=
+git.sr.ht/~tslocum/cview v0.2.1-0.20191231040847-3c1736c9c849/go.mod h1:92oD1V0TlLtAeJUruA+p5/P6sVL0p8TMMosPwqz0/gM=
 github.com/DATA-DOG/go-sqlmock v1.3.3 h1:CWUqKXe0s8A2z6qCgkP4Kru7wC11YoAnoupUKFDnH08=
 github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
 github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=


@@ 14,29 16,28 @@ github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
 github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
 github.com/lucasb-eyer/go-colorful v1.0.2 h1:mCMFu6PgSozg9tDNMMK3g18oJBX7oYGrC09mS6CXfO4=
 github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s=
-github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
-github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
+github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
+github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
+github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
+github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
 github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
 github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
-github.com/mattn/go-runewidth v0.0.6 h1:V2iyH+aX9C5fsYCpK60U8BYIvmhqxuOL3JZcqc1NB7k=
-github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
+github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
+github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
 github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
 github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
-github.com/tslocum/tview v0.0.0-20191018041445-09b275a4b660 h1:f/g7DFokEN2PRyGN4vIadgCDTTOoBoxiIxf4q1Re9PI=
-github.com/tslocum/tview v0.0.0-20191018041445-09b275a4b660/go.mod h1:vUK8oe0CfwPjo5JouPDJq6g9+KsPiASsJP4yndpkzTg=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20191107222254-f4817d981bb6 h1:VsmCukA2gDdC3Mu6evOIT0QjLSQWiJIwzv1Bdj4jdzU=
-golang.org/x/crypto v0.0.0-20191107222254-f4817d981bb6/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876 h1:sKJQZMuxjOAR/Uo2LBfU90onWEf1dF4C+0hPJCc9Mpc=
+golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY=
-golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191105231009-c1f44814a5cd h1:3x5uuvBgE6oaXJjCOvpCC1IpgJogqQ+PqGGU3ZxAgII=
-golang.org/x/sys v0.0.0-20191105231009-c1f44814a5cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191018095205-727590c5006e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8 h1:JA8d3MPx/IToSyXZG/RhwYEtfrKO1Fxrqe8KrkiLXKM=
+golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=

M pkg/game/game.go => pkg/game/game.go +0 -13
@@ 68,19 68,6 @@ type Game struct {
 	*sync.Mutex
 }
 
-/*
-func (g *Game) Lock() {
-	g.Log("LOCKING ", string(debug.Stack()))
-	g.Mutex.Lock()
-	g.Log("LOCKED ", string(debug.Stack()))
-}
-
-func (g *Game) Unlock() {
-	g.Log("UNLOCKED ", string(debug.Stack()))
-	g.Mutex.Unlock()
-}
-*/
-
 func NewGame(rank int, out func(GameCommandInterface), logger chan string, draw chan event.DrawObject) (*Game, error) {
 	minos, err := mino.Generate(rank)
 	if err != nil {

M pkg/mino/generate.go => pkg/mino/generate.go +13 -7
@@ 4,7 4,7 @@ import (
 	"errors"
 )
 
-// Generate
+// Generate procedurally generates minos of a supplied rank.
 func Generate(rank int) ([]Mino, error) {
 	switch {
 	case rank < 0:


@@ 19,14 19,20 @@ func Generate(rank int) ([]Mino, error) {
 			return nil, err
 		}
 
-		var minos []Mino
-		found := make(map[string]bool)
+		var (
+			minos []Mino
+			s     string
+			found = make(map[string]bool)
+		)
 		for _, mino := range r {
-			for _, newMino := range mino.newMinos() {
-				if s := newMino.Canonical().String(); !found[s] {
-					minos = append(minos, newMino.Canonical())
-					found[s] = true
+			for _, newMino := range mino.NewMinos() {
+				s = newMino.Canonical().String()
+				if found[s] {
+					continue
 				}
+
+				minos = append(minos, newMino.Canonical())
+				found[s] = true
 			}
 		}
 

M pkg/mino/matrix.go => pkg/mino/matrix.go +0 -13
@@ 85,19 85,6 @@ func NewMatrix(w int, h int, b int, players int, event chan<- interface{}, draw 
 	return &m
 }
 
-/*
-func (m *Matrix) Lock() {
-	log.Println("LOCKING ", string(debug.Stack()))
-	m.Mutex.Lock()
-	log.Println("LOCKED ", string(debug.Stack()))
-}
-
-func (m *Matrix) Unlock() {
-	log.Println("UNLOCKED ", string(debug.Stack()))
-	m.Mutex.Unlock()
-}
-*/
-
 func (m *Matrix) HandleReceiveGarbage() {
 	t := time.NewTicker(500 * time.Millisecond)
 	for {

M pkg/mino/mino.go => pkg/mino/mino.go +11 -7
@@ 261,22 261,26 @@ func (m Mino) Flatten() Mino {
 	return newMino
 }
 
-func (m Mino) newPoints() Mino {
-	var newMino Mino
+// NewPoints calculates the neighborhood of each point of a mino and returns only new points.
+func (m Mino) NewPoints() []Point {
+	var newPoints []Point
 
 	for _, p := range m {
 		for _, np := range p.Neighborhood() {
-			if !m.HasPoint(np) {
-				newMino = append(newMino, np)
+			if m.HasPoint(np) {
+				continue
 			}
+
+			newPoints = append(newPoints, np)
 		}
 	}
 
-	return newMino
+	return newPoints
 }
 
-func (m Mino) newMinos() []Mino {
-	points := m.newPoints()
+// NewMinos returns a new mino for every new neighborhood point of a mino.
+func (m Mino) NewMinos() []Mino {
+	points := m.NewPoints()
 
 	minos := make([]Mino, len(points))
 	for i, p := range points {

M pkg/mino/piece.go => pkg/mino/piece.go +22 -26
@@ 123,16 123,6 @@ func NewPiece(m Mino, loc Point) *Piece {
 	return p
 }
 
-/*
-func (p *Piece) MarshalJSON() ([]byte, error) {
-	log.Println("LOCK PIECE")
-	p.Lock()
-	defer p.Unlock()
-	defer log.Println("UNLOCKED PIECE")
-
-	return json.Marshal(LockedPiece(p))
-}*/
-
 // Rotate returns the new mino of a piece when a rotation is applied
 func (p *Piece) Rotate(rotations int, direction int) Mino {
 	p.Lock()


@@ 145,14 135,24 @@ func (p *Piece) Rotate(rotations int, direction int) Mino {
 	newMino := make(Mino, len(p.Mino))
 	copy(newMino, p.Mino.Origin())
 
-	var rotationPivot int
+	var rotationMatrix Point
+	if direction == 0 {
+		rotationMatrix = Point{1, -1}
+	} else {
+		rotationMatrix = Point{-1, 1}
+	}
+
+	var (
+		rotationPivot = p.Rotation
+		pivotPoint    Point
+		x, y          int
+	)
 	for j := 0; j < rotations; j++ {
 		if direction == 0 {
-			rotationPivot = p.Rotation + j
+			rotationPivot += j
 		} else {
-			rotationPivot = p.Rotation - j
+			rotationPivot -= j
 		}
-
 		if rotationPivot < 0 {
 			rotationPivot += RotationStates
 		}


@@ 160,21 160,17 @@ func (p *Piece) Rotate(rotations int, direction int) Mino {
 		if (rotationPivot == 3 && direction == 0) || (rotationPivot == 1 && direction == 1) {
 			newMino = p.original
 		} else {
-			pp := p.pivotsCW[rotationPivot%RotationStates]
-			if direction == 1 {
-				pp = p.pivotsCCW[rotationPivot%RotationStates]
+			if direction == 0 {
+				pivotPoint = p.pivotsCW[rotationPivot%RotationStates]
+			} else {
+				pivotPoint = p.pivotsCCW[rotationPivot%RotationStates]
 			}
-			px, py := pp.X, pp.Y
 
 			for i := 0; i < len(newMino); i++ {
-				x := newMino[i].X
-				y := newMino[i].Y
-
-				if direction == 0 {
-					newMino[i] = Point{(0 * (x - px)) + (1 * (y - py)), (-1 * (x - px)) + (0 * (y - py))}
-				} else {
-					newMino[i] = Point{(0 * (x - px)) + (-1 * (y - py)), (1 * (x - px)) + (0 * (y - py))}
-				}
+				x = newMino[i].X - pivotPoint.X
+				y = newMino[i].Y - pivotPoint.Y
+
+				newMino[i] = Point{y * rotationMatrix.X, x * rotationMatrix.Y}
 			}
 		}
 	}

M pkg/mino/point.go => pkg/mino/point.go +2 -2
@@ 26,8 26,8 @@ func (p Point) String() string {
 }
 
 // Neighborhood returns the Von Neumann neighborhood of a point
-func (p Point) Neighborhood() Mino {
-	return Mino{
+func (p Point) Neighborhood() []Point {
+	return []Point{
 		{p.X - 1, p.Y},
 		{p.X, p.Y - 1},
 		{p.X + 1, p.Y},