diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java index c23a74f3b..bc6ca59a1 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java @@ -24,12 +24,15 @@ import com.google.gson.reflect.TypeToken; import javafx.scene.image.Image; import org.jackhuang.hmcl.Metadata; import org.jackhuang.hmcl.download.LibraryAnalyzer; +import org.jackhuang.hmcl.event.Event; +import org.jackhuang.hmcl.event.EventManager; import org.jackhuang.hmcl.mod.Modpack; import org.jackhuang.hmcl.mod.ModpackConfiguration; import org.jackhuang.hmcl.mod.mcbbs.McbbsModpackLocalInstallTask; import org.jackhuang.hmcl.mod.mcbbs.McbbsModpackManifest; import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.setting.ProxyManager; +import org.jackhuang.hmcl.setting.VersionIconType; import org.jackhuang.hmcl.setting.VersionSetting; import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.StringUtils; @@ -38,6 +41,7 @@ import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.platform.JavaVersion; import org.jackhuang.hmcl.util.platform.OperatingSystem; import org.jackhuang.hmcl.util.versioning.VersionNumber; +import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; @@ -58,6 +62,8 @@ public class HMCLGameRepository extends DefaultGameRepository { private final Map localVersionSettings = new HashMap<>(); private final Set beingModpackVersions = new HashSet<>(); + public final EventManager onVersionIconChanged = new EventManager<>(); + public HMCLGameRepository(Profile profile, File baseDirectory) { super(baseDirectory); this.profile = profile; @@ -231,6 +237,7 @@ public class HMCLGameRepository extends DefaultGameRepository { * * @return corresponding version setting, null if the version has no its own version setting. */ + @Nullable public VersionSetting getLocalVersionSetting(String id) { if (!localVersionSettings.containsKey(id)) loadLocalVersionSetting(id); @@ -240,6 +247,15 @@ public class HMCLGameRepository extends DefaultGameRepository { return setting; } + @Nullable + public VersionSetting getLocalVersionSettingOrCreate(String id) { + VersionSetting vs = getLocalVersionSetting(id); + if (vs == null) { + vs = createLocalVersionSetting(id); + } + return vs; + } + public VersionSetting getVersionSetting(String id) { VersionSetting vs = getLocalVersionSetting(id); if (vs == null || vs.isUsesGlobal()) { @@ -258,14 +274,21 @@ public class HMCLGameRepository extends DefaultGameRepository { if (id == null || !isLoaded()) return newImage("/assets/img/grass.png"); - Version version = getVersion(id).resolve(this); - File iconFile = getVersionIconFile(id); - if (iconFile.exists()) - return new Image("file:" + iconFile.getAbsolutePath()); - else if (LibraryAnalyzer.isModded(this, version)) - return newImage("/assets/img/furnace.png"); - else - return newImage("/assets/img/grass.png"); + VersionSetting vs = getLocalVersionSettingOrCreate(id); + VersionIconType iconType = Optional.ofNullable(vs).map(VersionSetting::getVersionIcon).orElse(VersionIconType.DEFAULT); + + if (iconType == VersionIconType.DEFAULT) { + Version version = getVersion(id).resolve(this); + File iconFile = getVersionIconFile(id); + if (iconFile.exists()) + return new Image("file:" + iconFile.getAbsolutePath()); + else if (LibraryAnalyzer.isModded(this, version)) + return newImage("/assets/img/furnace.png"); + else + return newImage("/assets/img/grass.png"); + } else { + return newImage(iconType.getResourceUrl()); + } } public boolean saveVersionSetting(String id) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionIconType.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionIconType.java new file mode 100644 index 000000000..7c6a5026a --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionIconType.java @@ -0,0 +1,43 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2021 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.setting; + +public enum VersionIconType { + DEFAULT("/assets/img/grass.png"), + + GRASS("/assets/img/grass.png"), + CHEST("/assets/img/chest.png"), + CHICKEN("/assets/img/chicken.png"), + COMMAND("/assets/img/command.png"), + CRAFT_TABLE("/assets/img/craft_table.png"), + FABRIC("/assets/img/fabric.png"), + FORGE("/assets/img/forge.png"), + FURNACE("/assets/img/furnace.png"); + + // Please append new items at last + + private final String resourceUrl; + + VersionIconType(String resourceUrl) { + this.resourceUrl = resourceUrl; + } + + public String getResourceUrl() { + return resourceUrl; + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionSetting.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionSetting.java index e9cbb1dda..0707dc111 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionSetting.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionSetting.java @@ -1,6 +1,6 @@ /* * Hello Minecraft! Launcher - * Copyright (C) 2020 huangyuhui and contributors + * Copyright (C) 2021 huangyuhui and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -568,6 +568,20 @@ public final class VersionSetting implements Cloneable { this.useNativeOpenAL.set(useNativeOpenAL); } + private final ObjectProperty versionIcon = new SimpleObjectProperty<>(this, "versionIcon", VersionIconType.DEFAULT); + + public VersionIconType getVersionIcon() { + return versionIcon.get(); + } + + public ObjectProperty versionIconProperty() { + return versionIcon; + } + + public void setVersionIcon(VersionIconType versionIcon) { + this.versionIcon.set(versionIcon); + } + // launcher settings /** @@ -670,6 +684,7 @@ public final class VersionSetting implements Cloneable { defaultJavaPathProperty.addListener(listener); nativesDirProperty.addListener(listener); nativesDirTypeProperty.addListener(listener); + versionIcon.addListener(listener); } @Override @@ -703,6 +718,7 @@ public final class VersionSetting implements Cloneable { versionSetting.setUseNativeOpenAL(isUseNativeOpenAL()); versionSetting.setLauncherVisibility(getLauncherVisibility()); versionSetting.setNativesDir(getNativesDir()); + versionSetting.setVersionIcon(getVersionIcon()); return versionSetting; } @@ -741,6 +757,7 @@ public final class VersionSetting implements Cloneable { obj.addProperty("defaultJavaPath", src.getDefaultJavaPath()); obj.addProperty("nativesDir", src.getNativesDir()); obj.addProperty("nativesDirType", src.getNativesDirType().ordinal()); + obj.addProperty("versionIcon", src.getVersionIcon().ordinal()); return obj; } @@ -784,6 +801,7 @@ public final class VersionSetting implements Cloneable { vs.setGameDirType(GameDirectoryType.values()[Optional.ofNullable(obj.get("gameDirType")).map(JsonElement::getAsInt).orElse(GameDirectoryType.ROOT_FOLDER.ordinal())]); vs.setDefaultJavaPath(Optional.ofNullable(obj.get("defaultJavaPath")).map(JsonElement::getAsString).orElse(null)); vs.setNativesDirType(NativesDirectoryType.values()[Optional.ofNullable(obj.get("nativesDirType")).map(JsonElement::getAsInt).orElse(0)]); + vs.setVersionIcon(VersionIconType.values()[Optional.ofNullable(obj.get("versionIcon")).map(JsonElement::getAsInt).orElse(0)]); return vs; } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameAdvancedListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameAdvancedListItem.java index afdf19792..aedbab851 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameAdvancedListItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameAdvancedListItem.java @@ -20,17 +20,25 @@ package org.jackhuang.hmcl.ui.versions; import javafx.scene.Node; import javafx.scene.control.Tooltip; import javafx.scene.image.ImageView; +import org.jackhuang.hmcl.event.Event; +import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.setting.Profiles; import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.ui.WeakListenerHolder; import org.jackhuang.hmcl.ui.construct.AdvancedListItem; import org.jackhuang.hmcl.util.Pair; +import java.util.function.Consumer; + import static org.jackhuang.hmcl.ui.FXUtils.newImage; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; public class GameAdvancedListItem extends AdvancedListItem { private final Tooltip tooltip; private final ImageView imageView; + private final WeakListenerHolder holder = new WeakListenerHolder(); + private Profile profile; + private Consumer onVersionIconChangedListener; public GameAdvancedListItem() { tooltip = new Tooltip(); @@ -39,23 +47,31 @@ public class GameAdvancedListItem extends AdvancedListItem { setLeftGraphic(view.getKey()); imageView = view.getValue(); - FXUtils.onChangeAndOperate(Profiles.selectedVersionProperty(), version -> { - if (version != null && Profiles.getSelectedProfile() != null && - Profiles.getSelectedProfile().getRepository().hasVersion(version)) { - FXUtils.installFastTooltip(this, tooltip); - setTitle(version); - setSubtitle(null); - imageView.setImage(Profiles.getSelectedProfile().getRepository().getVersionIconImage(version)); - tooltip.setText(version); - } else { - Tooltip.uninstall(this,tooltip); - setTitle(i18n("version.empty")); - setSubtitle(i18n("version.empty.add")); - imageView.setImage(newImage("/assets/img/grass.png")); - tooltip.setText(""); - } - }); + holder.add(FXUtils.onWeakChangeAndOperate(Profiles.selectedVersionProperty(), this::loadVersion)); setActionButtonVisible(false); } + + private void loadVersion(String version) { + if (Profiles.getSelectedProfile() != profile) { + profile = Profiles.getSelectedProfile(); + onVersionIconChangedListener = profile.getRepository().onVersionIconChanged.registerWeak(event -> { + this.loadVersion(Profiles.getSelectedVersion()); + }); + } + if (version != null && Profiles.getSelectedProfile() != null && + Profiles.getSelectedProfile().getRepository().hasVersion(version)) { + FXUtils.installFastTooltip(this, tooltip); + setTitle(version); + setSubtitle(null); + imageView.setImage(Profiles.getSelectedProfile().getRepository().getVersionIconImage(version)); + tooltip.setText(version); + } else { + Tooltip.uninstall(this,tooltip); + setTitle(i18n("version.empty")); + setSubtitle(i18n("version.empty.add")); + imageView.setImage(newImage("/assets/img/grass.png")); + tooltip.setText(""); + } + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java new file mode 100644 index 000000000..317d169b7 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java @@ -0,0 +1,126 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2021 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.ui.versions; + +import javafx.scene.Node; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.FlowPane; +import javafx.stage.FileChooser; +import org.jackhuang.hmcl.event.Event; +import org.jackhuang.hmcl.setting.Profile; +import org.jackhuang.hmcl.setting.Theme; +import org.jackhuang.hmcl.setting.VersionIconType; +import org.jackhuang.hmcl.setting.VersionSetting; +import org.jackhuang.hmcl.ui.Controllers; +import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.ui.SVG; +import org.jackhuang.hmcl.ui.construct.DialogPane; +import org.jackhuang.hmcl.ui.construct.RipplerContainer; +import org.jackhuang.hmcl.util.Logging; +import org.jackhuang.hmcl.util.io.FileUtils; + +import java.io.File; +import java.io.IOException; +import java.util.logging.Level; + +import static org.jackhuang.hmcl.util.i18n.I18n.i18n; + +public class VersionIconDialog extends DialogPane { + private final Profile profile; + private final String versionId; + private final Runnable onFinish; + private final VersionSetting vs; + + public VersionIconDialog(Profile profile, String versionId, Runnable onFinish) { + this.profile = profile; + this.versionId = versionId; + this.onFinish = onFinish; + this.vs = profile.getRepository().getLocalVersionSettingOrCreate(versionId); + + setTitle(i18n("settings.icon")); + FlowPane pane = new FlowPane(); + setBody(pane); + + pane.getChildren().setAll( + createCustomIcon(), + createIcon(VersionIconType.GRASS), + createIcon(VersionIconType.CHEST), + createIcon(VersionIconType.CHICKEN), + createIcon(VersionIconType.COMMAND), + createIcon(VersionIconType.CRAFT_TABLE), + createIcon(VersionIconType.FABRIC), + createIcon(VersionIconType.FORGE), + createIcon(VersionIconType.FURNACE) + ); + } + + private void exploreIcon() { + FileChooser chooser = new FileChooser(); + chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("extension.png"), "*.png")); + File selectedFile = chooser.showOpenDialog(Controllers.getStage()); + if (selectedFile != null) { + File iconFile = profile.getRepository().getVersionIconFile(versionId); + try { + FileUtils.copyFile(selectedFile, iconFile); + + if (vs != null) { + vs.setVersionIcon(VersionIconType.DEFAULT); + } + + onAccept(); + } catch (IOException e) { + Logging.LOG.log(Level.SEVERE, "Failed to copy icon file from " + selectedFile + " to " + iconFile, e); + } + } + } + + private Node createCustomIcon() { + Node shape = SVG.plusCircleOutline(Theme.blackFillBinding(), 32, 32); + shape.setMouseTransparent(true); + RipplerContainer container = new RipplerContainer(shape); + FXUtils.setLimitWidth(container, 36); + FXUtils.setLimitHeight(container, 36); + container.setOnMouseClicked(e -> { + exploreIcon(); + }); + return container; + } + + private Node createIcon(VersionIconType type) { + ImageView imageView = new ImageView(new Image(type.getResourceUrl(), 32, 32, true, true)); + imageView.setMouseTransparent(true); + RipplerContainer container = new RipplerContainer(imageView); + FXUtils.setLimitWidth(container, 36); + FXUtils.setLimitHeight(container, 36); + container.setOnMouseClicked(e -> { + if (vs != null) { + vs.setVersionIcon(type); + onAccept(); + } + }); + return container; + } + + @Override + protected void onAccept() { + profile.getRepository().onVersionIconChanged.fireEvent(new Event(this)); + onFinish.run(); + super.onAccept(); + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java index 6c3670872..821192289 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java @@ -32,10 +32,7 @@ import javafx.scene.layout.*; import javafx.scene.text.Text; import javafx.stage.FileChooser; import org.jackhuang.hmcl.game.*; -import org.jackhuang.hmcl.setting.LauncherVisibility; -import org.jackhuang.hmcl.setting.Profile; -import org.jackhuang.hmcl.setting.Profiles; -import org.jackhuang.hmcl.setting.VersionSetting; +import org.jackhuang.hmcl.setting.*; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.Controllers; @@ -44,9 +41,7 @@ import org.jackhuang.hmcl.ui.WeakListenerHolder; import org.jackhuang.hmcl.ui.construct.*; import org.jackhuang.hmcl.ui.decorator.DecoratorPage; import org.jackhuang.hmcl.util.Lang; -import org.jackhuang.hmcl.util.Logging; import org.jackhuang.hmcl.util.Pair; -import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.javafx.BindingMapping; import org.jackhuang.hmcl.util.javafx.SafeStringConverter; import org.jackhuang.hmcl.util.platform.Architecture; @@ -55,17 +50,14 @@ import org.jackhuang.hmcl.util.platform.OperatingSystem; import org.jackhuang.hmcl.util.versioning.VersionNumber; import java.io.File; -import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.logging.Level; import java.util.stream.Collectors; -import static org.jackhuang.hmcl.ui.FXUtils.newImage; import static org.jackhuang.hmcl.ui.FXUtils.stringConverter; import static org.jackhuang.hmcl.util.Pair.pair; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; @@ -775,18 +767,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag if (versionId == null) return; - FileChooser chooser = new FileChooser(); - chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("extension.png"), "*.png")); - File selectedFile = chooser.showOpenDialog(Controllers.getStage()); - if (selectedFile != null) { - File iconFile = profile.getRepository().getVersionIconFile(versionId); - try { - FileUtils.copyFile(selectedFile, iconFile); - loadIcon(); - } catch (IOException e) { - Logging.LOG.log(Level.SEVERE, "Failed to copy icon file from " + selectedFile + " to " + iconFile, e); - } - } + Controllers.dialog(new VersionIconDialog(profile, versionId, this::loadIcon)); } private void onDeleteIcon() { @@ -796,6 +777,10 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag File iconFile = profile.getRepository().getVersionIconFile(versionId); if (iconFile.exists()) iconFile.delete(); + VersionSetting localVersionSetting = profile.getRepository().getLocalVersionSettingOrCreate(versionId); + if (localVersionSetting != null) { + localVersionSetting.setVersionIcon(VersionIconType.DEFAULT); + } loadIcon(); } @@ -804,11 +789,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag return; } - File iconFile = profile.getRepository().getVersionIconFile(versionId); - if (iconFile.exists()) - iconPickerItem.setImage(new Image("file:" + iconFile.getAbsolutePath())); - else - iconPickerItem.setImage(newImage("/assets/img/grass.png")); + iconPickerItem.setImage(profile.getRepository().getVersionIconImage(versionId)); FXUtils.limitSize(iconPickerItem.getImageView(), 32, 32); }