Skip to content

Commit

Permalink
feat(usability): select last played or installed game (#535)
Browse files Browse the repository at this point in the history
* feat(usability): select last played or installed game if applicable
* feat: unset `lasPlayed*` settings on last played game deletion
* feat: add new settings to new Configuration API
  - `lastPlayedGamePackage`
  - `lastInstalledGamePackage`
* fix: sync database on game download / deletion
* fix: sorting order to show latest installed

Co-authored-by: Tobias Nett <[email protected]>
  • Loading branch information
jdrueckert and skaldarnar authored Mar 28, 2020
1 parent 0684c5b commit 30d080a
Show file tree
Hide file tree
Showing 6 changed files with 272 additions and 12 deletions.
29 changes: 28 additions & 1 deletion src/main/java/org/terasology/launcher/config/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ public final class Config {
private final boolean closeAfterGameStarts;
private final boolean cacheGamePackages;
private final Package selectedPackage;
private final Package lastPlayedGamePackage;
private final Package lastInstalledGamePackage;

private Config(final Builder builder) {
this.gameConfig = builder.gameConfig;
Expand All @@ -44,6 +46,8 @@ private Config(final Builder builder) {
this.closeAfterGameStarts = builder.closeAfterGameStarts;
this.cacheGamePackages = builder.cacheGamePackages;
this.selectedPackage = builder.selectedPackage;
this.lastPlayedGamePackage = builder.lastPlayedGamePackage;
this.lastInstalledGamePackage = builder.lastInstalledGamePackage;
}

public GameConfig getGameConfig() {
Expand Down Expand Up @@ -74,6 +78,14 @@ public Package getSelectedPackage() {
return selectedPackage;
}

public Package getLastPlayedGamePackage() {
return lastPlayedGamePackage;
}

public Package getLastInstalledGamePackage() {
return lastInstalledGamePackage;
}

/**
* Provides a pre-filled {@link Builder} instance
* with all configurations copied from this. Use it
Expand Down Expand Up @@ -108,8 +120,11 @@ public static final class Builder {
private boolean closeAfterGameStarts;
private boolean cacheGamePackages;
private Package selectedPackage;
private Package lastPlayedGamePackage;
private Package lastInstalledGamePackage;

private Builder() { }
private Builder() {
}

private Builder(final Config last) {
gameConfig = last.gameConfig;
Expand All @@ -119,6 +134,8 @@ private Builder(final Config last) {
closeAfterGameStarts = last.closeAfterGameStarts;
cacheGamePackages = last.cacheGamePackages;
selectedPackage = last.selectedPackage;
lastPlayedGamePackage = last.lastPlayedGamePackage;
lastInstalledGamePackage = last.lastInstalledGamePackage;
}

public Builder gameConfig(final GameConfig newGameConfig) {
Expand Down Expand Up @@ -156,6 +173,16 @@ public Builder selectedPackage(final Package newSelectedPackage) {
return this;
}

public Builder lastPlayedGamePackage(final Package newLastPlayedGamePackage) {
lastPlayedGamePackage = newLastPlayedGamePackage;
return this;
}

public Builder lastInstalledGamePackage(final Package newLastInstalledGamePackage) {
lastInstalledGamePackage = newLastInstalledGamePackage;
return this;
}

public Config build() {
Objects.requireNonNull(gameConfig, "gameConfig must not be null");
Objects.requireNonNull(locale, "locale must not be null");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
import java.util.ResourceBundle;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Predicate;
import java.util.stream.Collectors;

public class ApplicationController {
Expand Down Expand Up @@ -206,12 +207,17 @@ private void startGameAction() {
launcherSettings.getUserGameParameterList(), launcherSettings.getLogLevel());
if (!gameStarted) {
GuiUtils.showErrorMessageDialog(stage, BundleUtils.getLabel("message_error_gameStart"));
} else if (launcherSettings.isCloseLauncherAfterGameStart()) {
if (gameDownloadWorker == null) {
logger.info("Close launcher after game start.");
close();
} else {
logger.info("The launcher can not be closed after game start, because a download is running.");
} else {
launcherSettings.setLastPlayedGameJob(selectedPackage.getId());
launcherSettings.setLastPlayedGameVersion(selectedPackage.getVersion());

if (launcherSettings.isCloseLauncherAfterGameStart()) {
if (gameDownloadWorker == null) {
logger.info("Close launcher after game start.");
close();
} else {
logger.info("The launcher can not be closed after game start, because a download is running.");
}
}
}
}
Expand All @@ -234,10 +240,13 @@ private void downloadAction() {
progressBar.setVisible(false);
startAndDownloadButton.setVisible(true);
cancelDownloadButton.setVisible(false);
packageManager.syncDatabase();

if (selectedPackage.isInstalled()) {
startAndDownloadButton.setGraphic(playImage);
deleteButton.setDisable(false);
launcherSettings.setLastInstalledGameJob(selectedPackage.getId());
launcherSettings.setLastInstalledGameVersion(selectedPackage.getVersion());
}
downloadTask = null;
});
Expand Down Expand Up @@ -275,10 +284,15 @@ protected void deleteAction() {
.filter(response -> response == ButtonType.OK)
.ifPresent(response -> {
logger.info("Removing game: {}-{}", selectedPackage.getId(), selectedPackage.getVersion());
// triggering a game deletion implies the player doesn't want to play this game anymore
// hence, we unset `lastPlayedGameJob` and `lastPlayedGameVersion` settings independent of deletion success
launcherSettings.setLastPlayedGameJob("");
launcherSettings.setLastPlayedGameVersion("");

deleteButton.setDisable(true);
final DeleteTask deleteTask = new DeleteTask(packageManager, selectedVersion);
deleteTask.onDone(() -> {
packageManager.syncDatabase();
if (!selectedPackage.isInstalled()) {
startAndDownloadButton.setGraphic(downloadImage);
} else {
Expand Down Expand Up @@ -324,6 +338,8 @@ public void update(final Path newLauncherDirectory, final Path newDownloadDirect
}

footerController.setHostServices(hostServices);

initializeComboBoxSelection();
}

// To be called after database sync is done
Expand Down Expand Up @@ -356,8 +372,20 @@ private void resetScrollBar(final ComboBox cb) {
private void initComboBoxes() {
jobBox.getSelectionModel().selectedItemProperty().addListener((obs, oldVal, newVal) -> {
buildVersionBox.setItems(newVal.versionItems);
//TODO remember selection / select latest installed version
buildVersionBox.getSelectionModel().select(0);

String lastPlayedGameJob = launcherSettings.getLastPlayedGameJob();
String selectedJobId = newVal.versionItems.get(0).linkedPackageProperty.get().getId();
if (lastPlayedGameJob.isEmpty() || !lastPlayedGameJob.equals(selectedJobId)) {
// select last installed package for the selected job or the latest one if none installed
String lastInstalledVersion = packageManager.getLatestInstalledPackageForId(selectedJobId)
.map(pkg -> pkg.getVersion()).orElseGet(() -> "");
selectItem(buildVersionBox, item ->
item.versionProperty.get().equals(lastInstalledVersion));
} else {
// select the package last played
selectItem(buildVersionBox, item ->
item.versionProperty.get().equals(launcherSettings.getLastPlayedGameVersion()));
}
});

buildVersionBox.setOnShowing(e -> resetScrollBar(buildVersionBox));
Expand All @@ -381,6 +409,70 @@ private void initComboBoxes() {
});
}

/**
* Select the first item matching given predicate, select the first item otherwise.
*
* @param comboBox the combo box to change the selection for
* @param predicate first item matching this predicate will be selected
*/
private <T> void selectItem(final ComboBox<T> comboBox, Predicate<T> predicate) {
final T item = comboBox.getItems().stream()
.filter(predicate)
.findFirst()
.orElse(comboBox.getItems().get(0));

comboBox.getSelectionModel().select(item);
}

/**
* Select the package item with given {@code jobId} or the first item of {@code jobBox}.
*
* @param jobId the job id of the package to be selected
*/
private void selectItemForJob(final String jobId) {
selectItem(jobBox, jobItem ->
jobItem.versionItems.stream()
.anyMatch(vItem -> vItem.linkedPackageProperty.get().getId().equals(jobId)));
}

/**
* Initialize selected game job and version based on last played and last installed games.
*
* The selection is derived from the following precedence rules:
* <ol>
* <li>Select the <b>last played game</b></li>
* <li>Select the <b>last installed game</b></li>
* <li>Select <b>latest version of default job</b> otherwise</li>
* </ol>
*/
//TODO: Reduce boilerplate code after switching to >= Java 9
// Use 'Optional::or' to chain logic together
private void initializeComboBoxSelection() {
String lastPlayedGameJob = launcherSettings.getLastPlayedGameJob();
if (!lastPlayedGameJob.isEmpty()) {
// select the package last played
selectItemForJob(launcherSettings.getLastPlayedGameJob());
selectItem(buildVersionBox, item ->
item.versionProperty.get().equals(launcherSettings.getLastPlayedGameVersion()));
} else {
String lastInstalledGameJob = launcherSettings.getLastInstalledGameJob();
if (!lastInstalledGameJob.isEmpty()) {
// select last installed package job and version
selectItemForJob(lastInstalledGameJob);
selectItem(buildVersionBox, item ->
item.versionProperty.get().equals(launcherSettings.getLastInstalledGameVersion()));
} else {
// select last installed package for the default job or the latest one if none installed
String defaultGameJob = launcherSettings.getDefaultGameJob();
selectItemForJob(defaultGameJob);
String lastInstalledVersion = packageManager.getLatestInstalledPackageForId(defaultGameJob)
.map(pkg -> pkg.getVersion()).orElseGet(() -> "");
selectItem(buildVersionBox, item ->
item.versionProperty.get().equals(lastInstalledVersion));
}
}
}

private void initButtons() {
cancelDownloadButton.setTooltip(new Tooltip(BundleUtils.getLabel("launcher_cancelDownload")));
cancelDownloadButton.managedProperty().bind(cancelDownloadButton.visibleProperty());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

/**
* Provides package details from all online and local repositories.
Expand Down Expand Up @@ -131,6 +133,13 @@ private void saveDatabase() {

List<Package> getPackages() {
return Collections.unmodifiableList(database);
}

Optional<Package> getLatestInstalledPackageForId(String packageId) {
return database.stream()
.filter(pkg -> pkg.getId().equals(packageId) && pkg.isInstalled())
.sorted(Comparator.comparing(Package::getVersion).reversed())
.findFirst();
}

static class PackageMetadata implements Serializable {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

/**
* Handles installation, removal and update of game packages.
Expand Down Expand Up @@ -198,6 +199,10 @@ public List<Integer> getPackageVersions(PackageBuild pkgBuild) {
.getPackageVersions(pkgBuild);
}

public Optional<Package> getLatestInstalledPackageForId(String packageId) {
return database.getLatestInstalledPackageForId(packageId);
}

public Path resolveInstallDir(Package target) {
return installDir.resolve(target.getId()).resolve(target.getVersion());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ public synchronized void init() {
initUserJavaParameters();
initUserGameParameters();
initLogLevel();
initDefaultGameJob();
initLastPlayedGameJob();
initLastPlayedGameVersion();
initLastInstalledGameJob();
initLastInstalledGameVersion();
}

// --------------------------------------------------------------------- //
Expand Down Expand Up @@ -81,6 +86,16 @@ public synchronized void init() {

protected abstract void initLocale();

protected abstract void initDefaultGameJob();

protected abstract void initLastPlayedGameJob();

protected abstract void initLastPlayedGameVersion();

protected abstract void initLastInstalledGameJob();

protected abstract void initLastInstalledGameVersion();

// --------------------------------------------------------------------- //
// GETTERS
// --------------------------------------------------------------------- //
Expand Down Expand Up @@ -115,6 +130,16 @@ public synchronized List<String> getUserGameParameterList() {

public abstract boolean isKeepDownloadedFiles();

public abstract String getDefaultGameJob();

public abstract String getLastPlayedGameJob();

public abstract String getLastPlayedGameVersion();

public abstract String getLastInstalledGameJob();

public abstract String getLastInstalledGameVersion();

// --------------------------------------------------------------------- //
// SETTERS
// --------------------------------------------------------------------- //
Expand All @@ -140,4 +165,14 @@ public synchronized List<String> getUserGameParameterList() {
public abstract void setGameDirectory(Path gameDirectory);

public abstract void setGameDataDirectory(Path gameDataDirectory);

public abstract void setDefaultGameJob(String lastPlayedGameJob);

public abstract void setLastPlayedGameJob(String lastPlayedGameJob);

public abstract void setLastPlayedGameVersion(String lastPlayedGameVersion);

public abstract void setLastInstalledGameJob(String lastInstalledGameJob);

public abstract void setLastInstalledGameVersion(String lastInstalledGameVersion);
}
Loading

0 comments on commit 30d080a

Please sign in to comment.