From 5fe22198eef286d28e98311a5e12e47f8e6be1e7 Mon Sep 17 00:00:00 2001 From: Joshua Skelton Date: Sun, 3 May 2020 10:58:38 -0700 Subject: [PATCH] Initial work for saving the level when dirty --- .../dungeoneer/editor/EditorActions.java | 112 +----- .../dungeoneer/editor/EditorApplication.java | 180 ++------- .../dungeoneer/editor/file/EditorFile.java | 378 ++++++++++++++++++ .../dungeoneer/editor/file/SaveAdapter.java | 12 + .../dungeoneer/editor/file/SaveListener.java | 7 + .../editor/history/EditorHistory.java | 9 + .../dungeoneer/editor/ui/EditorUi.java | 32 +- .../editor/ui/SaveChangesDialog.java | 126 ++++++ 8 files changed, 585 insertions(+), 271 deletions(-) create mode 100644 DelvEdit/src/com/interrupt/dungeoneer/editor/file/EditorFile.java create mode 100644 DelvEdit/src/com/interrupt/dungeoneer/editor/file/SaveAdapter.java create mode 100644 DelvEdit/src/com/interrupt/dungeoneer/editor/file/SaveListener.java create mode 100644 DelvEdit/src/com/interrupt/dungeoneer/editor/ui/SaveChangesDialog.java diff --git a/DelvEdit/src/com/interrupt/dungeoneer/editor/EditorActions.java b/DelvEdit/src/com/interrupt/dungeoneer/editor/EditorActions.java index 783f2fa5..a638dc52 100644 --- a/DelvEdit/src/com/interrupt/dungeoneer/editor/EditorActions.java +++ b/DelvEdit/src/com/interrupt/dungeoneer/editor/EditorActions.java @@ -1,17 +1,13 @@ package com.interrupt.dungeoneer.editor; import com.badlogic.gdx.Gdx; -import com.badlogic.gdx.files.FileHandle; -import com.interrupt.dungeoneer.editor.ui.EditorUi; -import com.interrupt.dungeoneer.editor.ui.FilePicker; +import com.interrupt.dungeoneer.editor.file.SaveAdapter; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -import java.awt.event.WindowEvent; -import java.io.File; -import java.io.FileFilter; public class EditorActions { + public ActionListener newAction; public ActionListener saveAction; public ActionListener saveAsAction; public ActionListener openAction; @@ -63,116 +59,34 @@ public EditorActions() { } private void initActions() { + newAction = new ActionListener() { + public void actionPerformed(ActionEvent event) { + Editor.app.file.create(); + } + }; + saveAction = new ActionListener() { + @Override public void actionPerformed (ActionEvent event) { - // Invoke the save as dialog if we haven't saved anything yet - if(Editor.app.currentFileName == null || Editor.app.currentDirectory == null) { - saveAsAction.actionPerformed(event); - } - else { - Editor.app.save(Editor.app.currentDirectory + Editor.app.currentFileName); - Editor.app.setTitle(Editor.app.currentFileName); - } + Editor.app.file.save(); } }; saveAsAction = new ActionListener() { public void actionPerformed (ActionEvent event) { - class WSFilter implements FileFilter { - @Override - public boolean accept(File pathname) { - return (pathname.getName().endsWith(".bin") || pathname.getName().endsWith(".dat")); - } - } - - FileFilter wsFilter = new WSFilter(); - - if(Editor.app.currentDirectory == null) { - Editor.app.currentDirectory = new FileHandle(".").file().getAbsolutePath(); - Editor.app.currentDirectory = Editor.app.currentDirectory.substring(0, Editor.app.currentDirectory.length() - 2); - } - - FilePicker picker = FilePicker.createSaveDialog("Save Level", EditorUi.getSmallSkin(), new FileHandle(Editor.app.currentDirectory)); - picker.setFileNameEnabled(true); - picker.setNewFolderEnabled(false); - picker.setFilter(wsFilter); - - if(Editor.app.currentFileName == null) { - picker.setFileName("level.bin"); - } - else { - picker.setFileName(Editor.app.currentFileName); - } - - picker.setResultListener(new FilePicker.ResultListener() { - @Override - public boolean result(boolean success, FileHandle result) { - if(success) { - try { - Editor.app.save(result.file().getAbsolutePath()); - Editor.app.currentDirectory = result.file().getParent() + "/"; - Editor.app.currentFileName = result.name(); - - Editor.app.setTitle(Editor.app.currentFileName); - } - catch(Exception ex) { - Gdx.app.error("DelvEdit", ex.getMessage()); - } - } - return true; - } - }); - - picker.show(Editor.app.ui.getStage()); - - Editor.app.ui.showingModal = picker; - Editor.app.editorInput.resetKeys(); + Editor.app.file.saveAs(new SaveAdapter()); } }; openAction = new ActionListener() { public void actionPerformed (ActionEvent event) { - class WSFilter implements FileFilter { - @Override - public boolean accept(File path) { - String name = path.getName(); - return (name.endsWith(".dat") || name.endsWith(".png") || name.endsWith(".bin")); - } - } - FileFilter wsFilter = new WSFilter(); - - if(Editor.app.currentDirectory == null) { - Editor.app.currentDirectory = new FileHandle(".").file().getAbsolutePath(); - Editor.app.currentDirectory = Editor.app.currentDirectory.substring(0, Editor.app.currentDirectory.length() - 2); - } - - FilePicker picker = FilePicker.createLoadDialog("Open Level", EditorUi.getSmallSkin(), new FileHandle(Editor.app.currentDirectory)); - picker.setFileNameEnabled(true); - picker.setNewFolderEnabled(false); - if(Editor.app.currentFileName != null) picker.setFileName(Editor.app.currentFileName); - picker.setFilter(wsFilter); - - picker.setResultListener(new FilePicker.ResultListener() { - @Override - public boolean result(boolean success, FileHandle result) { - if(success) { - Editor.app.open(result); - } - - return true; - } - }); - - picker.show(Editor.app.ui.getStage()); - - Editor.app.ui.showingModal = picker; - Editor.app.editorInput.resetKeys(); + Editor.app.file.open(); } }; exitAction = new ActionListener() { public void actionPerformed(ActionEvent e) { - Editor.app.frame.dispatchEvent(new WindowEvent(Editor.app.frame, WindowEvent.WINDOW_CLOSING)); + Gdx.app.exit(); } }; diff --git a/DelvEdit/src/com/interrupt/dungeoneer/editor/EditorApplication.java b/DelvEdit/src/com/interrupt/dungeoneer/editor/EditorApplication.java index ebffd4bf..5cc6b024 100644 --- a/DelvEdit/src/com/interrupt/dungeoneer/editor/EditorApplication.java +++ b/DelvEdit/src/com/interrupt/dungeoneer/editor/EditorApplication.java @@ -5,7 +5,6 @@ import com.badlogic.gdx.Input.Keys; import com.badlogic.gdx.backends.lwjgl.LwjglApplication; import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration; -import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.graphics.Camera; import com.badlogic.gdx.graphics.*; import com.badlogic.gdx.graphics.Pixmap.Format; @@ -24,16 +23,14 @@ import com.badlogic.gdx.math.collision.Ray; import com.badlogic.gdx.scenes.scene2d.InputEvent; import com.badlogic.gdx.scenes.scene2d.Stage; -import com.badlogic.gdx.scenes.scene2d.ui.CheckBox; -import com.badlogic.gdx.scenes.scene2d.ui.Image; -import com.badlogic.gdx.scenes.scene2d.ui.Label; -import com.badlogic.gdx.scenes.scene2d.ui.Table; +import com.badlogic.gdx.scenes.scene2d.ui.*; import com.badlogic.gdx.scenes.scene2d.utils.ClickListener; import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable; import com.badlogic.gdx.utils.*; import com.interrupt.dungeoneer.Audio; import com.interrupt.dungeoneer.*; import com.interrupt.dungeoneer.collision.Collidor; +import com.interrupt.dungeoneer.editor.file.EditorFile; import com.interrupt.dungeoneer.editor.gfx.SurfacePickerDecal; import com.interrupt.dungeoneer.editor.gizmos.Gizmo; import com.interrupt.dungeoneer.editor.gizmos.GizmoProvider; @@ -41,6 +38,7 @@ import com.interrupt.dungeoneer.editor.selection.AdjacentTileSelectionInfo; import com.interrupt.dungeoneer.editor.selection.TileSelectionInfo; import com.interrupt.dungeoneer.editor.ui.EditorUi; +import com.interrupt.dungeoneer.editor.ui.SaveChangesDialog; import com.interrupt.dungeoneer.editor.ui.TextureRegionPicker; import com.interrupt.dungeoneer.editor.utils.LiveReload; import com.interrupt.dungeoneer.entities.*; @@ -64,7 +62,6 @@ import com.interrupt.dungeoneer.gfx.drawables.DrawableSprite; import com.interrupt.dungeoneer.interfaces.Directional; import com.interrupt.dungeoneer.serializers.KryoSerializer; -import com.interrupt.dungeoneer.tiles.ExitTile; import com.interrupt.dungeoneer.tiles.Tile; import com.interrupt.dungeoneer.tiles.Tile.TileSpaceType; import com.interrupt.helpers.FloatTuple; @@ -74,7 +71,6 @@ import com.noise.PerlinNoise; import javax.swing.*; -import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.util.HashMap; @@ -87,6 +83,7 @@ public class EditorApplication implements ApplicationListener { public Player player = null; public Level level = null; public GlRenderer renderer = null; + public EditorFile file = null; private EditorClipboard clipboard = null; @@ -234,9 +231,6 @@ public boolean isWestFloor() { protected EntityManager entityManager; - public String currentFileName; - public String currentDirectory; - boolean readRotate; Mesh cubeMesh; @@ -327,13 +321,6 @@ protected Decal newObject () { public EditorApplication() { frame = new JFrame("DelvEdit"); - frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); - frame.addWindowListener(new WindowAdapter() { - @Override - public void windowClosing(WindowEvent e) { - Editor.dispose(); - } - }); Graphics.DisplayMode defaultMode = LwjglApplicationConfiguration.getDesktopDisplayMode(); @@ -351,7 +338,33 @@ public void windowClosing(WindowEvent e) { config.addIcon("icon-32.png", Files.FileType.Internal); // 32x32 icon (Windows + Linux) config.addIcon("icon-16.png", Files.FileType.Internal); // 16x16 icon (Windows) - new LwjglApplication(this, config); + new LwjglApplication(this, config) { + public void close() { + super.exit(); + System.exit(0); + } + + @Override + public void exit() { + if (!file.isDirty()) { + close(); + } + + Dialog savePrompt = new SaveChangesDialog() { + @Override + public void onSave() { + Editor.app.file.save(); + } + + @Override + public void onDontSave() { + close(); + } + }; + + savePrompt.show(Editor.app.ui.getStage()); + } + }; } public void init(){ @@ -375,6 +388,8 @@ public void init(){ cameraController = new EditorCameraController(); cameraController.init(); + file = new EditorFile(); + StringManager.init(); Game.init(); @@ -1901,113 +1916,6 @@ public void create() { gridMesh = genGrid(level.width,level.height); } - /** Save level. */ - public void save(String filename) { - - level.preSaveCleanup(); - - // cleanup some of the tiles - for(int x = 0; x < level.width; x++) { - for(int y = 0; y < level.height; y++) { - Tile cTile = level.getTileOrNull(x, y); - if(cTile == null) { - - // if any tiles around are not solid, make this a real tile - boolean makeRealTile = false; - for(int xx = x - 1; xx <= x + 1; xx += 2) { - for(int yy = y - 1; yy <= y + 1; yy += 2) { - Tile tile = level.getTile(xx, yy); - if(!tile.renderSolid) makeRealTile = true; - } - } - - if(makeRealTile) { - Tile t = new Tile(); - t.renderSolid = true; - t.blockMotion = true; - level.setTile(x, y, t); - } - } - else { - if(cTile.wallTex == 6 && !(cTile instanceof ExitTile) && cTile.IsSolid()) { - ExitTile exitTile = new ExitTile(); - Tile.copy(cTile, exitTile); - level.setTile(x, y, exitTile); - } - } - } - } - - // write as json - if(filename.endsWith(".dat")) { - Game.toJson(level, Gdx.files.absolute(filename)); - } - else { - KryoSerializer.saveLevel(Gdx.files.absolute(filename), level); - } - } - - /** Open level. */ - public void open(FileHandle fileHandle) { - try { - currentDirectory = fileHandle.file().getParent() + "/"; - currentFileName = fileHandle.name(); - - setTitle(currentFileName); - - String file = currentFileName; - String dir = currentDirectory; - - FileHandle levelFileHandle = Gdx.files.getFileHandle(fileHandle.file().getAbsolutePath(), Files.FileType.Absolute); - if(levelFileHandle.exists()) { - setTitle(currentFileName); - - Level openLevel; - - if(file.endsWith(".png")) { - String heightFile = dir + file.replace(".png", "-height.png"); - if(!Gdx.files.getFileHandle(heightFile, Files.FileType.Absolute).exists()) { - heightFile = dir + file.replace(".png", "_height.png"); - if(!Gdx.files.getFileHandle(heightFile, Files.FileType.Absolute).exists()) { - heightFile = null; - } - } - - openLevel = new Level(); - openLevel.loadForEditor(dir + file, heightFile); - } - else if(file.endsWith(".bin")) { - openLevel = KryoSerializer.loadLevel(levelFileHandle); - openLevel.init(Source.EDITOR); - } - else { - openLevel = Game.fromJson(Level.class, levelFileHandle); - openLevel.init(Source.EDITOR); - } - - level = openLevel; - refresh(); - - /* - camX = openLevel.width / 2f; - camZ = 4.5f; - camY = openLevel.height / 2f; - */ - - cameraController.setPosition(openLevel.width / 2f, 4.5f, openLevel.height / 2f); - - history = new EditorHistory(); - Editor.options.recentlyOpenedFiles.removeValue(levelFileHandle.path(), false); - Editor.options.recentlyOpenedFiles.insert(0, levelFileHandle.path()); - - viewSelected(); - } - } - catch(Exception ex) { - Gdx.app.error("DelvEdit", ex.getMessage()); - } - } - public boolean isSelected() { return selected; } @@ -2208,7 +2116,8 @@ public void clearSelection() { ui.hideContextMenu(); } - history.saveState(level); + // NOTE: Is this needed? + //history.saveState(level); } public void toggleLights() { @@ -3782,22 +3691,6 @@ else if (entity instanceof DynamicLight) { return new Vector3(entity.collision.x, entity.collision.z / 2, entity.collision.y).len(); } - public void createNewLevel(int width, int height) { - level = new Level(width,height); - refresh(); - - float x = level.width / 2f; - float z = 4.5f; - float y = level.height / 2f; - cameraController.setPosition(x, y, z); } - - public void createdNewLevel() { - currentDirectory = null; - currentFileName = null; - setTitle("New Level"); - viewSelected(); - } - public void resizeLevel(int levelWidth, int levelHeight) { Level oldLevel = level; level = new Level(levelWidth,levelHeight); @@ -3825,7 +3718,8 @@ public void clearEntitySelection() { controlPoints.clear(); pickedControlPoint = null; - history.saveState(level); + // NOTE: Is this needed? + //history.saveState(level); ui.showEntityPropertiesMenu(true); } diff --git a/DelvEdit/src/com/interrupt/dungeoneer/editor/file/EditorFile.java b/DelvEdit/src/com/interrupt/dungeoneer/editor/file/EditorFile.java new file mode 100644 index 00000000..28482316 --- /dev/null +++ b/DelvEdit/src/com/interrupt/dungeoneer/editor/file/EditorFile.java @@ -0,0 +1,378 @@ +package com.interrupt.dungeoneer.editor.file; + +import com.badlogic.gdx.Files; +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.files.FileHandle; +import com.badlogic.gdx.scenes.scene2d.Stage; +import com.badlogic.gdx.scenes.scene2d.ui.Dialog; +import com.badlogic.gdx.utils.TimeUtils; +import com.interrupt.dungeoneer.editor.Editor; +import com.interrupt.dungeoneer.editor.history.EditorHistory; +import com.interrupt.dungeoneer.editor.ui.EditorUi; +import com.interrupt.dungeoneer.editor.ui.FilePicker; +import com.interrupt.dungeoneer.editor.ui.NewLevelDialog; +import com.interrupt.dungeoneer.editor.ui.SaveChangesDialog; +import com.interrupt.dungeoneer.game.Game; +import com.interrupt.dungeoneer.game.Level; +import com.interrupt.dungeoneer.serializers.KryoSerializer; +import com.interrupt.dungeoneer.tiles.ExitTile; +import com.interrupt.dungeoneer.tiles.Tile; + +import java.io.File; +import java.io.FileFilter; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Date; +import java.util.concurrent.TimeUnit; + +/** Class for working with the level file in editor. */ +public class EditorFile { + final String fileName; + final String directory; + final Date lastSaveDate; + byte[] historyMarker; + + public static final String defaultName = "New Level"; + public static final String defaultDirectory = "."; + + public EditorFile() { + fileName = EditorFile.defaultName; + directory = Gdx.files.local(EditorFile.defaultDirectory).path(); + lastSaveDate = new Date(TimeUtils.millis()); + historyMarker = Editor.app.history.top(); + } + + public EditorFile(FileHandle handle) { + fileName = handle.name(); + directory = handle.file().getParent(); + lastSaveDate = new Date(TimeUtils.millis()); + historyMarker = Editor.app.history.top(); + } + + public String name() { + return fileName; + } + + public String directory() { + return directory; + } + + public boolean isDirty() { + return !Arrays.equals(historyMarker, Editor.app.history.top()); + } + + /** Save file. Will prompt user for path if needed. */ + public void save() { + save(new SaveAdapter()); + } + + /** Save file. Will prompt user for path if needed. Use the SaveListener for handling user choices. */ + public void save(final SaveListener listener) { + if(Editor.app.file.name().equals(EditorFile.defaultName) && Editor.app.file.directory().equals(EditorFile.defaultDirectory)) { + saveAs(listener); + } + else { + saveInternal(); + listener.onSave(); + } + } + + /** Save file as. Will prompt user for path. */ + public void saveAs(final SaveListener listener) { + class WSFilter implements FileFilter { + @Override + public boolean accept(File pathname) { + return (pathname.getName().endsWith(".bin") || pathname.getName().endsWith(".dat")); + } + } + + FileFilter wsFilter = new WSFilter(); + + FilePicker picker = FilePicker.createSaveDialog("Save Level", EditorUi.getSmallSkin(), new FileHandle(Editor.app.file.directory())); + picker.setFileNameEnabled(true); + picker.setNewFolderEnabled(false); + picker.setFilter(wsFilter); + picker.setFileName(Editor.app.file.name()); + + picker.setResultListener(new FilePicker.ResultListener() { + @Override + public boolean result(boolean success, FileHandle result) { + if(success) { + try { + Editor.app.file = new EditorFile(result); + Editor.app.file.saveInternal(); + } + catch(Exception ex) { + Gdx.app.error("DelvEdit", ex.getMessage()); + } + + listener.onSave(); + } + else { + listener.onCancel(); + } + + return true; + } + }); + + picker.show(Editor.app.ui.getStage()); + + Editor.app.ui.showingModal = picker; + Editor.app.editorInput.resetKeys(); + } + + private void saveInternal() { + saveInternal(Paths.get(directory, fileName).toString()); + } + + private void saveInternal(String fileName) { + Editor.app.setTitle(Editor.app.file.name()); + historyMarker = Editor.app.history.top(); + + Level level = Editor.app.level; + level.preSaveCleanup(); + + // cleanup some of the tiles + for(int x = 0; x < level.width; x++) { + for(int y = 0; y < level.height; y++) { + Tile cTile = level.getTileOrNull(x, y); + if(cTile == null) { + + // if any tiles around are not solid, make this a real tile + boolean makeRealTile = false; + for(int xx = x - 1; xx <= x + 1; xx += 2) { + for(int yy = y - 1; yy <= y + 1; yy += 2) { + Tile tile = level.getTile(xx, yy); + if(!tile.renderSolid) makeRealTile = true; + } + } + + if(makeRealTile) { + Tile t = new Tile(); + t.renderSolid = true; + t.blockMotion = true; + level.setTile(x, y, t); + } + } + else { + if(cTile.wallTex == 6 && !(cTile instanceof ExitTile) && cTile.IsSolid()) { + ExitTile exitTile = new ExitTile(); + Tile.copy(cTile, exitTile); + level.setTile(x, y, exitTile); + } + } + } + } + + // write as json + if(fileName.endsWith(".dat")) { + Game.toJson(level, Gdx.files.absolute(fileName)); + } + else { + KryoSerializer.saveLevel(Gdx.files.absolute(fileName), level); + } + } + + /** Open the given file. */ + public void open(final FileHandle fileHandle) { + Dialog savePrompt = new SaveChangesDialog() { + @Override + public void onSave() { + Editor.app.file.save(new SaveAdapter() { + @Override + public void onSave() { + openInternal(fileHandle); + } + }); + } + + @Override + public void onDontSave() { + openInternal(fileHandle); + } + }; + + savePrompt.show(Editor.app.ui.getStage()); + } + + /** Prompt user then open file. */ + public void open() { + Dialog savePrompt = new SaveChangesDialog() { + @Override + public void onSave() { + Editor.app.file.save(new SaveAdapter() { + @Override + public void onSave() { + promptOpenFile(); + } + }); + } + + @Override + public void onDontSave() { + promptOpenFile(); + } + }; + + savePrompt.show(Editor.app.ui.getStage()); + } + + private void promptOpenFile() { + class WSFilter implements FileFilter { + @Override + public boolean accept(File path) { + String name = path.getName(); + return (name.endsWith(".dat") || name.endsWith(".png") || name.endsWith(".bin")); + } + } + FileFilter wsFilter = new WSFilter(); + + if(Editor.app.file.directory() == null) { + Editor.app.file = new EditorFile(new FileHandle(".")); + } + + FilePicker picker = FilePicker.createLoadDialog("Open Level", EditorUi.getSmallSkin(), new FileHandle(Editor.app.file.directory())); + picker.setFileNameEnabled(true); + picker.setNewFolderEnabled(false); + if(Editor.app.file.name() != null) picker.setFileName(Editor.app.file.name()); + picker.setFilter(wsFilter); + + picker.setResultListener(new FilePicker.ResultListener() { + @Override + public boolean result(boolean success, FileHandle result) { + if(success) { + openInternal(result); + } + + return true; + } + }); + + picker.show(Editor.app.ui.getStage()); + + Editor.app.ui.showingModal = picker; + Editor.app.editorInput.resetKeys(); + } + + private void openInternal(FileHandle fileHandle) { + try { + // NOTE: You must access the new Editor file via Editor.app.file + // for the rest of this method. + Editor.app.file = new EditorFile(fileHandle); + Editor.app.setTitle(Editor.app.file.name()); + + String fileName = Editor.app.file.name(); + String dir = Editor.app.file.directory(); + + FileHandle levelFileHandle = Gdx.files.getFileHandle(fileHandle.file().getAbsolutePath(), Files.FileType.Absolute); + if(levelFileHandle.exists()) { + Editor.app.setTitle(Editor.app.file.name()); + + Level openLevel; + + if(fileName.endsWith(".png")) { + String heightFile = dir + fileName.replace(".png", "-height.png"); + if(!Gdx.files.getFileHandle(heightFile, Files.FileType.Absolute).exists()) { + heightFile = dir + fileName.replace(".png", "_height.png"); + if(!Gdx.files.getFileHandle(heightFile, Files.FileType.Absolute).exists()) { + heightFile = null; + } + } + + openLevel = new Level(); + openLevel.loadForEditor(dir + fileName, heightFile); + } + else if(fileName.endsWith(".bin")) { + openLevel = KryoSerializer.loadLevel(levelFileHandle); + openLevel.init(Level.Source.EDITOR); + } + else { + openLevel = Game.fromJson(Level.class, levelFileHandle); + openLevel.init(Level.Source.EDITOR); + } + + Editor.app.level = openLevel; + Editor.app.refresh(); + Editor.app.cameraController.setPosition(openLevel.width / 2f, 4.5f, openLevel.height / 2f); + + Editor.app.history = new EditorHistory(); + Editor.app.file.historyMarker = Editor.app.history.top(); + + Editor.options.recentlyOpenedFiles.removeValue(levelFileHandle.path(), false); + Editor.options.recentlyOpenedFiles.insert(0, levelFileHandle.path()); + + Editor.app.viewSelected(); + } + } + catch(Exception ex) { + Gdx.app.error("DelvEdit", ex.getMessage()); + } + } + + /** Prompt user and create a new level. */ + public void create() { + final Stage stage = Editor.app.ui.getStage(); + + final NewLevelDialog newLevelDialog = new NewLevelDialog(EditorUi.getSmallSkin()) { + @Override + protected void result(Object object) { + if((Boolean)object) { + createInternal(getLevelWidth(), getLevelHeight()); + } + } + }; + + SaveChangesDialog savePrompt = new SaveChangesDialog() { + @Override + public void onSave() { + Editor.app.file.save(new SaveAdapter() { + @Override + public void onSave() { + newLevelDialog.show(stage); + Editor.app.editorInput.resetKeys(); + } + }); + } + + @Override + public void onDontSave() { + newLevelDialog.show(stage); + Editor.app.editorInput.resetKeys(); + } + }; + + savePrompt.show(stage); + } + + private void createInternal(int width, int height) { + Editor.app.file = new EditorFile(); + Editor.app.setTitle(Editor.app.file.name()); + Editor.app.level = new Level(width,height); + Editor.app.refresh(); + historyMarker = Editor.app.history.top(); + + Editor.app.cameraController.setPosition( + Editor.app.level.width / 2f, + Editor.app.level.height / 2f, + 4.5f + ); + Editor.app.viewSelected(); + } + + public long getMillisSinceLastSave() { + return Math.abs(TimeUtils.millis() - lastSaveDate.getTime()); + } + + public long getSecondsSinceLastSave() { + return TimeUnit.SECONDS.convert(getMillisSinceLastSave(), TimeUnit.MILLISECONDS); + } + + public long getMinutesSinceLastSave() { + return TimeUnit.MINUTES.convert(getMillisSinceLastSave(), TimeUnit.MILLISECONDS); + } + + public long getHoursSinceLastSave() { + return TimeUnit.HOURS.convert(getMillisSinceLastSave(), TimeUnit.MILLISECONDS); + } +} diff --git a/DelvEdit/src/com/interrupt/dungeoneer/editor/file/SaveAdapter.java b/DelvEdit/src/com/interrupt/dungeoneer/editor/file/SaveAdapter.java new file mode 100644 index 00000000..dcc3b5bf --- /dev/null +++ b/DelvEdit/src/com/interrupt/dungeoneer/editor/file/SaveAdapter.java @@ -0,0 +1,12 @@ +package com.interrupt.dungeoneer.editor.file; + +public class SaveAdapter implements SaveListener { + @Override + public void onSave() {} + + @Override + public void onCancel() {} + + @Override + public void onDontSave() {} +} diff --git a/DelvEdit/src/com/interrupt/dungeoneer/editor/file/SaveListener.java b/DelvEdit/src/com/interrupt/dungeoneer/editor/file/SaveListener.java new file mode 100644 index 00000000..0d356fad --- /dev/null +++ b/DelvEdit/src/com/interrupt/dungeoneer/editor/file/SaveListener.java @@ -0,0 +1,7 @@ +package com.interrupt.dungeoneer.editor.file; + +public interface SaveListener { + void onSave(); + void onCancel(); + void onDontSave(); +} diff --git a/DelvEdit/src/com/interrupt/dungeoneer/editor/history/EditorHistory.java b/DelvEdit/src/com/interrupt/dungeoneer/editor/history/EditorHistory.java index ae43429b..ba03748b 100644 --- a/DelvEdit/src/com/interrupt/dungeoneer/editor/history/EditorHistory.java +++ b/DelvEdit/src/com/interrupt/dungeoneer/editor/history/EditorHistory.java @@ -54,4 +54,13 @@ public Level redo() { if(pos > 0) pos--; return KryoSerializer.loadLevel(history.get(pos)); } + + private final byte[] nullHistoryState = new byte[]{}; + public byte[] top() { + if (history.isEmpty()) { + return nullHistoryState; + } + + return history.peek(); + } } \ No newline at end of file diff --git a/DelvEdit/src/com/interrupt/dungeoneer/editor/ui/EditorUi.java b/DelvEdit/src/com/interrupt/dungeoneer/editor/ui/EditorUi.java index 5e56dd17..ba8578e3 100644 --- a/DelvEdit/src/com/interrupt/dungeoneer/editor/ui/EditorUi.java +++ b/DelvEdit/src/com/interrupt/dungeoneer/editor/ui/EditorUi.java @@ -15,12 +15,7 @@ import com.badlogic.gdx.utils.viewport.Viewport; import com.interrupt.api.steam.SteamApi; import com.interrupt.dungeoneer.editor.*; -import com.interrupt.dungeoneer.editor.ui.menu.DynamicMenuItem; -import com.interrupt.dungeoneer.editor.ui.menu.DynamicMenuItemAction; -import com.interrupt.dungeoneer.editor.ui.menu.MenuAccelerator; -import com.interrupt.dungeoneer.editor.ui.menu.MenuItem; -import com.interrupt.dungeoneer.editor.ui.menu.Scene2dMenu; -import com.interrupt.dungeoneer.editor.ui.menu.Scene2dMenuBar; +import com.interrupt.dungeoneer.editor.ui.menu.*; import com.interrupt.dungeoneer.entities.Entity; import com.interrupt.dungeoneer.game.Game; import com.interrupt.dungeoneer.game.Level; @@ -49,7 +44,6 @@ public class EditorUi { public Actor showingModal; ActionListener resizeWindowAction; - ActionListener newWindowAction; ActionListener pickAction; ActionListener uploadModAction; ActionListener setThemeAction; @@ -143,26 +137,6 @@ public EditorUi() { mainTable.setFillParent(true); mainTable.align(Align.left | Align.top); - // action listener for the new level dialog - newWindowAction = new ActionListener() { - public void actionPerformed(ActionEvent event) { - NewLevelDialog newLevelDialog = new NewLevelDialog(smallSkin) { - @Override - protected void result(Object object) { - if((Boolean)object) { - Editor.app.createNewLevel(getLevelWidth(), getLevelHeight()); - Editor.app.createdNewLevel(); - } - } - }; - - newLevelDialog.show(stage); - - // Dialog captures input, reset to a good state. - Editor.app.editorInput.resetKeys(); - } - }; - resizeWindowAction = new ActionListener() { public void actionPerformed(ActionEvent event) { NewLevelDialog newLevelDialog = new NewLevelDialog(smallSkin) { @@ -246,7 +220,7 @@ public void updateMenuItem(MenuItem item) { @Override public void actionPerformed(ActionEvent e) { FileHandle fh = Gdx.files.absolute(recentFile); - Editor.app.open(fh); + Editor.app.file.open(fh); } }) ); @@ -279,7 +253,7 @@ public void actionPerformed(ActionEvent e) { // make the menu bar menuBar = new Scene2dMenuBar(smallSkin); menuBar.addItem(new MenuItem("File", smallSkin) - .addItem(new MenuItem("New", smallSkin, newWindowAction).setAccelerator(new MenuAccelerator(Keys.N, true, false))) + .addItem(new MenuItem("New", smallSkin, actions.newAction).setAccelerator(new MenuAccelerator(Keys.N, true, false))) .addItem(new MenuItem("Open", smallSkin, actions.openAction).setAccelerator(new MenuAccelerator(Keys.O, true, false))) .addItem(openRecent) .addSeparator() diff --git a/DelvEdit/src/com/interrupt/dungeoneer/editor/ui/SaveChangesDialog.java b/DelvEdit/src/com/interrupt/dungeoneer/editor/ui/SaveChangesDialog.java new file mode 100644 index 00000000..9987eea1 --- /dev/null +++ b/DelvEdit/src/com/interrupt/dungeoneer/editor/ui/SaveChangesDialog.java @@ -0,0 +1,126 @@ +package com.interrupt.dungeoneer.editor.ui; + +import com.badlogic.gdx.scenes.scene2d.Stage; +import com.badlogic.gdx.scenes.scene2d.ui.Dialog; +import com.badlogic.gdx.scenes.scene2d.ui.Label; +import com.badlogic.gdx.scenes.scene2d.ui.Table; +import com.badlogic.gdx.scenes.scene2d.ui.TextButton; +import com.badlogic.gdx.utils.Align; +import com.interrupt.dungeoneer.editor.Editor; + +public class SaveChangesDialog extends Dialog { + public enum SaveChangesDialogResult { + SAVE, + CANCEL, + DONT_SAVE + } + + public SaveChangesDialog() { + super("Save Changes?", EditorUi.getSmallSkin()); + + Table contentTable = getContentTable(); + contentTable.align(Align.left); + + String docName = Editor.app.file.name(); + Label warningLabel = new Label("Do you want to save the changes you made to " + docName + "?", EditorUi.getMediumSkin()); + warningLabel.setWrap(true); + contentTable.add(warningLabel).width(425).align(Align.left); + contentTable.row(); + + if (!Editor.app.file.directory().equals("")) { + Label infoLabel = new Label("The last save was " + timeSinceLastSave() + " ago.", EditorUi.getSmallSkin()); + contentTable.add(infoLabel).width(425).align(Align.left); + } + else { + contentTable.add(new Label("", EditorUi.getSmallSkin())); + } + + Table buttonTable = getButtonTable(); + + TextButton saveButton = new TextButton("Save", EditorUi.getSmallSkin()); + buttonTable.add(saveButton); + setObject(saveButton, SaveChangesDialogResult.SAVE); + + TextButton cancelButton = new TextButton("Cancel", EditorUi.getSmallSkin()); + buttonTable.add(cancelButton).padRight(15); + setObject(cancelButton, SaveChangesDialogResult.CANCEL); + + TextButton dontSaveButton = new TextButton("Don't Save", EditorUi.getSmallSkin()); + buttonTable.add(dontSaveButton); + setObject(dontSaveButton, SaveChangesDialogResult.DONT_SAVE); + } + + @Override + public Dialog show (Stage stage) { + if (!Editor.app.file.isDirty()) { + onDontSave(); + return this; + } + + return super.show(stage); + } + + @Override + public void result(Object object) { + SaveChangesDialogResult result = (SaveChangesDialogResult)object; + if (result == null) { + return; + } + + switch (result) { + case SAVE: + onSave(); + break; + + case CANCEL: + onCancel(); + break; + + case DONT_SAVE: + onDontSave(); + break; + } + } + + public void onSave() {} + public void onCancel() {} + public void onDontSave() {} + + @Override + public float getPrefWidth() { + return 450; + } + + @Override + public float getPrefHeight() { + return 200; + } + + private String timeSinceLastSave() { + long hours = Editor.app.file.getHoursSinceLastSave(); + + if (hours > 0) { + long minutes = Editor.app.file.getMinutesSinceLastSave() % 60; + + return hours + " " + pluralize("hour", hours) + " and " + minutes + pluralize("minutes", minutes); + } + + long minutes = Editor.app.file.getMinutesSinceLastSave(); + + if (minutes > 0) { + return minutes + " " + pluralize("minute", minutes); + } + + long seconds = Editor.app.file.getSecondsSinceLastSave(); + + return seconds + " " + pluralize("second", seconds); + } + + private String pluralize(String base, long amount) { + if (amount == 1) { + return base; + } + + return base + "s"; + } +}