Add configdir and unify file loading/saving
* Create a configuration directory to store the user's local levels and doodads. On Linux this is at ~/.config/doodle * Unify the file loading and saving functions: you can type into the console "edit example" and it will open `example.level` from your levels folder or else `example.doodad` from the doodads folder, in the appropriate mode. * You can further specify the file extension: `edit example.doodad` and it will load it from the doodads folder only. * Any slash characters in a file name are taken literally as a relative or absolute path. * The UI Save/Load buttons now share the same code path as the console commands, so the `save` command always saves as a Doodad when the EditorScene is in Doodad Mode.
This commit is contained in:
parent
a7fd3aa1ca
commit
cfe26cb964
|
@ -176,8 +176,8 @@ Lesser important UI features that can come at any later time:
|
||||||
|
|
||||||
## Doodad Editor
|
## Doodad Editor
|
||||||
|
|
||||||
* [ ] The Edit Mode should support creating drawings for Doodads.
|
* [x] The Edit Mode should support creating drawings for Doodads.
|
||||||
* [ ] It should know whether you're drawing a Map or a Doodad as some
|
* [x] It should know whether you're drawing a Map or a Doodad as some
|
||||||
behaviors may need to be different between the two.
|
behaviors may need to be different between the two.
|
||||||
* [ ] Compress the coordinates down toward `(0,0)` when saving a Doodad,
|
* [ ] Compress the coordinates down toward `(0,0)` when saving a Doodad,
|
||||||
by finding the toppest, leftest point and making that `(0,0)` and adjusting
|
by finding the toppest, leftest point and making that `(0,0)` and adjusting
|
||||||
|
|
|
@ -43,7 +43,7 @@ func main() {
|
||||||
app.SetupEngine()
|
app.SetupEngine()
|
||||||
if filename != "" {
|
if filename != "" {
|
||||||
if edit {
|
if edit {
|
||||||
app.EditDrawing(filename)
|
app.EditFile(filename)
|
||||||
} else {
|
} else {
|
||||||
app.PlayLevel(filename)
|
app.PlayLevel(filename)
|
||||||
}
|
}
|
||||||
|
|
16
commands.go
16
commands.go
|
@ -4,6 +4,8 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"git.kirsle.net/apps/doodle/enum"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Command is a parsed shell command.
|
// Command is a parsed shell command.
|
||||||
|
@ -112,11 +114,17 @@ func (c Command) Save(d *Doodle) error {
|
||||||
} else if scene.filename != "" {
|
} else if scene.filename != "" {
|
||||||
filename = scene.filename
|
filename = scene.filename
|
||||||
} else {
|
} else {
|
||||||
return errors.New("usage: save <filename.json>")
|
return errors.New("usage: save <filename>")
|
||||||
}
|
}
|
||||||
|
|
||||||
d.shell.Write("Saving to file: " + filename)
|
switch scene.DrawingType {
|
||||||
|
case enum.LevelDrawing:
|
||||||
|
d.shell.Write("Saving Level: " + filename)
|
||||||
scene.SaveLevel(filename)
|
scene.SaveLevel(filename)
|
||||||
|
case enum.DoodadDrawing:
|
||||||
|
d.shell.Write("Saving Doodad: " + filename)
|
||||||
|
scene.SaveDoodad(filename)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return errors.New("save: only available in Edit Mode")
|
return errors.New("save: only available in Edit Mode")
|
||||||
}
|
}
|
||||||
|
@ -131,8 +139,8 @@ func (c Command) Edit(d *Doodle) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
filename := c.Args[0]
|
filename := c.Args[0]
|
||||||
d.shell.Write("Editing level: " + filename)
|
d.shell.Write("Editing file: " + filename)
|
||||||
d.EditDrawing(filename)
|
d.EditFile(filename)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
136
config.go
136
config.go
|
@ -1,6 +1,15 @@
|
||||||
package doodle
|
package doodle
|
||||||
|
|
||||||
import "git.kirsle.net/apps/doodle/render"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.kirsle.net/apps/doodle/render"
|
||||||
|
"github.com/kirsle/configdir"
|
||||||
|
)
|
||||||
|
|
||||||
// Configuration constants.
|
// Configuration constants.
|
||||||
var (
|
var (
|
||||||
|
@ -10,3 +19,128 @@ var (
|
||||||
DebugTextStroke = render.Grey
|
DebugTextStroke = render.Grey
|
||||||
DebugTextShadow = render.Black
|
DebugTextShadow = render.Black
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Profile Directory settings.
|
||||||
|
var (
|
||||||
|
ConfigDirectoryName = "doodle"
|
||||||
|
ProfileDirectory string
|
||||||
|
LevelDirectory string
|
||||||
|
DoodadDirectory string
|
||||||
|
|
||||||
|
// Regexp to match simple filenames for maps and doodads.
|
||||||
|
reSimpleFilename = regexp.MustCompile(`^([A-Za-z0-9-_.,+ '"\[\](){}]+)$`)
|
||||||
|
)
|
||||||
|
|
||||||
|
// File extensions
|
||||||
|
const (
|
||||||
|
extLevel = ".level"
|
||||||
|
extDoodad = ".doodad"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
ProfileDirectory = configdir.LocalConfig(ConfigDirectoryName)
|
||||||
|
LevelDirectory = configdir.LocalConfig(ConfigDirectoryName, "levels")
|
||||||
|
DoodadDirectory = configdir.LocalConfig(ConfigDirectoryName, "doodads")
|
||||||
|
configdir.MakePath(LevelDirectory, DoodadDirectory)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LevelPath will turn a "simple" filename into an absolute path in the user's
|
||||||
|
// local levels folder. If the filename already contains slashes, it is returned
|
||||||
|
// as-is as an absolute or relative path.
|
||||||
|
func LevelPath(filename string) string {
|
||||||
|
return resolvePath(LevelDirectory, filename, extLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DoodadPath is like LevelPath but for Doodad files.
|
||||||
|
func DoodadPath(filename string) string {
|
||||||
|
return resolvePath(DoodadDirectory, filename, extDoodad)
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolvePath is the inner logic for LevelPath and DoodadPath.
|
||||||
|
func resolvePath(directory, filename, extension string) string {
|
||||||
|
if strings.Contains(filename, "/") {
|
||||||
|
return filename
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach the file extension?
|
||||||
|
if strings.ToLower(filepath.Ext(filename)) != extension {
|
||||||
|
filename += extension
|
||||||
|
}
|
||||||
|
|
||||||
|
return filepath.Join(directory, filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
EditFile opens a drawing file (Level or Doodad) in the EditorScene.
|
||||||
|
|
||||||
|
The filename can be one of the following:
|
||||||
|
|
||||||
|
- A simple filename with no path separators in it and/or no file extension.
|
||||||
|
- An absolute path beginning with "/"
|
||||||
|
- A relative path beginning with "./"
|
||||||
|
|
||||||
|
If the filename has an extension (`.level` or `.doodad`), that will disambiguate
|
||||||
|
how to find the file and which mode to start the EditorMode in. Otherwise, the
|
||||||
|
"levels" folder is checked first and the "doodads" folder second.
|
||||||
|
*/
|
||||||
|
func (d *Doodle) EditFile(filename string) error {
|
||||||
|
var absPath string
|
||||||
|
|
||||||
|
// Is it a simple filename?
|
||||||
|
if m := reSimpleFilename.FindStringSubmatch(filename); len(m) > 0 {
|
||||||
|
log.Debug("EditFile: simple filename %s", filename)
|
||||||
|
extension := strings.ToLower(filepath.Ext(filename))
|
||||||
|
if foundFilename := d.ResolvePath(filename, extension, false); foundFilename != "" {
|
||||||
|
log.Info("EditFile: resolved name '%s' to path %s", filename, foundFilename)
|
||||||
|
absPath = foundFilename
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("EditFile: %s: no level or doodad found", filename)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Debug("Not a simple: %s %+v", filename, reSimpleFilename)
|
||||||
|
if _, err := os.Stat(filename); !os.IsNotExist(err) {
|
||||||
|
log.Debug("EditFile: verified path %s exists", filename)
|
||||||
|
absPath = filename
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d.EditDrawing(absPath)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolvePath takes an ambiguous simple filename and searches for a Level or
|
||||||
|
// Doodad that matches. Returns a blank string if no files found.
|
||||||
|
//
|
||||||
|
// Pass a true value for `one` if you are intending to create the file. It will
|
||||||
|
// only test one filepath and return the first one, regardless if the file
|
||||||
|
// existed. So the filename should have a ".level" or ".doodad" extension and
|
||||||
|
// then this path will resolve the ProfileDirectory of the file.
|
||||||
|
func (d *Doodle) ResolvePath(filename, extension string, one bool) string {
|
||||||
|
// If the filename exists outright, return it.
|
||||||
|
if _, err := os.Stat(filename); !os.IsNotExist(err) {
|
||||||
|
return filename
|
||||||
|
}
|
||||||
|
|
||||||
|
var paths []string
|
||||||
|
if extension == extLevel {
|
||||||
|
paths = append(paths, filepath.Join(LevelDirectory, filename))
|
||||||
|
} else if extension == extDoodad {
|
||||||
|
paths = append(paths, filepath.Join(DoodadDirectory, filename))
|
||||||
|
} else {
|
||||||
|
paths = append(paths,
|
||||||
|
filepath.Join(LevelDirectory, filename+".level"),
|
||||||
|
filepath.Join(DoodadDirectory, filename+".doodad"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range paths {
|
||||||
|
log.Debug("findFilename: try to find '%s' as %s", filename, test)
|
||||||
|
if _, err := os.Stat(test); os.IsNotExist(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return test
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"git.kirsle.net/apps/doodle/balance"
|
"git.kirsle.net/apps/doodle/balance"
|
||||||
"git.kirsle.net/apps/doodle/doodads"
|
"git.kirsle.net/apps/doodle/doodads"
|
||||||
|
@ -167,6 +168,10 @@ func (s *EditorScene) SaveLevel(filename string) error {
|
||||||
return errors.New("SaveLevel: current drawing is not a Level type")
|
return errors.New("SaveLevel: current drawing is not a Level type")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !strings.HasSuffix(filename, extLevel) {
|
||||||
|
filename += extLevel
|
||||||
|
}
|
||||||
|
|
||||||
s.filename = filename
|
s.filename = filename
|
||||||
|
|
||||||
m := s.Level
|
m := s.Level
|
||||||
|
@ -185,6 +190,9 @@ func (s *EditorScene) SaveLevel(filename string) error {
|
||||||
return fmt.Errorf("SaveLevel error: %s", err)
|
return fmt.Errorf("SaveLevel error: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save it to their profile directory.
|
||||||
|
filename = LevelPath(filename)
|
||||||
|
log.Info("Write Level: %s", filename)
|
||||||
err = ioutil.WriteFile(filename, json, 0644)
|
err = ioutil.WriteFile(filename, json, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Create map file error: %s", err)
|
return fmt.Errorf("Create map file error: %s", err)
|
||||||
|
@ -215,6 +223,10 @@ func (s *EditorScene) SaveDoodad(filename string) error {
|
||||||
return errors.New("SaveDoodad: current drawing is not a Doodad type")
|
return errors.New("SaveDoodad: current drawing is not a Doodad type")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !strings.HasSuffix(filename, extDoodad) {
|
||||||
|
filename += extDoodad
|
||||||
|
}
|
||||||
|
|
||||||
s.filename = filename
|
s.filename = filename
|
||||||
d := s.Doodad
|
d := s.Doodad
|
||||||
if d.Title == "" {
|
if d.Title == "" {
|
||||||
|
@ -228,7 +240,10 @@ func (s *EditorScene) SaveDoodad(filename string) error {
|
||||||
d.Palette = s.drawing.Palette
|
d.Palette = s.drawing.Palette
|
||||||
d.Layers[0].Chunker = s.drawing.Chunker()
|
d.Layers[0].Chunker = s.drawing.Chunker()
|
||||||
|
|
||||||
err := d.WriteJSON(s.filename)
|
// Save it to their profile directory.
|
||||||
|
filename = DoodadPath(filename)
|
||||||
|
log.Info("Write Doodad: %s", filename)
|
||||||
|
err := d.WriteJSON(filename)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user