Implement Developer Console with Initial Commands
Implements the dev console in-game with various commands to start out with. Press the Enter key to show or hide the console. Commands supported: new Start a new map in Edit Mode. save [filename.json] Save the current map to disk. Filename is required unless you have saved recently. edit filename.json Open a map from disk in Edit Mode. play filename.json Play a map from disk in Play Mode.
This commit is contained in:
parent
30be42c343
commit
9356502a50
17
balance/shell.go
Normal file
17
balance/shell.go
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
package balance
|
||||||
|
|
||||||
|
import "git.kirsle.net/apps/doodle/render"
|
||||||
|
|
||||||
|
// Shell related variables.
|
||||||
|
var (
|
||||||
|
// TODO: why not renders transparent
|
||||||
|
ShellBackgroundColor = render.Color{0, 10, 20, 128}
|
||||||
|
ShellForegroundColor = render.White
|
||||||
|
ShellPadding int32 = 8
|
||||||
|
ShellFontSize = 14
|
||||||
|
ShellCursorBlinkRate uint64 = 20
|
||||||
|
ShellHistoryLineCount = 8
|
||||||
|
|
||||||
|
// Ticks that a flashed message persists for.
|
||||||
|
FlashTTL uint64 = 200
|
||||||
|
)
|
102
commands.go
Normal file
102
commands.go
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
package doodle
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Command is a parsed shell command.
|
||||||
|
type Command struct {
|
||||||
|
Raw string // The complete raw command the user typed.
|
||||||
|
Command string // The first word of their command.
|
||||||
|
Args []string // The shell-args array of parameters.
|
||||||
|
ArgsLiteral string // The args portion of the command literally.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the command.
|
||||||
|
func (c Command) Run(d *Doodle) error {
|
||||||
|
if len(c.Raw) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch c.Command {
|
||||||
|
case "new":
|
||||||
|
return c.New(d)
|
||||||
|
case "save":
|
||||||
|
return c.Save(d)
|
||||||
|
case "edit":
|
||||||
|
return c.Edit(d)
|
||||||
|
case "play":
|
||||||
|
return c.Play(d)
|
||||||
|
case "exit":
|
||||||
|
case "quit":
|
||||||
|
return c.Quit()
|
||||||
|
default:
|
||||||
|
return c.Default()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// New opens a new map in the editor mode.
|
||||||
|
func (c Command) New(d *Doodle) error {
|
||||||
|
d.shell.Write("Starting a new map")
|
||||||
|
d.NewMap()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the current map to disk.
|
||||||
|
func (c Command) Save(d *Doodle) error {
|
||||||
|
if scene, ok := d.scene.(*EditorScene); ok {
|
||||||
|
filename := ""
|
||||||
|
if len(c.Args) > 0 {
|
||||||
|
filename = c.Args[0]
|
||||||
|
} else if scene.filename != "" {
|
||||||
|
filename = scene.filename
|
||||||
|
} else {
|
||||||
|
return errors.New("usage: save <filename.json>")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.shell.Write("Saving to file: " + filename)
|
||||||
|
scene.SaveLevel(filename)
|
||||||
|
} else {
|
||||||
|
return errors.New("save: only available in Edit Mode")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Edit a map from disk.
|
||||||
|
func (c Command) Edit(d *Doodle) error {
|
||||||
|
if len(c.Args) == 0 {
|
||||||
|
return errors.New("Usage: edit <file name>")
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := c.Args[0]
|
||||||
|
d.shell.Write("Editing level: " + filename)
|
||||||
|
d.EditLevel(filename)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Play a map.
|
||||||
|
func (c Command) Play(d *Doodle) error {
|
||||||
|
if len(c.Args) == 0 {
|
||||||
|
return errors.New("Usage: play <file name>")
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := c.Args[0]
|
||||||
|
d.shell.Write("Playing level: " + filename)
|
||||||
|
d.PlayLevel(filename)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quit the command line shell.
|
||||||
|
func (c Command) Quit() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default command.
|
||||||
|
func (c Command) Default() error {
|
||||||
|
return fmt.Errorf("%s: command not found. Try `help` for help",
|
||||||
|
c.Command,
|
||||||
|
)
|
||||||
|
}
|
44
doodle.go
44
doodle.go
|
@ -4,7 +4,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.kirsle.net/apps/doodle/events"
|
|
||||||
"git.kirsle.net/apps/doodle/render"
|
"git.kirsle.net/apps/doodle/render"
|
||||||
"github.com/kirsle/golog"
|
"github.com/kirsle/golog"
|
||||||
)
|
)
|
||||||
|
@ -28,10 +27,12 @@ type Doodle struct {
|
||||||
startTime time.Time
|
startTime time.Time
|
||||||
running bool
|
running bool
|
||||||
ticks uint64
|
ticks uint64
|
||||||
events *events.State
|
|
||||||
width int32
|
width int32
|
||||||
height int32
|
height int32
|
||||||
|
|
||||||
|
// Command line shell options.
|
||||||
|
shell Shell
|
||||||
|
|
||||||
scene Scene
|
scene Scene
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,11 +42,11 @@ func New(debug bool, engine render.Engine) *Doodle {
|
||||||
Debug: debug,
|
Debug: debug,
|
||||||
Engine: engine,
|
Engine: engine,
|
||||||
startTime: time.Now(),
|
startTime: time.Now(),
|
||||||
events: events.New(),
|
|
||||||
running: true,
|
running: true,
|
||||||
width: 800,
|
width: 800,
|
||||||
height: 600,
|
height: 600,
|
||||||
}
|
}
|
||||||
|
d.shell = NewShell(d)
|
||||||
|
|
||||||
if !debug {
|
if !debug {
|
||||||
log.Config.Level = golog.InfoLevel
|
log.Config.Level = golog.InfoLevel
|
||||||
|
@ -68,6 +69,8 @@ func (d *Doodle) Run() error {
|
||||||
|
|
||||||
log.Info("Enter Main Loop")
|
log.Info("Enter Main Loop")
|
||||||
for d.running {
|
for d.running {
|
||||||
|
d.Engine.Clear(render.White)
|
||||||
|
|
||||||
start := time.Now() // Record how long this frame took.
|
start := time.Now() // Record how long this frame took.
|
||||||
d.ticks++
|
d.ticks++
|
||||||
|
|
||||||
|
@ -79,8 +82,15 @@ func (d *Doodle) Run() error {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Command line shell.
|
||||||
|
if d.shell.Open {
|
||||||
|
|
||||||
|
} else if ev.EnterKey.Read() {
|
||||||
|
log.Debug("Shell: opening shell")
|
||||||
|
d.shell.Open = true
|
||||||
|
} else {
|
||||||
// Global event handlers.
|
// Global event handlers.
|
||||||
if ev.EscapeKey.Pressed() {
|
if ev.EscapeKey.Read() {
|
||||||
log.Error("Escape key pressed, shutting down")
|
log.Error("Escape key pressed, shutting down")
|
||||||
d.running = false
|
d.running = false
|
||||||
break
|
break
|
||||||
|
@ -91,12 +101,24 @@ func (d *Doodle) Run() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the scene.
|
||||||
|
d.scene.Draw(d)
|
||||||
|
|
||||||
|
// Draw the shell.
|
||||||
|
err = d.shell.Draw(d, ev)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("shell error: %s", err)
|
||||||
|
d.running = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
// Draw the debug overlay over all scenes.
|
// Draw the debug overlay over all scenes.
|
||||||
d.DrawDebugOverlay()
|
d.DrawDebugOverlay()
|
||||||
|
|
||||||
// Render the pixels to the screen.
|
// Render the pixels to the screen.
|
||||||
err = d.Engine.Draw()
|
err = d.Engine.Present()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("draw error: %s", err)
|
log.Error("draw error: %s", err)
|
||||||
d.running = false
|
d.running = false
|
||||||
|
@ -114,12 +136,22 @@ func (d *Doodle) Run() error {
|
||||||
|
|
||||||
// Track how long this frame took to measure FPS over time.
|
// Track how long this frame took to measure FPS over time.
|
||||||
d.TrackFPS(delay)
|
d.TrackFPS(delay)
|
||||||
|
|
||||||
|
// Consume any lingering key sym.
|
||||||
|
ev.KeyName.Read()
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Warn("Main Loop Exited! Shutting down...")
|
log.Warn("Main Loop Exited! Shutting down...")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewMap loads a new map in Edit Mode.
|
||||||
|
func (d *Doodle) NewMap() {
|
||||||
|
log.Info("Starting a new map")
|
||||||
|
scene := &EditorScene{}
|
||||||
|
d.Goto(scene)
|
||||||
|
}
|
||||||
|
|
||||||
// EditLevel loads a map from JSON into the EditorScene.
|
// EditLevel loads a map from JSON into the EditorScene.
|
||||||
func (d *Doodle) EditLevel(filename string) error {
|
func (d *Doodle) EditLevel(filename string) error {
|
||||||
log.Info("Loading level from file: %s", filename)
|
log.Info("Loading level from file: %s", filename)
|
||||||
|
@ -144,7 +176,7 @@ func (d *Doodle) PlayLevel(filename string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: not a global
|
// Pixel TODO: not a global
|
||||||
type Pixel struct {
|
type Pixel struct {
|
||||||
start bool
|
start bool
|
||||||
x int32
|
x int32
|
||||||
|
|
|
@ -19,6 +19,7 @@ type EditorScene struct {
|
||||||
// History of all the pixels placed by the user.
|
// History of all the pixels placed by the user.
|
||||||
pixelHistory []Pixel
|
pixelHistory []Pixel
|
||||||
canvas Grid
|
canvas Grid
|
||||||
|
filename string // Last saved filename.
|
||||||
|
|
||||||
// Canvas size
|
// Canvas size
|
||||||
width int32
|
width int32
|
||||||
|
@ -49,7 +50,6 @@ func (s *EditorScene) Loop(d *Doodle, ev *events.State) error {
|
||||||
if ev.ScreenshotKey.Pressed() {
|
if ev.ScreenshotKey.Pressed() {
|
||||||
log.Info("Taking a screenshot")
|
log.Info("Taking a screenshot")
|
||||||
s.Screenshot()
|
s.Screenshot()
|
||||||
s.SaveLevel()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear the canvas and fill it with white.
|
// Clear the canvas and fill it with white.
|
||||||
|
@ -81,6 +81,11 @@ func (s *EditorScene) Loop(d *Doodle, ev *events.State) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the current frame.
|
||||||
|
func (s *EditorScene) Draw(d *Doodle) error {
|
||||||
for i, pixel := range s.pixelHistory {
|
for i, pixel := range s.pixelHistory {
|
||||||
if !pixel.start && i > 0 {
|
if !pixel.start && i > 0 {
|
||||||
prev := s.pixelHistory[i-1]
|
prev := s.pixelHistory[i-1]
|
||||||
|
@ -105,6 +110,7 @@ func (s *EditorScene) Loop(d *Doodle, ev *events.State) error {
|
||||||
|
|
||||||
// LoadLevel loads a level from disk.
|
// LoadLevel loads a level from disk.
|
||||||
func (s *EditorScene) LoadLevel(filename string) error {
|
func (s *EditorScene) LoadLevel(filename string) error {
|
||||||
|
s.filename = filename
|
||||||
s.pixelHistory = []Pixel{}
|
s.pixelHistory = []Pixel{}
|
||||||
s.canvas = Grid{}
|
s.canvas = Grid{}
|
||||||
|
|
||||||
|
@ -129,7 +135,8 @@ func (s *EditorScene) LoadLevel(filename string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveLevel saves the level to disk.
|
// SaveLevel saves the level to disk.
|
||||||
func (s *EditorScene) SaveLevel() {
|
func (s *EditorScene) SaveLevel(filename string) {
|
||||||
|
s.filename = filename
|
||||||
m := level.Level{
|
m := level.Level{
|
||||||
Version: 1,
|
Version: 1,
|
||||||
Title: "Alpha",
|
Title: "Alpha",
|
||||||
|
@ -161,9 +168,6 @@ func (s *EditorScene) SaveLevel() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
filename := fmt.Sprintf("./map-%s.json",
|
|
||||||
time.Now().Format("2006-01-02T15-04-05"),
|
|
||||||
)
|
|
||||||
err = ioutil.WriteFile(filename, json, 0644)
|
err = ioutil.WriteFile(filename, json, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Create map file error: %s", err)
|
log.Error("Create map file error: %s", err)
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
// Package events manages mouse and keyboard SDL events for Doodle.
|
// Package events manages mouse and keyboard SDL events for Doodle.
|
||||||
package events
|
package events
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
// State keeps track of event states.
|
// State keeps track of event states.
|
||||||
type State struct {
|
type State struct {
|
||||||
// Mouse buttons.
|
// Mouse buttons.
|
||||||
|
@ -10,6 +12,9 @@ type State struct {
|
||||||
// Screenshot key.
|
// Screenshot key.
|
||||||
ScreenshotKey *BoolTick
|
ScreenshotKey *BoolTick
|
||||||
EscapeKey *BoolTick
|
EscapeKey *BoolTick
|
||||||
|
EnterKey *BoolTick
|
||||||
|
ShiftActive *BoolTick
|
||||||
|
KeyName *StringTick
|
||||||
Up *BoolTick
|
Up *BoolTick
|
||||||
Left *BoolTick
|
Left *BoolTick
|
||||||
Right *BoolTick
|
Right *BoolTick
|
||||||
|
@ -27,6 +32,9 @@ func New() *State {
|
||||||
Button2: &BoolTick{},
|
Button2: &BoolTick{},
|
||||||
ScreenshotKey: &BoolTick{},
|
ScreenshotKey: &BoolTick{},
|
||||||
EscapeKey: &BoolTick{},
|
EscapeKey: &BoolTick{},
|
||||||
|
EnterKey: &BoolTick{},
|
||||||
|
ShiftActive: &BoolTick{},
|
||||||
|
KeyName: &StringTick{},
|
||||||
Up: &BoolTick{},
|
Up: &BoolTick{},
|
||||||
Left: &BoolTick{},
|
Left: &BoolTick{},
|
||||||
Right: &BoolTick{},
|
Right: &BoolTick{},
|
||||||
|
@ -35,3 +43,43 @@ func New() *State {
|
||||||
CursorY: &Int32Tick{},
|
CursorY: &Int32Tick{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReadKey returns the normalized key symbol being pressed,
|
||||||
|
// taking the Shift key into account. QWERTY keyboard only, probably.
|
||||||
|
func (ev *State) ReadKey() string {
|
||||||
|
if key := ev.KeyName.Read(); key != "" {
|
||||||
|
if ev.ShiftActive.Pressed() {
|
||||||
|
if symbol, ok := shiftMap[key]; ok {
|
||||||
|
return symbol
|
||||||
|
}
|
||||||
|
return strings.ToUpper(key)
|
||||||
|
}
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// shiftMap maps keys to their Shift versions.
|
||||||
|
var shiftMap = map[string]string{
|
||||||
|
"`": "~",
|
||||||
|
"1": "!",
|
||||||
|
"2": "@",
|
||||||
|
"3": "#",
|
||||||
|
"4": "$",
|
||||||
|
"5": "%",
|
||||||
|
"6": "^",
|
||||||
|
"7": "&",
|
||||||
|
"8": "*",
|
||||||
|
"9": "(",
|
||||||
|
"0": ")",
|
||||||
|
"-": "_",
|
||||||
|
"=": "+",
|
||||||
|
"[": "{",
|
||||||
|
"]": "}",
|
||||||
|
`\`: "|",
|
||||||
|
";": ":",
|
||||||
|
`'`: `"`,
|
||||||
|
",": "<",
|
||||||
|
".": ">",
|
||||||
|
"/": "?",
|
||||||
|
}
|
||||||
|
|
|
@ -12,6 +12,12 @@ type Int32Tick struct {
|
||||||
Last int32
|
Last int32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StringTick manages strings between this frame and the previous.
|
||||||
|
type StringTick struct {
|
||||||
|
Now string
|
||||||
|
Last string
|
||||||
|
}
|
||||||
|
|
||||||
// Push a bool state, copying the current Now value to Last.
|
// Push a bool state, copying the current Now value to Last.
|
||||||
func (bs *BoolTick) Push(v bool) {
|
func (bs *BoolTick) Push(v bool) {
|
||||||
bs.Last = bs.Now
|
bs.Last = bs.Now
|
||||||
|
@ -23,8 +29,28 @@ func (bs *BoolTick) Pressed() bool {
|
||||||
return bs.Now && !bs.Last
|
return bs.Now && !bs.Last
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read a bool state, resetting its value to false.
|
||||||
|
func (bs *BoolTick) Read() bool {
|
||||||
|
now := bs.Now
|
||||||
|
bs.Push(false)
|
||||||
|
return now
|
||||||
|
}
|
||||||
|
|
||||||
// Push an int32 state, copying the current Now value to Last.
|
// Push an int32 state, copying the current Now value to Last.
|
||||||
func (is *Int32Tick) Push(v int32) {
|
func (is *Int32Tick) Push(v int32) {
|
||||||
is.Last = is.Now
|
is.Last = is.Now
|
||||||
is.Now = v
|
is.Now = v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Push a string state.
|
||||||
|
func (s *StringTick) Push(v string) {
|
||||||
|
s.Last = s.Now
|
||||||
|
s.Now = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read a string state, resetting its value.
|
||||||
|
func (s *StringTick) Read() string {
|
||||||
|
now := s.Now
|
||||||
|
s.Push("")
|
||||||
|
return now
|
||||||
|
}
|
||||||
|
|
21
fps.go
21
fps.go
|
@ -2,7 +2,6 @@ package doodle
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.kirsle.net/apps/doodle/render"
|
"git.kirsle.net/apps/doodle/render"
|
||||||
)
|
)
|
||||||
|
@ -26,11 +25,9 @@ func (d *Doodle) DrawDebugOverlay() {
|
||||||
}
|
}
|
||||||
|
|
||||||
label := fmt.Sprintf(
|
label := fmt.Sprintf(
|
||||||
"FPS: %d (%dms) (%d,%d) S:%s F12=screenshot",
|
"FPS: %d (%dms) S:%s F12=screenshot",
|
||||||
fpsCurrent,
|
fpsCurrent,
|
||||||
fpsSkipped,
|
fpsSkipped,
|
||||||
d.events.CursorX.Now,
|
|
||||||
d.events.CursorY.Now,
|
|
||||||
d.scene.Name(),
|
d.scene.Name(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -42,11 +39,9 @@ func (d *Doodle) DrawDebugOverlay() {
|
||||||
Stroke: DebugTextStroke,
|
Stroke: DebugTextStroke,
|
||||||
Shadow: DebugTextShadow,
|
Shadow: DebugTextShadow,
|
||||||
},
|
},
|
||||||
render.Rect{
|
render.Point{
|
||||||
X: DebugTextPadding,
|
X: DebugTextPadding,
|
||||||
Y: DebugTextPadding,
|
Y: DebugTextPadding,
|
||||||
W: d.width,
|
|
||||||
H: d.height,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -65,12 +60,12 @@ func (d *Doodle) TrackFPS(skipped uint32) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if fpsLastTime < fpsCurrentTicks-fpsInterval {
|
if fpsLastTime < fpsCurrentTicks-fpsInterval {
|
||||||
log.Debug("Uptime: %s FPS: %d deltaTicks: %d skipped: %dms",
|
// log.Debug("Uptime: %s FPS: %d deltaTicks: %d skipped: %dms",
|
||||||
time.Now().Sub(d.startTime),
|
// time.Now().Sub(d.startTime),
|
||||||
fpsCurrent,
|
// fpsCurrent,
|
||||||
fpsCurrentTicks-fpsLastTime,
|
// fpsCurrentTicks-fpsLastTime,
|
||||||
skipped,
|
// skipped,
|
||||||
)
|
// )
|
||||||
|
|
||||||
fpsLastTime = fpsCurrentTicks
|
fpsLastTime = fpsCurrentTicks
|
||||||
fpsCurrent = fpsFrames
|
fpsCurrent = fpsFrames
|
||||||
|
|
|
@ -42,9 +42,7 @@ func (s *PlayScene) Setup(d *Doodle) error {
|
||||||
// Loop the editor scene.
|
// Loop the editor scene.
|
||||||
func (s *PlayScene) Loop(d *Doodle, ev *events.State) error {
|
func (s *PlayScene) Loop(d *Doodle, ev *events.State) error {
|
||||||
s.movePlayer(ev)
|
s.movePlayer(ev)
|
||||||
|
return nil
|
||||||
// Apply gravity.
|
|
||||||
return s.Draw(d)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw the pixels on this frame.
|
// Draw the pixels on this frame.
|
||||||
|
@ -57,7 +55,6 @@ func (s *PlayScene) Draw(d *Doodle) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw our hero.
|
// Draw our hero.
|
||||||
log.Info("hero %s %+v", render.Magenta, render.Magenta)
|
|
||||||
d.Engine.DrawRect(render.Magenta, render.Rect{s.x, s.y, 16, 16})
|
d.Engine.DrawRect(render.Magenta, render.Rect{s.x, s.y, 16, 16})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -15,15 +15,16 @@ type Engine interface {
|
||||||
Poll() (*events.State, error)
|
Poll() (*events.State, error)
|
||||||
GetTicks() uint32
|
GetTicks() uint32
|
||||||
|
|
||||||
// Draw presents the current state to the screen.
|
// Present presents the current state to the screen.
|
||||||
Draw() error
|
Present() error
|
||||||
|
|
||||||
// Clear the full canvas and set this color.
|
// Clear the full canvas and set this color.
|
||||||
Clear(Color)
|
Clear(Color)
|
||||||
DrawPoint(Color, Point)
|
DrawPoint(Color, Point)
|
||||||
DrawLine(Color, Point, Point)
|
DrawLine(Color, Point, Point)
|
||||||
DrawRect(Color, Rect)
|
DrawRect(Color, Rect)
|
||||||
DrawText(Text, Rect) error
|
DrawBox(Color, Rect)
|
||||||
|
DrawText(Text, Point) error
|
||||||
|
|
||||||
// Delay for a moment using the render engine's delay method,
|
// Delay for a moment using the render engine's delay method,
|
||||||
// implemented by sdl.Delay(uint32)
|
// implemented by sdl.Delay(uint32)
|
||||||
|
|
|
@ -42,3 +42,16 @@ func (r *Renderer) DrawRect(color render.Color, rect render.Rect) {
|
||||||
H: rect.H,
|
H: rect.H,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DrawBox draws a filled rectangle.
|
||||||
|
func (r *Renderer) DrawBox(color render.Color, rect render.Rect) {
|
||||||
|
if color != r.lastColor {
|
||||||
|
r.renderer.SetDrawColor(color.Red, color.Green, color.Blue, color.Alpha)
|
||||||
|
}
|
||||||
|
r.renderer.FillRect(&sdl.Rect{
|
||||||
|
X: rect.X,
|
||||||
|
Y: rect.Y,
|
||||||
|
W: rect.W,
|
||||||
|
H: rect.H,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ var log *golog.Logger
|
||||||
var (
|
var (
|
||||||
DebugMouseEvents = false
|
DebugMouseEvents = false
|
||||||
DebugClickEvents = false
|
DebugClickEvents = false
|
||||||
|
DebugKeyEvents = false
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
|
@ -159,16 +159,23 @@ func (r *Renderer) Poll() (*events.State, error) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
case *sdl.KeyboardEvent:
|
case *sdl.KeyboardEvent:
|
||||||
|
if DebugKeyEvents {
|
||||||
log.Debug("[%d ms] tick:%d Keyboard type:%d sym:%c modifiers:%d state:%d repeat:%d\n",
|
log.Debug("[%d ms] tick:%d Keyboard type:%d sym:%c modifiers:%d state:%d repeat:%d\n",
|
||||||
t.Timestamp, r.ticks, t.Type, t.Keysym.Sym, t.Keysym.Mod, t.State, t.Repeat,
|
t.Timestamp, r.ticks, t.Type, t.Keysym.Sym, t.Keysym.Mod, t.State, t.Repeat,
|
||||||
)
|
)
|
||||||
if t.Repeat == 1 {
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch t.Keysym.Scancode {
|
switch t.Keysym.Scancode {
|
||||||
case sdl.SCANCODE_ESCAPE:
|
case sdl.SCANCODE_ESCAPE:
|
||||||
|
if t.Repeat == 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
s.EscapeKey.Push(t.State == 1)
|
s.EscapeKey.Push(t.State == 1)
|
||||||
|
case sdl.SCANCODE_RETURN:
|
||||||
|
if t.Repeat == 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
s.EnterKey.Push(t.State == 1)
|
||||||
case sdl.SCANCODE_F12:
|
case sdl.SCANCODE_F12:
|
||||||
s.ScreenshotKey.Push(t.State == 1)
|
s.ScreenshotKey.Push(t.State == 1)
|
||||||
case sdl.SCANCODE_UP:
|
case sdl.SCANCODE_UP:
|
||||||
|
@ -179,6 +186,25 @@ func (r *Renderer) Poll() (*events.State, error) {
|
||||||
s.Right.Push(t.State == 1)
|
s.Right.Push(t.State == 1)
|
||||||
case sdl.SCANCODE_DOWN:
|
case sdl.SCANCODE_DOWN:
|
||||||
s.Down.Push(t.State == 1)
|
s.Down.Push(t.State == 1)
|
||||||
|
case sdl.SCANCODE_LSHIFT:
|
||||||
|
case sdl.SCANCODE_RSHIFT:
|
||||||
|
s.ShiftActive.Push(t.State == 1)
|
||||||
|
continue
|
||||||
|
case sdl.SCANCODE_LALT:
|
||||||
|
case sdl.SCANCODE_RALT:
|
||||||
|
case sdl.SCANCODE_LCTRL:
|
||||||
|
case sdl.SCANCODE_RCTRL:
|
||||||
|
continue
|
||||||
|
case sdl.SCANCODE_BACKSPACE:
|
||||||
|
// Make it a key event with "\b" as the sequence.
|
||||||
|
if t.State == 1 || t.Repeat == 1 {
|
||||||
|
s.KeyName.Push(`\b`)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// Push the string value of the key.
|
||||||
|
if t.State == 1 {
|
||||||
|
s.KeyName.Push(string(t.Keysym.Sym))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -186,8 +212,8 @@ func (r *Renderer) Poll() (*events.State, error) {
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw a single frame.
|
// Present the current frame.
|
||||||
func (r *Renderer) Draw() error {
|
func (r *Renderer) Present() error {
|
||||||
r.renderer.Present()
|
r.renderer.Present()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package sdl
|
package sdl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.kirsle.net/apps/doodle/events"
|
||||||
"git.kirsle.net/apps/doodle/render"
|
"git.kirsle.net/apps/doodle/render"
|
||||||
"github.com/veandco/go-sdl2/sdl"
|
"github.com/veandco/go-sdl2/sdl"
|
||||||
"github.com/veandco/go-sdl2/ttf"
|
"github.com/veandco/go-sdl2/ttf"
|
||||||
|
@ -23,8 +26,22 @@ func LoadFont(size int) (*ttf.Font, error) {
|
||||||
return font, nil
|
return font, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Keysym returns the current key pressed, taking into account the Shift
|
||||||
|
// key modifier.
|
||||||
|
func (r *Renderer) Keysym(ev *events.State) string {
|
||||||
|
if key := ev.KeyName.Read(); key != "" {
|
||||||
|
if ev.ShiftActive.Pressed() {
|
||||||
|
if symbol, ok := shiftMap[key]; ok {
|
||||||
|
return symbol
|
||||||
|
}
|
||||||
|
return strings.ToUpper(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// DrawText draws text on the canvas.
|
// DrawText draws text on the canvas.
|
||||||
func (r *Renderer) DrawText(text render.Text, rect render.Rect) error {
|
func (r *Renderer) DrawText(text render.Text, point render.Point) error {
|
||||||
var (
|
var (
|
||||||
font *ttf.Font
|
font *ttf.Font
|
||||||
surface *sdl.Surface
|
surface *sdl.Surface
|
||||||
|
@ -48,8 +65,8 @@ func (r *Renderer) DrawText(text render.Text, rect render.Rect) error {
|
||||||
defer tex.Destroy()
|
defer tex.Destroy()
|
||||||
|
|
||||||
tmp := &sdl.Rect{
|
tmp := &sdl.Rect{
|
||||||
X: rect.X + dx,
|
X: point.X + dx,
|
||||||
Y: rect.Y + dy,
|
Y: point.Y + dy,
|
||||||
W: surface.W,
|
W: surface.W,
|
||||||
H: surface.H,
|
H: surface.H,
|
||||||
}
|
}
|
||||||
|
@ -79,3 +96,28 @@ func (r *Renderer) DrawText(text render.Text, rect render.Rect) error {
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// shiftMap maps keys to their Shift versions.
|
||||||
|
var shiftMap = map[string]string{
|
||||||
|
"`": "~",
|
||||||
|
"1": "!",
|
||||||
|
"2": "@",
|
||||||
|
"3": "#",
|
||||||
|
"4": "$",
|
||||||
|
"5": "%",
|
||||||
|
"6": "^",
|
||||||
|
"7": "&",
|
||||||
|
"8": "*",
|
||||||
|
"9": "(",
|
||||||
|
"0": ")",
|
||||||
|
"-": "_",
|
||||||
|
"=": "+",
|
||||||
|
"[": "{",
|
||||||
|
"]": "}",
|
||||||
|
`\`: "|",
|
||||||
|
";": ":",
|
||||||
|
`'`: `"`,
|
||||||
|
",": "<",
|
||||||
|
".": ">",
|
||||||
|
"/": "?",
|
||||||
|
}
|
||||||
|
|
6
scene.go
6
scene.go
|
@ -8,7 +8,13 @@ import "git.kirsle.net/apps/doodle/events"
|
||||||
type Scene interface {
|
type Scene interface {
|
||||||
Name() string
|
Name() string
|
||||||
Setup(*Doodle) error
|
Setup(*Doodle) error
|
||||||
|
|
||||||
|
// Loop should update the scene's state but not draw anything.
|
||||||
Loop(*Doodle, *events.State) error
|
Loop(*Doodle, *events.State) error
|
||||||
|
|
||||||
|
// Draw should use the scene's state to figure out what pixels need
|
||||||
|
// to draw to the screen.
|
||||||
|
Draw(*Doodle) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Goto a scene. First it unloads the current scene.
|
// Goto a scene. First it unloads the current scene.
|
||||||
|
|
|
@ -1,86 +0,0 @@
|
||||||
package scene
|
|
||||||
|
|
||||||
import "git.kirsle.net/apps/doodle/events"
|
|
||||||
|
|
||||||
// Editor is the drawing mode of the game where the user is clicking and
|
|
||||||
// dragging to draw pixels.
|
|
||||||
type Editor struct{}
|
|
||||||
|
|
||||||
func (s *Editor) String() string {
|
|
||||||
return "Editor"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup the scene.
|
|
||||||
func (s *Editor) Setup() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loop the scene.
|
|
||||||
func (s *Editor) Loop(ev *events.State) error {
|
|
||||||
// Taking a screenshot?
|
|
||||||
if ev.ScreenshotKey.Pressed() {
|
|
||||||
log.Info("Taking a screenshot")
|
|
||||||
d.Screenshot()
|
|
||||||
d.SaveLevel()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear the canvas and fill it with white.
|
|
||||||
d.renderer.SetDrawColor(255, 255, 255, 255)
|
|
||||||
d.renderer.Clear()
|
|
||||||
|
|
||||||
// Clicking? Log all the pixels while doing so.
|
|
||||||
if ev.Button1.Now {
|
|
||||||
pixel := Pixel{
|
|
||||||
start: ev.Button1.Pressed(),
|
|
||||||
x: ev.CursorX.Now,
|
|
||||||
y: ev.CursorY.Now,
|
|
||||||
dx: ev.CursorX.Now,
|
|
||||||
dy: ev.CursorY.Now,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append unique new pixels.
|
|
||||||
if len(pixelHistory) == 0 || pixelHistory[len(pixelHistory)-1] != pixel {
|
|
||||||
// If not a start pixel, make the delta coord the previous one.
|
|
||||||
if !pixel.start && len(pixelHistory) > 0 {
|
|
||||||
prev := pixelHistory[len(pixelHistory)-1]
|
|
||||||
pixel.dx = prev.x
|
|
||||||
pixel.dy = prev.y
|
|
||||||
}
|
|
||||||
|
|
||||||
pixelHistory = append(pixelHistory, pixel)
|
|
||||||
|
|
||||||
// Save in the pixel canvas map.
|
|
||||||
d.canvas[pixel] = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
d.renderer.SetDrawColor(0, 0, 0, 255)
|
|
||||||
for i, pixel := range pixelHistory {
|
|
||||||
if !pixel.start && i > 0 {
|
|
||||||
prev := pixelHistory[i-1]
|
|
||||||
if prev.x == pixel.x && prev.y == pixel.y {
|
|
||||||
d.renderer.DrawPoint(pixel.x, pixel.y)
|
|
||||||
} else {
|
|
||||||
d.renderer.DrawLine(
|
|
||||||
pixel.x,
|
|
||||||
pixel.y,
|
|
||||||
prev.x,
|
|
||||||
prev.y,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
d.renderer.DrawPoint(pixel.x, pixel.y)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw the FPS.
|
|
||||||
d.DrawDebugOverlay()
|
|
||||||
|
|
||||||
d.renderer.Present()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Destroy the scene.
|
|
||||||
func (s *Editor) Destroy() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
package scene
|
|
||||||
|
|
||||||
import "github.com/kirsle/golog"
|
|
||||||
|
|
||||||
var log *golog.Logger
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
log = golog.GetLogger("doodle")
|
|
||||||
}
|
|
|
@ -1,67 +0,0 @@
|
||||||
package scene
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"git.kirsle.net/apps/doodle/events"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Scene is an interface for the top level of a game mode. The game points to
|
|
||||||
// one Scene at a time, and that Scene has majority control of the main loop,
|
|
||||||
// and maintains its own state local to that scene.
|
|
||||||
type Scene interface {
|
|
||||||
String() string // the scene's name
|
|
||||||
Setup() error
|
|
||||||
Loop() error
|
|
||||||
Destroy() error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Manager is a type that provides context switching features to manage scenes.
|
|
||||||
type Manager struct {
|
|
||||||
events *events.State
|
|
||||||
scene Scene
|
|
||||||
ticks uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewManager creates the new manager.
|
|
||||||
func NewManager(events *events.State) Manager {
|
|
||||||
return Manager{
|
|
||||||
events: events,
|
|
||||||
scene: nil,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Go to a new scene. This tears down the existing scene, sets up the new one,
|
|
||||||
// and switches control to the new scene.
|
|
||||||
func (m *Manager) Go(scene Scene) error {
|
|
||||||
// Already running a scene?
|
|
||||||
if m.scene != nil {
|
|
||||||
if err := m.scene.Destroy(); err != nil {
|
|
||||||
return fmt.Errorf("couldn't destroy scene %s: %s", m.scene, err)
|
|
||||||
}
|
|
||||||
m.scene = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the new scene.
|
|
||||||
m.scene = scene
|
|
||||||
return m.scene.Setup()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loop the scene manager. This is the game's main loop which runs all the tasks
|
|
||||||
// that fall in the realm of the scene manager.
|
|
||||||
func (m *Manager) Loop() error {
|
|
||||||
if m.scene == nil {
|
|
||||||
return errors.New("no scene loaded")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Poll for events.
|
|
||||||
ev, err := m.events.Poll(m.ticks)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("event poll error: %s", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_ = ev
|
|
||||||
|
|
||||||
return m.scene.Loop()
|
|
||||||
}
|
|
252
shell.go
Normal file
252
shell.go
Normal file
|
@ -0,0 +1,252 @@
|
||||||
|
package doodle
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.kirsle.net/apps/doodle/balance"
|
||||||
|
"git.kirsle.net/apps/doodle/events"
|
||||||
|
"git.kirsle.net/apps/doodle/render"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Shell implements the developer console in-game.
|
||||||
|
type Shell struct {
|
||||||
|
parent *Doodle
|
||||||
|
Open bool
|
||||||
|
Prompt string
|
||||||
|
Text string
|
||||||
|
History []string
|
||||||
|
Output []string
|
||||||
|
Flashes []Flash
|
||||||
|
Cursor string
|
||||||
|
cursorFlip uint64 // ticks until cursor flip
|
||||||
|
cursorRate uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flash holds a message to flash on screen.
|
||||||
|
type Flash struct {
|
||||||
|
Text string
|
||||||
|
Expires uint64 // tick that it expires
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewShell initializes the shell helper (the "Shellper").
|
||||||
|
func NewShell(d *Doodle) Shell {
|
||||||
|
return Shell{
|
||||||
|
parent: d,
|
||||||
|
History: []string{},
|
||||||
|
Output: []string{},
|
||||||
|
Flashes: []Flash{},
|
||||||
|
Prompt: ">",
|
||||||
|
Cursor: "_",
|
||||||
|
cursorRate: balance.ShellCursorBlinkRate,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the shell, resetting its internal state.
|
||||||
|
func (s *Shell) Close() {
|
||||||
|
log.Debug("Shell: closing shell")
|
||||||
|
s.Open = false
|
||||||
|
s.Prompt = ">"
|
||||||
|
s.Text = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute a command in the shell.
|
||||||
|
func (s *Shell) Execute(input string) {
|
||||||
|
command := s.Parse(input)
|
||||||
|
err := command.Run(s.parent)
|
||||||
|
if err != nil {
|
||||||
|
s.Write(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if command.Raw != "" {
|
||||||
|
s.History = append(s.History, command.Raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset the text buffer in the shell.
|
||||||
|
s.Text = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write a line of output text to the console.
|
||||||
|
func (s *Shell) Write(line string) {
|
||||||
|
s.Output = append(s.Output, line)
|
||||||
|
s.Flashes = append(s.Flashes, Flash{
|
||||||
|
Text: line,
|
||||||
|
Expires: s.parent.ticks + balance.FlashTTL,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the command line.
|
||||||
|
func (s *Shell) Parse(input string) Command {
|
||||||
|
input = strings.TrimSpace(input)
|
||||||
|
if len(input) == 0 {
|
||||||
|
return Command{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
inQuote bool
|
||||||
|
buffer = bytes.NewBuffer([]byte{})
|
||||||
|
words = []string{}
|
||||||
|
)
|
||||||
|
for i := 0; i < len(input); i++ {
|
||||||
|
char := input[i]
|
||||||
|
switch char {
|
||||||
|
case ' ':
|
||||||
|
if inQuote {
|
||||||
|
buffer.WriteByte(char)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if word := buffer.String(); word != "" {
|
||||||
|
words = append(words, word)
|
||||||
|
buffer.Reset()
|
||||||
|
}
|
||||||
|
case '"':
|
||||||
|
if !inQuote {
|
||||||
|
// An opening quote character.
|
||||||
|
inQuote = true
|
||||||
|
} else {
|
||||||
|
// The closing quote.
|
||||||
|
inQuote = false
|
||||||
|
|
||||||
|
if word := buffer.String(); word != "" {
|
||||||
|
words = append(words, word)
|
||||||
|
buffer.Reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
buffer.WriteByte(char)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if remainder := buffer.String(); remainder != "" {
|
||||||
|
words = append(words, remainder)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Command{
|
||||||
|
Raw: input,
|
||||||
|
Command: words[0],
|
||||||
|
Args: words[1:],
|
||||||
|
ArgsLiteral: strings.TrimSpace(input[len(words[0]):]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the shell.
|
||||||
|
func (s *Shell) Draw(d *Doodle, ev *events.State) error {
|
||||||
|
if ev.EscapeKey.Read() {
|
||||||
|
s.Close()
|
||||||
|
return nil
|
||||||
|
} else if ev.EnterKey.Read() || ev.EscapeKey.Read() {
|
||||||
|
s.Execute(s.Text)
|
||||||
|
s.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute the line height we can draw.
|
||||||
|
lineHeight := balance.ShellFontSize + int(balance.ShellPadding)
|
||||||
|
|
||||||
|
// If the console is open, draw the console.
|
||||||
|
if s.Open {
|
||||||
|
// Cursor flip?
|
||||||
|
if d.ticks > s.cursorFlip {
|
||||||
|
s.cursorFlip = d.ticks + s.cursorRate
|
||||||
|
if s.Cursor == "" {
|
||||||
|
s.Cursor = "_"
|
||||||
|
} else {
|
||||||
|
s.Cursor = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read a character from the keyboard.
|
||||||
|
if key := ev.ReadKey(); key != "" {
|
||||||
|
// Backspace?
|
||||||
|
if key == `\b` {
|
||||||
|
if len(s.Text) > 0 {
|
||||||
|
s.Text = s.Text[:len(s.Text)-1]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s.Text += key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// How tall is the box?
|
||||||
|
boxHeight := int32(lineHeight*(balance.ShellHistoryLineCount+1)) + balance.ShellPadding
|
||||||
|
|
||||||
|
// Draw the background color.
|
||||||
|
d.Engine.DrawBox(
|
||||||
|
balance.ShellBackgroundColor,
|
||||||
|
render.Rect{
|
||||||
|
X: 0,
|
||||||
|
Y: d.height - boxHeight,
|
||||||
|
W: d.width,
|
||||||
|
H: boxHeight,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// Draw the recent commands.
|
||||||
|
outputY := d.height - int32(lineHeight*2)
|
||||||
|
for i := 0; i < balance.ShellHistoryLineCount; i++ {
|
||||||
|
if len(s.Output) > i {
|
||||||
|
line := s.Output[len(s.Output)-1-i]
|
||||||
|
d.Engine.DrawText(
|
||||||
|
render.Text{
|
||||||
|
Text: line,
|
||||||
|
Size: balance.ShellFontSize,
|
||||||
|
Color: render.Grey,
|
||||||
|
},
|
||||||
|
render.Point{
|
||||||
|
X: balance.ShellPadding,
|
||||||
|
Y: outputY,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
outputY -= int32(lineHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the command prompt.
|
||||||
|
d.Engine.DrawText(
|
||||||
|
render.Text{
|
||||||
|
Text: s.Prompt + s.Text + s.Cursor,
|
||||||
|
Size: balance.ShellFontSize,
|
||||||
|
Color: balance.ShellForegroundColor,
|
||||||
|
},
|
||||||
|
render.Point{
|
||||||
|
X: balance.ShellPadding,
|
||||||
|
Y: d.height - int32(balance.ShellFontSize) - balance.ShellPadding,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} else if len(s.Flashes) > 0 {
|
||||||
|
// Otherwise, just draw flashed messages.
|
||||||
|
valid := false // Did we actually draw any?
|
||||||
|
|
||||||
|
outputY := d.height - int32(lineHeight*2)
|
||||||
|
for i := len(s.Flashes); i > 0; i-- {
|
||||||
|
flash := s.Flashes[i-1]
|
||||||
|
if d.ticks >= flash.Expires {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Engine.DrawText(
|
||||||
|
render.Text{
|
||||||
|
Text: flash.Text,
|
||||||
|
Size: balance.ShellFontSize,
|
||||||
|
Color: render.SkyBlue,
|
||||||
|
Stroke: render.Grey,
|
||||||
|
Shadow: render.Black,
|
||||||
|
},
|
||||||
|
render.Point{
|
||||||
|
X: balance.ShellPadding,
|
||||||
|
Y: outputY,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
outputY -= int32(lineHeight)
|
||||||
|
valid = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we've exhausted all flashes, free up the memory.
|
||||||
|
if !valid {
|
||||||
|
s.Flashes = []Flash{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user