From 2bfcc3b67a4b2cb41ddb4bee9b322177bbdf35c1 Mon Sep 17 00:00:00 2001 From: AndyTechGuy <44126397+AndyTechGuy@users.noreply.github.com> Date: Tue, 13 Nov 2018 22:52:58 -0330 Subject: [PATCH 1/7] Add a helper function for renaming recordings Adds a helper function to RecordScreen that renames the recording to a provided title. This commit does not integrate this function into the UI. --- .../nui/layers/mainMenu/RecordScreen.java | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/engine/src/main/java/org/terasology/rendering/nui/layers/mainMenu/RecordScreen.java b/engine/src/main/java/org/terasology/rendering/nui/layers/mainMenu/RecordScreen.java index 201171433eb..fd75a0c00c0 100644 --- a/engine/src/main/java/org/terasology/rendering/nui/layers/mainMenu/RecordScreen.java +++ b/engine/src/main/java/org/terasology/rendering/nui/layers/mainMenu/RecordScreen.java @@ -34,6 +34,7 @@ import org.terasology.rendering.nui.widgets.UIButton; import java.io.File; +import java.io.IOException; import java.nio.file.Path; import java.util.Objects; import java.util.stream.Stream; @@ -110,10 +111,14 @@ protected void initWidgets() { private void loadGame(GameInfo item) { try { final GameManifest manifest = item.getManifest(); - copySaveDirectoryToRecordingLibrary(manifest.getTitle()); - recordAndReplayUtils.setGameTitle(manifest.getTitle()); + + String oldTitle = manifest.getTitle(); + String newTitle = oldTitle; + + copySaveDirectoryToRecordingLibrary(oldTitle, newTitle); + recordAndReplayUtils.setGameTitle(newTitle); config.getWorldGeneration().setDefaultSeed(manifest.getSeed()); - config.getWorldGeneration().setWorldTitle(manifest.getTitle()); + config.getWorldGeneration().setWorldTitle(newTitle); CoreRegistry.get(GameEngine.class).changeState(new StateLoading(manifest, NetworkMode.NONE)); } catch (Exception e) { logger.error("Failed to load saved game", e); @@ -121,17 +126,27 @@ private void loadGame(GameInfo item) { } } - private void copySaveDirectoryToRecordingLibrary(String gameTitle) { - File saveDirectory = new File(PathManager.getInstance().getSavePath(gameTitle).toString()); - Path destinationPath = PathManager.getInstance().getRecordingPath(gameTitle); + private void copySaveDirectoryToRecordingLibrary(String oldTitle, String newTitle) { + File saveDirectory = new File(PathManager.getInstance().getSavePath(oldTitle).toString()); + Path destinationPath = PathManager.getInstance().getRecordingPath(newTitle); File destDirectory = new File(destinationPath.toString()); try { FileUtils.copyDirectoryStructure(saveDirectory, destDirectory); + if(oldTitle != newTitle) { + rewriteGameTitle(destinationPath, newTitle); + } } catch (Exception e) { logger.error("Error trying to copy the save directory:", e); } } + private void rewriteGameTitle(Path destinationPath, String newTitle) throws IOException { + GameManifest manifest = GameManifest.load(destinationPath.resolve(GameManifest.DEFAULT_FILE_NAME)); + manifest.setTitle(newTitle); + GameManifest.save(destinationPath.resolve(GameManifest.DEFAULT_FILE_NAME), manifest); + } + + void setRecordAndReplayUtils(RecordAndReplayUtils recordAndReplayUtils) { this.recordAndReplayUtils = recordAndReplayUtils; } From 44544d6c2c2cb53098ef14e1f1b7dd3296785de9 Mon Sep 17 00:00:00 2001 From: AndyTechGuy <44126397+AndyTechGuy@users.noreply.github.com> Date: Thu, 15 Nov 2018 19:24:00 -0330 Subject: [PATCH 2/7] Add popup to RecordScreen to rename conflicting recordings Adds a NUI popup to RecordScreen to implement the helper function to rename a recording. Checks for if a recording with the same name already exists, and if so, will prompt the user to enter a different name for the recording. --- .../nui/layers/mainMenu/RecordScreen.java | 60 ++++++++++++++-- .../nui/layers/mainMenu/ShortEntryPopup.java | 57 +++++++++++++++ .../assets/ui/menu/shortEntryPopup.ui | 72 +++++++++++++++++++ 3 files changed, 183 insertions(+), 6 deletions(-) create mode 100644 engine/src/main/java/org/terasology/rendering/nui/layers/mainMenu/ShortEntryPopup.java create mode 100644 engine/src/main/resources/assets/ui/menu/shortEntryPopup.ui diff --git a/engine/src/main/java/org/terasology/rendering/nui/layers/mainMenu/RecordScreen.java b/engine/src/main/java/org/terasology/rendering/nui/layers/mainMenu/RecordScreen.java index fd75a0c00c0..cb98e7341dd 100644 --- a/engine/src/main/java/org/terasology/rendering/nui/layers/mainMenu/RecordScreen.java +++ b/engine/src/main/java/org/terasology/rendering/nui/layers/mainMenu/RecordScreen.java @@ -16,6 +16,7 @@ package org.terasology.rendering.nui.layers.mainMenu; import org.codehaus.plexus.util.FileUtils; +import org.codehaus.plexus.util.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.terasology.assets.ResourceUrn; @@ -29,6 +30,7 @@ import org.terasology.recording.RecordAndReplayUtils; import org.terasology.registry.CoreRegistry; import org.terasology.registry.In; +import org.terasology.rendering.nui.databinding.Binding; import org.terasology.rendering.nui.layers.mainMenu.savedGames.GameInfo; import org.terasology.rendering.nui.layers.mainMenu.savedGames.GameProvider; import org.terasology.rendering.nui.widgets.UIButton; @@ -76,7 +78,9 @@ public void initialise() { load.subscribe(button -> { final GameInfo gameInfo = getGameInfos().getSelection(); if (gameInfo != null) { - loadGame(gameInfo); + if (!rewriteCheck(gameInfo)) { + loadGame(gameInfo); + } } }); @@ -109,13 +113,14 @@ protected void initWidgets() { } private void loadGame(GameInfo item) { + loadGame(item, item.getManifest().getTitle()); + } + + private void loadGame(GameInfo item, String newTitle) { try { final GameManifest manifest = item.getManifest(); - String oldTitle = manifest.getTitle(); - String newTitle = oldTitle; - - copySaveDirectoryToRecordingLibrary(oldTitle, newTitle); + copySaveDirectoryToRecordingLibrary(manifest.getTitle(), newTitle); recordAndReplayUtils.setGameTitle(newTitle); config.getWorldGeneration().setDefaultSeed(manifest.getSeed()); config.getWorldGeneration().setWorldTitle(newTitle); @@ -132,7 +137,7 @@ private void copySaveDirectoryToRecordingLibrary(String oldTitle, String newTitl File destDirectory = new File(destinationPath.toString()); try { FileUtils.copyDirectoryStructure(saveDirectory, destDirectory); - if(oldTitle != newTitle) { + if (oldTitle != newTitle) { rewriteGameTitle(destinationPath, newTitle); } } catch (Exception e) { @@ -140,12 +145,55 @@ private void copySaveDirectoryToRecordingLibrary(String oldTitle, String newTitl } } + // TODO: Translation strings for rename prompt + private boolean rewriteCheck(GameInfo item) { + if (doesRecordingExist(item.getManifest().getTitle())) { + final ShortEntryPopup popup = getManager().createScreen(ShortEntryPopup.ASSET_URI, ShortEntryPopup.class); + popup.setTitle("Rename Recording"); + popup.setMessage("A recording with this name has already been saved! Please set a new name for this recording."); + popup.bindInput(new Binding() { + @Override + public String get() { + return null; + } + + @Override + public void set(String value) { + if (!isNameValid(value)) { + popup.setMessage("Invalid File Name! Please set a different name for this recording."); + getManager().pushScreen(popup); + return; + } + loadGame(item, value); + } + }); + getManager().pushScreen(popup); + + return true; + } + return false; + } + private void rewriteGameTitle(Path destinationPath, String newTitle) throws IOException { GameManifest manifest = GameManifest.load(destinationPath.resolve(GameManifest.DEFAULT_FILE_NAME)); manifest.setTitle(newTitle); GameManifest.save(destinationPath.resolve(GameManifest.DEFAULT_FILE_NAME), manifest); } + private boolean doesRecordingExist(String name) { + Path destinationPath = PathManager.getInstance().getRecordingPath(name); + return FileUtils.fileExists(destinationPath.toString()); + } + + private boolean isNameValid(String name) { + if (StringUtils.isBlank(name)) { + return false; + } + if (doesRecordingExist(name)) { + return false; + } + return true; + } void setRecordAndReplayUtils(RecordAndReplayUtils recordAndReplayUtils) { this.recordAndReplayUtils = recordAndReplayUtils; diff --git a/engine/src/main/java/org/terasology/rendering/nui/layers/mainMenu/ShortEntryPopup.java b/engine/src/main/java/org/terasology/rendering/nui/layers/mainMenu/ShortEntryPopup.java new file mode 100644 index 00000000000..4b31d519c8f --- /dev/null +++ b/engine/src/main/java/org/terasology/rendering/nui/layers/mainMenu/ShortEntryPopup.java @@ -0,0 +1,57 @@ +/* + * Copyright 2018 MovingBlocks + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.terasology.rendering.nui.layers.mainMenu; + +import org.terasology.assets.ResourceUrn; +import org.terasology.rendering.nui.CoreScreenLayer; +import org.terasology.rendering.nui.WidgetUtil; +import org.terasology.rendering.nui.databinding.Binding; +import org.terasology.rendering.nui.widgets.*; + +public class ShortEntryPopup extends CoreScreenLayer { + public static final ResourceUrn ASSET_URI = new ResourceUrn("engine:shortEntryPopup!instance"); + + private Binding inputBinding; + + @Override + public void initialise() { + WidgetUtil.trySubscribe(this, "enterButton", button -> enterPressed()); + WidgetUtil.trySubscribe(this, "returnButton", button -> returnPressed()); + } + + private void enterPressed() { + if (inputBinding != null) { + inputBinding.set(find("nameInput", UIText.class).getText()); + } + getManager().popScreen(); + } + + private void returnPressed() { + getManager().popScreen(); + } + + public void setTitle(String title) { + find("title", UILabel.class).setText(title); + } + + public void setMessage(String message) { + find("message", UILabel.class).setText(message); + } + + public void bindInput(Binding binding) { + this.inputBinding = binding; + } +} diff --git a/engine/src/main/resources/assets/ui/menu/shortEntryPopup.ui b/engine/src/main/resources/assets/ui/menu/shortEntryPopup.ui new file mode 100644 index 00000000000..d347e709124 --- /dev/null +++ b/engine/src/main/resources/assets/ui/menu/shortEntryPopup.ui @@ -0,0 +1,72 @@ +{ + "type": "shortEntryPopup", + "skin": "messageBox", + "contents": { + "type": "relativeLayout", + "contents": [ + { + "type": "UIBox", + "layoutInfo": { + "width": 500, + "use-content-height": true, + "position-horizontal-center": {}, + "position-vertical-center": {} + }, + "content": { + "type": "ColumnLayout", + "columns": 1, + "verticalSpacing": 8, + "contents": [ + { + "type": "UILabel", + "id": "title", + "family": "title" + }, + { + "type": "UISpace", + "size": [ + 1, + 16 + ] + }, + { + "type": "UILabel", + "id": "message" + }, + { + "type": "UISpace", + "size": [ + 1, + 16 + ] + }, + { + "type": "UIText", + "id": "nameInput" + }, + { + "type": "RowLayout", + "horizontalSpacing": 4, + "contents": [ + { + "type": "UIButton", + "id": "enterButton", + "text": "${engine:menu#dialog-ok}" + }, + { + "type": "UIButton", + "id": "returnButton", + "text": "${engine:menu#dialog-cancel}" + } + ], + "layoutInfo": { + "width": 200, + "position-right": {} + } + } + ] + } + } + ] + } +} From a6f2247a5e81bd7899190ec24853bc4865a15f14 Mon Sep 17 00:00:00 2001 From: AndyTechGuy <44126397+AndyTechGuy@users.noreply.github.com> Date: Thu, 15 Nov 2018 23:33:15 -0330 Subject: [PATCH 3/7] Make recording naming mandatory, and turn rename prompt into a Screen. This commit changes the behavior of recording renaming, from requesting only when there is a name conflict, to requesting upon every new recording. Because the rename prompt occurs on every recording, the popup has been changed to a separate screen altogether. Many functions from RecordScreen have been transplanted to accomodate this change. --- .../layers/mainMenu/NameRecordingScreen.java | 152 ++++++++++++++++++ .../nui/layers/mainMenu/RecordScreen.java | 108 ++----------- ...rtEntryPopup.ui => nameRecordingScreen.ui} | 28 ++-- 3 files changed, 182 insertions(+), 106 deletions(-) create mode 100644 engine/src/main/java/org/terasology/rendering/nui/layers/mainMenu/NameRecordingScreen.java rename engine/src/main/resources/assets/ui/menu/{shortEntryPopup.ui => nameRecordingScreen.ui} (75%) diff --git a/engine/src/main/java/org/terasology/rendering/nui/layers/mainMenu/NameRecordingScreen.java b/engine/src/main/java/org/terasology/rendering/nui/layers/mainMenu/NameRecordingScreen.java new file mode 100644 index 00000000000..c4478ac4baf --- /dev/null +++ b/engine/src/main/java/org/terasology/rendering/nui/layers/mainMenu/NameRecordingScreen.java @@ -0,0 +1,152 @@ +/* + * Copyright 2018 MovingBlocks + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.terasology.rendering.nui.layers.mainMenu; + +import org.codehaus.plexus.util.FileUtils; +import org.codehaus.plexus.util.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.terasology.assets.ResourceUrn; +import org.terasology.config.Config; +import org.terasology.engine.GameEngine; +import org.terasology.engine.modes.StateLoading; +import org.terasology.engine.paths.PathManager; +import org.terasology.game.GameManifest; +import org.terasology.network.NetworkMode; +import org.terasology.recording.RecordAndReplayUtils; +import org.terasology.registry.CoreRegistry; +import org.terasology.registry.In; +import org.terasology.rendering.nui.CoreScreenLayer; +import org.terasology.rendering.nui.animation.MenuAnimationSystems; +import org.terasology.rendering.nui.databinding.Binding; +import org.terasology.rendering.nui.layers.mainMenu.savedGames.GameInfo; +import org.terasology.rendering.nui.widgets.UIButton; +import org.terasology.rendering.nui.widgets.UILabel; +import org.terasology.rendering.nui.widgets.UIText; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; + +public class NameRecordingScreen extends CoreScreenLayer { + public static final ResourceUrn ASSET_URI = new ResourceUrn("engine:nameRecordingScreen!instance"); + + private static final Logger logger = LoggerFactory.getLogger(NameRecordingScreen.class); + + @In + protected Config config; + + private GameInfo gameInfo; + + private RecordAndReplayUtils recordAndReplayUtils; + + // widgets + private UILabel title; + private UILabel description; + private UIText nameInput; + private UIButton enter; + private UIButton cancel; + + @Override + public void initialise() { + setAnimationSystem(MenuAnimationSystems.createDefaultSwipeAnimation()); + + initWidgets(); + + // TODO: Translation strings for rename prompt + title.setText("Name Recording"); + description.setText("Set a unique name for this recording."); + + enter.subscribe(button -> enterPressed()); + + cancel.subscribe(button -> cancelPressed()); + } + + private void initWidgets() { + title = find("title", UILabel.class); + description = find("description", UILabel.class); + nameInput = find("nameInput", UIText.class); + enter = find("enterButton", UIButton.class); + cancel = find("cancelButton", UIButton.class); + } + + private void enterPressed() { // TODO: More translation strings + if(!isNameValid(nameInput.getText())) { + description.setText("This name is blank, or has disallowed characters in it! Please set a different name."); + return; + } + if(doesRecordingExist(nameInput.getText())) { + description.setText("This name is already taken! Please set a different name."); + return; + } + + loadGame(nameInput.getText()); + } + + private void cancelPressed() { + triggerBackAnimation(); + } + + private void loadGame(String newTitle) { + try { + final GameManifest manifest = gameInfo.getManifest(); + + copySaveDirectoryToRecordingLibrary(manifest.getTitle(), newTitle); + recordAndReplayUtils.setGameTitle(newTitle); + config.getWorldGeneration().setDefaultSeed(manifest.getSeed()); + config.getWorldGeneration().setWorldTitle(newTitle); + CoreRegistry.get(GameEngine.class).changeState(new StateLoading(manifest, NetworkMode.NONE)); + } catch (Exception e) { + logger.error("Failed to load saved game", e); + getManager().pushScreen(MessagePopup.ASSET_URI, MessagePopup.class).setMessage("Error Loading Game", e.getMessage()); + } + } + + private void copySaveDirectoryToRecordingLibrary(String oldTitle, String newTitle) { + File saveDirectory = new File(PathManager.getInstance().getSavePath(oldTitle).toString()); + Path destinationPath = PathManager.getInstance().getRecordingPath(newTitle); + File destDirectory = new File(destinationPath.toString()); + try { + FileUtils.copyDirectoryStructure(saveDirectory, destDirectory); + rewriteRecordingTitle(destinationPath, newTitle); + } catch (Exception e) { + logger.error("Error trying to copy the save directory:", e); + } + } + + private void rewriteRecordingTitle(Path destinationPath, String newTitle) throws IOException { + GameManifest manifest = GameManifest.load(destinationPath.resolve(GameManifest.DEFAULT_FILE_NAME)); + manifest.setTitle(newTitle); + GameManifest.save(destinationPath.resolve(GameManifest.DEFAULT_FILE_NAME), manifest); + } + + private boolean isNameValid(String name) { // In the future, this probably also check for invalid file characters (?, ", .) etc. + return !StringUtils.isBlank(name); // newGameScreen does it this way, so it should be fine..? + } + + private boolean doesRecordingExist(String name) { + Path destinationPath = PathManager.getInstance().getRecordingPath(name); + return FileUtils.fileExists(destinationPath.toString()); + } + + public void setRecordAndReplayUtils(RecordAndReplayUtils recordAndReplayUtils) { + this.recordAndReplayUtils = recordAndReplayUtils; + } + + public void setGameInfo(GameInfo gameInfo) { + this.gameInfo = gameInfo; + } +} diff --git a/engine/src/main/java/org/terasology/rendering/nui/layers/mainMenu/RecordScreen.java b/engine/src/main/java/org/terasology/rendering/nui/layers/mainMenu/RecordScreen.java index cb98e7341dd..263e5b3ac79 100644 --- a/engine/src/main/java/org/terasology/rendering/nui/layers/mainMenu/RecordScreen.java +++ b/engine/src/main/java/org/terasology/rendering/nui/layers/mainMenu/RecordScreen.java @@ -15,29 +15,19 @@ */ package org.terasology.rendering.nui.layers.mainMenu; -import org.codehaus.plexus.util.FileUtils; -import org.codehaus.plexus.util.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.terasology.assets.ResourceUrn; -import org.terasology.engine.GameEngine; -import org.terasology.engine.modes.StateLoading; import org.terasology.engine.paths.PathManager; -import org.terasology.game.GameManifest; -import org.terasology.network.NetworkMode; import org.terasology.recording.RecordAndReplayCurrentStatus; import org.terasology.recording.RecordAndReplayStatus; import org.terasology.recording.RecordAndReplayUtils; -import org.terasology.registry.CoreRegistry; import org.terasology.registry.In; -import org.terasology.rendering.nui.databinding.Binding; +import org.terasology.rendering.nui.animation.MenuAnimationSystems; import org.terasology.rendering.nui.layers.mainMenu.savedGames.GameInfo; import org.terasology.rendering.nui.layers.mainMenu.savedGames.GameProvider; import org.terasology.rendering.nui.widgets.UIButton; -import java.io.File; -import java.io.IOException; -import java.nio.file.Path; import java.util.Objects; import java.util.stream.Stream; @@ -59,28 +49,29 @@ public class RecordScreen extends SelectionScreen { private UIButton load; private UIButton close; - @Override public void initialise() { + setAnimationSystem(MenuAnimationSystems.createDefaultSwipeAnimation()); + initWidgets(); if (isValidScreen()) { initSaveGamePathWidget(PathManager.getInstance().getSavesPath()); + NameRecordingScreen nameRecordingScreen = getManager().createScreen(NameRecordingScreen.ASSET_URI, NameRecordingScreen.class); + getGameInfos().subscribeSelection((widget, item) -> { load.setEnabled(item != null); updateDescription(item); }); - getGameInfos().subscribe((widget, item) -> loadGame(item)); + getGameInfos().subscribe((widget, item) -> launchRename(nameRecordingScreen, item)); load.subscribe(button -> { final GameInfo gameInfo = getGameInfos().getSelection(); if (gameInfo != null) { - if (!rewriteCheck(gameInfo)) { - loadGame(gameInfo); - } + launchRename(nameRecordingScreen, gameInfo); } }); @@ -112,87 +103,10 @@ protected void initWidgets() { close = find("close", UIButton.class); } - private void loadGame(GameInfo item) { - loadGame(item, item.getManifest().getTitle()); - } - - private void loadGame(GameInfo item, String newTitle) { - try { - final GameManifest manifest = item.getManifest(); - - copySaveDirectoryToRecordingLibrary(manifest.getTitle(), newTitle); - recordAndReplayUtils.setGameTitle(newTitle); - config.getWorldGeneration().setDefaultSeed(manifest.getSeed()); - config.getWorldGeneration().setWorldTitle(newTitle); - CoreRegistry.get(GameEngine.class).changeState(new StateLoading(manifest, NetworkMode.NONE)); - } catch (Exception e) { - logger.error("Failed to load saved game", e); - getManager().pushScreen(MessagePopup.ASSET_URI, MessagePopup.class).setMessage("Error Loading Game", e.getMessage()); - } - } - - private void copySaveDirectoryToRecordingLibrary(String oldTitle, String newTitle) { - File saveDirectory = new File(PathManager.getInstance().getSavePath(oldTitle).toString()); - Path destinationPath = PathManager.getInstance().getRecordingPath(newTitle); - File destDirectory = new File(destinationPath.toString()); - try { - FileUtils.copyDirectoryStructure(saveDirectory, destDirectory); - if (oldTitle != newTitle) { - rewriteGameTitle(destinationPath, newTitle); - } - } catch (Exception e) { - logger.error("Error trying to copy the save directory:", e); - } - } - - // TODO: Translation strings for rename prompt - private boolean rewriteCheck(GameInfo item) { - if (doesRecordingExist(item.getManifest().getTitle())) { - final ShortEntryPopup popup = getManager().createScreen(ShortEntryPopup.ASSET_URI, ShortEntryPopup.class); - popup.setTitle("Rename Recording"); - popup.setMessage("A recording with this name has already been saved! Please set a new name for this recording."); - popup.bindInput(new Binding() { - @Override - public String get() { - return null; - } - - @Override - public void set(String value) { - if (!isNameValid(value)) { - popup.setMessage("Invalid File Name! Please set a different name for this recording."); - getManager().pushScreen(popup); - return; - } - loadGame(item, value); - } - }); - getManager().pushScreen(popup); - - return true; - } - return false; - } - - private void rewriteGameTitle(Path destinationPath, String newTitle) throws IOException { - GameManifest manifest = GameManifest.load(destinationPath.resolve(GameManifest.DEFAULT_FILE_NAME)); - manifest.setTitle(newTitle); - GameManifest.save(destinationPath.resolve(GameManifest.DEFAULT_FILE_NAME), manifest); - } - - private boolean doesRecordingExist(String name) { - Path destinationPath = PathManager.getInstance().getRecordingPath(name); - return FileUtils.fileExists(destinationPath.toString()); - } - - private boolean isNameValid(String name) { - if (StringUtils.isBlank(name)) { - return false; - } - if (doesRecordingExist(name)) { - return false; - } - return true; + private void launchRename(NameRecordingScreen nameRecordingScreen, GameInfo info) { + nameRecordingScreen.setGameInfo(info); + nameRecordingScreen.setRecordAndReplayUtils(recordAndReplayUtils); + triggerForwardAnimation(nameRecordingScreen); } void setRecordAndReplayUtils(RecordAndReplayUtils recordAndReplayUtils) { diff --git a/engine/src/main/resources/assets/ui/menu/shortEntryPopup.ui b/engine/src/main/resources/assets/ui/menu/nameRecordingScreen.ui similarity index 75% rename from engine/src/main/resources/assets/ui/menu/shortEntryPopup.ui rename to engine/src/main/resources/assets/ui/menu/nameRecordingScreen.ui index d347e709124..2f245147e6f 100644 --- a/engine/src/main/resources/assets/ui/menu/shortEntryPopup.ui +++ b/engine/src/main/resources/assets/ui/menu/nameRecordingScreen.ui @@ -1,11 +1,26 @@ { - "type": "shortEntryPopup", - "skin": "messageBox", + "type": "nameRecordingScreen", + "skin": "engine:mainMenu", "contents": { "type": "relativeLayout", "contents": [ + { + "type": "UILabel", + "id": "title", + "family": "title", + "layoutInfo": { + "height": 48, + "position-horizontal-center": {}, + "position-bottom": { + "target": "TOP", + "widget": "box", + "offset": 48 + } + } + }, { "type": "UIBox", + "id": "box", "layoutInfo": { "width": 500, "use-content-height": true, @@ -17,11 +32,6 @@ "columns": 1, "verticalSpacing": 8, "contents": [ - { - "type": "UILabel", - "id": "title", - "family": "title" - }, { "type": "UISpace", "size": [ @@ -31,7 +41,7 @@ }, { "type": "UILabel", - "id": "message" + "id": "description" }, { "type": "UISpace", @@ -55,7 +65,7 @@ }, { "type": "UIButton", - "id": "returnButton", + "id": "cancelButton", "text": "${engine:menu#dialog-cancel}" } ], From 898bbe9159c15465d1ad33664d8b5e70dd977288 Mon Sep 17 00:00:00 2001 From: AndyTechGuy <44126397+AndyTechGuy@users.noreply.github.com> Date: Fri, 16 Nov 2018 17:06:02 -0330 Subject: [PATCH 4/7] Add TranslationSystem statements to NameRecordingScreen, and slightly change isNameValid Adds translation strings to the GUI for the rename screen. Includes an (attempted) French translation. Also adds a step to the name validity check, checking for names made of invalid characters. --- .../layers/mainMenu/NameRecordingScreen.java | 23 ++++++++++++------- .../src/main/resources/assets/i18n/menu.lang | 4 ++++ .../main/resources/assets/i18n/menu_en.lang | 4 ++++ .../main/resources/assets/i18n/menu_fr.lang | 4 ++++ .../assets/ui/menu/nameRecordingScreen.ui | 4 +++- 5 files changed, 30 insertions(+), 9 deletions(-) diff --git a/engine/src/main/java/org/terasology/rendering/nui/layers/mainMenu/NameRecordingScreen.java b/engine/src/main/java/org/terasology/rendering/nui/layers/mainMenu/NameRecordingScreen.java index c4478ac4baf..14613439093 100644 --- a/engine/src/main/java/org/terasology/rendering/nui/layers/mainMenu/NameRecordingScreen.java +++ b/engine/src/main/java/org/terasology/rendering/nui/layers/mainMenu/NameRecordingScreen.java @@ -25,6 +25,7 @@ import org.terasology.engine.modes.StateLoading; import org.terasology.engine.paths.PathManager; import org.terasology.game.GameManifest; +import org.terasology.i18n.TranslationSystem; import org.terasology.network.NetworkMode; import org.terasology.recording.RecordAndReplayUtils; import org.terasology.registry.CoreRegistry; @@ -49,6 +50,9 @@ public class NameRecordingScreen extends CoreScreenLayer { @In protected Config config; + @In + private TranslationSystem translationSystem; + private GameInfo gameInfo; private RecordAndReplayUtils recordAndReplayUtils; @@ -66,10 +70,6 @@ public void initialise() { initWidgets(); - // TODO: Translation strings for rename prompt - title.setText("Name Recording"); - description.setText("Set a unique name for this recording."); - enter.subscribe(button -> enterPressed()); cancel.subscribe(button -> cancelPressed()); @@ -85,11 +85,11 @@ private void initWidgets() { private void enterPressed() { // TODO: More translation strings if(!isNameValid(nameInput.getText())) { - description.setText("This name is blank, or has disallowed characters in it! Please set a different name."); + description.setText(translationSystem.translate("${engine:menu#name-recording-error-invalid}")); return; } if(doesRecordingExist(nameInput.getText())) { - description.setText("This name is already taken! Please set a different name."); + description.setText(translationSystem.translate("${engine:menu#name-recording-error-duplicate}")); return; } @@ -133,8 +133,15 @@ private void rewriteRecordingTitle(Path destinationPath, String newTitle) throws GameManifest.save(destinationPath.resolve(GameManifest.DEFAULT_FILE_NAME), manifest); } - private boolean isNameValid(String name) { // In the future, this probably also check for invalid file characters (?, ", .) etc. - return !StringUtils.isBlank(name); // newGameScreen does it this way, so it should be fine..? + private boolean isNameValid(String name) { + Path destinationPath = PathManager.getInstance().getRecordingPath(name); + + // invalid characters are filtered, so if the file name is made up of entirely invalid characters, the path will have a blank name. + if(destinationPath == PathManager.getInstance().getRecordingPath("")) { + return false; + } + + return !StringUtils.isBlank(name); } private boolean doesRecordingExist(String name) { diff --git a/engine/src/main/resources/assets/i18n/menu.lang b/engine/src/main/resources/assets/i18n/menu.lang index 4cc30ea0021..24807ae501c 100644 --- a/engine/src/main/resources/assets/i18n/menu.lang +++ b/engine/src/main/resources/assets/i18n/menu.lang @@ -214,6 +214,10 @@ "light-shafts": "light-shafts", "listed-servers": "listed-servers", "load-game": "load-game", + "name-recording-title": "name-recording-title", + "name-recording-description": "name-recording-description", + "name-recording-error-duplicate": "name-recording-error-duplicate", + "name-recording-error-invalid": "name-recording-error-invalid", "menu-animations": "menu-animations", "missing-name-message": "missing-name-message", "module-details-title": "module-details-title", diff --git a/engine/src/main/resources/assets/i18n/menu_en.lang b/engine/src/main/resources/assets/i18n/menu_en.lang index 40317a0c628..b5460f3d1e8 100644 --- a/engine/src/main/resources/assets/i18n/menu_en.lang +++ b/engine/src/main/resources/assets/i18n/menu_en.lang @@ -217,6 +217,10 @@ "light-shafts": "Light Shafts", "listed-servers": "Listed", "load-game": "Load", + "name-recording-title": "Name Recording", + "name-recording-description": "Enter a unique name for this recording.", + "name-recording-error-duplicate": "A recording with this name already exists! Enter a different name.", + "name-recording-error-invalid": "This recording name is invalid! Enter a different name.", "menu-animations": "Animated Menus", "missing-name-message": "You must enter a name", "module-details-title": "Module Details", diff --git a/engine/src/main/resources/assets/i18n/menu_fr.lang b/engine/src/main/resources/assets/i18n/menu_fr.lang index e4572c7f6a3..51c1b3ca880 100644 --- a/engine/src/main/resources/assets/i18n/menu_fr.lang +++ b/engine/src/main/resources/assets/i18n/menu_fr.lang @@ -142,6 +142,10 @@ "mouse-sensitivity": "Sensibilité de la souris", "movement-dead-zone": "Mouvement dans l'axe de la zone morte", "music-volume": "Volume de la musique", + "name-recording-title": "Nommez l'enregistrement", + "name-recording-description": "Entrez un nom unique pour cet enregistrement.", + "name-recording-error-duplicate": "Un enregistrement avec ce nom existe déjà ! Entrez un nom différent.", + "name-recording-error-invalid": "Ce nom de l'enregistrement est erroné ! Entrez un nom différent.", "new-binding": "Nouveau racourcie", "next-toolbar-item": "Prochaine barre d'outils", "none": "Aucun", diff --git a/engine/src/main/resources/assets/ui/menu/nameRecordingScreen.ui b/engine/src/main/resources/assets/ui/menu/nameRecordingScreen.ui index 2f245147e6f..0e3780373c9 100644 --- a/engine/src/main/resources/assets/ui/menu/nameRecordingScreen.ui +++ b/engine/src/main/resources/assets/ui/menu/nameRecordingScreen.ui @@ -8,6 +8,7 @@ "type": "UILabel", "id": "title", "family": "title", + "text": "${engine:menu#name-recording-title}", "layoutInfo": { "height": 48, "position-horizontal-center": {}, @@ -41,7 +42,8 @@ }, { "type": "UILabel", - "id": "description" + "id": "description", + "text": "${engine:menu#name-recording-description}" }, { "type": "UISpace", From e29399ff1757157ec726f370cb236db747d81afc Mon Sep 17 00:00:00 2001 From: AndyTechGuy <44126397+AndyTechGuy@users.noreply.github.com> Date: Fri, 16 Nov 2018 17:18:58 -0330 Subject: [PATCH 5/7] Documentation, checkstyle, and other minor changes for clarity. --- .../layers/mainMenu/NameRecordingScreen.java | 57 ++++++++++++++++--- .../nui/layers/mainMenu/RecordScreen.java | 13 ++++- 2 files changed, 60 insertions(+), 10 deletions(-) diff --git a/engine/src/main/java/org/terasology/rendering/nui/layers/mainMenu/NameRecordingScreen.java b/engine/src/main/java/org/terasology/rendering/nui/layers/mainMenu/NameRecordingScreen.java index 14613439093..69eb2f40f79 100644 --- a/engine/src/main/java/org/terasology/rendering/nui/layers/mainMenu/NameRecordingScreen.java +++ b/engine/src/main/java/org/terasology/rendering/nui/layers/mainMenu/NameRecordingScreen.java @@ -32,7 +32,6 @@ import org.terasology.registry.In; import org.terasology.rendering.nui.CoreScreenLayer; import org.terasology.rendering.nui.animation.MenuAnimationSystems; -import org.terasology.rendering.nui.databinding.Binding; import org.terasology.rendering.nui.layers.mainMenu.savedGames.GameInfo; import org.terasology.rendering.nui.widgets.UIButton; import org.terasology.rendering.nui.widgets.UILabel; @@ -42,6 +41,9 @@ import java.io.IOException; import java.nio.file.Path; +/** + * Screen for setting the name of and ultimately loading a recording. + */ public class NameRecordingScreen extends CoreScreenLayer { public static final ResourceUrn ASSET_URI = new ResourceUrn("engine:nameRecordingScreen!instance"); @@ -75,6 +77,9 @@ public void initialise() { cancel.subscribe(button -> cancelPressed()); } + /** + * Sets the values of all widget references. + */ private void initWidgets() { title = find("title", UILabel.class); description = find("description", UILabel.class); @@ -83,12 +88,15 @@ private void initWidgets() { cancel = find("cancelButton", UIButton.class); } - private void enterPressed() { // TODO: More translation strings - if(!isNameValid(nameInput.getText())) { + /** + * Activates upon pressing the enter key. + */ + private void enterPressed() { + if (!isNameValid(nameInput.getText())) { description.setText(translationSystem.translate("${engine:menu#name-recording-error-invalid}")); return; } - if(doesRecordingExist(nameInput.getText())) { + if (doesRecordingExist(nameInput.getText())) { description.setText(translationSystem.translate("${engine:menu#name-recording-error-duplicate}")); return; } @@ -96,10 +104,19 @@ private void enterPressed() { // TODO: More translation strings loadGame(nameInput.getText()); } + /** + * Activates upon pressing the cancel key. + */ private void cancelPressed() { triggerBackAnimation(); } + /** + * Last step of the recording setup process. Copies the save files from the selected game, transplants them into the 'recordings' folder, and renames the map files + * to match the provided recording name. Then launches the game loading state. + * + * @param newTitle The title of the new recording. + */ private void loadGame(String newTitle) { try { final GameManifest manifest = gameInfo.getManifest(); @@ -115,35 +132,61 @@ private void loadGame(String newTitle) { } } + /** + * Copies the selected save files to a new recording directory. + * + * @param oldTitle The name of the original save directory. + * @param newTitle The name of the new recording directory. + */ private void copySaveDirectoryToRecordingLibrary(String oldTitle, String newTitle) { File saveDirectory = new File(PathManager.getInstance().getSavePath(oldTitle).toString()); Path destinationPath = PathManager.getInstance().getRecordingPath(newTitle); File destDirectory = new File(destinationPath.toString()); try { FileUtils.copyDirectoryStructure(saveDirectory, destDirectory); - rewriteRecordingTitle(destinationPath, newTitle); + rewriteManifestTitle(destinationPath, newTitle); } catch (Exception e) { logger.error("Error trying to copy the save directory:", e); } } - private void rewriteRecordingTitle(Path destinationPath, String newTitle) throws IOException { + /** + * Rewrites the title of the save game manifest to match the new directory title. + * + * @param destinationPath The path of the new recording files. + * @param newTitle The new name for the recording manifest. + * @throws IOException + */ + private void rewriteManifestTitle(Path destinationPath, String newTitle) throws IOException { + // simply grabs the manifest, changes it, and saves again. GameManifest manifest = GameManifest.load(destinationPath.resolve(GameManifest.DEFAULT_FILE_NAME)); manifest.setTitle(newTitle); GameManifest.save(destinationPath.resolve(GameManifest.DEFAULT_FILE_NAME), manifest); } + /** + * Tests if the provided string is valid for a game name. + * + * @param name The provided name string. + * @return true if name is valid, false otherwise. + */ private boolean isNameValid(String name) { Path destinationPath = PathManager.getInstance().getRecordingPath(name); // invalid characters are filtered, so if the file name is made up of entirely invalid characters, the path will have a blank name. - if(destinationPath == PathManager.getInstance().getRecordingPath("")) { + if (destinationPath == PathManager.getInstance().getRecordingPath("")) { return false; } return !StringUtils.isBlank(name); } + /** + * Tests if there is an existing recording with the provided name string. + * + * @param name The provided name string. + * @return true if recording exists, false otherwise. + */ private boolean doesRecordingExist(String name) { Path destinationPath = PathManager.getInstance().getRecordingPath(name); return FileUtils.fileExists(destinationPath.toString()); diff --git a/engine/src/main/java/org/terasology/rendering/nui/layers/mainMenu/RecordScreen.java b/engine/src/main/java/org/terasology/rendering/nui/layers/mainMenu/RecordScreen.java index 263e5b3ac79..60e71519c41 100644 --- a/engine/src/main/java/org/terasology/rendering/nui/layers/mainMenu/RecordScreen.java +++ b/engine/src/main/java/org/terasology/rendering/nui/layers/mainMenu/RecordScreen.java @@ -88,7 +88,8 @@ public void onOpened() { refreshGameInfoList(GameProvider.getSavedGames()); } else { final MessagePopup popup = getManager().createScreen(MessagePopup.ASSET_URI, MessagePopup.class); - popup.setMessage(translationSystem.translate("${engine:menu#game-details-errors-message-title}"), translationSystem.translate("${engine:menu#game-details-errors-message-body}")); + popup.setMessage(translationSystem.translate("${engine:menu#game-details-errors-message-title}"), + translationSystem.translate("${engine:menu#game-details-errors-message-body}")); popup.subscribeButton(e -> triggerBackAnimation()); getManager().pushScreen(popup); // disable child widgets @@ -103,6 +104,12 @@ protected void initWidgets() { close = find("close", UIButton.class); } + /** + * Launches {@link NameRecordingScreen} with the info of the game selected in this screen. + * + * @param nameRecordingScreen The instance of the screen to launch + * @param info The info of the selected game. + */ private void launchRename(NameRecordingScreen nameRecordingScreen, GameInfo info) { nameRecordingScreen.setGameInfo(info); nameRecordingScreen.setRecordAndReplayUtils(recordAndReplayUtils); @@ -116,8 +123,8 @@ void setRecordAndReplayUtils(RecordAndReplayUtils recordAndReplayUtils) { @Override protected boolean isValidScreen() { if (Stream.of(load, close) - .anyMatch(Objects::isNull) || - !super.isValidScreen()) { + .anyMatch(Objects::isNull) + || !super.isValidScreen()) { logger.error("Can't initialize screen correctly. At least one widget was missed!"); return false; } From 8cb34d587a3cfaa84d461c09f35921d053814511 Mon Sep 17 00:00:00 2001 From: AndyTechGuy <44126397+AndyTechGuy@users.noreply.github.com> Date: Fri, 16 Nov 2018 17:21:44 -0330 Subject: [PATCH 6/7] Delete ShortEntryPopup.java --- .../nui/layers/mainMenu/ShortEntryPopup.java | 57 ------------------- 1 file changed, 57 deletions(-) delete mode 100644 engine/src/main/java/org/terasology/rendering/nui/layers/mainMenu/ShortEntryPopup.java diff --git a/engine/src/main/java/org/terasology/rendering/nui/layers/mainMenu/ShortEntryPopup.java b/engine/src/main/java/org/terasology/rendering/nui/layers/mainMenu/ShortEntryPopup.java deleted file mode 100644 index 4b31d519c8f..00000000000 --- a/engine/src/main/java/org/terasology/rendering/nui/layers/mainMenu/ShortEntryPopup.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2018 MovingBlocks - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.terasology.rendering.nui.layers.mainMenu; - -import org.terasology.assets.ResourceUrn; -import org.terasology.rendering.nui.CoreScreenLayer; -import org.terasology.rendering.nui.WidgetUtil; -import org.terasology.rendering.nui.databinding.Binding; -import org.terasology.rendering.nui.widgets.*; - -public class ShortEntryPopup extends CoreScreenLayer { - public static final ResourceUrn ASSET_URI = new ResourceUrn("engine:shortEntryPopup!instance"); - - private Binding inputBinding; - - @Override - public void initialise() { - WidgetUtil.trySubscribe(this, "enterButton", button -> enterPressed()); - WidgetUtil.trySubscribe(this, "returnButton", button -> returnPressed()); - } - - private void enterPressed() { - if (inputBinding != null) { - inputBinding.set(find("nameInput", UIText.class).getText()); - } - getManager().popScreen(); - } - - private void returnPressed() { - getManager().popScreen(); - } - - public void setTitle(String title) { - find("title", UILabel.class).setText(title); - } - - public void setMessage(String message) { - find("message", UILabel.class).setText(message); - } - - public void bindInput(Binding binding) { - this.inputBinding = binding; - } -} From d63e2730dadde304f12b934eb45a8e279c810b55 Mon Sep 17 00:00:00 2001 From: AndyTechGuy <44126397+AndyTechGuy@users.noreply.github.com> Date: Sat, 17 Nov 2018 22:37:13 -0330 Subject: [PATCH 7/7] Implement reviews from llvieira and iaronaraujo Fixes issues brought up in PR reviews, including: - Screen error messages now reset when player opens the screen - File path comparison now performed properly - Renames screen launching function in RecordScreen - Removes unused 'title' field in NameRecordingScreen --- .../layers/mainMenu/NameRecordingScreen.java | 19 +++++++++---------- .../nui/layers/mainMenu/RecordScreen.java | 6 +++--- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/engine/src/main/java/org/terasology/rendering/nui/layers/mainMenu/NameRecordingScreen.java b/engine/src/main/java/org/terasology/rendering/nui/layers/mainMenu/NameRecordingScreen.java index 69eb2f40f79..cab18d9cf5c 100644 --- a/engine/src/main/java/org/terasology/rendering/nui/layers/mainMenu/NameRecordingScreen.java +++ b/engine/src/main/java/org/terasology/rendering/nui/layers/mainMenu/NameRecordingScreen.java @@ -16,7 +16,6 @@ package org.terasology.rendering.nui.layers.mainMenu; import org.codehaus.plexus.util.FileUtils; -import org.codehaus.plexus.util.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.terasology.assets.ResourceUrn; @@ -60,7 +59,6 @@ public class NameRecordingScreen extends CoreScreenLayer { private RecordAndReplayUtils recordAndReplayUtils; // widgets - private UILabel title; private UILabel description; private UIText nameInput; private UIButton enter; @@ -73,15 +71,19 @@ public void initialise() { initWidgets(); enter.subscribe(button -> enterPressed()); - cancel.subscribe(button -> cancelPressed()); } + @Override + public void onScreenOpened() { + // resets the description from any earlier error messages, in case the user re-opens the screen. + description.setText(translationSystem.translate("${engine:menu#name-recording-description}")); + } + /** * Sets the values of all widget references. */ private void initWidgets() { - title = find("title", UILabel.class); description = find("description", UILabel.class); nameInput = find("nameInput", UIText.class); enter = find("enterButton", UIButton.class); @@ -173,12 +175,9 @@ private void rewriteManifestTitle(Path destinationPath, String newTitle) throws private boolean isNameValid(String name) { Path destinationPath = PathManager.getInstance().getRecordingPath(name); - // invalid characters are filtered, so if the file name is made up of entirely invalid characters, the path will have a blank name. - if (destinationPath == PathManager.getInstance().getRecordingPath("")) { - return false; - } - - return !StringUtils.isBlank(name); + // invalid characters are filtered from paths, so if the file name is made up of entirely invalid characters, the path will have a blank file name. + // also acts as a check for blank input. + return !destinationPath.equals(PathManager.getInstance().getRecordingPath("")); } /** diff --git a/engine/src/main/java/org/terasology/rendering/nui/layers/mainMenu/RecordScreen.java b/engine/src/main/java/org/terasology/rendering/nui/layers/mainMenu/RecordScreen.java index 60e71519c41..f396929d6ce 100644 --- a/engine/src/main/java/org/terasology/rendering/nui/layers/mainMenu/RecordScreen.java +++ b/engine/src/main/java/org/terasology/rendering/nui/layers/mainMenu/RecordScreen.java @@ -66,12 +66,12 @@ public void initialise() { updateDescription(item); }); - getGameInfos().subscribe((widget, item) -> launchRename(nameRecordingScreen, item)); + getGameInfos().subscribe((widget, item) -> launchNamingScreen(nameRecordingScreen, item)); load.subscribe(button -> { final GameInfo gameInfo = getGameInfos().getSelection(); if (gameInfo != null) { - launchRename(nameRecordingScreen, gameInfo); + launchNamingScreen(nameRecordingScreen, gameInfo); } }); @@ -110,7 +110,7 @@ protected void initWidgets() { * @param nameRecordingScreen The instance of the screen to launch * @param info The info of the selected game. */ - private void launchRename(NameRecordingScreen nameRecordingScreen, GameInfo info) { + private void launchNamingScreen(NameRecordingScreen nameRecordingScreen, GameInfo info) { nameRecordingScreen.setGameInfo(info); nameRecordingScreen.setRecordAndReplayUtils(recordAndReplayUtils); triggerForwardAnimation(nameRecordingScreen);