Compare commits
44
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8210c77de9 | ||
|
|
b11b298312 | ||
|
|
5c605c58d2 | ||
|
|
01d4100a2f | ||
|
|
d07eb2f748 | ||
|
|
345bb176fd | ||
|
|
83a38fadf0 | ||
|
|
9980dc8aea | ||
|
|
7dfbf76b69 | ||
|
|
7f2f77f54f | ||
|
|
2dccd8099d | ||
|
|
d8a03cfed8 | ||
|
|
26a874a030 | ||
|
|
7c5c3e2fb4 | ||
|
|
62d7b36248 | ||
|
|
dec6837528 | ||
|
|
3484792e81 | ||
|
|
2b310e035d | ||
|
|
6e960612dd | ||
|
|
73a776b021 | ||
|
|
31a2f49ed2 | ||
|
|
ab268cfe48 | ||
|
|
721aeccfc0 | ||
|
|
e4348f727a | ||
|
|
dfc96f10da | ||
|
|
a5c7fe08d8 | ||
|
|
a6e0acca45 | ||
|
|
3d312f9247 | ||
|
|
18dc5b69c0 | ||
|
|
98af2c85b6 | ||
|
|
c4c0b1a1ce | ||
|
|
5e621ce340 | ||
|
|
1d1c8dfdc9 | ||
|
|
9b68b8825d | ||
|
|
5c17783223 | ||
|
|
a921be45f1 | ||
|
|
2dd1c0f85e | ||
|
|
7ba229b3e5 | ||
|
|
59a299630b | ||
|
|
b41b89e233 | ||
|
|
af137e5ff1 | ||
|
|
74ddbf5623 | ||
|
|
accef4a93f | ||
|
|
9c3a955fad |
No files matched your search
@@ -38,7 +38,6 @@ import javafx.scene.paint.Paint;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.useJFXContextMenu;
|
||||
|
||||
@@ -68,14 +67,18 @@ public class JFXPasswordField extends PasswordField {
|
||||
|
||||
private void initialize() {
|
||||
this.getStyleClass().add(DEFAULT_STYLE_CLASS);
|
||||
if ("dalvik".equals(System.getProperty("java.vm.name").toLowerCase(Locale.ROOT))) {
|
||||
this.setStyle("-fx-skin: \"com.jfoenix.android.skins.JFXPasswordFieldSkinAndroid\";");
|
||||
}
|
||||
|
||||
|
||||
useJFXContextMenu(this);
|
||||
}
|
||||
|
||||
/// Prevents the legacy JFoenix skin from continuously requesting another layout pass.
|
||||
// https://github.com/HMCL-dev/HMCL/issues/5822
|
||||
// TODO: This method may no longer be needed after we update JFXTextFieldSkin
|
||||
@Override
|
||||
protected void layoutChildren() {
|
||||
super.layoutChildren();
|
||||
this.setNeedsLayout(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the style class to 'jfx-password-field'.
|
||||
* <p>
|
||||
@@ -294,4 +297,3 @@ public class JFXPasswordField extends PasswordField {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,9 +15,13 @@ import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.event.EventTarget;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.ComboBoxBase;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.Paint;
|
||||
@@ -25,6 +29,8 @@ import javafx.scene.text.Text;
|
||||
import javafx.scene.transform.Scale;
|
||||
import javafx.util.Duration;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class JFXComboBoxListViewSkin<T> extends ComboBoxListViewSkin<T> {
|
||||
protected final ObjectProperty<Paint> promptTextFill;
|
||||
private boolean invalid = true;
|
||||
@@ -150,6 +156,30 @@ public class JFXComboBoxListViewSkin<T> extends ComboBoxListViewSkin<T> {
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
for (Node child : getChildren()) {
|
||||
if (child instanceof ListView<?> listView && "list-view".equals(listView.getId())) {
|
||||
// The default hideOnClick logic of ComboBoxListViewSkin doesn't work well with FloatScrollBarSkin.
|
||||
// We implement hideOnClick ourselves to prevent the popup from
|
||||
// hiding when releasing the mouse on the scrollbar.
|
||||
|
||||
this.setHideOnClick(false);
|
||||
listView.addEventFilter(MouseEvent.MOUSE_RELEASED, t -> {
|
||||
EventTarget target = t.getTarget();
|
||||
if (target instanceof Node targetNode) {
|
||||
List<String> s = targetNode.getStyleClass();
|
||||
if (s.contains("thumb")
|
||||
|| s.contains("track")
|
||||
|| s.contains("decrement-arrow")
|
||||
|| s.contains("increment-arrow")) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
comboBox.hide();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void layoutChildren(double x, double y, double w, double h) {
|
||||
|
||||
@@ -242,6 +242,19 @@ public final class HMCLGameRepository extends DefaultGameRepository {
|
||||
clean(getRunDirectory(id));
|
||||
}
|
||||
|
||||
/// Removes a version from disk and drops any cached instance settings for that version.
|
||||
@Override
|
||||
public boolean removeVersionFromDisk(String id) {
|
||||
boolean removed = super.removeVersionFromDisk(id);
|
||||
if (removed) {
|
||||
instanceGameSettings.remove(id);
|
||||
loadedInstanceGameSettings.remove(id);
|
||||
readOnlyInstanceGameSettings.remove(id);
|
||||
beingModpackVersions.remove(id);
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
public void duplicateVersion(String srcId, String dstId, boolean copySaves) throws IOException {
|
||||
Path srcDir = getVersionRoot(srcId);
|
||||
Path dstDir = getVersionRoot(dstId);
|
||||
@@ -284,7 +297,7 @@ public final class HMCLGameRepository extends DefaultGameRepository {
|
||||
newGameSettings.getOverrideProperties().add(GameSettings.PROPERTY_RUNNING_DIRECTORY);
|
||||
newGameSettings.runningDirectoryProperty().setValue("");
|
||||
initInstanceGameSettings(dstId, newGameSettings);
|
||||
saveGameSettings(dstId);
|
||||
saveGameSettingsSync(dstId);
|
||||
|
||||
Path dstGameDir = getRunDirectory(dstId);
|
||||
|
||||
@@ -382,8 +395,9 @@ public final class HMCLGameRepository extends DefaultGameRepository {
|
||||
+ file + ", Actual: " + schemaResult.actual());
|
||||
case UNEXPECTED_ID -> LOG.warning("Unexpected instance game settings schema. Expected: "
|
||||
+ GameSettings.Instance.CURRENT_SCHEMA + ", Actual: " + schemaResult.actual());
|
||||
case UNSUPPORTED_MAJOR, READ_ONLY_PRESERVE_SCHEMA -> LOG.warning("Unsupported instance game settings schema. Expected: "
|
||||
+ GameSettings.Instance.CURRENT_SCHEMA + ", Actual: " + schemaResult.actual());
|
||||
case UNSUPPORTED_MAJOR, READ_ONLY_PRESERVE_SCHEMA ->
|
||||
LOG.warning("Unsupported instance game settings schema. Expected: "
|
||||
+ GameSettings.Instance.CURRENT_SCHEMA + ", Actual: " + schemaResult.actual());
|
||||
case READ_WRITE, READ_WRITE_PRESERVE_SCHEMA -> {
|
||||
}
|
||||
}
|
||||
@@ -520,31 +534,35 @@ public final class HMCLGameRepository extends DefaultGameRepository {
|
||||
return parentSetting != null ? parentSetting : SettingsManager.getDefaultGameSettingsPresetOrCreate();
|
||||
}
|
||||
|
||||
public GameSettings.Effective getEffectiveGameSettings(String id) {
|
||||
GameSettings.Instance instance = getInstanceGameSettings(id);
|
||||
return GameSettings.resolve(getParentGameSettings(instance), instance);
|
||||
/// Returns whether a new instance should use an isolated running directory under the default isolation settings.
|
||||
public boolean shouldIsolateNewInstance(boolean modded) {
|
||||
GameSettings.Preset preset = getParentGameSettings(null);
|
||||
DefaultIsolationType type = Lang.requireNonNullElse(preset.defaultIsolationTypeProperty().getValue(), DefaultIsolationType.MODDED);
|
||||
return switch (type) {
|
||||
case NEVER -> false;
|
||||
case ALWAYS -> true;
|
||||
case MODDED -> modded;
|
||||
};
|
||||
}
|
||||
|
||||
public void applyDefaultIsolationSetting(String id) {
|
||||
if (!hasVersion(id)) {
|
||||
/// Applies default isolation to a new instance before the version metadata is saved.
|
||||
public void applyDefaultIsolationSettingForNewInstance(String id, boolean modded) {
|
||||
if (!shouldIsolateNewInstance(modded) || readOnlyInstanceGameSettings.contains(id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
GameSettings.Instance instanceSetting = getInstanceGameSettings(id);
|
||||
GameSettings.Preset preset = getParentGameSettings(instanceSetting);
|
||||
DefaultIsolationType type = Lang.requireNonNullElse(preset.defaultIsolationTypeProperty().getValue(), DefaultIsolationType.MODDED);
|
||||
boolean isolated = switch (type) {
|
||||
case NEVER -> false;
|
||||
case ALWAYS -> true;
|
||||
case MODDED -> LibraryAnalyzer.isModded(this, getVersion(id).resolve(this));
|
||||
};
|
||||
|
||||
if (isolated) {
|
||||
GameSettings.Instance setting = instanceSetting != null ? instanceSetting : getInstanceGameSettingsOrCreate(id);
|
||||
if (setting != null) {
|
||||
setting.getOverrideProperties().add(GameSettings.PROPERTY_RUNNING_DIRECTORY);
|
||||
}
|
||||
GameSettings.Instance setting = getInstanceGameSettings(id);
|
||||
if (setting == null) {
|
||||
setting = initInstanceGameSettings(id, new GameSettings.Instance());
|
||||
}
|
||||
if (setting.getOverrideProperties().add(GameSettings.PROPERTY_RUNNING_DIRECTORY)) {
|
||||
saveGameSettings(id);
|
||||
}
|
||||
}
|
||||
|
||||
public GameSettings.Effective getEffectiveGameSettings(String id) {
|
||||
GameSettings.Instance instance = getInstanceGameSettings(id);
|
||||
return GameSettings.resolve(getParentGameSettings(instance), instance);
|
||||
}
|
||||
|
||||
public Optional<Path> getVersionIconFile(String id) {
|
||||
@@ -684,7 +702,7 @@ public final class HMCLGameRepository extends DefaultGameRepository {
|
||||
|
||||
/// Result of loading an instance-specific game settings file.
|
||||
///
|
||||
/// @param setting the loaded instance settings, or `null` when unavailable
|
||||
/// @param setting the loaded instance settings, or `null` when unavailable
|
||||
/// @param allowSave whether the file may be overwritten
|
||||
private record InstanceGameSettingsLoadResult(
|
||||
@Nullable GameSettings.Instance setting,
|
||||
@@ -694,26 +712,31 @@ public final class HMCLGameRepository extends DefaultGameRepository {
|
||||
public LaunchOptions.Builder getLaunchOptions(String version, JavaRuntime javaVersion, Path gameDir, List<String> javaAgents, List<String> javaArguments, boolean makeLaunchScript) {
|
||||
GameSettings.Effective vs = getEffectiveGameSettings(version);
|
||||
boolean noJVMOptions = vs.getInheritable(GameSettings::noJVMOptionsProperty);
|
||||
boolean autoMemory = vs.get(GameSettings::autoMemoryProperty);
|
||||
boolean autoMemory = vs.getInheritable(GameSettings::autoMemoryProperty);
|
||||
GameVersionNumber gameVersionNumber = GameVersionNumber.asGameVersion(getGameVersion(version));
|
||||
|
||||
@Nullable Integer maxMemory;
|
||||
if (autoMemory) {
|
||||
maxMemory = noJVMOptions
|
||||
? null
|
||||
: Math.toIntExact(getAutoAllocatedMemory(SystemInfo.getPhysicalMemoryStatus().available()) / 1024L / 1024L);
|
||||
} else {
|
||||
maxMemory = vs.getMaxMemory();
|
||||
}
|
||||
|
||||
LaunchOptions.Builder builder = new LaunchOptions.Builder()
|
||||
.setGameDir(gameDir)
|
||||
.setJava(javaVersion)
|
||||
.setVersionType(Metadata.TITLE)
|
||||
.setVersionName(version)
|
||||
.setProfileName(Metadata.TITLE)
|
||||
.setGameArguments(StringUtils.tokenize(vs.get(GameSettings::gameArgumentsProperty)))
|
||||
.setOverrideJavaArguments(StringUtils.tokenize(vs.get(GameSettings::jvmOptionsProperty)))
|
||||
.setMaxMemory(noJVMOptions && autoMemory ? null : (int) (getAllocatedMemory(
|
||||
vs.getMaxMemory() * 1024L * 1024L,
|
||||
SystemInfo.getPhysicalMemoryStatus().available(),
|
||||
autoMemory
|
||||
) / 1024 / 1024))
|
||||
.setMinMemory(vs.get(GameSettings::minMemoryProperty))
|
||||
.setMetaspace(Lang.toIntOrNull(vs.get(GameSettings::permSizeProperty)))
|
||||
.setGameArguments(StringUtils.tokenize(vs.getInheritable(GameSettings::gameArgumentsProperty)))
|
||||
.setOverrideJavaArguments(StringUtils.tokenize(vs.getInheritable(GameSettings::jvmOptionsProperty)))
|
||||
.setMaxMemory(maxMemory)
|
||||
.setMinMemory(vs.getInheritable(GameSettings::minMemoryProperty))
|
||||
.setMetaspace(Lang.toIntOrNull(vs.getInheritable(GameSettings::permSizeProperty)))
|
||||
.setEnvironmentVariables(
|
||||
Lang.mapOf(StringUtils.tokenize(vs.get(GameSettings::environmentVariablesProperty))
|
||||
Lang.mapOf(StringUtils.tokenize(vs.getInheritable(GameSettings::environmentVariablesProperty))
|
||||
.stream()
|
||||
.map(it -> {
|
||||
int idx = it.indexOf('=');
|
||||
@@ -731,16 +754,16 @@ public final class HMCLGameRepository extends DefaultGameRepository {
|
||||
.setPostExitCommand(vs.getInheritable(GameSettings::postExitCommandProperty))
|
||||
.setNoGeneratedJVMArgs(noJVMOptions)
|
||||
.setNoGeneratedOptimizingJVMArgs(vs.getInheritable(GameSettings::noOptimizingJVMOptionsProperty))
|
||||
.setUseCustomNatives(vs.get(GameSettings::useCustomNativesProperty))
|
||||
.setNativesDir(vs.get(GameSettings::nativesDirectoryProperty))
|
||||
.setUseCustomNatives(vs.getInheritable(GameSettings::useCustomNativesProperty))
|
||||
.setNativesDir(vs.getInheritable(GameSettings::nativesDirectoryProperty))
|
||||
.setProcessPriority(vs.getInheritable(GameSettings::processPriorityProperty))
|
||||
.setGraphicsBackend(vs.getInheritable(GameSettings::graphicsBackendProperty))
|
||||
.setRenderer(vs.getRenderer(gameVersionNumber))
|
||||
.setEnableDebugLogOutput(vs.getInheritable(GameSettings::enableDebugLogOutputProperty))
|
||||
.setAllowAutoAgent(vs.getInheritable(GameSettings::allowAutoAgentProperty))
|
||||
.setDisableAutoGameOptions(vs.getInheritable(GameSettings::disableAutoGameOptionsProperty))
|
||||
.setUseNativeGLFW(vs.get(GameSettings::useNativeGLFWProperty))
|
||||
.setUseNativeOpenAL(vs.get(GameSettings::useNativeOpenALProperty))
|
||||
.setUseNativeGLFW(vs.getInheritable(GameSettings::useNativeGLFWProperty))
|
||||
.setUseNativeOpenAL(vs.getInheritable(GameSettings::useNativeOpenALProperty))
|
||||
.setDaemon(!makeLaunchScript && vs.getInheritable(GameSettings::launcherVisibilityProperty).isDaemon())
|
||||
.setJavaAgents(javaAgents)
|
||||
.setJavaArguments(javaArguments);
|
||||
@@ -838,22 +861,21 @@ public final class HMCLGameRepository extends DefaultGameRepository {
|
||||
}
|
||||
}
|
||||
|
||||
public static long getAllocatedMemory(long minimum, long available, boolean auto) {
|
||||
if (auto) {
|
||||
available -= 512 * 1024 * 1024; // Reserve 512 MiB memory for off-heap memory and HMCL itself
|
||||
if (available <= 0) {
|
||||
return minimum;
|
||||
}
|
||||
|
||||
final long threshold = 8L * 1024 * 1024 * 1024; // 8 GiB
|
||||
final long suggested = Math.min(available <= threshold
|
||||
? (long) (available * 0.8)
|
||||
: (long) (threshold * 0.8 + (available - threshold) * 0.2),
|
||||
16L * 1024 * 1024 * 1024);
|
||||
return Math.max(minimum, suggested);
|
||||
} else {
|
||||
return minimum;
|
||||
public static long getAutoAllocatedMemory(long available) {
|
||||
long usable = available - 512 * 1024 * 1024; // Reserve 512 MiB memory for off-heap memory and HMCL itself
|
||||
if (usable <= 0) {
|
||||
return available;
|
||||
}
|
||||
|
||||
final long threshold = 8L * 1024 * 1024 * 1024; // 8 GiB
|
||||
final long suggested;
|
||||
if (usable <= threshold)
|
||||
suggested = (long) (usable * 0.8);
|
||||
else
|
||||
suggested = Math.min(
|
||||
(long) (threshold * 0.8 + (usable - threshold) * 0.2),
|
||||
16L * 1024 * 1024 * 1024);
|
||||
return suggested;
|
||||
}
|
||||
|
||||
public static ProxyOption getProxyOption() {
|
||||
|
||||
@@ -544,7 +544,7 @@ public final class LauncherHelper {
|
||||
}
|
||||
|
||||
if (violatedMandatoryConstraints.contains(JavaVersionConstraint.VANILLA_LINUX_JAVA_8)) {
|
||||
if (!setting.get(GameSettings::useCustomNativesProperty)) {
|
||||
if (!setting.getInheritable(GameSettings::useCustomNativesProperty)) {
|
||||
FXUtils.runInFX(() -> Controllers.dialog(i18n("launch.advice.vanilla_linux_java_8"), i18n("message.error"), MessageType.ERROR, breakAction));
|
||||
return result;
|
||||
} else {
|
||||
@@ -581,7 +581,7 @@ public final class LauncherHelper {
|
||||
}
|
||||
|
||||
// 32-bit JVM cannot make use of too much memory.
|
||||
if (java.getBits() == Bits.BIT_32 && setting.getMaxMemory() > 1.5 * 1024) {
|
||||
if (java.getBits() == Bits.BIT_32 && !setting.getInheritable(GameSettings::autoMemoryProperty) && setting.getMaxMemory() > 1.5 * 1024) {
|
||||
// 1.5 * 1024 is an inaccurate number.
|
||||
// Actual memory limit depends on operating system and memory.
|
||||
suggestions.add(i18n("launch.advice.too_large_memory_for_32bit"));
|
||||
@@ -628,7 +628,7 @@ public final class LauncherHelper {
|
||||
suggestions.add(i18n("launch.advice.modlauncher8"));
|
||||
break;
|
||||
case VANILLA_X86:
|
||||
if (!setting.get(GameSettings::useCustomNativesProperty)
|
||||
if (!setting.getInheritable(GameSettings::useCustomNativesProperty)
|
||||
&& Platform.isSupportedTranslationX86_64()) {
|
||||
suggestions.add(i18n("launch.advice.vanilla_x86.translation"));
|
||||
}
|
||||
@@ -640,7 +640,7 @@ public final class LauncherHelper {
|
||||
|
||||
// Cannot allocate too much memory exceeding free space.
|
||||
long totalMemorySizeMB = (long) MEGABYTES.convertFromBytes(SystemInfo.getTotalMemorySize());
|
||||
if (totalMemorySizeMB > 0 && totalMemorySizeMB < setting.getMaxMemory()) {
|
||||
if (totalMemorySizeMB > 0 && !setting.getInheritable(GameSettings::autoMemoryProperty) && totalMemorySizeMB < setting.getMaxMemory()) {
|
||||
suggestions.add(i18n("launch.advice.not_enough_space", totalMemorySizeMB));
|
||||
}
|
||||
|
||||
|
||||
@@ -358,10 +358,10 @@ public final class ModpackHelper {
|
||||
GameSettings.PROPERTY_MAX_MEMORY,
|
||||
GameSettings.PROPERTY_PERM_SIZE
|
||||
));
|
||||
setting.autoMemoryProperty().setValue(effective.get(GameSettings::autoMemoryProperty));
|
||||
setting.minMemoryProperty().setValue(effective.get(GameSettings::minMemoryProperty));
|
||||
setting.autoMemoryProperty().setValue(effective.getInheritable(GameSettings::autoMemoryProperty));
|
||||
setting.minMemoryProperty().setValue(effective.getInheritable(GameSettings::minMemoryProperty));
|
||||
setting.maxMemoryProperty().setValue(manifest.getLaunchInfo().getMinMemory());
|
||||
setting.permSizeProperty().setValue(effective.get(GameSettings::permSizeProperty));
|
||||
setting.permSizeProperty().setValue(effective.getInheritable(GameSettings::permSizeProperty));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.SettingsManager.*;
|
||||
@@ -54,6 +55,14 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
@NotNullByDefault
|
||||
public final class GameDirectoryManager {
|
||||
|
||||
/// The stable ID shared by newly created and migrated current-workspace game directories.
|
||||
static final GameDirectoryID DEFAULT_GAME_DIRECTORY_ID =
|
||||
new GameDirectoryID(new UUID(0x7105bc1f490e5e8cL, 0x878cf5844c3d4bc3L));
|
||||
|
||||
/// The stable ID shared by newly created and migrated user-home game directories.
|
||||
static final GameDirectoryID HOME_GAME_DIRECTORY_ID =
|
||||
new GameDirectoryID(new UUID(0xf3eafde8506e5a77L, 0xbc88f24b4728dfb2L));
|
||||
|
||||
/// The default current-workspace game directory path.
|
||||
private static final PortablePath CURRENT_GAME_DIRECTORY_PATH = PortablePath.of(".minecraft");
|
||||
|
||||
@@ -154,12 +163,14 @@ public final class GameDirectoryManager {
|
||||
if (localGameDirectories().isNewlyCreated() && localGameDirectories().getGameDirectories().isEmpty()) {
|
||||
needRebuildGameDirectories = true;
|
||||
GameDirectories gameDirectories = localGameDirectories();
|
||||
gameDirectories.getGameDirectories().add(new GameDirectory(newGameDirectoryId(), null, CURRENT_GAME_DIRECTORY_PATH));
|
||||
gameDirectories.getGameDirectories().add(new GameDirectory(
|
||||
DEFAULT_GAME_DIRECTORY_ID, null, CURRENT_GAME_DIRECTORY_PATH));
|
||||
}
|
||||
if (userGameDirectories().isNewlyCreated() && userGameDirectories().getGameDirectories().isEmpty()) {
|
||||
needRebuildGameDirectories = true;
|
||||
GameDirectories gameDirectories = userGameDirectories();
|
||||
gameDirectories.getGameDirectories().add(new GameDirectory(newGameDirectoryId(), null, HOME_GAME_DIRECTORY_PATH));
|
||||
gameDirectories.getGameDirectories().add(new GameDirectory(
|
||||
HOME_GAME_DIRECTORY_ID, null, HOME_GAME_DIRECTORY_PATH));
|
||||
}
|
||||
|
||||
needRebuildGameDirectories |= createDefaultGameDirectoriesIfEmpty();
|
||||
@@ -216,9 +227,11 @@ public final class GameDirectoryManager {
|
||||
if (localGameDirectories().getGameDirectories().isEmpty()
|
||||
&& userGameDirectories().getGameDirectories().isEmpty()) {
|
||||
localGameDirectories().getGameDirectories()
|
||||
.add(new GameDirectory(newGameDirectoryId(), null, CURRENT_GAME_DIRECTORY_PATH));
|
||||
.add(new GameDirectory(
|
||||
DEFAULT_GAME_DIRECTORY_ID, null, CURRENT_GAME_DIRECTORY_PATH));
|
||||
userGameDirectories().getGameDirectories()
|
||||
.add(new GameDirectory(newGameDirectoryId(), null, HOME_GAME_DIRECTORY_PATH));
|
||||
.add(new GameDirectory(
|
||||
HOME_GAME_DIRECTORY_ID, null, HOME_GAME_DIRECTORY_PATH));
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
|
||||
@@ -67,7 +67,7 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
@NotNullByDefault
|
||||
public sealed abstract class GameSettings extends ObservableSetting {
|
||||
/// Suggested maximum heap memory in MiB.
|
||||
static final int SUGGESTED_MEMORY;
|
||||
public static final int SUGGESTED_MEMORY;
|
||||
|
||||
static {
|
||||
double totalMemoryMB = MEGABYTES.convertFromBytes(SystemInfo.getTotalMemorySize());
|
||||
@@ -331,6 +331,11 @@ public sealed abstract class GameSettings extends ObservableSetting {
|
||||
}
|
||||
|
||||
/// Creates a new inheritable property.
|
||||
protected final <T extends @Nullable Object> InheritableProperty<@Nullable T> newInheritableProperty(String name) {
|
||||
return new SimpleInheritableProperty<>(this, name, null);
|
||||
}
|
||||
|
||||
/// Creates a new inheritable property with the given default value.
|
||||
protected final <T extends @UnknownNullability Object> InheritableProperty<T> newInheritableProperty(String name, T defaultValue) {
|
||||
return new SimpleInheritableProperty<>(this, name, defaultValue);
|
||||
}
|
||||
@@ -388,10 +393,10 @@ public sealed abstract class GameSettings extends ObservableSetting {
|
||||
|
||||
/// User customized JVM options.
|
||||
@SerializedName(PROPERTY_JVM_OPTIONS)
|
||||
private final SettingProperty<String> jvmOptions = newSettingProperty(PROPERTY_JVM_OPTIONS, "");
|
||||
private final InheritableProperty<String> jvmOptions = newInheritableProperty(PROPERTY_JVM_OPTIONS, "");
|
||||
|
||||
/// Returns the user customized JVM options property.
|
||||
public SettingProperty<String> jvmOptionsProperty() {
|
||||
public InheritableProperty<String> jvmOptionsProperty() {
|
||||
return jvmOptions;
|
||||
}
|
||||
|
||||
@@ -448,10 +453,10 @@ public sealed abstract class GameSettings extends ObservableSetting {
|
||||
|
||||
/// If `true`, HMCL will automatically adjust memory allocation.
|
||||
@SerializedName(PROPERTY_AUTO_MEMORY)
|
||||
private final SettingProperty<Boolean> autoMemory = newSettingProperty(PROPERTY_AUTO_MEMORY, true);
|
||||
private final InheritableProperty<Boolean> autoMemory = newInheritableProperty(PROPERTY_AUTO_MEMORY, true);
|
||||
|
||||
/// Returns the automatic memory allocation property.
|
||||
public SettingProperty<Boolean> autoMemoryProperty() {
|
||||
public InheritableProperty<Boolean> autoMemoryProperty() {
|
||||
return autoMemory;
|
||||
}
|
||||
|
||||
@@ -460,10 +465,10 @@ public sealed abstract class GameSettings extends ObservableSetting {
|
||||
|
||||
/// The minimum heap memory in MiB.
|
||||
@SerializedName(PROPERTY_MIN_MEMORY)
|
||||
private final SettingProperty<@Nullable Integer> minMemory = newSettingProperty(PROPERTY_MIN_MEMORY);
|
||||
private final InheritableProperty<@Nullable Integer> minMemory = newInheritableProperty(PROPERTY_MIN_MEMORY);
|
||||
|
||||
/// Returns the minimum heap memory property.
|
||||
public SettingProperty<@Nullable Integer> minMemoryProperty() {
|
||||
public InheritableProperty<@Nullable Integer> minMemoryProperty() {
|
||||
return minMemory;
|
||||
}
|
||||
|
||||
@@ -472,10 +477,10 @@ public sealed abstract class GameSettings extends ObservableSetting {
|
||||
|
||||
/// The maximum heap memory in MiB.
|
||||
@SerializedName(PROPERTY_MAX_MEMORY)
|
||||
private final SettingProperty<@Nullable Integer> maxMemory = newSettingProperty(PROPERTY_MAX_MEMORY, SUGGESTED_MEMORY);
|
||||
private final InheritableProperty<@Nullable Integer> maxMemory = newInheritableProperty(PROPERTY_MAX_MEMORY, SUGGESTED_MEMORY);
|
||||
|
||||
/// Returns the maximum heap memory property.
|
||||
public SettingProperty<@Nullable Integer> maxMemoryProperty() {
|
||||
public InheritableProperty<@Nullable Integer> maxMemoryProperty() {
|
||||
return maxMemory;
|
||||
}
|
||||
|
||||
@@ -484,10 +489,10 @@ public sealed abstract class GameSettings extends ObservableSetting {
|
||||
|
||||
/// The permanent generation or metaspace size in MiB.
|
||||
@SerializedName(PROPERTY_PERM_SIZE)
|
||||
private final SettingProperty<String> permSize = newSettingProperty(PROPERTY_PERM_SIZE, "");
|
||||
private final InheritableProperty<String> permSize = newInheritableProperty(PROPERTY_PERM_SIZE, "");
|
||||
|
||||
/// Returns the permanent generation or metaspace size property.
|
||||
public SettingProperty<String> permSizeProperty() {
|
||||
public InheritableProperty<String> permSizeProperty() {
|
||||
return permSize;
|
||||
}
|
||||
|
||||
@@ -593,10 +598,10 @@ public sealed abstract class GameSettings extends ObservableSetting {
|
||||
|
||||
/// User customized arguments passed to the game.
|
||||
@SerializedName(PROPERTY_GAME_ARGS)
|
||||
private final SettingProperty<String> gameArguments = newSettingProperty(PROPERTY_GAME_ARGS, "");
|
||||
private final InheritableProperty<String> gameArguments = newInheritableProperty(PROPERTY_GAME_ARGS, "");
|
||||
|
||||
/// Returns the customized game arguments property.
|
||||
public SettingProperty<String> gameArgumentsProperty() {
|
||||
public InheritableProperty<String> gameArgumentsProperty() {
|
||||
return gameArguments;
|
||||
}
|
||||
|
||||
@@ -643,10 +648,10 @@ public sealed abstract class GameSettings extends ObservableSetting {
|
||||
|
||||
/// User customized environment variables passed to the game.
|
||||
@SerializedName(PROPERTY_ENVIRONMENT_VARIABLES)
|
||||
private final SettingProperty<String> environmentVariables = newSettingProperty(PROPERTY_ENVIRONMENT_VARIABLES, "");
|
||||
private final InheritableProperty<String> environmentVariables = newInheritableProperty(PROPERTY_ENVIRONMENT_VARIABLES, "");
|
||||
|
||||
/// Returns the customized environment variables property.
|
||||
public SettingProperty<String> environmentVariablesProperty() {
|
||||
public InheritableProperty<String> environmentVariablesProperty() {
|
||||
return environmentVariables;
|
||||
}
|
||||
|
||||
@@ -763,10 +768,10 @@ public sealed abstract class GameSettings extends ObservableSetting {
|
||||
|
||||
/// If `true`, HMCL does not patch native libraries.
|
||||
@SerializedName(PROPERTY_NOT_PATCH_NATIVES)
|
||||
private final SettingProperty<Boolean> notPatchNatives = newSettingProperty(PROPERTY_NOT_PATCH_NATIVES, false);
|
||||
private final InheritableProperty<Boolean> notPatchNatives = newInheritableProperty(PROPERTY_NOT_PATCH_NATIVES, false);
|
||||
|
||||
/// Returns the native library patching property.
|
||||
public SettingProperty<Boolean> notPatchNativesProperty() {
|
||||
public InheritableProperty<Boolean> notPatchNativesProperty() {
|
||||
return notPatchNatives;
|
||||
}
|
||||
|
||||
@@ -775,10 +780,10 @@ public sealed abstract class GameSettings extends ObservableSetting {
|
||||
|
||||
/// If `true`, HMCL leaves native library management to the user.
|
||||
@SerializedName(PROPERTY_USE_CUSTOM_NATIVES)
|
||||
private final SettingProperty<Boolean> useCustomNatives = newSettingProperty(PROPERTY_USE_CUSTOM_NATIVES, false);
|
||||
private final InheritableProperty<Boolean> useCustomNatives = newInheritableProperty(PROPERTY_USE_CUSTOM_NATIVES, false);
|
||||
|
||||
/// Returns the custom native libraries property.
|
||||
public SettingProperty<Boolean> useCustomNativesProperty() {
|
||||
public InheritableProperty<Boolean> useCustomNativesProperty() {
|
||||
return useCustomNatives;
|
||||
}
|
||||
|
||||
@@ -787,10 +792,10 @@ public sealed abstract class GameSettings extends ObservableSetting {
|
||||
|
||||
/// The path to the native library directory, or an empty string for the default path.
|
||||
@SerializedName(PROPERTY_NATIVES_DIRECTORY)
|
||||
private final SettingProperty<String> nativesDirectory = newSettingProperty(PROPERTY_NATIVES_DIRECTORY, "");
|
||||
private final InheritableProperty<String> nativesDirectory = newInheritableProperty(PROPERTY_NATIVES_DIRECTORY, "");
|
||||
|
||||
/// Returns the native library directory property.
|
||||
public SettingProperty<String> nativesDirectoryProperty() {
|
||||
public InheritableProperty<String> nativesDirectoryProperty() {
|
||||
return nativesDirectory;
|
||||
}
|
||||
|
||||
@@ -799,10 +804,10 @@ public sealed abstract class GameSettings extends ObservableSetting {
|
||||
|
||||
/// If `true`, HMCL will use native GLFW.
|
||||
@SerializedName(PROPERTY_USE_NATIVE_GLFW)
|
||||
private final SettingProperty<Boolean> useNativeGLFW = newSettingProperty(PROPERTY_USE_NATIVE_GLFW, false);
|
||||
private final InheritableProperty<Boolean> useNativeGLFW = newInheritableProperty(PROPERTY_USE_NATIVE_GLFW, false);
|
||||
|
||||
/// Returns the native GLFW property.
|
||||
public SettingProperty<Boolean> useNativeGLFWProperty() {
|
||||
public InheritableProperty<Boolean> useNativeGLFWProperty() {
|
||||
return useNativeGLFW;
|
||||
}
|
||||
|
||||
@@ -811,10 +816,10 @@ public sealed abstract class GameSettings extends ObservableSetting {
|
||||
|
||||
/// If `true`, HMCL will use native OpenAL.
|
||||
@SerializedName(PROPERTY_USE_NATIVE_OPENAL)
|
||||
private final SettingProperty<Boolean> useNativeOpenAL = newSettingProperty(PROPERTY_USE_NATIVE_OPENAL, false);
|
||||
private final InheritableProperty<Boolean> useNativeOpenAL = newInheritableProperty(PROPERTY_USE_NATIVE_OPENAL, false);
|
||||
|
||||
/// Returns the native OpenAL property.
|
||||
public SettingProperty<Boolean> useNativeOpenALProperty() {
|
||||
public InheritableProperty<Boolean> useNativeOpenALProperty() {
|
||||
return useNativeOpenAL;
|
||||
}
|
||||
|
||||
@@ -898,11 +903,6 @@ public sealed abstract class GameSettings extends ObservableSetting {
|
||||
return instance;
|
||||
}
|
||||
|
||||
/// Returns the effective value for a property whose override state is tracked by the property itself.
|
||||
public <T extends @UnknownNullability Object> T get(Function<GameSettings, SettingProperty<T>> propertyGetter) {
|
||||
return inherited(preset, instance, propertyGetter);
|
||||
}
|
||||
|
||||
/// Returns the effective value for an inheritable property.
|
||||
public <T extends @UnknownNullability Object> T getInheritable(
|
||||
Function<GameSettings, InheritableProperty<T>> propertyGetter) {
|
||||
@@ -981,7 +981,7 @@ public sealed abstract class GameSettings extends ObservableSetting {
|
||||
|
||||
/// Returns the effective maximum heap memory in MiB.
|
||||
public int getMaxMemory() {
|
||||
Integer value = get(GameSettings::maxMemoryProperty);
|
||||
Integer value = getInheritable(GameSettings::maxMemoryProperty);
|
||||
return value != null && value > 0 ? value : SUGGESTED_MEMORY;
|
||||
}
|
||||
|
||||
|
||||
@@ -87,6 +87,9 @@ public final class LauncherSettings extends ObservableSetting implements JsonSch
|
||||
/// Theme appearance override key for title-bar transparency.
|
||||
public static final String THEME_APPEARANCE_TITLE_BAR_TRANSPARENT = "titleBarTransparent";
|
||||
|
||||
/// Theme appearance override key for window transparency.
|
||||
public static final String THEME_APPEARANCE_WINDOW_TRANSPARENT = "windowTransparent";
|
||||
|
||||
/// Theme appearance override key for the primary background source.
|
||||
public static final String THEME_APPEARANCE_BACKGROUND = "background";
|
||||
|
||||
@@ -336,6 +339,15 @@ public final class LauncherSettings extends ObservableSetting implements JsonSch
|
||||
return titleBarTransparent;
|
||||
}
|
||||
|
||||
/// Whether translucent launcher backgrounds reveal content behind the window.
|
||||
@SerializedName("windowTransparent")
|
||||
private final BooleanProperty windowTransparent = new SimpleBooleanProperty(false);
|
||||
|
||||
/// Returns the window transparency property.
|
||||
public BooleanProperty windowTransparentProperty() {
|
||||
return windowTransparent;
|
||||
}
|
||||
|
||||
// Background source
|
||||
|
||||
/// The launcher background source type.
|
||||
|
||||
@@ -81,12 +81,6 @@ public final class LegacyConfigMigrator {
|
||||
/// The legacy built-in profile name for the user-home game directory.
|
||||
private static final String LEGACY_HOME_PROFILE = "Home";
|
||||
|
||||
/// The legacy built-in current-workspace profile ID.
|
||||
private static final GameDirectoryID LEGACY_DEFAULT_PROFILE_ID = getLegacyProfileID(LEGACY_DEFAULT_PROFILE);
|
||||
|
||||
/// The legacy built-in user-home profile ID.
|
||||
private static final GameDirectoryID LEGACY_HOME_PROFILE_ID = getLegacyProfileID(LEGACY_HOME_PROFILE);
|
||||
|
||||
/// The legacy Windows and portable configuration file name used before HMCL 3.16.
|
||||
private static final String LEGACY_CONFIG_FILENAME = "hmcl.json";
|
||||
|
||||
@@ -261,6 +255,10 @@ public final class LegacyConfigMigrator {
|
||||
renameMember(jsonObject, "bgpaint", "customBackgroundPaint");
|
||||
renameMember(jsonObject, "backgroundPaint", "customBackgroundPaint");
|
||||
migrateBackgroundOpacity(jsonObject, themeAppearanceOverrides);
|
||||
jsonObject.addProperty("windowTransparent", true);
|
||||
addThemeAppearanceOverride(
|
||||
themeAppearanceOverrides,
|
||||
LauncherSettings.THEME_APPEARANCE_WINDOW_TRANSPARENT);
|
||||
writeThemeAppearanceOverrides(jsonObject, themeAppearanceOverrides);
|
||||
renameMember(jsonObject, "proxyUserName", "proxyUser");
|
||||
migrateLegacySelectedVersions(jsonObject);
|
||||
@@ -1359,10 +1357,10 @@ public final class LegacyConfigMigrator {
|
||||
|
||||
/// Returns the legacy profile name used in `configurations`.
|
||||
private static @Nullable String getLegacyProfileName(GameDirectory gameDirectory) {
|
||||
if (LEGACY_DEFAULT_PROFILE_ID.equals(gameDirectory.getId())) {
|
||||
if (GameDirectoryManager.DEFAULT_GAME_DIRECTORY_ID.equals(gameDirectory.getId())) {
|
||||
return LEGACY_DEFAULT_PROFILE;
|
||||
}
|
||||
if (LEGACY_HOME_PROFILE_ID.equals(gameDirectory.getId())) {
|
||||
if (GameDirectoryManager.HOME_GAME_DIRECTORY_ID.equals(gameDirectory.getId())) {
|
||||
return LEGACY_HOME_PROFILE;
|
||||
}
|
||||
return GameDirectoryManager.getGameDirectoryCustomName(gameDirectory);
|
||||
|
||||
@@ -25,6 +25,9 @@ public enum ThemeColorType {
|
||||
/// Uses the built-in launcher default theme color.
|
||||
DEFAULT,
|
||||
|
||||
/// Follows the accent color provided by the operating system.
|
||||
SYSTEM,
|
||||
|
||||
/// Uses the custom launcher theme color selected by the user.
|
||||
CUSTOM,
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.theme;
|
||||
|
||||
import javafx.scene.layout.Background;
|
||||
import org.jetbrains.annotations.NotNullByDefault;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/// A launcher background and the opacity applied by its rendering node.
|
||||
///
|
||||
/// @param background the JavaFX background rendered by the launcher
|
||||
/// @param opacity the node opacity in the range `[0, 1]`
|
||||
@NotNullByDefault
|
||||
public record LauncherBackground(Background background, double opacity) {
|
||||
/// Creates a launcher background value.
|
||||
public LauncherBackground {
|
||||
Objects.requireNonNull(background);
|
||||
if (!Double.isFinite(opacity) || opacity < 0.0 || opacity > 1.0) {
|
||||
throw new IllegalArgumentException("Launcher background opacity must be between 0 and 1: " + opacity);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -43,6 +43,7 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
/// @param contrast the MonetFX contrast level, or `null` when inherited
|
||||
/// @param background the background settings, or `null` when inherited
|
||||
/// @param titleBar the title-bar settings, or `null` when inherited
|
||||
/// @param windowTransparent whether the launcher window should be transparent, or `null` when inherited
|
||||
@NotNullByDefault
|
||||
public record ThemeAppearance(
|
||||
@Nullable ThemeColorSource color,
|
||||
@@ -50,7 +51,8 @@ public record ThemeAppearance(
|
||||
@Nullable ColorStyle colorStyle,
|
||||
@Nullable Contrast contrast,
|
||||
@Nullable ThemeBackgroundSettings background,
|
||||
@Nullable ThemeTitleBar titleBar) {
|
||||
@Nullable ThemeTitleBar titleBar,
|
||||
@Nullable Boolean windowTransparent) {
|
||||
|
||||
/// JSON member name for the color seed.
|
||||
static final String FIELD_COLOR = "color";
|
||||
@@ -70,6 +72,9 @@ public record ThemeAppearance(
|
||||
/// JSON member name for title-bar settings.
|
||||
static final String FIELD_TITLE_BAR = "titleBar";
|
||||
|
||||
/// JSON member name for window transparency.
|
||||
static final String FIELD_WINDOW_TRANSPARENT = "windowTransparent";
|
||||
|
||||
/// Creates an appearance patch.
|
||||
///
|
||||
/// @param color the color seed source, or `null` when inherited
|
||||
@@ -78,6 +83,7 @@ public record ThemeAppearance(
|
||||
/// @param contrast the MonetFX contrast level, or `null` when inherited
|
||||
/// @param background the background settings, or `null` when inherited
|
||||
/// @param titleBar the title-bar settings, or `null` when inherited
|
||||
/// @param windowTransparent whether the launcher window should be transparent, or `null` when inherited
|
||||
public ThemeAppearance {
|
||||
if (background != null && background.isEmpty()) {
|
||||
background = null;
|
||||
@@ -100,7 +106,8 @@ public record ThemeAppearance(
|
||||
readColorStyle(object),
|
||||
readContrast(object),
|
||||
readBackground(object),
|
||||
readTitleBar(object));
|
||||
readTitleBar(object),
|
||||
readWindowTransparent(object));
|
||||
}
|
||||
|
||||
/// Returns whether this appearance contains no concrete fields.
|
||||
@@ -112,7 +119,8 @@ public record ThemeAppearance(
|
||||
&& colorStyle == null
|
||||
&& contrast == null
|
||||
&& background == null
|
||||
&& titleBar == null;
|
||||
&& titleBar == null
|
||||
&& windowTransparent == null;
|
||||
}
|
||||
|
||||
/// Adds this appearance patch's concrete fields to a JSON object.
|
||||
@@ -139,6 +147,9 @@ public record ThemeAppearance(
|
||||
if (titleBar != null) {
|
||||
object.add(FIELD_TITLE_BAR, titleBar.toJsonObject());
|
||||
}
|
||||
if (windowTransparent != null) {
|
||||
object.addProperty(FIELD_WINDOW_TRANSPARENT, windowTransparent);
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts this appearance patch to its JSON representation.
|
||||
@@ -167,7 +178,8 @@ public record ThemeAppearance(
|
||||
: patch.background != null ? patch.background : background,
|
||||
titleBar != null && patch.titleBar != null
|
||||
? titleBar.merge(patch.titleBar)
|
||||
: patch.titleBar != null ? patch.titleBar : titleBar);
|
||||
: patch.titleBar != null ? patch.titleBar : titleBar,
|
||||
patch.windowTransparent != null ? patch.windowTransparent : windowTransparent);
|
||||
}
|
||||
|
||||
/// Converts this appearance to concrete launcher theme values.
|
||||
@@ -309,6 +321,19 @@ public record ThemeAppearance(
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads the optional window transparency field.
|
||||
private static @Nullable Boolean readWindowTransparent(JsonObject object) {
|
||||
JsonElement element = object.get(FIELD_WINDOW_TRANSPARENT);
|
||||
if (element == null) {
|
||||
return null;
|
||||
}
|
||||
if (!(element instanceof JsonPrimitive primitive) || !primitive.isBoolean()) {
|
||||
LOG.warning("Ignored invalid theme windowTransparent: expected a boolean, got " + element);
|
||||
return null;
|
||||
}
|
||||
return primitive.getAsBoolean();
|
||||
}
|
||||
|
||||
/// Reads an optional string field.
|
||||
private static @Nullable String readString(JsonObject object, String field) {
|
||||
JsonElement element = object.get(field);
|
||||
|
||||
@@ -660,7 +660,8 @@ public final class ThemePackManager {
|
||||
currentColorStyle(),
|
||||
null,
|
||||
background,
|
||||
new ThemeTitleBar(currentTitleBarTransparent()));
|
||||
new ThemeTitleBar(currentTitleBarTransparent()),
|
||||
currentWindowTransparent());
|
||||
Theme theme = new Theme(
|
||||
null,
|
||||
null,
|
||||
@@ -711,6 +712,9 @@ public final class ThemePackManager {
|
||||
if (themeColorType == ThemeColorType.DEFAULT) {
|
||||
return ThemeColorSource.DEFAULT;
|
||||
}
|
||||
if (themeColorType == ThemeColorType.SYSTEM) {
|
||||
return ThemeColorSource.custom(Themes.getSystemThemeColor());
|
||||
}
|
||||
if (themeColorType == ThemeColorType.BACKGROUND) {
|
||||
if (backgroundType == BackgroundType.THEME_COLOR) {
|
||||
return ThemeColorSource.DEFAULT;
|
||||
@@ -755,6 +759,13 @@ public final class ThemePackManager {
|
||||
: Objects.requireNonNullElse(resolveCurrentTitleBarTransparent(currentResolveContext()), false);
|
||||
}
|
||||
|
||||
/// Returns whether the current launcher window should be transparent.
|
||||
private static boolean currentWindowTransparent() throws IOException {
|
||||
return SettingsManager.settings().getThemeAppearanceOverrides().contains(LauncherSettings.THEME_APPEARANCE_WINDOW_TRANSPARENT)
|
||||
? settings().windowTransparentProperty().get()
|
||||
: Objects.requireNonNullElse(resolveCurrentWindowTransparent(currentResolveContext()), false);
|
||||
}
|
||||
|
||||
/// Resolves the selected theme's controlled brightness.
|
||||
///
|
||||
/// @param context the condition resolution context
|
||||
@@ -795,6 +806,16 @@ public final class ThemePackManager {
|
||||
return appearance != null && appearance.titleBar() != null ? appearance.titleBar().transparent() : null;
|
||||
}
|
||||
|
||||
/// Resolves the selected theme window transparency directive.
|
||||
///
|
||||
/// @param context the condition resolution context
|
||||
/// @return the selected theme window transparency directive, or `null` when unavailable
|
||||
/// @throws IOException if the selected theme pack cannot be read
|
||||
public static @Nullable Boolean resolveCurrentWindowTransparent(ThemeResolveContext context) throws IOException {
|
||||
@Nullable ThemeAppearance appearance = resolveCurrentThemeAppearance(context);
|
||||
return appearance != null ? appearance.windowTransparent() : null;
|
||||
}
|
||||
|
||||
/// Resolves the current selected theme color source without extracting any wallpaper pixels.
|
||||
///
|
||||
/// @param context the condition resolution context
|
||||
|
||||
@@ -32,9 +32,6 @@ import javafx.collections.SetChangeListener;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.PixelReader;
|
||||
import javafx.scene.image.PixelWriter;
|
||||
import javafx.scene.image.WritableImage;
|
||||
import javafx.scene.layout.Background;
|
||||
import javafx.scene.layout.BackgroundFill;
|
||||
import javafx.scene.layout.BackgroundImage;
|
||||
@@ -115,6 +112,9 @@ public final class Themes {
|
||||
if (FXUtils.DARK_MODE != null) {
|
||||
observables.add(FXUtils.DARK_MODE);
|
||||
}
|
||||
if (FXUtils.ACCENT_COLOR != null) {
|
||||
observables.add(FXUtils.ACCENT_COLOR);
|
||||
}
|
||||
bind(observables.toArray(new Observable[0]));
|
||||
}
|
||||
|
||||
@@ -195,6 +195,7 @@ public final class Themes {
|
||||
}
|
||||
return switch (themeColorType) {
|
||||
case DEFAULT -> ThemeColor.DEFAULT;
|
||||
case SYSTEM -> getSystemThemeColor();
|
||||
case CUSTOM -> fallback;
|
||||
case BACKGROUND -> resolveWallpaperThemeColor(fallback, backgroundType);
|
||||
};
|
||||
@@ -282,9 +283,11 @@ public final class Themes {
|
||||
colorScheme
|
||||
);
|
||||
|
||||
/// The current JavaFX launcher background.
|
||||
private static final ReadOnlyObjectWrapper<Background> background = new ReadOnlyObjectWrapper<>(
|
||||
new Background(new BackgroundFill(Color.WHITE, CornerRadii.EMPTY, Insets.EMPTY)));
|
||||
/// The current JavaFX launcher background and its node opacity.
|
||||
private static final ReadOnlyObjectWrapper<LauncherBackground> background = new ReadOnlyObjectWrapper<>(
|
||||
new LauncherBackground(
|
||||
new Background(new BackgroundFill(Color.WHITE, CornerRadii.EMPTY, Insets.EMPTY)),
|
||||
1.0));
|
||||
|
||||
/// The last loaded background source used to rebuild the JavaFX background without reloading images.
|
||||
private static @Nullable LoadedBackground loadedBackground;
|
||||
@@ -300,8 +303,8 @@ public final class Themes {
|
||||
|
||||
/// A loaded JavaFX background source and the wallpaper color extracted while loading it.
|
||||
private sealed interface LoadedBackground {
|
||||
/// Returns the loaded JavaFX background.
|
||||
Background background();
|
||||
/// Returns the loaded JavaFX background and its node opacity.
|
||||
LauncherBackground background();
|
||||
|
||||
/// Returns whether this background is the configured fallback background.
|
||||
boolean fallbackBackground();
|
||||
@@ -316,7 +319,7 @@ public final class Themes {
|
||||
/// @param wallpaperThemeColor the seed color extracted from [#image], or `null` when unavailable
|
||||
/// @param fallbackBackground whether this background is the configured fallback background
|
||||
record Image(
|
||||
Background background,
|
||||
LauncherBackground background,
|
||||
javafx.scene.image.Image image,
|
||||
@Nullable ThemeColor wallpaperThemeColor,
|
||||
boolean fallbackBackground) implements LoadedBackground {
|
||||
@@ -339,7 +342,7 @@ public final class Themes {
|
||||
/// @param wallpaperThemeColor the seed color represented by [#paint], or `null` when unavailable
|
||||
/// @param fallbackBackground whether this background is the configured fallback background
|
||||
record Paint(
|
||||
Background background,
|
||||
LauncherBackground background,
|
||||
@Nullable javafx.scene.paint.Paint paint,
|
||||
@Nullable ThemeColor wallpaperThemeColor,
|
||||
boolean fallbackBackground) implements LoadedBackground {
|
||||
@@ -359,7 +362,7 @@ public final class Themes {
|
||||
/// @param background the loaded JavaFX background
|
||||
/// @param fallbackBackground whether this background is the configured fallback background
|
||||
record ThemeColorFill(
|
||||
Background background,
|
||||
LauncherBackground background,
|
||||
boolean fallbackBackground) implements LoadedBackground {
|
||||
/// Creates a loaded theme-color background.
|
||||
///
|
||||
@@ -528,6 +531,15 @@ public final class Themes {
|
||||
return defaultBrightness = brightness;
|
||||
}
|
||||
|
||||
/// Returns the current operating system accent color, or the launcher default when unavailable.
|
||||
public static ThemeColor getSystemThemeColor() {
|
||||
if (FXUtils.ACCENT_COLOR == null) {
|
||||
return ThemeColor.DEFAULT;
|
||||
}
|
||||
@Nullable Color accentColor = FXUtils.ACCENT_COLOR.get();
|
||||
return accentColor != null ? ThemeColor.of(accentColor) : ThemeColor.DEFAULT;
|
||||
}
|
||||
|
||||
/// Returns the current resolved launcher theme.
|
||||
public static ResolvedTheme getTheme() {
|
||||
return theme.get();
|
||||
@@ -543,8 +555,8 @@ public final class Themes {
|
||||
return colorScheme.get();
|
||||
}
|
||||
|
||||
/// Returns the current JavaFX launcher background property.
|
||||
public static ReadOnlyObjectProperty<Background> backgroundProperty() {
|
||||
/// Returns the current JavaFX launcher background and node opacity property.
|
||||
public static ReadOnlyObjectProperty<LauncherBackground> backgroundProperty() {
|
||||
startBackgroundUpdates();
|
||||
return background.getReadOnlyProperty();
|
||||
}
|
||||
@@ -824,7 +836,8 @@ public final class Themes {
|
||||
}
|
||||
@Nullable LoadedBackground loaded = loadedBackground;
|
||||
if (loaded instanceof LoadedBackground.ThemeColorFill themeColorFill) {
|
||||
Background newBackground = createThemeColorBackground(getLoadedBackgroundOpacity(loaded.fallbackBackground()));
|
||||
LauncherBackground newBackground =
|
||||
createThemeColorBackground(getLoadedBackgroundOpacity(loaded.fallbackBackground()));
|
||||
loadedBackground = new LoadedBackground.ThemeColorFill(newBackground, themeColorFill.fallbackBackground());
|
||||
background.set(newBackground);
|
||||
}
|
||||
@@ -858,17 +871,23 @@ public final class Themes {
|
||||
LoadedBackground refreshed;
|
||||
if (loaded instanceof LoadedBackground.Image imageBackground) {
|
||||
refreshed = new LoadedBackground.Image(
|
||||
createBackgroundWithOpacity(imageBackground.image(), opacity),
|
||||
new LauncherBackground(
|
||||
imageBackground.background().background(),
|
||||
MathUtils.clamp(opacity, 0., 1.)),
|
||||
imageBackground.image(),
|
||||
imageBackground.wallpaperThemeColor(),
|
||||
imageBackground.fallbackBackground());
|
||||
} else if (loaded instanceof LoadedBackground.ThemeColorFill themeColorFill) {
|
||||
refreshed = new LoadedBackground.ThemeColorFill(
|
||||
createThemeColorBackground(opacity),
|
||||
new LauncherBackground(
|
||||
themeColorFill.background().background(),
|
||||
MathUtils.clamp(opacity, 0., 1.)),
|
||||
themeColorFill.fallbackBackground());
|
||||
} else if (loaded instanceof LoadedBackground.Paint paintBackground) {
|
||||
refreshed = new LoadedBackground.Paint(
|
||||
createPaintBackground(paintBackground.paint(), opacity),
|
||||
new LauncherBackground(
|
||||
paintBackground.background().background(),
|
||||
MathUtils.clamp(opacity, 0., 1.)),
|
||||
paintBackground.paint(),
|
||||
paintBackground.wallpaperThemeColor(),
|
||||
paintBackground.fallbackBackground());
|
||||
@@ -917,60 +936,37 @@ public final class Themes {
|
||||
return FXUtils.loadImage(new CacheFileTask(url).run());
|
||||
}
|
||||
|
||||
/// Creates a JavaFX paint background with the requested opacity.
|
||||
private static Background createPaintBackground(@Nullable Paint paint, double opacity) {
|
||||
opacity = MathUtils.clamp(opacity, 0., 1.);
|
||||
if (paint instanceof Color || paint == null) {
|
||||
Color color = (Color) paint;
|
||||
if (color == null) {
|
||||
color = Color.WHITE;
|
||||
}
|
||||
if (opacity < 1.) {
|
||||
color = new Color(color.getRed(), color.getGreen(), color.getBlue(), color.getOpacity() * opacity);
|
||||
}
|
||||
return new Background(new BackgroundFill(color, CornerRadii.EMPTY, Insets.EMPTY));
|
||||
} else {
|
||||
return new Background(new BackgroundFill(paint, CornerRadii.EMPTY, Insets.EMPTY));
|
||||
}
|
||||
/// Creates a JavaFX paint background with a separately stored node opacity.
|
||||
private static LauncherBackground createPaintBackground(@Nullable Paint paint, double opacity) {
|
||||
return new LauncherBackground(
|
||||
new Background(new BackgroundFill(
|
||||
Objects.requireNonNullElse(paint, Color.WHITE),
|
||||
CornerRadii.EMPTY,
|
||||
Insets.EMPTY)),
|
||||
MathUtils.clamp(opacity, 0., 1.));
|
||||
}
|
||||
|
||||
/// Creates a JavaFX background from the current theme color scheme surface container.
|
||||
private static Background createThemeColorBackground(double opacity) {
|
||||
private static LauncherBackground createThemeColorBackground(double opacity) {
|
||||
return createPaintBackground(getColorScheme().getColor(ColorRole.SURFACE_CONTAINER), opacity);
|
||||
}
|
||||
|
||||
/// Creates a JavaFX image background with the requested opacity.
|
||||
private static Background createBackgroundWithOpacity(Image image, double opacity) {
|
||||
PixelReader pixelReader = image.getPixelReader();
|
||||
if (opacity <= 0) {
|
||||
return new Background(new BackgroundFill(new Color(1, 1, 1, 0), CornerRadii.EMPTY, Insets.EMPTY));
|
||||
} else if (opacity >= 1. || pixelReader == null) {
|
||||
return new Background(new BackgroundImage(
|
||||
image,
|
||||
BackgroundRepeat.NO_REPEAT,
|
||||
BackgroundRepeat.NO_REPEAT,
|
||||
BackgroundPosition.DEFAULT,
|
||||
new BackgroundSize(800, 480, false, false, true, true)
|
||||
));
|
||||
} else {
|
||||
WritableImage tempImage = new WritableImage((int) image.getWidth(), (int) image.getHeight());
|
||||
PixelWriter pixelWriter = tempImage.getPixelWriter();
|
||||
for (int y = 0; y < image.getHeight(); y++) {
|
||||
for (int x = 0; x < image.getWidth(); x++) {
|
||||
Color color = pixelReader.getColor(x, y);
|
||||
Color newColor = new Color(color.getRed(), color.getGreen(), color.getBlue(), color.getOpacity() * opacity);
|
||||
pixelWriter.setColor(x, y, newColor);
|
||||
}
|
||||
}
|
||||
|
||||
return new Background(new BackgroundImage(
|
||||
tempImage,
|
||||
BackgroundRepeat.NO_REPEAT,
|
||||
BackgroundRepeat.NO_REPEAT,
|
||||
BackgroundPosition.DEFAULT,
|
||||
new BackgroundSize(800, 480, false, false, true, true)
|
||||
));
|
||||
}
|
||||
/// Creates a JavaFX image background with a separately stored node opacity.
|
||||
private static LauncherBackground createBackgroundWithOpacity(Image image, double opacity) {
|
||||
Background imageBackground = new Background(new BackgroundImage(
|
||||
image,
|
||||
BackgroundRepeat.NO_REPEAT,
|
||||
BackgroundRepeat.NO_REPEAT,
|
||||
BackgroundPosition.DEFAULT,
|
||||
new BackgroundSize(
|
||||
BackgroundSize.AUTO,
|
||||
BackgroundSize.AUTO,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true)
|
||||
));
|
||||
return new LauncherBackground(imageBackground, MathUtils.clamp(opacity, 0., 1.));
|
||||
}
|
||||
|
||||
/// Loads a default launcher background image from local workspace files.
|
||||
@@ -1073,6 +1069,27 @@ public final class Themes {
|
||||
FXUtils.DARK_MODE != null ? FXUtils.DARK_MODE : settings().themeBrightnessModeProperty()
|
||||
);
|
||||
|
||||
/// Whether the launcher window should be transparent after applying launcher and theme settings.
|
||||
private static final BooleanBinding windowTransparent = Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
if (settings().getThemeAppearanceOverrides().contains(LauncherSettings.THEME_APPEARANCE_WINDOW_TRANSPARENT)) {
|
||||
return settings().windowTransparentProperty().get();
|
||||
}
|
||||
try {
|
||||
return Objects.requireNonNullElse(
|
||||
ThemePackManager.resolveCurrentWindowTransparent(ThemePackManager.currentResolveContext()),
|
||||
false);
|
||||
} catch (IOException | RuntimeException e) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
settings().windowTransparentProperty(),
|
||||
settings().getThemeAppearanceOverrides(),
|
||||
settings().selectedThemeProperty(),
|
||||
settings().themeBrightnessModeProperty(),
|
||||
FXUtils.DARK_MODE != null ? FXUtils.DARK_MODE : settings().themeBrightnessModeProperty()
|
||||
);
|
||||
|
||||
/// The title text fill derived from the current color scheme.
|
||||
private static final ObjectBinding<Color> titleFill = Bindings.createObjectBinding(
|
||||
() -> titleBarTransparent.get()
|
||||
@@ -1092,6 +1109,11 @@ public final class Themes {
|
||||
return titleBarTransparent;
|
||||
}
|
||||
|
||||
/// Returns whether the launcher window should be transparent after applying launcher and theme settings.
|
||||
public static BooleanBinding windowTransparentProperty() {
|
||||
return windowTransparent;
|
||||
}
|
||||
|
||||
/// Returns whether the current color scheme uses dark brightness.
|
||||
public static BooleanBinding darkModeProperty() {
|
||||
return darkMode;
|
||||
|
||||
@@ -338,6 +338,13 @@ public class InstallerItem extends Control {
|
||||
throw new AssertionError("Unknown state type: " + state.getClass());
|
||||
}
|
||||
}, control.resolvedStateProperty));
|
||||
FXUtils.onChangeAndOperate(
|
||||
control.resolvedStateProperty,
|
||||
state -> statusLabel.pseudoClassStateChanged(
|
||||
PseudoClass.getPseudoClass("incompatible"),
|
||||
state instanceof IncompatibleState
|
||||
)
|
||||
);
|
||||
BorderPane.setMargin(statusLabel, new Insets(0, 0, 0, 8));
|
||||
BorderPane.setAlignment(statusLabel, Pos.CENTER_LEFT);
|
||||
|
||||
|
||||
@@ -121,6 +121,7 @@ public final class AccountListItemSkin extends SkinBase<AccountListItem> {
|
||||
JFXButton btnRefresh = FXUtils.newToggleButton4(SVG.REFRESH);
|
||||
SpinnerPane spinnerRefresh = new SpinnerPane();
|
||||
spinnerRefresh.getStyleClass().setAll("small-spinner-pane");
|
||||
spinnerRefresh.setPrefSize(30.0, 30.0);
|
||||
if (skinnable.getAccount() instanceof MicrosoftAccount && Accounts.OAUTH_CALLBACK.getClientId().isEmpty()) {
|
||||
btnRefresh.setDisable(true);
|
||||
FXUtils.installFastTooltip(spinnerRefresh, i18n("account.methods.microsoft.snapshot.tooltip"));
|
||||
|
||||
@@ -29,7 +29,10 @@ import javafx.scene.Node;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.*;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class ComponentList extends Control implements NoPaddingComponent {
|
||||
|
||||
@@ -136,13 +139,25 @@ public class ComponentList extends Control implements NoPaddingComponent {
|
||||
}
|
||||
|
||||
public static Node createComponentListTitle(String title) {
|
||||
HBox node = new HBox();
|
||||
return createComponentListTitle(title, null);
|
||||
}
|
||||
|
||||
public static Node createComponentListTitle(String title, @Nullable String helpMessage) {
|
||||
HBox node = new HBox(4);
|
||||
node.setAlignment(Pos.CENTER_LEFT);
|
||||
node.setPadding(new Insets(8, 0, 0, 0));
|
||||
|
||||
{
|
||||
Label advanced = new Label(title);
|
||||
node.getChildren().setAll(advanced);
|
||||
}
|
||||
|
||||
if (helpMessage != null) {
|
||||
var helpIcon = new StackPane(SVG.HELP.createIcon(16));
|
||||
FXUtils.installFastTooltip(helpIcon, helpMessage);
|
||||
node.getChildren().add(helpIcon);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,8 +23,8 @@ import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.binding.NumberBinding;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.geometry.Orientation;
|
||||
import javafx.geometry.Point2D;
|
||||
import javafx.scene.Node;
|
||||
@@ -43,11 +43,10 @@ import org.jackhuang.hmcl.util.MathUtils;
|
||||
public class FloatScrollBarSkin implements Skin<ScrollBar> {
|
||||
private ScrollBar scrollBar;
|
||||
private Region group;
|
||||
private Rectangle track = new Rectangle();
|
||||
private Rectangle thumb = new Rectangle();
|
||||
private final Rectangle track = new Rectangle();
|
||||
private final Rectangle thumb = new Rectangle();
|
||||
|
||||
@SuppressWarnings({"FieldCanBeLocal", "FieldMayBeFinal"})
|
||||
private ChangeListener<Boolean> thumbHoverListener;
|
||||
private BooleanBinding hoverOrPressed;
|
||||
private Animation thumbHoverAnimation;
|
||||
|
||||
public FloatScrollBarSkin(final ScrollBar scrollBar) {
|
||||
@@ -214,7 +213,8 @@ public class FloatScrollBarSkin implements Skin<ScrollBar> {
|
||||
}
|
||||
};
|
||||
|
||||
this.thumbHoverListener = FXUtils.onWeakChangeAndOperate(thumb.hoverProperty(), newValue -> {
|
||||
this.hoverOrPressed = thumb.hoverProperty().or(thumb.pressedProperty());
|
||||
FXUtils.onChangeAndOperate(hoverOrPressed, newValue -> {
|
||||
if (thumbHoverAnimation != null) {
|
||||
thumbHoverAnimation.stop();
|
||||
thumbHoverAnimation = null;
|
||||
@@ -249,7 +249,7 @@ public class FloatScrollBarSkin implements Skin<ScrollBar> {
|
||||
thumbHoverAnimation.stop();
|
||||
thumbHoverAnimation = null;
|
||||
}
|
||||
thumbHoverListener = null;
|
||||
hoverOrPressed = null;
|
||||
scrollBar = null;
|
||||
group = null;
|
||||
}
|
||||
|
||||
@@ -26,10 +26,12 @@ import javafx.geometry.Insets;
|
||||
import javafx.geometry.Orientation;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.control.ContentDisplay;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.*;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -274,6 +276,8 @@ public abstract class LineComponent extends StackPane implements NoPaddingCompon
|
||||
|
||||
/// Updates the fixed preferred height after the row width or subtitle content changes.
|
||||
private void updatePreferredHeight() {
|
||||
if (FXUtils.getLimitHeight(this) > 0) return;
|
||||
|
||||
double width = getWidth();
|
||||
if (width <= 0) {
|
||||
setMinHeight(MIN_HEIGHT);
|
||||
|
||||
@@ -53,8 +53,13 @@ public abstract class MDListCell<T> extends ListCell<T> {
|
||||
|
||||
@Override
|
||||
protected void updateItem(T item, boolean empty) {
|
||||
T oldItem = getItem();
|
||||
boolean oldEmpty = isEmpty();
|
||||
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (oldItem == item && oldEmpty == empty) return;
|
||||
|
||||
updateControl(item, empty);
|
||||
if (empty) {
|
||||
setGraphic(null);
|
||||
|
||||
@@ -85,8 +85,8 @@ public final class RadioChoiceList<T extends @UnknownNullability Object> extends
|
||||
|
||||
group.selectedToggleProperty().addListener((observable, oldValue, newValue) -> {
|
||||
Choice<T> choice = newValue != null ? choiceByToggle.get(newValue) : null;
|
||||
selectedChoice.set(choice);
|
||||
selectedValue.set(choice != null ? choice.getValue() : null);
|
||||
selectedChoice.set(choice);
|
||||
});
|
||||
|
||||
selectedValue.addListener((observable, oldValue, newValue) -> {
|
||||
@@ -96,6 +96,12 @@ public final class RadioChoiceList<T extends @UnknownNullability Object> extends
|
||||
});
|
||||
}
|
||||
|
||||
/// Replaces the displayed choices.
|
||||
@SafeVarargs
|
||||
public final void setChoices(Choice<T>... choices) {
|
||||
setChoices(List.of(choices));
|
||||
}
|
||||
|
||||
/// Replaces the displayed choices.
|
||||
public void setChoices(Collection<? extends Choice<T>> choices) {
|
||||
this.choices.setAll(choices);
|
||||
|
||||
@@ -40,6 +40,7 @@ import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.theme.LauncherBackground;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
||||
import org.jackhuang.hmcl.ui.animation.Motion;
|
||||
@@ -49,7 +50,9 @@ import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
public class Decorator extends Control {
|
||||
private final ListProperty<Node> content = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||
private final ListProperty<Node> container = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||
private final ObjectProperty<Background> contentBackground = new SimpleObjectProperty<>();
|
||||
|
||||
/// The launcher background and opacity rendered by this decorator's skin.
|
||||
private final ObjectProperty<LauncherBackground> contentBackground = new SimpleObjectProperty<>();
|
||||
private final ObjectProperty<DecoratorPage.State> state = new SimpleObjectProperty<>();
|
||||
private final ObjectProperty<EventHandler<ActionEvent>> onCloseNavButtonAction = new SimpleObjectProperty<>();
|
||||
private final ObjectProperty<EventHandler<ActionEvent>> onBackNavButtonAction = new SimpleObjectProperty<>();
|
||||
@@ -151,15 +154,18 @@ public class Decorator extends Control {
|
||||
this.container.set(container);
|
||||
}
|
||||
|
||||
public Background getContentBackground() {
|
||||
/// Returns the launcher background rendered behind the content.
|
||||
public LauncherBackground getContentBackground() {
|
||||
return contentBackground.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<Background> contentBackgroundProperty() {
|
||||
/// Returns the launcher background property rendered by this decorator's skin.
|
||||
public ObjectProperty<LauncherBackground> contentBackgroundProperty() {
|
||||
return contentBackground;
|
||||
}
|
||||
|
||||
public void setContentBackground(Background contentBackground) {
|
||||
/// Sets the launcher background rendered behind the content.
|
||||
public void setContentBackground(LauncherBackground contentBackground) {
|
||||
this.contentBackground.set(contentBackground);
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ import javafx.scene.shape.Rectangle;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
import javafx.util.Duration;
|
||||
import org.glavo.monetfx.ColorRole;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.theme.Themes;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
@@ -134,9 +135,33 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
root.getChildren().setAll(shadowContainer);
|
||||
|
||||
StackPane wrapper = new StackPane();
|
||||
wrapper.backgroundProperty().bind(Bindings.createObjectBinding(
|
||||
() -> Themes.windowTransparentProperty().get()
|
||||
? null
|
||||
: new Background(new BackgroundFill(
|
||||
Themes.getColorScheme().getColor(ColorRole.SURFACE_CONTAINER),
|
||||
CornerRadii.EMPTY,
|
||||
Insets.EMPTY)),
|
||||
Themes.windowTransparentProperty(),
|
||||
Themes.colorSchemeProperty()));
|
||||
|
||||
Region backgroundNode = new Region();
|
||||
backgroundNode.setMouseTransparent(true);
|
||||
backgroundNode.backgroundProperty().bind(Bindings.createObjectBinding(
|
||||
() -> skinnable.getContentBackground() == null
|
||||
? null
|
||||
: skinnable.getContentBackground().background(),
|
||||
skinnable.contentBackgroundProperty()));
|
||||
backgroundNode.opacityProperty().bind(Bindings.createDoubleBinding(
|
||||
() -> skinnable.getContentBackground() == null
|
||||
? 1.0
|
||||
: skinnable.getContentBackground().opacity(),
|
||||
skinnable.contentBackgroundProperty()));
|
||||
StackPane.setAlignment(backgroundNode, Pos.BOTTOM_CENTER);
|
||||
|
||||
BorderPane frame = new BorderPane();
|
||||
frame.getStyleClass().addAll("jfx-decorator");
|
||||
wrapper.getChildren().setAll(frame);
|
||||
wrapper.getChildren().setAll(backgroundNode, frame);
|
||||
skinnable.setDrawerWrapper(wrapper);
|
||||
|
||||
parent.getChildren().add(wrapper);
|
||||
@@ -178,20 +203,20 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
titleContainer = new StackPane();
|
||||
titleContainer.setPickOnBounds(false);
|
||||
titleContainer.getStyleClass().addAll("jfx-tool-bar");
|
||||
backgroundNode.maxHeightProperty().bind(Bindings.createDoubleBinding(
|
||||
() -> Math.max(0.0, wrapper.getHeight()
|
||||
- (skinnable.isTitleTransparent() ? 0.0 : titleContainer.getHeight())),
|
||||
wrapper.heightProperty(),
|
||||
skinnable.titleTransparentProperty(),
|
||||
titleContainer.heightProperty()));
|
||||
|
||||
// Maybe, we can automatically identify whether the top part of the picture is light-coloured or dark when the title is transparent,
|
||||
// and decide whether the whole top bar should be rendered in white or black. TODO
|
||||
FXUtils.onChangeAndOperate(skinnable.titleTransparentProperty(), titleTransparent -> {
|
||||
if (titleTransparent) {
|
||||
wrapper.backgroundProperty().bind(skinnable.contentBackgroundProperty());
|
||||
container.backgroundProperty().unbind();
|
||||
container.setBackground(null);
|
||||
titleContainer.getStyleClass().remove("background");
|
||||
titleContainer.getStyleClass().add("gray-background");
|
||||
} else {
|
||||
container.backgroundProperty().bind(skinnable.contentBackgroundProperty());
|
||||
wrapper.backgroundProperty().unbind();
|
||||
wrapper.setBackground(null);
|
||||
titleContainer.getStyleClass().add("background");
|
||||
titleContainer.getStyleClass().remove("gray-background");
|
||||
}
|
||||
|
||||
@@ -318,9 +318,9 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
builder.version(remoteVersion);
|
||||
});
|
||||
|
||||
repository.applyDefaultIsolationSettingForNewInstance(name, settings.isInstallingModdedVersion());
|
||||
return builder.buildAsync().whenComplete(any -> {
|
||||
repository.refreshVersions();
|
||||
repository.applyDefaultIsolationSetting(name);
|
||||
}).thenRunAsync(Schedulers.javafx(), () -> repository.setSelectedInstance(name));
|
||||
}
|
||||
|
||||
|
||||
@@ -61,11 +61,12 @@ public abstract class ModpackPage extends SpinnerPane implements WizardPage {
|
||||
{
|
||||
var archiveNamePane = new LinePane();
|
||||
{
|
||||
FXUtils.setLimitHeight(archiveNamePane, 75);
|
||||
|
||||
archiveNamePane.setTitle(i18n("version.name"));
|
||||
|
||||
txtModpackName = new JFXTextField();
|
||||
txtModpackName.setPrefWidth(300);
|
||||
FXUtils.setLimitHeight(archiveNamePane, 75);
|
||||
// BorderPane.setMargin(txtModpackName, new Insets(0, 0, 8, 32));
|
||||
BorderPane.setAlignment(txtModpackName, Pos.CENTER_RIGHT);
|
||||
archiveNamePane.setRight(txtModpackName);
|
||||
|
||||
@@ -61,9 +61,9 @@ public final class VanillaInstallWizardProvider implements WizardProvider {
|
||||
builder.version(remoteVersion);
|
||||
});
|
||||
|
||||
repository.applyDefaultIsolationSettingForNewInstance(name, settings.isInstallingModdedVersion());
|
||||
return builder.buildAsync().whenComplete(any -> {
|
||||
repository.refreshVersions();
|
||||
repository.applyDefaultIsolationSetting(name);
|
||||
})
|
||||
.thenRunAsync(Schedulers.javafx(), () -> repository.setSelectedInstance(name));
|
||||
}
|
||||
|
||||
@@ -213,6 +213,7 @@ public final class VersionsPage extends Control implements WizardPage, Refreshab
|
||||
@Override
|
||||
public void updateItem(RemoteVersion remoteVersion, boolean empty) {
|
||||
RemoteVersion oldRemoteVersion = getItem();
|
||||
|
||||
super.updateItem(remoteVersion, empty);
|
||||
|
||||
if (empty) {
|
||||
@@ -221,6 +222,8 @@ public final class VersionsPage extends Control implements WizardPage, Refreshab
|
||||
}
|
||||
setGraphic(pane);
|
||||
|
||||
if (oldRemoteVersion == remoteVersion) return;
|
||||
|
||||
twoLineListItem.setTitle(I18n.getDisplayVersion(remoteVersion));
|
||||
if (remoteVersion.getReleaseDate() != null) {
|
||||
twoLineListItem.setSubtitle(I18n.formatDateTime(remoteVersion.getReleaseDate()));
|
||||
@@ -228,67 +231,65 @@ public final class VersionsPage extends Control implements WizardPage, Refreshab
|
||||
twoLineListItem.setSubtitle(null);
|
||||
}
|
||||
|
||||
if (oldRemoteVersion != remoteVersion) {
|
||||
twoLineListItem.getTags().clear();
|
||||
twoLineListItem.getTags().clear();
|
||||
|
||||
if (remoteVersion instanceof GameRemoteVersion) {
|
||||
RemoteVersion.Type versionType = remoteVersion.getVersionType();
|
||||
GameVersionNumber gameVersion = GameVersionNumber.asGameVersion(remoteVersion.getGameVersion());
|
||||
if (remoteVersion instanceof GameRemoteVersion) {
|
||||
RemoteVersion.Type versionType = remoteVersion.getVersionType();
|
||||
GameVersionNumber gameVersion = GameVersionNumber.asGameVersion(remoteVersion.getGameVersion());
|
||||
|
||||
switch (versionType) {
|
||||
case RELEASE -> {
|
||||
twoLineListItem.addTag(i18n("version.game.release"));
|
||||
imageView.setImage(VersionIconType.GRASS.getIcon());
|
||||
}
|
||||
case SNAPSHOT, PENDING, UNOBFUSCATED -> {
|
||||
if (versionType == RemoteVersion.Type.SNAPSHOT
|
||||
&& GameVersionNumber.asGameVersion(remoteVersion.getGameVersion()).isAprilFools()) {
|
||||
twoLineListItem.addTag(i18n("version.game.april_fools"));
|
||||
imageView.setImage(VersionIconType.APRIL_FOOLS.getIcon());
|
||||
} else {
|
||||
twoLineListItem.addTag(i18n("version.game.snapshot"));
|
||||
imageView.setImage(VersionIconType.COMMAND.getIcon());
|
||||
}
|
||||
}
|
||||
default -> {
|
||||
twoLineListItem.addTag(i18n("version.game.old"));
|
||||
imageView.setImage(VersionIconType.CRAFT_TABLE.getIcon());
|
||||
switch (versionType) {
|
||||
case RELEASE -> {
|
||||
twoLineListItem.addTag(i18n("version.game.release"));
|
||||
imageView.setImage(VersionIconType.GRASS.getIcon());
|
||||
}
|
||||
case SNAPSHOT, PENDING, UNOBFUSCATED -> {
|
||||
if (versionType == RemoteVersion.Type.SNAPSHOT
|
||||
&& GameVersionNumber.asGameVersion(remoteVersion.getGameVersion()).isAprilFools()) {
|
||||
twoLineListItem.addTag(i18n("version.game.april_fools"));
|
||||
imageView.setImage(VersionIconType.APRIL_FOOLS.getIcon());
|
||||
} else {
|
||||
twoLineListItem.addTag(i18n("version.game.snapshot"));
|
||||
imageView.setImage(VersionIconType.COMMAND.getIcon());
|
||||
}
|
||||
}
|
||||
|
||||
switch (NativePatcher.checkSupportedStatus(gameVersion, Platform.SYSTEM_PLATFORM, OperatingSystem.SYSTEM_VERSION)) {
|
||||
case UNTESTED -> twoLineListItem.addTagWarning(i18n("version.game.support_status.untested"));
|
||||
case UNSUPPORTED -> twoLineListItem.addTagWarning(i18n("version.game.support_status.unsupported"));
|
||||
default -> {
|
||||
twoLineListItem.addTag(i18n("version.game.old"));
|
||||
imageView.setImage(VersionIconType.CRAFT_TABLE.getIcon());
|
||||
}
|
||||
} else {
|
||||
VersionIconType iconType;
|
||||
if (remoteVersion instanceof LiteLoaderRemoteVersion)
|
||||
iconType = VersionIconType.CHICKEN;
|
||||
else if (remoteVersion instanceof OptiFineRemoteVersion)
|
||||
iconType = VersionIconType.OPTIFINE;
|
||||
else if (remoteVersion instanceof ForgeRemoteVersion)
|
||||
iconType = VersionIconType.FORGE;
|
||||
else if (remoteVersion instanceof CleanroomRemoteVersion)
|
||||
iconType = VersionIconType.CLEANROOM;
|
||||
else if (remoteVersion instanceof NeoForgeRemoteVersion)
|
||||
iconType = VersionIconType.NEO_FORGE;
|
||||
else if (remoteVersion instanceof LegacyFabricRemoteVersion || remoteVersion instanceof LegacyFabricAPIRemoteVersion)
|
||||
iconType = VersionIconType.LEGACY_FABRIC;
|
||||
else if (remoteVersion instanceof FabricRemoteVersion || remoteVersion instanceof FabricAPIRemoteVersion)
|
||||
iconType = VersionIconType.FABRIC;
|
||||
else if (remoteVersion instanceof QuiltRemoteVersion || remoteVersion instanceof QuiltAPIRemoteVersion)
|
||||
iconType = VersionIconType.QUILT;
|
||||
else
|
||||
iconType = VersionIconType.COMMAND;
|
||||
|
||||
imageView.setImage(iconType.getIcon());
|
||||
String displayGameVersion = I18n.getDisplayVersion(GameVersionNumber.asGameVersion(remoteVersion.getGameVersion()));
|
||||
|
||||
if (twoLineListItem.getSubtitle() == null)
|
||||
twoLineListItem.setSubtitle(displayGameVersion);
|
||||
else
|
||||
twoLineListItem.addTag(displayGameVersion);
|
||||
}
|
||||
|
||||
switch (NativePatcher.checkSupportedStatus(gameVersion, Platform.SYSTEM_PLATFORM, OperatingSystem.SYSTEM_VERSION)) {
|
||||
case UNTESTED -> twoLineListItem.addTagWarning(i18n("version.game.support_status.untested"));
|
||||
case UNSUPPORTED -> twoLineListItem.addTagWarning(i18n("version.game.support_status.unsupported"));
|
||||
}
|
||||
} else {
|
||||
VersionIconType iconType;
|
||||
if (remoteVersion instanceof LiteLoaderRemoteVersion)
|
||||
iconType = VersionIconType.CHICKEN;
|
||||
else if (remoteVersion instanceof OptiFineRemoteVersion)
|
||||
iconType = VersionIconType.OPTIFINE;
|
||||
else if (remoteVersion instanceof ForgeRemoteVersion)
|
||||
iconType = VersionIconType.FORGE;
|
||||
else if (remoteVersion instanceof CleanroomRemoteVersion)
|
||||
iconType = VersionIconType.CLEANROOM;
|
||||
else if (remoteVersion instanceof NeoForgeRemoteVersion)
|
||||
iconType = VersionIconType.NEO_FORGE;
|
||||
else if (remoteVersion instanceof LegacyFabricRemoteVersion || remoteVersion instanceof LegacyFabricAPIRemoteVersion)
|
||||
iconType = VersionIconType.LEGACY_FABRIC;
|
||||
else if (remoteVersion instanceof FabricRemoteVersion || remoteVersion instanceof FabricAPIRemoteVersion)
|
||||
iconType = VersionIconType.FABRIC;
|
||||
else if (remoteVersion instanceof QuiltRemoteVersion || remoteVersion instanceof QuiltAPIRemoteVersion)
|
||||
iconType = VersionIconType.QUILT;
|
||||
else
|
||||
iconType = VersionIconType.COMMAND;
|
||||
|
||||
imageView.setImage(iconType.getIcon());
|
||||
String displayGameVersion = I18n.getDisplayVersion(GameVersionNumber.asGameVersion(remoteVersion.getGameVersion()));
|
||||
|
||||
if (twoLineListItem.getSubtitle() == null)
|
||||
twoLineListItem.setSubtitle(displayGameVersion);
|
||||
else
|
||||
twoLineListItem.addTag(displayGameVersion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,7 +190,7 @@ public final class ExportWizardProvider implements WizardProvider {
|
||||
"OneSix",
|
||||
exportInfo.getName() + "-" + exportInfo.getVersion(),
|
||||
null,
|
||||
Lang.toIntOrNull(setting.get(GameSettings::permSizeProperty)),
|
||||
Lang.toIntOrNull(setting.getInheritable(GameSettings::permSizeProperty)),
|
||||
setting.getInheritable(GameSettings::commandWrapperProperty),
|
||||
setting.getInheritable(GameSettings::preLaunchCommandProperty),
|
||||
null,
|
||||
|
||||
@@ -101,9 +101,9 @@ public final class ModpackInfoPage extends Control implements WizardPage {
|
||||
author.set(Optional.ofNullable(Accounts.getSelectedAccount()).map(Account::getProfileName).orElse(""));
|
||||
|
||||
GameSettings.Effective versionSetting = gameRepository.getEffectiveGameSettings(versionName);
|
||||
minMemory.set(Optional.ofNullable(versionSetting.get(GameSettings::minMemoryProperty)).orElse(0));
|
||||
launchArguments.set(versionSetting.get(GameSettings::gameArgumentsProperty));
|
||||
javaArguments.set(versionSetting.get(GameSettings::jvmOptionsProperty));
|
||||
minMemory.set(Optional.ofNullable(versionSetting.getInheritable(GameSettings::minMemoryProperty)).orElse(0));
|
||||
launchArguments.set(versionSetting.getInheritable(GameSettings::gameArgumentsProperty));
|
||||
javaArguments.set(versionSetting.getInheritable(GameSettings::jvmOptionsProperty));
|
||||
|
||||
canIncludeLauncher = JarUtils.thisJarPath() != null;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,10 @@ import com.jfoenix.controls.JFXComboBox;
|
||||
import com.jfoenix.controls.JFXSlider;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
@@ -32,11 +35,7 @@ import javafx.geometry.Pos;
|
||||
import javafx.geometry.Rectangle2D;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.ContentDisplay;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.TextFormatter;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.stage.FileChooser;
|
||||
@@ -71,7 +70,6 @@ import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.SettingsManager.settings;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
@@ -107,6 +105,8 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
private boolean updatingJavaSetting = false;
|
||||
private boolean updatingSelectedJava = false;
|
||||
private boolean updatingParentSetting = false;
|
||||
private final ObjectProperty<GameSettings.@Nullable Preset> activeParentSetting =
|
||||
new SimpleObjectProperty<>(this, "activeParentSetting");
|
||||
|
||||
// GUI
|
||||
private final ScrollPane scrollPane;
|
||||
@@ -133,6 +133,9 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
assert settingType == GameSettings.Preset.class || settingType == GameSettings.Instance.class;
|
||||
|
||||
this.isPresetSetting = settingType == GameSettings.Preset.class;
|
||||
if (!isPresetSetting) {
|
||||
bindActiveParentSetting();
|
||||
}
|
||||
|
||||
this.scrollPane = new ScrollPane();
|
||||
scrollPane.setFitToHeight(true);
|
||||
@@ -158,11 +161,13 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
{
|
||||
if (isPresetSetting) {
|
||||
var presetSettings = new ComponentList();
|
||||
rootPane.getChildren().addAll(
|
||||
ComponentList.createComponentListTitle(i18n("settings.type.global.preset")),
|
||||
presetSettings,
|
||||
ComponentList.createComponentListTitle(i18n("settings.game.section.basic")),
|
||||
rootPane.getChildren().addAll(ComponentList.createComponentListTitle(i18n("settings.game.section.basic")),
|
||||
basicSettings,
|
||||
ComponentList.createComponentListTitle(
|
||||
i18n("settings.type.global.preset"),
|
||||
i18n("settings.type.global.preset.help")
|
||||
),
|
||||
presetSettings,
|
||||
ComponentList.createComponentListTitle(i18n("settings.game.section.game")),
|
||||
gameSettings,
|
||||
ComponentList.createComponentListTitle(i18n("settings.launcher")),
|
||||
@@ -191,6 +196,7 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
var parentGameSettingsPane = new LineSelectButton<GameSettings.@Nullable Preset>();
|
||||
basicSettings.getContent().add(parentGameSettingsPane);
|
||||
parentGameSettingsPane.setTitle(i18n("settings.type.global.preset"));
|
||||
parentGameSettingsPane.setSubtitle(i18n("settings.type.global.preset.subtitle"));
|
||||
parentGameSettingsPane.setConverter(setting -> setting != null
|
||||
? PresetManagementPane.getPresetDisplayName(setting)
|
||||
: i18n("settings.type.global.preset.default"));
|
||||
@@ -274,9 +280,7 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
|
||||
refreshJavaSettings();
|
||||
});
|
||||
SettingsManager.getGameSettings().addListener(weakJavaListener);
|
||||
settings().defaultGameSettingsPresetProperty().addListener(weakJavaListener);
|
||||
|
||||
activeParentSetting.addListener(weakJavaListener);
|
||||
javaItem.selectedChoiceProperty().addListener((observable, oldChoice, newChoice) -> {
|
||||
S setting = currentSetting.get();
|
||||
if (setting == null || updatingSelectedJava) {
|
||||
@@ -362,6 +366,7 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
|
||||
// Memory Setting
|
||||
@Nullable JFXButton autoMemoryButton = !isPresetSetting ? createInheritanceButton() : null;
|
||||
ObjectProperty<Boolean> memorySelectedValue = new SimpleObjectProperty<>(true);
|
||||
var memorySublist = new ComponentSublist(() -> {
|
||||
var memoryItem = new RadioChoiceList<Boolean>();
|
||||
memoryItem.setFallbackValue(true);
|
||||
@@ -381,16 +386,18 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
maxMemoryButton = createInheritanceButton();
|
||||
}
|
||||
|
||||
var options = new ArrayList<RadioChoiceList.Choice<Boolean>>();
|
||||
options.add(new RadioChoiceList.Choice<>(i18n("settings.memory.auto_allocate"), true));
|
||||
var manualMemoryEditor = new HBox(8);
|
||||
manualMemoryEditor.setAlignment(Pos.CENTER_RIGHT);
|
||||
manualMemoryEditor.getChildren().setAll(
|
||||
maxMemorySlider,
|
||||
maxMemoryTextField,
|
||||
new Label(i18n("settings.memory.unit.mib")));
|
||||
options.add(new EditorChoice<>(i18n("settings.memory.manual_allocate"), false, manualMemoryEditor, maxMemoryButton));
|
||||
memoryItem.setChoices(options);
|
||||
memoryItem.setChoices(List.of(
|
||||
new RadioChoiceList.Choice<>(i18n("settings.memory.auto_allocate"), true),
|
||||
new EditorChoice<>(i18n("settings.memory.manual_allocate"), false, manualMemoryEditor, maxMemoryButton)
|
||||
));
|
||||
memoryItem.selectedValueProperty().addListener((observable, oldValue, newValue) ->
|
||||
memorySelectedValue.set(newValue));
|
||||
|
||||
var memoryStatusBar = new MemoryStatusBar();
|
||||
|
||||
@@ -418,7 +425,8 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
maxMemoryButton,
|
||||
GameSettingsPage::updateInheritanceButton,
|
||||
holder,
|
||||
this::getEffectiveParentGameSettings);
|
||||
this::getEffectiveParentGameSettings,
|
||||
activeParentSetting);
|
||||
|
||||
return List.of(memoryItem, memoryStatusPane);
|
||||
});
|
||||
@@ -428,7 +436,10 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
gameSettings.getContent().add(memorySublist);
|
||||
memorySublist.setTitle(i18n("settings.memory"));
|
||||
memorySublist.setHasSubtitle(true);
|
||||
memorySublist.setDescription(i18n("settings.memory.auto_allocate"));
|
||||
memorySelectedValue.addListener((observable, oldValue, newValue) ->
|
||||
memorySublist.setDescription(getMemoryAllocationDisplayName(newValue)));
|
||||
memorySublist.setDescription(getMemoryAllocationDisplayName(memorySelectedValue.get()));
|
||||
bindMemorySelectedValue(memorySelectedValue);
|
||||
|
||||
// Launcher Visibility Setting
|
||||
var launcherVisibilityPane = createInheritableButton(
|
||||
@@ -837,18 +848,19 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
|
||||
private void bindInstanceParentSetting(LineSelectButton<GameSettings.@Nullable Preset> button) {
|
||||
ObservableList<GameSettings.Preset> items = FXCollections.observableArrayList();
|
||||
ObservableList<GameSettings.Preset> presets = getAvailableParentPresets();
|
||||
InvalidationListener updateItems = observable -> {
|
||||
@Nullable GameSettings.Preset selected = button.getValue();
|
||||
items.setAll((GameSettings.Preset) null);
|
||||
items.addAll(SettingsManager.getGameSettings());
|
||||
items.addAll(presets);
|
||||
refreshInstanceParentSettingConverter(button);
|
||||
if (selected != null && SettingsManager.getGameSettings(selected.idProperty().getValue()) == null) {
|
||||
if (selected != null && getAvailableParentPreset(selected.idProperty().getValue()) == null) {
|
||||
button.setValue(null);
|
||||
}
|
||||
};
|
||||
InvalidationListener weakUpdateItems = holder.weak(updateItems);
|
||||
updateItems.invalidated(SettingsManager.getGameSettings());
|
||||
SettingsManager.getGameSettings().addListener(weakUpdateItems);
|
||||
updateItems.invalidated(presets);
|
||||
presets.addListener(weakUpdateItems);
|
||||
button.setItems(items);
|
||||
refreshInstanceParentSettingConverter(button);
|
||||
|
||||
@@ -865,7 +877,7 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
try {
|
||||
refreshInstanceParentSettingConverter(button);
|
||||
GameSettingsPresetID parent = setting.parentProperty().getValue();
|
||||
button.setValue(parent != null ? SettingsManager.getGameSettings(parent) : null);
|
||||
button.setValue(parent != null ? getAvailableParentPreset(parent) : null);
|
||||
} finally {
|
||||
updatingParentSetting = false;
|
||||
}
|
||||
@@ -880,6 +892,49 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
: i18n("settings.type.global.preset.default"));
|
||||
}
|
||||
|
||||
/// Returns the presets that can be selected as an instance parent.
|
||||
private ObservableList<GameSettings.Preset> getAvailableParentPresets() {
|
||||
return SettingsManager.gameSettingsPresets().getPresets();
|
||||
}
|
||||
|
||||
/// Returns the preset selected by an instance parent ID.
|
||||
private GameSettings.@Nullable Preset getAvailableParentPreset(@Nullable GameSettingsPresetID id) {
|
||||
return SettingsManager.gameSettingsPresets().getPreset(id);
|
||||
}
|
||||
|
||||
/// Keeps [#activeParentSetting] synchronized with the current instance's resolved parent preset.
|
||||
private void bindActiveParentSetting() {
|
||||
InvalidationListener refresh = observable -> refreshActiveParentSetting();
|
||||
InvalidationListener weakRefresh = holder.weak(refresh);
|
||||
|
||||
getAvailableParentPresets().addListener(weakRefresh);
|
||||
SettingsManager.settings().defaultGameSettingsPresetProperty().addListener(weakRefresh);
|
||||
currentSetting.addListener((observable, oldValue, newValue) -> {
|
||||
if (oldValue != null) {
|
||||
oldValue.removeListener(weakRefresh);
|
||||
}
|
||||
|
||||
if (newValue != null) {
|
||||
newValue.addListener(weakRefresh);
|
||||
}
|
||||
|
||||
refreshActiveParentSetting();
|
||||
});
|
||||
|
||||
S setting = currentSetting.get();
|
||||
if (setting != null) {
|
||||
setting.addListener(weakRefresh);
|
||||
}
|
||||
refreshActiveParentSetting();
|
||||
}
|
||||
|
||||
/// Refreshes the current instance's resolved parent preset.
|
||||
private void refreshActiveParentSetting() {
|
||||
activeParentSetting.set(currentSetting.get() instanceof GameSettings.Instance instance
|
||||
? getEffectiveParentGameSettings(instance)
|
||||
: null);
|
||||
}
|
||||
|
||||
/// Adds the title-line inheritance button for the Java selection sublist.
|
||||
private void bindJavaInheritanceButton(ComponentSublist sublist) {
|
||||
if (isPresetSetting) {
|
||||
@@ -1033,7 +1088,8 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
this::createInheritanceButton,
|
||||
GameSettingsPage::updateInheritanceButton,
|
||||
holder,
|
||||
this::getEffectiveParentGameSettings);
|
||||
this::getEffectiveParentGameSettings,
|
||||
activeParentSetting);
|
||||
}
|
||||
|
||||
/// Binds an integer text field to a setting property with independent override state.
|
||||
@@ -1050,7 +1106,8 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
this::createInheritanceButton,
|
||||
GameSettingsPage::updateInheritanceButton,
|
||||
holder,
|
||||
this::getEffectiveParentGameSettings);
|
||||
this::getEffectiveParentGameSettings,
|
||||
activeParentSetting);
|
||||
}
|
||||
|
||||
/// Binds the windowed resolution selector to the width and height settings.
|
||||
@@ -1093,6 +1150,7 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
};
|
||||
InvalidationListener weakPropertyListener = holder.weak(propertyListener);
|
||||
refreshHolder.value = weakPropertyListener;
|
||||
activeParentSetting.addListener(weakPropertyListener);
|
||||
|
||||
ChangeListener<@Nullable Boolean> focusedListener = (observable, oldValue, newValue) -> {
|
||||
if (!newValue) {
|
||||
@@ -1178,8 +1236,6 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
widthProperty.addListener(weakPropertyListener);
|
||||
heightProperty.addListener(weakPropertyListener);
|
||||
}
|
||||
SettingsManager.getGameSettings().addListener(weakPropertyListener);
|
||||
settings().defaultGameSettingsPresetProperty().addListener(weakPropertyListener);
|
||||
propertyListener.invalidated(setting);
|
||||
}
|
||||
|
||||
@@ -1416,7 +1472,11 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
|
||||
InvalidationListener refresh = observable -> {
|
||||
GameSettings setting = currentSetting.get();
|
||||
updateParentInheritablePropertyListener(setting, activeParentProperty, propertyGetter, refreshHolder.value);
|
||||
updateParentInheritablePropertyListener(
|
||||
setting,
|
||||
activeParentProperty,
|
||||
propertyGetter,
|
||||
refreshHolder.value);
|
||||
InheritableProperty<String> property = activeProperty.get();
|
||||
if (setting == null || property == null || updating.value) {
|
||||
return;
|
||||
@@ -1434,6 +1494,7 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
};
|
||||
InvalidationListener weakRefresh = holder.weak(refresh);
|
||||
refreshHolder.value = weakRefresh;
|
||||
activeParentSetting.addListener(weakRefresh);
|
||||
|
||||
textProperty.addListener((observable, oldValue, newValue) -> {
|
||||
GameSettings setting = currentSetting.get();
|
||||
@@ -1499,9 +1560,6 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
refresh.invalidated(newValue);
|
||||
});
|
||||
|
||||
SettingsManager.getGameSettings().addListener(weakRefresh);
|
||||
settings().defaultGameSettingsPresetProperty().addListener(weakRefresh);
|
||||
|
||||
S setting = currentSetting.get();
|
||||
if (setting != null) {
|
||||
InheritableProperty<String> property = propertyGetter.apply(setting);
|
||||
@@ -1524,7 +1582,11 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
|
||||
InvalidationListener refresh = observable -> {
|
||||
GameSettings setting = currentSetting.get();
|
||||
updateParentInheritablePropertyListener(setting, activeParentProperty, propertyGetter, refreshHolder.value);
|
||||
updateParentInheritablePropertyListener(
|
||||
setting,
|
||||
activeParentProperty,
|
||||
propertyGetter,
|
||||
refreshHolder.value);
|
||||
InheritableProperty<String> property = activeProperty.get();
|
||||
if (setting == null || property == null || updating.value) {
|
||||
return;
|
||||
@@ -1542,6 +1604,7 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
};
|
||||
InvalidationListener weakRefresh = holder.weak(refresh);
|
||||
refreshHolder.value = weakRefresh;
|
||||
activeParentSetting.addListener(weakRefresh);
|
||||
|
||||
textProperty.addListener((observable, oldValue, newValue) -> {
|
||||
GameSettings setting = currentSetting.get();
|
||||
@@ -1607,9 +1670,6 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
refresh.invalidated(newValue);
|
||||
});
|
||||
|
||||
SettingsManager.getGameSettings().addListener(weakRefresh);
|
||||
settings().defaultGameSettingsPresetProperty().addListener(weakRefresh);
|
||||
|
||||
S setting = currentSetting.get();
|
||||
if (setting != null) {
|
||||
InheritableProperty<String> property = propertyGetter.apply(setting);
|
||||
@@ -1660,6 +1720,8 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
updating.value = false;
|
||||
}
|
||||
refresh.invalidated(instance);
|
||||
|
||||
fireEvent(new VersionPage.WorkingDirChangedEvent());
|
||||
});
|
||||
|
||||
currentSetting.addListener((observable, oldValue, newValue) -> {
|
||||
@@ -1728,6 +1790,7 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
};
|
||||
InvalidationListener weakRefresh = holder.weak(refresh);
|
||||
refreshHolder.value = weakRefresh;
|
||||
activeParentSetting.addListener(weakRefresh);
|
||||
|
||||
inheritButton.addEventFilter(MouseEvent.MOUSE_CLICKED, event -> {
|
||||
GameSettings setting = currentSetting.get();
|
||||
@@ -1747,6 +1810,9 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
updating.value = false;
|
||||
}
|
||||
refresh.invalidated(instance);
|
||||
|
||||
fireEvent(new VersionPage.WorkingDirChangedEvent());
|
||||
|
||||
event.consume();
|
||||
});
|
||||
|
||||
@@ -1766,6 +1832,8 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
} finally {
|
||||
updating.value = false;
|
||||
}
|
||||
|
||||
fireEvent(new VersionPage.WorkingDirChangedEvent());
|
||||
});
|
||||
|
||||
currentSetting.addListener((observable, oldValue, newValue) -> {
|
||||
@@ -1779,9 +1847,6 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
refresh.invalidated(newValue);
|
||||
});
|
||||
|
||||
SettingsManager.getGameSettings().addListener(weakRefresh);
|
||||
settings().defaultGameSettingsPresetProperty().addListener(weakRefresh);
|
||||
|
||||
S setting = currentSetting.get();
|
||||
if (setting instanceof GameSettings.Instance instance) {
|
||||
instance.addListener(weakRefresh);
|
||||
@@ -1825,13 +1890,96 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
}
|
||||
}
|
||||
|
||||
/// Keeps a listener attached to the current instance's parent preset setting property.
|
||||
private <T extends @UnknownNullability Object> void updateParentSettingPropertyListener(
|
||||
@Nullable GameSettings setting,
|
||||
ObjectProperty<@Nullable SettingProperty<T>> activeParentProperty,
|
||||
Function<GameSettings, ? extends SettingProperty<T>> propertyGetter,
|
||||
InvalidationListener listener) {
|
||||
SettingProperty<T> oldParentProperty = activeParentProperty.get();
|
||||
SettingProperty<T> newParentProperty = setting instanceof GameSettings.Instance instance
|
||||
? propertyGetter.apply(getEffectiveParentGameSettings(instance))
|
||||
: null;
|
||||
if (oldParentProperty == newParentProperty) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (oldParentProperty != null) {
|
||||
oldParentProperty.removeListener(listener);
|
||||
}
|
||||
activeParentProperty.set(newParentProperty);
|
||||
if (newParentProperty != null) {
|
||||
newParentProperty.addListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
/// Binds the memory sublist description source to the effective automatic memory setting.
|
||||
private void bindMemorySelectedValue(ObjectProperty<Boolean> memorySelectedValue) {
|
||||
ObjectProperty<@Nullable SettingProperty<Boolean>> activeAutoMemoryProperty = new SimpleObjectProperty<>();
|
||||
ObjectProperty<@Nullable SettingProperty<Boolean>> activeParentAutoMemoryProperty = new SimpleObjectProperty<>();
|
||||
final Holder<InvalidationListener> refreshHolder = new Holder<>();
|
||||
|
||||
InvalidationListener refresh = observable -> {
|
||||
S setting = currentSetting.get();
|
||||
updateParentSettingPropertyListener(
|
||||
setting,
|
||||
activeParentAutoMemoryProperty,
|
||||
GameSettings::autoMemoryProperty,
|
||||
refreshHolder.value);
|
||||
if (setting == null) {
|
||||
memorySelectedValue.set(true);
|
||||
return;
|
||||
}
|
||||
|
||||
memorySelectedValue.set(resolveEffectiveSetting(setting)
|
||||
.getInheritable(GameSettings::autoMemoryProperty));
|
||||
};
|
||||
InvalidationListener weakRefresh = holder.weak(refresh);
|
||||
refreshHolder.value = weakRefresh;
|
||||
activeParentSetting.addListener(weakRefresh);
|
||||
|
||||
currentSetting.addListener((observable, oldValue, newValue) -> {
|
||||
if (oldValue != null) {
|
||||
oldValue.removeListener(weakRefresh);
|
||||
}
|
||||
|
||||
SettingProperty<Boolean> oldAutoMemoryProperty = activeAutoMemoryProperty.get();
|
||||
if (oldAutoMemoryProperty != null) {
|
||||
oldAutoMemoryProperty.removeListener(weakRefresh);
|
||||
}
|
||||
|
||||
SettingProperty<Boolean> newAutoMemoryProperty = newValue != null
|
||||
? newValue.autoMemoryProperty()
|
||||
: null;
|
||||
activeAutoMemoryProperty.set(newAutoMemoryProperty);
|
||||
if (newValue != null) {
|
||||
newValue.addListener(weakRefresh);
|
||||
}
|
||||
if (newAutoMemoryProperty != null) {
|
||||
newAutoMemoryProperty.addListener(weakRefresh);
|
||||
}
|
||||
refresh.invalidated(newValue);
|
||||
});
|
||||
|
||||
S setting = currentSetting.get();
|
||||
if (setting != null) {
|
||||
SettingProperty<Boolean> autoMemoryProperty = setting.autoMemoryProperty();
|
||||
activeAutoMemoryProperty.set(autoMemoryProperty);
|
||||
setting.addListener(weakRefresh);
|
||||
autoMemoryProperty.addListener(weakRefresh);
|
||||
}
|
||||
refresh.invalidated(setting);
|
||||
}
|
||||
|
||||
/// Binds a radio choice list to an inheritable setting property.
|
||||
private <T> void bindInheritableRadioChoiceList(
|
||||
ComponentSublist sublist,
|
||||
RadioChoiceList<T> item,
|
||||
Function<GameSettings, InheritableProperty<T>> propertyGetter) {
|
||||
ObjectProperty<@Nullable InheritableProperty<T>> activeProperty = new SimpleObjectProperty<>();
|
||||
ObjectProperty<@Nullable InheritableProperty<T>> activeParentProperty = new SimpleObjectProperty<>();
|
||||
final Holder<Boolean> updating = new Holder<>(false);
|
||||
final Holder<InvalidationListener> refreshHolder = new Holder<>();
|
||||
@Nullable JFXButton inheritButton = null;
|
||||
if (!isPresetSetting) {
|
||||
inheritButton = createInheritanceButton();
|
||||
@@ -1841,6 +1989,11 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
|
||||
InvalidationListener propertyListener = observable -> {
|
||||
GameSettings setting = currentSetting.get();
|
||||
updateParentInheritablePropertyListener(
|
||||
setting,
|
||||
activeParentProperty,
|
||||
propertyGetter,
|
||||
refreshHolder.value);
|
||||
InheritableProperty<T> property = activeProperty.get();
|
||||
if (setting == null || property == null || updating.value) {
|
||||
return;
|
||||
@@ -1857,6 +2010,8 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
}
|
||||
};
|
||||
InvalidationListener weakPropertyListener = holder.weak(propertyListener);
|
||||
refreshHolder.value = weakPropertyListener;
|
||||
activeParentSetting.addListener(weakPropertyListener);
|
||||
|
||||
ChangeListener<@Nullable T> itemListener = (observable, oldValue, newValue) -> {
|
||||
GameSettings setting = currentSetting.get();
|
||||
@@ -1921,9 +2076,6 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
}
|
||||
propertyListener.invalidated(newProperty);
|
||||
});
|
||||
SettingsManager.getGameSettings().addListener(weakPropertyListener);
|
||||
settings().defaultGameSettingsPresetProperty().addListener(weakPropertyListener);
|
||||
|
||||
S setting = currentSetting.get();
|
||||
if (setting != null) {
|
||||
InheritableProperty<T> property = propertyGetter.apply(setting);
|
||||
@@ -1937,7 +2089,9 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
/// Binds the window type editor to the window type setting.
|
||||
private void bindWindowSettings(ComponentSublist sublist, RadioChoiceList<GameWindowType> item) {
|
||||
ObjectProperty<@Nullable InheritableProperty<GameWindowType>> activeWindowTypeProperty = new SimpleObjectProperty<>();
|
||||
ObjectProperty<@Nullable InheritableProperty<GameWindowType>> activeParentWindowTypeProperty = new SimpleObjectProperty<>();
|
||||
final Holder<Boolean> updating = new Holder<>(false);
|
||||
final Holder<InvalidationListener> refreshHolder = new Holder<>();
|
||||
@Nullable JFXButton inheritButton = null;
|
||||
if (!isPresetSetting) {
|
||||
inheritButton = createInheritanceButton();
|
||||
@@ -1947,6 +2101,11 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
|
||||
InvalidationListener propertyListener = observable -> {
|
||||
GameSettings setting = currentSetting.get();
|
||||
updateParentInheritablePropertyListener(
|
||||
setting,
|
||||
activeParentWindowTypeProperty,
|
||||
GameSettings::windowTypeProperty,
|
||||
refreshHolder.value);
|
||||
InheritableProperty<GameWindowType> property = activeWindowTypeProperty.get();
|
||||
if (setting == null || property == null || updating.value) {
|
||||
return;
|
||||
@@ -1963,6 +2122,8 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
}
|
||||
};
|
||||
InvalidationListener weakPropertyListener = holder.weak(propertyListener);
|
||||
refreshHolder.value = weakPropertyListener;
|
||||
activeParentSetting.addListener(weakPropertyListener);
|
||||
|
||||
item.selectedValueProperty().addListener((observable, oldValue, newValue) -> {
|
||||
GameSettings setting = currentSetting.get();
|
||||
@@ -2022,9 +2183,6 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
propertyListener.invalidated(newValue);
|
||||
});
|
||||
|
||||
SettingsManager.getGameSettings().addListener(weakPropertyListener);
|
||||
settings().defaultGameSettingsPresetProperty().addListener(weakPropertyListener);
|
||||
|
||||
S setting = currentSetting.get();
|
||||
if (setting != null) {
|
||||
activeWindowTypeProperty.set(setting.windowTypeProperty());
|
||||
@@ -2057,8 +2215,19 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
private <T> void bindInheritableSublistDescription(ComponentSublist sublist,
|
||||
Function<GameSettings, InheritableProperty<T>> propertyGetter,
|
||||
Function<T, String> converter) {
|
||||
InvalidationListener propertyListener = observable -> initInheritableSublistDescription(sublist, propertyGetter, converter);
|
||||
ObjectProperty<@Nullable InheritableProperty<T>> activeParentProperty = new SimpleObjectProperty<>();
|
||||
final Holder<InvalidationListener> refreshHolder = new Holder<>();
|
||||
InvalidationListener propertyListener = observable -> {
|
||||
updateParentInheritablePropertyListener(
|
||||
currentSetting.get(),
|
||||
activeParentProperty,
|
||||
propertyGetter,
|
||||
refreshHolder.value);
|
||||
initInheritableSublistDescription(sublist, propertyGetter, converter);
|
||||
};
|
||||
InvalidationListener weakPropertyListener = holder.weak(propertyListener);
|
||||
refreshHolder.value = weakPropertyListener;
|
||||
activeParentSetting.addListener(weakPropertyListener);
|
||||
|
||||
currentSetting.addListener((observable, oldValue, newValue) -> {
|
||||
if (oldValue != null) {
|
||||
@@ -2069,16 +2238,13 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
propertyGetter.apply(newValue).addListener(weakPropertyListener);
|
||||
}
|
||||
|
||||
initInheritableSublistDescription(sublist, propertyGetter, converter);
|
||||
propertyListener.invalidated(newValue);
|
||||
});
|
||||
SettingsManager.getGameSettings().addListener(weakPropertyListener);
|
||||
settings().defaultGameSettingsPresetProperty().addListener(weakPropertyListener);
|
||||
|
||||
S setting = currentSetting.get();
|
||||
if (setting != null) {
|
||||
propertyGetter.apply(setting).addListener(weakPropertyListener);
|
||||
}
|
||||
initInheritableSublistDescription(sublist, propertyGetter, converter);
|
||||
propertyListener.invalidated(setting);
|
||||
}
|
||||
|
||||
private <T> void initInheritableSublistDescription(ComponentSublist sublist,
|
||||
@@ -2093,6 +2259,13 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
sublist.setDescription(converter.apply(getEffectiveValue(setting, propertyGetter)));
|
||||
}
|
||||
|
||||
/// Returns the localized memory allocation mode display name.
|
||||
private static String getMemoryAllocationDisplayName(Boolean autoMemory) {
|
||||
return i18n(Boolean.TRUE.equals(autoMemory)
|
||||
? "settings.memory.auto_allocate"
|
||||
: "settings.memory.manual_allocate");
|
||||
}
|
||||
|
||||
private static String getWindowTypeDisplayName(GameWindowType type) {
|
||||
return switch (type) {
|
||||
case FULLSCREEN -> i18n("settings.game.window_type.fullscreen");
|
||||
@@ -2139,7 +2312,13 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
button.setOverriddenTooltip(i18n("settings.game.override_global"));
|
||||
button.setInheritAvailable(!isPresetSetting);
|
||||
|
||||
IndependentSettingBinder.bindToggleButton(currentSetting, button, propertyGetter, holder, this::getEffectiveParentGameSettings);
|
||||
IndependentSettingBinder.bindToggleButton(
|
||||
currentSetting,
|
||||
button,
|
||||
propertyGetter,
|
||||
holder,
|
||||
this::getEffectiveParentGameSettings,
|
||||
activeParentSetting);
|
||||
return button;
|
||||
}
|
||||
|
||||
@@ -2160,7 +2339,9 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
LineSelectButton<T> button,
|
||||
Function<GameSettings, InheritableProperty<T>> propertyGetter) {
|
||||
ObjectProperty<@Nullable InheritableProperty<T>> activeProperty = new SimpleObjectProperty<>();
|
||||
ObjectProperty<@Nullable InheritableProperty<T>> activeParentProperty = new SimpleObjectProperty<>();
|
||||
final Holder<Boolean> updating = new Holder<>(false);
|
||||
final Holder<InvalidationListener> refreshHolder = new Holder<>();
|
||||
@Nullable JFXButton inheritButton = null;
|
||||
if (!isPresetSetting) {
|
||||
inheritButton = createInheritanceButton();
|
||||
@@ -2170,6 +2351,7 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
|
||||
InvalidationListener refresh = observable -> {
|
||||
GameSettings setting = currentSetting.get();
|
||||
updateParentInheritablePropertyListener(setting, activeParentProperty, propertyGetter, refreshHolder.value);
|
||||
InheritableProperty<T> property = activeProperty.get();
|
||||
if (setting == null || property == null || updating.value) {
|
||||
return;
|
||||
@@ -2186,6 +2368,8 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
}
|
||||
};
|
||||
InvalidationListener weakRefresh = holder.weak(refresh);
|
||||
refreshHolder.value = weakRefresh;
|
||||
activeParentSetting.addListener(weakRefresh);
|
||||
|
||||
button.valueProperty().addListener((observable, oldValue, newValue) -> {
|
||||
GameSettings setting = currentSetting.get();
|
||||
@@ -2251,9 +2435,6 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
refresh.invalidated(newValue);
|
||||
});
|
||||
|
||||
SettingsManager.getGameSettings().addListener(weakRefresh);
|
||||
settings().defaultGameSettingsPresetProperty().addListener(weakRefresh);
|
||||
|
||||
S setting = currentSetting.get();
|
||||
if (setting != null) {
|
||||
activeProperty.set(propertyGetter.apply(setting));
|
||||
@@ -2268,10 +2449,13 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
LineInheritableToggleButton button,
|
||||
Function<GameSettings, InheritableProperty<Boolean>> propertyGetter) {
|
||||
ObjectProperty<@Nullable InheritableProperty<Boolean>> activeProperty = new SimpleObjectProperty<>();
|
||||
ObjectProperty<@Nullable InheritableProperty<Boolean>> activeParentProperty = new SimpleObjectProperty<>();
|
||||
final Holder<Boolean> updating = new Holder<>(false);
|
||||
final Holder<InvalidationListener> refreshHolder = new Holder<>();
|
||||
|
||||
InvalidationListener refresh = observable -> {
|
||||
GameSettings setting = currentSetting.get();
|
||||
updateParentInheritablePropertyListener(setting, activeParentProperty, propertyGetter, refreshHolder.value);
|
||||
InheritableProperty<Boolean> property = activeProperty.get();
|
||||
if (setting == null || property == null || updating.value) {
|
||||
return;
|
||||
@@ -2280,7 +2464,9 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
updating.value = true;
|
||||
try {
|
||||
boolean overridden = isPropertyOverridden(setting, property);
|
||||
button.setRawValue(overridden ? getDirectValue(property) : getEffectiveValue(setting, propertyGetter));
|
||||
button.setRawValue(overridden
|
||||
? getDirectValue(property)
|
||||
: getEffectiveValue(setting, propertyGetter));
|
||||
button.setOverridden(overridden);
|
||||
button.setEffectiveValue(getEffectiveValue(setting, propertyGetter));
|
||||
} finally {
|
||||
@@ -2288,6 +2474,8 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
}
|
||||
};
|
||||
InvalidationListener weakRefresh = holder.weak(refresh);
|
||||
refreshHolder.value = weakRefresh;
|
||||
activeParentSetting.addListener(weakRefresh);
|
||||
|
||||
button.rawValueProperty().addListener((observable, oldValue, newValue) -> {
|
||||
InheritableProperty<Boolean> property = activeProperty.get();
|
||||
@@ -2338,9 +2526,6 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
refresh.invalidated(newValue);
|
||||
});
|
||||
|
||||
SettingsManager.getGameSettings().addListener(weakRefresh);
|
||||
settings().defaultGameSettingsPresetProperty().addListener(weakRefresh);
|
||||
|
||||
S setting = currentSetting.get();
|
||||
if (setting != null) {
|
||||
activeProperty.set(propertyGetter.apply(setting));
|
||||
@@ -2409,7 +2594,7 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
/// Returns the explicitly configured parent preset for an instance, falling back to the default preset.
|
||||
private GameSettings.Preset getExplicitParentGameSettings(GameSettings.Instance instance) {
|
||||
GameSettingsPresetID parent = instance.parentProperty().getValue();
|
||||
GameSettings.Preset parentSetting = SettingsManager.getGameSettings(parent);
|
||||
GameSettings.Preset parentSetting = getAvailableParentPreset(parent);
|
||||
return parentSetting != null ? parentSetting : SettingsManager.getDefaultGameSettingsPresetOrCreate();
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXSlider;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
@@ -28,7 +29,6 @@ import javafx.scene.control.Label;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.setting.SettingsManager;
|
||||
import org.jackhuang.hmcl.setting.GameSettings;
|
||||
import org.jackhuang.hmcl.setting.property.SettingProperty;
|
||||
import org.jackhuang.hmcl.ui.MemoryStatusBar;
|
||||
@@ -48,7 +48,6 @@ import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.SettingsManager.settings;
|
||||
import static org.jackhuang.hmcl.util.DataSizeUnit.GIGABYTES;
|
||||
import static org.jackhuang.hmcl.util.DataSizeUnit.MEGABYTES;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
@@ -69,7 +68,8 @@ final class IndependentSettingBinder {
|
||||
Supplier<JFXButton> inheritanceButtonFactory,
|
||||
BiConsumer<JFXButton, Boolean> inheritanceButtonUpdater,
|
||||
WeakListenerHolder listenerHolder,
|
||||
Function<GameSettings.Instance, GameSettings.Preset> parentGetter) {
|
||||
Function<GameSettings.Instance, GameSettings.Preset> parentGetter,
|
||||
Observable parentSettingsInvalidation) {
|
||||
ObjectProperty<@Nullable SettingProperty<String>> activeProperty = new SimpleObjectProperty<>();
|
||||
ObjectProperty<@Nullable SettingProperty<String>> activeParentProperty = new SimpleObjectProperty<>();
|
||||
final Holder<Boolean> updating = new Holder<>(false);
|
||||
@@ -84,7 +84,12 @@ final class IndependentSettingBinder {
|
||||
|
||||
InvalidationListener refresh = observable -> {
|
||||
GameSettings setting = currentSetting.get();
|
||||
updateParentPropertyListener(setting, activeParentProperty, propertyGetter, parentGetter, refreshHolder.value);
|
||||
updateParentPropertyListener(
|
||||
setting,
|
||||
activeParentProperty,
|
||||
propertyGetter,
|
||||
parentGetter,
|
||||
refreshHolder.value);
|
||||
SettingProperty<String> property = activeProperty.get();
|
||||
if (setting == null || property == null || updating.value) {
|
||||
return;
|
||||
@@ -104,6 +109,7 @@ final class IndependentSettingBinder {
|
||||
};
|
||||
InvalidationListener weakRefresh = listenerHolder.weak(refresh);
|
||||
refreshHolder.value = weakRefresh;
|
||||
parentSettingsInvalidation.addListener(weakRefresh);
|
||||
|
||||
textField.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||
GameSettings setting = currentSetting.get();
|
||||
@@ -161,9 +167,12 @@ final class IndependentSettingBinder {
|
||||
Supplier<JFXButton> inheritanceButtonFactory,
|
||||
BiConsumer<JFXButton, Boolean> inheritanceButtonUpdater,
|
||||
WeakListenerHolder listenerHolder,
|
||||
Function<GameSettings.Instance, GameSettings.Preset> parentGetter) {
|
||||
Function<GameSettings.Instance, GameSettings.Preset> parentGetter,
|
||||
Observable parentSettingsInvalidation) {
|
||||
ObjectProperty<@Nullable SettingProperty<Integer>> activeProperty = new SimpleObjectProperty<>();
|
||||
ObjectProperty<@Nullable SettingProperty<Integer>> activeParentProperty = new SimpleObjectProperty<>();
|
||||
final Holder<Boolean> updating = new Holder<>(false);
|
||||
final Holder<InvalidationListener> refreshHolder = new Holder<>();
|
||||
@Nullable JFXButton inheritButton;
|
||||
if (presetSetting) {
|
||||
inheritButton = null;
|
||||
@@ -174,6 +183,12 @@ final class IndependentSettingBinder {
|
||||
|
||||
InvalidationListener refresh = observable -> {
|
||||
GameSettings setting = currentSetting.get();
|
||||
updateParentPropertyListener(
|
||||
setting,
|
||||
activeParentProperty,
|
||||
propertyGetter,
|
||||
parentGetter,
|
||||
refreshHolder.value);
|
||||
SettingProperty<Integer> property = activeProperty.get();
|
||||
if (setting == null || property == null || updating.value) {
|
||||
return;
|
||||
@@ -193,6 +208,8 @@ final class IndependentSettingBinder {
|
||||
}
|
||||
};
|
||||
InvalidationListener weakRefresh = listenerHolder.weak(refresh);
|
||||
refreshHolder.value = weakRefresh;
|
||||
parentSettingsInvalidation.addListener(weakRefresh);
|
||||
|
||||
textField.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||
GameSettings setting = currentSetting.get();
|
||||
@@ -257,12 +274,16 @@ final class IndependentSettingBinder {
|
||||
@Nullable JFXButton maxMemoryButton,
|
||||
BiConsumer<JFXButton, Boolean> inheritanceButtonUpdater,
|
||||
WeakListenerHolder listenerHolder,
|
||||
Function<GameSettings.Instance, GameSettings.Preset> parentGetter) {
|
||||
Function<GameSettings.Instance, GameSettings.Preset> parentGetter,
|
||||
Observable parentSettingsInvalidation) {
|
||||
ObjectProperty<@Nullable SettingProperty<Boolean>> activeAutoMemoryProperty = new SimpleObjectProperty<>();
|
||||
ObjectProperty<@Nullable SettingProperty<Integer>> activeMaxMemoryProperty = new SimpleObjectProperty<>();
|
||||
ObjectProperty<@Nullable SettingProperty<Boolean>> activeParentAutoMemoryProperty = new SimpleObjectProperty<>();
|
||||
ObjectProperty<@Nullable SettingProperty<Integer>> activeParentMaxMemoryProperty = new SimpleObjectProperty<>();
|
||||
Label physicalMemoryLabel = (Label) memoryStatusLabels.getLeft();
|
||||
Label allocatedMemoryLabel = (Label) memoryStatusLabels.getRight();
|
||||
final Holder<Boolean> updating = new Holder<>(false);
|
||||
final Holder<InvalidationListener> refreshHolder = new Holder<>();
|
||||
|
||||
int totalMemoryMiB = Math.max(1, (int) MEGABYTES.convertFromBytes(SystemInfo.getTotalMemorySize()));
|
||||
maxMemorySlider.setValueFactory(slider -> Bindings.createStringBinding(
|
||||
@@ -271,6 +292,18 @@ final class IndependentSettingBinder {
|
||||
|
||||
InvalidationListener refresh = observable -> {
|
||||
GameSettings setting = currentSetting.get();
|
||||
updateParentPropertyListener(
|
||||
setting,
|
||||
activeParentAutoMemoryProperty,
|
||||
GameSettings::autoMemoryProperty,
|
||||
parentGetter,
|
||||
refreshHolder.value);
|
||||
updateParentPropertyListener(
|
||||
setting,
|
||||
activeParentMaxMemoryProperty,
|
||||
GameSettings::maxMemoryProperty,
|
||||
parentGetter,
|
||||
refreshHolder.value);
|
||||
SettingProperty<Boolean> autoMemoryProperty = activeAutoMemoryProperty.get();
|
||||
SettingProperty<Integer> maxMemoryProperty = activeMaxMemoryProperty.get();
|
||||
if (setting == null || autoMemoryProperty == null || maxMemoryProperty == null || updating.value) {
|
||||
@@ -299,6 +332,8 @@ final class IndependentSettingBinder {
|
||||
}
|
||||
};
|
||||
InvalidationListener weakRefresh = listenerHolder.weak(refresh);
|
||||
refreshHolder.value = weakRefresh;
|
||||
parentSettingsInvalidation.addListener(weakRefresh);
|
||||
|
||||
choiceList.selectedValueProperty().addListener((observable, oldValue, newValue) -> {
|
||||
GameSettings setting = currentSetting.get();
|
||||
@@ -433,8 +468,6 @@ final class IndependentSettingBinder {
|
||||
}
|
||||
refresh.invalidated(newValue);
|
||||
});
|
||||
SettingsManager.getGameSettings().addListener(weakRefresh);
|
||||
settings().defaultGameSettingsPresetProperty().addListener(weakRefresh);
|
||||
memoryStatusBar.memoryStatusProperty().addListener(observable -> {
|
||||
GameSettings setting = currentSetting.get();
|
||||
if (setting == null) {
|
||||
@@ -468,12 +501,21 @@ final class IndependentSettingBinder {
|
||||
LineInheritableToggleButton button,
|
||||
Function<GameSettings, SettingProperty<Boolean>> propertyGetter,
|
||||
WeakListenerHolder listenerHolder,
|
||||
Function<GameSettings.Instance, GameSettings.Preset> parentGetter) {
|
||||
Function<GameSettings.Instance, GameSettings.Preset> parentGetter,
|
||||
Observable parentSettingsInvalidation) {
|
||||
ObjectProperty<@Nullable SettingProperty<Boolean>> activeProperty = new SimpleObjectProperty<>();
|
||||
ObjectProperty<@Nullable SettingProperty<Boolean>> activeParentProperty = new SimpleObjectProperty<>();
|
||||
final Holder<Boolean> updating = new Holder<>(false);
|
||||
final Holder<InvalidationListener> refreshHolder = new Holder<>();
|
||||
|
||||
InvalidationListener refresh = observable -> {
|
||||
GameSettings setting = currentSetting.get();
|
||||
updateParentPropertyListener(
|
||||
setting,
|
||||
activeParentProperty,
|
||||
propertyGetter,
|
||||
parentGetter,
|
||||
refreshHolder.value);
|
||||
SettingProperty<Boolean> property = activeProperty.get();
|
||||
if (setting == null || property == null || updating.value) {
|
||||
return;
|
||||
@@ -483,7 +525,9 @@ final class IndependentSettingBinder {
|
||||
try {
|
||||
boolean overridden = isOverridden(setting, property);
|
||||
Boolean effectiveValue = getEffectiveValue(setting, propertyGetter, parentGetter);
|
||||
button.setRawValue(overridden ? getDirectValue(property) : Boolean.TRUE.equals(effectiveValue));
|
||||
button.setRawValue(overridden
|
||||
? getDirectValue(property)
|
||||
: Boolean.TRUE.equals(effectiveValue));
|
||||
button.setOverridden(overridden);
|
||||
button.setEffectiveValue(Boolean.TRUE.equals(effectiveValue));
|
||||
} finally {
|
||||
@@ -491,6 +535,8 @@ final class IndependentSettingBinder {
|
||||
}
|
||||
};
|
||||
InvalidationListener weakRefresh = listenerHolder.weak(refresh);
|
||||
refreshHolder.value = weakRefresh;
|
||||
parentSettingsInvalidation.addListener(weakRefresh);
|
||||
|
||||
button.rawValueProperty().addListener((observable, oldValue, newValue) -> {
|
||||
GameSettings setting = currentSetting.get();
|
||||
@@ -503,7 +549,8 @@ final class IndependentSettingBinder {
|
||||
try {
|
||||
setOverridden(setting, property, true);
|
||||
property.setValue(newValue);
|
||||
button.setEffectiveValue(Boolean.TRUE.equals(getEffectiveValue(setting, propertyGetter, parentGetter)));
|
||||
button.setEffectiveValue(Boolean.TRUE.equals(
|
||||
getEffectiveValue(setting, propertyGetter, parentGetter)));
|
||||
} finally {
|
||||
updating.value = false;
|
||||
}
|
||||
@@ -522,7 +569,8 @@ final class IndependentSettingBinder {
|
||||
if (newValue) {
|
||||
property.setValue(button.getRawValue());
|
||||
}
|
||||
button.setEffectiveValue(Boolean.TRUE.equals(getEffectiveValue(setting, propertyGetter, parentGetter)));
|
||||
button.setEffectiveValue(Boolean.TRUE.equals(
|
||||
getEffectiveValue(setting, propertyGetter, parentGetter)));
|
||||
} finally {
|
||||
updating.value = false;
|
||||
}
|
||||
@@ -569,47 +617,26 @@ final class IndependentSettingBinder {
|
||||
Label allocatedMemoryLabel,
|
||||
@Nullable Boolean autoMemory,
|
||||
@Nullable Integer maxMemory) {
|
||||
memoryStatusBar.memoryAllocatedProperty().set(calculateAllocatedMemory(memoryStatusBar, autoMemory, maxMemory));
|
||||
updateMemoryLabels(memoryStatusBar, physicalMemoryLabel, allocatedMemoryLabel, autoMemory, maxMemory);
|
||||
}
|
||||
|
||||
private static double calculateAllocatedMemory(
|
||||
MemoryStatusBar memoryStatusBar,
|
||||
@Nullable Boolean autoMemory,
|
||||
@Nullable Integer maxMemory) {
|
||||
long maxMemoryBytes = Math.max(0, maxMemory != null ? maxMemory : 0) * 1024L * 1024L;
|
||||
return HMCLGameRepository.getAllocatedMemory(
|
||||
maxMemoryBytes,
|
||||
memoryStatusBar.getMemoryStatus().available(),
|
||||
Boolean.TRUE.equals(autoMemory));
|
||||
}
|
||||
|
||||
private static void updateMemoryLabels(
|
||||
MemoryStatusBar memoryStatusBar,
|
||||
Label physicalMemoryLabel,
|
||||
Label allocatedMemoryLabel,
|
||||
@Nullable Boolean autoMemory,
|
||||
@Nullable Integer maxMemory) {
|
||||
PhysicalMemoryStatus memoryStatus = memoryStatusBar.getMemoryStatus();
|
||||
long maxMemoryBytes = Math.max(0, maxMemory != null ? maxMemory : 0) * 1024L * 1024L;
|
||||
boolean autoMemoryEnabled = Boolean.TRUE.equals(autoMemory);
|
||||
|
||||
long memoryAllocated;
|
||||
if (Boolean.TRUE.equals(autoMemory)) {
|
||||
memoryAllocated = HMCLGameRepository.getAutoAllocatedMemory(memoryStatus.available());
|
||||
} else if (maxMemory != null && maxMemory > 0) {
|
||||
memoryAllocated = maxMemory * 1024L * 1024L;
|
||||
} else {
|
||||
memoryAllocated = GameSettings.SUGGESTED_MEMORY * 1024L * 1024L;
|
||||
}
|
||||
|
||||
memoryStatusBar.memoryAllocatedProperty().set(memoryAllocated);
|
||||
|
||||
physicalMemoryLabel.setText(i18n("settings.memory.used_per_total",
|
||||
GIGABYTES.convertFromBytes(memoryStatus.getUsed()),
|
||||
GIGABYTES.convertFromBytes(memoryStatus.total())));
|
||||
allocatedMemoryLabel.setText(i18n(
|
||||
memoryStatus.hasAvailable() && maxMemoryBytes > memoryStatus.available()
|
||||
? (autoMemoryEnabled
|
||||
? "settings.memory.allocate.auto.exceeded"
|
||||
: "settings.memory.allocate.manual.exceeded")
|
||||
: (autoMemoryEnabled
|
||||
? "settings.memory.allocate.auto"
|
||||
: "settings.memory.allocate.manual"),
|
||||
GIGABYTES.convertFromBytes(maxMemoryBytes),
|
||||
GIGABYTES.convertFromBytes(HMCLGameRepository.getAllocatedMemory(
|
||||
maxMemoryBytes,
|
||||
memoryStatus.available(),
|
||||
autoMemoryEnabled)),
|
||||
GIGABYTES.convertFromBytes(memoryStatus.available())));
|
||||
allocatedMemoryLabel.setText(
|
||||
memoryStatus.hasAvailable() && memoryAllocated > memoryStatus.available()
|
||||
? i18n("settings.memory.allocate.exceeded", GIGABYTES.convertFromBytes(memoryAllocated), GIGABYTES.convertFromBytes(memoryStatus.available()))
|
||||
: i18n("settings.memory.allocate", GIGABYTES.convertFromBytes(memoryAllocated)));
|
||||
}
|
||||
|
||||
private static <T> void toggleOverride(
|
||||
@@ -656,9 +683,6 @@ final class IndependentSettingBinder {
|
||||
}
|
||||
refresh.invalidated(newProperty);
|
||||
});
|
||||
SettingsManager.getGameSettings().addListener(weakRefresh);
|
||||
settings().defaultGameSettingsPresetProperty().addListener(weakRefresh);
|
||||
|
||||
GameSettings setting = currentSetting.get();
|
||||
if (setting != null) {
|
||||
SettingProperty<T> property = propertyGetter.apply(setting);
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.WeakListenerHolder;
|
||||
import org.jackhuang.hmcl.ui.construct.ComponentList;
|
||||
import org.jackhuang.hmcl.ui.construct.ImageContainer;
|
||||
import org.jackhuang.hmcl.ui.construct.LineButton;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
@@ -136,14 +137,17 @@ public final class AboutPage extends SpinnerPane {
|
||||
if (obj.has("image")) {
|
||||
JsonElement image = obj.get("image");
|
||||
if (image.isJsonPrimitive()) {
|
||||
button.setLeading(loadImage(image.getAsString()));
|
||||
var imageView = new ImageContainer(32, 32);
|
||||
imageView.setImage(loadImage(image.getAsString()));
|
||||
imageView.setMouseTransparent(true);
|
||||
|
||||
|
||||
button.setLeading(imageView);
|
||||
} else if (image.isJsonObject()) {
|
||||
holder.add(FXUtils.onWeakChangeAndOperate(Themes.darkModeProperty(), darkMode -> {
|
||||
button.setLeading(darkMode
|
||||
? loadImage(image.getAsJsonObject().get("dark").getAsString())
|
||||
: loadImage(image.getAsJsonObject().get("light").getAsString())
|
||||
);
|
||||
}));
|
||||
holder.add(FXUtils.onWeakChangeAndOperate(Themes.darkModeProperty(), darkMode -> button.setLeading(darkMode
|
||||
? loadImage(image.getAsJsonObject().get("dark").getAsString())
|
||||
: loadImage(image.getAsJsonObject().get("light").getAsString())
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.jfoenix.controls.*;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
|
||||
import javafx.scene.control.ScrollPane;
|
||||
@@ -33,14 +34,15 @@ import org.jackhuang.hmcl.task.FetchTask;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.WeakListenerHolder;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.util.Holder;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.SafeStringConverter;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.SettingsManager.settings;
|
||||
@@ -64,10 +66,10 @@ public class DownloadSettingsPage extends StackPane {
|
||||
downloadSource.getStyleClass().add("card-non-transparent");
|
||||
{
|
||||
Function<DownloadSource, String> converter = source -> switch (source) {
|
||||
case DEFAULT -> i18n("settings.launcher.download_source.auto");
|
||||
case OFFICIAL -> i18n("download.provider.official");
|
||||
case MIRROR -> i18n("download.provider.mirror");
|
||||
};
|
||||
case DEFAULT -> i18n("settings.launcher.download_source.auto");
|
||||
case OFFICIAL -> i18n("download.provider.official");
|
||||
case MIRROR -> i18n("download.provider.mirror");
|
||||
};
|
||||
Function<DownloadSource, String> descriptionConverter = source -> {
|
||||
String bundleKey = switch (source) {
|
||||
case DEFAULT -> "download.provider.balanced.desc";
|
||||
@@ -106,91 +108,83 @@ public class DownloadSettingsPage extends StackPane {
|
||||
{
|
||||
var downloadList = new ComponentList();
|
||||
|
||||
VBox downloadThreads = new VBox(16);
|
||||
ComponentSublist fileCommonLocationSublist = new ComponentSublist(() -> {
|
||||
MultiFileItem<EnumCommonDirectory> fileCommonLocation = new MultiFileItem<>();
|
||||
fileCommonLocation.loadChildren(Arrays.asList(
|
||||
new MultiFileItem.Option<>(i18n("launcher.cache_directory.default"), EnumCommonDirectory.DEFAULT),
|
||||
new MultiFileItem.FileOption<>(i18n("settings.custom"), EnumCommonDirectory.CUSTOM)
|
||||
.setChooserTitle(i18n("launcher.cache_directory.choose"))
|
||||
.setSelectionMode(FileSelector.SelectionMode.DIRECTORY)
|
||||
.bindBidirectional(settings().commonDirectoryProperty())
|
||||
));
|
||||
fileCommonLocation.selectedDataProperty().bindBidirectional(settings().commonDirectoryTypeProperty());
|
||||
return List.of(fileCommonLocation);
|
||||
});
|
||||
fileCommonLocationSublist.setTitle(i18n("launcher.cache_directory"));
|
||||
fileCommonLocationSublist.setHasSubtitle(true);
|
||||
fileCommonLocationSublist.descriptionProperty().bind(
|
||||
Bindings.createObjectBinding(() -> Optional.ofNullable(settings().getResolvedCommonDirectory())
|
||||
.orElse(i18n("launcher.cache_directory.disabled")),
|
||||
settings().commonDirectoryProperty(), settings().commonDirectoryTypeProperty()));
|
||||
|
||||
ComponentSublist fileCommonLocationSublist = new ComponentSublist();
|
||||
JFXButton cleanButton = FXUtils.newBorderButton(i18n("launcher.cache_directory.clean"));
|
||||
cleanButton.setOnAction(e -> clearCacheDirectory());
|
||||
fileCommonLocationSublist.setHeaderRight(cleanButton);
|
||||
|
||||
{
|
||||
{
|
||||
MultiFileItem<EnumCommonDirectory> fileCommonLocation = new MultiFileItem<>();
|
||||
fileCommonLocation.loadChildren(Arrays.asList(
|
||||
new MultiFileItem.Option<>(i18n("launcher.cache_directory.default"), EnumCommonDirectory.DEFAULT),
|
||||
new MultiFileItem.FileOption<>(i18n("settings.custom"), EnumCommonDirectory.CUSTOM)
|
||||
.setChooserTitle(i18n("launcher.cache_directory.choose"))
|
||||
.setSelectionMode(FileSelector.SelectionMode.DIRECTORY)
|
||||
.bindBidirectional(settings().commonDirectoryProperty())
|
||||
));
|
||||
fileCommonLocation.selectedDataProperty().bindBidirectional(settings().commonDirectoryTypeProperty());
|
||||
ComponentSublist downloadThreadsSublist = new ComponentSublist(() -> {
|
||||
var downloadThreadsList = new RadioChoiceList<Boolean>();
|
||||
downloadThreadsList.setChoices(
|
||||
new RadioChoiceList.Choice<>(i18n("settings.launcher.download.threads.auto"), true),
|
||||
new RadioChoiceList.Choice<>(i18n("settings.launcher.download.threads.custom"), false) {
|
||||
@Override
|
||||
protected Node createRightNode() {
|
||||
HBox hbox = new HBox(8);
|
||||
hbox.setViewOrder(-1);
|
||||
hbox.setAlignment(Pos.CENTER);
|
||||
// hbox.setPadding(new Insets(0, 0, 0, 30));
|
||||
hbox.disableProperty().bind(settings().autoDownloadThreadsProperty());
|
||||
|
||||
fileCommonLocationSublist.getContent().add(fileCommonLocation);
|
||||
fileCommonLocationSublist.setTitle(i18n("launcher.cache_directory"));
|
||||
fileCommonLocationSublist.setHasSubtitle(true);
|
||||
fileCommonLocationSublist.descriptionProperty().bind(
|
||||
Bindings.createObjectBinding(() -> Optional.ofNullable(settings().getResolvedCommonDirectory())
|
||||
.orElse(i18n("launcher.cache_directory.disabled")),
|
||||
settings().commonDirectoryProperty(), settings().commonDirectoryTypeProperty()));
|
||||
JFXSlider slider = new JFXSlider(1, 256, 64);
|
||||
HBox.setHgrow(slider, Priority.ALWAYS);
|
||||
|
||||
JFXButton cleanButton = FXUtils.newBorderButton(i18n("launcher.cache_directory.clean"));
|
||||
cleanButton.setOnAction(e -> clearCacheDirectory());
|
||||
fileCommonLocationSublist.setHeaderRight(cleanButton);
|
||||
}
|
||||
JFXTextField threadsField = new JFXTextField();
|
||||
FXUtils.setLimitWidth(threadsField, 60);
|
||||
FXUtils.bind(threadsField, settings().downloadThreadsProperty(), SafeStringConverter.fromInteger()
|
||||
.restrict(it -> it > 0)
|
||||
.fallbackTo(FetchTask.DEFAULT_CONCURRENCY)
|
||||
.asPredicate(Validator.addTo(threadsField)));
|
||||
|
||||
{
|
||||
JFXCheckBox chkAutoDownloadThreads = new JFXCheckBox(i18n("settings.launcher.download.threads.auto"));
|
||||
VBox.setMargin(chkAutoDownloadThreads, new Insets(8, 0, 0, 0));
|
||||
chkAutoDownloadThreads.selectedProperty().bindBidirectional(settings().autoDownloadThreadsProperty());
|
||||
downloadThreads.getChildren().add(chkAutoDownloadThreads);
|
||||
var changedByTextField = new Holder<>(false);
|
||||
FXUtils.onChangeAndOperate(settings().downloadThreadsProperty(), value -> {
|
||||
changedByTextField.value = true;
|
||||
slider.setValue(value.intValue());
|
||||
changedByTextField.value = false;
|
||||
});
|
||||
slider.valueProperty().addListener((value, oldVal, newVal) -> {
|
||||
if (changedByTextField.value) return;
|
||||
settings().downloadThreadsProperty().set(value.getValue().intValue());
|
||||
});
|
||||
|
||||
chkAutoDownloadThreads.selectedProperty().addListener((a, b, newValue) -> {
|
||||
if (newValue) {
|
||||
settings().downloadThreadsProperty().set(FetchTask.DEFAULT_CONCURRENCY);
|
||||
hbox.getChildren().setAll(slider, threadsField);
|
||||
return hbox;
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
downloadThreadsList.selectedValueProperty().bindBidirectional(settings().autoDownloadThreadsProperty());
|
||||
|
||||
return List.of(downloadThreadsList);
|
||||
});
|
||||
downloadThreadsSublist.setTitle(i18n("settings.launcher.download.threads"));
|
||||
downloadThreadsSublist.descriptionProperty().bind(Bindings.createStringBinding(() -> {
|
||||
if (settings().autoDownloadThreadsProperty().get()) {
|
||||
return i18n("settings.launcher.download.threads.auto");
|
||||
} else {
|
||||
return Integer.toString(settings().downloadThreadsProperty().get());
|
||||
}
|
||||
}, settings().autoDownloadThreadsProperty(), settings().downloadThreadsProperty()));
|
||||
|
||||
{
|
||||
HBox hbox = new HBox(8);
|
||||
hbox.setStyle("-fx-view-order: -1;"); // prevent the indicator from being covered by the hint
|
||||
hbox.setAlignment(Pos.CENTER);
|
||||
hbox.setPadding(new Insets(0, 0, 0, 30));
|
||||
hbox.disableProperty().bind(settings().autoDownloadThreadsProperty());
|
||||
Label label = new Label(i18n("settings.launcher.download.threads"));
|
||||
|
||||
JFXSlider slider = new JFXSlider(1, 256, 64);
|
||||
HBox.setHgrow(slider, Priority.ALWAYS);
|
||||
|
||||
JFXTextField threadsField = new JFXTextField();
|
||||
FXUtils.setLimitWidth(threadsField, 60);
|
||||
FXUtils.bind(threadsField, settings().downloadThreadsProperty(), SafeStringConverter.fromInteger()
|
||||
.restrict(it -> it > 0)
|
||||
.fallbackTo(FetchTask.DEFAULT_CONCURRENCY)
|
||||
.asPredicate(Validator.addTo(threadsField)));
|
||||
|
||||
AtomicBoolean changedByTextField = new AtomicBoolean(false);
|
||||
FXUtils.onChangeAndOperate(settings().downloadThreadsProperty(), value -> {
|
||||
changedByTextField.set(true);
|
||||
slider.setValue(value.intValue());
|
||||
changedByTextField.set(false);
|
||||
});
|
||||
slider.valueProperty().addListener((value, oldVal, newVal) -> {
|
||||
if (changedByTextField.get()) return;
|
||||
settings().downloadThreadsProperty().set(value.getValue().intValue());
|
||||
});
|
||||
|
||||
hbox.getChildren().setAll(label, slider, threadsField);
|
||||
downloadThreads.getChildren().add(hbox);
|
||||
}
|
||||
|
||||
{
|
||||
HintPane hintPane = new HintPane(MessageDialogPane.MessageType.INFO);
|
||||
VBox.setMargin(hintPane, new Insets(0, 0, 0, 30));
|
||||
hintPane.disableProperty().bind(settings().autoDownloadThreadsProperty());
|
||||
hintPane.setText(i18n("settings.launcher.download.threads.hint"));
|
||||
downloadThreads.getChildren().add(hintPane);
|
||||
}
|
||||
}
|
||||
|
||||
downloadList.getContent().addAll(fileCommonLocationSublist, downloadThreads);
|
||||
downloadList.getContent().addAll(fileCommonLocationSublist, downloadThreadsSublist);
|
||||
content.getChildren().addAll(ComponentList.createComponentListTitle(i18n("download")), downloadList);
|
||||
}
|
||||
|
||||
|
||||
@@ -232,6 +232,7 @@ public final class JavaDownloadDialog extends StackPane {
|
||||
this.remoteVersionBox.setConverter(FXUtils.stringConverter(JavaRemoteVersion::getDistributionVersion));
|
||||
|
||||
this.packageTypeBox = new JFXComboBox<>(FXCollections.observableArrayList());
|
||||
this.packageTypeBox.setConverter(FXUtils.stringConverter(JavaPackageType::getDisplayName));
|
||||
|
||||
this.downloadButton = new JFXButton(i18n("download"));
|
||||
downloadButton.setOnAction(e -> onDownload());
|
||||
|
||||
@@ -25,6 +25,7 @@ import javafx.collections.FXCollections;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.control.Tooltip;
|
||||
@@ -226,6 +227,7 @@ public final class JavaManagementPage extends ListPageBase<JavaRuntime> {
|
||||
|
||||
private static final class JavaItemCell extends ListCell<JavaRuntime> {
|
||||
private final Node graphic;
|
||||
private final Label label = new Label();
|
||||
private final TwoLineListItem content;
|
||||
|
||||
private SVG removeIcon;
|
||||
@@ -241,11 +243,17 @@ public final class JavaManagementPage extends ListPageBase<JavaRuntime> {
|
||||
center.setSpacing(8);
|
||||
center.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
label.setAlignment(Pos.CENTER);
|
||||
label.setMinSize(24, 24);
|
||||
label.setMaxSize(24, 24);
|
||||
label.setPrefSize(24, 24);
|
||||
label.setStyle("-fx-background-color: -monet-secondary-container; -fx-background-radius: 2; -fx-padding: 2; -fx-font-weight: normal; -fx-font-size: 12px;");
|
||||
|
||||
this.content = new TwoLineListItem();
|
||||
HBox.setHgrow(content, Priority.ALWAYS);
|
||||
|
||||
BorderPane.setAlignment(content, Pos.CENTER);
|
||||
center.getChildren().setAll(content);
|
||||
center.getChildren().setAll(label, content);
|
||||
root.setCenter(center);
|
||||
|
||||
HBox right = new HBox();
|
||||
@@ -293,6 +301,9 @@ public final class JavaManagementPage extends ListPageBase<JavaRuntime> {
|
||||
if (empty || item == null) {
|
||||
setGraphic(null);
|
||||
} else {
|
||||
int parsedVersion = item.getParsedVersion();
|
||||
label.setText(parsedVersion >= 0 ? String.valueOf(parsedVersion) : "?");
|
||||
|
||||
content.setTitle((item.isJDK() ? "JDK" : "JRE") + " " + item.getVersion());
|
||||
content.setSubtitle(item.getBinary().toString());
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXPopup;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.RotateTransition;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.collections.FXCollections;
|
||||
@@ -29,6 +30,7 @@ import javafx.event.EventHandler;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.image.ImageView;
|
||||
@@ -254,14 +256,37 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
|
||||
menuButton = new JFXButton();
|
||||
menuButton.getStyleClass().add("menu-button");
|
||||
menuButton.setOnAction(e -> GameListPopupMenu.show(
|
||||
menuButton.setOnAction(e -> {
|
||||
JFXPopup popup = GameListPopupMenu.showAndGetPopup(
|
||||
menuButton,
|
||||
JFXPopup.PopupVPosition.BOTTOM,
|
||||
JFXPopup.PopupHPosition.RIGHT,
|
||||
0,
|
||||
-menuButton.getHeight(),
|
||||
repository, versions
|
||||
));
|
||||
);
|
||||
|
||||
if (popup != null) {
|
||||
Node graphic = menuButton.getGraphic();
|
||||
if (graphic != null) {
|
||||
if (AnimationUtils.isAnimationEnabled()) {
|
||||
Duration duration = Duration.millis(200);
|
||||
RotateTransition rotateOpen = new RotateTransition(duration, graphic);
|
||||
rotateOpen.setToAngle(-180);
|
||||
FXUtils.playAnimation(graphic, "arrow-rotation", rotateOpen);
|
||||
|
||||
popup.setOnHidden(windowEvent -> {
|
||||
RotateTransition rotateClose = new RotateTransition(duration, graphic);
|
||||
rotateClose.setToAngle(0);
|
||||
FXUtils.playAnimation(graphic, "arrow-rotation", rotateClose);
|
||||
});
|
||||
} else {
|
||||
graphic.setRotate(-180);
|
||||
popup.setOnHidden(windowEvent -> graphic.setRotate(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
FXUtils.installFastTooltip(menuButton, i18n("version.switch"));
|
||||
menuButton.setGraphic(SVG.ARROW_DROP_UP.createIcon(30));
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.StringBinding;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
@@ -69,6 +70,7 @@ import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.SafeStringConverter;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jetbrains.annotations.NotNullByDefault;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -213,6 +215,60 @@ public class PersonalizationPage extends StackPane {
|
||||
refresh.invalidated(null);
|
||||
}
|
||||
|
||||
/// Binds a line toggle button to an inheritable theme appearance setting.
|
||||
private static void bindThemeAppearanceToggleButton(
|
||||
LineToggleButton button,
|
||||
String setting,
|
||||
BooleanProperty directProperty,
|
||||
Supplier<Boolean> effectiveValueSupplier) {
|
||||
JFXButton inheritButton = createThemeAppearanceOverrideButton();
|
||||
button.setTitleTrailing(inheritButton);
|
||||
|
||||
Holder<Boolean> updating = new Holder<>(false);
|
||||
InvalidationListener refresh = ignored -> {
|
||||
if (updating.value) {
|
||||
return;
|
||||
}
|
||||
updating.value = true;
|
||||
try {
|
||||
boolean overridden = settings().getThemeAppearanceOverrides().contains(setting);
|
||||
button.setSelected(overridden ? directProperty.get() : effectiveValueSupplier.get());
|
||||
updateThemeAppearanceOverrideButton(inheritButton, !overridden);
|
||||
} finally {
|
||||
updating.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
button.selectedProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (updating.value) {
|
||||
return;
|
||||
}
|
||||
updating.value = true;
|
||||
try {
|
||||
directProperty.set(Boolean.TRUE.equals(newValue));
|
||||
settings().getThemeAppearanceOverrides().add(setting);
|
||||
updateThemeAppearanceOverrideButton(inheritButton, false);
|
||||
} finally {
|
||||
updating.value = false;
|
||||
}
|
||||
refresh.invalidated(null);
|
||||
});
|
||||
directProperty.addListener(refresh);
|
||||
addThemeAppearanceRefreshListener(refresh);
|
||||
|
||||
inheritButton.addEventFilter(MouseEvent.MOUSE_CLICKED, event -> {
|
||||
if (!settings().getThemeAppearanceOverrides().contains(setting)) {
|
||||
directProperty.set(button.isSelected());
|
||||
settings().getThemeAppearanceOverrides().add(setting);
|
||||
} else {
|
||||
settings().getThemeAppearanceOverrides().remove(setting);
|
||||
}
|
||||
refresh.invalidated(null);
|
||||
event.consume();
|
||||
});
|
||||
refresh.invalidated(null);
|
||||
}
|
||||
|
||||
/// Returns the display name for the currently selected launcher theme.
|
||||
private static String getSelectedThemeTitle() {
|
||||
ThemeReference reference = settings().getSelectedThemeOrDefault();
|
||||
@@ -323,10 +379,11 @@ public class PersonalizationPage extends StackPane {
|
||||
private void exportCurrentThemePack(String packId, String version, String packName, String authorName) {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle(i18n("theme_pack.export.title"));
|
||||
String initialFileName = sanitizeThemePackFileName(packName) + ThemePackExporter.FILE_EXTENSION;
|
||||
chooser.setInitialFileName(FileUtils.isNameValid(initialFileName)
|
||||
? initialFileName
|
||||
: "theme-pack" + ThemePackExporter.FILE_EXTENSION);
|
||||
String initialFileName = sanitizeThemePackFileName(packName);
|
||||
chooser.setInitialFileName(
|
||||
(FileUtils.isNameValid(initialFileName) ? initialFileName : "theme-pack")
|
||||
+ (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS ? "" : ThemePackExporter.FILE_EXTENSION)
|
||||
);
|
||||
chooser.getExtensionFilters().setAll(
|
||||
new FileChooser.ExtensionFilter(i18n("theme_pack.file"), "*" + ThemePackExporter.FILE_EXTENSION));
|
||||
|
||||
@@ -443,6 +500,10 @@ public class PersonalizationPage extends StackPane {
|
||||
i18n("settings.launcher.theme_color_type.default"),
|
||||
ThemeColorType.DEFAULT);
|
||||
|
||||
var systemColorChoice = new RadioChoiceList.Choice<ThemeColorType>(
|
||||
i18n("settings.launcher.theme_color_type.system"),
|
||||
ThemeColorType.SYSTEM);
|
||||
|
||||
var customColorChoice = new RadioChoiceList.Choice<ThemeColorType>(
|
||||
i18n("settings.launcher.theme_color_type.custom"),
|
||||
ThemeColorType.CUSTOM) {
|
||||
@@ -459,7 +520,11 @@ public class PersonalizationPage extends StackPane {
|
||||
|
||||
RadioChoiceList<ThemeColorType> themeColorChoiceList = new RadioChoiceList<>();
|
||||
themeColorChoiceList.setFallbackValue(ThemeColorType.DEFAULT);
|
||||
themeColorChoiceList.setChoices(Arrays.asList(defaultColorChoice, customColorChoice, backgroundColorChoice));
|
||||
themeColorChoiceList.setChoices(Arrays.asList(
|
||||
defaultColorChoice,
|
||||
systemColorChoice,
|
||||
customColorChoice,
|
||||
backgroundColorChoice));
|
||||
|
||||
JFXButton themeColorOverrideButton = createThemeAppearanceOverrideButton();
|
||||
themeColorSublist.setTitleRight(themeColorOverrideButton);
|
||||
@@ -1076,6 +1141,25 @@ public class PersonalizationPage extends StackPane {
|
||||
refresh.invalidated(null);
|
||||
themeAppearanceList.getContent().add(titleBarTransparentButton);
|
||||
}
|
||||
|
||||
LineToggleButton windowTransparentButton = new LineToggleButton();
|
||||
windowTransparentButton.setTitle(i18n("settings.launcher.window_transparent"));
|
||||
bindThemeAppearanceToggleButton(
|
||||
windowTransparentButton,
|
||||
LauncherSettings.THEME_APPEARANCE_WINDOW_TRANSPARENT,
|
||||
settings().windowTransparentProperty(),
|
||||
() -> {
|
||||
try {
|
||||
return Objects.requireNonNullElse(
|
||||
ThemePackManager.resolveCurrentWindowTransparent(
|
||||
ThemePackManager.currentResolveContext()),
|
||||
false);
|
||||
} catch (IOException | RuntimeException e) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
themeAppearanceList.getContent().add(windowTransparentButton);
|
||||
|
||||
content.getChildren().addAll(
|
||||
ComponentList.createComponentListTitle(i18n("settings.launcher.appearance")),
|
||||
themeAppearanceList,
|
||||
|
||||
@@ -514,15 +514,23 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
pane.setCenter(spinnerPane);
|
||||
{
|
||||
spinnerPane.loadingProperty().bind(getSkinnable().loadingProperty());
|
||||
spinnerPane.failedReasonProperty().bind(Bindings.createStringBinding(() -> {
|
||||
if (getSkinnable().isFailed()) {
|
||||
return i18n("download.failed.refresh");
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}, getSkinnable().failedProperty()));
|
||||
spinnerPane.failedReasonProperty().bind(
|
||||
Bindings.createStringBinding(() -> {
|
||||
if (getSkinnable().isFailed()) {
|
||||
return i18n("download.failed.refresh");
|
||||
} else if (!getSkinnable().isLoading() && getSkinnable().pageCount.get() >= 0 && getSkinnable().items.isEmpty()) {
|
||||
return i18n("download.failed.no_results_found");
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
getSkinnable().failedProperty(),
|
||||
getSkinnable().loadingProperty(),
|
||||
getSkinnable().pageCount,
|
||||
getSkinnable().items)
|
||||
);
|
||||
spinnerPane.setOnFailedAction(e -> {
|
||||
if (getSkinnable().retrySearch != null) {
|
||||
if (getSkinnable().isFailed() && getSkinnable().retrySearch != null) {
|
||||
getSkinnable().retrySearch.run();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -19,6 +19,8 @@ package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import javafx.geometry.Pos;
|
||||
import org.jackhuang.hmcl.event.Event;
|
||||
import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.setting.GameDirectoryManager;
|
||||
import org.jackhuang.hmcl.setting.VersionIconType;
|
||||
@@ -35,25 +37,35 @@ public class GameAdvancedListItem extends AdvancedListItem {
|
||||
private final ImageContainer imageContainer;
|
||||
private final WeakListenerHolder holder = new WeakListenerHolder();
|
||||
private HMCLGameRepository repository;
|
||||
@SuppressWarnings("unused")
|
||||
@SuppressWarnings({"unused", "FieldCanBeLocal"})
|
||||
private Consumer<Event> onVersionIconChangedListener;
|
||||
|
||||
@SuppressWarnings({"unused", "FieldCanBeLocal"})
|
||||
private Consumer<RefreshedVersionsEvent> onRefreshedVersionsListener;
|
||||
|
||||
public GameAdvancedListItem() {
|
||||
this.imageContainer = new ImageContainer(LEFT_GRAPHIC_SIZE);
|
||||
imageContainer.setMouseTransparent(true);
|
||||
AdvancedListItem.setAlignment(imageContainer, Pos.CENTER);
|
||||
setLeftGraphic(imageContainer);
|
||||
|
||||
holder.add(FXUtils.onWeakChangeAndOperate(GameDirectoryManager.selectedInstanceProperty(), this::loadVersion));
|
||||
holder.add(FXUtils.onWeakChangeAndOperate(GameDirectoryManager.selectedInstanceProperty(), it -> this.loadVersion()));
|
||||
}
|
||||
|
||||
private void loadVersion(String version) {
|
||||
if (GameDirectoryManager.getSelectedRepository() != repository) {
|
||||
private void loadVersion() {
|
||||
String version = GameDirectoryManager.getSelectedInstance();
|
||||
|
||||
boolean repositoryChanged = GameDirectoryManager.getSelectedRepository() != repository;
|
||||
if (repositoryChanged) {
|
||||
repository = GameDirectoryManager.getSelectedRepository();
|
||||
if (repository != null) {
|
||||
onVersionIconChangedListener = repository.onVersionIconChanged.registerWeak(event -> {
|
||||
this.loadVersion(repository.getSelectedInstance());
|
||||
});
|
||||
onVersionIconChangedListener = repository.onVersionIconChanged.registerWeak(event -> {
|
||||
FXUtils.runInFX(this::loadVersion);
|
||||
});
|
||||
|
||||
if (!repository.isLoaded()) {
|
||||
onRefreshedVersionsListener = EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class)
|
||||
.registerWeak(event -> FXUtils.runInFX(this::loadVersion));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (version != null && repository != null && repository.hasVersion(version)) {
|
||||
|
||||
@@ -80,6 +80,7 @@ public final class GameListCell extends ListCell<GameListItem> {
|
||||
|
||||
{
|
||||
HBox center = new HBox();
|
||||
BorderPane.setMargin(center, new Insets(0, 0, 0, 8));
|
||||
center.setMouseTransparent(true);
|
||||
root.setCenter(center);
|
||||
center.setPrefWidth(Region.USE_PREF_SIZE);
|
||||
|
||||
@@ -51,6 +51,12 @@ public final class GameListPopupMenu extends StackPane {
|
||||
public static void show(Node owner, JFXPopup.PopupVPosition vAlign, JFXPopup.PopupHPosition hAlign,
|
||||
double initOffsetX, double initOffsetY,
|
||||
HMCLGameRepository repository, List<Version> versions) {
|
||||
showAndGetPopup(owner, vAlign, hAlign, initOffsetX, initOffsetY, repository, versions);
|
||||
}
|
||||
|
||||
public static JFXPopup showAndGetPopup(Node owner, JFXPopup.PopupVPosition vAlign, JFXPopup.PopupHPosition hAlign,
|
||||
double initOffsetX, double initOffsetY,
|
||||
HMCLGameRepository repository, List<Version> versions) {
|
||||
GameListPopupMenu menu = new GameListPopupMenu();
|
||||
menu.getItems().setAll(versions.stream()
|
||||
.filter(it -> repository.hasVersion(it.getId()))
|
||||
@@ -58,6 +64,8 @@ public final class GameListPopupMenu extends StackPane {
|
||||
.toList());
|
||||
JFXPopup popup = new JFXPopup(menu);
|
||||
popup.show(owner, vAlign, hAlign, initOffsetX, initOffsetY);
|
||||
|
||||
return popup;
|
||||
}
|
||||
|
||||
private final JFXListView<GameItem> listView = new JFXListView<>();
|
||||
|
||||
@@ -163,7 +163,7 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
|
||||
}
|
||||
|
||||
// Sinytra Connector
|
||||
if (analyzer.has(LibraryAnalyzer.LibraryType.NEO_FORGE) && modManager.hasMod("connectormod", ModLoaderType.NEO_FORGE)
|
||||
if (analyzer.has(LibraryAnalyzer.LibraryType.NEO_FORGE) && (modManager.hasMod("connector", ModLoaderType.NEO_FORGE) || modManager.hasMod("connectormod", ModLoaderType.NEO_FORGE))
|
||||
|| "1.20.1".equals(gameVersion) && analyzer.has(LibraryAnalyzer.LibraryType.FORGE) && modManager.hasMod("connectormod", ModLoaderType.FORGE)) {
|
||||
supportedLoaders.add(ModLoaderType.FABRIC);
|
||||
}
|
||||
|
||||
@@ -522,7 +522,7 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
private static final PseudoClass WARNING = PseudoClass.getPseudoClass("warning");
|
||||
|
||||
JFXCheckBox checkBox = new JFXCheckBox();
|
||||
ImageContainer imageContainer = new ImageContainer(24);
|
||||
ImageContainer imageContainer = new ImageContainer(32);
|
||||
TwoLineListItem content = new TwoLineListItem();
|
||||
JFXButton restoreButton = FXUtils.newToggleButton4(SVG.RESTORE);
|
||||
JFXButton infoButton = FXUtils.newToggleButton4(SVG.INFO);
|
||||
|
||||
@@ -471,7 +471,7 @@ public final class ResourcePackListPage extends ListPageBase<ResourcePackListPag
|
||||
private final ResourcePackListPage page;
|
||||
|
||||
private final JFXCheckBox checkBox;
|
||||
private final ImageContainer imageContainer = new ImageContainer(24);
|
||||
private final ImageContainer imageContainer = new ImageContainer(32);
|
||||
private final TwoLineListItem content = new TwoLineListItem();
|
||||
private final JFXButton btnReveal = FXUtils.newToggleButton4(SVG.FOLDER);
|
||||
private final JFXButton btnInfo = FXUtils.newToggleButton4(SVG.INFO);
|
||||
|
||||
@@ -314,8 +314,13 @@ public final class WorldListPage extends ListPageBase<World> implements VersionP
|
||||
|
||||
@Override
|
||||
protected void updateItem(World world, boolean empty) {
|
||||
World oldWorld = getItem();
|
||||
boolean oldEmpty = isEmpty();
|
||||
|
||||
super.updateItem(world, empty);
|
||||
|
||||
if (oldWorld == world && oldEmpty == empty) return;
|
||||
|
||||
this.content.getTags().clear();
|
||||
|
||||
if (empty || world == null) {
|
||||
|
||||
@@ -78,7 +78,7 @@ public final class NativePatcher {
|
||||
JavaRuntime javaVersion,
|
||||
GameSettings.Effective settings,
|
||||
List<String> javaArguments) {
|
||||
if (settings.get(GameSettings::useCustomNativesProperty)) {
|
||||
if (settings.getInheritable(GameSettings::useCustomNativesProperty)) {
|
||||
if (gameVersion != null && GameVersionNumber.compare(gameVersion, "1.19") < 0)
|
||||
return version;
|
||||
|
||||
@@ -96,8 +96,8 @@ public final class NativePatcher {
|
||||
return version.setLibraries(newLibraries);
|
||||
}
|
||||
|
||||
final boolean useNativeGLFW = settings.get(GameSettings::useNativeGLFWProperty);
|
||||
final boolean useNativeOpenAL = settings.get(GameSettings::useNativeOpenALProperty);
|
||||
final boolean useNativeGLFW = settings.getInheritable(GameSettings::useNativeGLFWProperty);
|
||||
final boolean useNativeOpenAL = settings.getInheritable(GameSettings::useNativeOpenALProperty);
|
||||
|
||||
if (OperatingSystem.CURRENT_OS.isLinuxOrBSD() && (useNativeGLFW || useNativeOpenAL)
|
||||
&& gameVersion != null && GameVersionNumber.compare(gameVersion, "1.19") >= 0) {
|
||||
@@ -124,7 +124,7 @@ public final class NativePatcher {
|
||||
Architecture arch = javaVersion.getArchitecture();
|
||||
GameVersionNumber gameVersionNumber = gameVersion != null ? GameVersionNumber.asGameVersion(gameVersion) : null;
|
||||
|
||||
if (settings.get(GameSettings::notPatchNativesProperty))
|
||||
if (settings.getInheritable(GameSettings::notPatchNativesProperty))
|
||||
return version;
|
||||
|
||||
if (arch.isX86() && (os == OperatingSystem.WINDOWS || os == OperatingSystem.LINUX || os == OperatingSystem.MACOS))
|
||||
|
||||
@@ -7,14 +7,17 @@
|
||||
}
|
||||
|
||||
.log-window {
|
||||
-fixed-log-background: #282828;
|
||||
-fixed-log-toggle-selected: #DEE2E6;
|
||||
-fixed-log-toggle-unselected: #6C757D;
|
||||
-fixed-log-text-fill: #FFFFFF;
|
||||
-fixed-log-trace: #495057;
|
||||
-fixed-log-debug: #343A40;
|
||||
-fixed-log-info: #212529;
|
||||
-fixed-log-warn: #331904;
|
||||
-fixed-log-error: #58151C;
|
||||
-fixed-log-fatal: #842029;
|
||||
-fixed-log-selected: #6C757D;
|
||||
-fixed-log-text-fill: #e3e3e3;
|
||||
-fixed-log-trace-background: #495057;
|
||||
-fixed-log-debug-background: #343A40;
|
||||
-fixed-log-info-background: #212529;
|
||||
-fixed-log-warn-background: #413c26;
|
||||
-fixed-log-warn-text-fill: #fdf3aa;
|
||||
-fixed-log-error-background: #4e3534;
|
||||
-fixed-log-error-text-fill: #f9dedc;
|
||||
-fixed-log-fatal-background: #842029;
|
||||
-fixed-log-selected: #3e3e3e;
|
||||
}
|
||||
@@ -7,14 +7,17 @@
|
||||
}
|
||||
|
||||
.log-window {
|
||||
-fixed-log-background: #ffffff;
|
||||
-fixed-log-toggle-selected: #000000;
|
||||
-fixed-log-toggle-unselected: #6C757D;
|
||||
-fixed-log-text-fill: #000000;
|
||||
-fixed-log-trace: #EEE9E0;
|
||||
-fixed-log-debug: #EEE9E0;
|
||||
-fixed-log-info: #FFFFFF;
|
||||
-fixed-log-warn: #FFEECC;
|
||||
-fixed-log-error: #FFCCBB;
|
||||
-fixed-log-fatal: #F7A699;
|
||||
-fixed-log-selected: #C4C4C4;
|
||||
-fixed-log-text-fill: #1f1f1f;
|
||||
-fixed-log-trace-background: #EEE9E0;
|
||||
-fixed-log-debug-background: #EEE9E0;
|
||||
-fixed-log-info-background: #FFFFFF;
|
||||
-fixed-log-warn-background: #fef6d5;
|
||||
-fixed-log-warn-text-fill: #3e2f00;
|
||||
-fixed-log-error-background: #fcebeb;
|
||||
-fixed-log-error-text-fill: #410e0b;
|
||||
-fixed-log-fatal-background: #F7A699;
|
||||
-fixed-log-selected: #f2f2f2;
|
||||
}
|
||||
@@ -396,6 +396,10 @@
|
||||
-fx-cursor: hand;
|
||||
}
|
||||
|
||||
.installer-item-status:incompatible {
|
||||
-fx-opacity: 0.6;
|
||||
}
|
||||
|
||||
.installer-item-wrapper {
|
||||
-fx-background-color: -monet-surface;
|
||||
-fx-background-radius: 4;
|
||||
@@ -448,16 +452,24 @@
|
||||
}
|
||||
|
||||
.launch-pane > .jfx-button.launch-button {
|
||||
-fx-max-width: 207px;
|
||||
-fx-min-width: 207px;
|
||||
-fx-max-width: 200px;
|
||||
-fx-min-width: 200px;
|
||||
-fx-border-width: 0 3px 0 0;
|
||||
-fx-border-color: -monet-on-surface-variant;
|
||||
-fx-border-color: linear-gradient(
|
||||
to bottom,
|
||||
transparent 0%,
|
||||
transparent 20%,
|
||||
-monet-on-surface-variant 20%,
|
||||
-monet-on-surface-variant 80%,
|
||||
transparent 80%,
|
||||
transparent 100%
|
||||
);
|
||||
-fx-background-radius: 4px 0 0 4px;
|
||||
}
|
||||
|
||||
.launch-pane > .jfx-button.menu-button {
|
||||
-fx-max-width: 20px;
|
||||
-fx-min-width: 20px;
|
||||
-fx-max-width: 27px;
|
||||
-fx-min-width: 27px;
|
||||
-fx-font-size: 15px;
|
||||
-fx-background-radius: 0 4px 4px 0;
|
||||
}
|
||||
@@ -1382,27 +1394,27 @@
|
||||
}
|
||||
|
||||
.log-toggle.fatal {
|
||||
-fx-background-color: -fixed-log-fatal;
|
||||
-fx-background-color: -fixed-log-fatal-background;
|
||||
}
|
||||
|
||||
.log-toggle.error {
|
||||
-fx-background-color: -fixed-log-error;
|
||||
-fx-background-color: -fixed-log-error-background;
|
||||
}
|
||||
|
||||
.log-toggle.warn {
|
||||
-fx-background-color: -fixed-log-warn;
|
||||
-fx-background-color: -fixed-log-warn-background;
|
||||
}
|
||||
|
||||
.log-toggle.info {
|
||||
-fx-background-color: -fixed-log-info;
|
||||
-fx-background-color: -fixed-log-info-background;
|
||||
}
|
||||
|
||||
.log-toggle.debug {
|
||||
-fx-background-color: -fixed-log-debug;
|
||||
-fx-background-color: -fixed-log-debug-background;
|
||||
}
|
||||
|
||||
.log-toggle.trace {
|
||||
-fx-background-color: -fixed-log-trace;
|
||||
-fx-background-color: -fixed-log-trace-background;
|
||||
}
|
||||
|
||||
.log-window-list-cell:selected {
|
||||
@@ -1410,7 +1422,7 @@
|
||||
}
|
||||
|
||||
.log-window {
|
||||
-fx-background-color: -monet-surface-container;
|
||||
-fx-background-color: -fixed-log-background;
|
||||
}
|
||||
|
||||
.log-window .scroll-bar .thumb {
|
||||
@@ -1433,36 +1445,36 @@
|
||||
|
||||
.log-window-list-cell:fatal {
|
||||
-fx-text-fill: -fixed-log-text-fill;
|
||||
-fx-background-color: -fixed-log-fatal;
|
||||
-fx-background-color: -fixed-log-fatal-background;
|
||||
}
|
||||
|
||||
.log-window-list-cell:error {
|
||||
-fx-text-fill: -fixed-log-text-fill;
|
||||
-fx-background-color: -fixed-log-error;
|
||||
-fx-text-fill: -fixed-log-error-text-fill;
|
||||
-fx-background-color: -fixed-log-error-background;
|
||||
}
|
||||
|
||||
.log-window-list-cell:warn {
|
||||
-fx-text-fill: -fixed-log-text-fill;
|
||||
-fx-background-color: -fixed-log-warn;
|
||||
-fx-text-fill: -fixed-log-warn-text-fill;
|
||||
-fx-background-color: -fixed-log-warn-background;
|
||||
}
|
||||
|
||||
.log-window-list-cell:info {
|
||||
-fx-text-fill: -fixed-log-text-fill;
|
||||
-fx-background-color: -fixed-log-info;
|
||||
-fx-background-color: -fixed-log-info-background;
|
||||
}
|
||||
|
||||
.log-window-list-cell {
|
||||
-fx-background-color: -fixed-log-info;
|
||||
-fx-background-color: -fixed-log-info-background;
|
||||
}
|
||||
|
||||
.log-window-list-cell:debug {
|
||||
-fx-text-fill: -fixed-log-text-fill;
|
||||
-fx-background-color: -fixed-log-debug;
|
||||
-fx-background-color: -fixed-log-debug-background;
|
||||
}
|
||||
|
||||
.log-window-list-cell:trace {
|
||||
-fx-text-fill: -fixed-log-text-fill;
|
||||
-fx-background-color: -fixed-log-trace;
|
||||
-fx-background-color: -fixed-log-trace-background;
|
||||
}
|
||||
|
||||
.log-window-list-cell:selected {
|
||||
|
||||
@@ -399,6 +399,7 @@ download.external_link=Visit Download Website
|
||||
download.failed=Failed to download "%1$s", response code: %2$d.
|
||||
download.failed.empty=No versions are available. Please click here to go back.
|
||||
download.failed.no_code=Failed to download
|
||||
download.failed.no_results_found=No results found
|
||||
download.failed.refresh=Failed to fetch version list. Please click here to retry.
|
||||
download.game=New Game
|
||||
download.provider.bmclapi=BMCLAPI & MCIM
|
||||
@@ -487,7 +488,7 @@ fatal.samba=If you opened Hello Minecraft! Launcher from a Samba network drive,
|
||||
fatal.illegal_char=Your working directory contains an illegal character "=". You will not be able to use authlib-injector or change the skin of your offline account.
|
||||
fatal.unsupported_platform=Minecraft is not fully supported on your platform yet, so you may experience missing features or even be unable to launch the game.\n\
|
||||
\n\
|
||||
If you cannot launch Minecraft 1.17 and later, you can try switching the "Renderer" to "Mesa LLVMpipe" in "Global/Instance-specific Settings → Advanced Settings" to use CPU rendering for better compatibility.
|
||||
If you cannot launch Minecraft 1.17 and later, you can try switching the "Renderer" to "Mesa LLVMpipe" or "Mesa Lavapipe" in "Global/Instance-specific Settings → Graphics Settings" to use CPU rendering for better compatibility.
|
||||
fatal.unsupported_platform.loongarch=Hello Minecraft! Launcher has provided support for the Loongson platform.\n\
|
||||
If you encounter problems when playing a game, you can visit https://docs.hmcl.net/groups.html for help.
|
||||
fatal.unsupported_platform.windows_arm64=Hello Minecraft! Launcher has provided native support for the Windows on Arm platform. If you encounter problems when playing a game, please try launching the game with Java based on x86 architecture.\n\
|
||||
@@ -1535,7 +1536,7 @@ settings.game_directories.read_only=The game directory file was saved by a newer
|
||||
|
||||
settings.launcher=Launcher Settings
|
||||
settings.launcher.allow_auto_agent=Allow HMCL to modify the game
|
||||
settings.launcher.allow_auto_agent.subtitle=Allow HMCL to modify the game by attaching a Java Agent to improve the gaming experience.
|
||||
settings.launcher.allow_auto_agent.subtitle=Allow HMCL to modify the game by attaching a Java Agent to improve the gaming experience
|
||||
settings.launcher.animation=Animation
|
||||
settings.launcher.appearance=Appearance
|
||||
settings.launcher.brightness=Theme Mode
|
||||
@@ -1547,8 +1548,9 @@ settings.launcher.debug=Debug
|
||||
settings.launcher.disable_april_fools=Do not enable April Fools features
|
||||
settings.launcher.disable_auto_game_options=Do not switch game language
|
||||
settings.launcher.download=Download
|
||||
settings.launcher.download.threads=Threads
|
||||
settings.launcher.download.threads=Download Threads
|
||||
settings.launcher.download.threads.auto=Automatically Determine
|
||||
settings.launcher.download.threads.custom=Custom
|
||||
settings.launcher.download.threads.hint=Too many threads may cause your system to freeze, and your download speed may be affected by your ISP and download servers. It is not always the case that more threads increase your download speed.
|
||||
settings.launcher.download_source=Download Source
|
||||
settings.launcher.download_source.auto=Automatically Choose Download Sources
|
||||
@@ -1604,16 +1606,16 @@ settings.launcher.theme_color_type.background=Follow Background Wallpaper
|
||||
settings.launcher.theme_color_type.custom=Custom Color
|
||||
settings.launcher.theme_color_type.custom.description=Use the color selected above.
|
||||
settings.launcher.theme_color_type.default=Default
|
||||
settings.launcher.theme_color_type.system=Follow System
|
||||
settings.launcher.title_transparent=Transparent Titlebar
|
||||
settings.launcher.turn_off_animations=Disable Animation
|
||||
settings.launcher.version_list_source=Version List
|
||||
settings.launcher.window_transparent=Transparent Window
|
||||
settings.launcher.background.settings.opacity=Opacity
|
||||
|
||||
settings.memory=Memory
|
||||
settings.memory.allocate.auto=%1$.1f GiB Minimum / %2$.1f GiB Allocated
|
||||
settings.memory.allocate.auto.exceeded=%1$.1f GiB Minimum / %2$.1f GiB Allocated (%3$.1f GiB Available)
|
||||
settings.memory.allocate.manual=%1$.1f GiB Allocated
|
||||
settings.memory.allocate.manual.exceeded=%1$.1f GiB Allocated (%3$.1f GiB Available)
|
||||
settings.memory.allocate=%1$.1f GiB Allocated
|
||||
settings.memory.allocate.exceeded=%1$.1f GiB Allocated (%2$.1f GiB Available)
|
||||
settings.memory.auto_allocate=Automatically Allocate
|
||||
settings.memory.lower_bound=Minimum Memory
|
||||
settings.memory.manual_allocate=Manually Allocate Memory
|
||||
@@ -1632,11 +1634,14 @@ settings.type.global.preset=Preset
|
||||
settings.type.global.preset.create=Create Preset
|
||||
settings.type.global.preset.default=Use Default Preset
|
||||
settings.type.global.preset.duplicate_name=Preset name already exists
|
||||
settings.type.global.preset.help=A game settings preset manages a set of game settings that you can adjust on this page.\n\
|
||||
All instances inherit game settings from the current preset by default. You can override specific settings on the instance game settings page.
|
||||
settings.type.global.preset.manage_all=Current Preset
|
||||
settings.type.global.preset.name=Preset Name
|
||||
settings.type.global.preset.rename=Rename Preset
|
||||
settings.type.global.preset.remove=Delete Preset
|
||||
settings.type.global.preset.remove.confirm=Are you sure you want to delete preset "%s"? Instances using this preset will fall back to the default preset.
|
||||
settings.type.global.preset.subtitle=The current instance will inherit game settings from the specified preset
|
||||
settings.type.global.preset.auto_name=Preset %d
|
||||
|
||||
shaderpack.download.title=Download Shader - %1s
|
||||
|
||||
@@ -1428,10 +1428,8 @@ settings.launcher.version_list_source=قائمة الإصدارات
|
||||
settings.launcher.background.settings.opacity=الشفافية
|
||||
|
||||
settings.memory=الذاكرة
|
||||
settings.memory.allocate.auto=%1$.1f جيجابايت كحد أدنى / %2$.1f جيجابايت مخصص
|
||||
settings.memory.allocate.auto.exceeded=%1$.1f جيجابايت كحد أدنى / %2$.1f جيجابايت مخصص (%3$.1f جيجابايت متاح)
|
||||
settings.memory.allocate.manual=%1$.1f جيجابايت مخصص
|
||||
settings.memory.allocate.manual.exceeded=%1$.1f جيجابايت مخصص (%3$.1f جيجابايت متاح)
|
||||
settings.memory.allocate=%1$.1f جيجابايت مخصص
|
||||
settings.memory.allocate.exceeded=%1$.1f جيجابايت مخصص (%2$.1f جيجابايت متاح)
|
||||
settings.memory.auto_allocate=تخصيص تلقائي
|
||||
settings.memory.lower_bound=الحد الأدنى للذاكرة
|
||||
settings.memory.unit.mib=ميجابايت
|
||||
|
||||
@@ -1329,10 +1329,8 @@ settings.launcher.version_list_source=Lista de versiones
|
||||
settings.launcher.background.settings.opacity=Opacidad
|
||||
|
||||
settings.memory=Memoria
|
||||
settings.memory.allocate.auto=%1$.1f GiB Mínimo / %2$.1f GiB Asignado
|
||||
settings.memory.allocate.auto.exceeded=%1$.1f GiB Mínimo / %2$.1f GiB Asignado (%3$.1f GiB Disponible)
|
||||
settings.memory.allocate.manual=%1$.1f GiB Asignados
|
||||
settings.memory.allocate.manual.exceeded=%1$.1f GiB Asignados (%3$.1f GiB Disponibles)
|
||||
settings.memory.allocate=%1$.1f GiB Asignados
|
||||
settings.memory.allocate.exceeded=%1$.1f GiB Asignados (%2$.1f GiB Disponibles)
|
||||
settings.memory.auto_allocate=Asignar automáticamente
|
||||
settings.memory.lower_bound=Memoria mínima
|
||||
settings.memory.unit.mib=MiB
|
||||
|
||||
@@ -856,10 +856,8 @@ settings.launcher.title_transparent=タイトルの透明性
|
||||
settings.launcher.version_list_source=バージョンリストソース
|
||||
|
||||
settings.memory=ゲームメモリ
|
||||
settings.memory.allocate.auto=最小 %1$.1f GiB /実際の %2$.1f GiB
|
||||
settings.memory.allocate.auto.exceeded=最小 %1$.1f GiB /実際の %2$.1f GiB(%3$.1f GiB使用可能)
|
||||
settings.memory.allocate.manual=割り当て %1$.1f GiB
|
||||
settings.memory.allocate.manual.exceeded=割り当て %1$.1f GiB(%3$.1f GiB使用可能)
|
||||
settings.memory.allocate=割り当て %1$.1f GiB
|
||||
settings.memory.allocate.exceeded=割り当て %1$.1f GiB(%2$.1f GiB使用可能)
|
||||
settings.memory.auto_allocate=メモリサイズの自動割り当て
|
||||
settings.memory.lower_bound=最小メモリ
|
||||
settings.memory.unit.mib=MiB
|
||||
|
||||
@@ -1110,7 +1110,7 @@ settings.icon=戲徵
|
||||
|
||||
settings.launcher=啟者置設
|
||||
settings.launcher.allow_auto_agent=許改戲
|
||||
settings.launcher.allow_auto_agent.subtitle=許 HMCL 假附加爪哇 Agent 以改戲,而優其戲感。
|
||||
settings.launcher.allow_auto_agent.subtitle=許 HMCL 假附加爪哇 Agent 以改戲,而優其戲感
|
||||
settings.launcher.appearance=外觀
|
||||
settings.launcher.common_path.tooltip=啟者置戲資與府庫於是。倘戲有成案,則弗取公庫之案。
|
||||
settings.launcher.debug=勘誤
|
||||
@@ -1152,10 +1152,8 @@ settings.launcher.version_list_source=版列供者
|
||||
settings.launcher.background.settings.opacity=陰翳
|
||||
|
||||
settings.memory=戲憶
|
||||
settings.memory.allocate.auto=至低分之 %1$.1f GiB / 實分之 %2$.1f GiB
|
||||
settings.memory.allocate.auto.exceeded=至低分之 %1$.1f GiB / 實分之 %2$.1f GiB (械祇 %3$.1f GiB)
|
||||
settings.memory.allocate.manual=戲分之 %1$.1f GiB
|
||||
settings.memory.allocate.manual.exceeded=戲分之 %1$.1f GiB (械祇 %3$.1f GiB)
|
||||
settings.memory.allocate=戲分之 %1$.1f GiB
|
||||
settings.memory.allocate.exceeded=戲分之 %1$.1f GiB (械祇 %2$.1f GiB)
|
||||
settings.memory.auto_allocate=自調其憶
|
||||
settings.memory.lower_bound=至低憶界
|
||||
settings.memory.unit.mib=兆二進字節
|
||||
|
||||
@@ -1318,10 +1318,8 @@ settings.launcher.version_list_source=Список версий
|
||||
settings.launcher.background.settings.opacity=Непрозрачность
|
||||
|
||||
settings.memory=Оперативная память
|
||||
settings.memory.allocate.auto=Минимум %1$.1f ГиБ / Выделено %2$.1f ГиБ
|
||||
settings.memory.allocate.auto.exceeded=Минимум %1$.1f ГиБ / Выделено %2$.1f ГиБ (Доступно %3$.1f ГиБ)
|
||||
settings.memory.allocate.manual=Выделено %1$.1f ГиБ
|
||||
settings.memory.allocate.manual.exceeded=Выделено %1$.1f ГиБ (Доступно %3$.1f ГиБ)
|
||||
settings.memory.allocate=Выделено %1$.1f ГиБ
|
||||
settings.memory.allocate.exceeded=Выделено %1$.1f ГиБ (Доступно %2$.1f ГиБ)
|
||||
settings.memory.auto_allocate=Автовыделение
|
||||
settings.memory.lower_bound=Мин. объём памяти
|
||||
settings.memory.unit.mib=МиБ
|
||||
|
||||
@@ -1267,10 +1267,8 @@ settings.launcher.version_list_source=Список версій
|
||||
settings.launcher.background.settings.opacity=Непрозорість
|
||||
|
||||
settings.memory=Пам'ять
|
||||
settings.memory.allocate.auto=%1$.1f ГіБ Мінімум / %2$.1f ГіБ Виділено
|
||||
settings.memory.allocate.auto.exceeded=%1$.1f ГіБ Мінімум / %2$.1f ГіБ Виділено (%3$.1f ГіБ Доступно)
|
||||
settings.memory.allocate.manual=%1$.1f ГіБ Виділено
|
||||
settings.memory.allocate.manual.exceeded=%1$.1f ГіБ Виділено (%3$.1f ГіБ Доступно)
|
||||
settings.memory.allocate=%1$.1f ГіБ Виділено
|
||||
settings.memory.allocate.exceeded=%1$.1f ГіБ Виділено (%2$.1f ГіБ Доступно)
|
||||
settings.memory.auto_allocate=Автоматично виділяти
|
||||
settings.memory.lower_bound=Мінімальна пам'ять
|
||||
settings.memory.unit.mib=МіБ
|
||||
|
||||
@@ -397,6 +397,7 @@ download.external_link=開啟下載網站
|
||||
download.failed=下載失敗:%1$s\n錯誤碼:%2$d
|
||||
download.failed.empty=沒有可供安裝的版本,按一下此處返回。
|
||||
download.failed.no_code=下載失敗
|
||||
download.failed.no_results_found=搜尋無結果
|
||||
download.failed.refresh=載入版本清單失敗,按一下此處重試。
|
||||
download.game=新遊戲
|
||||
download.provider.bmclapi=BMCLAPI & MCIM
|
||||
@@ -457,7 +458,7 @@ fatal.deprecated_java_version.download_link=下載 Java %d
|
||||
fatal.samba=如果您正在透過 Samba 共亯的目錄中開啟 Hello Minecraft! Launcher,啟動器可能無法正常工作,請嘗試更新您的 Java 或在本機目錄內開啟 HMCL。
|
||||
fatal.illegal_char=由於您的執行路徑中存在無效字元『=』,您將無法使用外部登入帳戶以及離線登入更換外觀功能。
|
||||
fatal.unsupported_platform=Minecraft 尚未對您的平臺提供完善支援,所以可能影響遊戲體驗或無法啟動遊戲。\n\
|
||||
若無法啟動 Minecraft 1.17 及更高版本,可以嘗試在「(全域/實例特定) 遊戲設定 → 進階設定 → 除錯選項」中將「繪製器」切換為「Mesa LLVMpipe」,以獲得更好的相容性。
|
||||
若無法啟動 Minecraft 1.17 及更高版本,可以嘗試在「(全域/實例特定) 遊戲設定 → 圖形設定」中將「繪製器」切換為「Mesa LLVMpipe」/「Mesa Lavapipe」,以獲得更好的相容性。
|
||||
fatal.unsupported_platform.loongarch=Hello Minecraft! Launcher 已為龍芯提供支援。\n如果遇到問題,你可以點擊右上角幫助按鈕進行求助。
|
||||
fatal.unsupported_platform.windows_arm64=Hello Minecraft! Launcher 已為 Windows on Arm 平臺提供原生支援。如果你在遊戲中遭遇問題,請嘗試使用 x86 架構的 Java 啟動遊戲。\n\n如果你正在使用<b>高通</b>平臺,你可能需要安裝 <a href="ms-windows-store://pdp/?productid=9NQPSL29BFFF">OpenGL 相容包</a>後才能進行遊戲。點擊連結前往 Microsoft Store 安裝相容包。
|
||||
# When translating the following string into non-Latin languages, please retain the English translation
|
||||
@@ -1293,7 +1294,7 @@ settings.game.inherit_global=目前繼承遊戲設定預設。點擊後使用實
|
||||
settings.game.instance_settings.unsupported=目前遊戲設定檔案是由更高版本的 HMCL 儲存的,目前版本的 HMCL 無法正常載入。
|
||||
settings.game.presets.unsupported=遊戲設定預設檔案是由更高版本的 HMCL 儲存的,目前版本的 HMCL 無法修改全域遊戲設定。
|
||||
settings.game.isolation=版本隔離
|
||||
settings.game.isolation.subtitle=啟用後,目前實例將使用獨立的遊戲執行路徑設定,而不是繼承全域設定。
|
||||
settings.game.isolation.subtitle=啟用後,目前實例將使用獨立的遊戲執行路徑設定,而不是繼承全域設定
|
||||
settings.game.java_directory=遊戲 Java
|
||||
settings.game.java_directory.auto=自動選取合適的 Java
|
||||
settings.game.java_directory.auto.not_found=沒有合適的 Java
|
||||
@@ -1326,7 +1327,7 @@ settings.game_directories.read_only=遊戲目錄設定檔是由更高版本的 H
|
||||
|
||||
settings.launcher=啟動器設定
|
||||
settings.launcher.allow_auto_agent=允許 HMCL 修改遊戲
|
||||
settings.launcher.allow_auto_agent.subtitle=允許 HMCL 透過附加 Java Agent 修改遊戲以改善遊戲體驗。
|
||||
settings.launcher.allow_auto_agent.subtitle=允許 HMCL 透過附加 Java Agent 修改遊戲以改善遊戲體驗
|
||||
settings.launcher.animation=动画
|
||||
settings.launcher.appearance=外觀
|
||||
settings.launcher.brightness=主題模式
|
||||
@@ -1338,8 +1339,9 @@ settings.launcher.debug=除錯
|
||||
settings.launcher.disable_april_fools=不啟用愚人節功能
|
||||
settings.launcher.disable_auto_game_options=不自動切換遊戲語言
|
||||
settings.launcher.download=下載
|
||||
settings.launcher.download.threads=執行緒數
|
||||
settings.launcher.download.threads=下載執行緒數
|
||||
settings.launcher.download.threads.auto=自動選取執行緒數
|
||||
settings.launcher.download.threads.custom=自訂執行緒數
|
||||
settings.launcher.download.threads.hint=執行緒數過高可能導致系統卡頓。你的下載速度會受到網際網路運營商、下載來源伺服器等方面的影響。調高下載執行緒數不一定能大幅提升總下載速度。
|
||||
settings.launcher.download_source=下載來源
|
||||
settings.launcher.download_source.auto=自動選取下載來源
|
||||
@@ -1395,16 +1397,16 @@ settings.launcher.theme_color_type.background=跟隨背景圖片
|
||||
settings.launcher.theme_color_type.custom=自訂顏色
|
||||
settings.launcher.theme_color_type.custom.description=使用上方選擇的顏色。
|
||||
settings.launcher.theme_color_type.default=預設
|
||||
settings.launcher.theme_color_type.system=跟隨系統
|
||||
settings.launcher.title_transparent=標題欄透明
|
||||
settings.launcher.turn_off_animations=關閉動畫
|
||||
settings.launcher.version_list_source=版本清單來源
|
||||
settings.launcher.window_transparent=視窗透明
|
||||
settings.launcher.background.settings.opacity=不透明度
|
||||
|
||||
settings.memory=遊戲記憶體
|
||||
settings.memory.allocate.auto=最低分配 %1$.1f GiB / 實際分配 %2$.1f GiB
|
||||
settings.memory.allocate.auto.exceeded=最低分配 %1$.1f GiB / 實際分配 %2$.1f GiB (%3$.1f GiB 可用)
|
||||
settings.memory.allocate.manual=遊戲分配 %1$.1f GiB
|
||||
settings.memory.allocate.manual.exceeded=遊戲分配 %1$.1f GiB (%3$.1f GiB 可用)
|
||||
settings.memory.allocate=遊戲分配 %1$.1f GiB
|
||||
settings.memory.allocate.exceeded=遊戲分配 %1$.1f GiB (%2$.1f GiB 可用)
|
||||
settings.memory.auto_allocate=自動分配
|
||||
settings.memory.lower_bound=最低分配
|
||||
settings.memory.manual_allocate=手動選取記憶體
|
||||
@@ -1423,11 +1425,14 @@ settings.type.global.preset=預設
|
||||
settings.type.global.preset.create=新增預設
|
||||
settings.type.global.preset.default=使用預設項目
|
||||
settings.type.global.preset.duplicate_name=預設名稱已存在
|
||||
settings.type.global.preset.help=遊戲設定預設管理著一組遊戲設定,你可以在下方調整這些設定。\n\
|
||||
所有實例預設繼承當前預設中的遊戲設定,你可以在實例遊戲設定頁面中覆蓋特定的設定。
|
||||
settings.type.global.preset.manage_all=當前預設
|
||||
settings.type.global.preset.name=預設名稱
|
||||
settings.type.global.preset.rename=重新命名預設
|
||||
settings.type.global.preset.remove=刪除預設
|
||||
settings.type.global.preset.remove.confirm=確定要刪除預設「%s」嗎?使用該預設的實例將改用預設項目。
|
||||
settings.type.global.preset.subtitle=當前實例將繼承指定預設中的遊戲設定
|
||||
settings.type.global.preset.auto_name=預設 %d
|
||||
|
||||
shaderpack.download.title=光影下載 - %1s
|
||||
|
||||
@@ -399,6 +399,7 @@ download.external_link=打开下载网站
|
||||
download.failed=下载失败: %1$s,\n错误码:%2$d\n你可以点击右上角帮助按钮进行求助。
|
||||
download.failed.empty=[没有可供安装的版本,点击此处返回]\n(你可以点击右上角帮助按钮进行求助)
|
||||
download.failed.no_code=下载失败
|
||||
download.failed.no_results_found=搜索无结果
|
||||
download.failed.refresh=[加载版本列表失败,点击此处重试]\n(你可以点击右上角帮助按钮进行求助)
|
||||
download.game=新游戏
|
||||
download.provider.bmclapi=BMCLAPI & MCIM
|
||||
@@ -460,7 +461,7 @@ fatal.deprecated_java_version.download_link=下载 Java %d
|
||||
fatal.samba=如果你正在通过 Samba 共享的文件夹中运行 Hello Minecraft! Launcher,启动器可能无法正常工作。请尝试更新你的 Java 或在本地文件夹内运行 HMCL。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。
|
||||
fatal.illegal_char=由于你的运行路径中存在非法字符“=”,你将无法使用外置登录账户以及离线登录更换皮肤功能。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。
|
||||
fatal.unsupported_platform=Minecraft 尚未对你的平台提供完善支持,所以可能影响游戏体验或无法启动游戏。\n\
|
||||
若无法启动 Minecraft 1.17 及更高版本,可以尝试在“(全局/实例特定) 游戏设置 → 高级设置 → 调试选项”中将“渲染器”切换为“Mesa LLVMpipe”,以获得更好的兼容性。\n\
|
||||
若无法启动 Minecraft 1.17 及更高版本,可以尝试在“(全局/实例特定) 游戏设置 → 图形设置”中将“渲染器”切换为“Mesa LLVMpipe” / “Mesa Lavapipe”,以获得更好的兼容性。\n\
|
||||
如遇到问题,你可以点击右上角帮助按钮进行求助。
|
||||
fatal.unsupported_platform.loongarch=Hello Minecraft! Launcher 已为龙芯提供支持。\n如果遇到问题,你可以点击右上角帮助按钮进行求助。
|
||||
fatal.unsupported_platform.windows_arm64=Hello Minecraft! Launcher 已为 Windows on Arm 平台提供原生支持。如果你在游戏中遇到问题,请尝试使用 x86 架构的 Java 启动游戏。\n如果你正在使用 <b>高通</b> 平台,你可能需要安装 <a href="ms-windows-store://pdp/?productid=9NQPSL29BFFF">OpenGL 兼容包</a> 后才能进行游戏。点击链接前往 Microsoft Store 安装兼容包。\n如遇到问题,你可以点击右上角帮助按钮进行求助。
|
||||
@@ -1298,7 +1299,7 @@ settings.game.inherit_global=当前继承游戏设置预设。点击后使用实
|
||||
settings.game.instance_settings.unsupported=当前游戏设置文件是由更高版本的 HMCL 保存的,当前版本的 HMCL 无法正常加载。
|
||||
settings.game.presets.unsupported=游戏设置预设文件是由更高版本的 HMCL 保存的,当前版本的 HMCL 无法修改全局游戏设置。
|
||||
settings.game.isolation=版本隔离
|
||||
settings.game.isolation.subtitle=启用后,当前实例将使用独立的游戏运行路径设置,而不是继承全局设置。
|
||||
settings.game.isolation.subtitle=启用后,当前实例将使用独立的游戏运行路径设置,而不是继承全局设置
|
||||
settings.game.java_directory=游戏 Java
|
||||
settings.game.java_directory.auto=自动选择合适的 Java
|
||||
settings.game.java_directory.auto.not_found=没有合适的 Java
|
||||
@@ -1331,7 +1332,7 @@ settings.game_directories.read_only=游戏文件夹配置文件是由更高版
|
||||
|
||||
settings.launcher=启动器设置
|
||||
settings.launcher.allow_auto_agent=允许 HMCL 修改游戏
|
||||
settings.launcher.allow_auto_agent.subtitle=允许 HMCL 通过附加 Java Agent 修改游戏以改善游戏体验。
|
||||
settings.launcher.allow_auto_agent.subtitle=允许 HMCL 通过附加 Java Agent 修改游戏以改善游戏体验
|
||||
settings.launcher.animation=动画
|
||||
settings.launcher.appearance=外观
|
||||
settings.launcher.brightness=主题模式
|
||||
@@ -1343,8 +1344,9 @@ settings.launcher.debug=调试
|
||||
settings.launcher.disable_april_fools=不启用愚人节功能
|
||||
settings.launcher.disable_auto_game_options=不自动切换游戏语言
|
||||
settings.launcher.download=下载
|
||||
settings.launcher.download.threads=线程数
|
||||
settings.launcher.download.threads=下载线程数
|
||||
settings.launcher.download.threads.auto=自动选择线程数
|
||||
settings.launcher.download.threads.custom=自定义线程数
|
||||
settings.launcher.download.threads.hint=线程数过高可能导致系统卡顿。你的下载速度会受到互联网运营商、下载源服务器等方面的影响。调高下载线程数不一定能大幅提升总下载速度。
|
||||
settings.launcher.download_source=下载源
|
||||
settings.launcher.download_source.auto=自动选择下载源
|
||||
@@ -1400,16 +1402,16 @@ settings.launcher.theme_color_type.background=跟随背景壁纸
|
||||
settings.launcher.theme_color_type.custom=自定义颜色
|
||||
settings.launcher.theme_color_type.custom.description=使用上方选择的颜色。
|
||||
settings.launcher.theme_color_type.default=默认
|
||||
settings.launcher.theme_color_type.system=跟随系统
|
||||
settings.launcher.title_transparent=标题栏透明
|
||||
settings.launcher.turn_off_animations=关闭动画
|
||||
settings.launcher.version_list_source=版本列表源
|
||||
settings.launcher.window_transparent=窗口透明
|
||||
settings.launcher.background.settings.opacity=不透明度
|
||||
|
||||
settings.memory=游戏内存
|
||||
settings.memory.allocate.auto=最低分配 %1$.1f GiB / 实际分配 %2$.1f GiB
|
||||
settings.memory.allocate.auto.exceeded=最低分配 %1$.1f GiB / 实际分配 %2$.1f GiB (%3$.1f GiB 可用)
|
||||
settings.memory.allocate.manual=游戏分配 %1$.1f GiB
|
||||
settings.memory.allocate.manual.exceeded=游戏分配 %1$.1f GiB (设备仅 %3$.1f GiB 可用)
|
||||
settings.memory.allocate=游戏分配 %1$.1f GiB
|
||||
settings.memory.allocate.exceeded=游戏分配 %1$.1f GiB (设备仅 %2$.1f GiB 可用)
|
||||
settings.memory.auto_allocate=自动分配内存
|
||||
settings.memory.lower_bound=最低内存分配
|
||||
settings.memory.manual_allocate=手动选择内存
|
||||
@@ -1428,11 +1430,14 @@ settings.type.global.preset=预设
|
||||
settings.type.global.preset.create=新建预设
|
||||
settings.type.global.preset.default=使用默认预设
|
||||
settings.type.global.preset.duplicate_name=预设名称已存在
|
||||
settings.type.global.preset.help=游戏设置预设管理着一组游戏设置,你可以在下方调整这些设置。\n\
|
||||
所有实例默认继承当前预设中的游戏设置,你可以在实例游戏设置页面中覆盖特定的设置。
|
||||
settings.type.global.preset.manage_all=当前预设
|
||||
settings.type.global.preset.name=预设名称
|
||||
settings.type.global.preset.rename=重命名预设
|
||||
settings.type.global.preset.remove=删除预设
|
||||
settings.type.global.preset.remove.confirm=确定要删除预设“%s”吗?使用该预设的实例将回退到默认预设。
|
||||
settings.type.global.preset.subtitle=当前实例将继承指定预设中的游戏设置
|
||||
settings.type.global.preset.auto_name=预设 %d
|
||||
|
||||
shaderpack.download.title=光影下载 - %1s
|
||||
|
||||
@@ -902,6 +902,149 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-sdl:3.4.1:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-sdl:3.4.1:natives-linux-arm64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-sdl/3.4.1/lwjgl-sdl-3.4.1-natives-linux-arm64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-sdl/3.4.1/lwjgl-sdl-3.4.1-natives-linux-arm64.jar",
|
||||
"sha1": "b5a62eb82113b0227077fb270128487a80a20299",
|
||||
"size": 1434288
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl:3.4.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl:3.4.2:natives-linux-arm64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl/3.4.2/lwjgl-3.4.2-natives-linux-arm64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.4.2/lwjgl-3.4.2-natives-linux-arm64.jar",
|
||||
"sha1": "a2229c542e410157bc79aead90243bd50dbcd79c",
|
||||
"size": 125841
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-jemalloc:3.4.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-jemalloc:3.4.2:natives-linux-arm64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-jemalloc/3.4.2/lwjgl-jemalloc-3.4.2-natives-linux-arm64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.4.2/lwjgl-jemalloc-3.4.2-natives-linux-arm64.jar",
|
||||
"sha1": "8e51830cf9077fb0a89a6f43fa09df55c8c0e8ef",
|
||||
"size": 235933
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-openal:3.4.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-openal:3.4.2:natives-linux-arm64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-openal/3.4.2/lwjgl-openal-3.4.2-natives-linux-arm64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.4.2/lwjgl-openal-3.4.2-natives-linux-arm64.jar",
|
||||
"sha1": "a3c98570beeffd241263e9bc63d8650147b33c4c",
|
||||
"size": 866040
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-opengl:3.4.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-opengl:3.4.2:natives-linux-arm64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-opengl/3.4.2/lwjgl-opengl-3.4.2-natives-linux-arm64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.4.2/lwjgl-opengl-3.4.2-natives-linux-arm64.jar",
|
||||
"sha1": "52f6e847226e41f60773a62314cfada4f82b578c",
|
||||
"size": 80853
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-glfw:3.4.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-glfw:3.4.2:natives-linux-arm64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-glfw/3.4.2/lwjgl-glfw-3.4.2-natives-linux-arm64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.4.2/lwjgl-glfw-3.4.2-natives-linux-arm64.jar",
|
||||
"sha1": "943ec4651c874e9e0a59724976923c7bd8fceb9f",
|
||||
"size": 144523
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-stb:3.4.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-stb:3.4.2:natives-linux-arm64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-stb/3.4.2/lwjgl-stb-3.4.2-natives-linux-arm64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.4.2/lwjgl-stb-3.4.2-natives-linux-arm64.jar",
|
||||
"sha1": "30a261559033e22e7eb7f9a4ac1f2ea96d76f4d8",
|
||||
"size": 258999
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-tinyfd:3.4.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-tinyfd:3.4.2:natives-linux-arm64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-tinyfd/3.4.2/lwjgl-tinyfd-3.4.2-natives-linux-arm64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.4.2/lwjgl-tinyfd-3.4.2-natives-linux-arm64.jar",
|
||||
"sha1": "03b72a19851c1e4fa0ad51a03f78d08f664afc91",
|
||||
"size": 45348
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-freetype:3.4.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-freetype:3.4.2:natives-linux-arm64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-freetype/3.4.2/lwjgl-freetype-3.4.2-natives-linux-arm64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-freetype/3.4.2/lwjgl-freetype-3.4.2-natives-linux-arm64.jar",
|
||||
"sha1": "b5fb3db06d1ecb15f54fabda6a4914ae933525ee",
|
||||
"size": 1319451
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-shaderc:3.4.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-shaderc:3.4.2:natives-linux-arm64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-shaderc/3.4.2/lwjgl-shaderc-3.4.2-natives-linux-arm64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-shaderc/3.4.2/lwjgl-shaderc-3.4.2-natives-linux-arm64.jar",
|
||||
"sha1": "23403f5c295d946d1d2f7785c551f48be2b2fe9b",
|
||||
"size": 3463790
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-spvc:3.4.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-spvc:3.4.2:natives-linux-arm64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-spvc/3.4.2/lwjgl-spvc-3.4.2-natives-linux-arm64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-spvc/3.4.2/lwjgl-spvc-3.4.2-natives-linux-arm64.jar",
|
||||
"sha1": "8d7b46d68788d217143eee402126ea286d100683",
|
||||
"size": 1166742
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-vma:3.4.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-vma:3.4.2:natives-linux-arm64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-vma/3.4.2/lwjgl-vma-3.4.2-natives-linux-arm64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-vma/3.4.2/lwjgl-vma-3.4.2-natives-linux-arm64.jar",
|
||||
"sha1": "c6fb35dd852aedf2266d462c9b7142c41a860298",
|
||||
"size": 60868
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-sdl:3.4.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-sdl:3.4.2:natives-linux-arm64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-sdl/3.4.2/lwjgl-sdl-3.4.2-natives-linux-arm64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-sdl/3.4.2/lwjgl-sdl-3.4.2-natives-linux-arm64.jar",
|
||||
"sha1": "e723c33b467c5ff2af02942a1459db4e3686ce57",
|
||||
"size": 1455063
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl.lwjgl:lwjgl-platform:2.9.0:natives": {
|
||||
"name": "org.glavo.hmcl:lwjgl2-natives:2.9.3",
|
||||
"downloads": {
|
||||
@@ -2637,6 +2780,182 @@
|
||||
"org.lwjgl:lwjgl-shaderc:3.4.1:natives-linux": null,
|
||||
"org.lwjgl:lwjgl-spvc:3.4.1:natives-linux": null,
|
||||
"org.lwjgl:lwjgl-vma:3.4.1:natives-linux": null,
|
||||
"org.lwjgl:lwjgl-sdl:3.4.1:natives-linux": null,
|
||||
"org.lwjgl:lwjgl:3.4.2": {
|
||||
"name": "org.lwjgl:lwjgl:3.4.1",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl/3.4.1/lwjgl-3.4.1.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.4.1/lwjgl-3.4.1.jar",
|
||||
"sha1": "6105690714874b995aa7812e4f7732a21109276c",
|
||||
"size": 1157957
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl:3.4.2:natives-linux": {
|
||||
"name": "org.glavo.hmcl:lwjgl3-natives:3.4.1-rc1",
|
||||
"downloads": {
|
||||
"classifiers": {
|
||||
"linux-loongarch64": {
|
||||
"path": "org/glavo/hmcl/lwjgl3-natives/3.4.1-rc1/lwjgl3-natives-3.4.1-rc1-linux-loongarch64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl3-natives/3.4.1-rc1-linux-loongarch64/lwjgl3-natives-3.4.1-rc1-linux-loongarch64.jar",
|
||||
"sha1": "3fed872fc2f9929d2be25389dd312dfb48e9f89e",
|
||||
"size": 22854580
|
||||
}
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"exclude": [
|
||||
"META-INF/"
|
||||
]
|
||||
},
|
||||
"natives": {
|
||||
"linux": "linux-loongarch64"
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-jemalloc:3.4.2": {
|
||||
"name": "org.lwjgl:lwjgl-jemalloc:3.4.1",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-jemalloc/3.4.1/lwjgl-jemalloc-3.4.1.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.4.1/lwjgl-jemalloc-3.4.1.jar",
|
||||
"sha1": "16b663092854fc6adfe0a941e61cae2bb2e437b6",
|
||||
"size": 47872
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-jemalloc:3.4.2:natives-linux": null,
|
||||
"org.lwjgl:lwjgl-openal:3.4.2": {
|
||||
"name": "org.lwjgl:lwjgl-openal:3.4.1",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-openal/3.4.1/lwjgl-openal-3.4.1.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.4.1/lwjgl-openal-3.4.1.jar",
|
||||
"sha1": "8508bd60589de0539aa465b78fcf838efd07ae9a",
|
||||
"size": 153919
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-openal:3.4.2:natives-linux": null,
|
||||
"org.lwjgl:lwjgl-opengl:3.4.2": {
|
||||
"name": "org.lwjgl:lwjgl-opengl:3.4.1",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-opengl/3.4.1/lwjgl-opengl-3.4.1.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.4.1/lwjgl-opengl-3.4.1.jar",
|
||||
"sha1": "830412cab823c029cda6b9729f9d2ed36cf79959",
|
||||
"size": 937660
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-opengl:3.4.2:natives-linux": null,
|
||||
"org.lwjgl:lwjgl-glfw:3.4.2": {
|
||||
"name": "org.lwjgl:lwjgl-glfw:3.4.1",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-glfw/3.4.1/lwjgl-glfw-3.4.1.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.4.1/lwjgl-glfw-3.4.1.jar",
|
||||
"sha1": "a782b1ddd175c9107bf300fd92920579ea65e2a4",
|
||||
"size": 151893
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-glfw:3.4.2:natives-linux": null,
|
||||
"org.lwjgl:lwjgl-stb:3.4.2": {
|
||||
"name": "org.lwjgl:lwjgl-stb:3.4.1",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-stb/3.4.1/lwjgl-stb-3.4.1.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.4.1/lwjgl-stb-3.4.1.jar",
|
||||
"sha1": "f077c3dafc31924fbe8acb0b913f08977ba27f8e",
|
||||
"size": 142927
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-stb:3.4.2:natives-linux": null,
|
||||
"org.lwjgl:lwjgl-tinyfd:3.4.2": {
|
||||
"name": "org.lwjgl:lwjgl-tinyfd:3.4.1",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-tinyfd/3.4.1/lwjgl-tinyfd-3.4.1.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.4.1/lwjgl-tinyfd-3.4.1.jar",
|
||||
"sha1": "562697d40dbd2ed0424bf24dcf65d51170f81adf",
|
||||
"size": 15931
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-tinyfd:3.4.2:natives-linux": null,
|
||||
"org.lwjgl:lwjgl-freetype:3.4.2": {
|
||||
"name": "org.lwjgl:lwjgl-freetype:3.4.1",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-freetype/3.4.1/lwjgl-freetype-3.4.1.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-freetype/3.4.1/lwjgl-freetype-3.4.1.jar",
|
||||
"sha1": "caa75a37e857efc9b0686e434d708d6420c5b6ea",
|
||||
"size": 464342
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-freetype:3.4.2:natives-linux": null,
|
||||
"org.lwjgl:lwjgl-shaderc:3.4.2": {
|
||||
"name": "org.lwjgl:lwjgl-shaderc:3.4.1",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-shaderc/3.4.1/lwjgl-shaderc-3.4.1.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-shaderc/3.4.1/lwjgl-shaderc-3.4.1.jar",
|
||||
"sha1": "5c02c88f0d879529014c78463b76f43fdecfd272",
|
||||
"size": 153051
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-shaderc:3.4.2:natives-linux": null,
|
||||
"org.lwjgl:lwjgl-spvc:3.4.2": {
|
||||
"name": "org.lwjgl:lwjgl-spvc:3.4.1",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-spvc/3.4.1/lwjgl-spvc-3.4.1.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-spvc/3.4.1/lwjgl-spvc-3.4.1.jar",
|
||||
"sha1": "188553592e97b5ab2dba54a1b403ee80b48dad4c",
|
||||
"size": 148285
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-spvc:3.4.2:natives-linux": null,
|
||||
"org.lwjgl:lwjgl-vma:3.4.2": {
|
||||
"name": "org.lwjgl:lwjgl-vma:3.4.1",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-vma/3.4.1/lwjgl-vma-3.4.1.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-vma/3.4.1/lwjgl-vma-3.4.1.jar",
|
||||
"sha1": "2c54ce10154feb1ede4d23c5947438cdbfdcc003",
|
||||
"size": 109209
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-vma:3.4.2:natives-linux": null,
|
||||
"org.lwjgl:lwjgl-vulkan:3.4.2": {
|
||||
"name": "org.lwjgl:lwjgl-vulkan:3.4.1",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-vulkan/3.4.1/lwjgl-vulkan-3.4.1.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-vulkan/3.4.1/lwjgl-vulkan-3.4.1.jar",
|
||||
"sha1": "d0601fdc38890755e5638048c14cc0f7b990964b",
|
||||
"size": 8356700
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-sdl:3.4.2": {
|
||||
"name": "org.lwjgl:lwjgl-sdl:3.4.1",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-sdl/3.4.1/lwjgl-sdl-3.4.1.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-sdl/3.4.1/lwjgl-sdl-3.4.1.jar",
|
||||
"sha1": "ec28db6e949478044c649fea25eae43b1b161971",
|
||||
"size": 983498
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-sdl:3.4.2:natives-linux": null,
|
||||
"net.java.dev.jna:jna:5.8.0": {
|
||||
"name": "net.java.dev.jna:jna:5.13.0",
|
||||
"downloads": {
|
||||
@@ -4384,6 +4703,160 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-sdl:3.4.1": {
|
||||
"name": "org.lwjgl:lwjgl-sdl:3.4.1",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-sdl/3.4.1/lwjgl-sdl-3.4.1.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-sdl/3.4.1/lwjgl-sdl-3.4.1.jar",
|
||||
"sha1": "ec28db6e949478044c649fea25eae43b1b161971",
|
||||
"size": 983498
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-sdl:3.4.1:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-sdl:3.4.1:natives-linux-riscv64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-sdl/3.4.1/lwjgl-sdl-3.4.1-natives-linux-riscv64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-sdl/3.4.1/lwjgl-sdl-3.4.1-natives-linux-riscv64.jar",
|
||||
"sha1": "d443cd6e73467f437d0be30a3ece7b6fe162a10a",
|
||||
"size": 1207531
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl:3.4.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl:3.4.2:natives-linux-riscv64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl/3.4.2/lwjgl-3.4.2-natives-linux-riscv64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.4.2/lwjgl-3.4.2-natives-linux-riscv64.jar",
|
||||
"sha1": "402730ca3ff248480161a9ed3a9fdf8d4fde91c9",
|
||||
"size": 125934
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-jemalloc:3.4.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-jemalloc:3.4.2:natives-linux-riscv64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-jemalloc/3.4.2/lwjgl-jemalloc-3.4.2-natives-linux-riscv64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.4.2/lwjgl-jemalloc-3.4.2-natives-linux-riscv64.jar",
|
||||
"sha1": "a77b3d7ff67746d6e564e71d394794060470f8af",
|
||||
"size": 248366
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-openal:3.4.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-openal:3.4.2:natives-linux-riscv64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-openal/3.4.2/lwjgl-openal-3.4.2-natives-linux-riscv64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.4.2/lwjgl-openal-3.4.2-natives-linux-riscv64.jar",
|
||||
"sha1": "e2ca54d5de15665bfea2b0565605b7833a1f432d",
|
||||
"size": 938689
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-opengl:3.4.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-opengl:3.4.2:natives-linux-riscv64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-opengl/3.4.2/lwjgl-opengl-3.4.2-natives-linux-riscv64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.4.2/lwjgl-opengl-3.4.2-natives-linux-riscv64.jar",
|
||||
"sha1": "f3daf496bf2a69399dc4e09fe2e2d201893c540f",
|
||||
"size": 79432
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-glfw:3.4.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-glfw:3.4.2:natives-linux-riscv64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-glfw/3.4.2/lwjgl-glfw-3.4.2-natives-linux-riscv64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.4.2/lwjgl-glfw-3.4.2-natives-linux-riscv64.jar",
|
||||
"sha1": "d74e0dcc6378f3113178ca2813901e837f1d1cd5",
|
||||
"size": 141084
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-stb:3.4.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-stb:3.4.2:natives-linux-riscv64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-stb/3.4.2/lwjgl-stb-3.4.2-natives-linux-riscv64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.4.2/lwjgl-stb-3.4.2-natives-linux-riscv64.jar",
|
||||
"sha1": "855b70e32183c611e1299af340e415800fd7e1a8",
|
||||
"size": 268104
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-tinyfd:3.4.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-tinyfd:3.4.2:natives-linux-riscv64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-tinyfd/3.4.2/lwjgl-tinyfd-3.4.2-natives-linux-riscv64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.4.2/lwjgl-tinyfd-3.4.2-natives-linux-riscv64.jar",
|
||||
"sha1": "7efeb9e482978a26dfbb1997f75dba16111a3479",
|
||||
"size": 56944
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-freetype:3.4.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-freetype:3.4.2:natives-linux-riscv64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-freetype/3.4.2/lwjgl-freetype-3.4.2-natives-linux-riscv64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-freetype/3.4.2/lwjgl-freetype-3.4.2-natives-linux-riscv64.jar",
|
||||
"sha1": "9ff54d6bc88c303ba8b159e70a3ecdb4b0d2264b",
|
||||
"size": 1420614
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-shaderc:3.4.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-shaderc:3.4.2:natives-linux-riscv64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-shaderc/3.4.2/lwjgl-shaderc-3.4.2-natives-linux-riscv64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-shaderc/3.4.2/lwjgl-shaderc-3.4.2-natives-linux-riscv64.jar",
|
||||
"sha1": "fba9ad10d282fd27c5818bb8eeafb6f9310bea6d",
|
||||
"size": 3617389
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-spvc:3.4.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-spvc:3.4.2:natives-linux-riscv64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-spvc/3.4.2/lwjgl-spvc-3.4.2-natives-linux-riscv64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-spvc/3.4.2/lwjgl-spvc-3.4.2-natives-linux-riscv64.jar",
|
||||
"sha1": "a2f363009b51e8be9b81c0eb003f6b3f49b7e0c9",
|
||||
"size": 1221335
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-vma:3.4.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-vma:3.4.2:natives-linux-riscv64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-vma/3.4.2/lwjgl-vma-3.4.2-natives-linux-riscv64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-vma/3.4.2/lwjgl-vma-3.4.2-natives-linux-riscv64.jar",
|
||||
"sha1": "367ae2f56dac78acf1fe634f06247c183e657717",
|
||||
"size": 66535
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-sdl:3.4.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-sdl:3.4.2:natives-linux-riscv64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-sdl/3.4.2/lwjgl-sdl-3.4.2-natives-linux-riscv64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-sdl/3.4.2/lwjgl-sdl-3.4.2-natives-linux-riscv64.jar",
|
||||
"sha1": "89f145ab3fc5a049ea23dcbd045b3a7782744f76",
|
||||
"size": 1494992
|
||||
}
|
||||
}
|
||||
},
|
||||
"com.github.oshi:oshi-core:6.6.5": {
|
||||
"name": "com.github.oshi:oshi-core:6.8.0",
|
||||
"downloads": {
|
||||
@@ -6427,6 +6900,160 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-sdl:3.4.1": {
|
||||
"name": "org.lwjgl:lwjgl-sdl:3.4.1",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-sdl/3.4.1/lwjgl-sdl-3.4.1.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-sdl/3.4.1/lwjgl-sdl-3.4.1.jar",
|
||||
"sha1": "ec28db6e949478044c649fea25eae43b1b161971",
|
||||
"size": 983498
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-sdl:3.4.1:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-sdl:3.4.1:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-sdl/3.4.1/lwjgl-sdl-3.4.1-natives-freebsd.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-sdl/3.4.1/lwjgl-sdl-3.4.1-natives-freebsd.jar",
|
||||
"sha1": "1616d4bd1f5ae03faa9c815c728f74d186526eee",
|
||||
"size": 1241210
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl:3.4.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl:3.4.2:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl/3.4.2/lwjgl-3.4.2-natives-freebsd.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.4.2/lwjgl-3.4.2-natives-freebsd.jar",
|
||||
"sha1": "21ddad3dd00753f11618283192c1bc5736049d33",
|
||||
"size": 110201
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-jemalloc:3.4.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-jemalloc:3.4.2:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-jemalloc/3.4.2/lwjgl-jemalloc-3.4.2-natives-freebsd.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.4.2/lwjgl-jemalloc-3.4.2-natives-freebsd.jar",
|
||||
"sha1": "4428d2e872a98a9d176955932be42300d62289a5",
|
||||
"size": 160301
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-openal:3.4.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-openal:3.4.2:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-openal/3.4.2/lwjgl-openal-3.4.2-natives-freebsd.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.4.2/lwjgl-openal-3.4.2-natives-freebsd.jar",
|
||||
"sha1": "8634fe3955f934ade0d6e532a05f2d032d236e2b",
|
||||
"size": 851114
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-opengl:3.4.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-opengl:3.4.2:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-opengl/3.4.2/lwjgl-opengl-3.4.2-natives-freebsd.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.4.2/lwjgl-opengl-3.4.2-natives-freebsd.jar",
|
||||
"sha1": "525c36e210f5d1377a5a43340bbfe4410cb92ad4",
|
||||
"size": 83073
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-glfw:3.4.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-glfw:3.4.2:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-glfw/3.4.2/lwjgl-glfw-3.4.2-natives-freebsd.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.4.2/lwjgl-glfw-3.4.2-natives-freebsd.jar",
|
||||
"sha1": "57e4f91a563b1c6922493d464b3959e6d0729650",
|
||||
"size": 108294
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-stb:3.4.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-stb:3.4.2:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-stb/3.4.2/lwjgl-stb-3.4.2-natives-freebsd.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.4.2/lwjgl-stb-3.4.2-natives-freebsd.jar",
|
||||
"sha1": "613223c342f73b9bacb021427b7b9fe4c884da5b",
|
||||
"size": 225722
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-tinyfd:3.4.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-tinyfd:3.4.2:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-tinyfd/3.4.2/lwjgl-tinyfd-3.4.2-natives-freebsd.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.4.2/lwjgl-tinyfd-3.4.2-natives-freebsd.jar",
|
||||
"sha1": "2fecbb68cf59babd6feccf0e46dcf101e8f1bacd",
|
||||
"size": 40587
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-freetype:3.4.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-freetype:3.4.2:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-freetype/3.4.2/lwjgl-freetype-3.4.2-natives-freebsd.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-freetype/3.4.2/lwjgl-freetype-3.4.2-natives-freebsd.jar",
|
||||
"sha1": "c76950cce5113badc2e2efc4239cb53cf3aeb10b",
|
||||
"size": 1217460
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-shaderc:3.4.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-shaderc:3.4.2:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-shaderc/3.4.2/lwjgl-shaderc-3.4.2-natives-freebsd.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-shaderc/3.4.2/lwjgl-shaderc-3.4.2-natives-freebsd.jar",
|
||||
"sha1": "69342b19493df3bdefeb2be70109c281b83b1a46",
|
||||
"size": 3007116
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-spvc:3.4.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-spvc:3.4.2:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-spvc/3.4.2/lwjgl-spvc-3.4.2-natives-freebsd.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-spvc/3.4.2/lwjgl-spvc-3.4.2-natives-freebsd.jar",
|
||||
"sha1": "4232b25322e980a736e6a41a3fa81955e9eb3a77",
|
||||
"size": 1122080
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-vma:3.4.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-vma:3.4.2:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-vma/3.4.2/lwjgl-vma-3.4.2-natives-freebsd.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-vma/3.4.2/lwjgl-vma-3.4.2-natives-freebsd.jar",
|
||||
"sha1": "f01da65a11d2ef6a88d87969e22c8df367eeb6e6",
|
||||
"size": 63341
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-sdl:3.4.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-sdl:3.4.2:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-sdl/3.4.2/lwjgl-sdl-3.4.2-natives-freebsd.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-sdl/3.4.2/lwjgl-sdl-3.4.2-natives-freebsd.jar",
|
||||
"sha1": "cf0638454457e5977f1b5dc2db8911e430942322",
|
||||
"size": 1240342
|
||||
}
|
||||
}
|
||||
},
|
||||
"net.java.jinput:jinput-platform:2.0.5:natives": null,
|
||||
"com.mojang:text2speech:1.10.3:natives": null,
|
||||
"com.mojang:text2speech:1.11.3:natives": null,
|
||||
|
||||
@@ -1,82 +1,119 @@
|
||||
{
|
||||
"$schema" : "https://schemas.glavo.site/hmcl/theme-pack/1.0.0",
|
||||
"id" : "hmcl.classic",
|
||||
"version" : "1.0.0",
|
||||
"name" : {
|
||||
"default" : "Classic",
|
||||
"zh-Hans" : "经典主题",
|
||||
"zh-Hant" : "經典主題"
|
||||
"$schema": "https://schemas.glavo.site/hmcl/theme-pack/1.0.0",
|
||||
"id": "hmcl.classic",
|
||||
"version": "1.0.0",
|
||||
"name": {
|
||||
"default": "Classic",
|
||||
"es": "Clásico",
|
||||
"ja": "クラシック",
|
||||
"ru": "Классическая",
|
||||
"uk": "Класична",
|
||||
"zh-Hans": "经典主题",
|
||||
"zh-Hant": "經典主題",
|
||||
"lzh": "舊觀"
|
||||
},
|
||||
"description" : {
|
||||
"default" : "Classic HMCL launcher themes",
|
||||
"zh-Hans" : "HMCL 启动器经典主题",
|
||||
"zh-Hant" : "HMCL 啟動器經典主題"
|
||||
"description": {
|
||||
"default": "Classic HMCL launcher themes",
|
||||
"es": "Temas clásicos del launcher HMCL",
|
||||
"ja": "HMCL ランチャーのクラシックテーマ",
|
||||
"ru": "Классические темы лаунчера HMCL",
|
||||
"uk": "Класичні теми лаунчера HMCL",
|
||||
"zh-Hans": "HMCL 启动器经典主题",
|
||||
"zh-Hant": "HMCL 啟動器經典主題",
|
||||
"lzh": "HMCL 啟動器之舊觀"
|
||||
},
|
||||
"authors" : [
|
||||
"authors": [
|
||||
{
|
||||
"name" : "HMCL"
|
||||
"name": {
|
||||
"default": "HMCL",
|
||||
"en-Qabs": "ꞀƆWH"
|
||||
}
|
||||
}
|
||||
],
|
||||
"themes" : [
|
||||
"themes": [
|
||||
{
|
||||
"id" : "2021-08-26",
|
||||
"name" : "HMCL 3.15",
|
||||
"description" : {
|
||||
"default" : "HMCL 3.15 classic appearance",
|
||||
"zh-Hans" : "HMCL 3.15 经典外观",
|
||||
"zh-Hant" : "HMCL 3.15 經典外觀"
|
||||
"id": "2021-08-26",
|
||||
"name": {
|
||||
"default": "Current Theme",
|
||||
"es": "Tema actual",
|
||||
"ja": "現在のテーマ",
|
||||
"ru": "Текущая тема",
|
||||
"uk": "Поточна тема",
|
||||
"zh-Hans": "当前主题",
|
||||
"zh-Hant": "當前主題",
|
||||
"lzh": "今觀"
|
||||
},
|
||||
"color" : "#5C6BC0",
|
||||
"brightness" : "light",
|
||||
"colorStyle" : "fidelity",
|
||||
"background" : {
|
||||
"type" : "builtin",
|
||||
"id" : "2021-08-26",
|
||||
"opacity" : 1.0
|
||||
"description": {
|
||||
"default": "HMCL current theme",
|
||||
"es": "Tema actual de HMCL",
|
||||
"ja": "HMCL の現在のテーマ",
|
||||
"ru": "Текущая тема HMCL",
|
||||
"uk": "Поточна тема HMCL",
|
||||
"zh-Hans": "HMCL 当前主题",
|
||||
"zh-Hant": "HMCL 當前主題",
|
||||
"lzh": "HMCL 之今觀"
|
||||
},
|
||||
"titleBar" : {
|
||||
"transparent" : false
|
||||
"color": "#5C6BC0",
|
||||
"brightness": "light",
|
||||
"colorStyle": "fidelity",
|
||||
"background": {
|
||||
"type": "builtin",
|
||||
"id": "2021-08-26",
|
||||
"opacity": 1.0
|
||||
},
|
||||
"titleBar": {
|
||||
"transparent": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"id" : "2016-02-25",
|
||||
"name" : "HMCL 3.3",
|
||||
"description" : {
|
||||
"default" : "HMCL 3.3 classic appearance",
|
||||
"zh-Hans" : "HMCL 3.3 经典外观",
|
||||
"zh-Hant" : "HMCL 3.3 經典外觀"
|
||||
"id": "2016-02-25",
|
||||
"name": "HMCL 3.3",
|
||||
"description": {
|
||||
"default": "HMCL 3.3 classic appearance",
|
||||
"es": "Apariencia clásica de HMCL 3.3",
|
||||
"ja": "HMCL 3.3 のクラシックな外観",
|
||||
"ru": "Классический вид HMCL 3.3",
|
||||
"uk": "Класичний вигляд HMCL 3.3",
|
||||
"zh-Hans": "HMCL 3.3 经典外观",
|
||||
"zh-Hant": "HMCL 3.3 經典外觀",
|
||||
"lzh": "HMCL 3.3 之舊觀"
|
||||
},
|
||||
"color" : "#5C6BC0",
|
||||
"brightness" : "light",
|
||||
"colorStyle" : "fidelity",
|
||||
"background" : {
|
||||
"type" : "builtin",
|
||||
"id" : "2016-02-25",
|
||||
"opacity" : 1.0
|
||||
"color": "#5C6BC0",
|
||||
"brightness": "light",
|
||||
"colorStyle": "fidelity",
|
||||
"background": {
|
||||
"type": "builtin",
|
||||
"id": "2016-02-25",
|
||||
"opacity": 1.0
|
||||
},
|
||||
"titleBar" : {
|
||||
"transparent" : true
|
||||
"titleBar": {
|
||||
"transparent": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id" : "2015-06-22",
|
||||
"name" : {
|
||||
"default" : "HMCL 2.3"
|
||||
"id": "2015-06-22",
|
||||
"name": {
|
||||
"default": "HMCL 2.3"
|
||||
},
|
||||
"description" : {
|
||||
"default" : "HMCL 2.3 classic appearance",
|
||||
"zh-Hans" : "HMCL 2.3 经典外观",
|
||||
"zh-Hant" : "HMCL 2.3 經典外觀"
|
||||
"description": {
|
||||
"default": "HMCL 2.3 classic appearance",
|
||||
"es": "Apariencia clásica de HMCL 2.3",
|
||||
"ja": "HMCL 2.3 のクラシックな外観",
|
||||
"ru": "Классический вид HMCL 2.3",
|
||||
"uk": "Класичний вигляд HMCL 2.3",
|
||||
"zh-Hans": "HMCL 2.3 经典外观",
|
||||
"zh-Hant": "HMCL 2.3 經典外觀",
|
||||
"lzh": "HMCL 2.3 之舊觀"
|
||||
},
|
||||
"color" : "#E67E22",
|
||||
"brightness" : "light",
|
||||
"colorStyle" : "fidelity",
|
||||
"background" : {
|
||||
"type" : "builtin",
|
||||
"id" : "2015-06-22"
|
||||
"color": "#E67E22",
|
||||
"brightness": "light",
|
||||
"colorStyle": "fidelity",
|
||||
"background": {
|
||||
"type": "builtin",
|
||||
"id": "2015-06-22"
|
||||
},
|
||||
"titleBar" : {
|
||||
"transparent" : false
|
||||
"titleBar": {
|
||||
"transparent": false
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -4,17 +4,31 @@
|
||||
"version": "1.0.0",
|
||||
"name": {
|
||||
"default": "Default",
|
||||
"en-Qabs": "ʇꞁnɐɟǝᗡ",
|
||||
"es": "Predeterminado",
|
||||
"ja": "デフォルト",
|
||||
"ru": "По умолчанию",
|
||||
"uk": "За замовчуванням",
|
||||
"zh-Hans": "默认主题",
|
||||
"zh-Hant": "預設主題"
|
||||
"zh-Hant": "預設主題",
|
||||
"lzh": "預設主題"
|
||||
},
|
||||
"description": {
|
||||
"default": "HMCL Default",
|
||||
"es": "HMCL Predeterminado",
|
||||
"ja": "HMCL デフォルト",
|
||||
"ru": "HMCL По умолчанию",
|
||||
"uk": "HMCL За замовчуванням",
|
||||
"zh-Hans": "HMCL 默认主题",
|
||||
"zh-Hant": "HMCL 預設主題"
|
||||
"zh-Hant": "HMCL 預設主題",
|
||||
"lzh": "HMCL 預設主題"
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
"name": "HMCL"
|
||||
"name": {
|
||||
"default": "HMCL",
|
||||
"en-Qabs": "ꞀƆWH"
|
||||
}
|
||||
}
|
||||
],
|
||||
"theme": {}
|
||||
|
||||
@@ -96,8 +96,8 @@ public final class GameDirectoriesTest {
|
||||
|
||||
GameDirectories gameDirectories = Objects.requireNonNull(LegacyConfigMigrator.extractGameDirectoriesFromConfigJson(settings));
|
||||
|
||||
GameDirectoryID defaultGameDirectoryId = LegacyConfigMigrator.getLegacyProfileID("Default");
|
||||
GameDirectoryID homeGameDirectoryId = LegacyConfigMigrator.getLegacyProfileID("Home");
|
||||
GameDirectoryID defaultGameDirectoryId = GameDirectoryManager.DEFAULT_GAME_DIRECTORY_ID;
|
||||
GameDirectoryID homeGameDirectoryId = GameDirectoryManager.HOME_GAME_DIRECTORY_ID;
|
||||
GameDirectory defaultGameDirectory = gameDirectories.getGameDirectories().stream()
|
||||
.filter(gameDirectory -> defaultGameDirectoryId.equals(gameDirectory.getId()))
|
||||
.findFirst()
|
||||
@@ -357,12 +357,36 @@ public final class GameDirectoriesTest {
|
||||
GameDirectory userGameDirectory = assertSingleDefaultGameDirectory(
|
||||
userDirectories,
|
||||
PortablePath.fromPath(Metadata.MINECRAFT_DIRECTORY));
|
||||
assertEquals(GameDirectoryManager.DEFAULT_GAME_DIRECTORY_ID, localGameDirectory.getId());
|
||||
assertEquals(GameDirectoryManager.HOME_GAME_DIRECTORY_ID, userGameDirectory.getId());
|
||||
assertEquals(2, GameDirectoryManager.getGameDirectories().size());
|
||||
assertTrue(GameDirectoryManager.getGameDirectories().contains(localGameDirectory));
|
||||
assertTrue(GameDirectoryManager.getGameDirectories().contains(userGameDirectory));
|
||||
}
|
||||
}
|
||||
|
||||
/// Tests that newly created stores use the stable IDs for their built-in game directories.
|
||||
@Test
|
||||
public void createsBuiltInGameDirectoriesWithStableIdsForNewStores() throws ReflectiveOperationException {
|
||||
GameDirectories userDirectories = new GameDirectories();
|
||||
userDirectories.setUserFile(true);
|
||||
userDirectories.setNewlyCreated(true);
|
||||
GameDirectories localDirectories = new GameDirectories();
|
||||
localDirectories.setUserFile(false);
|
||||
localDirectories.setNewlyCreated(true);
|
||||
|
||||
try (GameDirectoryEnvironment ignored = new GameDirectoryEnvironment(localDirectories, userDirectories)) {
|
||||
GameDirectoryManager.init();
|
||||
|
||||
GameDirectory localGameDirectory = assertSingleDefaultGameDirectory(localDirectories, PortablePath.of(".minecraft"));
|
||||
GameDirectory userGameDirectory = assertSingleDefaultGameDirectory(
|
||||
userDirectories,
|
||||
PortablePath.fromPath(Metadata.MINECRAFT_DIRECTORY));
|
||||
assertEquals(GameDirectoryManager.DEFAULT_GAME_DIRECTORY_ID, localGameDirectory.getId());
|
||||
assertEquals(GameDirectoryManager.HOME_GAME_DIRECTORY_ID, userGameDirectory.getId());
|
||||
}
|
||||
}
|
||||
|
||||
/// Tests that the selected game directory getter restores a valid selection instead of returning null.
|
||||
@Test
|
||||
public void selectedGameDirectoryGetterNeverReturnsNullWhenGameDirectoriesAreLoaded() throws ReflectiveOperationException {
|
||||
@@ -408,6 +432,44 @@ public final class GameDirectoriesTest {
|
||||
}
|
||||
}
|
||||
|
||||
/// Tests that new isolated installing instances resolve content directories under the version root before metadata is saved.
|
||||
@Test
|
||||
public void newIsolatedInstallingInstanceUsesVersionRootBeforeVersionExists(@TempDir Path tempDirectory)
|
||||
throws ReflectiveOperationException {
|
||||
GameSettingsPresetID defaultPresetId =
|
||||
GameSettingsPresetID.parse("game-settings-preset:123e4567-e89b-12d3-a456-426614174002");
|
||||
GameSettings.Preset defaultPreset = new GameSettings.Preset(defaultPresetId);
|
||||
defaultPreset.defaultIsolationTypeProperty().setValue(DefaultIsolationType.MODDED);
|
||||
GameSettingsPresets presets = new GameSettingsPresets();
|
||||
presets.getPresets().setAll(defaultPreset);
|
||||
|
||||
GameDirectory gameDirectory = new GameDirectory(
|
||||
GameDirectoryID.generate(),
|
||||
LocalizedText.plain("Dev"),
|
||||
PortablePath.of(tempDirectory.toString()));
|
||||
GameDirectories localDirectories = new GameDirectories();
|
||||
localDirectories.getGameDirectories().add(gameDirectory);
|
||||
GameDirectories userDirectories = new GameDirectories();
|
||||
|
||||
try (GameDirectoryEnvironment ignored =
|
||||
new GameDirectoryEnvironment(localDirectories, userDirectories, presets)) {
|
||||
settings().defaultGameSettingsPresetProperty().set(defaultPresetId);
|
||||
HMCLGameRepository repository = new HMCLGameRepository(gameDirectory);
|
||||
String id = "1.21.11-fabric";
|
||||
|
||||
assertFalse(repository.hasVersion(id));
|
||||
assertEquals(repository.getBaseDirectory(), repository.getRunDirectory(id));
|
||||
|
||||
repository.applyDefaultIsolationSettingForNewInstance(id, true);
|
||||
|
||||
assertEquals(repository.getVersionRoot(id), repository.getRunDirectory(id));
|
||||
assertEquals(repository.getVersionRoot(id).resolve("mods"), repository.getModsDirectory(id));
|
||||
|
||||
assertTrue(repository.removeVersionFromDisk(id));
|
||||
assertEquals(repository.getBaseDirectory(), repository.getRunDirectory(id));
|
||||
}
|
||||
}
|
||||
|
||||
/// Tests that instance settings without an explicit parent use the default preset.
|
||||
@Test
|
||||
public void nullInstanceParentUsesDefaultPresetInsteadOfLegacyGameDirectoryPreset()
|
||||
|
||||
@@ -137,6 +137,7 @@ public final class LauncherSettingsMigrationTest {
|
||||
assertEquals(ThemeColor.of("#336699"), launcherSettings.customThemeColorProperty().get());
|
||||
assertEquals(ThemeColorType.CUSTOM, launcherSettings.themeColorTypeProperty().get());
|
||||
assertTrue(launcherSettings.titleBarTransparentProperty().get());
|
||||
assertTrue(launcherSettings.windowTransparentProperty().get());
|
||||
assertEquals("/pictures/background.png", launcherSettings.customBackgroundImagePathProperty().get());
|
||||
assertEquals("https://example.com/background.png",
|
||||
launcherSettings.networkBackgroundImageUrlProperty().get());
|
||||
@@ -148,6 +149,8 @@ public final class LauncherSettingsMigrationTest {
|
||||
assertTrue(launcherSettings.getThemeAppearanceOverrides().contains(LauncherSettings.THEME_APPEARANCE_COLOR));
|
||||
assertTrue(launcherSettings.getThemeAppearanceOverrides().contains(
|
||||
LauncherSettings.THEME_APPEARANCE_TITLE_BAR_TRANSPARENT));
|
||||
assertTrue(launcherSettings.getThemeAppearanceOverrides().contains(
|
||||
LauncherSettings.THEME_APPEARANCE_WINDOW_TRANSPARENT));
|
||||
assertFalse(launcherSettings.getThemeAppearanceOverrides().contains("networkBackgroundImageCachePolicy"));
|
||||
assertEquals("/pictures/background.png", serialized.get("customBackgroundImagePath").getAsString());
|
||||
assertEquals("https://example.com/background.png",
|
||||
@@ -158,6 +161,7 @@ public final class LauncherSettingsMigrationTest {
|
||||
assertEquals(ThemeColorType.CUSTOM.name(), serialized.get("themeColorType").getAsString());
|
||||
assertEquals("dark", serialized.get("themeBrightnessMode").getAsString());
|
||||
assertTrue(serialized.get("titleBarTransparent").getAsBoolean());
|
||||
assertTrue(serialized.get("windowTransparent").getAsBoolean());
|
||||
assertEquals("#336699", serialized.get("customBackgroundPaint").getAsString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.theme;
|
||||
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.layout.Background;
|
||||
import javafx.scene.layout.BackgroundFill;
|
||||
import javafx.scene.layout.CornerRadii;
|
||||
import javafx.scene.paint.Color;
|
||||
import org.jetbrains.annotations.NotNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
/// Tests launcher background opacity storage.
|
||||
@NotNullByDefault
|
||||
public final class LauncherBackgroundTest {
|
||||
/// Verifies opacity changes reuse the unmodified JavaFX background.
|
||||
@Test
|
||||
public void changesOpacityWithoutChangingBackground() {
|
||||
Background background = new Background(
|
||||
new BackgroundFill(Color.WHITE, CornerRadii.EMPTY, Insets.EMPTY));
|
||||
LauncherBackground changed = new LauncherBackground(background, 0.4);
|
||||
|
||||
assertSame(background, changed.background());
|
||||
assertEquals(0.4, changed.opacity());
|
||||
assertEquals(1.0, ((Color) changed.background().getFills().get(0).getFill()).getOpacity());
|
||||
}
|
||||
|
||||
/// Verifies invalid node opacity values are rejected.
|
||||
@Test
|
||||
public void rejectsInvalidOpacity() {
|
||||
assertThrows(IllegalArgumentException.class, () -> new LauncherBackground(Background.EMPTY, -0.1));
|
||||
assertThrows(IllegalArgumentException.class, () -> new LauncherBackground(Background.EMPTY, 1.1));
|
||||
assertThrows(IllegalArgumentException.class, () -> new LauncherBackground(Background.EMPTY, Double.NaN));
|
||||
}
|
||||
}
|
||||
@@ -86,6 +86,7 @@ public final class ThemePackManifestTest {
|
||||
|
||||
assertEquals(BackgroundType.DEFAULT, background.type());
|
||||
assertEquals(1.0, background.opacity(), 0.0);
|
||||
assertNull(theme.appearance().windowTransparent());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,15 +21,17 @@ package org.jackhuang.hmcl.download.java;
|
||||
* @author Glavo
|
||||
*/
|
||||
public enum JavaPackageType {
|
||||
JRE(false, false),
|
||||
JDK(true, false),
|
||||
JREFX(false, true),
|
||||
JDKFX(true, true);
|
||||
JRE("JRE", false, false),
|
||||
JDK("JDK", true, false),
|
||||
JREFX("JRE FX", false, true),
|
||||
JDKFX("JDK FX", true, true);
|
||||
|
||||
private final String displayName;
|
||||
private final boolean jdk;
|
||||
private final boolean javafx;
|
||||
|
||||
JavaPackageType(boolean jdk, boolean javafx) {
|
||||
JavaPackageType(String displayName, boolean jdk, boolean javafx) {
|
||||
this.displayName = displayName;
|
||||
this.jdk = jdk;
|
||||
this.javafx = javafx;
|
||||
}
|
||||
@@ -41,6 +43,10 @@ public enum JavaPackageType {
|
||||
return javafx ? JREFX : JRE;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public boolean isJDK() {
|
||||
return jdk;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import org.jackhuang.hmcl.task.GetTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.gson.JsonSerializable;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@@ -85,12 +86,17 @@ public final class QuiltInstallTask extends Task<Version> {
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
setResult(getPatch(JsonUtils.GSON.fromJson(launchMetaTask.getResult(), QuiltInfo.class), remote.getGameVersion(), remote.getSelfVersion()));
|
||||
setResult(getPatch(JsonUtils.GSON.fromJson(launchMetaTask.getResult(), QuiltInfo.class), remote.getSelfVersion()));
|
||||
|
||||
dependencies.add(dependencyManager.checkLibraryCompletionAsync(getResult(), true));
|
||||
}
|
||||
|
||||
private Version getPatch(QuiltInfo quiltInfo, String gameVersion, String loaderVersion) {
|
||||
/// Creates the Quilt version patch described by the metadata response.
|
||||
///
|
||||
/// @param quiltInfo the metadata returned by Quilt Meta
|
||||
/// @param loaderVersion the Quilt Loader version
|
||||
/// @return the version patch to install
|
||||
static Version getPatch(QuiltInfo quiltInfo, String loaderVersion) {
|
||||
JsonObject launcherMeta = quiltInfo.launcherMeta;
|
||||
Arguments arguments = new Arguments();
|
||||
|
||||
@@ -118,7 +124,9 @@ public final class QuiltInstallTask extends Task<Version> {
|
||||
}
|
||||
|
||||
// libraries.add(new Library(Artifact.fromDescriptor(quiltInfo.hashed.maven), getMavenRepositoryByGroup(quiltInfo.hashed.maven), null));
|
||||
libraries.add(new Library(Artifact.fromDescriptor(quiltInfo.intermediary.maven), getMavenRepositoryByGroup(quiltInfo.intermediary.maven), null));
|
||||
if (quiltInfo.intermediary != null) {
|
||||
libraries.add(new Library(Artifact.fromDescriptor(quiltInfo.intermediary.maven), getMavenRepositoryByGroup(quiltInfo.intermediary.maven), null));
|
||||
}
|
||||
libraries.add(new Library(Artifact.fromDescriptor(quiltInfo.loader.maven), getMavenRepositoryByGroup(quiltInfo.loader.maven), null));
|
||||
|
||||
return new Version(LibraryAnalyzer.LibraryType.QUILT.getPatchId(), loaderVersion, Version.PRIORITY_LOADER, arguments, mainClass, libraries);
|
||||
@@ -139,11 +147,11 @@ public final class QuiltInstallTask extends Task<Version> {
|
||||
@JsonSerializable
|
||||
public static class QuiltInfo {
|
||||
private final LoaderInfo loader;
|
||||
private final IntermediaryInfo hashed;
|
||||
private final IntermediaryInfo intermediary;
|
||||
private final @Nullable IntermediaryInfo hashed;
|
||||
private final @Nullable IntermediaryInfo intermediary;
|
||||
private final JsonObject launcherMeta;
|
||||
|
||||
public QuiltInfo(LoaderInfo loader, IntermediaryInfo hashed, IntermediaryInfo intermediary, JsonObject launcherMeta) {
|
||||
public QuiltInfo(LoaderInfo loader, @Nullable IntermediaryInfo hashed, @Nullable IntermediaryInfo intermediary, JsonObject launcherMeta) {
|
||||
this.loader = loader;
|
||||
this.hashed = hashed;
|
||||
this.intermediary = intermediary;
|
||||
@@ -154,11 +162,11 @@ public final class QuiltInstallTask extends Task<Version> {
|
||||
return loader;
|
||||
}
|
||||
|
||||
public IntermediaryInfo getHashed() {
|
||||
public @Nullable IntermediaryInfo getHashed() {
|
||||
return hashed;
|
||||
}
|
||||
|
||||
public IntermediaryInfo getIntermediary() {
|
||||
public @Nullable IntermediaryInfo getIntermediary() {
|
||||
return intermediary;
|
||||
}
|
||||
|
||||
|
||||
@@ -233,7 +233,7 @@ public class DefaultGameRepository implements GameRepository {
|
||||
public boolean removeVersionFromDisk(String id) {
|
||||
if (EventBus.EVENT_BUS.fireEvent(new RemoveVersionEvent(this, id)) == Event.Result.DENY)
|
||||
return false;
|
||||
if (!versions.containsKey(id))
|
||||
if (versions == null || !versions.containsKey(id))
|
||||
return FileUtils.deleteDirectoryQuietly(getVersionRoot(id));
|
||||
Path file = getVersionRoot(id);
|
||||
if (Files.notExists(file))
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -25,7 +27,7 @@ import java.util.Map;
|
||||
|
||||
/// A wrapper for `Map<String, Object>`, supporting type-safe reading and writing of values.
|
||||
///
|
||||
/// @author Glavo
|
||||
/// @author Glavo
|
||||
public final class SettingsMap {
|
||||
public record Key<T>(String key) {
|
||||
}
|
||||
@@ -87,6 +89,19 @@ public final class SettingsMap {
|
||||
map.clear();
|
||||
}
|
||||
|
||||
/// Returns whether the selected installation includes any non-vanilla component.
|
||||
public boolean isInstallingModdedVersion() {
|
||||
for (LibraryAnalyzer.LibraryType value : LibraryAnalyzer.LibraryType.values()) {
|
||||
if (value != LibraryAnalyzer.LibraryType.MINECRAFT
|
||||
&& value.isModLoader()
|
||||
&& get(value.getPatchId()) instanceof RemoteVersion) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SettingsMap" + map;
|
||||
|
||||
@@ -131,78 +131,105 @@ public abstract class HttpRequest {
|
||||
return con;
|
||||
}
|
||||
|
||||
public static class HttpGetRequest extends HttpRequest {
|
||||
protected HttpGetRequest(String url) {
|
||||
super(url, "GET");
|
||||
protected void checkResponseCode(HttpURLConnection con) throws IOException {
|
||||
int code = con.getResponseCode();
|
||||
if (code / 100 != 2) {
|
||||
if (!ignoreHttpCode && !toleratedHttpCodes.contains(code)) {
|
||||
try {
|
||||
throw new ResponseCodeException(this.url, code, NetworkUtils.readFullyAsString(con));
|
||||
} catch (IOException e) {
|
||||
throw new ResponseCodeException(this.url, code, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static abstract class HttpSimpleRequest extends HttpRequest {
|
||||
protected HttpSimpleRequest(String url, String method) {
|
||||
super(url, method);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString() throws IOException {
|
||||
return getStringWithRetry(() -> {
|
||||
HttpURLConnection con = createConnection();
|
||||
con = resolveConnection(con);
|
||||
return IOUtils.readFullyAsString("gzip".equals(con.getContentEncoding()) ? IOUtils.wrapFromGZip(con.getInputStream()) : con.getInputStream());
|
||||
checkResponseCode(con);
|
||||
|
||||
return IOUtils.readFullyAsString("gzip".equals(con.getContentEncoding())
|
||||
? IOUtils.wrapFromGZip(con.getInputStream())
|
||||
: con.getInputStream());
|
||||
}, retryTimes);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class HttpPostRequest extends HttpRequest {
|
||||
private byte[] bytes;
|
||||
public static class HttpGetRequest extends HttpSimpleRequest {
|
||||
protected HttpGetRequest(String url) { super(url, "GET"); }
|
||||
}
|
||||
|
||||
private HttpPostRequest(String url) {
|
||||
super(url, "POST");
|
||||
public static class HttpDeleteRequest extends HttpSimpleRequest {
|
||||
protected HttpDeleteRequest(String url) { super(url, "DELETE"); }
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static abstract class HttpEntityRequest<T extends HttpEntityRequest<T>> extends HttpRequest {
|
||||
protected byte[] bytes;
|
||||
|
||||
protected HttpEntityRequest(String url, String method) {
|
||||
super(url, method);
|
||||
}
|
||||
|
||||
public HttpPostRequest contentType(String contentType) {
|
||||
public T contentType(String contentType) {
|
||||
headers.put("Content-Type", contentType);
|
||||
return this;
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
public HttpPostRequest json(Object payload) throws JsonParseException {
|
||||
public T json(Object payload) throws JsonParseException {
|
||||
return string(payload instanceof String ? (String) payload : GSON.toJson(payload), "application/json");
|
||||
}
|
||||
|
||||
public HttpPostRequest form(Map<String, String> params) {
|
||||
public T form(Map<String, String> params) {
|
||||
return string(NetworkUtils.withQuery("", params), "application/x-www-form-urlencoded");
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public final HttpPostRequest form(Pair<String, String>... params) {
|
||||
public final T form(Pair<String, String>... params) {
|
||||
return form(mapOf(params));
|
||||
}
|
||||
|
||||
public HttpPostRequest string(String payload, String contentType) {
|
||||
public T string(String payload, String contentType) {
|
||||
bytes = payload.getBytes(UTF_8);
|
||||
header("Content-Length", "" + bytes.length);
|
||||
header("Content-Length", String.valueOf(bytes.length));
|
||||
contentType(contentType + "; charset=utf-8");
|
||||
return this;
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString() throws IOException {
|
||||
return getStringWithRetry(() -> {
|
||||
HttpURLConnection con = createConnection();
|
||||
con.setDoOutput(true);
|
||||
|
||||
try (OutputStream os = con.getOutputStream()) {
|
||||
os.write(bytes);
|
||||
}
|
||||
|
||||
URL url = new URL(this.url);
|
||||
|
||||
if (con.getResponseCode() / 100 != 2) {
|
||||
if (!ignoreHttpCode && !toleratedHttpCodes.contains(con.getResponseCode())) {
|
||||
try {
|
||||
throw new ResponseCodeException(url.toString(), con.getResponseCode(), NetworkUtils.readFullyAsString(con));
|
||||
} catch (IOException e) {
|
||||
throw new ResponseCodeException(url.toString(), con.getResponseCode(), e);
|
||||
}
|
||||
if (bytes != null) {
|
||||
try (OutputStream os = con.getOutputStream()) {
|
||||
os.write(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
checkResponseCode(con);
|
||||
return NetworkUtils.readFullyAsString(con);
|
||||
}, retryTimes);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class HttpPostRequest extends HttpEntityRequest<HttpPostRequest> {
|
||||
private HttpPostRequest(String url) { super(url, "POST"); }
|
||||
}
|
||||
|
||||
public static final class HttpPutRequest extends HttpEntityRequest<HttpPutRequest> {
|
||||
private HttpPutRequest(String url) { super(url, "PUT"); }
|
||||
}
|
||||
|
||||
public static HttpGetRequest GET(String url) {
|
||||
return new HttpGetRequest(url);
|
||||
}
|
||||
@@ -216,6 +243,19 @@ public abstract class HttpRequest {
|
||||
return new HttpPostRequest(url);
|
||||
}
|
||||
|
||||
public static HttpPutRequest PUT(String url) throws MalformedURLException {
|
||||
return new HttpPutRequest(url);
|
||||
}
|
||||
|
||||
public static HttpDeleteRequest DELETE(String url) {
|
||||
return new HttpDeleteRequest(url);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static HttpDeleteRequest DELETE(String url, Pair<String, String>... query) {
|
||||
return DELETE(NetworkUtils.withQuery(url, mapOf(query)));
|
||||
}
|
||||
|
||||
private static String getStringWithRetry(ExceptionalSupplier<String, IOException> supplier, int retryTimes) throws IOException {
|
||||
Throwable exception = null;
|
||||
for (int i = 0; i < retryTimes; i++) {
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jetbrains.annotations.NotNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/// Tests for installer state stored in [SettingsMap].
|
||||
@NotNullByDefault
|
||||
public final class SettingsMapTest {
|
||||
/// Tests that vanilla game selection alone is not treated as a modded installation.
|
||||
@Test
|
||||
public void minecraftSelectionIsNotModdedInstallation() {
|
||||
SettingsMap settings = new SettingsMap();
|
||||
settings.put(LibraryAnalyzer.LibraryType.MINECRAFT.getPatchId(), remoteVersion("game"));
|
||||
|
||||
assertFalse(settings.isInstallingModdedVersion());
|
||||
}
|
||||
|
||||
/// Tests that selecting a non-vanilla installer is treated as a modded installation.
|
||||
@Test
|
||||
public void modLoaderSelectionIsModdedInstallation() {
|
||||
SettingsMap settings = new SettingsMap();
|
||||
settings.put(LibraryAnalyzer.LibraryType.MINECRAFT.getPatchId(), remoteVersion("game"));
|
||||
settings.put(LibraryAnalyzer.LibraryType.FABRIC.getPatchId(), remoteVersion("fabric"));
|
||||
|
||||
assertTrue(settings.isInstallingModdedVersion());
|
||||
}
|
||||
|
||||
/// Creates a minimal remote version for installer state tests.
|
||||
private static RemoteVersion remoteVersion(String libraryId) {
|
||||
return new RemoteVersion(libraryId, "1.21.11", "test", Instant.EPOCH, List.of());
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import org.jackhuang.hmcl.gradle.l10n.ParseLanguageSubtagRegistry
|
||||
|
||||
plugins {
|
||||
id("checkstyle")
|
||||
id("org.glavo.gradle-wrapper-neo") version "0.2.0"
|
||||
}
|
||||
|
||||
group = "org.jackhuang"
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
"themeColor",
|
||||
"themeColorStyle",
|
||||
"titleBarTransparent",
|
||||
"windowTransparent",
|
||||
"background",
|
||||
"backgroundOpacity"
|
||||
]
|
||||
@@ -137,6 +138,9 @@
|
||||
"launcherFontFamily": {
|
||||
"type": "string"
|
||||
},
|
||||
"windowTransparent": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"animationDisabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
||||
@@ -174,6 +174,9 @@
|
||||
"titleBar": {
|
||||
"$ref": "#/$defs/titleBar"
|
||||
},
|
||||
"windowTransparent": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"overrides": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@@ -217,6 +220,11 @@
|
||||
"required": [
|
||||
"titleBar"
|
||||
]
|
||||
},
|
||||
{
|
||||
"required": [
|
||||
"windowTransparent"
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
@@ -240,6 +248,9 @@
|
||||
},
|
||||
"titleBar": {
|
||||
"$ref": "#/$defs/titleBar"
|
||||
},
|
||||
"windowTransparent": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -17,7 +17,7 @@ fxsvgimage = "1.3"
|
||||
jna = "5.18.1"
|
||||
pci-ids = "0.4.0"
|
||||
java-info = "1.0"
|
||||
authlib-injector = "1.2.7"
|
||||
authlib-injector = "1.2.8"
|
||||
lwjgl-unsafe-agent = "2.0"
|
||||
monet-fx = "0.4.0"
|
||||
terracotta = "0.4.2"
|
||||
|
||||
Vendored
+4105
File diff suppressed because it is too large.
Load diff
Vendored
BIN
Binary file not shown.
+5
-3
@@ -1,7 +1,9 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip
|
||||
networkTimeout=120000
|
||||
validateDistributionUrl=true
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip
|
||||
distributionSha256Sum=9c0f7faeeb306cb14e4279a3e084ca6b596894089a0638e68a07c945a32c9e14
|
||||
networkTimeout=10000
|
||||
retries=0
|
||||
retryBackOffMs=500
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
# Gradle Wrapper Neo startup script for POSIX.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
@@ -29,7 +29,7 @@
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
# ksh gradlew
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
@@ -56,29 +56,29 @@
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
# (3) This file is maintained by the Gradle Wrapper Neo project. It compiles
|
||||
# gradle/wrapper/GradleWrapperNeo.java on demand and launches the Wrapper
|
||||
# without storing a Wrapper JAR in the project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
# Documentation and updates: https://github.com/Glavo/gradle-wrapper-neo
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Determine the directory containing this launcher
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
LAUNCHER_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
*) app_path=$LAUNCHER_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
@@ -86,7 +86,7 @@ done
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
LAUNCHER_HOME=$( cd -P "${LAUNCHER_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
@@ -170,7 +170,7 @@ fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
LAUNCHER_HOME=$( cygpath --path --mixed "$LAUNCHER_HOME" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
@@ -202,17 +202,110 @@ fi
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Select the wrapper entry point. GradleWrapperNeo.java is compiled to a temporary
|
||||
# classes directory only for the first bootstrap; Java packages the final JAR.
|
||||
LAUNCHER_WRAPPER_DIR=$LAUNCHER_HOME/gradle/wrapper
|
||||
APP_HOME=
|
||||
|
||||
if [ -f "$LAUNCHER_WRAPPER_DIR/gradle-wrapper.properties" ] ; then
|
||||
APP_HOME=$LAUNCHER_HOME
|
||||
else
|
||||
NEO_SEARCH_START=$( pwd -P )
|
||||
NEO_SEARCH_DIR=$NEO_SEARCH_START
|
||||
while [ -z "$APP_HOME" ] ; do
|
||||
NEO_CANDIDATE_WRAPPER_DIR=$NEO_SEARCH_DIR/gradle/wrapper
|
||||
if [ -f "$NEO_CANDIDATE_WRAPPER_DIR/gradle-wrapper.properties" ] ; then
|
||||
APP_HOME=$NEO_SEARCH_DIR
|
||||
elif [ "$NEO_SEARCH_DIR" = / ] ; then
|
||||
die "ERROR: Could not find gradle/wrapper/gradle-wrapper.properties searching from $NEO_SEARCH_START."
|
||||
else
|
||||
NEO_SEARCH_DIR=${NEO_SEARCH_DIR%/*}
|
||||
if [ -z "$NEO_SEARCH_DIR" ] ; then
|
||||
NEO_SEARCH_DIR=/
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
PROJECT_NEO_SOURCE=$APP_HOME/gradle/wrapper/GradleWrapperNeo.java
|
||||
if [ -f "$PROJECT_NEO_SOURCE" ] ; then
|
||||
NEO_SOURCE=$PROJECT_NEO_SOURCE
|
||||
else
|
||||
NEO_SOURCE=$LAUNCHER_WRAPPER_DIR/GradleWrapperNeo.java
|
||||
fi
|
||||
|
||||
NEO_WORK_DIR=$APP_HOME/.gradle/wrapper-neo
|
||||
NEO_JAR=$NEO_WORK_DIR/gradle-wrapper-neo.jar
|
||||
NEO_BOOTSTRAP_DIR=$NEO_WORK_DIR/bootstrap/$$
|
||||
NEO_CLASSES_DIR=$NEO_BOOTSTRAP_DIR/classes
|
||||
|
||||
NEO_JAVA_APP_HOME=$APP_HOME
|
||||
NEO_JAVA_SOURCE=$NEO_SOURCE
|
||||
NEO_JAVA_JAR=$NEO_JAR
|
||||
NEO_JAVA_CLASSES_DIR=$NEO_CLASSES_DIR
|
||||
if "$cygwin" || "$msys" ; then
|
||||
NEO_JAVA_APP_HOME=$( cygpath --mixed "$APP_HOME" )
|
||||
NEO_JAVA_SOURCE=$( cygpath --mixed "$NEO_SOURCE" )
|
||||
NEO_JAVA_JAR=$( cygpath --mixed "$NEO_JAR" )
|
||||
NEO_JAVA_CLASSES_DIR=$( cygpath --mixed "$NEO_CLASSES_DIR" )
|
||||
fi
|
||||
|
||||
if [ -f "$NEO_SOURCE" ] ; then
|
||||
if [ -n "$NEO_JAR" ] && [ -f "$NEO_JAR" ] ; then
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
"-Dorg.gradle.wrapper.neo.app-home=$NEO_JAVA_APP_HOME" \
|
||||
"-Dorg.gradle.wrapper.neo.source-file=$NEO_JAVA_SOURCE" \
|
||||
"-Dorg.gradle.wrapper.neo.jar-file=$NEO_JAVA_JAR" \
|
||||
-jar "$NEO_JAVA_JAR" \
|
||||
"$@"
|
||||
else
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
JAVACMD_COMPILER=$JAVA_HOME/bin/javac
|
||||
else
|
||||
JAVACMD_COMPILER=javac
|
||||
fi
|
||||
if ! command -v "$JAVACMD_COMPILER" >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: GradleWrapperNeo.java exists, but javac could not be found.
|
||||
|
||||
Please run this wrapper with a JDK so GradleWrapperNeo.java can be compiled."
|
||||
fi
|
||||
|
||||
rm -rf "$NEO_CLASSES_DIR"
|
||||
mkdir -p "$NEO_CLASSES_DIR" || exit
|
||||
JAVAC_VERSION=$("$JAVACMD_COMPILER" -version 2>&1)
|
||||
case "$JAVAC_VERSION" in
|
||||
javac\ 1.*) JAVAC_TARGET_ARGS="-source 8 -target 8" ;;
|
||||
*) JAVAC_TARGET_ARGS="--release 8" ;;
|
||||
esac
|
||||
# shellcheck disable=SC2086
|
||||
if ! "$JAVACMD_COMPILER" $JAVAC_TARGET_ARGS -Xlint:-options -encoding UTF-8 -d "$NEO_CLASSES_DIR" "$NEO_SOURCE"
|
||||
then
|
||||
rm -rf "$NEO_CLASSES_DIR"
|
||||
die "ERROR: Could not compile $NEO_SOURCE"
|
||||
fi
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
"-Dorg.gradle.wrapper.neo.app-home=$NEO_JAVA_APP_HOME" \
|
||||
"-Dorg.gradle.wrapper.neo.source-file=$NEO_JAVA_SOURCE" \
|
||||
"-Dorg.gradle.wrapper.neo.jar-file=$NEO_JAVA_JAR" \
|
||||
"-Dgradle.wrapper.neo.bootstrap=true" \
|
||||
-cp "$NEO_JAVA_CLASSES_DIR" \
|
||||
GradleWrapperNeo \
|
||||
"$@"
|
||||
fi
|
||||
else
|
||||
die "ERROR: $NEO_SOURCE was not found."
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
|
||||
Vendored
+24
-59
@@ -19,75 +19,40 @@
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem Gradle Wrapper Neo forwarding script for Windows
|
||||
@rem
|
||||
@rem This file is maintained by the Gradle Wrapper Neo project. It delegates
|
||||
@rem to gradlew.ps1, which compiles GradleWrapperNeo.java on demand and
|
||||
@rem launches the Wrapper without storing a Wrapper JAR in the project.
|
||||
@rem
|
||||
@rem Documentation and updates: https://github.com/Glavo/gradle-wrapper-neo
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
setlocal EnableExtensions DisableDelayedExpansion
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
set "POWERSHELL_EXE=pwsh.exe"
|
||||
"%POWERSHELL_EXE%" -NoLogo -NoProfile -NonInteractive -Command "exit 0" >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
set "POWERSHELL_EXE=%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe"
|
||||
if exist "%POWERSHELL_EXE%" goto execute
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
set "POWERSHELL_EXE=powershell.exe"
|
||||
"%POWERSHELL_EXE%" -NoLogo -NoProfile -NonInteractive -Command "exit 0" >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo ERROR: PowerShell could not be found. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
echo Please install Windows PowerShell 5.1 or PowerShell 7 and make it available in PATH. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
endlocal
|
||||
exit /b 1
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
set "GRADLE_WRAPPER_NEO_BATCH_LAUNCH=1"
|
||||
set "GRADLE_WRAPPER_NEO_BATCH_ARGUMENTS=%*"
|
||||
"%POWERSHELL_EXE%" -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "%~dp0gradlew.ps1"
|
||||
set "EXIT_CODE=%ERRORLEVEL%"
|
||||
endlocal & exit /b %EXIT_CODE%
|
||||
+382
@@ -0,0 +1,382 @@
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# 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
|
||||
#
|
||||
# https://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.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle Wrapper Neo startup script for Windows.
|
||||
#
|
||||
# This file is maintained by the Gradle Wrapper Neo project. It compiles
|
||||
# gradle/wrapper/GradleWrapperNeo.java on demand and launches the Wrapper
|
||||
# without storing a Wrapper JAR in the project.
|
||||
#
|
||||
# Documentation and updates: https://github.com/Glavo/gradle-wrapper-neo
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
Set-StrictMode -Version 2.0
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$script:WrapperExitCode = 1
|
||||
|
||||
$script:AppBaseName = [IO.Path]::GetFileNameWithoutExtension($MyInvocation.MyCommand.Name)
|
||||
$script:LauncherHome = [IO.Path]::GetFullPath($PSScriptRoot)
|
||||
|
||||
function ConvertFrom-WindowsCommandLine {
|
||||
param([string] $CommandLine)
|
||||
|
||||
if ($null -eq ('GradleWrapperNeo.NativeCommandLine' -as [type])) {
|
||||
Add-Type -TypeDefinition @"
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace GradleWrapperNeo {
|
||||
public static class NativeCommandLine {
|
||||
[DllImport("shell32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
public static extern IntPtr CommandLineToArgvW(string commandLine, out int argumentCount);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern IntPtr LocalFree(IntPtr memory);
|
||||
}
|
||||
}
|
||||
"@
|
||||
}
|
||||
|
||||
$nativeCommandLine = 'gradlew.exe ' + $CommandLine
|
||||
$argumentCount = 0
|
||||
$argumentVector = [GradleWrapperNeo.NativeCommandLine]::CommandLineToArgvW(
|
||||
$nativeCommandLine,
|
||||
[ref] $argumentCount
|
||||
)
|
||||
if ($argumentVector -eq [IntPtr]::Zero) {
|
||||
throw [ComponentModel.Win32Exception]::new([Runtime.InteropServices.Marshal]::GetLastWin32Error())
|
||||
}
|
||||
|
||||
try {
|
||||
$result = New-Object 'System.Collections.Generic.List[string]'
|
||||
for ($index = 1; $index -lt $argumentCount; $index++) {
|
||||
$argumentPointer = [Runtime.InteropServices.Marshal]::ReadIntPtr(
|
||||
$argumentVector,
|
||||
$index * [IntPtr]::Size
|
||||
)
|
||||
[void] $result.Add([Runtime.InteropServices.Marshal]::PtrToStringUni($argumentPointer))
|
||||
}
|
||||
return $result.ToArray()
|
||||
} finally {
|
||||
[void] [GradleWrapperNeo.NativeCommandLine]::LocalFree($argumentVector)
|
||||
}
|
||||
}
|
||||
|
||||
function Split-ArgumentString {
|
||||
param([string] $Value)
|
||||
|
||||
if ($null -eq $Value) {
|
||||
return @()
|
||||
}
|
||||
|
||||
$result = New-Object 'System.Collections.Generic.List[string]'
|
||||
$currentOption = New-Object Text.StringBuilder
|
||||
[char] $currentQuote = [char] 0
|
||||
$insideQuote = $false
|
||||
$hasOption = $false
|
||||
|
||||
for ($index = 0; $index -lt $Value.Length; $index++) {
|
||||
[char] $character = $Value[$index]
|
||||
if ((-not $insideQuote) -and [char]::IsWhiteSpace($character)) {
|
||||
if ($hasOption) {
|
||||
[void] $result.Add($currentOption.ToString())
|
||||
[void] $currentOption.Clear()
|
||||
$hasOption = $false
|
||||
}
|
||||
} elseif ((-not $insideQuote) -and (($character -eq [char] 34) -or ($character -eq [char] 39))) {
|
||||
$currentQuote = $character
|
||||
$insideQuote = $true
|
||||
$hasOption = $true
|
||||
} elseif ($insideQuote -and ($character -eq $currentQuote)) {
|
||||
$insideQuote = $false
|
||||
} else {
|
||||
[void] $currentOption.Append($character)
|
||||
$hasOption = $true
|
||||
}
|
||||
}
|
||||
|
||||
if ($hasOption) {
|
||||
[void] $result.Add($currentOption.ToString())
|
||||
}
|
||||
|
||||
return $result.ToArray()
|
||||
}
|
||||
|
||||
function Resolve-JavaExecutable {
|
||||
if (-not [string]::IsNullOrWhiteSpace($env:JAVA_HOME)) {
|
||||
$javaHome = $env:JAVA_HOME.Trim([char] 34)
|
||||
$javaExecutable = Join-Path $javaHome 'bin\java.exe'
|
||||
if (-not (Test-Path -LiteralPath $javaExecutable -PathType Leaf)) {
|
||||
throw "JAVA_HOME is set to an invalid directory: $javaHome"
|
||||
}
|
||||
return [IO.Path]::GetFullPath($javaExecutable)
|
||||
}
|
||||
|
||||
$javaCommand = Get-Command -Name 'java.exe' -CommandType Application -ErrorAction SilentlyContinue |
|
||||
Select-Object -First 1
|
||||
if ($null -eq $javaCommand) {
|
||||
throw "JAVA_HOME is not set and no 'java' command could be found in PATH."
|
||||
}
|
||||
return $javaCommand.Path
|
||||
}
|
||||
|
||||
function Resolve-JavacExecutable {
|
||||
if (-not [string]::IsNullOrWhiteSpace($env:JAVA_HOME)) {
|
||||
$javaHome = $env:JAVA_HOME.Trim([char] 34)
|
||||
$javacExecutable = Join-Path $javaHome 'bin\javac.exe'
|
||||
if (Test-Path -LiteralPath $javacExecutable -PathType Leaf) {
|
||||
return [IO.Path]::GetFullPath($javacExecutable)
|
||||
}
|
||||
} else {
|
||||
$javacCommand = Get-Command -Name 'javac.exe' -CommandType Application -ErrorAction SilentlyContinue |
|
||||
Select-Object -First 1
|
||||
if ($null -ne $javacCommand) {
|
||||
return $javacCommand.Path
|
||||
}
|
||||
}
|
||||
|
||||
throw 'GradleWrapperNeo.java exists, but javac could not be found. Run this wrapper with a JDK so the source can be compiled.'
|
||||
}
|
||||
|
||||
function Find-AppHome {
|
||||
param(
|
||||
[string] $LauncherHome,
|
||||
[string] $WorkingDirectory
|
||||
)
|
||||
|
||||
$launcherProperties = Join-Path $LauncherHome 'gradle\wrapper\gradle-wrapper.properties'
|
||||
if (Test-Path -LiteralPath $launcherProperties -PathType Leaf) {
|
||||
return $LauncherHome
|
||||
}
|
||||
|
||||
$searchDirectory = Get-Item -LiteralPath $WorkingDirectory
|
||||
$searchStart = $searchDirectory.FullName
|
||||
while ($null -ne $searchDirectory) {
|
||||
$propertiesFile = Join-Path $searchDirectory.FullName 'gradle\wrapper\gradle-wrapper.properties'
|
||||
if (Test-Path -LiteralPath $propertiesFile -PathType Leaf) {
|
||||
return $searchDirectory.FullName
|
||||
}
|
||||
$searchDirectory = $searchDirectory.Parent
|
||||
}
|
||||
|
||||
throw "Could not find gradle/wrapper/gradle-wrapper.properties searching from '$searchStart'."
|
||||
}
|
||||
|
||||
function ConvertTo-JavaPath {
|
||||
param([string] $Path)
|
||||
|
||||
return $Path.Replace('\', '/')
|
||||
}
|
||||
|
||||
function ConvertTo-WindowsCommandLineArgument {
|
||||
param([AllowEmptyString()][string] $Argument)
|
||||
|
||||
if (($Argument.Length -gt 0) -and ($Argument -notmatch '[\s"]')) {
|
||||
return $Argument
|
||||
}
|
||||
|
||||
$result = New-Object Text.StringBuilder
|
||||
[void] $result.Append([char] 34)
|
||||
$backslashCount = 0
|
||||
for ($index = 0; $index -lt $Argument.Length; $index++) {
|
||||
[char] $character = $Argument[$index]
|
||||
if ($character -eq [char] 92) {
|
||||
$backslashCount++
|
||||
continue
|
||||
}
|
||||
|
||||
if ($character -eq [char] 34) {
|
||||
[void] $result.Append(([string] [char] 92) * (($backslashCount * 2) + 1))
|
||||
} elseif ($backslashCount -gt 0) {
|
||||
[void] $result.Append(([string] [char] 92) * $backslashCount)
|
||||
}
|
||||
$backslashCount = 0
|
||||
[void] $result.Append($character)
|
||||
}
|
||||
|
||||
if ($backslashCount -gt 0) {
|
||||
[void] $result.Append(([string] [char] 92) * ($backslashCount * 2))
|
||||
}
|
||||
[void] $result.Append([char] 34)
|
||||
return $result.ToString()
|
||||
}
|
||||
|
||||
function Invoke-NativeApplication {
|
||||
param(
|
||||
[string] $Executable,
|
||||
[string[]] $Arguments,
|
||||
[string] $WorkingDirectory
|
||||
)
|
||||
|
||||
$quotedArguments = New-Object 'System.Collections.Generic.List[string]'
|
||||
foreach ($argument in $Arguments) {
|
||||
[void] $quotedArguments.Add((ConvertTo-WindowsCommandLineArgument -Argument $argument))
|
||||
}
|
||||
|
||||
$startInfo = New-Object Diagnostics.ProcessStartInfo
|
||||
$startInfo.FileName = $Executable
|
||||
$startInfo.Arguments = [string]::Join(' ', $quotedArguments.ToArray())
|
||||
$startInfo.WorkingDirectory = $WorkingDirectory
|
||||
$startInfo.UseShellExecute = $false
|
||||
|
||||
$process = New-Object Diagnostics.Process
|
||||
$process.StartInfo = $startInfo
|
||||
try {
|
||||
if (-not $process.Start()) {
|
||||
throw "Could not start '$Executable'."
|
||||
}
|
||||
$process.WaitForExit()
|
||||
return $process.ExitCode
|
||||
} finally {
|
||||
$process.Dispose()
|
||||
}
|
||||
}
|
||||
|
||||
function Compile-WrapperSource {
|
||||
param(
|
||||
[string] $SourceFile,
|
||||
[string] $ClassesDirectory,
|
||||
[string] $BootstrapDirectory
|
||||
)
|
||||
|
||||
$javacExecutable = Resolve-JavacExecutable
|
||||
[void] [IO.Directory]::CreateDirectory($ClassesDirectory)
|
||||
|
||||
$versionOutput = & $javacExecutable -version 2>&1
|
||||
$versionExitCode = $LASTEXITCODE
|
||||
if ($versionExitCode -ne 0) {
|
||||
Remove-Item -LiteralPath $BootstrapDirectory -Recurse -Force -ErrorAction SilentlyContinue
|
||||
throw "Could not run javac at '$javacExecutable'."
|
||||
}
|
||||
$versionText = ($versionOutput | Out-String).Trim()
|
||||
|
||||
if ($versionText.StartsWith('javac 1.')) {
|
||||
[string[]] $targetArguments = @('-source', '8', '-target', '8')
|
||||
} else {
|
||||
[string[]] $targetArguments = @('--release', '8')
|
||||
}
|
||||
|
||||
[string[]] $compileArguments = @(
|
||||
$targetArguments
|
||||
'-Xlint:-options'
|
||||
'-encoding'
|
||||
'UTF-8'
|
||||
'-d'
|
||||
$ClassesDirectory
|
||||
$SourceFile
|
||||
)
|
||||
|
||||
& $javacExecutable @compileArguments
|
||||
$compileExitCode = $LASTEXITCODE
|
||||
if ($compileExitCode -ne 0) {
|
||||
Remove-Item -LiteralPath $BootstrapDirectory -Recurse -Force -ErrorAction SilentlyContinue
|
||||
throw "Could not compile '$SourceFile'."
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-GradleWrapper {
|
||||
param([string[]] $GradleArguments)
|
||||
|
||||
$currentLocation = Get-Location
|
||||
if ($currentLocation.Provider.Name -ne 'FileSystem') {
|
||||
throw "The current location is not a file system directory: $currentLocation"
|
||||
}
|
||||
$workingDirectory = $currentLocation.ProviderPath
|
||||
|
||||
$appHome = Find-AppHome -LauncherHome $script:LauncherHome -WorkingDirectory $workingDirectory
|
||||
$launcherWrapperDirectory = Join-Path $script:LauncherHome 'gradle\wrapper'
|
||||
$projectSource = Join-Path $appHome 'gradle\wrapper\GradleWrapperNeo.java'
|
||||
if (Test-Path -LiteralPath $projectSource -PathType Leaf) {
|
||||
$sourceFile = $projectSource
|
||||
} else {
|
||||
$sourceFile = Join-Path $launcherWrapperDirectory 'GradleWrapperNeo.java'
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath $sourceFile -PathType Leaf)) {
|
||||
throw "Wrapper source file '$sourceFile' was not found."
|
||||
}
|
||||
|
||||
$workDirectory = Join-Path $appHome '.gradle\wrapper-neo'
|
||||
$jarFile = Join-Path $workDirectory 'gradle-wrapper-neo.jar'
|
||||
$bootstrapDirectory = Join-Path (Join-Path $workDirectory 'bootstrap') ([Guid]::NewGuid().ToString('N'))
|
||||
$classesDirectory = Join-Path $bootstrapDirectory 'classes'
|
||||
|
||||
$javaExecutable = Resolve-JavaExecutable
|
||||
[string[]] $jvmArguments = @('-Xmx64m', '-Xms64m')
|
||||
foreach ($optionSource in @($env:JAVA_OPTS, $env:GRADLE_OPTS)) {
|
||||
foreach ($option in @(Split-ArgumentString -Value $optionSource)) {
|
||||
$jvmArguments += $option
|
||||
}
|
||||
}
|
||||
|
||||
$javaAppHome = ConvertTo-JavaPath -Path $appHome
|
||||
$javaSourceFile = ConvertTo-JavaPath -Path $sourceFile
|
||||
$javaJarFile = ConvertTo-JavaPath -Path $jarFile
|
||||
$javaClassesDirectory = ConvertTo-JavaPath -Path $classesDirectory
|
||||
|
||||
[string[]] $javaArguments = @(
|
||||
$jvmArguments
|
||||
"-Dorg.gradle.appname=$script:AppBaseName"
|
||||
"-Dorg.gradle.wrapper.neo.app-home=$javaAppHome"
|
||||
"-Dorg.gradle.wrapper.neo.source-file=$javaSourceFile"
|
||||
"-Dorg.gradle.wrapper.neo.jar-file=$javaJarFile"
|
||||
)
|
||||
|
||||
$cachedJar = Get-Item -LiteralPath $jarFile -ErrorAction SilentlyContinue
|
||||
if (($null -ne $cachedJar) -and ($cachedJar.Length -eq 0)) {
|
||||
Remove-Item -LiteralPath $jarFile -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
if (Test-Path -LiteralPath $jarFile -PathType Leaf) {
|
||||
$javaArguments += @('-jar', $javaJarFile)
|
||||
} else {
|
||||
Compile-WrapperSource -SourceFile $sourceFile -ClassesDirectory $classesDirectory -BootstrapDirectory $bootstrapDirectory
|
||||
$javaArguments += @(
|
||||
'-Dgradle.wrapper.neo.bootstrap=true'
|
||||
'-cp'
|
||||
$javaClassesDirectory
|
||||
'GradleWrapperNeo'
|
||||
)
|
||||
}
|
||||
|
||||
$javaArguments += $GradleArguments
|
||||
$script:WrapperExitCode = Invoke-NativeApplication `
|
||||
-Executable $javaExecutable `
|
||||
-Arguments $javaArguments `
|
||||
-WorkingDirectory $workingDirectory
|
||||
}
|
||||
|
||||
try {
|
||||
if ($env:GRADLE_WRAPPER_NEO_BATCH_LAUNCH -eq '1') {
|
||||
$rawArguments = $env:GRADLE_WRAPPER_NEO_BATCH_ARGUMENTS
|
||||
Remove-Item Env:GRADLE_WRAPPER_NEO_BATCH_LAUNCH -ErrorAction SilentlyContinue
|
||||
Remove-Item Env:GRADLE_WRAPPER_NEO_BATCH_ARGUMENTS -ErrorAction SilentlyContinue
|
||||
[string[]] $gradleArguments = @(ConvertFrom-WindowsCommandLine -CommandLine $rawArguments)
|
||||
} else {
|
||||
[string[]] $gradleArguments = @($args)
|
||||
}
|
||||
Invoke-GradleWrapper -GradleArguments $gradleArguments
|
||||
exit $script:WrapperExitCode
|
||||
} catch {
|
||||
[Console]::Error.WriteLine()
|
||||
[Console]::Error.WriteLine('ERROR: ' + $_.Exception.Message)
|
||||
[Console]::Error.WriteLine()
|
||||
exit 1
|
||||
}
|
||||
Reference in new issue
Block a user