Compare commits
101
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5803235407 | ||
|
|
afbf567fef | ||
|
|
72b3c1981a | ||
|
|
72ee82543b | ||
|
|
996c9f4c86 | ||
|
|
f8c1e97376 | ||
|
|
f146040ff3 | ||
|
|
0da28173ab | ||
|
|
be822666a7 | ||
|
|
8c99ee3b0d | ||
|
|
0e3b4a7853 | ||
|
|
615b1dbaae | ||
|
|
0fa51f830d | ||
|
|
007bb345d0 | ||
|
|
46536f11d3 | ||
|
|
b1232b5dc8 | ||
|
|
f8f047d67e | ||
|
|
86529c6f15 | ||
|
|
d63829c344 | ||
|
|
34359a7e86 | ||
|
|
aa335d8365 | ||
|
|
a565d3b7ce | ||
|
|
3bff3c7f23 | ||
|
|
4e6e832e9c | ||
|
|
d6648a840e | ||
|
|
f8c2af716f | ||
|
|
bd812ccb67 | ||
|
|
dad190f7e6 | ||
|
|
1b1ba64a38 | ||
|
|
e31268c39d | ||
|
|
cb9d32dc91 | ||
|
|
36568df5b1 | ||
|
|
b4ac42d789 | ||
|
|
32aafb11a3 | ||
|
|
36dab70b7a | ||
|
|
ebf253b1a7 | ||
|
|
539dcca5ca | ||
|
|
659b6c2a70 | ||
|
|
3dcc7ab311 | ||
|
|
e0ccb58272 | ||
|
|
d4ec8ebec8 | ||
|
|
f3b21e5099 | ||
|
|
202fd38180 | ||
|
|
f5768f2f95 | ||
|
|
2d377d06de | ||
|
|
42d1581ba0 | ||
|
|
995a23c2bc | ||
|
|
8969f55a1f | ||
|
|
312a285792 | ||
|
|
d85b052195 | ||
|
|
cd0d3d1b97 | ||
|
|
6ccc7df998 | ||
|
|
73e5909a69 | ||
|
|
ade487277d | ||
|
|
e755d17977 | ||
|
|
292288de9e | ||
|
|
57f6dec462 | ||
|
|
8e1a549c9d | ||
|
|
1d18eae377 | ||
|
|
6ef04d2fa5 | ||
|
|
1778e9d916 | ||
|
|
dbb63e9faa | ||
|
|
6b1c3182eb | ||
|
|
82460ebbcb | ||
|
|
3e8a9bf1c4 | ||
|
|
a2db925e86 | ||
|
|
fe70cc3daf | ||
|
|
5991112cd8 | ||
|
|
084d91c62a | ||
|
|
4963f7a5f0 | ||
|
|
f42d5219c2 | ||
|
|
101a9b5d5e | ||
|
|
7cd79a4e2a | ||
|
|
30c3acfb23 | ||
|
|
31f04db555 | ||
|
|
7d41f7bdd3 | ||
|
|
8e6ac947ed | ||
|
|
20e321f502 | ||
|
|
00b323e507 | ||
|
|
333b593c8a | ||
|
|
665f6df142 | ||
|
|
9e16130537 | ||
|
|
5c98d6734d | ||
|
|
c3801a3445 | ||
|
|
1d137a3a88 | ||
|
|
2e8b9ae3ca | ||
|
|
fb750e8717 | ||
|
|
f9ae0902d8 | ||
|
|
8d69cbc02f | ||
|
|
90c2372c92 | ||
|
|
84c4943de5 | ||
|
|
057943b6c0 | ||
|
|
e943cb0aec | ||
|
|
70290e205b | ||
|
|
a73b8c7172 | ||
|
|
a5942c8e91 | ||
|
|
dc6a97520b | ||
|
|
8be3f04cc8 | ||
|
|
8d85b1537d | ||
|
|
b16f2a6ce6 | ||
|
|
e19f175e29 |
No files matched your search
@@ -0,0 +1,3 @@
|
||||
code_review:
|
||||
pull_request_opened:
|
||||
code_review: false
|
||||
@@ -48,3 +48,9 @@ jobs:
|
||||
name: HMCL-${{ env.SHORT_SHA }}-sh
|
||||
path: HMCL/build/libs/HMCL-*.sh
|
||||
archive: false
|
||||
- name: Upload DEB
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: HMCL-${{ env.SHORT_SHA }}-deb
|
||||
path: HMCL/build/libs/HMCL-*.deb
|
||||
archive: false
|
||||
@@ -46,16 +46,20 @@ jobs:
|
||||
wget "$HMCL_CI_DOWNLOAD_BASE_URI/HMCL-$HMCL_VERSION.jar.sha256"
|
||||
wget "$HMCL_CI_DOWNLOAD_BASE_URI/HMCL-$HMCL_VERSION.sh"
|
||||
wget "$HMCL_CI_DOWNLOAD_BASE_URI/HMCL-$HMCL_VERSION.sh.sha256"
|
||||
wget "$HMCL_CI_DOWNLOAD_BASE_URI/HMCL-$HMCL_VERSION.deb"
|
||||
wget "$HMCL_CI_DOWNLOAD_BASE_URI/HMCL-$HMCL_VERSION.deb.sha256"
|
||||
- name: Verify artifacts
|
||||
if: ${{ env.continue == 'true' }}
|
||||
run: |
|
||||
export JAR_SHA256=$(cat HMCL-$HMCL_VERSION.jar.sha256 | tr -d '\n')
|
||||
export EXE_SHA256=$(cat HMCL-$HMCL_VERSION.exe.sha256 | tr -d '\n')
|
||||
export SH_SHA256=$(cat HMCL-$HMCL_VERSION.sh.sha256 | tr -d '\n')
|
||||
export DEB_SHA256=$(cat HMCL-$HMCL_VERSION.deb.sha256 | tr -d '\n')
|
||||
|
||||
echo "$JAR_SHA256 HMCL-$HMCL_VERSION.jar" | sha256sum -c
|
||||
echo "$EXE_SHA256 HMCL-$HMCL_VERSION.exe" | sha256sum -c
|
||||
echo "$SH_SHA256 HMCL-$HMCL_VERSION.sh" | sha256sum -c
|
||||
echo "$DEB_SHA256 HMCL-$HMCL_VERSION.deb" | sha256sum -c
|
||||
- name: Generate release note
|
||||
if: ${{ env.continue == 'true' }}
|
||||
run: |
|
||||
@@ -67,6 +71,7 @@ jobs:
|
||||
echo "| [HMCL-$HMCL_VERSION.exe]($GH_DOWNLOAD_BASE_URL/v$HMCL_VERSION/HMCL-$HMCL_VERSION.exe) | \`$(cat HMCL-$HMCL_VERSION.exe.sha256)\` |" >> RELEASE_NOTE
|
||||
echo "| [HMCL-$HMCL_VERSION.jar]($GH_DOWNLOAD_BASE_URL/v$HMCL_VERSION/HMCL-$HMCL_VERSION.jar) | \`$(cat HMCL-$HMCL_VERSION.jar.sha256)\` |" >> RELEASE_NOTE
|
||||
echo "| [HMCL-$HMCL_VERSION.sh]($GH_DOWNLOAD_BASE_URL/v$HMCL_VERSION/HMCL-$HMCL_VERSION.sh) | \`$(cat HMCL-$HMCL_VERSION.sh.sha256)\` |" >> RELEASE_NOTE
|
||||
echo "| [HMCL-$HMCL_VERSION.deb]($GH_DOWNLOAD_BASE_URL/v$HMCL_VERSION/HMCL-$HMCL_VERSION.deb) | \`$(cat HMCL-$HMCL_VERSION.deb.sha256)\` |" >> RELEASE_NOTE
|
||||
|
||||
# CNB Release Note
|
||||
echo "[更新日志](https://docs.hmcl.net/changelog/${{ matrix.channel }}.html#HMCL-$HMCL_VERSION)" >> CNB_RELEASE_NOTE
|
||||
@@ -76,6 +81,8 @@ jobs:
|
||||
echo "| [HMCL-$HMCL_VERSION.exe]($CNB_DOWNLOAD_BASE_URL/v$HMCL_VERSION/HMCL-$HMCL_VERSION.exe) | \`$(cat HMCL-$HMCL_VERSION.exe.sha256)\` |" >> CNB_RELEASE_NOTE
|
||||
echo "| [HMCL-$HMCL_VERSION.jar]($CNB_DOWNLOAD_BASE_URL/v$HMCL_VERSION/HMCL-$HMCL_VERSION.jar) | \`$(cat HMCL-$HMCL_VERSION.jar.sha256)\` |" >> CNB_RELEASE_NOTE
|
||||
echo "| [HMCL-$HMCL_VERSION.sh]($CNB_DOWNLOAD_BASE_URL/v$HMCL_VERSION/HMCL-$HMCL_VERSION.sh) | \`$(cat HMCL-$HMCL_VERSION.sh.sha256)\` |" >> CNB_RELEASE_NOTE
|
||||
echo "| [HMCL-$HMCL_VERSION.deb]($CNB_DOWNLOAD_BASE_URL/v$HMCL_VERSION/HMCL-$HMCL_VERSION.deb) | \`$(cat HMCL-$HMCL_VERSION.deb.sha256)\` |" >> CNB_RELEASE_NOTE
|
||||
|
||||
env:
|
||||
GH_DOWNLOAD_BASE_URL: https://github.com/${{ github.repository }}/releases/download
|
||||
CNB_DOWNLOAD_BASE_URL: https://cnb.cool/HMCL-dev/HMCL/-/releases/download
|
||||
@@ -86,6 +93,7 @@ jobs:
|
||||
"HMCL-${{ env.HMCL_VERSION }}.exe" \
|
||||
"HMCL-${{ env.HMCL_VERSION }}.jar" \
|
||||
"HMCL-${{ env.HMCL_VERSION }}.sh" \
|
||||
"HMCL-${{ env.HMCL_VERSION }}.deb" \
|
||||
--target "${{ env.HMCL_COMMIT_SHA }}" \
|
||||
--title "${{ env.HMCL_TAG_NAME }}" \
|
||||
--notes-file RELEASE_NOTE \
|
||||
@@ -120,6 +128,9 @@ jobs:
|
||||
|
||||
echo "Uploading HMCL-$HMCL_VERSION.sh"
|
||||
~/go/bin/git-cnb release asset-upload --repo="$CNB_REPO" --tag-name "$HMCL_TAG_NAME" --file-name "HMCL-$HMCL_VERSION.sh"
|
||||
|
||||
echo "Uploading HMCL-$HMCL_VERSION.deb"
|
||||
~/go/bin/git-cnb release asset-upload --repo="$CNB_REPO" --tag-name "$HMCL_TAG_NAME" --file-name "HMCL-$HMCL_VERSION.deb"
|
||||
env:
|
||||
CNB_TOKEN: ${{ secrets.CNB_SYNC_TOKEN }}
|
||||
CNB_REPO: HMCL-dev/HMCL
|
||||
+7
-1
@@ -64,4 +64,10 @@ language-subtag-registry
|
||||
# test
|
||||
/hmcl.json
|
||||
/.hmcl.json
|
||||
/.hmcl/
|
||||
/.hmcl/
|
||||
|
||||
# AI
|
||||
/.gemini/
|
||||
!/.gemini/config.yaml
|
||||
.codex
|
||||
/.gradle-*/
|
||||
Generated
+6
@@ -0,0 +1,6 @@
|
||||
<component name="CopyrightManager">
|
||||
<copyright>
|
||||
<option name="notice" value="Hello Minecraft! Launcher Copyright (C) &#36;today.year 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/>." />
|
||||
<option name="myName" value="HMCL" />
|
||||
</copyright>
|
||||
</component>
|
||||
Generated
+8
@@ -0,0 +1,8 @@
|
||||
<component name="CopyrightManager">
|
||||
<settings default="HMCL">
|
||||
<LanguageOptions name="JAVA">
|
||||
<option name="fileTypeOverride" value="3" />
|
||||
<option name="addBlankAfter" value="false" />
|
||||
</LanguageOptions>
|
||||
</settings>
|
||||
</component>
|
||||
@@ -0,0 +1,27 @@
|
||||
# Java Code Style Requirements
|
||||
|
||||
These rules apply to all Java code written or modified in this repository.
|
||||
|
||||
## Nullability
|
||||
|
||||
- Annotate every class with JetBrains Annotations `@NotNullByDefault`.
|
||||
- Any type, field, parameter, return value, local variable, or generic type argument that may be `null` must be explicitly annotated with `@Nullable`.
|
||||
- Nullability must never be implicit.
|
||||
|
||||
## Immutability
|
||||
|
||||
- Immutable arrays and collections must be explicitly annotated with JetBrains Annotations `@Unmodifiable` or `@UnmodifiableView` as appropriate.
|
||||
- For arrays, use type-use syntax such as `String @Unmodifiable []`.
|
||||
|
||||
## Documentation
|
||||
|
||||
- Every class, field, and method must have documentation.
|
||||
- Documentation must use `///` Markdown-style Javadoc comments.
|
||||
- Keep documentation accurate and specific to the actual behavior, constraints, and side effects.
|
||||
- Add concise implementation comments inside complex logic whenever they materially improve readability or explain non-obvious behavior.
|
||||
|
||||
## Gradle
|
||||
|
||||
- When invoking Gradle in this repository, always set `GRADLE_USER_HOME` to the workspace-local `.gradle-user-home` directory.
|
||||
- Prefer commands such as `./gradlew -g .gradle-user-home ...` or the equivalent environment-variable-based configuration.
|
||||
- When running Gradle `test` tasks, use a higher timeout of ten minutes.
|
||||
+39
-10
@@ -6,6 +6,8 @@ import org.jackhuang.hmcl.gradle.l10n.CreateLanguageList
|
||||
import org.jackhuang.hmcl.gradle.l10n.CreateLocaleNamesResourceBundle
|
||||
import org.jackhuang.hmcl.gradle.l10n.UpsideDownTranslate
|
||||
import org.jackhuang.hmcl.gradle.mod.ParseModDataTask
|
||||
import org.jackhuang.hmcl.gradle.pack.CreateDeb
|
||||
import org.jackhuang.hmcl.gradle.pack.ReleaseType
|
||||
import org.jackhuang.hmcl.gradle.utils.PropertiesUtils
|
||||
import java.net.URI
|
||||
import java.nio.file.FileSystems
|
||||
@@ -56,7 +58,7 @@ dependencies {
|
||||
implementation(project(":HMCLCore"))
|
||||
implementation(project(":HMCLBoot"))
|
||||
implementation("libs:JFoenix")
|
||||
implementation(libs.twelvemonkeys.imageio.webp)
|
||||
implementation(libs.jwebp)
|
||||
implementation(libs.fxsvgimage)
|
||||
implementation(libs.java.info)
|
||||
implementation(libs.monet.fx)
|
||||
@@ -195,7 +197,7 @@ tasks.shadowJar {
|
||||
exclude("META-INF/services/javax.imageio.spi.ImageInputStreamSpi")
|
||||
|
||||
listOf(
|
||||
"aix-*", "sunos-*", "openbsd-*", "dragonflybsd-*", "freebsd-*", "linux-*", "darwin-*",
|
||||
"aix-*", "sunos-*", "openbsd-*", "dragonflybsd-*", "freebsd-*", "linux-*",
|
||||
"*-ppc", "*-ppc64le", "*-s390x", "*-armel",
|
||||
).forEach { exclude("com/sun/jna/$it/**") }
|
||||
|
||||
@@ -251,20 +253,22 @@ tasks.processResources {
|
||||
}
|
||||
}
|
||||
|
||||
fun artifactFile(ext: String) = jarPath.resolveSibling(jarPath.nameWithoutExtension + '.' + ext)
|
||||
|
||||
val makeExecutables by tasks.registering {
|
||||
val extensions = listOf("exe", "sh")
|
||||
|
||||
dependsOn(tasks.jar)
|
||||
|
||||
inputs.file(jarPath)
|
||||
outputs.files(extensions.map { File(jarPath.parentFile, jarPath.nameWithoutExtension + '.' + it) })
|
||||
outputs.files(extensions.map { artifactFile(it) })
|
||||
|
||||
doLast {
|
||||
val jarContent = jarPath.readBytes()
|
||||
|
||||
ZipFile(jarPath).use { zipFile ->
|
||||
for (extension in extensions) {
|
||||
val output = File(jarPath.parentFile, jarPath.nameWithoutExtension + '.' + extension)
|
||||
val output = artifactFile(extension)
|
||||
val entry = zipFile.getEntry("assets/HMCLauncher.$extension")
|
||||
?: throw GradleException("HMCLauncher.$extension not found")
|
||||
|
||||
@@ -279,8 +283,31 @@ val makeExecutables by tasks.registering {
|
||||
}
|
||||
}
|
||||
|
||||
val makeDeb by tasks.registering(CreateDeb::class) {
|
||||
dependsOn(makeExecutables)
|
||||
|
||||
val debFile = layout.file(provider { artifactFile("deb") })
|
||||
|
||||
val debChannel = when (versionType) {
|
||||
"stable" -> ReleaseType.STABLE
|
||||
"dev" -> ReleaseType.DEVELOPMENT
|
||||
else -> ReleaseType.NIGHTLY
|
||||
}
|
||||
|
||||
version.set(project.version.toString())
|
||||
releaseType.set(debChannel)
|
||||
appShFile.set(layout.file(provider { artifactFile("sh") }))
|
||||
iconFile.set(layout.projectDirectory.file("image/hmcl.png"))
|
||||
outputFile.set(debFile)
|
||||
|
||||
doLast {
|
||||
createChecksum(debFile.get().asFile)
|
||||
}
|
||||
}
|
||||
|
||||
tasks.build {
|
||||
dependsOn(makeExecutables)
|
||||
dependsOn(makeDeb)
|
||||
}
|
||||
|
||||
fun parseToolOptions(options: String?): MutableList<String> {
|
||||
@@ -377,12 +404,14 @@ val upgradeTerracottaConfig = tasks.register<TerracottaConfigUpgradeTask>("upgra
|
||||
val destination = layout.projectDirectory.file("src/main/resources/assets/terracotta.json")
|
||||
val source = layout.projectDirectory.file("terracotta-template.json");
|
||||
|
||||
classifiers.set(listOf(
|
||||
"windows-x86_64", "windows-arm64",
|
||||
"macos-x86_64", "macos-arm64",
|
||||
"linux-x86_64", "linux-arm64", "linux-loongarch64", "linux-riscv64",
|
||||
"freebsd-x86_64"
|
||||
))
|
||||
classifiers.set(
|
||||
listOf(
|
||||
"windows-x86_64", "windows-arm64",
|
||||
"macos-x86_64", "macos-arm64",
|
||||
"linux-x86_64", "linux-arm64", "linux-loongarch64", "linux-riscv64",
|
||||
"freebsd-x86_64"
|
||||
)
|
||||
)
|
||||
|
||||
version.set(libs.versions.terracotta)
|
||||
downloadURL.set($$"https://github.com/burningtnt/Terracotta/releases/download/v${version}/terracotta-${version}-${classifier}-pkg.tar.gz")
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
@@ -231,10 +231,11 @@ public class JFXRippler extends StackPane {
|
||||
*
|
||||
* @return the ripple radius size
|
||||
*/
|
||||
protected double computeRippleRadius() {
|
||||
double width2 = control.getLayoutBounds().getWidth() * control.getLayoutBounds().getWidth();
|
||||
double height2 = control.getLayoutBounds().getHeight() * control.getLayoutBounds().getHeight();
|
||||
return Math.min(Math.sqrt(width2 + height2), RIPPLE_MAX_RADIUS) * 1.1 + 5;
|
||||
protected double computeRippleRadius(double centerX, double centerY) {
|
||||
double dx = Math.max(centerX, control.getLayoutBounds().getWidth() - centerX);
|
||||
double dy = Math.max(centerY, control.getLayoutBounds().getHeight() - centerY);
|
||||
|
||||
return (Math.sqrt(dx * dx + dy * dy) / 0.9) + 2;
|
||||
}
|
||||
|
||||
protected void setOverLayBounds(Rectangle overlay) {
|
||||
@@ -473,7 +474,7 @@ public class JFXRippler extends StackPane {
|
||||
super(centerX,
|
||||
centerY,
|
||||
getRipplerRadius() == Region.USE_COMPUTED_SIZE ?
|
||||
computeRippleRadius() : getRipplerRadius(), null);
|
||||
computeRippleRadius(centerX, centerY) : getRipplerRadius(), null);
|
||||
setCache(true);
|
||||
setCacheHint(CacheHint.SPEED);
|
||||
setCacheShape(true);
|
||||
@@ -518,7 +519,7 @@ public class JFXRippler extends StackPane {
|
||||
new KeyValue(opacityProperty(),
|
||||
1,
|
||||
RIPPLE_INTERPOLATOR)
|
||||
), new KeyFrame(Duration.millis(900), inKeyValues));
|
||||
), new KeyFrame(Duration.millis(600), inKeyValues));
|
||||
|
||||
setScaleX(0);
|
||||
setScaleY(0);
|
||||
|
||||
@@ -0,0 +1,277 @@
|
||||
//
|
||||
// Source code recreated from a .class file by IntelliJ IDEA
|
||||
// (powered by Fernflower decompiler)
|
||||
//
|
||||
|
||||
package com.jfoenix.skins;
|
||||
|
||||
import com.jfoenix.adapters.skins.ComboBoxListViewSkin;
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import com.jfoenix.controls.JFXComboBox;
|
||||
import com.jfoenix.transitions.CachedTransition;
|
||||
import javafx.animation.*;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.ComboBoxBase;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.Paint;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.transform.Scale;
|
||||
import javafx.util.Duration;
|
||||
|
||||
public class JFXComboBoxListViewSkin<T> extends ComboBoxListViewSkin<T> {
|
||||
protected final ObjectProperty<Paint> promptTextFill;
|
||||
private boolean invalid = true;
|
||||
private final StackPane customPane;
|
||||
private final StackPane line = new StackPane();
|
||||
private final StackPane focusedLine = new StackPane();
|
||||
private final Text promptText = new Text();
|
||||
private final double initScale = 0.05;
|
||||
private final Scale scale;
|
||||
private final Timeline linesAnimation;
|
||||
private ParallelTransition transition;
|
||||
private CachedTransition promptTextUpTransition;
|
||||
private CachedTransition promptTextDownTransition;
|
||||
private CachedTransition promptTextColorTransition;
|
||||
private final Scale promptTextScale;
|
||||
private Paint oldPromptTextFill;
|
||||
private final BooleanBinding usePromptText;
|
||||
|
||||
public JFXComboBoxListViewSkin(JFXComboBox<T> comboBox) {
|
||||
super(comboBox);
|
||||
this.scale = new Scale(this.initScale, 1.0F);
|
||||
this.linesAnimation = new Timeline(new KeyFrame(Duration.ZERO, new KeyValue(this.scale.xProperty(), this.initScale, Interpolator.EASE_BOTH), new KeyValue(this.focusedLine.opacityProperty(), 0, Interpolator.EASE_BOTH)), new KeyFrame(Duration.millis(1.0F), new KeyValue(this.focusedLine.opacityProperty(), 1, Interpolator.EASE_BOTH)), new KeyFrame(Duration.millis(160.0F), new KeyValue(this.scale.xProperty(), 1, Interpolator.EASE_BOTH)));
|
||||
this.promptTextScale = new Scale(1.0F, 1.0F, 0.0F, 0.0F);
|
||||
this.promptTextFill = new SimpleObjectProperty(Color.valueOf("#B2B2B2"));
|
||||
this.usePromptText = Bindings.createBooleanBinding(this::usePromptText, ((JFXComboBox) this.getSkinnable()).valueProperty(), this.getSkinnable().promptTextProperty());
|
||||
this.getArrowButton().setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, null, null)));
|
||||
this.promptText.textProperty().bind(comboBox.promptTextProperty());
|
||||
this.promptText.fillProperty().bind(this.promptTextFill);
|
||||
this.promptText.getStyleClass().addAll("text", "prompt-text");
|
||||
this.promptText.getTransforms().add(this.promptTextScale);
|
||||
if (!comboBox.isLabelFloat()) {
|
||||
this.promptText.visibleProperty().bind(this.usePromptText);
|
||||
}
|
||||
|
||||
this.customPane = new StackPane();
|
||||
this.customPane.setMouseTransparent(true);
|
||||
this.customPane.getStyleClass().add("combo-box-button-container");
|
||||
this.customPane.backgroundProperty().bindBidirectional(this.getSkinnable().backgroundProperty());
|
||||
this.customPane.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, null, null)));
|
||||
this.customPane.getChildren().add(this.promptText);
|
||||
this.getChildren().add(0, this.customPane);
|
||||
StackPane.setAlignment(this.promptText, Pos.CENTER_LEFT);
|
||||
this.line.getStyleClass().add("input-line");
|
||||
this.focusedLine.getStyleClass().add("input-focused-line");
|
||||
this.getChildren().add(this.line);
|
||||
this.getChildren().add(this.focusedLine);
|
||||
this.line.setPrefHeight(1.0F);
|
||||
this.line.setTranslateY(1.0F);
|
||||
this.line.setManaged(false);
|
||||
this.line.setBackground(new Background(new BackgroundFill(((JFXComboBox) this.getSkinnable()).getUnFocusColor(), CornerRadii.EMPTY, Insets.EMPTY)));
|
||||
if (this.getSkinnable().isDisabled()) {
|
||||
this.line.setBorder(new Border(new BorderStroke(((JFXComboBox) this.getSkinnable()).getUnFocusColor(), BorderStrokeStyle.DASHED, CornerRadii.EMPTY, new BorderWidths(1.0F))));
|
||||
this.line.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY)));
|
||||
}
|
||||
|
||||
this.focusedLine.setPrefHeight(2.0F);
|
||||
this.focusedLine.setTranslateY(0.0F);
|
||||
this.focusedLine.setBackground(new Background(new BackgroundFill(((JFXComboBox) this.getSkinnable()).getFocusColor(), CornerRadii.EMPTY, Insets.EMPTY)));
|
||||
this.focusedLine.setOpacity(0.0F);
|
||||
this.focusedLine.getTransforms().add(this.scale);
|
||||
this.focusedLine.setManaged(false);
|
||||
if (comboBox.isEditable()) {
|
||||
comboBox.getEditor().setStyle("-fx-background-color:TRANSPARENT;-fx-padding: 4 8 4 8");
|
||||
comboBox.getEditor().promptTextProperty().unbind();
|
||||
comboBox.getEditor().setPromptText(null);
|
||||
comboBox.getEditor().textProperty().addListener((o, oldVal, newVal) -> {
|
||||
this.usePromptText.invalidate();
|
||||
comboBox.setValue(this.getConverter().fromString(newVal));
|
||||
});
|
||||
}
|
||||
|
||||
comboBox.labelFloatProperty().addListener((o, oldVal, newVal) -> {
|
||||
if (newVal) {
|
||||
this.promptText.visibleProperty().unbind();
|
||||
JFXUtilities.runInFX(() -> this.createFloatingAnimation());
|
||||
} else {
|
||||
this.promptText.visibleProperty().bind(this.usePromptText);
|
||||
}
|
||||
|
||||
this.createFocusTransition();
|
||||
});
|
||||
comboBox.focusColorProperty().addListener((o, oldVal, newVal) -> {
|
||||
if (newVal != null) {
|
||||
this.focusedLine.setBackground(new Background(new BackgroundFill(newVal, CornerRadii.EMPTY, Insets.EMPTY)));
|
||||
if (((JFXComboBox) this.getSkinnable()).isLabelFloat()) {
|
||||
this.promptTextColorTransition = new CachedTransition(this.customPane, new Timeline(new KeyFrame(Duration.millis(1300.0F), new KeyValue(this.promptTextFill, newVal, Interpolator.EASE_BOTH)))) {
|
||||
{
|
||||
this.setDelay(Duration.millis(0.0F));
|
||||
this.setCycleDuration(Duration.millis(160.0F));
|
||||
}
|
||||
|
||||
protected void starting() {
|
||||
super.starting();
|
||||
JFXComboBoxListViewSkin.this.oldPromptTextFill = JFXComboBoxListViewSkin.this.promptTextFill.get();
|
||||
}
|
||||
};
|
||||
this.transition = null;
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
comboBox.unFocusColorProperty().addListener((o, oldVal, newVal) -> {
|
||||
if (newVal != null) {
|
||||
this.line.setBackground(new Background(new BackgroundFill(newVal, CornerRadii.EMPTY, Insets.EMPTY)));
|
||||
}
|
||||
|
||||
});
|
||||
comboBox.disabledProperty().addListener((o, oldVal, newVal) -> {
|
||||
this.line.setBorder(newVal ? new Border(new BorderStroke(((JFXComboBox) this.getSkinnable()).getUnFocusColor(), BorderStrokeStyle.DASHED, CornerRadii.EMPTY, new BorderWidths(this.line.getHeight()))) : Border.EMPTY);
|
||||
this.line.setBackground(new Background(new BackgroundFill(newVal ? Color.TRANSPARENT : ((JFXComboBox) this.getSkinnable()).getUnFocusColor(), CornerRadii.EMPTY, Insets.EMPTY)));
|
||||
});
|
||||
comboBox.focusedProperty().addListener((o, oldVal, newVal) -> {
|
||||
if (newVal) {
|
||||
this.focus();
|
||||
} else {
|
||||
this.unFocus();
|
||||
}
|
||||
|
||||
});
|
||||
comboBox.valueProperty().addListener((o, oldVal, newVal) -> {
|
||||
if (((JFXComboBox) this.getSkinnable()).isLabelFloat()) {
|
||||
this.animateFloatingLabel(newVal != null && !newVal.toString().isEmpty());
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
protected void layoutChildren(double x, double y, double w, double h) {
|
||||
super.layoutChildren(x, y, w, h);
|
||||
this.customPane.resizeRelocate(x, y, w, h);
|
||||
if (this.invalid) {
|
||||
this.invalid = false;
|
||||
if (!this.getSkinnable().isEditable()) {
|
||||
Text javaPromptText = (Text) super.getDisplayNode().lookup(".text");
|
||||
if (javaPromptText != null) {
|
||||
this.promptTextFill.set(javaPromptText.getFill());
|
||||
}
|
||||
}
|
||||
|
||||
this.createFloatingAnimation();
|
||||
if (((ComboBoxBase) this.getSkinnable()).getValue() != null) {
|
||||
this.animateFloatingLabel(true);
|
||||
}
|
||||
}
|
||||
|
||||
this.focusedLine.resizeRelocate(x, this.getSkinnable().getHeight(), w, this.focusedLine.prefHeight(-1.0F));
|
||||
this.line.resizeRelocate(x, this.getSkinnable().getHeight(), w, this.line.prefHeight(-1.0F));
|
||||
this.scale.setPivotX(w / (double) 2.0F);
|
||||
}
|
||||
|
||||
private void createFloatingAnimation() {
|
||||
this.promptTextUpTransition = new CachedTransition(this.customPane, new Timeline(new KeyFrame(Duration.millis(1300.0F), new KeyValue(this.promptText.translateYProperty(), -this.customPane.getHeight() + 6.05, Interpolator.EASE_BOTH), new KeyValue(this.promptTextScale.xProperty(), 0.85, Interpolator.EASE_BOTH), new KeyValue(this.promptTextScale.yProperty(), 0.85, Interpolator.EASE_BOTH)))) {
|
||||
{
|
||||
this.setDelay(Duration.millis(0.0F));
|
||||
this.setCycleDuration(Duration.millis(240.0F));
|
||||
}
|
||||
};
|
||||
this.promptTextColorTransition = new CachedTransition(this.customPane, new Timeline(new KeyFrame(Duration.millis(1300.0F), new KeyValue(this.promptTextFill, ((JFXComboBox) this.getSkinnable()).getFocusColor(), Interpolator.EASE_BOTH)))) {
|
||||
{
|
||||
this.setDelay(Duration.millis(0.0F));
|
||||
this.setCycleDuration(Duration.millis(160.0F));
|
||||
}
|
||||
|
||||
protected void starting() {
|
||||
super.starting();
|
||||
JFXComboBoxListViewSkin.this.oldPromptTextFill = JFXComboBoxListViewSkin.this.promptTextFill.get();
|
||||
}
|
||||
};
|
||||
this.promptTextDownTransition = new CachedTransition(this.customPane, new Timeline(new KeyFrame(Duration.millis(1300.0F), new KeyValue(this.promptText.translateYProperty(), 0, Interpolator.EASE_BOTH), new KeyValue(this.promptTextScale.xProperty(), 1, Interpolator.EASE_BOTH), new KeyValue(this.promptTextScale.yProperty(), 1, Interpolator.EASE_BOTH)))) {
|
||||
{
|
||||
this.setDelay(Duration.millis(0.0F));
|
||||
this.setCycleDuration(Duration.millis(240.0F));
|
||||
}
|
||||
};
|
||||
this.promptTextDownTransition.setOnFinished((finish) -> {
|
||||
this.promptText.setTranslateY(0.0F);
|
||||
this.promptTextScale.setX(1.0F);
|
||||
this.promptTextScale.setY(1.0F);
|
||||
});
|
||||
}
|
||||
|
||||
private void focus() {
|
||||
if (this.transition == null) {
|
||||
this.createFocusTransition();
|
||||
}
|
||||
|
||||
this.transition.play();
|
||||
}
|
||||
|
||||
private void animateFloatingLabel(boolean up) {
|
||||
if (this.promptText == null) {
|
||||
Platform.runLater(() -> this.animateFloatingLabel(up));
|
||||
} else {
|
||||
if (this.transition != null) {
|
||||
this.transition.stop();
|
||||
this.transition.getChildren().remove(this.promptTextUpTransition);
|
||||
this.transition.getChildren().remove(this.promptTextColorTransition);
|
||||
this.transition = null;
|
||||
}
|
||||
|
||||
if (up && this.promptText.getTranslateY() == (double) 0.0F) {
|
||||
this.promptTextDownTransition.stop();
|
||||
this.promptTextUpTransition.play();
|
||||
if (this.getSkinnable().isFocused()) {
|
||||
this.promptTextColorTransition.play();
|
||||
}
|
||||
} else if (!up) {
|
||||
this.promptTextUpTransition.stop();
|
||||
if (this.getSkinnable().isFocused()) {
|
||||
this.promptTextFill.set(this.oldPromptTextFill);
|
||||
}
|
||||
|
||||
this.promptTextDownTransition.play();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void createFocusTransition() {
|
||||
this.transition = new ParallelTransition();
|
||||
if (((JFXComboBox) this.getSkinnable()).isLabelFloat()) {
|
||||
this.transition.getChildren().add(this.promptTextUpTransition);
|
||||
this.transition.getChildren().add(this.promptTextColorTransition);
|
||||
}
|
||||
|
||||
this.transition.getChildren().add(this.linesAnimation);
|
||||
}
|
||||
|
||||
private void unFocus() {
|
||||
if (this.transition != null) {
|
||||
this.transition.stop();
|
||||
}
|
||||
|
||||
this.scale.setX(this.initScale);
|
||||
this.focusedLine.setOpacity(0.0F);
|
||||
if (((JFXComboBox) this.getSkinnable()).isLabelFloat() && this.oldPromptTextFill != null) {
|
||||
this.promptTextFill.set(this.oldPromptTextFill);
|
||||
if (this.usePromptText()) {
|
||||
this.promptTextDownTransition.play();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private boolean usePromptText() {
|
||||
Object txt = ((JFXComboBox) this.getSkinnable()).getValue();
|
||||
String promptTxt = this.getSkinnable().getPromptText();
|
||||
return (txt == null || txt.toString().isEmpty()) && promptTxt != null && !promptTxt.isEmpty() && !this.promptTextFill.get().equals(Color.TRANSPARENT);
|
||||
}
|
||||
}
|
||||
@@ -39,12 +39,10 @@ import org.jackhuang.hmcl.task.AsyncTaskExecutor;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.theme.Themes;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateHandler;
|
||||
import org.jackhuang.hmcl.util.CrashReporter;
|
||||
import org.jackhuang.hmcl.util.FileSaver;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.io.JarUtils;
|
||||
import org.jackhuang.hmcl.util.platform.*;
|
||||
|
||||
@@ -138,6 +136,9 @@ public final class Launcher extends Application {
|
||||
Platform.setImplicitExit(false);
|
||||
Controllers.initialize(primaryStage);
|
||||
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS)
|
||||
Themes.applyNativeDarkMode(primaryStage);
|
||||
|
||||
UpdateChecker.init();
|
||||
|
||||
primaryStage.show();
|
||||
@@ -343,6 +344,8 @@ public final class Launcher extends Application {
|
||||
LOG.info("XDG Current Desktop: " + System.getenv("XDG_CURRENT_DESKTOP"));
|
||||
}
|
||||
|
||||
LOG.info("Zlib Compatible: " + ZlibUtils.IS_ZLIB_COMPATIBLE);
|
||||
|
||||
Lang.thread(SystemInfo::initialize, "Detection System Information", true);
|
||||
|
||||
launch(Launcher.class, args);
|
||||
|
||||
@@ -24,7 +24,6 @@ import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.EnumSet;
|
||||
|
||||
/**
|
||||
@@ -46,7 +45,6 @@ public final class Metadata {
|
||||
public static final int RECOMMENDED_JAVA_VERSION = 21;
|
||||
|
||||
public static final String PUBLISH_URL = "https://hmcl.huangyuhui.net";
|
||||
public static final String ABOUT_URL = PUBLISH_URL + "/about";
|
||||
public static final String DOWNLOAD_URL = PUBLISH_URL + "/download";
|
||||
public static final String HMCL_UPDATE_URL = System.getProperty("hmcl.update_source.override", PUBLISH_URL + "/api/update_link");
|
||||
public static final String MANUAL_UPDATE_URL = "https://github.com/HMCL-dev/HMCL/releases";
|
||||
@@ -60,34 +58,38 @@ public final class Metadata {
|
||||
public static final String BUILD_CHANNEL = JarUtils.getAttribute("hmcl.version.type", "nightly");
|
||||
public static final String GITHUB_SHA = JarUtils.getAttribute("hmcl.version.hash", null);
|
||||
|
||||
public static final Path CURRENT_DIRECTORY = Paths.get(System.getProperty("user.dir")).toAbsolutePath().normalize();
|
||||
public static final Path CURRENT_DIRECTORY = Path.of(System.getProperty("user.dir")).toAbsolutePath().normalize();
|
||||
public static final Path MINECRAFT_DIRECTORY = OperatingSystem.getWorkingDirectory("minecraft");
|
||||
public static final Path HMCL_GLOBAL_DIRECTORY;
|
||||
public static final Path HMCL_CURRENT_DIRECTORY;
|
||||
public static final Path DEPENDENCIES_DIRECTORY;
|
||||
|
||||
static {
|
||||
String hmclHome = System.getProperty("hmcl.home");
|
||||
if (hmclHome == null) {
|
||||
String hmclHome = System.getProperty("hmcl.home", System.getenv("HMCL_USER_HOME"));
|
||||
if (StringUtils.isBlank(hmclHome)) {
|
||||
if (OperatingSystem.CURRENT_OS.isLinuxOrBSD()) {
|
||||
String xdgData = System.getenv("XDG_DATA_HOME");
|
||||
if (StringUtils.isNotBlank(xdgData)) {
|
||||
HMCL_GLOBAL_DIRECTORY = Paths.get(xdgData, "hmcl").toAbsolutePath().normalize();
|
||||
HMCL_GLOBAL_DIRECTORY = Path.of(xdgData, "hmcl").toAbsolutePath().normalize();
|
||||
} else {
|
||||
HMCL_GLOBAL_DIRECTORY = Paths.get(System.getProperty("user.home"), ".local", "share", "hmcl").toAbsolutePath().normalize();
|
||||
HMCL_GLOBAL_DIRECTORY = Path.of(System.getProperty("user.home"), ".local", "share", "hmcl").toAbsolutePath().normalize();
|
||||
}
|
||||
} else {
|
||||
HMCL_GLOBAL_DIRECTORY = OperatingSystem.getWorkingDirectory("hmcl");
|
||||
}
|
||||
} else {
|
||||
HMCL_GLOBAL_DIRECTORY = Paths.get(hmclHome).toAbsolutePath().normalize();
|
||||
HMCL_GLOBAL_DIRECTORY = Path.of(hmclHome).toAbsolutePath().normalize();
|
||||
}
|
||||
|
||||
String hmclCurrentDir = System.getProperty("hmcl.dir");
|
||||
HMCL_CURRENT_DIRECTORY = hmclCurrentDir != null
|
||||
? Paths.get(hmclCurrentDir).toAbsolutePath().normalize()
|
||||
String hmclCurrentDir = System.getProperty("hmcl.dir", System.getenv("HMCL_LOCAL_HOME"));
|
||||
HMCL_CURRENT_DIRECTORY = StringUtils.isNotBlank(hmclCurrentDir)
|
||||
? Path.of(hmclCurrentDir).toAbsolutePath().normalize()
|
||||
: CURRENT_DIRECTORY.resolve(".hmcl");
|
||||
DEPENDENCIES_DIRECTORY = HMCL_CURRENT_DIRECTORY.resolve("dependencies");
|
||||
|
||||
String hmclDependencies = System.getProperty("hmcl.dependencies.dir", System.getenv("HMCL_DEPENDENCIES_DIR"));
|
||||
DEPENDENCIES_DIRECTORY = StringUtils.isNotBlank(hmclDependencies)
|
||||
? Path.of(hmclDependencies).toAbsolutePath().normalize()
|
||||
: HMCL_CURRENT_DIRECTORY.resolve("dependencies");
|
||||
}
|
||||
|
||||
public static boolean isStable() {
|
||||
|
||||
@@ -430,6 +430,7 @@ public final class HMCLGameRepository extends DefaultGameRepository {
|
||||
.setNativesDirType(vs.getNativesDirType())
|
||||
.setNativesDir(vs.getNativesDir())
|
||||
.setProcessPriority(vs.getProcessPriority())
|
||||
.setGraphicsBackend(vs.getGraphicsBackend())
|
||||
.setRenderer(vs.getRenderer())
|
||||
.setEnableDebugLogOutput(vs.isEnableDebugLogOutput())
|
||||
.setUseNativeGLFW(vs.isUseNativeGLFW())
|
||||
@@ -514,7 +515,7 @@ public final class HMCLGameRepository extends DefaultGameRepository {
|
||||
FORBIDDEN_VERSION_IDS.contains(id.toLowerCase(Locale.ROOT)))
|
||||
return false;
|
||||
|
||||
return FileUtils.isNameValid(id);
|
||||
return FileUtils.isNameValidForJar(id);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -72,7 +72,6 @@ import static org.jackhuang.hmcl.util.Lang.resolveException;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
import static org.jackhuang.hmcl.util.platform.Platform.SYSTEM_PLATFORM;
|
||||
import static org.jackhuang.hmcl.util.platform.Platform.isCompatibleWithX86Java;
|
||||
|
||||
public final class LauncherHelper {
|
||||
|
||||
@@ -173,27 +172,27 @@ public final class LauncherHelper {
|
||||
}
|
||||
}),
|
||||
Task.composeAsync(() -> {
|
||||
Renderer renderer = setting.getRenderer();
|
||||
if (renderer != Renderer.DEFAULT && OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
Library lib = NativePatcher.getWindowsMesaLoader(java, renderer, OperatingSystem.SYSTEM_VERSION);
|
||||
if (lib == null)
|
||||
return null;
|
||||
Path file = dependencyManager.getGameRepository().getLibraryFile(version.get(), lib);
|
||||
if (file.toAbsolutePath().toString().indexOf('=') >= 0) {
|
||||
LOG.warning("Invalid character '=' in the libraries directory path, unable to attach software renderer loader");
|
||||
return null;
|
||||
}
|
||||
if (OperatingSystem.CURRENT_OS != OperatingSystem.WINDOWS
|
||||
|| !(setting.getRenderer() instanceof Renderer.Driver renderer)
|
||||
|| renderer.mesaDriverName() == null)
|
||||
return null;
|
||||
|
||||
String agent = FileUtils.getAbsolutePath(file) + "=" + renderer.name().toLowerCase(Locale.ROOT);
|
||||
Library lib = NativePatcher.getWindowsMesaLoader(java, renderer, OperatingSystem.SYSTEM_VERSION);
|
||||
if (lib == null)
|
||||
return null;
|
||||
Path file = dependencyManager.getGameRepository().getLibraryFile(version.get(), lib);
|
||||
if (file.toAbsolutePath().toString().indexOf('=') >= 0) {
|
||||
LOG.warning("Invalid character '=' in the libraries directory path, unable to attach software renderer loader");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (GameLibrariesTask.shouldDownloadLibrary(repository, version.get(), lib, integrityCheck)) {
|
||||
return new LibraryDownloadTask(dependencyManager, file, lib)
|
||||
.thenRunAsync(() -> javaAgents.add(agent));
|
||||
} else {
|
||||
javaAgents.add(agent);
|
||||
return null;
|
||||
}
|
||||
String agent = FileUtils.getAbsolutePath(file) + "=" + renderer.mesaDriverName();
|
||||
|
||||
if (GameLibrariesTask.shouldDownloadLibrary(repository, version.get(), lib, integrityCheck)) {
|
||||
return new LibraryDownloadTask(dependencyManager, file, lib)
|
||||
.thenRunAsync(() -> javaAgents.add(agent));
|
||||
} else {
|
||||
javaAgents.add(agent);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
@@ -232,6 +231,7 @@ public final class LauncherHelper {
|
||||
if (quickPlayOption != null) {
|
||||
launchOptionsBuilder.setQuickPlayOption(quickPlayOption);
|
||||
}
|
||||
|
||||
LaunchOptions launchOptions = launchOptionsBuilder.create();
|
||||
|
||||
LOG.info("Here's the structure of game mod directory:\n" + FileUtils.printFileStructure(repository.getModsDirectory(selectedVersion), 10));
|
||||
@@ -624,7 +624,7 @@ public final class LauncherHelper {
|
||||
break;
|
||||
case VANILLA_X86:
|
||||
if (setting.getNativesDirType() == NativesDirectoryType.VERSION_FOLDER
|
||||
&& isCompatibleWithX86Java()) {
|
||||
&& Platform.isSupportedTranslationX86_64()) {
|
||||
suggestions.add(i18n("launch.advice.vanilla_x86.translation"));
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -31,7 +31,6 @@ public final class Log {
|
||||
|
||||
private final String log;
|
||||
private Log4jLevel level;
|
||||
private boolean selected = false;
|
||||
|
||||
public Log(String log) {
|
||||
this.log = log;
|
||||
@@ -57,14 +56,6 @@ public final class Log {
|
||||
return level;
|
||||
}
|
||||
|
||||
public boolean isSelected() {
|
||||
return selected;
|
||||
}
|
||||
|
||||
public void setSelected(boolean selected) {
|
||||
this.selected = selected;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return log;
|
||||
|
||||
@@ -51,7 +51,10 @@ import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
@@ -167,7 +170,7 @@ public final class ModpackHelper {
|
||||
|
||||
return new ServerModpackRemoteInstallTask(profile.getDependency(), manifest, name)
|
||||
.whenComplete(Schedulers.defaultScheduler(), success, failure)
|
||||
.withStagesHints("hmcl.modpack", "hmcl.modpack.download");
|
||||
.withStagesHints(new Task.StagesHint("hmcl.modpack"), new Task.StagesHint("hmcl.modpack.download", List.of("hmcl.install.assets", "hmcl.install.libraries")));
|
||||
}
|
||||
|
||||
public static boolean isExternalGameNameConflicts(String name) {
|
||||
@@ -211,16 +214,16 @@ public final class ModpackHelper {
|
||||
return modpack.getInstallTask(profile.getDependency(), zipFile, name, iconUrl)
|
||||
.whenComplete(Schedulers.defaultScheduler(), success, failure)
|
||||
.thenComposeAsync(createMultiMCPostInstallTask(profile, (MultiMCInstanceConfiguration) modpack.getManifest(), name))
|
||||
.withStagesHints("hmcl.modpack", "hmcl.modpack.download");
|
||||
.withStagesHints(new Task.StagesHint("hmcl.modpack"), new Task.StagesHint("hmcl.modpack.download", List.of("hmcl.install.assets", "hmcl.install.libraries")));
|
||||
else if (modpack.getManifest() instanceof McbbsModpackManifest)
|
||||
return modpack.getInstallTask(profile.getDependency(), zipFile, name, iconUrl)
|
||||
.whenComplete(Schedulers.defaultScheduler(), success, failure)
|
||||
.thenComposeAsync(createMcbbsPostInstallTask(profile, (McbbsModpackManifest) modpack.getManifest(), name))
|
||||
.withStagesHints("hmcl.modpack", "hmcl.modpack.download");
|
||||
.withStagesHints(new Task.StagesHint("hmcl.modpack"), new Task.StagesHint("hmcl.modpack.download", List.of("hmcl.install.assets", "hmcl.install.libraries")));
|
||||
else
|
||||
return modpack.getInstallTask(profile.getDependency(), zipFile, name, iconUrl)
|
||||
.whenComplete(Schedulers.javafx(), success, failure)
|
||||
.withStagesHints("hmcl.modpack", "hmcl.modpack.download");
|
||||
.withStagesHints(new Task.StagesHint("hmcl.modpack"), new Task.StagesHint("hmcl.modpack.download", List.of("hmcl.install.assets", "hmcl.install.libraries")));
|
||||
}
|
||||
|
||||
public static Task<Void> getUpdateTask(Profile profile, ServerModpackManifest manifest, Charset charset, String name, ModpackConfiguration<?> configuration) throws UnsupportedModpackException {
|
||||
@@ -228,7 +231,7 @@ public final class ModpackHelper {
|
||||
case ServerModpackRemoteInstallTask.MODPACK_TYPE:
|
||||
return new ModpackUpdateTask(profile.getRepository(), name, new ServerModpackRemoteInstallTask(profile.getDependency(), manifest, name))
|
||||
.thenComposeAsync(profile.getRepository().refreshVersionsAsync())
|
||||
.withStagesHints("hmcl.modpack", "hmcl.modpack.download");
|
||||
.withStagesHints(new Task.StagesHint("hmcl.modpack"), new Task.StagesHint("hmcl.modpack.download", List.of("hmcl.install.assets", "hmcl.install.libraries")));
|
||||
default:
|
||||
throw new UnsupportedModpackException();
|
||||
}
|
||||
|
||||
@@ -163,6 +163,13 @@ public final class OAuthServer extends NanoHTTPD implements OAuth.Session {
|
||||
return newFixedLengthResponse(Response.Status.OK, "text/html; charset=UTF-8", html);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (!future.isDone())
|
||||
future.completeExceptionally(new AuthenticationException("OAuth server is closing"));
|
||||
stop();
|
||||
}
|
||||
|
||||
public static class Factory implements OAuth.Callback {
|
||||
public final EventManager<GrantDeviceCodeEvent> onGrantDeviceCode = new EventManager<>();
|
||||
public final EventManager<OpenBrowserEvent> onOpenBrowserAuthorizationCode = new EventManager<>();
|
||||
|
||||
@@ -99,7 +99,7 @@ public final class HMCLJavaRepository implements JavaRepository {
|
||||
return getJavaExecutable(platform, MOJANG_JAVA_PREFIX + gameJavaVersion.component());
|
||||
}
|
||||
|
||||
private static void getAllJava(List<JavaRuntime> list, Platform platform, Path platformRoot, boolean isManaged) {
|
||||
private static void getAllJava(List<Path> list, Platform platform, Path platformRoot) {
|
||||
try (DirectoryStream<Path> stream = Files.newDirectoryStream(platformRoot)) {
|
||||
for (Path file : stream) {
|
||||
try {
|
||||
@@ -117,8 +117,7 @@ public final class HMCLJavaRepository implements JavaRepository {
|
||||
}
|
||||
|
||||
if (Files.isDirectory(javaDir)) {
|
||||
JavaManifest manifest = JsonUtils.fromJsonFile(file, JavaManifest.class);
|
||||
list.add(JavaRuntime.of(executable, manifest.getInfo(), isManaged));
|
||||
list.add(executable);
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
@@ -131,28 +130,23 @@ public final class HMCLJavaRepository implements JavaRepository {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<JavaRuntime> getAllJava(Platform platform) {
|
||||
public Collection<Path> getAllJava(Platform platform) {
|
||||
Path platformRoot = getPlatformRoot(platform);
|
||||
if (!Files.isDirectory(platformRoot))
|
||||
return Collections.emptyList();
|
||||
|
||||
ArrayList<JavaRuntime> list = new ArrayList<>();
|
||||
|
||||
getAllJava(list, platform, platformRoot, true);
|
||||
if (platform.getOperatingSystem() == OperatingSystem.MACOS) {
|
||||
platformRoot = root.resolve(platform.getOperatingSystem().getMojangName() + "-" + platform.getArchitecture().getCheckedName());
|
||||
if (Files.isDirectory(platformRoot))
|
||||
getAllJava(list, platform, platformRoot, false);
|
||||
}
|
||||
ArrayList<Path> list = new ArrayList<>();
|
||||
|
||||
getAllJava(list, platform, platformRoot);
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task<JavaRuntime> getDownloadJavaTask(DownloadProvider downloadProvider, Platform platform, GameJavaVersion gameJavaVersion) {
|
||||
Path javaDir = getJavaDir(platform, gameJavaVersion);
|
||||
Path tempDir = getPlatformRoot(platform).resolve(".tmp").resolve(javaDir.getFileName());
|
||||
|
||||
return new MojangJavaDownloadTask(downloadProvider, javaDir, gameJavaVersion, JavaManager.getMojangJavaPlatform(platform)).thenApplyAsync(result -> {
|
||||
return new MojangJavaDownloadTask(downloadProvider, javaDir, tempDir, gameJavaVersion, JavaManager.getMojangJavaPlatform(platform)).thenApplyAsync(result -> {
|
||||
Path executable;
|
||||
try {
|
||||
executable = JavaManager.getExecutable(javaDir).toRealPath();
|
||||
@@ -165,16 +159,16 @@ public final class HMCLJavaRepository implements JavaRepository {
|
||||
|
||||
JavaInfo info;
|
||||
if (JavaManager.isCompatible(platform))
|
||||
info = JavaInfoUtils.fromExecutable(executable, false);
|
||||
info = JavaInfoUtils.fromExecutable(executable);
|
||||
else
|
||||
info = new JavaInfo(platform, result.download.getVersion().getName(), null);
|
||||
info = new JavaInfo(platform, result.download().version().name(), null);
|
||||
|
||||
Map<String, Object> update = new LinkedHashMap<>();
|
||||
update.put("provider", "mojang");
|
||||
update.put("component", gameJavaVersion.component());
|
||||
|
||||
Map<String, JavaLocalFiles.Local> files = new LinkedHashMap<>();
|
||||
result.remoteFiles.getFiles().forEach((path, file) -> {
|
||||
result.remoteFiles().getFiles().forEach((path, file) -> {
|
||||
if (file instanceof MojangJavaRemoteFiles.RemoteFile) {
|
||||
DownloadInfo downloadInfo = ((MojangJavaRemoteFiles.RemoteFile) file).getDownloads().get("raw");
|
||||
if (downloadInfo != null) {
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package org.jackhuang.hmcl.java;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import org.jackhuang.hmcl.util.gson.JsonSerializable;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.JarUtils;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
@@ -27,7 +28,6 @@ import org.jackhuang.hmcl.util.platform.SystemUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
@@ -39,35 +39,10 @@ public final class JavaInfoUtils {
|
||||
private JavaInfoUtils() {
|
||||
}
|
||||
|
||||
private static Path tryFindReleaseFile(Path executable) {
|
||||
Path parent = executable.getParent();
|
||||
if (parent != null && parent.getFileName() != null && parent.getFileName().toString().equals("bin")) {
|
||||
Path javaHome = parent.getParent();
|
||||
if (javaHome != null && javaHome.getFileName() != null) {
|
||||
Path releaseFile = javaHome.resolve("release");
|
||||
String javaHomeName = javaHome.getFileName().toString();
|
||||
if ((javaHomeName.contains("jre") || javaHomeName.contains("jdk") || javaHomeName.contains("openj9"))
|
||||
&& Files.isRegularFile(releaseFile)) {
|
||||
return releaseFile;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static @NotNull JavaInfo fromExecutable(Path executable, boolean tryFindReleaseFile) throws IOException {
|
||||
public static @NotNull JavaInfo fromExecutable(Path executable) throws IOException {
|
||||
assert executable.isAbsolute();
|
||||
|
||||
Path releaseFile;
|
||||
if (tryFindReleaseFile && (releaseFile = tryFindReleaseFile(executable)) != null) {
|
||||
try {
|
||||
return JavaInfo.fromReleaseFile(releaseFile);
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
Path thisPath = JarUtils.thisJarPath();
|
||||
|
||||
if (thisPath == null) {
|
||||
throw new IOException("Failed to find current HMCL location");
|
||||
}
|
||||
@@ -94,7 +69,6 @@ public final class JavaInfoUtils {
|
||||
? architecture
|
||||
: Architecture.SYSTEM_ARCH);
|
||||
|
||||
|
||||
return new JavaInfo(platform, result.javaVersion, result.javaVendor);
|
||||
} catch (IOException e) {
|
||||
throw e;
|
||||
@@ -103,14 +77,9 @@ public final class JavaInfoUtils {
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Result {
|
||||
@SerializedName("os.name")
|
||||
public String osName;
|
||||
@SerializedName("os.arch")
|
||||
public String osArch;
|
||||
@SerializedName("java.version")
|
||||
public String javaVersion;
|
||||
@SerializedName("java.vendor")
|
||||
public String javaVendor;
|
||||
@JsonSerializable
|
||||
private record Result(@SerializedName("os.name") String osName, @SerializedName("os.arch") String osArch,
|
||||
@SerializedName("java.version") String javaVersion,
|
||||
@SerializedName("java.vendor") String javaVendor) {
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,8 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.java;
|
||||
|
||||
import com.google.gson.*;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
@@ -30,7 +32,9 @@ import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.util.CacheRepository;
|
||||
import org.jackhuang.hmcl.util.DigestUtils;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.platform.*;
|
||||
import org.jackhuang.hmcl.util.platform.windows.WinReg;
|
||||
@@ -40,9 +44,14 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
@@ -55,6 +64,16 @@ public final class JavaManager {
|
||||
private JavaManager() {
|
||||
}
|
||||
|
||||
private static final String[] KNOWN_VENDOR_DIRECTORIES = {
|
||||
"Java",
|
||||
"BellSoft",
|
||||
"AdoptOpenJDK",
|
||||
"Zulu",
|
||||
"Microsoft",
|
||||
"Eclipse Foundation",
|
||||
"Semeru"
|
||||
};
|
||||
|
||||
public static final HMCLJavaRepository REPOSITORY = new HMCLJavaRepository(Metadata.HMCL_GLOBAL_DIRECTORY.resolve("java"));
|
||||
public static final HMCLJavaRepository LOCAL_REPOSITORY = new HMCLJavaRepository(Metadata.HMCL_CURRENT_DIRECTORY.resolve("java"));
|
||||
|
||||
@@ -103,16 +122,27 @@ public final class JavaManager {
|
||||
switch (OperatingSystem.CURRENT_OS) {
|
||||
case WINDOWS:
|
||||
if (Architecture.SYSTEM_ARCH == Architecture.X86_64)
|
||||
// Windows x86-64 platform is compatible with x86 programs
|
||||
return architecture == Architecture.X86;
|
||||
if (Architecture.SYSTEM_ARCH == Architecture.ARM64)
|
||||
return OperatingSystem.SYSTEM_BUILD_NUMBER >= 21277 && architecture == Architecture.X86_64 || architecture == Architecture.X86;
|
||||
if (Architecture.SYSTEM_ARCH == Architecture.ARM64) {
|
||||
|
||||
// Since Windows 10 Build 21277, Windows Arm64 has been compatible with x86-64 programs via translation
|
||||
if (architecture == Architecture.X86_64 && Platform.isSupportedTranslationX86_64())
|
||||
return true;
|
||||
|
||||
// Windows Arm64 is compatible with x86 programs via translation
|
||||
if (architecture == Architecture.X86)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case LINUX:
|
||||
if (Architecture.SYSTEM_ARCH == Architecture.X86_64)
|
||||
return architecture == Architecture.X86;
|
||||
break;
|
||||
case MACOS:
|
||||
if (Architecture.SYSTEM_ARCH == Architecture.ARM64)
|
||||
// macOS Arm64 compatible with x86-64 programs via Rosetta 2.
|
||||
if (Architecture.SYSTEM_ARCH == Architecture.ARM64 && Platform.isSupportedTranslationX86_64())
|
||||
return architecture == Architecture.X86_64;
|
||||
break;
|
||||
}
|
||||
@@ -160,12 +190,12 @@ public final class JavaManager {
|
||||
return javaRuntime;
|
||||
}
|
||||
|
||||
JavaInfo info = JavaInfoUtils.fromExecutable(executable, true);
|
||||
JavaInfo info = JavaInfoUtils.fromExecutable(executable);
|
||||
return JavaRuntime.of(executable, info, false);
|
||||
}
|
||||
|
||||
public static void refresh() {
|
||||
Task.supplyAsync(JavaManager::searchPotentialJavaExecutables).whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
Task.supplyAsync(() -> searchPotentialJavaExecutables(false)).whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
if (result != null) {
|
||||
LATCH.await();
|
||||
allJava = result;
|
||||
@@ -326,7 +356,7 @@ public final class JavaManager {
|
||||
}
|
||||
|
||||
public static void initialize() {
|
||||
Map<Path, JavaRuntime> allJava = searchPotentialJavaExecutables();
|
||||
Map<Path, JavaRuntime> allJava = searchPotentialJavaExecutables(true);
|
||||
JavaManager.allJava = allJava;
|
||||
LATCH.countDown();
|
||||
FXUtils.runInFX(() -> updateAllJavaProperty(allJava));
|
||||
@@ -334,56 +364,56 @@ public final class JavaManager {
|
||||
|
||||
// search java
|
||||
|
||||
private static Map<Path, JavaRuntime> searchPotentialJavaExecutables() {
|
||||
Map<Path, JavaRuntime> javaRuntimes = new HashMap<>();
|
||||
searchAllJavaInRepository(javaRuntimes, Platform.SYSTEM_PLATFORM);
|
||||
private static Map<Path, JavaRuntime> searchPotentialJavaExecutables(boolean useCache) {
|
||||
Searcher searcher = new Searcher(Metadata.HMCL_GLOBAL_DIRECTORY.resolve("javaCache.json"));
|
||||
if (useCache)
|
||||
searcher.loadCache();
|
||||
|
||||
searcher.searchAllJavaInRepository(Platform.SYSTEM_PLATFORM);
|
||||
switch (OperatingSystem.CURRENT_OS) {
|
||||
case WINDOWS:
|
||||
if (Architecture.SYSTEM_ARCH == Architecture.X86_64)
|
||||
searchAllJavaInRepository(javaRuntimes, Platform.WINDOWS_X86);
|
||||
searcher.searchAllJavaInRepository(Platform.WINDOWS_X86);
|
||||
if (Architecture.SYSTEM_ARCH == Architecture.ARM64) {
|
||||
if (OperatingSystem.SYSTEM_BUILD_NUMBER >= 21277)
|
||||
searchAllJavaInRepository(javaRuntimes, Platform.WINDOWS_X86_64);
|
||||
searchAllJavaInRepository(javaRuntimes, Platform.WINDOWS_X86);
|
||||
if (Platform.isSupportedTranslationX86_64())
|
||||
searcher.searchAllJavaInRepository(Platform.WINDOWS_X86_64);
|
||||
searcher.searchAllJavaInRepository(Platform.WINDOWS_X86);
|
||||
}
|
||||
break;
|
||||
case MACOS:
|
||||
if (Architecture.SYSTEM_ARCH == Architecture.ARM64)
|
||||
searchAllJavaInRepository(javaRuntimes, Platform.MACOS_X86_64);
|
||||
if (Architecture.SYSTEM_ARCH == Architecture.ARM64 && Platform.isSupportedTranslationX86_64())
|
||||
searcher.searchAllJavaInRepository(Platform.MACOS_X86_64);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (OperatingSystem.CURRENT_OS) {
|
||||
case WINDOWS:
|
||||
queryJavaInRegistryKey(javaRuntimes, WinReg.HKEY.HKEY_LOCAL_MACHINE, "SOFTWARE\\JavaSoft\\Java Runtime Environment");
|
||||
queryJavaInRegistryKey(javaRuntimes, WinReg.HKEY.HKEY_LOCAL_MACHINE, "SOFTWARE\\JavaSoft\\Java Development Kit");
|
||||
queryJavaInRegistryKey(javaRuntimes, WinReg.HKEY.HKEY_LOCAL_MACHINE, "SOFTWARE\\JavaSoft\\JRE");
|
||||
queryJavaInRegistryKey(javaRuntimes, WinReg.HKEY.HKEY_LOCAL_MACHINE, "SOFTWARE\\JavaSoft\\JDK");
|
||||
searcher.queryJavaInRegistryKey(WinReg.HKEY.HKEY_LOCAL_MACHINE, "SOFTWARE\\JavaSoft\\Java Runtime Environment");
|
||||
searcher.queryJavaInRegistryKey(WinReg.HKEY.HKEY_LOCAL_MACHINE, "SOFTWARE\\JavaSoft\\Java Development Kit");
|
||||
searcher.queryJavaInRegistryKey(WinReg.HKEY.HKEY_LOCAL_MACHINE, "SOFTWARE\\JavaSoft\\JRE");
|
||||
searcher.queryJavaInRegistryKey(WinReg.HKEY.HKEY_LOCAL_MACHINE, "SOFTWARE\\JavaSoft\\JDK");
|
||||
|
||||
searchJavaInProgramFiles(javaRuntimes, "ProgramFiles", "C:\\Program Files");
|
||||
searchJavaInProgramFiles(javaRuntimes, "ProgramFiles(x86)", "C:\\Program Files (x86)");
|
||||
if (Architecture.SYSTEM_ARCH == Architecture.ARM64) {
|
||||
searchJavaInProgramFiles(javaRuntimes, "ProgramFiles(ARM)", "C:\\Program Files (ARM)");
|
||||
}
|
||||
searcher.searchJavaInProgramFiles("ProgramFiles", "C:\\Program Files");
|
||||
searcher.searchJavaInProgramFiles("ProgramFiles(x86)", "C:\\Program Files (x86)");
|
||||
break;
|
||||
case LINUX:
|
||||
searchAllJavaInDirectory(javaRuntimes, Paths.get("/usr/java")); // Oracle RPMs
|
||||
searchAllJavaInDirectory(javaRuntimes, Paths.get("/usr/lib/jvm")); // General locations
|
||||
searchAllJavaInDirectory(javaRuntimes, Paths.get("/usr/lib32/jvm")); // General locations
|
||||
searchAllJavaInDirectory(javaRuntimes, Paths.get("/usr/lib64/jvm")); // General locations
|
||||
searchAllJavaInDirectory(javaRuntimes, Paths.get(System.getProperty("user.home"), "/.sdkman/candidates/java")); // SDKMAN!
|
||||
searcher.searchAllJavaInDirectory(Path.of("/usr/java")); // Oracle RPMs
|
||||
searcher.searchAllJavaInDirectory(Path.of("/usr/lib/jvm")); // General locations
|
||||
searcher.searchAllJavaInDirectory(Path.of("/usr/lib32/jvm")); // General locations
|
||||
searcher.searchAllJavaInDirectory(Path.of("/usr/lib64/jvm")); // General locations
|
||||
searcher.searchAllJavaInDirectory(Path.of(System.getProperty("user.home"), "/.sdkman/candidates/java")); // SDKMAN!
|
||||
break;
|
||||
case MACOS:
|
||||
searchJavaInMacJavaVirtualMachines(javaRuntimes, Paths.get("/Library/Java/JavaVirtualMachines"));
|
||||
searchJavaInMacJavaVirtualMachines(javaRuntimes, Paths.get(System.getProperty("user.home"), "/Library/Java/JavaVirtualMachines"));
|
||||
tryAddJavaExecutable(javaRuntimes, Paths.get("/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/bin/java"));
|
||||
tryAddJavaExecutable(javaRuntimes, Paths.get("/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/MacOS/itms/java/bin/java"));
|
||||
searcher.searchJavaInMacJavaVirtualMachines(Path.of("/Library/Java/JavaVirtualMachines"));
|
||||
searcher.searchJavaInMacJavaVirtualMachines(Path.of(System.getProperty("user.home"), "/Library/Java/JavaVirtualMachines"));
|
||||
searcher.tryAddJavaExecutable(Path.of("/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/bin/java"));
|
||||
searcher.tryAddJavaExecutable(Path.of("/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/MacOS/itms/java/bin/java"));
|
||||
// Homebrew
|
||||
tryAddJavaExecutable(javaRuntimes, Paths.get("/opt/homebrew/opt/java/bin/java"));
|
||||
searchAllJavaInDirectory(javaRuntimes, Paths.get("/opt/homebrew/Cellar/openjdk"));
|
||||
try (DirectoryStream<Path> dirs = Files.newDirectoryStream(Paths.get("/opt/homebrew/Cellar"), "openjdk@*")) {
|
||||
searcher.tryAddJavaExecutable(Path.of("/opt/homebrew/opt/java/bin/java"));
|
||||
searcher.searchAllJavaInDirectory(Path.of("/opt/homebrew/Cellar/openjdk"));
|
||||
try (DirectoryStream<Path> dirs = Files.newDirectoryStream(Path.of("/opt/homebrew/Cellar"), "openjdk@*")) {
|
||||
for (Path dir : dirs) {
|
||||
searchAllJavaInDirectory(javaRuntimes, dir);
|
||||
searcher.searchAllJavaInDirectory(dir);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to get subdirectories of /opt/homebrew/Cellar");
|
||||
@@ -397,16 +427,16 @@ public final class JavaManager {
|
||||
// Search Minecraft bundled runtimes
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS && Architecture.SYSTEM_ARCH.isX86()) {
|
||||
FileUtils.tryGetPath(System.getenv("localappdata"), "Packages\\Microsoft.4297127D64EC6_8wekyb3d8bbwe\\LocalCache\\Local\\runtime")
|
||||
.ifPresent(it -> searchAllOfficialJava(javaRuntimes, it, false));
|
||||
.ifPresent(it -> searcher.searchAllOfficialJava(it, false));
|
||||
|
||||
FileUtils.tryGetPath(Lang.requireNonNullElse(System.getenv("ProgramFiles(x86)"), "C:\\Program Files (x86)"), "Minecraft Launcher\\runtime")
|
||||
.ifPresent(it -> searchAllOfficialJava(javaRuntimes, it, false));
|
||||
.ifPresent(it -> searcher.searchAllOfficialJava(it, false));
|
||||
} else if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX && Architecture.SYSTEM_ARCH == Architecture.X86_64) {
|
||||
searchAllOfficialJava(javaRuntimes, Paths.get(System.getProperty("user.home"), ".minecraft/runtime"), false);
|
||||
searcher.searchAllOfficialJava(Path.of(System.getProperty("user.home"), ".minecraft/runtime"), false);
|
||||
} else if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS) {
|
||||
searchAllOfficialJava(javaRuntimes, Paths.get(System.getProperty("user.home"), "Library/Application Support/minecraft/runtime"), false);
|
||||
searcher.searchAllOfficialJava(Path.of(System.getProperty("user.home"), "Library/Application Support/minecraft/runtime"), false);
|
||||
}
|
||||
searchAllOfficialJava(javaRuntimes, CacheRepository.getInstance().getCacheDirectory().resolve("java"), true);
|
||||
searcher.searchAllOfficialJava(CacheRepository.getInstance().getCacheDirectory().resolve("java"), true);
|
||||
|
||||
// Search in PATH.
|
||||
if (System.getenv("PATH") != null) {
|
||||
@@ -420,7 +450,7 @@ public final class JavaManager {
|
||||
}
|
||||
|
||||
try {
|
||||
tryAddJavaExecutable(javaRuntimes, Path.of(path, OperatingSystem.CURRENT_OS.getJavaExecutable()));
|
||||
searcher.tryAddJavaExecutable(Path.of(path, OperatingSystem.CURRENT_OS.getJavaExecutable()));
|
||||
} catch (InvalidPathException ignored) {
|
||||
}
|
||||
}
|
||||
@@ -430,16 +460,17 @@ public final class JavaManager {
|
||||
String[] paths = System.getenv("HMCL_JRES").split(File.pathSeparator);
|
||||
for (String path : paths) {
|
||||
try {
|
||||
tryAddJavaHome(javaRuntimes, Paths.get(path));
|
||||
searcher.tryAddJavaHome(Path.of(path));
|
||||
} catch (InvalidPathException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
searchAllJavaInDirectory(javaRuntimes, Paths.get(System.getProperty("user.home"), ".jdks"));
|
||||
|
||||
searcher.searchAllJavaInDirectory(Path.of(System.getProperty("user.home"), ".jdks"));
|
||||
|
||||
for (String javaPath : ConfigHolder.globalConfig().getUserJava()) {
|
||||
try {
|
||||
tryAddJavaExecutable(javaRuntimes, Paths.get(javaPath));
|
||||
searcher.tryAddJavaExecutable(Path.of(javaPath));
|
||||
} catch (InvalidPathException e) {
|
||||
LOG.warning("Invalid Java path: " + javaPath);
|
||||
}
|
||||
@@ -447,233 +478,406 @@ public final class JavaManager {
|
||||
|
||||
JavaRuntime currentJava = JavaRuntime.CURRENT_JAVA;
|
||||
if (currentJava != null
|
||||
&& !javaRuntimes.containsKey(currentJava.getBinary())
|
||||
&& !searcher.javaRuntimes.containsKey(currentJava.getBinary())
|
||||
&& !ConfigHolder.globalConfig().getDisabledJava().contains(currentJava.getBinary().toString())) {
|
||||
javaRuntimes.put(currentJava.getBinary(), currentJava);
|
||||
searcher.addResult(currentJava.getBinary(), currentJava);
|
||||
}
|
||||
|
||||
LOG.trace(javaRuntimes.values().stream().sorted()
|
||||
searcher.saveCache();
|
||||
|
||||
LOG.trace(searcher.javaRuntimes.values().stream().sorted()
|
||||
.map(it -> String.format(" - %s %s (%s, %s): %s",
|
||||
it.isJDK() ? "JDK" : "JRE",
|
||||
it.getVersion(),
|
||||
it.getPlatform().getArchitecture().getDisplayName(),
|
||||
Lang.requireNonNullElse(it.getVendor(), "Unknown"),
|
||||
it.getBinary()))
|
||||
.collect(Collectors.joining("\n", "Finished Java lookup, found " + javaRuntimes.size() + "\n", "")));
|
||||
|
||||
return javaRuntimes;
|
||||
.collect(Collectors.joining("\n", "Finished Java lookup, found " + searcher.javaRuntimes.size() + "\n", "")));
|
||||
return searcher.javaRuntimes;
|
||||
}
|
||||
|
||||
private static void tryAddJavaHome(Map<Path, JavaRuntime> javaRuntimes, Path javaHome) {
|
||||
Path executable = getExecutable(javaHome);
|
||||
if (!Files.isRegularFile(executable)) {
|
||||
return;
|
||||
private static final class Searcher {
|
||||
private final Path cacheFile;
|
||||
final Map<Path, JavaRuntime> javaRuntimes = new HashMap<>();
|
||||
private final LinkedHashMap<Path, JavaInfoCache> caches = new LinkedHashMap<>();
|
||||
private final Set<Path> failed = new HashSet<>();
|
||||
private boolean needRefreshCache = false;
|
||||
|
||||
Searcher(Path cacheFile) {
|
||||
this.cacheFile = cacheFile;
|
||||
}
|
||||
|
||||
try {
|
||||
executable = executable.toRealPath();
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to resolve path " + executable, e);
|
||||
return;
|
||||
private static final Pattern CACHE_VERSION_PATTERN = Pattern.compile("(?<major>\\d+)(?:\\.(?<minor>\\d+))?");
|
||||
private static final int CACHE_MAJOR_VERSION = 0;
|
||||
private static final int CACHE_MINOR_VERSION = 0;
|
||||
|
||||
private record JavaInfoCache(String key, JavaInfo info) {
|
||||
}
|
||||
|
||||
if (javaRuntimes.containsKey(executable) || ConfigHolder.globalConfig().getDisabledJava().contains(executable.toString())) {
|
||||
return;
|
||||
}
|
||||
void loadCache() {
|
||||
if (Files.notExists(cacheFile))
|
||||
return;
|
||||
|
||||
JavaInfo info = null;
|
||||
|
||||
Path releaseFile = javaHome.resolve("release");
|
||||
if (Files.exists(releaseFile)) {
|
||||
try {
|
||||
info = JavaInfo.fromReleaseFile(releaseFile);
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to read release file " + releaseFile, e);
|
||||
JsonObject jsonFile = JsonUtils.fromJsonFile(cacheFile, JsonObject.class);
|
||||
JsonElement fileVersion = jsonFile.get("version");
|
||||
|
||||
Matcher matcher;
|
||||
if (fileVersion instanceof JsonPrimitive version
|
||||
&& (matcher = CACHE_VERSION_PATTERN.matcher(version.getAsString())).matches()) {
|
||||
int major = Integer.parseInt(matcher.group("major"));
|
||||
|
||||
String minorString = matcher.group("minor");
|
||||
int minor = minorString != null ? Integer.parseInt(minorString) : 0;
|
||||
|
||||
if (major != CACHE_MAJOR_VERSION || minor < CACHE_MINOR_VERSION)
|
||||
throw new IOException("Unsupported cache file, version: %s".formatted(version.getAsString()));
|
||||
} else
|
||||
throw new IOException("Invalid version JSON: " + fileVersion);
|
||||
|
||||
JsonArray cachesArray = jsonFile.getAsJsonArray("caches");
|
||||
|
||||
for (JsonElement element : cachesArray) {
|
||||
try {
|
||||
var obj = (JsonObject) element;
|
||||
|
||||
Path realPath = Path.of(obj.getAsJsonPrimitive("path").getAsString()).toRealPath();
|
||||
String key = obj.getAsJsonPrimitive("key").getAsString();
|
||||
|
||||
OperatingSystem osName = OperatingSystem.parseOSName(obj.getAsJsonPrimitive("os.name").getAsString());
|
||||
Architecture osArch = Architecture.parseArchName(obj.getAsJsonPrimitive("os.arch").getAsString());
|
||||
String javaVersion = obj.getAsJsonPrimitive("java.version").getAsString();
|
||||
|
||||
JavaInfo.Builder infoBuilder = JavaInfo.newBuilder(Platform.getPlatform(osName, osArch), javaVersion);
|
||||
|
||||
if (obj.get("java.vendor") instanceof JsonPrimitive vendor)
|
||||
infoBuilder.setVendor(vendor.getAsString());
|
||||
|
||||
caches.put(realPath, new JavaInfoCache(key, infoBuilder.build()));
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Invalid cache: " + element);
|
||||
needRefreshCache = true;
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
LOG.warning("Failed to load cache file: " + cacheFile);
|
||||
needRefreshCache = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (info == null) {
|
||||
void saveCache() {
|
||||
if (!needRefreshCache)
|
||||
return;
|
||||
|
||||
needRefreshCache = false;
|
||||
try {
|
||||
info = JavaInfoUtils.fromExecutable(executable, false);
|
||||
FileUtils.saveSafely(cacheFile, output -> {
|
||||
try (var writer = new JsonWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8))) {
|
||||
writer.beginObject();
|
||||
|
||||
writer.name("version").value("%d.%d".formatted(CACHE_MAJOR_VERSION, CACHE_MINOR_VERSION));
|
||||
|
||||
writer.name("caches");
|
||||
writer.beginArray();
|
||||
for (Map.Entry<Path, JavaInfoCache> entry : caches.entrySet()) {
|
||||
Path path = entry.getKey();
|
||||
JavaInfoCache cache = entry.getValue();
|
||||
JavaInfo info = cache.info();
|
||||
|
||||
writer.beginObject();
|
||||
|
||||
writer.name("path").value(path.toString());
|
||||
writer.name("key").value(cache.key());
|
||||
|
||||
writer.name("os.name").value(info.getPlatform().os().getCheckedName());
|
||||
writer.name("os.arch").value(info.getPlatform().arch().getCheckedName());
|
||||
writer.name("java.version").value(info.getVersion());
|
||||
if (info.getVendor() != null)
|
||||
writer.name("java.vendor").value(info.getVendor());
|
||||
|
||||
writer.endObject();
|
||||
}
|
||||
writer.endArray();
|
||||
|
||||
writer.endObject();
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to save cache file: " + cacheFile);
|
||||
}
|
||||
}
|
||||
|
||||
private static @Nullable String createCacheKey(Path realPath) {
|
||||
Path binDir = realPath.getParent();
|
||||
if (binDir == null || !FileUtils.getName(binDir).equals("bin"))
|
||||
return null;
|
||||
|
||||
if (Files.isRegularFile(realPath.resolveSibling("ikvm.properties")))
|
||||
return null;
|
||||
|
||||
Path javaHome = binDir.getParent();
|
||||
if (javaHome == null)
|
||||
return null;
|
||||
|
||||
Path libDir = javaHome.resolve("lib");
|
||||
if (!Files.isDirectory(libDir))
|
||||
return null;
|
||||
|
||||
BasicFileAttributes launcherAttributes;
|
||||
String releaseHash = null;
|
||||
BasicFileAttributes coreLibsAttributes = null;
|
||||
|
||||
try {
|
||||
launcherAttributes = Files.readAttributes(realPath, BasicFileAttributes.class);
|
||||
|
||||
Path releaseFile = javaHome.resolve("release");
|
||||
if (Files.exists(releaseFile)) {
|
||||
releaseHash = DigestUtils.digestToString("SHA-1", releaseFile);
|
||||
} else {
|
||||
Path coreLibsFile = libDir.resolve("rt.jar");
|
||||
if (!Files.isRegularFile(coreLibsFile)) {
|
||||
coreLibsFile = javaHome.resolve("jre/lib/rt.jar");
|
||||
if (!Files.isRegularFile(coreLibsFile))
|
||||
return null;
|
||||
|
||||
coreLibsAttributes = Files.readAttributes(coreLibsFile, BasicFileAttributes.class);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to create cache key for " + realPath, e);
|
||||
return null;
|
||||
}
|
||||
|
||||
StringJoiner joiner = new StringJoiner(",");
|
||||
|
||||
joiner.add("sz:" + launcherAttributes.size());
|
||||
joiner.add("lm:" + launcherAttributes.lastModifiedTime().toMillis());
|
||||
|
||||
if (releaseHash != null)
|
||||
joiner.add(releaseHash);
|
||||
|
||||
if (coreLibsAttributes != null) {
|
||||
joiner.add("rsz:" + coreLibsAttributes.size());
|
||||
joiner.add("rlm:" + coreLibsAttributes.lastModifiedTime().toMillis());
|
||||
}
|
||||
|
||||
return joiner.toString();
|
||||
}
|
||||
|
||||
void addResult(Path realPath, JavaRuntime javaRuntime) {
|
||||
javaRuntimes.put(realPath, javaRuntime);
|
||||
}
|
||||
|
||||
void tryAddJavaHome(Path javaHome) {
|
||||
tryAddJavaExecutable(getExecutable(javaHome));
|
||||
}
|
||||
|
||||
void tryAddJavaExecutable(Path executable) {
|
||||
tryAddJavaExecutable(executable, false);
|
||||
}
|
||||
|
||||
void tryAddJavaExecutable(Path executable, boolean isManaged) {
|
||||
try {
|
||||
executable = executable.toRealPath();
|
||||
} catch (IOException e) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (javaRuntimes.containsKey(executable)
|
||||
|| failed.contains(executable)
|
||||
|| ConfigHolder.globalConfig().getDisabledJava().contains(executable.toString())) {
|
||||
return;
|
||||
}
|
||||
|
||||
String cacheKey = createCacheKey(executable);
|
||||
if (cacheKey != null) {
|
||||
JavaInfoCache cache = caches.get(executable);
|
||||
if (cache != null) {
|
||||
if (isCompatible(cache.info().getPlatform()) && cacheKey.equals(cache.key())) {
|
||||
javaRuntimes.put(executable, JavaRuntime.of(executable, cache.info(), isManaged));
|
||||
return;
|
||||
} else {
|
||||
caches.remove(executable);
|
||||
needRefreshCache = true;
|
||||
}
|
||||
}
|
||||
} else if (caches.remove(executable) != null) {
|
||||
needRefreshCache = true;
|
||||
}
|
||||
|
||||
JavaInfo info;
|
||||
try {
|
||||
info = JavaInfoUtils.fromExecutable(executable);
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to lookup Java executable at " + executable, e);
|
||||
failed.add(executable);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cacheKey != null) {
|
||||
caches.put(executable, new JavaInfoCache(cacheKey, info));
|
||||
needRefreshCache = true;
|
||||
}
|
||||
|
||||
javaRuntimes.put(executable, JavaRuntime.of(executable, info, isManaged));
|
||||
}
|
||||
|
||||
void tryAddJavaInComponentDir(String platform, Path component, boolean verify) {
|
||||
Path sha1File = component.resolve(platform).resolve(component.getFileName() + ".sha1");
|
||||
if (!Files.isRegularFile(sha1File))
|
||||
return;
|
||||
|
||||
Path dir = component.resolve(platform).resolve(component.getFileName());
|
||||
|
||||
if (verify) {
|
||||
try (BufferedReader reader = Files.newBufferedReader(sha1File)) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (line.isEmpty()) continue;
|
||||
|
||||
int idx = line.indexOf(" /#//");
|
||||
if (idx <= 0)
|
||||
throw new IOException("Illegal line: " + line);
|
||||
|
||||
Path file = dir.resolve(line.substring(0, idx));
|
||||
|
||||
// Should we check the sha1 of files? This will take a lot of time.
|
||||
if (Files.notExists(file))
|
||||
throw new NoSuchFileException(file.toAbsolutePath().toString());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to verify Java in " + component, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS) {
|
||||
Path macPath = dir.resolve("jre.bundle/Contents/Home");
|
||||
if (Files.exists(macPath)) {
|
||||
tryAddJavaHome(macPath);
|
||||
return;
|
||||
} else
|
||||
LOG.warning("The Java is not in 'jre.bundle/Contents/Home'");
|
||||
}
|
||||
|
||||
tryAddJavaHome(dir);
|
||||
}
|
||||
|
||||
void searchAllJavaInRepository(Platform platform) {
|
||||
for (Path java : REPOSITORY.getAllJava(platform)) {
|
||||
tryAddJavaExecutable(java, true);
|
||||
}
|
||||
|
||||
for (Path java : LOCAL_REPOSITORY.getAllJava(platform)) {
|
||||
tryAddJavaExecutable(java, true);
|
||||
}
|
||||
|
||||
if (platform.os() == OperatingSystem.MACOS) {
|
||||
// In the past, we used 'osx' as the checked name for macOS
|
||||
Path platformRoot = REPOSITORY.getPlatformRoot(platform).resolveSibling("osx-" + platform.getArchitecture().getCheckedName());
|
||||
searchAllJavaInDirectory(platformRoot);
|
||||
}
|
||||
}
|
||||
|
||||
if (info != null && isCompatible(info.getPlatform()))
|
||||
javaRuntimes.put(executable, JavaRuntime.of(executable, info, false));
|
||||
}
|
||||
void searchAllOfficialJava(Path directory, boolean verify) {
|
||||
if (!Files.isDirectory(directory))
|
||||
return;
|
||||
// Examples:
|
||||
// $HOME/Library/Application Support/minecraft/runtime/java-runtime-beta/mac-os/java-runtime-beta/jre.bundle/Contents/Home
|
||||
// $HOME/.minecraft/runtime/java-runtime-beta/linux/java-runtime-beta
|
||||
|
||||
private static void tryAddJavaExecutable(Map<Path, JavaRuntime> javaRuntimes, Path executable) {
|
||||
try {
|
||||
executable = executable.toRealPath();
|
||||
} catch (IOException e) {
|
||||
return;
|
||||
String javaPlatform = getMojangJavaPlatform(Platform.SYSTEM_PLATFORM);
|
||||
if (javaPlatform != null) {
|
||||
searchAllOfficialJava(directory, javaPlatform, verify);
|
||||
}
|
||||
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
if (Architecture.SYSTEM_ARCH == Architecture.X86_64) {
|
||||
searchAllOfficialJava(directory, getMojangJavaPlatform(Platform.WINDOWS_X86), verify);
|
||||
} else if (Architecture.SYSTEM_ARCH == Architecture.ARM64) {
|
||||
if (Platform.isSupportedTranslationX86_64()) {
|
||||
searchAllOfficialJava(directory, getMojangJavaPlatform(Platform.WINDOWS_X86_64), verify);
|
||||
}
|
||||
searchAllOfficialJava(directory, getMojangJavaPlatform(Platform.WINDOWS_X86), verify);
|
||||
}
|
||||
} else if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS) {
|
||||
if (Architecture.SYSTEM_ARCH == Architecture.ARM64 && Platform.isSupportedTranslationX86_64()) {
|
||||
searchAllOfficialJava(directory, getMojangJavaPlatform(Platform.MACOS_X86_64), verify);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (javaRuntimes.containsKey(executable) || ConfigHolder.globalConfig().getDisabledJava().contains(executable.toString())) {
|
||||
return;
|
||||
}
|
||||
|
||||
JavaInfo info = null;
|
||||
try {
|
||||
info = JavaInfoUtils.fromExecutable(executable, true);
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to lookup Java executable at " + executable, e);
|
||||
}
|
||||
|
||||
if (info != null && isCompatible(info.getPlatform())) {
|
||||
javaRuntimes.put(executable, JavaRuntime.of(executable, info, false));
|
||||
}
|
||||
}
|
||||
|
||||
private static void tryAddJavaInComponentDir(Map<Path, JavaRuntime> javaRuntimes, String platform, Path component, boolean verify) {
|
||||
Path sha1File = component.resolve(platform).resolve(component.getFileName() + ".sha1");
|
||||
if (!Files.isRegularFile(sha1File))
|
||||
return;
|
||||
|
||||
Path dir = component.resolve(platform).resolve(component.getFileName());
|
||||
|
||||
if (verify) {
|
||||
try (BufferedReader reader = Files.newBufferedReader(sha1File)) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (line.isEmpty()) continue;
|
||||
|
||||
int idx = line.indexOf(" /#//");
|
||||
if (idx <= 0)
|
||||
throw new IOException("Illegal line: " + line);
|
||||
|
||||
Path file = dir.resolve(line.substring(0, idx));
|
||||
|
||||
// Should we check the sha1 of files? This will take a lot of time.
|
||||
if (Files.notExists(file))
|
||||
throw new NoSuchFileException(file.toAbsolutePath().toString());
|
||||
void searchAllOfficialJava(Path directory, String platform, boolean verify) {
|
||||
try (DirectoryStream<Path> dir = Files.newDirectoryStream(directory)) {
|
||||
// component can be jre-legacy, java-runtime-alpha, java-runtime-beta, java-runtime-gamma or any other being added in the future.
|
||||
for (Path component : dir) {
|
||||
tryAddJavaInComponentDir(platform, component, verify);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to verify Java in " + component, e);
|
||||
return;
|
||||
LOG.warning("Failed to list java-runtime directory " + directory, e);
|
||||
}
|
||||
}
|
||||
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS) {
|
||||
Path macPath = dir.resolve("jre.bundle/Contents/Home");
|
||||
if (Files.exists(macPath)) {
|
||||
tryAddJavaHome(javaRuntimes, macPath);
|
||||
void searchAllJavaInDirectory(Path directory) {
|
||||
if (!Files.isDirectory(directory)) {
|
||||
return;
|
||||
} else
|
||||
LOG.warning("The Java is not in 'jre.bundle/Contents/Home'");
|
||||
}
|
||||
}
|
||||
|
||||
tryAddJavaHome(javaRuntimes, dir);
|
||||
}
|
||||
|
||||
private static void searchAllJavaInRepository(Map<Path, JavaRuntime> javaRuntimes, Platform platform) {
|
||||
for (JavaRuntime java : REPOSITORY.getAllJava(platform)) {
|
||||
javaRuntimes.put(java.getBinary(), java);
|
||||
}
|
||||
|
||||
for (JavaRuntime java : LOCAL_REPOSITORY.getAllJava(platform)) {
|
||||
javaRuntimes.put(java.getBinary(), java);
|
||||
}
|
||||
}
|
||||
|
||||
private static void searchAllOfficialJava(Map<Path, JavaRuntime> javaRuntimes, Path directory, boolean verify) {
|
||||
if (!Files.isDirectory(directory))
|
||||
return;
|
||||
// Examples:
|
||||
// $HOME/Library/Application Support/minecraft/runtime/java-runtime-beta/mac-os/java-runtime-beta/jre.bundle/Contents/Home
|
||||
// $HOME/.minecraft/runtime/java-runtime-beta/linux/java-runtime-beta
|
||||
|
||||
String javaPlatform = getMojangJavaPlatform(Platform.SYSTEM_PLATFORM);
|
||||
if (javaPlatform != null) {
|
||||
searchAllOfficialJava(javaRuntimes, directory, javaPlatform, verify);
|
||||
}
|
||||
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
if (Architecture.SYSTEM_ARCH == Architecture.X86_64) {
|
||||
searchAllOfficialJava(javaRuntimes, directory, getMojangJavaPlatform(Platform.WINDOWS_X86), verify);
|
||||
} else if (Architecture.SYSTEM_ARCH == Architecture.ARM64) {
|
||||
if (OperatingSystem.SYSTEM_BUILD_NUMBER >= 21277) {
|
||||
searchAllOfficialJava(javaRuntimes, directory, getMojangJavaPlatform(Platform.WINDOWS_X86_64), verify);
|
||||
try (DirectoryStream<Path> stream = Files.newDirectoryStream(directory)) {
|
||||
for (Path subDir : stream) {
|
||||
tryAddJavaHome(subDir);
|
||||
}
|
||||
searchAllOfficialJava(javaRuntimes, directory, getMojangJavaPlatform(Platform.WINDOWS_X86), verify);
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to find Java in " + directory, e);
|
||||
}
|
||||
} else if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS && Architecture.CURRENT_ARCH == Architecture.ARM64) {
|
||||
searchAllOfficialJava(javaRuntimes, directory, getMojangJavaPlatform(Platform.MACOS_X86_64), verify);
|
||||
}
|
||||
}
|
||||
|
||||
private static void searchAllOfficialJava(Map<Path, JavaRuntime> javaRuntimes, Path directory, String platform, boolean verify) {
|
||||
try (DirectoryStream<Path> dir = Files.newDirectoryStream(directory)) {
|
||||
// component can be jre-legacy, java-runtime-alpha, java-runtime-beta, java-runtime-gamma or any other being added in the future.
|
||||
for (Path component : dir) {
|
||||
tryAddJavaInComponentDir(javaRuntimes, platform, component, verify);
|
||||
void searchJavaInProgramFiles(String env, String defaultValue) {
|
||||
String programFiles = Lang.requireNonNullElse(System.getenv(env), defaultValue);
|
||||
Path path;
|
||||
try {
|
||||
path = Path.of(programFiles);
|
||||
} catch (InvalidPathException ignored) {
|
||||
return;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to list java-runtime directory " + directory, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void searchAllJavaInDirectory(Map<Path, JavaRuntime> javaRuntimes, Path directory) {
|
||||
if (!Files.isDirectory(directory)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try (DirectoryStream<Path> stream = Files.newDirectoryStream(directory)) {
|
||||
for (Path subDir : stream) {
|
||||
tryAddJavaHome(javaRuntimes, subDir);
|
||||
for (String vendor : KNOWN_VENDOR_DIRECTORIES) {
|
||||
searchAllJavaInDirectory(path.resolve(vendor));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to find Java in " + directory, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void searchJavaInProgramFiles(Map<Path, JavaRuntime> javaRuntimes, String env, String defaultValue) {
|
||||
String programFiles = Lang.requireNonNullElse(System.getenv(env), defaultValue);
|
||||
Path path;
|
||||
try {
|
||||
path = Paths.get(programFiles);
|
||||
} catch (InvalidPathException ignored) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (String vendor : new String[]{"Java", "BellSoft", "AdoptOpenJDK", "Zulu", "Microsoft", "Eclipse Foundation", "Semeru"}) {
|
||||
searchAllJavaInDirectory(javaRuntimes, path.resolve(vendor));
|
||||
}
|
||||
}
|
||||
|
||||
private static void searchJavaInMacJavaVirtualMachines(Map<Path, JavaRuntime> javaRuntimes, Path directory) {
|
||||
if (!Files.isDirectory(directory)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try (DirectoryStream<Path> stream = Files.newDirectoryStream(directory)) {
|
||||
for (Path subDir : stream) {
|
||||
tryAddJavaHome(javaRuntimes, subDir.resolve("Contents/Home"));
|
||||
void searchJavaInMacJavaVirtualMachines(Path directory) {
|
||||
if (!Files.isDirectory(directory)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try (DirectoryStream<Path> stream = Files.newDirectoryStream(directory)) {
|
||||
for (Path subDir : stream) {
|
||||
tryAddJavaHome(subDir.resolve("Contents/Home"));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to find Java in " + directory, e);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to find Java in " + directory, e);
|
||||
}
|
||||
}
|
||||
|
||||
// ==== Windows Registry Support ====
|
||||
private static void queryJavaInRegistryKey(Map<Path, JavaRuntime> javaRuntimes, WinReg.HKEY hkey, String location) {
|
||||
WinReg reg = WinReg.INSTANCE;
|
||||
if (reg == null)
|
||||
return;
|
||||
// ==== Windows Registry Support ====
|
||||
void queryJavaInRegistryKey(WinReg.HKEY hkey, String location) {
|
||||
WinReg reg = WinReg.INSTANCE;
|
||||
if (reg == null)
|
||||
return;
|
||||
|
||||
for (String java : reg.querySubKeys(hkey, location)) {
|
||||
if (!reg.querySubKeys(hkey, java).contains(java + "\\MSI"))
|
||||
continue;
|
||||
Object home = reg.queryValue(hkey, java, "JavaHome");
|
||||
if (home instanceof String) {
|
||||
try {
|
||||
tryAddJavaHome(javaRuntimes, Paths.get((String) home));
|
||||
} catch (InvalidPathException e) {
|
||||
LOG.warning("Invalid Java path in system registry: " + home);
|
||||
for (String java : reg.querySubKeys(hkey, location)) {
|
||||
if (!reg.querySubKeys(hkey, java).contains(java + "\\MSI"))
|
||||
continue;
|
||||
if (reg.queryValue(hkey, java, "JavaHome") instanceof String home) {
|
||||
try {
|
||||
tryAddJavaHome(Path.of(home));
|
||||
} catch (InvalidPathException e) {
|
||||
LOG.warning("Invalid Java path in system registry: " + home);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -468,7 +468,9 @@ public final class Accounts {
|
||||
}
|
||||
} else if (exception instanceof MicrosoftService.XBox400Exception) {
|
||||
return i18n("account.methods.microsoft.error.wrong_verify_method");
|
||||
} else if (exception instanceof MicrosoftService.NoMinecraftJavaEditionProfileException) {
|
||||
} else if (exception instanceof MicrosoftService.MinecraftJavaEditionLicenseNotFoundException) {
|
||||
return i18n("account.methods.microsoft.error.no_license");
|
||||
} else if (exception instanceof MicrosoftService.MinecraftJavaEditionProfileNotFoundException) {
|
||||
return i18n("account.methods.microsoft.error.no_character");
|
||||
} else if (exception instanceof MicrosoftService.NoXuiException) {
|
||||
return i18n("account.methods.microsoft.error.add_family");
|
||||
|
||||
@@ -45,7 +45,7 @@ public enum VersionIconType {
|
||||
public static VersionIconType getIconType(ModLoaderType modLoaderType) {
|
||||
return switch (modLoaderType) {
|
||||
case FORGE -> VersionIconType.FORGE;
|
||||
case NEO_FORGED -> VersionIconType.NEO_FORGE;
|
||||
case NEO_FORGE -> VersionIconType.NEO_FORGE;
|
||||
case FABRIC -> VersionIconType.FABRIC;
|
||||
case QUILT -> VersionIconType.QUILT;
|
||||
case LITE_LOADER -> VersionIconType.CHICKEN;
|
||||
|
||||
@@ -603,6 +603,20 @@ public final class VersionSetting implements Cloneable, Observable {
|
||||
processPriorityProperty.set(processPriority);
|
||||
}
|
||||
|
||||
private final ObjectProperty<GraphicsAPI> graphicsBackend = new SimpleObjectProperty<>(this, "graphicsBackend", GraphicsAPI.DEFAULT);
|
||||
|
||||
public ObjectProperty<GraphicsAPI> graphicsBackendProperty() {
|
||||
return graphicsBackend;
|
||||
}
|
||||
|
||||
public GraphicsAPI getGraphicsBackend() {
|
||||
return graphicsBackendProperty().get();
|
||||
}
|
||||
|
||||
public void setGraphicsBackend(GraphicsAPI api) {
|
||||
graphicsBackendProperty().set(api);
|
||||
}
|
||||
|
||||
private final ObjectProperty<Renderer> rendererProperty = new SimpleObjectProperty<>(this, "renderer", Renderer.DEFAULT);
|
||||
|
||||
public Renderer getRenderer() {
|
||||
@@ -820,8 +834,9 @@ public final class VersionSetting implements Cloneable, Observable {
|
||||
}
|
||||
obj.addProperty("java", java);
|
||||
|
||||
obj.addProperty("graphicsBackend", src.getGraphicsBackend().name());
|
||||
obj.addProperty("renderer", src.getRenderer().name());
|
||||
if (src.getRenderer() == Renderer.LLVMPIPE)
|
||||
if (src.getRenderer() == Renderer.OpenGL.LLVMPIPE)
|
||||
obj.addProperty("useSoftwareRenderer", true);
|
||||
|
||||
return obj;
|
||||
@@ -894,16 +909,19 @@ public final class VersionSetting implements Cloneable, Observable {
|
||||
}
|
||||
|
||||
vs.setRenderer(Optional.ofNullable(obj.get("renderer")).map(JsonElement::getAsString)
|
||||
.map(Renderer::of).orElseGet(() -> {
|
||||
boolean useSoftwareRenderer = Optional.ofNullable(obj.get("useSoftwareRenderer")).map(JsonElement::getAsBoolean).orElse(false);
|
||||
return useSoftwareRenderer ? Renderer.OpenGL.LLVMPIPE : Renderer.DEFAULT;
|
||||
}));
|
||||
|
||||
vs.setGraphicsBackend(Optional.ofNullable(obj.get("graphicsBackend")).map(JsonElement::getAsString)
|
||||
.flatMap(name -> {
|
||||
try {
|
||||
return Optional.of(Renderer.valueOf(name.toUpperCase(Locale.ROOT)));
|
||||
return Optional.of(GraphicsAPI.valueOf(name.toUpperCase(Locale.ROOT)));
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}).orElseGet(() -> {
|
||||
boolean useSoftwareRenderer = Optional.ofNullable(obj.get("useSoftwareRenderer")).map(JsonElement::getAsBoolean).orElse(false);
|
||||
return useSoftwareRenderer ? Renderer.LLVMPIPE : Renderer.DEFAULT;
|
||||
}));
|
||||
}).orElseGet(() -> vs.getRenderer() instanceof Renderer.Driver renderer ? renderer.api() : GraphicsAPI.DEFAULT));
|
||||
|
||||
return vs;
|
||||
}
|
||||
|
||||
@@ -71,9 +71,15 @@ public final class TerracottaBundle {
|
||||
return new Context() {
|
||||
@Override
|
||||
public void withResult(boolean success) {
|
||||
super.withResult(success);
|
||||
delegate.withResult(success);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() throws IOException {
|
||||
delegate.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] buffer, int offset, int len) throws IOException {
|
||||
context.checkCancellation();
|
||||
@@ -113,7 +119,7 @@ public final class TerracottaBundle {
|
||||
throw new ArtifactMalformedException(String.format("Expecting %s file in terracotta bundle.", file));
|
||||
}
|
||||
|
||||
MessageDigest digest = DigestUtils.getDigest(check.getAlgorithm());
|
||||
MessageDigest digest = DigestUtils.getDigest(check.algorithm());
|
||||
try (
|
||||
InputStream is = tree.getInputStream(archive);
|
||||
OutputStream os = new DigestOutputStream(Files.newOutputStream(path), digest)
|
||||
@@ -122,8 +128,8 @@ public final class TerracottaBundle {
|
||||
}
|
||||
|
||||
String hash = HexFormat.of().formatHex(digest.digest());
|
||||
if (!check.getChecksum().equalsIgnoreCase(hash)) {
|
||||
throw new ChecksumMismatchException(check.getAlgorithm(), check.getChecksum(), hash);
|
||||
if (!check.checksum().equalsIgnoreCase(hash)) {
|
||||
throw new ChecksumMismatchException(check.algorithm(), check.checksum(), hash);
|
||||
}
|
||||
|
||||
switch (OperatingSystem.CURRENT_OS) {
|
||||
@@ -172,7 +178,7 @@ public final class TerracottaBundle {
|
||||
return false;
|
||||
}
|
||||
|
||||
MessageDigest digest = DigestUtils.getDigest(check.getAlgorithm());
|
||||
MessageDigest digest = DigestUtils.getDigest(check.algorithm());
|
||||
try (InputStream is = new DigestInputStream(Files.newInputStream(path), digest)) {
|
||||
int n;
|
||||
while ((n = is.read(buffer)) >= 0) {
|
||||
@@ -182,7 +188,7 @@ public final class TerracottaBundle {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!HexFormat.of().formatHex(digest.digest()).equalsIgnoreCase(check.getChecksum())) {
|
||||
if (!HexFormat.of().formatHex(digest.digest()).equalsIgnoreCase(check.checksum())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,8 +36,9 @@ import org.glavo.monetfx.beans.property.ColorSchemeProperty;
|
||||
import org.glavo.monetfx.beans.property.ReadOnlyColorSchemeProperty;
|
||||
import org.glavo.monetfx.beans.property.SimpleColorSchemeProperty;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.ui.MacOSNativeUtils;
|
||||
import org.jackhuang.hmcl.ui.WindowsNativeUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.platform.NativeUtils;
|
||||
import org.jackhuang.hmcl.util.platform.OSVersion;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
@@ -233,6 +234,11 @@ public final class Themes {
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS && MacOSNativeUtils.isSupported()) {
|
||||
MacOSNativeUtils.setAppearance(darkModeProperty().get());
|
||||
|
||||
ChangeListener<Boolean> listener = FXUtils.onWeakChange(Themes.darkModeProperty(), MacOSNativeUtils::setAppearance);
|
||||
stage.getProperties().put("Themes.applyNativeDarkMode.listener", listener);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -70,6 +70,7 @@ import org.jackhuang.hmcl.util.i18n.SupportedLocale;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -105,9 +106,14 @@ public final class Controllers {
|
||||
GameListPage gameListPage = new GameListPage();
|
||||
gameListPage.selectedProfileProperty().bindBidirectional(Profiles.selectedProfileProperty());
|
||||
gameListPage.profilesProperty().bindContent(Profiles.profilesProperty());
|
||||
FXUtils.applyDragListener(gameListPage, ModpackHelper::isFileModpackByExtension, modpacks -> {
|
||||
Path modpack = modpacks.get(0);
|
||||
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(Profiles.getSelectedProfile(), modpack), i18n("install.modpack"));
|
||||
FXUtils.applyDragListener(gameListPage, file -> ModpackHelper.isFileModpackByExtension(file) || "json".equalsIgnoreCase(FileUtils.getNameWithoutExtension(file)), files -> {
|
||||
Path file = files.get(0);
|
||||
|
||||
if (ModpackHelper.isFileModpackByExtension(file)) {
|
||||
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(Profiles.getSelectedProfile(), file), i18n("install.modpack"));
|
||||
} else if ("json".equalsIgnoreCase(FileUtils.getExtension(file))) {
|
||||
Versions.installFromJson(Profiles.getSelectedProfile(), file);
|
||||
}
|
||||
});
|
||||
return gameListPage;
|
||||
});
|
||||
@@ -208,6 +214,21 @@ public final class Controllers {
|
||||
return decorator;
|
||||
}
|
||||
|
||||
public static void saveWindowStates() {
|
||||
if (stageX != null) {
|
||||
config().setX(stageX.get() / SCREEN.getBounds().getWidth());
|
||||
}
|
||||
if (stageY != null) {
|
||||
config().setY(stageY.get() / SCREEN.getBounds().getHeight());
|
||||
}
|
||||
if (stageHeight != null) {
|
||||
config().setHeight(stageHeight.get());
|
||||
}
|
||||
if (stageWidth != null) {
|
||||
config().setWidth(stageWidth.get());
|
||||
}
|
||||
}
|
||||
|
||||
public static void onApplicationStop() {
|
||||
stageSizeChangeListener = null;
|
||||
if (stageX != null) {
|
||||
@@ -370,7 +391,7 @@ public final class Controllers {
|
||||
Runnable continueAction = () -> globalConfig().setPlatformPromptVersion(1);
|
||||
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS && Architecture.SYSTEM_ARCH == Architecture.ARM64) {
|
||||
Controllers.dialog(i18n("fatal.unsupported_platform.macos_arm64"), null, MessageType.INFO, continueAction);
|
||||
continueAction.run();
|
||||
} else if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS && Architecture.SYSTEM_ARCH == Architecture.ARM64) {
|
||||
Controllers.dialog(i18n("fatal.unsupported_platform.windows_arm64"), null, MessageType.INFO, continueAction);
|
||||
} else if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX &&
|
||||
@@ -574,7 +595,7 @@ public final class Controllers {
|
||||
return pane.getCompletableFuture();
|
||||
}
|
||||
|
||||
public static TaskExecutorDialogPane taskDialog(TaskExecutor executor, String title, TaskCancellationAction onCancel) {
|
||||
public static TaskExecutorDialogPane taskDialog(TaskExecutor executor, String title, @NotNull TaskCancellationAction onCancel) {
|
||||
TaskExecutorDialogPane pane = new TaskExecutorDialogPane(onCancel);
|
||||
pane.setTitle(title);
|
||||
pane.setExecutor(executor);
|
||||
@@ -582,7 +603,7 @@ public final class Controllers {
|
||||
return pane;
|
||||
}
|
||||
|
||||
public static TaskExecutorDialogPane taskDialog(Task<?> task, String title, TaskCancellationAction onCancel) {
|
||||
public static TaskExecutorDialogPane taskDialog(Task<?> task, String title, @NotNull TaskCancellationAction onCancel) {
|
||||
TaskExecutor executor = task.executor();
|
||||
TaskExecutorDialogPane pane = taskDialog(executor, title, onCancel);
|
||||
executor.start();
|
||||
|
||||
@@ -62,6 +62,7 @@ import javafx.stage.Stage;
|
||||
import javafx.util.Callback;
|
||||
import javafx.util.Duration;
|
||||
import javafx.util.StringConverter;
|
||||
import org.glavo.url.WebURL;
|
||||
import org.jackhuang.hmcl.setting.StyleSheets;
|
||||
import org.jackhuang.hmcl.task.CacheFileTask;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
@@ -1166,12 +1167,10 @@ public final class FXUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static Image loadImage(String url) throws Exception {
|
||||
URI uri = NetworkUtils.toURI(url);
|
||||
|
||||
URLConnection connection = NetworkUtils.createConnection(uri);
|
||||
if (connection instanceof HttpURLConnection)
|
||||
connection = NetworkUtils.resolveConnection((HttpURLConnection) connection);
|
||||
public static Image loadImage(WebURL url) throws Exception {
|
||||
URLConnection connection = NetworkUtils.createConnection(url);
|
||||
if (connection instanceof HttpURLConnection httpConnection)
|
||||
connection = NetworkUtils.resolveConnection(httpConnection);
|
||||
|
||||
try (BufferedInputStream input = new BufferedInputStream(connection.getInputStream())) {
|
||||
String contentType = Objects.requireNonNull(connection.getContentType(), "");
|
||||
|
||||
@@ -37,6 +37,7 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class HTMLRenderer {
|
||||
private static final String INDENT = " ";
|
||||
private static URI resolveLink(Node linkNode) {
|
||||
String href = linkNode.absUrl("href");
|
||||
if (href.isEmpty())
|
||||
@@ -57,6 +58,7 @@ public final class HTMLRenderer {
|
||||
private boolean underline;
|
||||
private boolean strike;
|
||||
private boolean highlight;
|
||||
private int indentLevel;
|
||||
private String headerLevel;
|
||||
private Node hyperlink;
|
||||
|
||||
@@ -218,7 +220,7 @@ public final class HTMLRenderer {
|
||||
appendImage(node);
|
||||
break;
|
||||
case "li":
|
||||
appendText("\n \u2022 ");
|
||||
appendText("\n" + INDENT.repeat(indentLevel) + " \u2022 ");
|
||||
break;
|
||||
case "dt":
|
||||
appendText(" ");
|
||||
@@ -237,11 +239,14 @@ public final class HTMLRenderer {
|
||||
}
|
||||
|
||||
if (node.childNodeSize() > 0) {
|
||||
boolean isLiNode = "li".equals(name);
|
||||
if (isLiNode) indentLevel++;
|
||||
pushNode(node);
|
||||
for (Node childNode : node.childNodes()) {
|
||||
appendNode(childNode);
|
||||
}
|
||||
popNode();
|
||||
if (isLiNode) indentLevel--;
|
||||
}
|
||||
|
||||
switch (name) {
|
||||
|
||||
@@ -30,7 +30,6 @@ import javafx.geometry.Pos;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.stage.Stage;
|
||||
import org.jackhuang.hmcl.game.GameDumpGenerator;
|
||||
@@ -39,7 +38,6 @@ import org.jackhuang.hmcl.setting.StyleSheets;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.theme.Themes;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.NoneMultipleSelectionModel;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
import org.jackhuang.hmcl.util.CircularArrayList;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
@@ -170,6 +168,7 @@ public final class LogWindow extends Stage {
|
||||
LogWindowImpl() {
|
||||
getStyleClass().add("log-window");
|
||||
|
||||
listView.getStyleClass().add("no-horizontal-scrollbar");
|
||||
listView.getProperties().put("no-smooth-scrolling", true);
|
||||
listView.setItems(FXCollections.observableList(new CircularArrayList<>(logs.size())));
|
||||
|
||||
@@ -260,9 +259,6 @@ public final class LogWindow extends Stage {
|
||||
private static final PseudoClass INFO = PseudoClass.getPseudoClass("info");
|
||||
private static final PseudoClass DEBUG = PseudoClass.getPseudoClass("debug");
|
||||
private static final PseudoClass TRACE = PseudoClass.getPseudoClass("trace");
|
||||
private static final PseudoClass SELECTED = PseudoClass.getPseudoClass("selected");
|
||||
|
||||
private final Set<ListCell<Log>> selected = new HashSet<>();
|
||||
private final JFXSnackbar snackbar = new JFXSnackbar();
|
||||
|
||||
LogWindowSkin(LogWindowImpl control) {
|
||||
@@ -308,6 +304,7 @@ public final class LogWindow extends Stage {
|
||||
|
||||
{
|
||||
ListView<Log> listView = control.listView;
|
||||
listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
|
||||
listView.getItems().addListener((InvalidationListener) observable -> {
|
||||
if (!listView.getItems().isEmpty() && control.autoScroll.get())
|
||||
listView.scrollTo(listView.getItems().size() - 1);
|
||||
@@ -317,7 +314,6 @@ public final class LogWindow extends Stage {
|
||||
+ "\"; -fx-font-size: " + config().getFontSize() + "px;");
|
||||
listView.setCellFactory(x -> new ListCell<>() {
|
||||
{
|
||||
x.setSelectionModel(new NoneMultipleSelectionModel<>());
|
||||
getStyleClass().add("log-window-list-cell");
|
||||
Region clippedContainer = (Region) listView.lookup(".clipped-container");
|
||||
if (clippedContainer != null) {
|
||||
@@ -327,32 +323,6 @@ public final class LogWindow extends Stage {
|
||||
setPadding(new Insets(2));
|
||||
setWrapText(true);
|
||||
setGraphic(null);
|
||||
|
||||
setOnMouseClicked(event -> {
|
||||
if (event.getButton() != MouseButton.PRIMARY)
|
||||
return;
|
||||
|
||||
if (!event.isControlDown()) {
|
||||
for (ListCell<Log> logListCell : selected) {
|
||||
if (logListCell != this) {
|
||||
logListCell.pseudoClassStateChanged(SELECTED, false);
|
||||
if (logListCell.getItem() != null) {
|
||||
logListCell.getItem().setSelected(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
selected.clear();
|
||||
}
|
||||
|
||||
selected.add(this);
|
||||
pseudoClassStateChanged(SELECTED, true);
|
||||
if (getItem() != null) {
|
||||
getItem().setSelected(true);
|
||||
}
|
||||
|
||||
event.consume();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -366,7 +336,6 @@ public final class LogWindow extends Stage {
|
||||
pseudoClassStateChanged(INFO, !empty && item.getLevel() == Log4jLevel.INFO);
|
||||
pseudoClassStateChanged(DEBUG, !empty && item.getLevel() == Log4jLevel.DEBUG);
|
||||
pseudoClassStateChanged(TRACE, !empty && item.getLevel() == Log4jLevel.TRACE);
|
||||
pseudoClassStateChanged(SELECTED, !empty && item.isSelected());
|
||||
|
||||
if (empty) {
|
||||
setText(null);
|
||||
@@ -378,10 +347,13 @@ public final class LogWindow extends Stage {
|
||||
|
||||
listView.setOnKeyPressed(event -> {
|
||||
if (event.isControlDown() && event.getCode() == KeyCode.C) {
|
||||
if (listView.getSelectionModel().isEmpty())
|
||||
return;
|
||||
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
|
||||
for (Log item : listView.getItems()) {
|
||||
if (item != null && item.isSelected()) {
|
||||
for (Log item : listView.getSelectionModel().getSelectedItems()) {
|
||||
if (item != null) {
|
||||
if (item.getLog() != null)
|
||||
stringBuilder.append(item.getLog());
|
||||
stringBuilder.append('\n');
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* 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.ui;
|
||||
|
||||
import com.sun.jna.Pointer;
|
||||
import org.jackhuang.hmcl.util.platform.macos.ObjectiveCRuntime;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public final class MacOSNativeUtils {
|
||||
|
||||
private static final Pointer nsApp = init();
|
||||
|
||||
private static @Nullable Pointer init() {
|
||||
if (ObjectiveCRuntime.INSTANCE == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
var objc = ObjectiveCRuntime.INSTANCE;
|
||||
|
||||
Pointer nsApplication = objc.objc_getClass("NSApplication");
|
||||
if (!isNull(nsApplication)) {
|
||||
Pointer sharedSel = objc.sel_registerName("sharedApplication");
|
||||
if (!isNull(sharedSel))
|
||||
return objc.objc_msgSend(nsApplication, sharedSel);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
LOG.warning("Failed to initialize macOS appearance support", e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean isSupported() {
|
||||
return nsApp != null;
|
||||
}
|
||||
|
||||
private static boolean isNull(Pointer pointer) {
|
||||
return pointer == null || Pointer.nativeValue(pointer) == 0;
|
||||
}
|
||||
|
||||
public static void setAppearance(boolean dark) {
|
||||
setAppearance(dark, false);
|
||||
}
|
||||
|
||||
public static void setAppearance(boolean dark, boolean highContrast) {
|
||||
if (nsApp == null) return;
|
||||
|
||||
try {
|
||||
var objc = ObjectiveCRuntime.INSTANCE;
|
||||
|
||||
Pointer nsAppearance = objc.objc_getClass("NSAppearance");
|
||||
if (isNull(nsAppearance))
|
||||
return;
|
||||
|
||||
Pointer namedSel = objc.sel_registerName("appearanceNamed:");
|
||||
Pointer nsString = objc.objc_getClass("NSString");
|
||||
if (isNull(nsString)) return;
|
||||
|
||||
Pointer sel = objc.sel_registerName("stringWithUTF8String:");
|
||||
|
||||
String appearanceName;
|
||||
if (highContrast) {
|
||||
appearanceName = dark ? "NSAppearanceNameAccessibilityHighContrastDarkAqua" : "NSAppearanceNameAccessibilityHighContrastAqua";
|
||||
} else {
|
||||
appearanceName = dark ? "NSAppearanceNameDarkAqua" : "NSAppearanceNameAqua";
|
||||
}
|
||||
|
||||
Pointer appearanceNamePtr = objc.objc_msgSend(nsString, sel, appearanceName);
|
||||
if (isNull(appearanceNamePtr)) return;
|
||||
|
||||
Pointer appearance = objc.objc_msgSend(nsAppearance, namedSel, appearanceNamePtr);
|
||||
if (isNull(appearance)) return;
|
||||
|
||||
Pointer setSel = objc.sel_registerName("setAppearance:");
|
||||
objc.objc_msgSend(nsApp, setSel, appearance);
|
||||
} catch (Throwable t) {
|
||||
LOG.warning("Failed to set macOS appearance", t);
|
||||
}
|
||||
}
|
||||
|
||||
private MacOSNativeUtils() {
|
||||
}
|
||||
}
|
||||
@@ -62,6 +62,7 @@ public enum SVG {
|
||||
EXTENSION_FILL("M8.8 21H5q-.825 0-1.4125-.5875T3 19V15.2q1.2 0 2.1-.7625T6 12.5q0-1.175-.9-1.9375T3 9.8V6q0-.825.5875-1.4125T5 4H9q0-1.05.725-1.775T11.5 1.5q1.05 0 1.775.725T14 4h4q.825 0 1.4125.5875T20 6v4q1.05 0 1.775.725T22.5 12.5q0 1.05-.725 1.775T20 15v4q0 .825-.5875 1.4125T18 21H14.2q0-1.25-.7875-2.125T11.5 18q-1.125 0-1.9125.875T8.8 21Z"),
|
||||
FEEDBACK("M12 15Q12.425 15 12.7125 14.7125T13 14Q13 13.575 12.7125 13.2875T12 13Q11.575 13 11.2875 13.2875T11 14Q11 14.425 11.2875 14.7125T12 15ZM11 11H13V5H11V11ZM2 22V4Q2 3.175 2.5875 2.5875T4 2H20Q20.825 2 21.4125 2.5875T22 4V16Q22 16.825 21.4125 17.4125T20 18H6L2 22ZM5.15 16H20V4H4V17.125L5.15 16ZM4 16V4 16Z"),
|
||||
FEEDBACK_FILL("M2 22V4q0-.825.5875-1.4125T4 2H20q.825 0 1.4125.5875T22 4V16q0 .825-.5875 1.4125T20 18H6L2 22Zm10-7q.425 0 .7125-.2875T13 14t-.2875-.7125T12 13t-.7125.2875T11 14t.2875.7125T12 15Zm-1-4h2V5H11v6Z"),
|
||||
FILE_OPEN("M6 22q-0.825 0-1.4125-0.5875T4 20v-16q0-0.825 0.5875-1.4125T6 2h8l6 6v6h-2v-5H13v-5H6v16h9v2H6Zm15.95 0.375L19 19.425v2.225h-2v-5.65h5.65v2h-2.25l2.95 2.95-1.4 1.425Zm-15.95-2.375v-16 16Z"),
|
||||
FOLDER("M4 20Q3.175 20 2.5875 19.4125T2 18V6Q2 5.175 2.5875 4.5875T4 4H10L12 6H20Q20.825 6 21.4125 6.5875T22 8V18Q22 18.825 21.4125 19.4125T20 20H4ZM4 18H20V8H11.175L9.175 6H4V18ZM4 18V6 18Z"),
|
||||
FOLDER_COPY("M3 21Q2.175 21 1.5875 20.4125T1 19V6H3V19H20V21H3ZM7 17Q6.175 17 5.5875 16.4125T5 15V4Q5 3.175 5.5875 2.5875T7 2H12L14 4H21Q21.825 4 22.4125 4.5875T23 6V15Q23 15.825 22.4125 16.4125T21 17H7ZM7 15H21V6H13.175L11.175 4H7V15ZM7 15V4 15Z"),
|
||||
FOLDER_OPEN("M4 20Q3.175 20 2.5875 19.4125T2 18V6Q2 5.175 2.5875 4.5875T4 4H10L12 6H20Q20.825 6 21.4125 6.5875T22 8H11.175L9.175 6H4V18L6.4 10H23.5L20.925 18.575Q20.725 19.225 20.1875 19.6125T19 20H4ZM6.1 18H19L20.8 12H7.9L6.1 18ZM6.1 18 7.9 12 6.1 18ZM4 8V6 8Z"),
|
||||
|
||||
@@ -50,10 +50,10 @@ public final class UpgradeDialog extends JFXDialogLayout {
|
||||
setHeading(new Label(i18n("update.changelog")));
|
||||
setBody(new JFXSpinner());
|
||||
|
||||
String url = CHANGELOG_URL + remoteVersion.getChannel().channelName + ".html";
|
||||
String url = CHANGELOG_URL + remoteVersion.channel().channelName + ".html";
|
||||
|
||||
Task.supplyAsync(Schedulers.io(), () -> {
|
||||
VersionNumber targetVersion = VersionNumber.asVersion(remoteVersion.getVersion());
|
||||
VersionNumber targetVersion = VersionNumber.asVersion(remoteVersion.version());
|
||||
VersionNumber currentVersion = VersionNumber.asVersion(Metadata.VERSION);
|
||||
if (targetVersion.compareTo(currentVersion) <= 0)
|
||||
// Downgrade update, no need to display changelog
|
||||
|
||||
@@ -164,6 +164,9 @@ public class AccountListItem extends RadioButton {
|
||||
if (skinImg.isError()) {
|
||||
throw new InvalidSkinException("Failed to read skin image", skinImg.getException());
|
||||
}
|
||||
if (skinImg.getWidth() != 64 || (skinImg.getHeight() != 32 && skinImg.getHeight() != 64)) {
|
||||
throw new InvalidSkinException("Invalid skin size");
|
||||
}
|
||||
NormalizedSkin skin = new NormalizedSkin(skinImg);
|
||||
String model = skin.isSlim() ? "slim" : "";
|
||||
LOG.info("Uploading skin [" + selectedFile + "], model [" + model + "]");
|
||||
|
||||
@@ -115,7 +115,7 @@ public final class AddAuthlibInjectorServerPane extends TransitionPane implement
|
||||
GridPane.setRowIndex(lblServerName, 1);
|
||||
|
||||
lblServerWarning = new Label(i18n("account.injector.http"));
|
||||
lblServerWarning.setStyle("-fx-text-fill: red;");
|
||||
lblServerWarning.setStyle("-fx-text-fill: -monet-error;");
|
||||
GridPane.setColumnIndex(lblServerWarning, 0);
|
||||
GridPane.setRowIndex(lblServerWarning, 2);
|
||||
lblServerWarning.managedProperty().bind(lblServerWarning.visibleProperty());
|
||||
|
||||
@@ -303,7 +303,7 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
||||
for (String key : ALLOWED_LINKS) {
|
||||
String value = links.get(key);
|
||||
if (value != null) {
|
||||
Hyperlink link = new Hyperlink(i18n("account.injector.link." + key));
|
||||
JFXHyperlink link = new JFXHyperlink(i18n("account.injector.link." + key));
|
||||
FXUtils.installSlowTooltip(link, value);
|
||||
link.setOnAction(e -> FXUtils.openLink(value));
|
||||
result.add(link);
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXComboBox;
|
||||
import com.jfoenix.controls.JFXDialogLayout;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
import javafx.animation.PauseTransition;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.geometry.Insets;
|
||||
@@ -29,6 +30,7 @@ import javafx.scene.control.Label;
|
||||
import javafx.scene.input.DragEvent;
|
||||
import javafx.scene.input.TransferMode;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
|
||||
import org.jackhuang.hmcl.auth.offline.Skin;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.TextureModel;
|
||||
@@ -135,7 +137,9 @@ public class OfflineAccountSkinPane extends StackPane {
|
||||
capeSelector.setValue(account.getSkin().getLocalCapePath());
|
||||
}
|
||||
|
||||
skinBinding = FXUtils.observeWeak(() -> {
|
||||
PauseTransition pauseTransition = new PauseTransition(Duration.seconds(1));
|
||||
|
||||
Runnable loadSkin = () -> {
|
||||
getSkin().load(account.getUsername())
|
||||
.whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
if (exception != null) {
|
||||
@@ -157,6 +161,23 @@ public class OfflineAccountSkinPane extends StackPane {
|
||||
result.getCape() != null ? result.getCape().getImage() : null);
|
||||
}
|
||||
}).start();
|
||||
};
|
||||
|
||||
pauseTransition.setOnFinished(e -> loadSkin.run());
|
||||
|
||||
skinBinding = FXUtils.observeWeak(() -> {
|
||||
Skin.Type selectedType = skinItem.getSelectedData();
|
||||
|
||||
if (selectedType == Skin.Type.CUSTOM_SKIN_LOADER_API) {
|
||||
if (!cslApiField.validate()) {
|
||||
pauseTransition.stop();
|
||||
return;
|
||||
}
|
||||
pauseTransition.playFromStart();
|
||||
} else {
|
||||
pauseTransition.stop();
|
||||
loadSkin.run();
|
||||
}
|
||||
}, skinItem.selectedDataProperty(), cslApiField.textProperty(), modelCombobox.valueProperty(), skinSelector.valueProperty(), capeSelector.valueProperty());
|
||||
|
||||
FXUtils.onChangeAndOperate(skinItem.selectedDataProperty(), selectedData -> {
|
||||
|
||||
@@ -19,6 +19,8 @@ package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import javafx.animation.*;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
@@ -28,6 +30,7 @@ import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.theme.Themes;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
@@ -43,8 +46,6 @@ final class ComponentSublistWrapper extends VBox implements NoPaddingComponent {
|
||||
private boolean expanded = false;
|
||||
|
||||
ComponentSublistWrapper(ComponentSublist sublist) {
|
||||
boolean noPadding = !sublist.hasComponentPadding();
|
||||
|
||||
this.getStyleClass().add("options-sublist-wrapper");
|
||||
|
||||
Node expandIcon = SVG.KEYBOARD_ARROW_DOWN.createIcon(20);
|
||||
@@ -104,12 +105,26 @@ final class ComponentSublistWrapper extends VBox implements NoPaddingComponent {
|
||||
|
||||
if (container == null) {
|
||||
this.container = new VBox();
|
||||
|
||||
if (!noPadding) {
|
||||
container.setPadding(new Insets(8, 16, 10, 16));
|
||||
}
|
||||
this.container.getStyleClass().add("container");
|
||||
FXUtils.setLimitHeight(container, 0);
|
||||
FXUtils.setOverflowHidden(container);
|
||||
|
||||
Rectangle rectangle = FXUtils.setOverflowHidden(container);
|
||||
rectangle.getStyleClass().add("overflow-hidden");
|
||||
|
||||
var last = PseudoClass.getPseudoClass("last");
|
||||
|
||||
InvalidationListener updateArc = o -> {
|
||||
if (ComponentSublistWrapper.this.getPseudoClassStates().contains(last)) {
|
||||
rectangle.setArcHeight(4);
|
||||
rectangle.setArcWidth(4);
|
||||
} else {
|
||||
rectangle.setArcHeight(0);
|
||||
rectangle.setArcWidth(0);
|
||||
}
|
||||
};
|
||||
updateArc.invalidated(null);
|
||||
ComponentSublistWrapper.this.getPseudoClassStates().addListener(updateArc);
|
||||
|
||||
container.getChildren().setAll(sublist);
|
||||
ComponentSublistWrapper.this.getChildren().add(container);
|
||||
|
||||
@@ -120,8 +135,7 @@ final class ComponentSublistWrapper extends VBox implements NoPaddingComponent {
|
||||
}
|
||||
|
||||
Platform.runLater(() -> {
|
||||
// FIXME: ComponentSubList without padding must have a 4 pixel padding for displaying a border radius.
|
||||
double contentHeight = expanded ? (sublist.prefHeight(sublist.getWidth()) + (noPadding ? 4 : 8 + 10)) : 0;
|
||||
double contentHeight = expanded ? sublist.prefHeight(sublist.getWidth()) : 0;
|
||||
double targetRotate = expanded ? -180 : 0;
|
||||
|
||||
if (AnimationUtils.isAnimationEnabled()) {
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXPopup;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
@@ -30,6 +31,7 @@ import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.nio.file.Path;
|
||||
@@ -38,10 +40,18 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class FileSelector extends HBox {
|
||||
private final StringProperty value = new SimpleStringProperty();
|
||||
private String chooserTitle = i18n("selector.choose_file");
|
||||
private boolean directory = false;
|
||||
private String chooserTitle = "";
|
||||
private SelectionMode selectionMode = SelectionMode.FILE;
|
||||
private final ObservableList<FileChooser.ExtensionFilter> extensionFilters = FXCollections.observableArrayList();
|
||||
|
||||
JFXButton selectButton = FXUtils.newToggleButton4(SVG.FOLDER_OPEN, 15);
|
||||
|
||||
public enum SelectionMode {
|
||||
FILE,
|
||||
DIRECTORY,
|
||||
FILE_OR_DIRECTORY
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value.get();
|
||||
}
|
||||
@@ -63,12 +73,12 @@ public class FileSelector extends HBox {
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isDirectory() {
|
||||
return directory;
|
||||
public SelectionMode getSelectionMode() {
|
||||
return selectionMode;
|
||||
}
|
||||
|
||||
public FileSelector setDirectory(boolean directory) {
|
||||
this.directory = directory;
|
||||
public FileSelector setSelectionMode(SelectionMode selectionMode) {
|
||||
this.selectionMode = selectionMode;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -80,26 +90,20 @@ public class FileSelector extends HBox {
|
||||
JFXTextField customField = new JFXTextField();
|
||||
FXUtils.bindString(customField, valueProperty());
|
||||
|
||||
JFXButton selectButton = FXUtils.newToggleButton4(SVG.FOLDER_OPEN, 15);
|
||||
selectButton.setOnAction(e -> {
|
||||
if (directory) {
|
||||
DirectoryChooser chooser = new DirectoryChooser();
|
||||
chooser.setTitle(chooserTitle);
|
||||
Path dir = FileUtils.toPath(chooser.showDialog(Controllers.getStage()));
|
||||
if (dir != null) {
|
||||
String path = FileUtils.getAbsolutePath(dir);
|
||||
customField.setText(path);
|
||||
value.setValue(path);
|
||||
}
|
||||
} else {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.getExtensionFilters().addAll(getExtensionFilters());
|
||||
chooser.setTitle(chooserTitle);
|
||||
Path file = FileUtils.toPath(chooser.showOpenDialog(Controllers.getStage()));
|
||||
if (file != null) {
|
||||
String path = FileUtils.getAbsolutePath(file);
|
||||
customField.setText(path);
|
||||
value.setValue(path);
|
||||
switch (selectionMode) {
|
||||
case FILE -> openFileChooser(customField);
|
||||
case DIRECTORY -> openDirectoryChooser(customField);
|
||||
case FILE_OR_DIRECTORY -> {
|
||||
PopupMenu selectPopupMenu = new PopupMenu();
|
||||
JFXPopup selectModePopup = new JFXPopup(selectPopupMenu);
|
||||
|
||||
selectPopupMenu.getContent().addAll(
|
||||
new IconedMenuItem(SVG.FILE_OPEN, i18n("selector.choose_file"), () -> openFileChooser(customField), selectModePopup),
|
||||
new IconedMenuItem(SVG.FOLDER_OPEN, i18n("selector.choose_directory"), () -> openDirectoryChooser(customField), selectModePopup)
|
||||
);
|
||||
|
||||
selectModePopup.show(selectButton, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, -selectButton.getWidth(), 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -108,4 +112,27 @@ public class FileSelector extends HBox {
|
||||
setSpacing(3);
|
||||
getChildren().addAll(customField, selectButton);
|
||||
}
|
||||
|
||||
private void openFileChooser(JFXTextField customField) {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.getExtensionFilters().addAll(getExtensionFilters());
|
||||
chooser.setTitle(StringUtils.isBlank(chooserTitle) ? i18n("selector.choose_file") : chooserTitle);
|
||||
Path file = FileUtils.toPath(chooser.showOpenDialog(Controllers.getStage()));
|
||||
if (file != null) {
|
||||
String path = FileUtils.getAbsolutePath(file);
|
||||
customField.setText(path);
|
||||
value.setValue(path);
|
||||
}
|
||||
}
|
||||
|
||||
private void openDirectoryChooser(JFXTextField customField) {
|
||||
DirectoryChooser chooser = new DirectoryChooser();
|
||||
chooser.setTitle(StringUtils.isBlank(chooserTitle) ? i18n("selector.choose_directory") : chooserTitle);
|
||||
Path dir = FileUtils.toPath(chooser.showDialog(Controllers.getStage()));
|
||||
if (dir != null) {
|
||||
String path = FileUtils.getAbsolutePath(dir);
|
||||
customField.setText(path);
|
||||
value.setValue(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,8 +17,14 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.utils.JFXNodeUtils;
|
||||
import javafx.animation.Animation;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.NumberBinding;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.geometry.Orientation;
|
||||
import javafx.geometry.Point2D;
|
||||
import javafx.scene.Node;
|
||||
@@ -26,6 +32,10 @@ import javafx.scene.control.ScrollBar;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
||||
import org.jackhuang.hmcl.ui.animation.Motion;
|
||||
import org.jackhuang.hmcl.util.MathUtils;
|
||||
|
||||
// Referenced in root.css
|
||||
@@ -36,6 +46,10 @@ public class FloatScrollBarSkin implements Skin<ScrollBar> {
|
||||
private Rectangle track = new Rectangle();
|
||||
private Rectangle thumb = new Rectangle();
|
||||
|
||||
@SuppressWarnings({"FieldCanBeLocal", "FieldMayBeFinal"})
|
||||
private ChangeListener<Boolean> thumbHoverListener;
|
||||
private Animation thumbHoverAnimation;
|
||||
|
||||
public FloatScrollBarSkin(final ScrollBar scrollBar) {
|
||||
this.scrollBar = scrollBar;
|
||||
scrollBar.setPrefHeight(1e-18);
|
||||
@@ -45,8 +59,8 @@ public class FloatScrollBarSkin implements Skin<ScrollBar> {
|
||||
Point2D dragStart;
|
||||
double preDragThumbPos;
|
||||
|
||||
NumberBinding range = Bindings.subtract(scrollBar.maxProperty(), scrollBar.minProperty());
|
||||
NumberBinding position = Bindings.divide(Bindings.subtract(scrollBar.valueProperty(), scrollBar.minProperty()), range);
|
||||
final NumberBinding range = Bindings.subtract(scrollBar.maxProperty(), scrollBar.minProperty());
|
||||
final NumberBinding position = Bindings.divide(Bindings.subtract(scrollBar.valueProperty(), scrollBar.minProperty()), range);
|
||||
|
||||
{
|
||||
// Children are added unmanaged because for some reason the height of the bar keeps changing
|
||||
@@ -63,7 +77,6 @@ public class FloatScrollBarSkin implements Skin<ScrollBar> {
|
||||
|
||||
setup();
|
||||
|
||||
|
||||
thumb.setOnMousePressed(me -> {
|
||||
if (me.isSynthesized()) {
|
||||
// touch-screen events handled by Scroll handler
|
||||
@@ -128,14 +141,18 @@ public class FloatScrollBarSkin implements Skin<ScrollBar> {
|
||||
track.widthProperty().unbind();
|
||||
track.heightProperty().unbind();
|
||||
|
||||
double offset = 4;
|
||||
|
||||
if (scrollBar.getOrientation() == Orientation.HORIZONTAL) {
|
||||
track.relocate(0, -8);
|
||||
track.widthProperty().bind(scrollBar.widthProperty());
|
||||
track.setHeight(8);
|
||||
track.relocate(offset, -6.0);
|
||||
NumberBinding trackWidth = Bindings.max(0, Bindings.subtract(scrollBar.widthProperty(), offset * 2));
|
||||
track.widthProperty().bind(trackWidth);
|
||||
track.setHeight(6.0);
|
||||
} else {
|
||||
track.relocate(-8, 0);
|
||||
track.setWidth(8);
|
||||
track.heightProperty().bind(scrollBar.heightProperty());
|
||||
track.relocate(-6.0, offset);
|
||||
track.setWidth(6.0);
|
||||
NumberBinding trackHeight = Bindings.max(0, Bindings.subtract(scrollBar.heightProperty(), offset * 2));
|
||||
track.heightProperty().bind(trackHeight);
|
||||
}
|
||||
|
||||
thumb.xProperty().unbind();
|
||||
@@ -144,15 +161,37 @@ public class FloatScrollBarSkin implements Skin<ScrollBar> {
|
||||
thumb.heightProperty().unbind();
|
||||
|
||||
if (scrollBar.getOrientation() == Orientation.HORIZONTAL) {
|
||||
thumb.relocate(0, -8);
|
||||
thumb.widthProperty().bind(Bindings.max(20, scrollBar.visibleAmountProperty().divide(range).multiply(scrollBar.widthProperty())));
|
||||
thumb.setHeight(8);
|
||||
thumb.xProperty().bind(Bindings.subtract(scrollBar.widthProperty(), thumb.widthProperty()).multiply(position));
|
||||
thumb.relocate(0, -6.0);
|
||||
thumb.setHeight(6.0);
|
||||
|
||||
NumberBinding trackWidth = Bindings.max(0, Bindings.subtract(scrollBar.widthProperty(), offset * 2));
|
||||
|
||||
thumb.widthProperty().bind(Bindings.min(trackWidth,
|
||||
Bindings.max(20, scrollBar.visibleAmountProperty().divide(range).multiply(trackWidth))));
|
||||
|
||||
thumb.xProperty().bind(
|
||||
Bindings.add(offset,
|
||||
Bindings.multiply(
|
||||
Bindings.max(0, Bindings.subtract(trackWidth, thumb.widthProperty())),
|
||||
position)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
thumb.relocate(-8, 0);
|
||||
thumb.setWidth(8);
|
||||
thumb.heightProperty().bind(Bindings.max(20, scrollBar.visibleAmountProperty().divide(range).multiply(scrollBar.heightProperty())));
|
||||
thumb.yProperty().bind(Bindings.subtract(scrollBar.heightProperty(), thumb.heightProperty()).multiply(position));
|
||||
thumb.relocate(-6.0, 0);
|
||||
thumb.setWidth(6.0);
|
||||
|
||||
NumberBinding trackHeight = Bindings.max(0, Bindings.subtract(scrollBar.heightProperty(), offset * 2));
|
||||
|
||||
thumb.heightProperty().bind(Bindings.min(trackHeight,
|
||||
Bindings.max(20, scrollBar.visibleAmountProperty().divide(range).multiply(trackHeight))));
|
||||
|
||||
thumb.yProperty().bind(
|
||||
Bindings.add(offset,
|
||||
Bindings.multiply(
|
||||
Bindings.max(0, Bindings.subtract(trackHeight, thumb.heightProperty())),
|
||||
position)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,7 +201,7 @@ public class FloatScrollBarSkin implements Skin<ScrollBar> {
|
||||
return Double.MAX_VALUE;
|
||||
}
|
||||
|
||||
return 8;
|
||||
return 6.0;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -171,13 +210,46 @@ public class FloatScrollBarSkin implements Skin<ScrollBar> {
|
||||
return Double.MAX_VALUE;
|
||||
}
|
||||
|
||||
return 8;
|
||||
return 6.0;
|
||||
}
|
||||
};
|
||||
|
||||
this.thumbHoverListener = FXUtils.onWeakChangeAndOperate(thumb.hoverProperty(), newValue -> {
|
||||
if (thumbHoverAnimation != null) {
|
||||
thumbHoverAnimation.stop();
|
||||
thumbHoverAnimation = null;
|
||||
}
|
||||
|
||||
double targetOpacity = newValue ? 1.0 : 0.5;
|
||||
double currentOpacity = thumb.getOpacity();
|
||||
|
||||
double opacityAdjustment = targetOpacity - currentOpacity;
|
||||
if (Math.abs(opacityAdjustment) < 0.001) {
|
||||
if (opacityAdjustment != 0)
|
||||
thumb.setOpacity(targetOpacity);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (AnimationUtils.isAnimationEnabled() && JFXNodeUtils.isTreeVisible(thumb)) {
|
||||
thumbHoverAnimation = new Timeline(
|
||||
new KeyFrame(Duration.ZERO, new KeyValue(thumb.opacityProperty(), currentOpacity)),
|
||||
new KeyFrame(Motion.SHORT2, new KeyValue(thumb.opacityProperty(), targetOpacity))
|
||||
);
|
||||
thumbHoverAnimation.play();
|
||||
} else {
|
||||
thumb.setOpacity(targetOpacity);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
if (thumbHoverAnimation != null) {
|
||||
thumbHoverAnimation.stop();
|
||||
thumbHoverAnimation = null;
|
||||
}
|
||||
thumbHoverListener = null;
|
||||
scrollBar = null;
|
||||
group = null;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXDialogLayout;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
import com.jfoenix.validation.base.ValidatorBase;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
@@ -56,6 +57,7 @@ public class InputDialogPane extends JFXDialogLayout implements DialogAware {
|
||||
this.setBody(new VBox(textField));
|
||||
|
||||
lblCreationWarning = new Label();
|
||||
lblCreationWarning.setPadding(new Insets(0, 5, 0, 0));
|
||||
|
||||
acceptPane = new SpinnerPane();
|
||||
acceptPane.getStyleClass().add("small-spinner-pane");
|
||||
|
||||
@@ -20,14 +20,12 @@ package org.jackhuang.hmcl.ui.construct;
|
||||
import com.jfoenix.controls.JFXPopup;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.ListProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleListProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.input.*;
|
||||
import javafx.scene.layout.StackPane;
|
||||
@@ -35,6 +33,8 @@ import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.UnknownNullability;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
@@ -43,24 +43,22 @@ import java.util.function.Function;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.determineOptimalPopupPosition;
|
||||
|
||||
/// @author Glavo
|
||||
public class LineSelectButton<T> extends LineButton {
|
||||
public class LineSelectButton<T extends @UnknownNullability Object> extends LineButton {
|
||||
|
||||
private static final String DEFAULT_STYLE_CLASS = "line-select-button";
|
||||
private static final PseudoClass SELECTED_PSEUDO_CLASS = PseudoClass.getPseudoClass("selected");
|
||||
|
||||
private JFXPopup popup;
|
||||
@SuppressWarnings({"FieldCanBeLocal", "unused"})
|
||||
private ObservableList<Node> popupItems; // keep a reference
|
||||
|
||||
public LineSelectButton() {
|
||||
this.getStyleClass().add(DEFAULT_STYLE_CLASS);
|
||||
|
||||
InvalidationListener updateTrailingText = observable -> {
|
||||
Function<T, String> converter = getConverter();
|
||||
T value = getValue();
|
||||
if (value != null) {
|
||||
Function<T, String> converter = getConverter();
|
||||
setTrailingText(converter != null ? converter.apply(value) : value.toString());
|
||||
} else {
|
||||
setTrailingText(null);
|
||||
}
|
||||
setTrailingText(converter != null ? converter.apply(value) : Objects.toString(value, ""));
|
||||
};
|
||||
converterProperty().addListener(updateTrailingText);
|
||||
valueProperty().addListener(updateTrailingText);
|
||||
@@ -87,15 +85,12 @@ public class LineSelectButton<T> extends LineButton {
|
||||
|
||||
ripplerContainer.addEventFilter(ScrollEvent.ANY, ignored -> popup.hide());
|
||||
|
||||
Bindings.bindContent(popupMenu.getContent(), MappedObservableList.create(itemsProperty(), item -> {
|
||||
Bindings.bindContent(popupMenu.getContent(), popupItems = MappedObservableList.create(itemsProperty(), item -> {
|
||||
VBox vbox = new VBox();
|
||||
|
||||
var itemTitleLabel = new Label();
|
||||
itemTitleLabel.getStyleClass().add("title-label");
|
||||
itemTitleLabel.textProperty().bind(Bindings.createStringBinding(() -> {
|
||||
if (item == null)
|
||||
return "";
|
||||
|
||||
Function<T, String> converter = getConverter();
|
||||
return converter != null ? converter.apply(item) : Objects.toString(item, "");
|
||||
}, converterProperty()));
|
||||
@@ -170,10 +165,14 @@ public class LineSelectButton<T> extends LineButton {
|
||||
return converterProperty().get();
|
||||
}
|
||||
|
||||
public void setConverter(Function<T, String> value) {
|
||||
public void setConverter(Function<T, String> value) { // TODO: rename
|
||||
converterProperty().set(value);
|
||||
}
|
||||
|
||||
public void setNullSafeConverter(Function<@NotNull T, String> value) {
|
||||
converterProperty().set(it -> it != null ? value.apply(it) : "");
|
||||
}
|
||||
|
||||
private ObjectProperty<Function<T, String>> descriptionConverter;
|
||||
|
||||
public ObjectProperty<Function<T, String>> descriptionConverterProperty() {
|
||||
|
||||
@@ -19,8 +19,8 @@ package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import org.jackhuang.hmcl.theme.Themes;
|
||||
|
||||
public class MenuSeparator extends StackPane {
|
||||
|
||||
@@ -28,7 +28,7 @@ public class MenuSeparator extends StackPane {
|
||||
Rectangle rect = new Rectangle();
|
||||
rect.widthProperty().bind(widthProperty().add(-14));
|
||||
rect.setHeight(1);
|
||||
rect.setFill(Color.GRAY);
|
||||
rect.fillProperty().bind(Themes.colorSchemeProperty().getOutlineVariant());
|
||||
maxHeightProperty().set(10);
|
||||
setPadding(new Insets(3));
|
||||
getChildren().setAll(rect);
|
||||
|
||||
@@ -55,7 +55,8 @@ public final class MultiFileItem<T> extends VBox {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public MultiFileItem() {
|
||||
setPadding(new Insets(0, 0, 10, 0));
|
||||
this.getStyleClass().add("multi-file-item");
|
||||
|
||||
setSpacing(8);
|
||||
|
||||
group.selectedToggleProperty().addListener((a, b, newValue) -> {
|
||||
@@ -266,8 +267,8 @@ public final class MultiFileItem<T> extends VBox {
|
||||
selector.setValue(value);
|
||||
}
|
||||
|
||||
public FileOption<T> setDirectory(boolean directory) {
|
||||
selector.setDirectory(directory);
|
||||
public FileOption<T> setSelectionMode(FileSelector.SelectionMode selectionMode) {
|
||||
selector.setSelectionMode(selectionMode);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -81,12 +81,11 @@ public class TaskExecutorDialogPane extends BorderPane {
|
||||
|
||||
setCancel(cancel);
|
||||
|
||||
btnCancel.setDisable(onCancel.getCancellationAction() == null);
|
||||
btnCancel.setOnAction(e -> {
|
||||
if (onCancel.getCancellationAction() != null) {
|
||||
if (executor != null)
|
||||
executor.cancel();
|
||||
onCancel.getCancellationAction().accept(this);
|
||||
}
|
||||
if (executor != null)
|
||||
executor.cancel();
|
||||
onCancel.getCancellationAction().accept(this);
|
||||
});
|
||||
|
||||
speedEventHandler = FetchTask.SPEED_EVENT.registerWeak(speedEvent -> {
|
||||
|
||||
@@ -30,7 +30,6 @@ import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
|
||||
public class TwoLineListItem extends VBox {
|
||||
private static final String DEFAULT_STYLE_CLASS = "two-line-list-item";
|
||||
@@ -147,6 +146,10 @@ public class TwoLineListItem extends VBox {
|
||||
subtitleProperty().set(subtitle);
|
||||
}
|
||||
|
||||
public HBox getFirstLine() {
|
||||
return firstLine;
|
||||
}
|
||||
|
||||
public Label getTitleLabel() {
|
||||
return lblTitle;
|
||||
}
|
||||
@@ -168,11 +171,23 @@ public class TwoLineListItem extends VBox {
|
||||
Bindings.bindContent(tagsBox.getChildren(), tags);
|
||||
|
||||
var scrollPane = new ScrollPane(tagsBox);
|
||||
scrollPane.setMinSize(0, 0);
|
||||
HBox.setHgrow(scrollPane, Priority.ALWAYS);
|
||||
lblTitle.setMinWidth(Label.USE_PREF_SIZE);
|
||||
scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||
scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||
FXUtils.onChangeAndOperate(tagsBox.heightProperty(), height -> FXUtils.setLimitHeight(scrollPane, height.doubleValue()));
|
||||
var expectedHeight = Bindings.createDoubleBinding(
|
||||
() -> {
|
||||
if (tags.isEmpty()) return 0.0;
|
||||
double h = tagsBox.prefHeight(-1);
|
||||
return h > 0 ? h : Double.NaN;
|
||||
},
|
||||
tags, tagsBox.heightProperty()
|
||||
);
|
||||
|
||||
scrollPane.minHeightProperty().bind(expectedHeight);
|
||||
scrollPane.prefHeightProperty().bind(expectedHeight);
|
||||
scrollPane.maxHeightProperty().bind(expectedHeight);
|
||||
firstLine.getChildren().setAll(lblTitle, scrollPane);
|
||||
|
||||
tags.addListener((InvalidationListener) ignored -> scrollPane.requestLayout());
|
||||
|
||||
@@ -20,8 +20,8 @@ package org.jackhuang.hmcl.ui.construct;
|
||||
import com.jfoenix.validation.base.ValidatorBase;
|
||||
import javafx.beans.NamedArg;
|
||||
import javafx.scene.control.TextInputControl;
|
||||
import org.glavo.url.WebURL;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
@@ -54,12 +54,7 @@ public class URLValidator extends ValidatorBase {
|
||||
if (StringUtils.isBlank(textField.getText()))
|
||||
hasErrors.set(!nullable);
|
||||
else {
|
||||
try {
|
||||
NetworkUtils.toURI(textField.getText());
|
||||
hasErrors.set(false);
|
||||
} catch (IllegalArgumentException e) {
|
||||
hasErrors.set(true);
|
||||
}
|
||||
hasErrors.set(WebURL.tryParseBrowserInput(textField.getText()) == null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,7 @@ import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.Paint;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.Duration;
|
||||
import org.glavo.url.WebURL;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDnD;
|
||||
import org.jackhuang.hmcl.setting.EnumBackgroundImage;
|
||||
@@ -205,7 +206,7 @@ public class DecoratorController {
|
||||
String backgroundImageUrl = config().getBackgroundImageUrl();
|
||||
if (backgroundImageUrl != null) {
|
||||
try {
|
||||
image = FXUtils.loadImage(backgroundImageUrl);
|
||||
image = FXUtils.loadImage(WebURL.parseBrowserInput(backgroundImageUrl));
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Couldn't load background image", e);
|
||||
}
|
||||
|
||||
@@ -274,6 +274,7 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
|
||||
if (canBack) {
|
||||
JFXButton backNavButton = new JFXButton();
|
||||
skinnable.forbidDraggingWindow(backNavButton);
|
||||
backNavButton.setFocusTraversable(false);
|
||||
backNavButton.setGraphic(SVG.ARROW_BACK.createIcon(Themes.titleFillProperty()));
|
||||
backNavButton.getStyleClass().add("jfx-decorator-button");
|
||||
@@ -285,6 +286,7 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
|
||||
if (canClose) {
|
||||
JFXButton closeNavButton = new JFXButton();
|
||||
skinnable.forbidDraggingWindow(closeNavButton);
|
||||
closeNavButton.setFocusTraversable(false);
|
||||
closeNavButton.setGraphic(SVG.CLOSE.createIcon(Themes.titleFillProperty()));
|
||||
closeNavButton.getStyleClass().add("jfx-decorator-button");
|
||||
@@ -349,13 +351,9 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
refreshNavButton.setGraphic(SVG.REFRESH.createIcon(Themes.titleFillProperty()));
|
||||
refreshNavButton.getStyleClass().add("jfx-decorator-button");
|
||||
refreshNavButton.onActionProperty().bind(skinnable.onRefreshNavButtonActionProperty());
|
||||
skinnable.forbidDraggingWindow(refreshNavButton);
|
||||
|
||||
Rectangle separator = new Rectangle();
|
||||
separator.visibleProperty().bind(refreshNavButton.visibleProperty());
|
||||
separator.heightProperty().bind(navBar.heightProperty());
|
||||
separator.setFill(Color.GRAY);
|
||||
|
||||
navRight.getChildren().setAll(refreshNavButton, separator);
|
||||
navRight.getChildren().setAll(refreshNavButton);
|
||||
navBar.setRight(navRight);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,13 +54,18 @@ import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
private final ReadOnlyObjectWrapper<DecoratorPage.State> state = new ReadOnlyObjectWrapper<>(DecoratorPage.State.fromTitle(i18n("download"), -1));
|
||||
@@ -134,6 +139,19 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
|
||||
Path runDirectory = profile.getRepository().hasVersion(version) ? profile.getRepository().getRunDirectory(version) : profile.getRepository().getBaseDirectory();
|
||||
|
||||
Set<String> existingFiles;
|
||||
|
||||
try (var list = Files.list(runDirectory.resolve(subdirectoryName))) {
|
||||
existingFiles = list.map(Path::getFileName)
|
||||
.map(Path::toString)
|
||||
.collect(Collectors.toSet());
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to list files in " + runDirectory.resolve(subdirectoryName), e);
|
||||
existingFiles = Set.of();
|
||||
}
|
||||
|
||||
Set<String> finalExistingFiles = existingFiles;
|
||||
|
||||
Controllers.prompt(i18n("archive.file.name"), (result, handler) -> {
|
||||
Path dest = runDirectory.resolve(subdirectoryName).resolve(result);
|
||||
|
||||
@@ -153,7 +171,7 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
}
|
||||
}), i18n("message.downloading"), TaskCancellationAction.NORMAL);
|
||||
handler.resolve();
|
||||
}, file.getFile().getFilename(), new Validator(i18n("install.new_game.malformed"), FileUtils::isNameValid));
|
||||
}, file.getFile().getFilename(), new Validator(i18n("install.new_game.malformed"), FileUtils::isNameValidForJar), new Validator(i18n("profile.already_exists"), (it) -> !finalExistingFiles.contains(it)));
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -97,8 +97,7 @@ public final class ModpackSelectionPage extends VBox implements WizardPage {
|
||||
graphic.setMouseTransparent(true);
|
||||
graphic.setLeft(new TwoLineListItem(i18n("modpack.choose." + type), i18n("modpack.choose." + type + ".detail")));
|
||||
|
||||
SVGPath arrow = new SVGPath();
|
||||
arrow.setContent(SVG.ARROW_FORWARD.getPath());
|
||||
SVGPath arrow = SVG.ARROW_FORWARD.createIcon();
|
||||
BorderPane.setAlignment(arrow, Pos.CENTER);
|
||||
graphic.setRight(arrow);
|
||||
|
||||
|
||||
@@ -423,7 +423,11 @@ public final class VersionsPage extends Control implements WizardPage, Refreshab
|
||||
if (status == Status.LOADING)
|
||||
transitionPane.setContent(spinner, ContainerAnimations.FADE);
|
||||
else if (status == Status.SUCCESS)
|
||||
transitionPane.setContent(centerWrapper, ContainerAnimations.FADE);
|
||||
if (control.versions.isEmpty()) {
|
||||
transitionPane.setContent(emptyPane, ContainerAnimations.FADE);
|
||||
} else {
|
||||
transitionPane.setContent(centerWrapper, ContainerAnimations.FADE);
|
||||
}
|
||||
else // if (status == Status.FAILED)
|
||||
transitionPane.setContent(failedPane, ContainerAnimations.FADE);
|
||||
});
|
||||
|
||||
@@ -214,7 +214,7 @@ public final class ExportWizardProvider implements WizardProvider {
|
||||
vs.isFullscreen(),
|
||||
vs.getWidth(),
|
||||
vs.getHeight(),
|
||||
vs.getMaxMemory(),
|
||||
null,
|
||||
exportInfo.getMinMemory(),
|
||||
vs.isShowLogs(),
|
||||
/* showConsoleOnError */ true,
|
||||
|
||||
@@ -316,7 +316,7 @@ public final class ModpackInfoPage extends Control implements WizardPage {
|
||||
if (skinnable.options.isRequireAuthlibInjectorServer()) {
|
||||
var serversSelectButton = new LineSelectButton<AuthlibInjectorServer>();
|
||||
serversSelectButton.setTitle(i18n("account.injector.server"));
|
||||
serversSelectButton.setConverter(AuthlibInjectorServer::getName);
|
||||
serversSelectButton.setNullSafeConverter(AuthlibInjectorServer::getName);
|
||||
serversSelectButton.setDescriptionConverter(AuthlibInjectorServer::getUrl);
|
||||
serversSelectButton.itemsProperty().set(config().getAuthlibInjectorServers());
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.image;
|
||||
|
||||
import com.twelvemonkeys.imageio.plugins.webp.WebPImageReaderSpi;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.SnapshotParameters;
|
||||
@@ -29,6 +28,9 @@ import org.girod.javafx.svgimage.LoaderParameters;
|
||||
import org.girod.javafx.svgimage.SVGImage;
|
||||
import org.girod.javafx.svgimage.SVGLoader;
|
||||
import org.girod.javafx.svgimage.ScaleQuality;
|
||||
import org.glavo.webp.WebPImage;
|
||||
import org.glavo.webp.WebPImageLoadOptions;
|
||||
import org.glavo.webp.javafx.WebPFXImage;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.ui.image.apng.Png;
|
||||
import org.jackhuang.hmcl.ui.image.apng.argb8888.Argb8888Bitmap;
|
||||
@@ -38,13 +40,8 @@ import org.jackhuang.hmcl.ui.image.apng.chunks.PngFrameControl;
|
||||
import org.jackhuang.hmcl.ui.image.apng.error.PngException;
|
||||
import org.jackhuang.hmcl.ui.image.apng.error.PngIntegrityException;
|
||||
import org.jackhuang.hmcl.ui.image.internal.AnimationImageImpl;
|
||||
import org.jackhuang.hmcl.util.SwingFXUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -71,16 +68,8 @@ public final class ImageUtils {
|
||||
};
|
||||
|
||||
public static final ImageLoader WEBP = (input, requestedWidth, requestedHeight, preserveRatio, smooth) -> {
|
||||
WebPImageReaderSpi spi = new WebPImageReaderSpi();
|
||||
ImageReader reader = spi.createReaderInstance(null);
|
||||
BufferedImage bufferedImage;
|
||||
try (ImageInputStream imageInput = ImageIO.createImageInputStream(input)) {
|
||||
reader.setInput(imageInput, true, true);
|
||||
bufferedImage = reader.read(0, reader.getDefaultReadParam());
|
||||
} finally {
|
||||
reader.dispose();
|
||||
}
|
||||
return SwingFXUtils.toFXImage(bufferedImage, requestedWidth, requestedHeight, preserveRatio, smooth);
|
||||
var options = new WebPImageLoadOptions(requestedWidth, requestedHeight, preserveRatio, smooth);
|
||||
return new WebPFXImage(WebPImage.read(input, options));
|
||||
};
|
||||
|
||||
public static final ImageLoader SVG = (input, requestedWidth, requestedHeight, preserveRatio, smooth) -> {
|
||||
@@ -468,6 +457,19 @@ public final class ImageUtils {
|
||||
return new AnimationImageImpl(width, height, framePixels, durations, cycleCount);
|
||||
}
|
||||
|
||||
private static int[] rgbaToArgb(ByteBuffer rgba) {
|
||||
int pixelCount = rgba.remaining() / 4;
|
||||
int[] argb = new int[pixelCount];
|
||||
for (int i = 0; i < pixelCount; i++) {
|
||||
int r = rgba.get() & 0xFF;
|
||||
int g = rgba.get() & 0xFF;
|
||||
int b = rgba.get() & 0xFF;
|
||||
int a = rgba.get() & 0xFF;
|
||||
argb[i] = (a << 24) | (r << 16) | (g << 8) | b;
|
||||
}
|
||||
return argb;
|
||||
}
|
||||
|
||||
private ImageUtils() {
|
||||
}
|
||||
}
|
||||
@@ -72,7 +72,7 @@ public final class AboutPage extends SpinnerPane {
|
||||
|
||||
ComponentList legal = new ComponentList();
|
||||
{
|
||||
var copyright = LineButton.createExternalLinkButton(Metadata.ABOUT_URL);
|
||||
var copyright = new LineButton();
|
||||
copyright.setLargeTitle(true);
|
||||
copyright.setTitle(i18n("about.copyright"));
|
||||
copyright.setSubtitle(i18n("about.copyright.statement"));
|
||||
|
||||
@@ -27,14 +27,20 @@ import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.layout.*;
|
||||
import org.jackhuang.hmcl.setting.DownloadProviders;
|
||||
import org.jackhuang.hmcl.setting.EnumCommonDirectory;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
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.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.SafeStringConverter;
|
||||
|
||||
import java.net.Proxy;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Function;
|
||||
|
||||
@@ -72,7 +78,7 @@ public class DownloadSettingsPage extends StackPane {
|
||||
var versionListSourcePane = new LineSelectButton<String>();
|
||||
versionListSourcePane.disableProperty().bind(autoChooseDownloadSource.selectedProperty().not());
|
||||
versionListSourcePane.setTitle(i18n("settings.launcher.version_list_source"));
|
||||
versionListSourcePane.setConverter(converter);
|
||||
versionListSourcePane.setNullSafeConverter(converter);
|
||||
versionListSourcePane.setDescriptionConverter(descriptionConverter);
|
||||
versionListSourcePane.setItems(DownloadProviders.AUTO_PROVIDERS.keySet());
|
||||
versionListSourcePane.valueProperty().bindBidirectional(config().versionListSourceProperty());
|
||||
@@ -80,14 +86,14 @@ public class DownloadSettingsPage extends StackPane {
|
||||
var downloadSourcePane = new LineSelectButton<String>();
|
||||
downloadSourcePane.disableProperty().bind(autoChooseDownloadSource.selectedProperty());
|
||||
downloadSourcePane.setTitle(i18n("settings.launcher.download_source"));
|
||||
downloadSourcePane.setConverter(converter);
|
||||
downloadSourcePane.setNullSafeConverter(converter);
|
||||
downloadSourcePane.setDescriptionConverter(descriptionConverter);
|
||||
downloadSourcePane.setItems(DownloadProviders.DIRECT_PROVIDERS.keySet());
|
||||
downloadSourcePane.valueProperty().bindBidirectional(config().downloadTypeProperty());
|
||||
|
||||
var defaultAddonSourcePane = new LineSelectButton<String>();
|
||||
defaultAddonSourcePane.setTitle(i18n("settings.launcher.default_addon_source"));
|
||||
defaultAddonSourcePane.setConverter(key -> I18n.i18n("mods." + key));
|
||||
defaultAddonSourcePane.setNullSafeConverter(key -> I18n.i18n("mods." + key));
|
||||
defaultAddonSourcePane.setItems("modrinth", "curseforge");
|
||||
defaultAddonSourcePane.valueProperty().bindBidirectional(config().defaultAddonSourceProperty());
|
||||
|
||||
@@ -98,9 +104,37 @@ public class DownloadSettingsPage extends StackPane {
|
||||
}
|
||||
|
||||
{
|
||||
var downloadList = new ComponentList();
|
||||
|
||||
VBox downloadThreads = new VBox(16);
|
||||
downloadThreads.getStyleClass().add("card-non-transparent");
|
||||
|
||||
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(config().commonDirectoryProperty())
|
||||
));
|
||||
fileCommonLocation.selectedDataProperty().bindBidirectional(config().commonDirTypeProperty());
|
||||
|
||||
fileCommonLocationSublist.getContent().add(fileCommonLocation);
|
||||
fileCommonLocationSublist.setTitle(i18n("launcher.cache_directory"));
|
||||
fileCommonLocationSublist.setHasSubtitle(true);
|
||||
fileCommonLocationSublist.subtitleProperty().bind(
|
||||
Bindings.createObjectBinding(() -> Optional.ofNullable(Settings.instance().getCommonDirectory())
|
||||
.orElse(i18n("launcher.cache_directory.disabled")),
|
||||
config().commonDirectoryProperty(), config().commonDirTypeProperty()));
|
||||
|
||||
JFXButton cleanButton = FXUtils.newBorderButton(i18n("launcher.cache_directory.clean"));
|
||||
cleanButton.setOnAction(e -> clearCacheDirectory());
|
||||
fileCommonLocationSublist.setHeaderRight(cleanButton);
|
||||
}
|
||||
|
||||
{
|
||||
JFXCheckBox chkAutoDownloadThreads = new JFXCheckBox(i18n("settings.launcher.download.threads.auto"));
|
||||
VBox.setMargin(chkAutoDownloadThreads, new Insets(8, 0, 0, 0));
|
||||
@@ -153,7 +187,8 @@ public class DownloadSettingsPage extends StackPane {
|
||||
}
|
||||
}
|
||||
|
||||
content.getChildren().addAll(ComponentList.createComponentListTitle(i18n("download")), downloadThreads);
|
||||
downloadList.getContent().addAll(fileCommonLocationSublist, downloadThreads);
|
||||
content.getChildren().addAll(ComponentList.createComponentListTitle(i18n("download")), downloadList);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -330,4 +365,11 @@ public class DownloadSettingsPage extends StackPane {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void clearCacheDirectory() {
|
||||
String commonDirectory = Settings.instance().getCommonDirectory();
|
||||
if (commonDirectory != null) {
|
||||
FileUtils.cleanDirectoryQuietly(Path.of(commonDirectory, "cache"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,7 @@ import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.JFXHyperlink;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
import org.jackhuang.hmcl.ui.wizard.SinglePageWizardProvider;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.Result;
|
||||
@@ -233,7 +234,6 @@ public final class JavaDownloadDialog extends StackPane {
|
||||
downloadButton.setOnAction(e -> onDownload());
|
||||
downloadButton.getStyleClass().add("dialog-accept");
|
||||
downloadButton.disableProperty().bind(Bindings.isNull(remoteVersionBox.getSelectionModel().selectedItemProperty()));
|
||||
downloadButtonPane.getChildren().setAll(downloadButton);
|
||||
|
||||
JFXButton cancelButton = new JFXButton(i18n("button.cancel"));
|
||||
cancelButton.setOnAction(e -> fireEvent(new DialogCloseEvent()));
|
||||
@@ -300,6 +300,11 @@ public final class JavaDownloadDialog extends StackPane {
|
||||
remoteVersionBox.getSelectionModel().selectFirst();
|
||||
});
|
||||
|
||||
SpinnerPane spinnerPane = new SpinnerPane();
|
||||
spinnerPane.getStyleClass().add("small-spinner-pane");
|
||||
spinnerPane.setContent(downloadButton);
|
||||
downloadButtonPane.getChildren().setAll(spinnerPane);
|
||||
|
||||
Consumer<DiscoJavaVersionList> updateListStatus = list -> {
|
||||
remoteVersionBox.setItems(null);
|
||||
packageTypeBox.getItems().clear();
|
||||
@@ -308,11 +313,11 @@ public final class JavaDownloadDialog extends StackPane {
|
||||
warningLabel.setText(null);
|
||||
|
||||
if (list == null || (list.versions != null && list.versions.isEmpty()))
|
||||
downloadButtonPane.getChildren().setAll(downloadButton);
|
||||
spinnerPane.hideSpinner();
|
||||
else if (list.status == DiscoJavaVersionList.Status.LOADING)
|
||||
downloadButtonPane.getChildren().setAll(new JFXSpinner());
|
||||
spinnerPane.showSpinner();
|
||||
else {
|
||||
downloadButtonPane.getChildren().setAll(downloadButton);
|
||||
spinnerPane.hideSpinner();
|
||||
|
||||
if (list.status == DiscoJavaVersionList.Status.SUCCESS) {
|
||||
packageTypeBox.getItems().setAll(list.versions.keySet());
|
||||
|
||||
@@ -28,6 +28,7 @@ import javafx.collections.ObservableList;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.image.ImageView;
|
||||
@@ -170,6 +171,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
FXUtils.setLimitHeight(updatePane, 55);
|
||||
StackPane.setAlignment(updatePane, Pos.TOP_RIGHT);
|
||||
FXUtils.onClicked(updatePane, this::onUpgrade);
|
||||
updatePane.setCursor(Cursor.HAND);
|
||||
FXUtils.onChange(showUpdateProperty(), this::showUpdate);
|
||||
|
||||
{
|
||||
@@ -183,7 +185,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
prompt.setSubtitle(i18n("update.bubble.subtitle"));
|
||||
prompt.setPickOnBounds(false);
|
||||
prompt.titleProperty().bind(BindingMapping.of(latestVersionProperty()).map(latestVersion ->
|
||||
latestVersion == null ? "" : i18n("update.bubble.title", latestVersion.getVersion())));
|
||||
latestVersion == null ? "" : i18n("update.bubble.title", latestVersion.version())));
|
||||
|
||||
hBox.getChildren().setAll(SVG.UPDATE.createIcon(20), prompt);
|
||||
}
|
||||
@@ -279,10 +281,10 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
|
||||
if (show && !config().isDisableAutoShowUpdateDialog()
|
||||
&& getLatestVersion() != null
|
||||
&& !Objects.equals(config().getPromptedVersion(), getLatestVersion().getVersion())) {
|
||||
Controllers.dialog(new MessageDialogPane.Builder("", i18n("update.bubble.title", getLatestVersion().getVersion()), MessageDialogPane.MessageType.INFO)
|
||||
&& !Objects.equals(config().getPromptedVersion(), getLatestVersion().version())) {
|
||||
Controllers.dialog(new MessageDialogPane.Builder("", i18n("update.bubble.title", getLatestVersion().version()), MessageDialogPane.MessageType.INFO)
|
||||
.addAction(i18n("button.view"), () -> {
|
||||
config().setPromptedVersion(getLatestVersion().getVersion());
|
||||
config().setPromptedVersion(getLatestVersion().version());
|
||||
onUpgrade();
|
||||
})
|
||||
.addCancel(null)
|
||||
|
||||
@@ -79,7 +79,7 @@ public class PersonalizationPage extends StackPane {
|
||||
{
|
||||
var brightnessPane = new LineSelectButton<String>();
|
||||
brightnessPane.setTitle(i18n("settings.launcher.brightness"));
|
||||
brightnessPane.setConverter(name -> i18n("settings.launcher.brightness." + name));
|
||||
brightnessPane.setNullSafeConverter(name -> i18n("settings.launcher.brightness." + name));
|
||||
brightnessPane.setItems("auto", "light", "dark");
|
||||
brightnessPane.valueProperty().bindBidirectional(config().themeBrightnessProperty());
|
||||
|
||||
@@ -135,6 +135,7 @@ public class PersonalizationPage extends StackPane {
|
||||
new MultiFileItem.FileOption<>(i18n("settings.custom"), EnumBackgroundImage.CUSTOM)
|
||||
.setChooserTitle(i18n("launcher.background.choose"))
|
||||
.addExtensionFilter(FXUtils.getImageExtensionFilter())
|
||||
.setSelectionMode(FileSelector.SelectionMode.FILE_OR_DIRECTORY)
|
||||
.bindBidirectional(config().backgroundImageProperty()),
|
||||
new MultiFileItem.StringOption<>(i18n("launcher.background.network"), EnumBackgroundImage.NETWORK)
|
||||
.setValidators(new URLValidator(true))
|
||||
@@ -294,7 +295,7 @@ public class PersonalizationPage extends StackPane {
|
||||
var fontAntiAliasingPane = new LineSelectButton<Optional<FontSmoothingType>>();
|
||||
fontAntiAliasingPane.setTitle(i18n("settings.launcher.font.anti_aliasing"));
|
||||
fontAntiAliasingPane.setSubtitle(i18n("settings.take_effect_after_restart"));
|
||||
fontAntiAliasingPane.setConverter(value ->
|
||||
fontAntiAliasingPane.setNullSafeConverter(value ->
|
||||
value.isPresent()
|
||||
? i18n("settings.launcher.font.anti_aliasing." + value.get().name().toLowerCase(Locale.ROOT))
|
||||
: i18n("settings.launcher.font.anti_aliasing.auto")
|
||||
|
||||
@@ -54,6 +54,7 @@ import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.platform.*;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
|
||||
@@ -98,7 +99,7 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
if (mainPage == null) {
|
||||
MainPage mainPage = new MainPage();
|
||||
FXUtils.applyDragListener(mainPage,
|
||||
file -> ModpackHelper.isFileModpackByExtension(file) || NBTFileType.isNBTFileByExtension(file),
|
||||
file -> ModpackHelper.isFileModpackByExtension(file) || NBTFileType.isNBTFileByExtension(file) || "json".equalsIgnoreCase(FileUtils.getExtension(file)),
|
||||
modpacks -> {
|
||||
Path file = modpacks.get(0);
|
||||
if (ModpackHelper.isFileModpackByExtension(file)) {
|
||||
@@ -113,6 +114,8 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
Controllers.dialog(i18n("nbt.open.failed") + "\n\n" + StringUtils.getStackTrace(e),
|
||||
i18n("message.error"), MessageDialogPane.MessageType.ERROR);
|
||||
}
|
||||
} else if ("json".equalsIgnoreCase(FileUtils.getExtension(file))) {
|
||||
Versions.installFromJson(Profiles.getSelectedProfile(), file);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -20,21 +20,17 @@ package org.jackhuang.hmcl.ui.main;
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.WeakInvalidationListener;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.geometry.VPos;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.text.TextAlignment;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.setting.EnumCommonDirectory;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
@@ -61,7 +57,9 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.ZipEntry;
|
||||
@@ -78,242 +76,193 @@ public final class SettingsPage extends ScrollPane {
|
||||
public SettingsPage() {
|
||||
this.setFitToWidth(true);
|
||||
|
||||
VBox rootPane = new VBox();
|
||||
VBox rootPane = new VBox(10);
|
||||
rootPane.setPadding(new Insets(10));
|
||||
this.setContent(rootPane);
|
||||
FXUtils.smoothScrolling(this);
|
||||
|
||||
ComponentList settingsPane = new ComponentList();
|
||||
{
|
||||
ComponentList updatePaneList = new ComponentList();
|
||||
{
|
||||
StackPane sponsorPane = new StackPane();
|
||||
sponsorPane.setCursor(Cursor.HAND);
|
||||
FXUtils.onClicked(sponsorPane, this::onSponsor);
|
||||
sponsorPane.setPadding(new Insets(8, 0, 8, 0));
|
||||
|
||||
GridPane gridPane = new GridPane();
|
||||
|
||||
ColumnConstraints col = new ColumnConstraints();
|
||||
col.setHgrow(Priority.SOMETIMES);
|
||||
col.setMaxWidth(Double.POSITIVE_INFINITY);
|
||||
|
||||
gridPane.getColumnConstraints().setAll(col);
|
||||
|
||||
RowConstraints row = new RowConstraints();
|
||||
row.setMinHeight(Double.NEGATIVE_INFINITY);
|
||||
row.setValignment(VPos.TOP);
|
||||
row.setVgrow(Priority.SOMETIMES);
|
||||
gridPane.getRowConstraints().setAll(row);
|
||||
|
||||
ObjectProperty<UpdateChannel> updateChannel;
|
||||
{
|
||||
Label label = new Label(i18n("sponsor.hmcl"));
|
||||
label.setWrapText(true);
|
||||
label.setTextAlignment(TextAlignment.JUSTIFY);
|
||||
GridPane.setRowIndex(label, 0);
|
||||
GridPane.setColumnIndex(label, 0);
|
||||
gridPane.getChildren().add(label);
|
||||
}
|
||||
|
||||
sponsorPane.getChildren().setAll(gridPane);
|
||||
settingsPane.getContent().add(sponsorPane);
|
||||
}
|
||||
JFXButton updateButton = FXUtils.newToggleButton4(SVG.UPDATE, 20);
|
||||
updateButton.setOnAction(e -> onUpdate());
|
||||
updateButton.setPadding(Insets.EMPTY);
|
||||
FXUtils.installFastTooltip(updateButton, i18n("update.tooltip"));
|
||||
|
||||
ObjectProperty<UpdateChannel> updateChannel;
|
||||
{
|
||||
var updatePane = new LineSelectButton<UpdateChannel>() {
|
||||
|
||||
JFXButton updateButton = new JFXButton();
|
||||
updateButton.setGraphic(SVG.UPDATE.createIcon(20));
|
||||
updateButton.setOnAction(e -> onUpdate());
|
||||
updateButton.setPadding(Insets.EMPTY);
|
||||
FXUtils.installFastTooltip(updateButton, i18n("update.tooltip"));
|
||||
{
|
||||
getStyleClass().add("update-pane");
|
||||
setNode(IDX_TRAILING, updateButton);
|
||||
}
|
||||
|
||||
var updatePane = new LineSelectButton<UpdateChannel>() {
|
||||
|
||||
{
|
||||
getStyleClass().add("update-pane");
|
||||
setNode(IDX_TRAILING, updateButton);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getTrailingTextIndex() {
|
||||
return LineComponent.IDX_TRAILING + 1;
|
||||
}
|
||||
};
|
||||
updateChannel = updatePane.valueProperty();
|
||||
updatePane.setTitle(i18n("update"));
|
||||
updatePane.setValue(UpdateChannel.getChannel());
|
||||
|
||||
updatePane.setConverter(channel -> i18n("update.channel." + channel.channelName));
|
||||
updatePane.setItems(List.of(UpdateChannel.STABLE, UpdateChannel.DEVELOPMENT));
|
||||
updatePane.setDescriptionConverter(channel -> i18n("update.note." + channel.channelName));
|
||||
|
||||
final StringProperty lblUpdateSubProperty = updatePane.subtitleProperty();
|
||||
|
||||
{
|
||||
updateListener = any -> {
|
||||
boolean outdated = UpdateChecker.isOutdated();
|
||||
|
||||
updateButton.setVisible(outdated);
|
||||
updateButton.setManaged(outdated);
|
||||
updatePane.pseudoClassStateChanged(PseudoClass.getPseudoClass("active"), outdated);
|
||||
|
||||
if (UpdateChecker.isOutdated()) {
|
||||
lblUpdateSubProperty.set(i18n("update.newest_version", UpdateChecker.getLatestVersion().getVersion()));
|
||||
} else if (UpdateChecker.isCheckingUpdate()) {
|
||||
lblUpdateSubProperty.set(i18n("update.checking"));
|
||||
} else {
|
||||
lblUpdateSubProperty.set(i18n("update.latest"));
|
||||
@Override
|
||||
protected int getTrailingTextIndex() {
|
||||
return LineComponent.IDX_TRAILING + 1;
|
||||
}
|
||||
};
|
||||
UpdateChecker.latestVersionProperty().addListener(new WeakInvalidationListener(updateListener));
|
||||
UpdateChecker.outdatedProperty().addListener(new WeakInvalidationListener(updateListener));
|
||||
UpdateChecker.checkingUpdateProperty().addListener(new WeakInvalidationListener(updateListener));
|
||||
updateListener.invalidated(null);
|
||||
updateChannel = updatePane.valueProperty();
|
||||
updatePane.setTitle(i18n("update"));
|
||||
updatePane.setValue(UpdateChannel.getChannel());
|
||||
|
||||
updatePane.setNullSafeConverter(channel -> i18n("update.channel." + channel.channelName));
|
||||
updatePane.setItems(List.of(UpdateChannel.STABLE, UpdateChannel.DEVELOPMENT));
|
||||
updatePane.setDescriptionConverter(channel -> i18n("update.note." + channel.channelName));
|
||||
|
||||
final StringProperty lblUpdateSubProperty = updatePane.subtitleProperty();
|
||||
|
||||
{
|
||||
updateListener = any -> {
|
||||
boolean outdated = UpdateChecker.isOutdated();
|
||||
|
||||
updateButton.setVisible(outdated);
|
||||
updateButton.setManaged(outdated);
|
||||
updatePane.pseudoClassStateChanged(PseudoClass.getPseudoClass("active"), outdated);
|
||||
|
||||
if (UpdateChecker.isOutdated()) {
|
||||
lblUpdateSubProperty.set(i18n("update.newest_version", UpdateChecker.getLatestVersion().version()));
|
||||
} else if (UpdateChecker.isCheckingUpdate()) {
|
||||
lblUpdateSubProperty.set(i18n("update.checking"));
|
||||
} else {
|
||||
lblUpdateSubProperty.set(i18n("update.latest"));
|
||||
}
|
||||
};
|
||||
UpdateChecker.latestVersionProperty().addListener(new WeakInvalidationListener(updateListener));
|
||||
UpdateChecker.outdatedProperty().addListener(new WeakInvalidationListener(updateListener));
|
||||
UpdateChecker.checkingUpdateProperty().addListener(new WeakInvalidationListener(updateListener));
|
||||
updateListener.invalidated(null);
|
||||
}
|
||||
|
||||
updatePaneList.getContent().add(updatePane);
|
||||
}
|
||||
|
||||
settingsPane.getContent().add(updatePane);
|
||||
{
|
||||
LineToggleButton previewPane = new LineToggleButton();
|
||||
previewPane.setTitle(i18n("update.preview"));
|
||||
previewPane.setSubtitle(i18n("update.preview.subtitle"));
|
||||
previewPane.selectedProperty().bindBidirectional(config().acceptPreviewUpdateProperty());
|
||||
|
||||
InvalidationListener checkUpdateListener = e -> {
|
||||
UpdateChecker.requestCheckUpdate(updateChannel.get(), previewPane.isSelected());
|
||||
};
|
||||
updateChannel.addListener(checkUpdateListener);
|
||||
previewPane.selectedProperty().addListener(checkUpdateListener);
|
||||
|
||||
updatePaneList.getContent().add(previewPane);
|
||||
}
|
||||
|
||||
{
|
||||
LineToggleButton disableAutoShowUpdateDialogPane = new LineToggleButton();
|
||||
disableAutoShowUpdateDialogPane.setTitle(i18n("update.disable_auto_show_update_dialog"));
|
||||
disableAutoShowUpdateDialogPane.setSubtitle(i18n("update.disable_auto_show_update_dialog.subtitle"));
|
||||
disableAutoShowUpdateDialogPane.selectedProperty().bindBidirectional(config().disableAutoShowUpdateDialogProperty());
|
||||
updatePaneList.getContent().add(disableAutoShowUpdateDialogPane);
|
||||
}
|
||||
|
||||
rootPane.getChildren().addAll(ComponentList.createComponentListTitle(i18n("update")), updatePaneList);
|
||||
}
|
||||
|
||||
{
|
||||
LineToggleButton previewPane = new LineToggleButton();
|
||||
previewPane.setTitle(i18n("update.preview"));
|
||||
previewPane.setSubtitle(i18n("update.preview.subtitle"));
|
||||
previewPane.selectedProperty().bindBidirectional(config().acceptPreviewUpdateProperty());
|
||||
ComponentList languagePaneList = new ComponentList();
|
||||
|
||||
InvalidationListener checkUpdateListener = e -> {
|
||||
UpdateChecker.requestCheckUpdate(updateChannel.get(), previewPane.isSelected());
|
||||
};
|
||||
updateChannel.addListener(checkUpdateListener);
|
||||
previewPane.selectedProperty().addListener(checkUpdateListener);
|
||||
{
|
||||
var chooseLanguagePane = new LineSelectButton<SupportedLocale>();
|
||||
chooseLanguagePane.setTitle(i18n("settings.launcher.language"));
|
||||
chooseLanguagePane.setSubtitle(i18n("settings.take_effect_after_restart"));
|
||||
|
||||
settingsPane.getContent().add(previewPane);
|
||||
SupportedLocale currentLocale = I18n.getLocale();
|
||||
chooseLanguagePane.setNullSafeConverter(locale -> {
|
||||
if (locale.isDefault())
|
||||
return locale.getDisplayName(currentLocale);
|
||||
else if (locale.isSameLanguage(currentLocale))
|
||||
return locale.getDisplayName(locale);
|
||||
else
|
||||
return locale.getDisplayName(currentLocale) + " - " + locale.getDisplayName(locale);
|
||||
});
|
||||
chooseLanguagePane.setItems(SupportedLocale.getSupportedLocales());
|
||||
chooseLanguagePane.valueProperty().bindBidirectional(config().localizationProperty());
|
||||
|
||||
languagePaneList.getContent().add(chooseLanguagePane);
|
||||
|
||||
LineToggleButton disableAutoGameOptionsPane = new LineToggleButton();
|
||||
disableAutoGameOptionsPane.setTitle(i18n("settings.launcher.disable_auto_game_options"));
|
||||
disableAutoGameOptionsPane.selectedProperty().bindBidirectional(config().disableAutoGameOptionsProperty());
|
||||
|
||||
languagePaneList.getContent().add(disableAutoGameOptionsPane);
|
||||
}
|
||||
|
||||
rootPane.getChildren().addAll(ComponentList.createComponentListTitle(i18n("settings.launcher.language")), languagePaneList);
|
||||
}
|
||||
|
||||
{
|
||||
LineToggleButton disableAutoShowUpdateDialogPane = new LineToggleButton();
|
||||
disableAutoShowUpdateDialogPane.setTitle(i18n("update.disable_auto_show_update_dialog"));
|
||||
disableAutoShowUpdateDialogPane.setSubtitle(i18n("update.disable_auto_show_update_dialog.subtitle"));
|
||||
disableAutoShowUpdateDialogPane.selectedProperty().bindBidirectional(config().disableAutoShowUpdateDialogProperty());
|
||||
settingsPane.getContent().add(disableAutoShowUpdateDialogPane);
|
||||
ComponentList miscPaneList = new ComponentList();
|
||||
|
||||
if (AprilFools.isShowAprilFoolsSettings()) {
|
||||
LineToggleButton disableAprilFools = new LineToggleButton();
|
||||
disableAprilFools.setTitle(i18n("settings.launcher.disable_april_fools"));
|
||||
disableAprilFools.setSubtitle(i18n("settings.take_effect_after_restart"));
|
||||
disableAprilFools.selectedProperty().bindBidirectional(config().disableAprilFoolsProperty());
|
||||
miscPaneList.getContent().add(disableAprilFools);
|
||||
}
|
||||
|
||||
{
|
||||
LineToggleButton allowAutoAgentPane = new LineToggleButton();
|
||||
allowAutoAgentPane.setTitle(i18n("settings.launcher.allow_auto_agent"));
|
||||
allowAutoAgentPane.setSubtitle(i18n("settings.launcher.allow_auto_agent.subtitle"));
|
||||
allowAutoAgentPane.selectedProperty().bindBidirectional(config().allowAutoAgentProperty());
|
||||
|
||||
miscPaneList.getContent().add(allowAutoAgentPane);
|
||||
}
|
||||
|
||||
{
|
||||
BorderPane debugPane = new BorderPane();
|
||||
|
||||
Label left = new Label(i18n("settings.launcher.debug"));
|
||||
BorderPane.setAlignment(left, Pos.CENTER_LEFT);
|
||||
debugPane.setLeft(left);
|
||||
|
||||
JFXButton openLogFolderButton = new JFXButton(i18n("settings.launcher.launcher_log.reveal"));
|
||||
openLogFolderButton.setOnAction(e -> openLogFolder());
|
||||
openLogFolderButton.getStyleClass().add("jfx-button-border");
|
||||
if (LOG.getLogFile() == null)
|
||||
openLogFolderButton.setDisable(true);
|
||||
|
||||
SpinnerPane exportLogPane = new SpinnerPane();
|
||||
|
||||
JFXButton logButton = FXUtils.newBorderButton(i18n("settings.launcher.launcher_log.export"));
|
||||
exportLogPane.setContent(logButton);
|
||||
logButton.setOnAction(e -> {
|
||||
exportLogPane.showSpinner();
|
||||
onExportLogs().whenCompleteAsync((result, exception) -> {
|
||||
exportLogPane.hideSpinner();
|
||||
if (exception == null) {
|
||||
Controllers.dialog(i18n("settings.launcher.launcher_log.export.success", result));
|
||||
FXUtils.showFileInExplorer(result);
|
||||
} else {
|
||||
LOG.warning("Failed to export logs", exception);
|
||||
Controllers.dialog(
|
||||
i18n("settings.launcher.launcher_log.export.failed") + "\n" + StringUtils.getStackTrace(exception),
|
||||
null,
|
||||
MessageType.ERROR
|
||||
);
|
||||
}
|
||||
}, Schedulers.javafx());
|
||||
});
|
||||
|
||||
HBox buttonBox = new HBox();
|
||||
buttonBox.setSpacing(10);
|
||||
buttonBox.getChildren().addAll(openLogFolderButton, exportLogPane);
|
||||
BorderPane.setAlignment(buttonBox, Pos.CENTER_RIGHT);
|
||||
debugPane.setRight(buttonBox);
|
||||
|
||||
miscPaneList.getContent().add(debugPane);
|
||||
}
|
||||
|
||||
rootPane.getChildren().addAll(ComponentList.createComponentListTitle(i18n("settings.launcher.misc")), miscPaneList);
|
||||
}
|
||||
|
||||
if (AprilFools.isShowAprilFoolsSettings()) {
|
||||
LineToggleButton disableAprilFools = new LineToggleButton();
|
||||
disableAprilFools.setTitle(i18n("settings.launcher.disable_april_fools"));
|
||||
disableAprilFools.setSubtitle(i18n("settings.take_effect_after_restart"));
|
||||
disableAprilFools.selectedProperty().bindBidirectional(config().disableAprilFoolsProperty());
|
||||
settingsPane.getContent().add(disableAprilFools);
|
||||
}
|
||||
|
||||
{
|
||||
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"))
|
||||
.setDirectory(true)
|
||||
.bindBidirectional(config().commonDirectoryProperty())
|
||||
));
|
||||
fileCommonLocation.selectedDataProperty().bindBidirectional(config().commonDirTypeProperty());
|
||||
|
||||
ComponentSublist fileCommonLocationSublist = new ComponentSublist();
|
||||
fileCommonLocationSublist.getContent().add(fileCommonLocation);
|
||||
fileCommonLocationSublist.setTitle(i18n("launcher.cache_directory"));
|
||||
fileCommonLocationSublist.setHasSubtitle(true);
|
||||
fileCommonLocationSublist.subtitleProperty().bind(
|
||||
Bindings.createObjectBinding(() -> Optional.ofNullable(Settings.instance().getCommonDirectory())
|
||||
.orElse(i18n("launcher.cache_directory.disabled")),
|
||||
config().commonDirectoryProperty(), config().commonDirTypeProperty()));
|
||||
|
||||
JFXButton cleanButton = FXUtils.newBorderButton(i18n("launcher.cache_directory.clean"));
|
||||
cleanButton.setOnAction(e -> clearCacheDirectory());
|
||||
fileCommonLocationSublist.setHeaderRight(cleanButton);
|
||||
|
||||
settingsPane.getContent().add(fileCommonLocationSublist);
|
||||
}
|
||||
|
||||
{
|
||||
var chooseLanguagePane = new LineSelectButton<SupportedLocale>();
|
||||
chooseLanguagePane.setTitle(i18n("settings.launcher.language"));
|
||||
chooseLanguagePane.setSubtitle(i18n("settings.take_effect_after_restart"));
|
||||
|
||||
SupportedLocale currentLocale = I18n.getLocale();
|
||||
chooseLanguagePane.setConverter(locale -> {
|
||||
if (locale.isDefault())
|
||||
return locale.getDisplayName(currentLocale);
|
||||
else if (locale.isSameLanguage(currentLocale))
|
||||
return locale.getDisplayName(locale);
|
||||
else
|
||||
return locale.getDisplayName(currentLocale) + " - " + locale.getDisplayName(locale);
|
||||
});
|
||||
chooseLanguagePane.setItems(SupportedLocale.getSupportedLocales());
|
||||
chooseLanguagePane.valueProperty().bindBidirectional(config().localizationProperty());
|
||||
|
||||
settingsPane.getContent().add(chooseLanguagePane);
|
||||
}
|
||||
|
||||
{
|
||||
LineToggleButton disableAutoGameOptionsPane = new LineToggleButton();
|
||||
disableAutoGameOptionsPane.setTitle(i18n("settings.launcher.disable_auto_game_options"));
|
||||
disableAutoGameOptionsPane.selectedProperty().bindBidirectional(config().disableAutoGameOptionsProperty());
|
||||
|
||||
settingsPane.getContent().add(disableAutoGameOptionsPane);
|
||||
}
|
||||
|
||||
{
|
||||
LineToggleButton allowAutoAgentPane = new LineToggleButton();
|
||||
allowAutoAgentPane.setTitle(i18n("settings.launcher.allow_auto_agent"));
|
||||
allowAutoAgentPane.setSubtitle(i18n("settings.launcher.allow_auto_agent.subtitle"));
|
||||
allowAutoAgentPane.selectedProperty().bindBidirectional(config().allowAutoAgentProperty());
|
||||
|
||||
settingsPane.getContent().add(allowAutoAgentPane);
|
||||
}
|
||||
|
||||
{
|
||||
BorderPane debugPane = new BorderPane();
|
||||
|
||||
Label left = new Label(i18n("settings.launcher.debug"));
|
||||
BorderPane.setAlignment(left, Pos.CENTER_LEFT);
|
||||
debugPane.setLeft(left);
|
||||
|
||||
JFXButton openLogFolderButton = new JFXButton(i18n("settings.launcher.launcher_log.reveal"));
|
||||
openLogFolderButton.setOnAction(e -> openLogFolder());
|
||||
openLogFolderButton.getStyleClass().add("jfx-button-border");
|
||||
if (LOG.getLogFile() == null)
|
||||
openLogFolderButton.setDisable(true);
|
||||
|
||||
SpinnerPane exportLogPane = new SpinnerPane();
|
||||
|
||||
JFXButton logButton = FXUtils.newBorderButton(i18n("settings.launcher.launcher_log.export"));
|
||||
exportLogPane.setContent(logButton);
|
||||
logButton.setOnAction(e -> {
|
||||
exportLogPane.showSpinner();
|
||||
onExportLogs().whenCompleteAsync((result, exception) -> {
|
||||
exportLogPane.hideSpinner();
|
||||
if (exception == null) {
|
||||
Controllers.dialog(i18n("settings.launcher.launcher_log.export.success", result));
|
||||
FXUtils.showFileInExplorer(result);
|
||||
} else {
|
||||
LOG.warning("Failed to export logs", exception);
|
||||
Controllers.dialog(
|
||||
i18n("settings.launcher.launcher_log.export.failed") + "\n" + StringUtils.getStackTrace(exception),
|
||||
null,
|
||||
MessageType.ERROR
|
||||
);
|
||||
}
|
||||
}, Schedulers.javafx());
|
||||
});
|
||||
|
||||
HBox buttonBox = new HBox();
|
||||
buttonBox.setSpacing(10);
|
||||
buttonBox.getChildren().addAll(openLogFolderButton, exportLogPane);
|
||||
BorderPane.setAlignment(buttonBox, Pos.CENTER_RIGHT);
|
||||
debugPane.setRight(buttonBox);
|
||||
|
||||
settingsPane.getContent().add(debugPane);
|
||||
}
|
||||
|
||||
rootPane.getChildren().add(settingsPane);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -452,15 +401,4 @@ public final class SettingsPage extends ScrollPane {
|
||||
return outputFile;
|
||||
}), Schedulers.io());
|
||||
}
|
||||
|
||||
private void onSponsor() {
|
||||
FXUtils.openLink("https://github.com/HMCL-dev/HMCL");
|
||||
}
|
||||
|
||||
private void clearCacheDirectory() {
|
||||
String commonDirectory = Settings.instance().getCommonDirectory();
|
||||
if (commonDirectory != null) {
|
||||
FileUtils.cleanDirectoryQuietly(Path.of(commonDirectory, "cache"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,7 @@ public class SkinCube extends MeshView {
|
||||
public static float[] createTexCoords(float width, float height, float depth, float scaleX, float scaleY,
|
||||
float startX, float startY, boolean isSlim) {
|
||||
float x = (width + depth) * 2, y = height + depth, half_width = width / x * scaleX, half_depth = depth / x * scaleX,
|
||||
top_x = depth / x * scaleX + startX, top_y = startY, arm4 = isSlim ? half_depth : half_width,
|
||||
top_x = depth / x * scaleX + startX, top_y = startY, arm4 = half_width,
|
||||
bottom_x = startX, middle_y = depth / y * scaleY + top_y, bottom_y = scaleY + top_y;
|
||||
return new float[]{
|
||||
top_x, top_y, // T0 ---
|
||||
@@ -58,14 +58,14 @@ public class SkinCube extends MeshView {
|
||||
5, 0, 4, 1, 0, 5, //P5,T0, P4,T1, P0,T5
|
||||
5, 0, 0, 5, 1, 4, //P5,T0, P0,T5, P1,T4
|
||||
// RIGHT
|
||||
0, 5, 4, 6, 6, 11, //P0,T4 ,P4,T3, P6,T8
|
||||
0, 5, 6, 11, 2, 10, //P0,T4 ,P6,T8, P2,T9
|
||||
5, 3, 1, 4, 3, 9, //P5,T3, P1,T4, P3,T9
|
||||
5, 3, 3, 9, 7, 8, //P5,T3, P3,T9, P7,T8
|
||||
// FRONT
|
||||
1, 4, 0, 5, 2, 10, //P1,T5, P0,T4, P2,T9
|
||||
1, 4, 2, 10, 3, 9, //P1,T5, P2,T9, P3,T10
|
||||
// LEFT
|
||||
5, 3, 1, 4, 3, 9, //P5,T6, P1,T5, P3,T10
|
||||
5, 3, 3, 9, 7, 8, //P5,T6, P3,T10,P7,T11
|
||||
0, 5, 4, 6, 6, 11, //P0,T4 ,P4,T3, P6,T8
|
||||
0, 5, 6, 11, 2, 10, //P0,T4 ,P6,T8, P2,T9
|
||||
// BACK
|
||||
4, 6, 5, 7, 7, 12, //P4,T6, P5,T7, P7,T12
|
||||
4, 6, 7, 12, 6, 11, //P4,T6, P7,T12,P6,T11
|
||||
|
||||
@@ -131,14 +131,15 @@ public class SkinMultipleCubes extends Group {
|
||||
// LEFT
|
||||
getChildren().add(new Face(skin, start_x + width_interval + depth_interval, start_y + depth_interval, depth, height, interval, false, false,
|
||||
() -> new Box(thick, length, length), (b, p) -> {
|
||||
b.setTranslateX((width * length + thick) / 2.0);
|
||||
b.setTranslateX(-(width * length + thick) / 2.0);
|
||||
b.setTranslateY(-((height - 1) / 2.0 - p.getY()) * b.getHeight());
|
||||
b.setTranslateZ(((depth - 1) / 2.0 - p.getX()) * b.getDepth());
|
||||
}));
|
||||
|
||||
// RIGHT
|
||||
getChildren().add(new Face(skin, start_x, start_y + depth_interval, depth, height, interval, true, false,
|
||||
() -> new Box(thick, length, length), (b, p) -> {
|
||||
b.setTranslateX(-(width * length + thick) / 2.0);
|
||||
b.setTranslateX((width * length + thick) / 2.0);
|
||||
b.setTranslateY(-((height - 1) / 2.0 - p.getY()) * b.getHeight());
|
||||
b.setTranslateZ(((depth - 1) / 2.0 - p.getX()) * b.getDepth());
|
||||
}));
|
||||
|
||||
@@ -246,7 +246,7 @@ public class TerracottaControllerPage extends StackPane {
|
||||
} else {
|
||||
handler.resolve();
|
||||
}
|
||||
});
|
||||
}, "", new RequiredValidator(i18n("input.not_empty")));
|
||||
});
|
||||
|
||||
if (ThreadLocalRandom.current().nextDouble() < 0.02D) {
|
||||
|
||||
@@ -25,6 +25,7 @@ import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.*;
|
||||
import org.jackhuang.hmcl.game.GraphicsAPI;
|
||||
import org.jackhuang.hmcl.game.NativesDirectoryType;
|
||||
import org.jackhuang.hmcl.game.Renderer;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
@@ -36,6 +37,7 @@ import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.platform.Platform;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.nio.file.FileSystems;
|
||||
@@ -69,6 +71,7 @@ public final class AdvancedVersionSettingPage extends StackPane implements Decor
|
||||
private final ComponentSublist nativesDirSublist;
|
||||
private final MultiFileItem<NativesDirectoryType> nativesDirItem;
|
||||
private final MultiFileItem.FileOption<NativesDirectoryType> nativesDirCustomOption;
|
||||
private final LineSelectButton<GraphicsAPI> graphicsBackendPane;
|
||||
private final LineSelectButton<Renderer> rendererPane;
|
||||
|
||||
public AdvancedVersionSettingPage(Profile profile, @Nullable String versionId, VersionSetting versionSetting) {
|
||||
@@ -79,6 +82,10 @@ public final class AdvancedVersionSettingPage extends StackPane implements Decor
|
||||
versionId == null ? i18n("settings.advanced") : i18n("settings.advanced.title", versionId)
|
||||
));
|
||||
|
||||
@Nullable GameVersionNumber gameVersion = versionId != null
|
||||
? GameVersionNumber.asGameVersion(profile.getRepository().getGameVersion(versionId))
|
||||
: null;
|
||||
|
||||
this.getStyleClass().add("gray-background");
|
||||
|
||||
ScrollPane scrollPane = new ScrollPane();
|
||||
@@ -176,23 +183,55 @@ public final class AdvancedVersionSettingPage extends StackPane implements Decor
|
||||
nativesDirSublist.setHasSubtitle(true);
|
||||
nativesDirCustomOption = new MultiFileItem.FileOption<>(i18n("settings.advanced.natives_directory.custom"), NativesDirectoryType.CUSTOM)
|
||||
.setChooserTitle(i18n("settings.advanced.natives_directory.choose"))
|
||||
.setDirectory(true);
|
||||
.setSelectionMode(FileSelector.SelectionMode.DIRECTORY);
|
||||
nativesDirItem.loadChildren(Arrays.asList(
|
||||
new MultiFileItem.Option<>(i18n("settings.advanced.natives_directory.default"), NativesDirectoryType.VERSION_FOLDER),
|
||||
nativesDirCustomOption
|
||||
));
|
||||
HintPane nativesDirHint = new HintPane(MessageDialogPane.MessageType.WARNING);
|
||||
nativesDirHint.setText(i18n("settings.advanced.natives_directory.hint"));
|
||||
nativesDirItem.getChildren().add(nativesDirHint);
|
||||
|
||||
graphicsBackendPane = new LineSelectButton<>();
|
||||
graphicsBackendPane.setTitle(i18n("settings.advanced.graphics_backend"));
|
||||
graphicsBackendPane.setNullSafeConverter(backend -> i18n("settings.advanced.graphics_backend." + backend.name().toLowerCase(Locale.ROOT)));
|
||||
graphicsBackendPane.setDescriptionConverter(backend -> switch (backend) {
|
||||
case DEFAULT -> i18n("settings.advanced.graphics_backend.default.desc");
|
||||
case OPENGL -> i18n("settings.advanced.graphics_backend.opengl.desc");
|
||||
case VULKAN -> {
|
||||
if (gameVersion == null)
|
||||
yield i18n("settings.advanced.graphics_backend.vulkan.desc.global");
|
||||
else if (gameVersion.compareTo("26.2-snapshot-2") < 0)
|
||||
yield i18n("settings.advanced.graphics_backend.vulkan.desc.unsupported");
|
||||
else
|
||||
yield i18n("settings.advanced.graphics_backend.vulkan.desc");
|
||||
}
|
||||
default -> null;
|
||||
});
|
||||
graphicsBackendPane.setValue(GraphicsAPI.DEFAULT);
|
||||
graphicsBackendPane.setItems(GraphicsAPI.values());
|
||||
|
||||
rendererPane = new LineSelectButton<>();
|
||||
rendererPane.setTitle(i18n("settings.advanced.renderer"));
|
||||
rendererPane.setConverter(e -> i18n("settings.advanced.renderer." + e.name().toLowerCase(Locale.ROOT)));
|
||||
rendererPane.setNullSafeConverter(e -> i18n("settings.advanced.renderer." + e.name().toLowerCase(Locale.ROOT)));
|
||||
rendererPane.setDescriptionConverter(e -> {
|
||||
String bundleKey = "settings.advanced.renderer." + e.name().toLowerCase(Locale.ROOT) + ".desc";
|
||||
return I18n.hasKey(bundleKey) ? i18n(bundleKey) : null;
|
||||
});
|
||||
rendererPane.setItems(Renderer.values());
|
||||
rendererPane.setValue(Renderer.DEFAULT);
|
||||
|
||||
FXUtils.onChangeAndOperate(graphicsBackendPane.valueProperty(), backend -> {
|
||||
if (backend == null) { // unbind
|
||||
return;
|
||||
}
|
||||
|
||||
rendererPane.setItems(Renderer.getSupported(backend));
|
||||
if (backend == GraphicsAPI.DEFAULT) {
|
||||
rendererPane.setDisable(true);
|
||||
rendererPane.setValue(Renderer.DEFAULT);
|
||||
} else {
|
||||
rendererPane.setDisable(false);
|
||||
if (!(rendererPane.getValue() instanceof Renderer.Driver driver) || driver.api() != backend)
|
||||
rendererPane.setValue(Renderer.DEFAULT);
|
||||
}
|
||||
});
|
||||
|
||||
noJVMArgsPane = new LineToggleButton();
|
||||
noJVMArgsPane.setTitle(i18n("settings.advanced.no_jvm_args"));
|
||||
@@ -219,7 +258,7 @@ public final class AdvancedVersionSettingPage extends StackPane implements Decor
|
||||
useNativeOpenALPane.setSubtitle(i18n("settings.advanced.linux_freebsd_only"));
|
||||
|
||||
workaroundPane.getContent().setAll(
|
||||
nativesDirSublist, rendererPane, noJVMArgsPane, noOptimizingJVMArgsPane, noGameCheckPane,
|
||||
nativesDirSublist, graphicsBackendPane, rendererPane, noJVMArgsPane, noOptimizingJVMArgsPane, noGameCheckPane,
|
||||
noJVMCheckPane, noNativesPatchPane
|
||||
);
|
||||
|
||||
@@ -252,6 +291,7 @@ public final class AdvancedVersionSettingPage extends StackPane implements Decor
|
||||
FXUtils.bindString(txtPreLaunchCommand, versionSetting.preLaunchCommandProperty());
|
||||
FXUtils.bindString(txtPostExitCommand, versionSetting.postExitCommandProperty());
|
||||
rendererPane.valueProperty().bindBidirectional(versionSetting.rendererProperty());
|
||||
graphicsBackendPane.valueProperty().bindBidirectional(versionSetting.graphicsBackendProperty());
|
||||
noGameCheckPane.selectedProperty().bindBidirectional(versionSetting.notCheckGameProperty());
|
||||
noJVMCheckPane.selectedProperty().bindBidirectional(versionSetting.notCheckJVMProperty());
|
||||
noJVMArgsPane.selectedProperty().bindBidirectional(versionSetting.noJVMArgsProperty());
|
||||
@@ -294,6 +334,7 @@ public final class AdvancedVersionSettingPage extends StackPane implements Decor
|
||||
FXUtils.unbind(txtPreLaunchCommand, versionSetting.preLaunchCommandProperty());
|
||||
FXUtils.unbind(txtPostExitCommand, versionSetting.postExitCommandProperty());
|
||||
rendererPane.valueProperty().unbindBidirectional(versionSetting.rendererProperty());
|
||||
graphicsBackendPane.valueProperty().unbindBidirectional(versionSetting.graphicsBackendProperty());
|
||||
noGameCheckPane.selectedProperty().unbindBidirectional(versionSetting.notCheckGameProperty());
|
||||
noJVMCheckPane.selectedProperty().unbindBidirectional(versionSetting.notCheckJVMProperty());
|
||||
noJVMArgsPane.selectedProperty().unbindBidirectional(versionSetting.noJVMArgsProperty());
|
||||
|
||||
@@ -28,6 +28,7 @@ import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.transformation.FilteredList;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
@@ -97,6 +98,14 @@ final class DataPackListPageSkin extends SkinBase<DataPackListPage> {
|
||||
listView = new JFXListView<>();
|
||||
filteredList = new FilteredList<>(skinnable.getItems());
|
||||
|
||||
// reason for not using selectAll() is that selectAll() first clears all selected then selects all, causing the toolbar to flicker
|
||||
var selectAllButton = createToolbarButton2(i18n("button.select_all"), SVG.SELECT_ALL, () -> listView.getSelectionModel().selectRange(0, listView.getItems().size()));
|
||||
|
||||
ListChangeListener<Object> listener = change -> {
|
||||
selectAllButton.setDisable(!listView.getItems().isEmpty()
|
||||
&& listView.getSelectionModel().getSelectedItems().size() == listView.getItems().size());
|
||||
};
|
||||
|
||||
{
|
||||
toolbarPane = new TransitionPane();
|
||||
searchBar = new HBox();
|
||||
@@ -123,12 +132,13 @@ final class DataPackListPageSkin extends SkinBase<DataPackListPage> {
|
||||
enableButton.disableProperty().bind(getSkinnable().readOnly);
|
||||
disableButton.disableProperty().bind(getSkinnable().readOnly);
|
||||
|
||||
listView.getSelectionModel().getSelectedItems().addListener(listener);
|
||||
|
||||
selectingToolbar.getChildren().addAll(
|
||||
removeButton,
|
||||
enableButton,
|
||||
disableButton,
|
||||
createToolbarButton2(i18n("button.select_all"), SVG.SELECT_ALL, () ->
|
||||
listView.getSelectionModel().selectRange(0, listView.getItems().size())),//reason for not using selectAll() is that selectAll() first clears all selected then selects all, causing the toolbar to flicker
|
||||
selectAllButton,
|
||||
createToolbarButton2(i18n("button.cancel"), SVG.CANCEL, () ->
|
||||
listView.getSelectionModel().clearSelection())
|
||||
);
|
||||
@@ -184,6 +194,7 @@ final class DataPackListPageSkin extends SkinBase<DataPackListPage> {
|
||||
listView.setCellFactory(x -> new DataPackInfoListCell(listView, getSkinnable().readOnly));
|
||||
listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
|
||||
this.listView.setItems(filteredList);
|
||||
listView.getItems().addListener(listener);
|
||||
|
||||
// ListViewBehavior would consume ESC pressed event, preventing us from handling it, so we ignore it here
|
||||
FXUtils.ignoreEvent(listView, KeyEvent.KEY_PRESSED, e -> e.getCode() == KeyCode.ESCAPE);
|
||||
|
||||
@@ -531,7 +531,6 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
listView.setSelectionModel(new NoneMultipleSelectionModel<>());
|
||||
// ListViewBehavior would consume ESC pressed event, preventing us from handling it, so we ignore it here
|
||||
ignoreEvent(listView, KeyEvent.KEY_PRESSED, e -> e.getCode() == KeyCode.ESCAPE);
|
||||
|
||||
listView.setCellFactory(x -> new ListCell<>() {
|
||||
private static final Insets PADDING = new Insets(9, 9, 0, 9);
|
||||
|
||||
@@ -578,7 +577,11 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
} else {
|
||||
ModTranslations.Mod mod = ModTranslations.getTranslationsByRepositoryType(getSkinnable().repository.getType()).getModByCurseForgeId(item.getSlug());
|
||||
content.setTitle(mod != null && I18n.isUseChinese() ? mod.getDisplayName() : item.getTitle());
|
||||
content.setSubtitle(item.getDescription());
|
||||
String description = item.getDescription();
|
||||
if (description != null) {
|
||||
description = description.replaceAll("\\R", " ");
|
||||
}
|
||||
content.setSubtitle(description);
|
||||
content.getTags().clear();
|
||||
for (String category : item.getCategories()) {
|
||||
if (getSkinnable().shouldDisplayCategory(category))
|
||||
|
||||
@@ -225,8 +225,10 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
content.setSubtitle(getSkinnable().addon.getDescription());
|
||||
content.getSubtitleLabel().setWrapText(true);
|
||||
getSkinnable().addon.getCategories().stream()
|
||||
.filter(category -> getSkinnable().page.shouldDisplayCategory(category))
|
||||
.map(category -> getSkinnable().page.getLocalizedCategory(category))
|
||||
.forEach(content::addTag);
|
||||
content.getFirstLine().setMinWidth(0);
|
||||
descriptionPane.getChildren().add(content);
|
||||
|
||||
if (getSkinnable().mod != null) {
|
||||
@@ -273,7 +275,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
Version game = repository.getResolvedPreservingPatchesVersion(control.version.getVersion());
|
||||
String gameVersion = repository.getGameVersion(game).orElse(null);
|
||||
|
||||
if (gameVersion != null) {
|
||||
if (gameVersion != null && control.versions.containsKey(gameVersion)) {
|
||||
List<RemoteMod.Version> modVersions = control.versions.get(gameVersion);
|
||||
if (modVersions != null && !modVersions.isEmpty()) {
|
||||
Set<ModLoaderType> targetLoaders = LibraryAnalyzer.analyze(game, gameVersion).getModLoaders();
|
||||
@@ -351,7 +353,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
}
|
||||
}
|
||||
|
||||
private static final class DependencyModItem extends StackPane {
|
||||
private static final class DependencyModItem extends LineButton {
|
||||
public static final EnumMap<RemoteMod.DependencyType, String> I18N_KEY = new EnumMap<>(Lang.mapOf(
|
||||
Pair.pair(RemoteMod.DependencyType.EMBEDDED, "mods.dependency.embedded"),
|
||||
Pair.pair(RemoteMod.DependencyType.OPTIONAL, "mods.dependency.optional"),
|
||||
@@ -367,16 +369,16 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
pane.setPadding(new Insets(0, 8, 0, 8));
|
||||
pane.setAlignment(Pos.CENTER_LEFT);
|
||||
TwoLineListItem content = new TwoLineListItem();
|
||||
pane.setMouseTransparent(true);
|
||||
HBox.setHgrow(content, Priority.ALWAYS);
|
||||
var imageView = new ImageContainer(40);
|
||||
pane.getChildren().setAll(imageView, content);
|
||||
|
||||
RipplerContainer container = new RipplerContainer(pane);
|
||||
FXUtils.onClicked(container, () -> {
|
||||
FXUtils.setLimitHeight(this, 60);
|
||||
setOnAction((e) -> {
|
||||
fireEvent(new DialogCloseEvent());
|
||||
Controllers.navigate(new DownloadPage(page, addon, version, callback));
|
||||
});
|
||||
getChildren().setAll(container);
|
||||
setNode(IDX_LEADING, pane);
|
||||
|
||||
if (addon != RemoteMod.BROKEN) {
|
||||
ModTranslations.Mod mod = ModTranslations.getTranslationsByRepositoryType(page.repository.getType()).getModByCurseForgeId(addon.getSlug());
|
||||
@@ -439,7 +441,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
case CLEANROOM:
|
||||
content.addTag(i18n("install.installer.cleanroom"));
|
||||
break;
|
||||
case NEO_FORGED:
|
||||
case NEO_FORGE:
|
||||
content.addTag(i18n("install.installer.neoforge"));
|
||||
break;
|
||||
case FABRIC:
|
||||
@@ -493,6 +495,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
loadDependencies(version, selfPage, spinnerPane, dependenciesList);
|
||||
spinnerPane.setOnFailedAction(e -> loadDependencies(version, selfPage, spinnerPane, dependenciesList));
|
||||
|
||||
scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||
scrollPane.setContent(dependenciesList);
|
||||
scrollPane.setFitToWidth(true);
|
||||
scrollPane.setFitToHeight(true);
|
||||
|
||||
@@ -160,11 +160,11 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
|
||||
|
||||
if (analyzer.has(LibraryAnalyzer.LibraryType.FABRIC) && modManager.hasMod("kilt", ModLoaderType.FABRIC)) {
|
||||
supportedLoaders.add(ModLoaderType.FORGE);
|
||||
supportedLoaders.add(ModLoaderType.NEO_FORGED);
|
||||
supportedLoaders.add(ModLoaderType.NEO_FORGE);
|
||||
}
|
||||
|
||||
// Sinytra Connector
|
||||
if (analyzer.has(LibraryAnalyzer.LibraryType.NEO_FORGE) && modManager.hasMod("connectormod", ModLoaderType.NEO_FORGED)
|
||||
if (analyzer.has(LibraryAnalyzer.LibraryType.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);
|
||||
}
|
||||
|
||||
@@ -54,7 +54,10 @@ import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.FXThread;
|
||||
import org.jackhuang.hmcl.util.Lazy;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
@@ -154,6 +157,18 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
);
|
||||
|
||||
// Toolbar Selecting
|
||||
|
||||
// reason for not using selectAll() is that selectAll() first clears all selected then selects all, causing the toolbar to flicker
|
||||
var selectAll = createToolbarButton2(i18n("button.select_all"), SVG.SELECT_ALL, () -> listView.getSelectionModel().selectRange(0, listView.getItems().size()));
|
||||
|
||||
ListChangeListener<Object> listener = change -> {
|
||||
selectAll.setDisable(!listView.getItems().isEmpty()
|
||||
&& listView.getSelectionModel().getSelectedItems().size() == listView.getItems().size());
|
||||
};
|
||||
|
||||
listView.getSelectionModel().getSelectedItems().addListener(listener);
|
||||
listView.getItems().addListener(listener);
|
||||
|
||||
toolbarSelecting.getChildren().setAll(
|
||||
createToolbarButton2(i18n("button.remove"), SVG.DELETE_FOREVER, () -> {
|
||||
Controllers.confirm(i18n("button.remove.confirm"), i18n("button.remove"), () -> {
|
||||
@@ -171,8 +186,7 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
.toList()
|
||||
)
|
||||
),
|
||||
createToolbarButton2(i18n("button.select_all"), SVG.SELECT_ALL, () ->
|
||||
listView.getSelectionModel().selectAll()),
|
||||
selectAll,
|
||||
createToolbarButton2(i18n("button.cancel"), SVG.CANCEL, () ->
|
||||
listView.getSelectionModel().clearSelection())
|
||||
);
|
||||
@@ -473,7 +487,7 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
case FORGE -> i18n("install.installer.forge");
|
||||
case CLEANROOM -> i18n("install.installer.cleanroom");
|
||||
case LEGACY_FABRIC -> i18n("install.installer.legacyfabric");
|
||||
case NEO_FORGED -> i18n("install.installer.neoforge");
|
||||
case NEO_FORGE -> i18n("install.installer.neoforge");
|
||||
case FABRIC -> i18n("install.installer.fabric");
|
||||
case LITE_LOADER -> i18n("install.installer.liteloader");
|
||||
case QUILT -> i18n("install.installer.quilt");
|
||||
@@ -643,7 +657,7 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
case FORGE -> content.addTagWarning(i18n("install.installer.forge"));
|
||||
case LEGACY_FABRIC -> content.addTagWarning(i18n("install.installer.legacyfabric"));
|
||||
case CLEANROOM -> content.addTagWarning(i18n("install.installer.cleanroom"));
|
||||
case NEO_FORGED -> content.addTagWarning(i18n("install.installer.neoforge"));
|
||||
case NEO_FORGE -> content.addTagWarning(i18n("install.installer.neoforge"));
|
||||
case FABRIC -> content.addTagWarning(i18n("install.installer.fabric"));
|
||||
case LITE_LOADER -> content.addTagWarning(i18n("install.installer.liteloader"));
|
||||
case QUILT -> content.addTagWarning(i18n("install.installer.quilt"));
|
||||
|
||||
@@ -23,7 +23,10 @@ import com.jfoenix.controls.JFXListView;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.PixelWriter;
|
||||
import javafx.scene.image.WritableImage;
|
||||
@@ -157,20 +160,9 @@ public final class SchematicsPage extends ListPageBase<SchematicsPage.Item> impl
|
||||
return;
|
||||
|
||||
Path parent = currentDirectory.path;
|
||||
Controllers.dialog(new InputDialogPane(
|
||||
Controllers.prompt(
|
||||
i18n("schematics.create_directory.prompt"),
|
||||
"",
|
||||
(result, handler) -> {
|
||||
if (StringUtils.isBlank(result)) {
|
||||
handler.reject(i18n("schematics.create_directory.failed.empty_name"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.contains("/") || result.contains("\\") || !FileUtils.isNameValid(result)) {
|
||||
handler.reject(i18n("schematics.create_directory.failed.invalid_name"));
|
||||
return;
|
||||
}
|
||||
|
||||
Path targetDir = parent.resolve(result);
|
||||
if (Files.exists(targetDir)) {
|
||||
handler.reject(i18n("schematics.create_directory.failed.already_exists"));
|
||||
@@ -185,7 +177,7 @@ public final class SchematicsPage extends ListPageBase<SchematicsPage.Item> impl
|
||||
LOG.warning("Failed to create directory: " + targetDir, e);
|
||||
handler.reject(i18n("schematics.create_directory.failed", targetDir));
|
||||
}
|
||||
}));
|
||||
}, "", new RequiredValidator(), new Validator(i18n("schematics.create_directory.failed.invalid_name"), FileUtils::isNameValid));
|
||||
}
|
||||
|
||||
private DirItem loadAll(Path dir, @Nullable DirItem parent) {
|
||||
|
||||
@@ -239,7 +239,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
gameDirItem.disableProperty().bind(modpack);
|
||||
gameDirCustomOption = new MultiFileItem.FileOption<>(i18n("settings.custom"), GameDirectoryType.CUSTOM)
|
||||
.setChooserTitle(i18n("settings.game.working_directory.choose"))
|
||||
.setDirectory(true);
|
||||
.setSelectionMode(FileSelector.SelectionMode.DIRECTORY);
|
||||
|
||||
gameDirItem.loadChildren(Arrays.asList(
|
||||
new MultiFileItem.Option<>(i18n("settings.advanced.game_dir.default"), GameDirectoryType.ROOT_FOLDER),
|
||||
@@ -336,7 +336,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
launcherVisibilityPane = new LineSelectButton<>();
|
||||
launcherVisibilityPane.setTitle(i18n("settings.advanced.launcher_visible"));
|
||||
launcherVisibilityPane.setItems(LauncherVisibility.values());
|
||||
launcherVisibilityPane.setConverter(e -> i18n("settings.advanced.launcher_visibility." + e.name().toLowerCase(Locale.ROOT)));
|
||||
launcherVisibilityPane.setNullSafeConverter(e -> i18n("settings.advanced.launcher_visibility." + e.name().toLowerCase(Locale.ROOT)));
|
||||
|
||||
BorderPane dimensionPane = new BorderPane();
|
||||
{
|
||||
@@ -351,7 +351,6 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
cboWindowsSize.setPrefWidth(150);
|
||||
right.setLeft(cboWindowsSize);
|
||||
cboWindowsSize.setEditable(true);
|
||||
cboWindowsSize.setStyle("-fx-padding: 4 4 4 16");
|
||||
cboWindowsSize.setPromptText("854x480");
|
||||
cboWindowsSize.getItems().setAll(getSupportedResolutions());
|
||||
|
||||
@@ -373,7 +372,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
enableDebugLogOutputPane.setTitle(i18n("settings.enable_debug_log_output"));
|
||||
processPriorityPane = new LineSelectButton<>();
|
||||
processPriorityPane.setTitle(i18n("settings.advanced.process_priority"));
|
||||
processPriorityPane.setConverter(e -> i18n("settings.advanced.process_priority." + e.name().toLowerCase(Locale.ROOT)));
|
||||
processPriorityPane.setNullSafeConverter(e -> i18n("settings.advanced.process_priority." + e.name().toLowerCase(Locale.ROOT)));
|
||||
processPriorityPane.setDescriptionConverter(e -> {
|
||||
String bundleKey = "settings.advanced.process_priority." + e.name().toLowerCase(Locale.ROOT) + ".desc";
|
||||
return I18n.hasKey(bundleKey) ? i18n(bundleKey) : null;
|
||||
|
||||
@@ -22,8 +22,11 @@ import javafx.application.Platform;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.game.GameAssetDownloadTask;
|
||||
import org.jackhuang.hmcl.download.game.GameDownloadTask;
|
||||
import org.jackhuang.hmcl.download.game.GameLibrariesTask;
|
||||
import org.jackhuang.hmcl.game.*;
|
||||
import org.jackhuang.hmcl.mod.RemoteMod;
|
||||
import org.jackhuang.hmcl.setting.*;
|
||||
@@ -146,7 +149,8 @@ public final class Versions {
|
||||
} else {
|
||||
handler.reject(i18n("version.manage.rename.fail"));
|
||||
}
|
||||
}, version, new Validator(i18n("install.new_game.malformed"), HMCLGameRepository::isValidVersionId),
|
||||
}, version,
|
||||
new Validator(i18n("install.new_game.malformed"), HMCLGameRepository::isValidVersionId),
|
||||
new Validator(i18n("install.new_game.already_exists"), newVersionName -> !profile.getRepository().versionIdConflicts(newVersionName) || newVersionName.equals(version)));
|
||||
}
|
||||
|
||||
@@ -158,6 +162,43 @@ public final class Versions {
|
||||
FXUtils.openFolder(profile.getRepository().getRunDirectory(version));
|
||||
}
|
||||
|
||||
public static void installFromJson(Profile profile, Path file) {
|
||||
Version version;
|
||||
try {
|
||||
version = profile.getRepository().readVersionJson(file);
|
||||
} catch (Exception e) {
|
||||
Controllers.dialog(i18n("install.new_game.malformed_json"), i18n("message.error"), MessageDialogPane.MessageType.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
Controllers.prompt(i18n("version.manage.duplicate.prompt"), (result, handler) -> {
|
||||
handler.resolve();
|
||||
|
||||
DefaultDependencyManager dependencyManager = profile.getDependency();
|
||||
HMCLGameRepository repository = profile.getRepository();
|
||||
Version newVersion = version.setId(result).setJar(result);
|
||||
|
||||
Controllers.taskDialog(
|
||||
Task.allOf(new GameDownloadTask(dependencyManager, null, newVersion),
|
||||
Task.allOf(
|
||||
new GameAssetDownloadTask(dependencyManager, newVersion, GameAssetDownloadTask.DOWNLOAD_INDEX_FORCIBLY, true),
|
||||
new GameLibrariesTask(dependencyManager, newVersion, true)
|
||||
).withRunAsync(() -> {
|
||||
// ignore failure
|
||||
}))
|
||||
.thenComposeAsync(repository.saveAsync(newVersion))
|
||||
.thenRunAsync(repository::refreshVersions)
|
||||
.whenComplete(Schedulers.javafx(), (exception) -> {
|
||||
if (exception == null) {
|
||||
profile.setSelectedVersion(result);
|
||||
} else {
|
||||
Controllers.dialog(
|
||||
DownloadProviders.localizeErrorMessage(exception), i18n("install.failed"), MessageDialogPane.MessageType.ERROR);
|
||||
}
|
||||
}), i18n("install.new_game"), TaskCancellationAction.NORMAL);
|
||||
}, FileUtils.getNameWithoutExtension(file), new Validator(i18n("install.new_game.malformed"), HMCLGameRepository::isValidVersionId), new Validator(i18n("install.new_game.already_exists"), newVersionName -> !profile.getRepository().versionIdConflicts(newVersionName)));
|
||||
}
|
||||
|
||||
public static void duplicateVersion(Profile profile, String version) {
|
||||
Controllers.prompt(
|
||||
new PromptDialogPane.Builder(i18n("version.manage.duplicate.prompt"), (res, handler) -> {
|
||||
@@ -223,6 +264,11 @@ public final class Versions {
|
||||
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("extension.ps1"), "*.ps1"));
|
||||
Path file = FileUtils.toPath(chooser.showSaveDialog(Controllers.getStage()));
|
||||
if (file != null) {
|
||||
if (!isValidScriptExtension(FileUtils.getExtension(file))) {
|
||||
String defaultExt = getDefaultScriptExtension();
|
||||
file = file.resolveSibling(file.getFileName().toString() + "." + defaultExt);
|
||||
}
|
||||
|
||||
LauncherHelper launcherHelper = new LauncherHelper(profile, account, id);
|
||||
for (Consumer<LauncherHelper> injecter : injecters) {
|
||||
injecter.accept(launcherHelper);
|
||||
@@ -232,6 +278,21 @@ public final class Versions {
|
||||
});
|
||||
}
|
||||
|
||||
private static boolean isValidScriptExtension(String ext) {
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
return ext.equalsIgnoreCase("bat") || ext.equalsIgnoreCase("ps1");
|
||||
}
|
||||
return ext.equalsIgnoreCase("sh") || ext.equalsIgnoreCase("bash") || ext.equalsIgnoreCase("command") || ext.equalsIgnoreCase("ps1");
|
||||
}
|
||||
|
||||
private static String getDefaultScriptExtension() {
|
||||
return switch (OperatingSystem.CURRENT_OS) {
|
||||
case WINDOWS -> "bat";
|
||||
case MACOS -> "command";
|
||||
default -> "sh";
|
||||
};
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static void launch(Profile profile, String id, Consumer<LauncherHelper>... injecters) {
|
||||
if (!checkVersionForLaunching(profile, id))
|
||||
|
||||
@@ -40,10 +40,12 @@ import org.jackhuang.hmcl.ui.construct.RipplerContainer;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
@@ -153,7 +155,7 @@ public final class WorldBackupsPage extends ListPageBase<WorldBackupsPage.Backup
|
||||
LOG.warning("Failed to create backup", exception);
|
||||
Controllers.dialog(i18n("world.backup.create.failed", StringUtils.getStackTrace(exception)), null, MessageDialogPane.MessageType.WARNING);
|
||||
}
|
||||
}), i18n("world.backup"), null);
|
||||
}), i18n("world.backup"), TaskCancellationAction.NO_CANCEL);
|
||||
}
|
||||
|
||||
private final class WorldBackupsPageSkin extends ToolbarListPageSkin<BackupInfo, WorldBackupsPage> {
|
||||
|
||||
@@ -24,13 +24,11 @@ import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.effect.BoxBlur;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.glavo.nbt.tag.*;
|
||||
@@ -162,11 +160,10 @@ public final class WorldInfoPage extends SpinnerPane implements WorldManagePage.
|
||||
seedPane.setTitle(i18n("world.info.random_seed"));
|
||||
|
||||
SimpleBooleanProperty visibility = new SimpleBooleanProperty();
|
||||
StackPane visibilityButton = new StackPane();
|
||||
JFXButton visibilityButton = FXUtils.newToggleButton4(SVG.VISIBILITY_OFF);
|
||||
{
|
||||
visibilityButton.setCursor(Cursor.HAND);
|
||||
visibilityButton.setAlignment(Pos.CENTER_RIGHT);
|
||||
FXUtils.onClicked(visibilityButton, () -> visibility.set(!visibility.get()));
|
||||
visibilityButton.setAlignment(Pos.CENTER);
|
||||
visibilityButton.setOnAction((event) -> visibility.set(!visibility.get()));
|
||||
}
|
||||
|
||||
Label seedLabel = new Label();
|
||||
@@ -180,7 +177,7 @@ public final class WorldInfoPage extends SpinnerPane implements WorldManagePage.
|
||||
blur.setIterations(3);
|
||||
FXUtils.onChangeAndOperate(visibility, isVisibility -> {
|
||||
SVG icon = isVisibility ? SVG.VISIBILITY : SVG.VISIBILITY_OFF;
|
||||
visibilityButton.getChildren().setAll(icon.createIcon(12));
|
||||
visibilityButton.setGraphic(icon.createIcon(16));
|
||||
seedLabel.setEffect(isVisibility ? null : blur);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -23,8 +23,9 @@ import org.jackhuang.hmcl.game.WorldLockedException;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.construct.InputDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.RequiredValidator;
|
||||
import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
import org.jackhuang.hmcl.ui.wizard.SinglePageWizardProvider;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
@@ -89,20 +90,9 @@ public final class WorldManageUIUtils {
|
||||
|
||||
public static void copyWorld(World world, Runnable runnable) {
|
||||
Path worldPath = world.getFile();
|
||||
Controllers.dialog(new InputDialogPane(
|
||||
Controllers.prompt(
|
||||
i18n("world.duplicate.prompt"),
|
||||
"",
|
||||
(result, handler) -> {
|
||||
if (StringUtils.isBlank(result)) {
|
||||
handler.reject(i18n("world.duplicate.failed.empty_name"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.contains("/") || result.contains("\\") || !FileUtils.isNameValid(result)) {
|
||||
handler.reject(i18n("world.duplicate.failed.invalid_name"));
|
||||
return;
|
||||
}
|
||||
|
||||
Path targetDir = worldPath.resolveSibling(result);
|
||||
if (Files.exists(targetDir)) {
|
||||
handler.reject(i18n("world.duplicate.failed.already_exists"));
|
||||
@@ -125,7 +115,7 @@ public final class WorldManageUIUtils {
|
||||
}
|
||||
})
|
||||
.start();
|
||||
}));
|
||||
}, "", new RequiredValidator(), new Validator(i18n("world.duplicate.failed.invalid_name"), FileUtils::isNameValid));
|
||||
}
|
||||
|
||||
public static void closeSessionLockChannel(World world, FileChannel sessionLockChannel) throws IOException {
|
||||
|
||||
@@ -27,8 +27,8 @@ final class HMCLDownloadTask extends FileDownloadTask {
|
||||
private final RemoteVersion.Type archiveFormat;
|
||||
|
||||
public HMCLDownloadTask(RemoteVersion version, Path target) {
|
||||
super(version.getUrl(), target, version.getIntegrityCheck());
|
||||
archiveFormat = version.getType();
|
||||
super(version.url(), target, version.integrityCheck());
|
||||
archiveFormat = version.type();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -23,11 +23,13 @@ import com.google.gson.JsonParseException;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask.IntegrityCheck;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
|
||||
public final class RemoteVersion {
|
||||
public record RemoteVersion(UpdateChannel channel, String version, String url, Type type, IntegrityCheck integrityCheck,
|
||||
boolean preview, boolean force) {
|
||||
|
||||
public static RemoteVersion fetch(UpdateChannel channel, boolean preview, String url) throws IOException {
|
||||
try {
|
||||
@@ -46,54 +48,8 @@ public final class RemoteVersion {
|
||||
}
|
||||
}
|
||||
|
||||
private final UpdateChannel channel;
|
||||
private final String version;
|
||||
private final String url;
|
||||
private final Type type;
|
||||
private final IntegrityCheck integrityCheck;
|
||||
private final boolean preview;
|
||||
private final boolean force;
|
||||
|
||||
public RemoteVersion(UpdateChannel channel, String version, String url, Type type, IntegrityCheck integrityCheck, boolean preview, boolean force) {
|
||||
this.channel = channel;
|
||||
this.version = version;
|
||||
this.url = url;
|
||||
this.type = type;
|
||||
this.integrityCheck = integrityCheck;
|
||||
this.preview = preview;
|
||||
this.force = force;
|
||||
}
|
||||
|
||||
public UpdateChannel getChannel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public IntegrityCheck getIntegrityCheck() {
|
||||
return integrityCheck;
|
||||
}
|
||||
|
||||
public boolean isPreview() {
|
||||
return preview;
|
||||
}
|
||||
|
||||
public boolean isForce() {
|
||||
return force;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
public @NotNull String toString() {
|
||||
return "[" + version + " from " + url + "]";
|
||||
}
|
||||
|
||||
|
||||
@@ -43,13 +43,13 @@ public final class UpdateChecker {
|
||||
RemoteVersion latest = latestVersion.get();
|
||||
if (latest == null || isDevelopmentVersion(Metadata.VERSION)) {
|
||||
return false;
|
||||
} else if (latest.isForce()
|
||||
} else if (latest.force()
|
||||
|| Metadata.isNightly()
|
||||
|| latest.getChannel() == UpdateChannel.NIGHTLY
|
||||
|| latest.getChannel() != UpdateChannel.getChannel()) {
|
||||
return !latest.getVersion().equals(Metadata.VERSION);
|
||||
|| latest.channel() == UpdateChannel.NIGHTLY
|
||||
|| latest.channel() != UpdateChannel.getChannel()) {
|
||||
return !latest.version().equals(Metadata.VERSION);
|
||||
} else {
|
||||
return VersionNumber.compare(Metadata.VERSION, latest.getVersion()) < 0;
|
||||
return VersionNumber.compare(Metadata.VERSION, latest.version()) < 0;
|
||||
}
|
||||
},
|
||||
latestVersion);
|
||||
|
||||
@@ -20,20 +20,20 @@ package org.jackhuang.hmcl.upgrade;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonParseException;
|
||||
import javafx.application.Platform;
|
||||
|
||||
import org.jackhuang.hmcl.EntryPoint;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.java.JavaRuntime;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.UpgradeDialog;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
||||
import org.jackhuang.hmcl.util.FileSaver;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.SwingUtils;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.io.JarUtils;
|
||||
import org.jackhuang.hmcl.java.JavaRuntime;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -43,13 +43,15 @@ import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.checkFxUserThread;
|
||||
import static org.jackhuang.hmcl.util.Lang.thread;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public final class UpdateHandler {
|
||||
private UpdateHandler() {
|
||||
@@ -125,6 +127,29 @@ public final class UpdateHandler {
|
||||
throw new IOException("Current JAR is not verified");
|
||||
}
|
||||
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
|
||||
Platform.runLater(() -> {
|
||||
try {
|
||||
Controllers.saveWindowStates();
|
||||
} finally {
|
||||
future.complete(null);
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
future.get();
|
||||
} catch (ExecutionException | InterruptedException ignored) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
FileSaver.waitForAllSaves();
|
||||
} catch (InterruptedException ignored) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
requestUpdate(downloaded, getCurrentLocation());
|
||||
EntryPoint.exit(0);
|
||||
} catch (IOException e) {
|
||||
|
||||
@@ -189,15 +189,16 @@ public final class NativePatcher {
|
||||
return version.setLibraries(newLibraries);
|
||||
}
|
||||
|
||||
public static @Nullable Library getWindowsMesaLoader(@NotNull JavaRuntime javaVersion, @NotNull Renderer renderer, @NotNull OSVersion windowsVersion) {
|
||||
/// @see <a href="https://github.com/HMCL-dev/mesa-loader-windows">Java Mesa Loader for Windows</a>
|
||||
public static @Nullable Library getWindowsMesaLoader(@NotNull JavaRuntime java, @NotNull Renderer renderer, @NotNull OSVersion windowsVersion) {
|
||||
if (renderer == Renderer.DEFAULT)
|
||||
return null;
|
||||
|
||||
if (windowsVersion.isAtLeast(OSVersion.WINDOWS_10)) {
|
||||
return getNatives(javaVersion.getPlatform()).get("mesa-loader");
|
||||
return getNatives(java.getPlatform()).get("mesa-loader");
|
||||
} else if (windowsVersion.isAtLeast(OSVersion.WINDOWS_7)) {
|
||||
if (renderer == Renderer.LLVMPIPE)
|
||||
return getNatives(javaVersion.getPlatform()).get("software-renderer-loader");
|
||||
if (renderer == Renderer.OpenGL.LLVMPIPE)
|
||||
return getNatives(java.getPlatform()).get("software-renderer-loader");
|
||||
else
|
||||
return null;
|
||||
} else {
|
||||
|
||||
@@ -1,162 +0,0 @@
|
||||
// Copy from javafx.swing
|
||||
/*
|
||||
* Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code 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
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import javafx.scene.image.*;
|
||||
import javafx.scene.image.Image;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.DataBufferInt;
|
||||
import java.awt.image.SampleModel;
|
||||
import java.awt.image.SinglePixelPackedSampleModel;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
/**
|
||||
* This class provides utility methods for converting data types between
|
||||
* Swing/AWT and JavaFX formats.
|
||||
*
|
||||
* @since JavaFX 2.2
|
||||
*/
|
||||
public final class SwingFXUtils {
|
||||
private SwingFXUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Snapshots the specified {@link BufferedImage} and stores a copy of
|
||||
* its pixels into a JavaFX {@link Image} object, creating a new
|
||||
* object if needed.
|
||||
* The returned {@code Image} will be a static snapshot of the state
|
||||
* of the pixels in the {@code BufferedImage} at the time the method
|
||||
* completes. Further changes to the {@code BufferedImage} will not
|
||||
* be reflected in the {@code Image}.
|
||||
* <p>
|
||||
* The optional JavaFX {@link WritableImage} parameter may be reused
|
||||
* to store the copy of the pixels.
|
||||
* A new {@code Image} will be created if the supplied object is null,
|
||||
* is too small or of a type which the image pixels cannot be easily
|
||||
* converted into.
|
||||
*
|
||||
* @param bimg the {@code BufferedImage} object to be converted
|
||||
* @param wimg an optional {@code WritableImage} object that can be
|
||||
* used to store the returned pixel data
|
||||
* @return an {@code Image} object representing a snapshot of the
|
||||
* current pixels in the {@code BufferedImage}.
|
||||
* @since JavaFX 2.2
|
||||
*/
|
||||
public static WritableImage toFXImage(BufferedImage bimg, WritableImage wimg) {
|
||||
int bw = bimg.getWidth();
|
||||
int bh = bimg.getHeight();
|
||||
switch (bimg.getType()) {
|
||||
case BufferedImage.TYPE_INT_ARGB:
|
||||
case BufferedImage.TYPE_INT_ARGB_PRE:
|
||||
break;
|
||||
default:
|
||||
BufferedImage converted =
|
||||
new BufferedImage(bw, bh, BufferedImage.TYPE_INT_ARGB_PRE);
|
||||
Graphics2D g2d = converted.createGraphics();
|
||||
g2d.drawImage(bimg, 0, 0, null);
|
||||
g2d.dispose();
|
||||
bimg = converted;
|
||||
break;
|
||||
}
|
||||
// assert(bimg.getType == TYPE_INT_ARGB[_PRE]);
|
||||
if (wimg != null) {
|
||||
int iw = (int) wimg.getWidth();
|
||||
int ih = (int) wimg.getHeight();
|
||||
if (iw < bw || ih < bh) {
|
||||
wimg = null;
|
||||
} else if (bw < iw || bh < ih) {
|
||||
int[] empty = new int[iw];
|
||||
PixelWriter pw = wimg.getPixelWriter();
|
||||
PixelFormat<IntBuffer> pf = PixelFormat.getIntArgbPreInstance();
|
||||
if (bw < iw) {
|
||||
pw.setPixels(bw, 0, iw - bw, bh, pf, empty, 0, 0);
|
||||
}
|
||||
if (bh < ih) {
|
||||
pw.setPixels(0, bh, iw, ih - bh, pf, empty, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (wimg == null) {
|
||||
wimg = new WritableImage(bw, bh);
|
||||
}
|
||||
PixelWriter pw = wimg.getPixelWriter();
|
||||
DataBufferInt db = (DataBufferInt) bimg.getRaster().getDataBuffer();
|
||||
int[] data = db.getData();
|
||||
int offset = bimg.getRaster().getDataBuffer().getOffset();
|
||||
int scan = 0;
|
||||
SampleModel sm = bimg.getRaster().getSampleModel();
|
||||
if (sm instanceof SinglePixelPackedSampleModel) {
|
||||
scan = ((SinglePixelPackedSampleModel) sm).getScanlineStride();
|
||||
}
|
||||
|
||||
PixelFormat<IntBuffer> pf = (bimg.isAlphaPremultiplied() ?
|
||||
PixelFormat.getIntArgbPreInstance() :
|
||||
PixelFormat.getIntArgbInstance());
|
||||
pw.setPixels(0, 0, bw, bh, pf, data, offset, scan);
|
||||
return wimg;
|
||||
}
|
||||
|
||||
public static WritableImage toFXImage(BufferedImage bimg, double requestedWidth, double requestedHeight, boolean preserveRatio, boolean smooth) {
|
||||
if (requestedWidth <= 0. || requestedHeight <= 0.) {
|
||||
return toFXImage(bimg, null);
|
||||
}
|
||||
|
||||
int width = (int) requestedWidth;
|
||||
int height = (int) requestedHeight;
|
||||
|
||||
// Calculate actual dimensions if preserveRatio is true
|
||||
if (preserveRatio) {
|
||||
double originalWidth = bimg.getWidth();
|
||||
double originalHeight = bimg.getHeight();
|
||||
double scaleX = requestedWidth / originalWidth;
|
||||
double scaleY = requestedHeight / originalHeight;
|
||||
double scale = Math.min(scaleX, scaleY);
|
||||
|
||||
width = (int) (originalWidth * scale);
|
||||
height = (int) (originalHeight * scale);
|
||||
}
|
||||
|
||||
// Create scaled BufferedImage
|
||||
BufferedImage scaledImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB_PRE);
|
||||
Graphics2D g2d = scaledImage.createGraphics();
|
||||
try {
|
||||
if (smooth) {
|
||||
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
}
|
||||
|
||||
g2d.drawImage(bimg, 0, 0, width, height, null);
|
||||
} finally {
|
||||
g2d.dispose();
|
||||
}
|
||||
|
||||
return toFXImage(scaledImage, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,6 +96,10 @@ public class Translator_lzh extends Translator {
|
||||
case GA -> {
|
||||
// do nothing
|
||||
}
|
||||
case SNAPSHOT -> {
|
||||
builder.append("之快照");
|
||||
appendDigitByDigit(builder, release.getEaVersion().toString());
|
||||
}
|
||||
case PRE_RELEASE -> {
|
||||
builder.append("之預");
|
||||
appendDigitByDigit(builder, release.getEaVersion().toString());
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
{
|
||||
"title" : "HelloNBT",
|
||||
"subtitle" : "Copyright © 2026 Glavo.\nLicensed under the Apache 2.0 License.",
|
||||
"externalLink" : "https://github.com/Glavo/HelloNBT"
|
||||
"externalLink" : "https://github.com/HMCL-dev/HelloNBT"
|
||||
},
|
||||
{
|
||||
"title" : "minecraft-jfx-skin",
|
||||
@@ -88,5 +88,20 @@
|
||||
"title": "MonetFX",
|
||||
"subtitle": "Copyright © 2025 Glavo.\nLicensed under the Apache 2.0 License.",
|
||||
"externalLink": "https://github.com/Glavo/MonetFX"
|
||||
},
|
||||
{
|
||||
"title": "LWJGL Unsafe Agent",
|
||||
"subtitle": "Copyright © 2026 Glavo.\nLicensed under the Apache 2.0 License.",
|
||||
"externalLink": "https://github.com/HMCL-dev/lwjgl-unsafe-agent"
|
||||
},
|
||||
{
|
||||
"title": "TomlJ",
|
||||
"subtitle": "Licensed under the Apache 2.0 License.",
|
||||
"externalLink": "https://github.com/tomlj/tomlj"
|
||||
},
|
||||
{
|
||||
"title": "WebURL for Java",
|
||||
"subtitle": "Licensed under the Apache 2.0 License.",
|
||||
"externalLink": "https://github.com/Glavo/weburl-java"
|
||||
}
|
||||
]
|
||||
@@ -70,6 +70,12 @@
|
||||
"subtitleLocalized" : "about.thanks_to.mcim.statement",
|
||||
"externalLink" : "https://github.com/mcmod-info-mirror"
|
||||
},
|
||||
{
|
||||
"image" : "/assets/img/8mi-tech.png",
|
||||
"titleLocalized" : "about.thanks_to.8mi-tech",
|
||||
"subtitleLocalized" : "about.thanks_to.8mi-tech.statement",
|
||||
"externalLink" : "https://8mi.tech"
|
||||
},
|
||||
{
|
||||
"image" : {
|
||||
"light" : "/assets/img/github.png",
|
||||
|
||||
@@ -29,8 +29,8 @@
|
||||
|
||||
.scroll-bar .thumb {
|
||||
-fx-fill: -monet-surface-tint;
|
||||
-fx-arc-width: 8;
|
||||
-fx-arc-height: 8;
|
||||
-fx-arc-width: 6;
|
||||
-fx-arc-height: 6;
|
||||
}
|
||||
|
||||
.title-label {
|
||||
@@ -414,7 +414,7 @@
|
||||
}
|
||||
|
||||
.installer-item-wrapper .installer-item:list-item > .installer-item-name {
|
||||
-fx-pref-width: 80px;
|
||||
-fx-pref-width: 100px;
|
||||
}
|
||||
|
||||
.installer-item-wrapper .installer-item:list-item > .installer-item-status {
|
||||
@@ -1093,8 +1093,12 @@
|
||||
-fx-background-color: -monet-error-container;
|
||||
}
|
||||
|
||||
.multi-file-item {
|
||||
-fx-padding: 10 0 10 0;
|
||||
}
|
||||
|
||||
.options-sublist {
|
||||
-fx-background-color: -monet-surface;
|
||||
-fx-background-color: -monet-surface-container;
|
||||
}
|
||||
|
||||
.options-list-item {
|
||||
@@ -1105,6 +1109,14 @@
|
||||
-fx-font-size: 12;
|
||||
}
|
||||
|
||||
.options-sublist .options-list-item {
|
||||
-fx-background-color: -monet-surface-container;
|
||||
}
|
||||
|
||||
.options-sublist .multi-file-item {
|
||||
-fx-padding: 10 10 10 10;
|
||||
}
|
||||
|
||||
.options-list-item .svg {
|
||||
-fx-fill: -monet-on-surface;
|
||||
-fx-border-color: -monet-outline-variant;
|
||||
@@ -1140,6 +1152,10 @@
|
||||
-fx-border-width: 0;
|
||||
}
|
||||
|
||||
.options-sublist-wrapper > .container {
|
||||
-fx-background-color: -monet-surface-container;
|
||||
}
|
||||
|
||||
.options-sublist-wrapper .expand-icon .svg {
|
||||
-fx-fill: -monet-on-surface-variant;
|
||||
}
|
||||
@@ -1482,6 +1498,11 @@
|
||||
-fx-max-width: 1000000000;
|
||||
}
|
||||
|
||||
.jfx-combo-box .arrow {
|
||||
-fx-background-color: -monet-on-surface-variant;
|
||||
-fx-shape: "M12 15 7 10H17L12 15Z";
|
||||
}
|
||||
|
||||
.jfx-combo-box .text {
|
||||
-fx-fill: -monet-on-surface;
|
||||
}
|
||||
@@ -1497,6 +1518,7 @@
|
||||
.combo-box-popup .list-view .jfx-list-cell {
|
||||
-fx-background-color: -monet-surface-container;
|
||||
-fx-text-fill: -monet-on-surface;
|
||||
-fx-cursor: hand;
|
||||
-fx-background-insets: 0.0;
|
||||
}
|
||||
|
||||
@@ -1506,6 +1528,10 @@
|
||||
-fx-text-fill: -monet-on-secondary-container;
|
||||
}
|
||||
|
||||
.combo-box-popup .list-view .list-cell:hover > .jfx-rippler > StackPane {
|
||||
-fx-background-color: -monet-secondary-container-transparent-50;
|
||||
}
|
||||
|
||||
.jfx-combo-box-warning {
|
||||
-jfx-focus-color: #D34336;
|
||||
-jfx-unfocus-color: #D34336;
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.6 KiB |
@@ -48,6 +48,8 @@ about.thanks_to.shulkersakura.statement=For providing the logo for HMCL.
|
||||
about.thanks_to.users=HMCL User Group Members
|
||||
about.thanks_to.users.statement=Thanks for donations, bug reports, and so on.
|
||||
about.thanks_to.yushijinhun.statement=For providing the authlib-injector related support.
|
||||
about.thanks_to.8mi-tech=8Mi-Tech (Quanzhou) Co., Ltd.
|
||||
about.thanks_to.8mi-tech.statement=Provides an accelerated download service for some of the game's static resources.
|
||||
about.open_source=Open Source
|
||||
about.open_source.statement=GPL v3 (https://github.com/HMCL-dev/HMCL)
|
||||
|
||||
@@ -108,9 +110,9 @@ account.methods.microsoft.close_page=Microsoft account authorization is now comp
|
||||
account.methods.microsoft.deauthorize=Deauthorize
|
||||
account.methods.microsoft.error.add_family=Please click <a href="https://support.microsoft.com/account-billing/837badbc-999e-54d2-2617-d19206b9540a">here</a> to change your account birth date to be over 18 years old, or add your account to a family.
|
||||
account.methods.microsoft.error.country_unavailable=Xbox Live is not available in your current country/region.
|
||||
account.methods.microsoft.error.missing_xbox_account=Your Microsoft account does not have a linked Xbox account yet. Please click <a href="https://www.minecraft.net/msaprofile/mygames/editprofile">here</a> to link one.
|
||||
account.methods.microsoft.error.no_character=Please confirm that you have purchased Minecraft: Java Edition.\n\
|
||||
If you have already purchased it, a game profile may not have been created. Please click <a href="https://www.minecraft.net/msaprofile/mygames/editprofile">here</a> to create a game profile.
|
||||
account.methods.microsoft.error.missing_xbox_account=Please confirm that you have purchased Minecraft: Java Edition. If you have, please click <a href="https://www.minecraft.net/msaprofile/mygames/editprofile">here</a> to create your game profile.
|
||||
account.methods.microsoft.error.no_license=No Minecraft purchase record found. Please click <a href="https://www.xbox.com/games/store/minecraft-java-bedrock-edition-for-pc/9nxp44l49shj">here</a> to get the game.\nIf you are using Game Pass, please create your game profile <a href="https://www.minecraft.net/msaprofile/mygames/editprofile">here</a>. If you have already purchased the game, please check if you are logged into the correct account.
|
||||
account.methods.microsoft.error.no_character=You have not created a game profile yet. Please click <a href="https://www.minecraft.net/msaprofile/mygames/editprofile">here</a> to create one.
|
||||
account.methods.microsoft.error.banned=Your account may have been banned by Xbox Live.\n\
|
||||
You can click <a href="https://enforcement.xbox.com/enforcement/showenforcementhistory">here</a> to check the ban status of your account.
|
||||
account.methods.microsoft.error.unknown=Failed to log in, error code: %d.
|
||||
@@ -363,16 +365,16 @@ download.failed.empty=No versions are available. Please click here to go back.
|
||||
download.failed.no_code=Failed to download
|
||||
download.failed.refresh=Failed to fetch version list. Please click here to retry.
|
||||
download.game=New Game
|
||||
download.provider.bmclapi=BMCLAPI
|
||||
download.provider.bmclapi.desc=bangbang93, https://bmclapi2.bangbang93.com/
|
||||
download.provider.bmclapi=BMCLAPI & MCIM
|
||||
download.provider.bmclapi.desc=This option is provided exclusively for users in Chinese Mainland.
|
||||
download.provider.mojang=Official
|
||||
download.provider.mojang.desc=OptiFine is provided by BMCLAPI
|
||||
download.provider.mojang.desc=Note that OptiFine is provided by BMCLAPI.
|
||||
download.provider.official=From Official Sources
|
||||
download.provider.official.desc=Latest, but may load slowly
|
||||
download.provider.official.desc=Always obtain from official sources.
|
||||
download.provider.balanced=From Fastest Available
|
||||
download.provider.balanced.desc=Balanced, but may not be the latest
|
||||
download.provider.balanced.desc=Best choice for ALL users.
|
||||
download.provider.mirror=From Mirror
|
||||
download.provider.mirror.desc=Fast, but may not be the latest
|
||||
download.provider.mirror.desc=Recommended option for users in Chinese Mainland.
|
||||
download.java=Downloading Java
|
||||
download.java.process=Java Download Process
|
||||
download.javafx=Downloading dependencies for the launcher...
|
||||
@@ -452,8 +454,6 @@ fatal.unsupported_platform=Minecraft is not fully supported on your platform yet
|
||||
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.
|
||||
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.macos_arm64=Hello Minecraft! Launcher has provided support for the Apple silicon platform, using native ARM Java to launch games to get a smoother gaming experience.\n\
|
||||
If you encounter problems when playing a game, launching the game with Java based on x86-64 architecture may offer better compatibility.
|
||||
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\
|
||||
\n\
|
||||
If you are using the <b>Qualcomm</b> platform, you may need to install the <a href="ms-windows-store://pdp/?productid=9NQPSL29BFFF">OpenGL Compatibility Pack</a> before playing games.\n\
|
||||
@@ -762,6 +762,7 @@ install.new_game.already_exists=This instance name already exists. Please use an
|
||||
install.new_game.current_game_version=Current Instance Version
|
||||
install.new_game.installation=Instance Installation
|
||||
install.new_game.malformed=Invalid name.
|
||||
install.new_game.malformed_json=Invalid Minecraft instance JSON file.
|
||||
install.select=Choose operation
|
||||
install.success=Successfully installed.
|
||||
|
||||
@@ -1231,7 +1232,7 @@ world.load.fail=Failed to load world
|
||||
world.locked=In use
|
||||
world.locked.failed=The world is currently in use. Please close the game and try again.
|
||||
world.manage=Worlds
|
||||
world.manage.button=World Management
|
||||
world.manage.button=Manage World
|
||||
world.manage.title=World - %s
|
||||
world.name=World Name
|
||||
world.name.enter=Enter the world name
|
||||
@@ -1308,6 +1309,7 @@ search.page_n=%1$d / %2$s
|
||||
|
||||
selector.choose=Choose
|
||||
selector.choose_file=Choose file
|
||||
selector.choose_directory=Choose directory
|
||||
selector.custom=Custom
|
||||
|
||||
settings=Settings
|
||||
@@ -1336,6 +1338,15 @@ settings.advanced.dont_patch_natives=Do not attempt to automatically replace nat
|
||||
settings.advanced.environment_variables=Environment Variables
|
||||
settings.advanced.game_dir.default=Default (".minecraft/")
|
||||
settings.advanced.game_dir.independent=Isolated (".minecraft/versions/<instance name>/", except for assets and libraries)
|
||||
settings.advanced.graphics_backend=Graphics API
|
||||
settings.advanced.graphics_backend.default=Default
|
||||
settings.advanced.graphics_backend.default.desc=Follow the game's settings
|
||||
settings.advanced.graphics_backend.opengl=OpenGL
|
||||
settings.advanced.graphics_backend.opengl.desc=Use OpenGL for rendering
|
||||
settings.advanced.graphics_backend.vulkan=Vulkan
|
||||
settings.advanced.graphics_backend.vulkan.desc=Use Vulkan for rendering
|
||||
settings.advanced.graphics_backend.vulkan.desc.global=Use Vulkan for rendering (Since Minecraft 26.2)
|
||||
settings.advanced.graphics_backend.vulkan.desc.unsupported=Use Vulkan for rendering (not supported for the current game version)
|
||||
settings.advanced.java_permanent_generation_space=PermGen Space
|
||||
settings.advanced.java_permanent_generation_space.prompt=in MiB
|
||||
settings.advanced.jvm=JVM Options
|
||||
@@ -1355,10 +1366,6 @@ settings.advanced.natives_directory.choose=Choose the location of the desired na
|
||||
settings.advanced.natives_directory.custom=Custom
|
||||
settings.advanced.natives_directory.default=Default
|
||||
settings.advanced.natives_directory.default.version_id=<Instance Name>
|
||||
settings.advanced.natives_directory.hint=This option is intended only for users of Apple silicon or other not officially supported platforms. Please do not edit this option unless you know what you are doing.\n\
|
||||
\n\
|
||||
Before proceeding, please make sure all libraries (e.g. lwjgl.dll, libopenal.so) are provided in your desired directory.\n\
|
||||
Note: It is recommended to use a fully English-letters path for the specified local library file. Otherwise it may lead to game launch failure.
|
||||
settings.advanced.no_jvm_args=Do not add default JVM arguments
|
||||
settings.advanced.no_optimizing_jvm_args=Do not add default JVM optimization arguments
|
||||
settings.advanced.precall_command=Pre-launch Command
|
||||
@@ -1378,13 +1385,47 @@ settings.advanced.post_exit_command=Post-exit Command
|
||||
settings.advanced.post_exit_command.prompt=Commands to execute after the game exits
|
||||
settings.advanced.renderer=Renderer
|
||||
settings.advanced.renderer.default=Default
|
||||
settings.advanced.renderer.default.desc=OpenGL
|
||||
settings.advanced.renderer.d3d12=Mesa D3D12
|
||||
settings.advanced.renderer.d3d12.desc=DirectX 12 (Poor performance and compatibility)
|
||||
settings.advanced.renderer.default.desc=Use System Default
|
||||
# Vulkan Renderers
|
||||
settings.advanced.renderer.lavapipe=Mesa Lavapipe
|
||||
settings.advanced.renderer.lavapipe.desc=Software Vulkan Renderer
|
||||
settings.advanced.renderer.dozen=Mesa Dozen
|
||||
settings.advanced.renderer.dozen.desc=Vulkan renderer based on DirectX 12 (Experimental)
|
||||
settings.advanced.renderer.nvidia_vulkan=NVIDIA
|
||||
settings.advanced.renderer.nvidia_vulkan.desc=Official Vulkan driver for NVIDIA GPUs
|
||||
settings.advanced.renderer.nvidia_nvk=NVIDIA (NVK)
|
||||
settings.advanced.renderer.nvidia_nvk.desc=Open-source Vulkan driver for NVIDIA GPUs
|
||||
settings.advanced.renderer.amdvlk=AMD
|
||||
settings.advanced.renderer.amdvlk.desc=Official Vulkan driver for AMD GPUs
|
||||
settings.advanced.renderer.amd_radv=AMD (RADV)
|
||||
settings.advanced.renderer.amd_radv.desc=Open-source Vulkan driver for AMD GPUs
|
||||
settings.advanced.renderer.intel_vulkan=Intel
|
||||
settings.advanced.renderer.intel_vulkan.desc=Official Vulkan driver for Intel GPUs
|
||||
settings.advanced.renderer.intel_anv=Intel (ANV)
|
||||
settings.advanced.renderer.intel_anv.desc=Open-source Vulkan driver for Intel GPUs
|
||||
settings.advanced.renderer.intel_hasvk=Intel (HASVK)
|
||||
settings.advanced.renderer.intel_hasvk.desc=Open-source Vulkan driver for older Intel GPUs
|
||||
settings.advanced.renderer.qualcomm=Qualcomm
|
||||
settings.advanced.renderer.qualcomm.desc=Official Vulkan driver for Qualcomm Adreno GPUs
|
||||
settings.advanced.renderer.turnip=Qualcomm (Turnip)
|
||||
settings.advanced.renderer.turnip.desc=Open-source Vulkan driver for Qualcomm Adreno GPUs
|
||||
settings.advanced.renderer.moltenvk=MoltenVK
|
||||
settings.advanced.renderer.moltenvk.desc=Vulkan renderer based on Metal
|
||||
settings.advanced.renderer.kosmickrisp=KosmicKrisp
|
||||
settings.advanced.renderer.kosmickrisp.desc=Vulkan renderer based on Metal 4 (Experimental)
|
||||
settings.advanced.renderer.powervr=PowerVR
|
||||
settings.advanced.renderer.powervr.desc=Open-source Vulkan driver for PowerVR GPUs (Experimental)
|
||||
settings.advanced.renderer.panvk=PanVK
|
||||
settings.advanced.renderer.panvk.desc=Open-source Vulkan driver for Arm Mali GPUs (Experimental)
|
||||
settings.advanced.renderer.v3dv=V3DV
|
||||
settings.advanced.renderer.v3dv.desc=Open-source Vulkan driver for Raspberry Pi 4/5 GPUs (Experimental)
|
||||
# OpenGL Renderers
|
||||
settings.advanced.renderer.llvmpipe=Mesa LLVMpipe
|
||||
settings.advanced.renderer.llvmpipe.desc=Software (Poor performance, best compatibility)
|
||||
settings.advanced.renderer.llvmpipe.desc=Software OpenGL renderer
|
||||
settings.advanced.renderer.d3d12=Mesa D3D12
|
||||
settings.advanced.renderer.d3d12.desc=OpenGL renderer based on DirectX 12
|
||||
settings.advanced.renderer.zink=Mesa Zink
|
||||
settings.advanced.renderer.zink.desc=Vulkan (Best performance, poor compatibility)
|
||||
settings.advanced.renderer.zink.desc=OpenGL renderer based on Vulkan
|
||||
settings.advanced.server_ip=Server Address
|
||||
settings.advanced.server_ip.prompt=Automatically join after launching the game
|
||||
settings.advanced.unsupported_system_options=Settings not applicable to the current system
|
||||
@@ -1443,6 +1484,7 @@ settings.launcher.download_source=Download Source
|
||||
settings.launcher.download_source.auto=Automatically Choose Download Sources
|
||||
settings.launcher.default_addon_source=Default Addon Source
|
||||
settings.launcher.enable_game_list=Show instance list in homepage
|
||||
settings.launcher.misc=Misc
|
||||
settings.launcher.font=Font
|
||||
settings.launcher.font.anti_aliasing=Anti-aliasing
|
||||
settings.launcher.font.anti_aliasing.auto=Auto
|
||||
|
||||
@@ -354,18 +354,11 @@ download.external_link=زيارة موقع التنزيل
|
||||
download.failed=فشل تنزيل "%1$s"، رمز الاستجابة: %2$d.
|
||||
download.failed.empty=لا توجد إصدارات متاحة. انقر هنا للرجوع.
|
||||
download.failed.no_code=فشل التنزيل
|
||||
download.failed.refresh=فشل جلب قائمة الإصدارات. انقر هنا لإعادة المحاولة.
|
||||
download.game=لعبة جديدة
|
||||
download.provider.bmclapi=BMCLAPI
|
||||
download.provider.bmclapi.desc=bangbang93, https://bmclapi2.bangbang93.com/
|
||||
download.provider.mojang=رسمي
|
||||
download.provider.mojang.desc=يتم توفير OptiFine عبر BMCLAPI
|
||||
download.failed.refresh=فشل في جلب قائمة الإصدارات. يرجى النقر هنا لإعادة المحاولة.
|
||||
download.game=New Game
|
||||
download.provider.official=من المصادر الرسمية
|
||||
download.provider.official.desc=الأحدث، لكن قد يكون التحميل بطيئاً
|
||||
download.provider.balanced=من أسرع المصادر المتاحة
|
||||
download.provider.balanced.desc=متوازن، لكن قد لا يكون الأحدث
|
||||
download.provider.mirror=من مرآة
|
||||
download.provider.mirror.desc=سريع، لكن قد لا يكون الأحدث
|
||||
download.java=جارٍ تنزيل Java
|
||||
download.java.process=عملية تنزيل Java
|
||||
download.javafx=جارٍ تنزيل مكتبات المشغّل...
|
||||
@@ -445,8 +438,6 @@ fatal.unsupported_platform=لا يدعم Minecraft منصتك الحالية ب
|
||||
إذا لم تتمكن من تشغيل Minecraft 1.17 أو أحدث، يمكنك محاولة تغيير "المُصيِّر" إلى "Mesa LLVMpipe" في "الإعدادات العامة/الخاصة بالنسخة ← الإعدادات المتقدمة" لاستخدام معالجة المعالج المركزي لتوافق أفضل.
|
||||
fatal.unsupported_platform.loongarch=وفّر Hello Minecraft! Launcher دعماً لمنصة Loongson.\n\
|
||||
إذا واجهت مشاكل أثناء اللعب، يمكنك زيارة https://docs.hmcl.net/groups.html للحصول على مساعدة.
|
||||
fatal.unsupported_platform.macos_arm64=وفّر Hello Minecraft! Launcher دعماً لمنصة Apple silicon، باستخدام Java ARM الأصلي لتشغيل الألعاب للحصول على تجربة لعب أكثر سلاسة.\n\
|
||||
إذا واجهت مشاكل أثناء اللعب، قد يوفر تشغيل اللعبة بـ Java المبني على معمارية x86-64 توافقاً أفضل.
|
||||
fatal.unsupported_platform.windows_arm64=وفّر Hello Minecraft! Launcher دعماً أصلياً لمنصة Windows on Arm. إذا واجهت مشاكل أثناء اللعب، يُرجى محاولة تشغيل اللعبة بـ Java المبني على معمارية x86.\n\
|
||||
\n\
|
||||
إذا كنت تستخدم منصة <b>Qualcomm</b>، قد تحتاج إلى تثبيت <a href="ms-windows-store://pdp/?productid=9NQPSL29BFFF">حزمة توافق OpenGL</a> قبل اللعب.\n\
|
||||
@@ -1218,7 +1209,6 @@ world.load.fail=فشل تحميل العالم
|
||||
world.locked=قيد الاستخدام
|
||||
world.locked.failed=العالم قيد الاستخدام حالياً. يُرجى إغلاق اللعبة والمحاولة مجدداً.
|
||||
world.manage=العوالم
|
||||
world.manage.button=إدارة العوالم
|
||||
world.manage.title=العالم - %s
|
||||
world.name=اسم العالم
|
||||
world.name.enter=أدخل اسم العالم
|
||||
@@ -1342,10 +1332,6 @@ settings.advanced.natives_directory.choose=اختر موقع المكتبة ال
|
||||
settings.advanced.natives_directory.custom=مخصص
|
||||
settings.advanced.natives_directory.default=افتراضي
|
||||
settings.advanced.natives_directory.default.version_id=<اسم النسخة>
|
||||
settings.advanced.natives_directory.hint=هذا الخيار مخصص فقط لمستخدمي Apple silicon والمنصات غير المدعومة رسمياً. يُرجى عدم تعديل هذا الخيار إلا إذا كنت تعرف ما تفعله.\n\
|
||||
\n\
|
||||
قبل المتابعة، يُرجى التأكد من توفر جميع المكتبات (مثل lwjgl.dll وlibopenal.so) في المجلد المحدد.\n\
|
||||
ملاحظة: يُنصح باستخدام مسار يحتوي على حروف إنجليزية فقط لملف المكتبة المحلية المحدد، وإلا قد يتسبب ذلك في فشل تشغيل اللعبة.
|
||||
settings.advanced.no_jvm_args=عدم إضافة وسيطات JVM الافتراضية
|
||||
settings.advanced.no_optimizing_jvm_args=عدم إضافة وسيطات تحسين JVM الافتراضية
|
||||
settings.advanced.precall_command=أمر ما قبل التشغيل
|
||||
@@ -1365,13 +1351,9 @@ settings.advanced.post_exit_command=أمر ما بعد الإغلاق
|
||||
settings.advanced.post_exit_command.prompt=أوامر تُنفَّذ بعد إغلاق اللعبة
|
||||
settings.advanced.renderer=المُصيِّر
|
||||
settings.advanced.renderer.default=افتراضي
|
||||
settings.advanced.renderer.default.desc=OpenGL
|
||||
settings.advanced.renderer.d3d12=Mesa D3D12
|
||||
settings.advanced.renderer.d3d12.desc=DirectX 12 (أداء وتوافق ضعيفان)
|
||||
settings.advanced.renderer.llvmpipe=Mesa LLVMpipe
|
||||
settings.advanced.renderer.llvmpipe.desc=برمجي (أداء ضعيف، توافق أفضل)
|
||||
settings.advanced.renderer.zink=Mesa Zink
|
||||
settings.advanced.renderer.zink.desc=Vulkan (أفضل أداء، توافق ضعيف)
|
||||
settings.advanced.server_ip=عنوان الخادم
|
||||
settings.advanced.server_ip.prompt=الانضمام تلقائياً بعد تشغيل اللعبة
|
||||
settings.advanced.unsupported_system_options=إعدادات غير مطبّقة على النظام الحالي
|
||||
|
||||
@@ -336,10 +336,6 @@ download.failed.empty=No hay versiones disponibles, por favor haga clic aquí pa
|
||||
download.failed.no_code=No se ha podido descargar
|
||||
download.failed.refresh=No se ha podido obtener la lista de versiones. Por favor, haga clic aquí para volver a intentarlo.
|
||||
download.game=Nuevo juego
|
||||
download.provider.bmclapi=BMCLAPI
|
||||
download.provider.bmclapi.desc=bangbang93, https://bmclapi2.bangbang93.com/
|
||||
download.provider.mojang=Oficial
|
||||
download.provider.mojang.desc=OptiFine es proporcionado por BMCLAPI
|
||||
download.provider.official=De fuentes oficiales
|
||||
download.provider.balanced=De la fuente más rápida disponible
|
||||
download.provider.mirror=Desde espejo
|
||||
@@ -418,8 +414,6 @@ fatal.unsupported_platform=Minecraft aún no es totalmente compatible con tu pla
|
||||
Si no puedes iniciar Minecraft 1.17 y versiones posteriores, puedes intentar cambiar el «Renderizador» a «Mesa LLVMpipe» en «Configuración global/específica de la instancia → Configuración avanzada» para utilizar el renderizado por CPU y mejorar la compatibilidad.
|
||||
fatal.unsupported_platform.loongarch=Hello Minecraft! Launcher ha prestado apoyo a la plataforma Loongson.\n\
|
||||
Si tienes problemas al jugar, puedes visitar https://docs.hmcl.net/groups.html para obtener ayuda.
|
||||
fatal.unsupported_platform.macos_arm64=Hello Minecraft! Launcher ha proporcionado soporte para la plataforma de chips de Apple, utilizando Java nativo de ARM para ejecutar juegos y conseguir una experiencia de juego más fluida.\n\
|
||||
Si tienes problemas al jugar a un juego, ejecutarlo con Java de arquitectura x86-64 puede tener mejor compatibilidad.
|
||||
fatal.unsupported_platform.windows_arm64=Hello Minecraft! Launcher ha proporcionado soporte nativo para la plataforma Windows en Arm. Si tiene problemas al jugar a un juego, intente iniciarlo con Java de arquitectura x86.\n\
|
||||
\n\
|
||||
Si utilizas la plataforma <b>Qualcomm</b>, es posible que tengas que instalar el <a href="ms-windows-store://pdp/?productid=9NQPSL29BFFF">paquete de compatibilidad OpenGL</a> antes de jugar.\n\
|
||||
@@ -1138,7 +1132,6 @@ world.info.random_seed=Semilla
|
||||
world.locked=En uso
|
||||
world.locked.failed=El mundo está actualmente en uso. Por favor, cierra el juego e inténtalo de nuevo.
|
||||
world.manage=Mundos
|
||||
world.manage.button=Administrar
|
||||
world.manage.title=Mundo - %s
|
||||
world.name=Nombre del mundo
|
||||
world.name.enter=Introducir el nombre del mundo
|
||||
@@ -1252,9 +1245,6 @@ settings.advanced.natives_directory.choose=Elija la ubicación de la biblioteca
|
||||
settings.advanced.natives_directory.custom=Personalizado
|
||||
settings.advanced.natives_directory.default=Por defecto
|
||||
settings.advanced.natives_directory.default.version_id=<Nombre de la instancia>
|
||||
settings.advanced.natives_directory.hint=Esta opción está pensada sólo para usuarios de chips de Apple u otras plataformas no soportadas oficialmente. Por favor, no modifique esta opción a menos que sepa lo que está haciendo.\n\
|
||||
\n\
|
||||
Antes de proceder, por favor, asegúrese de que todas las bibliotecas (por ejemplo, lwjgl.dll, libopenal.so) se proporcionan en su directorio deseado.
|
||||
settings.advanced.no_jvm_args=No añadir argumentos JVM por defecto
|
||||
settings.advanced.no_optimizing_jvm_args=No añadir argumentos de optimización de JVM
|
||||
settings.advanced.precall_command=Comando pre-lanzamiento:
|
||||
@@ -1269,13 +1259,9 @@ settings.advanced.post_exit_command=Comando post-salida
|
||||
settings.advanced.post_exit_command.prompt=El comando se ejecuta después de que el juego se detenga
|
||||
settings.advanced.renderer=Renderizador
|
||||
settings.advanced.renderer.default=Por defecto
|
||||
settings.advanced.renderer.default.desc=OpenGL
|
||||
settings.advanced.renderer.d3d12=Mesa D3D12
|
||||
settings.advanced.renderer.d3d12.desc=DirectX 12 (Rendimiento y compatibilidad deficientes)
|
||||
settings.advanced.renderer.llvmpipe=Mesa LLVMpipe
|
||||
settings.advanced.renderer.llvmpipe.desc=Software (Bajo rendimiento, máxima compatibilidad)
|
||||
settings.advanced.renderer.zink=Mesa Zink
|
||||
settings.advanced.renderer.zink.desc=Vulkan (Máximo rendimiento, baja compatibilidad)
|
||||
settings.advanced.server_ip=Dirección del servidor
|
||||
settings.advanced.server_ip.prompt=Entrar automáticamente después de ejecutar el juego
|
||||
settings.advanced.unsupported_system_options=Configuración no aplicable al sistema actual
|
||||
|
||||
@@ -293,10 +293,6 @@ download.failed=%1$s のダウンロードに失敗しました、応答コー
|
||||
download.failed.empty=候補者なし。戻るにはここをクリックしてください。
|
||||
download.failed.refresh=バージョンリストをダウンロードできません。ここをクリックして再試行してください。
|
||||
download.game=ゲームのダウンロード
|
||||
download.provider.bmclapi=BMCLAPI
|
||||
download.provider.bmclapi.desc=ミラーソース
|
||||
download.provider.mojang=Mojang
|
||||
download.provider.mojang.desc=OptiFineはBMCLAPIにより提供されます
|
||||
download.provider.official=公式ソースからロード
|
||||
download.provider.balanced=mcbbsのソースからロード
|
||||
download.provider.mirror=ミラーソースからロード
|
||||
@@ -778,7 +774,6 @@ settings.advanced.natives_directory=ネイティブライブラリパス(例
|
||||
settings.advanced.natives_directory.choose=ネイティブライブラリパスを選択してください
|
||||
settings.advanced.natives_directory.custom=Custom(ゲームが実行時に必要とするネイティブライブラリを提供する必要があります)
|
||||
settings.advanced.natives_directory.default=Standard(ネイティブライブラリは当社が提供します)
|
||||
settings.advanced.natives_directory.hint=このオプションは、Appleシリコンまたはその他の公式にサポートされていないプラットフォーム/アーキテクチャでゲームをプレイする場合にのみ使用されます。\nこのオプションを変更する意味がわからない場合は、このオプションを変更しないことをお勧めします。、またはゲームが起動しません。\nこのオプションを変更する必要がある場合は、ゲームを実行するために必要なすべてのネイティブライブラリをカスタムネイティブディレクトリの下に配置する必要があります。lwjgl.dll(liblwjgl.so)、openal.dll(libopenal.so)。HMCLは、不足しているネイティブライブラリを提供しません。
|
||||
settings.advanced.no_jvm_args=デフォルトのJVM引数なし
|
||||
settings.advanced.precall_command=起動前コマンド
|
||||
settings.advanced.precall_command.prompt=ゲーム開始前に実行されます
|
||||
@@ -792,13 +787,9 @@ settings.advanced.post_exit_command=終了後のコマンド
|
||||
settings.advanced.post_exit_command.prompt=ゲーム終了後に実行されます
|
||||
settings.advanced.renderer=レンダラー
|
||||
settings.advanced.renderer.default=デフォルト
|
||||
settings.advanced.renderer.default.desc=OpenGL
|
||||
settings.advanced.renderer.d3d12=Mesa D3D12
|
||||
settings.advanced.renderer.d3d12.desc=DirectX 12 (低パフォーマンス、低互換性)
|
||||
settings.advanced.renderer.llvmpipe=Mesa LLVMpipe
|
||||
settings.advanced.renderer.llvmpipe.desc=ソフトウェア (低速だが、互換性は最高)
|
||||
settings.advanced.renderer.zink=Mesa Zink
|
||||
settings.advanced.renderer.zink.desc=Vulkan (最高パフォーマンス、低互換性)
|
||||
settings.advanced.server_ip=サーバーアドレス
|
||||
settings.advanced.server_ip.prompt=ゲームの起動時にサーバーに参加する
|
||||
settings.advanced.unsupported_system_options=サポートされていないシステムオプション
|
||||
|
||||
@@ -46,6 +46,8 @@ about.thanks_to.shulkersakura.statement=鑄其徽
|
||||
about.thanks_to.users=HMCL 群組諸員
|
||||
about.thanks_to.users.statement=敬謝諸君之貲勉、報謬、謀案
|
||||
about.thanks_to.yushijinhun.statement=有助於 authlib-injector
|
||||
about.thanks_to.8mi-tech=八藍米資訊科技 (泉州) 有限公所
|
||||
about.thanks_to.8mi-tech.statement=供遊戲部分靜態資源之加速下載役
|
||||
about.open_source=開源
|
||||
about.open_source.statement=GPL v3 (https://github.com/HMCL-dev/HMCL)
|
||||
|
||||
@@ -332,10 +334,6 @@ download.failed.empty=[無可裝之版,點此返]\n(君可求助於右上之
|
||||
download.failed.no_code=引之未成
|
||||
download.failed.refresh=[載版列未成,點此重試]\n(君可求助於右上之鈕)
|
||||
download.game=新戲
|
||||
download.provider.bmclapi=BMCLAPI
|
||||
download.provider.bmclapi.desc=bangbang93,https://bmclapi2.bangbang93.com
|
||||
download.provider.mojang=官版
|
||||
download.provider.mojang.desc=Optifine 猶取諸 BMCLAPI 也
|
||||
download.provider.official=先官版
|
||||
download.provider.official.desc=至新,容有艱
|
||||
download.provider.balanced=先其快者
|
||||
@@ -385,7 +383,6 @@ fatal.illegal_char=君之案夾名有諱文「=」,是以外載之戶簿、離
|
||||
fatal.unsupported_platform=礦藝未盡適君之算機,是以戲事或損,至於不能啟。\n\
|
||||
礦藝一點一七以晉之不能啟,或於「(全例/例殊) 戲設 → 進階置設 → 勘誤置設」改「繪製之器」以「Mesa LLVMpipe」,或能兼之。\n君可求助於右上之鈕。
|
||||
fatal.unsupported_platform.loongarch=HMCL 既適龍芯。\n凡有謬,遽求助於右上之鈕。
|
||||
fatal.unsupported_platform.macos_arm64=HMCL 既適蘋矽。宜啟以 ARM 之爪哇,以益君之戲事。\n誠有謬,宜啟以 x86-64 之爪哇,以益其兼。\n凡有謬,遽求助於右上之鈕。
|
||||
fatal.unsupported_platform.windows_arm64=HMCL 既適 ARM 之視窗。誠有謬,宜啟以 x86 之爪哇。\n誠用<b>栝柑</b>,或須置<a href="ms-windows-store://pdp/?productid=9NQPSL29BFFF">開圖庫兼囊</a>,而後可戯。擊鏈以置之於微軟貨舍。\n君可求助於右上之鈕。
|
||||
|
||||
file=案
|
||||
@@ -951,8 +948,8 @@ world.info.player.xp_level=經驗之層
|
||||
world.info.random_seed=種
|
||||
world.locked=見用
|
||||
world.manage=司生界
|
||||
world.manage.button=司生界
|
||||
world.manage.title=司生界 - %s
|
||||
world.manage.button=司此生界
|
||||
world.manage.title=生界 - %s
|
||||
world.name=生界之名
|
||||
world.name.enter=書生界之名
|
||||
world.show_all=全顯
|
||||
@@ -1066,7 +1063,6 @@ settings.advanced.natives_directory.choose=擇府庫之徑
|
||||
settings.advanced.natives_directory.custom=自定 (賴君所供之府庫也)
|
||||
settings.advanced.natives_directory.default=本 (賴啟者所供之府庫)
|
||||
settings.advanced.natives_directory.default.version_id=<例名>
|
||||
settings.advanced.natives_directory.hint=供於械網之弗見適者,有如蘋矽,以自定府庫也。微其之知,毋改是,改則或弗啟。\n\n將改,而宜錄府庫於是,蓋 lwjgl.dll (liblwjgl.so)、openal.dll (libopenal.so) 之類。啟者弗補是府庫也!\n\n誡:府庫之徑,但宜書以英文、數、下綫,否則或弗啟。
|
||||
settings.advanced.no_jvm_args=無添爪哇虛機之本通弦
|
||||
settings.advanced.precall_command=令於戲前
|
||||
settings.advanced.precall_command.prompt=將前戲啟而行
|
||||
@@ -1085,13 +1081,9 @@ settings.advanced.post_exit_command=令於戲訖
|
||||
settings.advanced.post_exit_command.prompt=將後戲訖而行
|
||||
settings.advanced.renderer=繪器
|
||||
settings.advanced.renderer.default=本
|
||||
settings.advanced.renderer.default.desc=開圖庫
|
||||
settings.advanced.renderer.d3d12=Mesa D3D12
|
||||
settings.advanced.renderer.d3d12.desc=DirectX 12 (效與所適皆差,勘誤之用)
|
||||
settings.advanced.renderer.llvmpipe=Mesa LLVMpipe
|
||||
settings.advanced.renderer.llvmpipe.desc=軟繪器 (效差,所適至)
|
||||
settings.advanced.renderer.zink=Mesa Zink
|
||||
settings.advanced.renderer.zink.desc=Vulkan (效至,所適差)
|
||||
settings.advanced.server_ip=伺服器之址
|
||||
settings.advanced.server_ip.prompt=本之址,啟訖徑入是伺服器
|
||||
settings.advanced.unsupported_system_options=不逮今之械網所選
|
||||
|
||||
@@ -333,10 +333,6 @@ download.failed.empty=Версий нет. Нажмите здесь, чтобы
|
||||
download.failed.no_code=Не удалось скачать
|
||||
download.failed.refresh=Не удалось получить список версий. Нажмите здесь, чтобы повторить попытку.
|
||||
download.game=Новая игра
|
||||
download.provider.bmclapi=BMCLAPI
|
||||
download.provider.bmclapi.desc=bangbang93, https://bmclapi2.bangbang93.com/
|
||||
download.provider.mojang=Официальный
|
||||
download.provider.mojang.desc=OptiFine предоставляется BMCLAPI
|
||||
download.provider.official=Из официальных источников
|
||||
download.provider.balanced=Из самых быстрых доступных
|
||||
download.provider.mirror=Из зеркала
|
||||
@@ -412,8 +408,6 @@ fatal.unsupported_platform=Minecraft еще не полностью поддер
|
||||
Если вы не можете запустить Minecraft версии 1.17 и новее, попробуйте переключить «Рендерер» на «Mesa LLVMpipe» в разделе «Глобальные/настройки экземпляра → Расширенные настройки», чтобы использовать рендеринг через CPU для лучшей совместимости.
|
||||
fatal.unsupported_platform.loongarch=Лаунчер обеспечил поддержку платформы Loongson.\n\
|
||||
Если у вас возникнут проблемы во время игры, вы можете обратиться за помощью на сайт https://docs.hmcl.net/groups.html.
|
||||
fatal.unsupported_platform.macos_arm64=Лаунчер обеспечил поддержку платформы Apple silicon, используя родную ARM Java для запуска игр, чтобы получить более плавный игровой опыт.\n\
|
||||
Если вы столкнулись с проблемами во время игры, запуск игры с Java на базе архитектуры x86-64 может обеспечить лучшую совместимость.
|
||||
fatal.unsupported_platform.windows_arm64=Лаунчер обеспечил нативную поддержку платформы Windows на архитектуре Arm. Если у вам возникли проблемы во время игры, попробуйте запустить игру с Java на базе архитектуры x86.\n\
|
||||
\n\
|
||||
Если вы используете платформу <b>Qualcomm</b>, вам может потребоваться установить <a href="ms-windows-store://pdp/?productid=9NQPSL29BFFF">пакет совместимости OpenGL</a> перед началом игры.\n\
|
||||
@@ -1130,7 +1124,6 @@ world.info.random_seed=Ключ генератора мира
|
||||
world.locked=В эксплуатации
|
||||
world.locked.failed=В настоящее время мир находится в эксплуатации. Закройте игру и попробуйте снова.
|
||||
world.manage=Миры
|
||||
world.manage.button=Управлять
|
||||
world.manage.title=Мир - %s
|
||||
world.name=Название мира
|
||||
world.name.enter=Введите название мира
|
||||
@@ -1243,10 +1236,6 @@ settings.advanced.natives_directory.choose=Выберите расоложени
|
||||
settings.advanced.natives_directory.custom=Пользовательское
|
||||
settings.advanced.natives_directory.default=По умолчанию
|
||||
settings.advanced.natives_directory.default.version_id=<Название сборки>
|
||||
settings.advanced.natives_directory.hint=Параметр предназначен только для пользователей Apple silicon или других официально не поддерживаемых платформ. Не изменяйте, если не знаете что делаете.\n\
|
||||
\n\
|
||||
Прежде чем продолжить, убедитесь что все библиотеки (например, lwjgl.dll, libopenal.so) находятся в нужной папке.\n\
|
||||
Примечание: Рекомендуется использовать полностью английские пути для указанных файлов локальных библиотек, иначе игра может не запуститься.
|
||||
settings.advanced.no_jvm_args=Не добавлять аргументы JVM по умолчанию
|
||||
settings.advanced.precall_command=Команда перед запуском
|
||||
settings.advanced.precall_command.prompt=Команды, которые необходимо выполнить перед запуском игры
|
||||
@@ -1260,13 +1249,9 @@ settings.advanced.post_exit_command=Команда после выхода
|
||||
settings.advanced.post_exit_command.prompt=Команды, которые необходимо выполнить после выхода из игры
|
||||
settings.advanced.renderer=Рендерер
|
||||
settings.advanced.renderer.default=По умолчанию
|
||||
settings.advanced.renderer.default.desc=OpenGL
|
||||
settings.advanced.renderer.d3d12=Mesa D3D12
|
||||
settings.advanced.renderer.d3d12.desc=DirectX 12 (Низкая производительность и совместимость)
|
||||
settings.advanced.renderer.llvmpipe=Mesa LLVMpipe
|
||||
settings.advanced.renderer.llvmpipe.desc=ПО (Низкая производительность, лучшая совместимость)
|
||||
settings.advanced.renderer.zink=Mesa Zink
|
||||
settings.advanced.renderer.zink.desc=Vulkan (Лучшая производительность, низкая совместимость)
|
||||
settings.advanced.server_ip=Адрес сервера
|
||||
settings.advanced.server_ip.prompt=Присоединяться к серверу при запуске игры
|
||||
settings.advanced.unsupported_system_options=Настройки, не применимые к текущей системе
|
||||
|
||||
@@ -332,10 +332,6 @@ download.failed.empty=Немає доступних версій. Натисні
|
||||
download.failed.no_code=Не вдалося завантажити
|
||||
download.failed.refresh=Не вдалося отримати список версій. Натисніть тут, щоб повторити спробу.
|
||||
download.game=Нова гра
|
||||
download.provider.bmclapi=BMCLAPI
|
||||
download.provider.bmclapi.desc=bangbang93, https://bmclapi2.bangbang93.com/
|
||||
download.provider.mojang=Офіційне
|
||||
download.provider.mojang.desc=OptiFine надається BMCLAPI
|
||||
download.provider.official=З офіційних джерел
|
||||
download.provider.balanced=З найшвидшого доступного
|
||||
download.provider.mirror=З дзеркала
|
||||
@@ -398,8 +394,6 @@ fatal.unsupported_platform=Minecraft ще не повністю підтриму
|
||||
Якщо ви не можете запустити Minecraft версії 1.17 і новіші, спробуйте змінити «Рендерер» на «Mesa LLVMpipe» у розділі «Глобальні/налаштування екземпляра → Розширені налаштування», щоб використовувати рендеринг через CPU для кращої сумісності.
|
||||
fatal.unsupported_platform.loongarch=Hello Minecraft! Лаунчер надав підтримку платформи Loongson.\n\
|
||||
Якщо у вас виникли проблеми під час гри, ви можете відвідати https://docs.hmcl.net/groups.html для отримання допомоги.
|
||||
fatal.unsupported_platform.macos_arm64=Hello Minecraft! Лаунчер надав підтримку платформи Apple silicon, використовуючи нативну ARM Java для запуску ігор для отримання плавнішого ігрового досвіду.\n\
|
||||
Якщо у вас виникли проблеми під час гри, запуск гри з Java на основі архітектури x86-64 може забезпечити кращу сумісність.
|
||||
fatal.unsupported_platform.windows_arm64=Hello Minecraft! Лаунчер надав нативну підтримку платформи Windows on Arm. Якщо у вас виникли проблеми під час гри, спробуйте запустити гру з Java на основі архітектури x86.\n\
|
||||
Якщо ви використовуєте платформу <b>Qualcomm</b>, вам може знадобитися встановити <a href="ms-windows-store://pdp/?productid=9NQPSL29BFFF">Пакет сумісності OpenGL</a> перед грою.\n\
|
||||
Натисніть посилання, щоб перейти до Microsoft Store та встановити пакет сумісності.
|
||||
@@ -1078,7 +1072,6 @@ world.info.random_seed=Насіння
|
||||
world.locked=Використовується
|
||||
world.locked.failed=Світ наразі використовується. Закрийте гру та спробуйте знову.
|
||||
world.manage=Світи
|
||||
world.manage.button=Керування світами
|
||||
world.manage.title=Світ - %s
|
||||
world.name=Назва світу
|
||||
world.name.enter=Введіть назву світу
|
||||
@@ -1191,9 +1184,6 @@ settings.advanced.natives_directory.choose=Виберіть місце розт
|
||||
settings.advanced.natives_directory.custom=Власний
|
||||
settings.advanced.natives_directory.default=Типово
|
||||
settings.advanced.natives_directory.default.version_id=<Назва екземпляра>
|
||||
settings.advanced.natives_directory.hint=Ця опція призначена лише для користувачів Apple silicon або інших неофіційно підтримуваних платформ. Не редагуйте цю опцію, якщо ви не знаєте, що робите. \n\
|
||||
Перед продовженням переконайтеся, що всі бібліотеки (наприклад, lwjgl.dll, libopenal.so) надано у вашому бажаному каталозі. \n\
|
||||
Примітка: Рекомендується використовувати повністю англійські літери для вказаного локального файлу бібліотеки. Інакше це може призвести до невдалого запуску гри.
|
||||
settings.advanced.no_jvm_args=Не додавати типові аргументи JVM
|
||||
settings.advanced.precall_command=Команда перед запуском
|
||||
settings.advanced.precall_command.prompt=Команди для виконання перед запуском гри
|
||||
@@ -1207,13 +1197,9 @@ settings.advanced.post_exit_command=Команда після виходу
|
||||
settings.advanced.post_exit_command.prompt=Команди для виконання після виходу з гри
|
||||
settings.advanced.renderer=Рендерер
|
||||
settings.advanced.renderer.default=По умолчанию
|
||||
settings.advanced.renderer.default.desc=OpenGL
|
||||
settings.advanced.renderer.d3d12=Mesa D3D12
|
||||
settings.advanced.renderer.d3d12.desc=DirectX 12 (Погана продуктивність та сумісність)
|
||||
settings.advanced.renderer.llvmpipe=Mesa LLVMpipe
|
||||
settings.advanced.renderer.llvmpipe.desc=Програмний (Погана продуктивність, найкраща сумісність)
|
||||
settings.advanced.renderer.zink=Mesa Zink
|
||||
settings.advanced.renderer.zink.desc=Vulkan (Найкраща продуктивність, погана сумісність)
|
||||
settings.advanced.server_ip=Адреса сервера
|
||||
settings.advanced.server_ip.prompt=Автоматично приєднатися після запуску гри
|
||||
settings.advanced.unsupported_system_options=Налаштування, що не застосовуються до поточної системи
|
||||
|
||||
@@ -47,6 +47,8 @@ about.thanks_to.shulkersakura.statement=提供 HMCL 的標誌
|
||||
about.thanks_to.users=HMCL 使用者群組成員
|
||||
about.thanks_to.users.statement=感謝使用者群組成員贊助充電、積極催更、回報問題、出謀劃策
|
||||
about.thanks_to.yushijinhun.statement=提供 authlib-injector 相關支援
|
||||
about.thanks_to.8mi-tech=八藍米信息科技 (泉州) 有限公司
|
||||
about.thanks_to.8mi-tech.statement=提供了遊戲部分靜態資源的加速下載服務
|
||||
about.open_source=開放原始碼
|
||||
about.open_source.statement=GPL v3 (https://github.com/HMCL-dev/HMCL/)
|
||||
|
||||
@@ -109,8 +111,9 @@ account.methods.microsoft.close_page=已完成 Microsoft 帳戶授權。啟動
|
||||
account.methods.microsoft.deauthorize=移除應用存取權
|
||||
account.methods.microsoft.error.add_family=請點擊 <a href="https://support.microsoft.com/account-billing/837badbc-999e-54d2-2617-d19206b9540a">此處</a> 更改你的帳戶出生日期,使年齡滿 18 歲以上,或將帳戶加入到家庭中。
|
||||
account.methods.microsoft.error.country_unavailable=你所在的國家或地區不受 Xbox Live 的支援。
|
||||
account.methods.microsoft.error.missing_xbox_account=請點擊下方 <a href="https://www.minecraft.net/msaprofile/mygames/editprofile">此處</a> 關聯 Xbox 帳戶。
|
||||
account.methods.microsoft.error.no_character=請確認你已經購買了 Minecraft: Java 版。\n若已購買,則可能未建立遊戲檔案。請點擊 <a href="https://www.minecraft.net/msaprofile/mygames/editprofile">此處</a> 建立遊戲檔案。
|
||||
account.methods.microsoft.error.missing_xbox_account=請確認你已經購買了 Minecraft: Java 版。若已購買,請點擊 <a href="https://www.minecraft.net/msaprofile/mygames/editprofile">此處</a> 建立遊戲檔案。
|
||||
account.methods.microsoft.error.no_license=未找到 Minecraft 購買記錄。請點擊 <a href="https://www.xbox.com/games/store/minecraft-java-bedrock-edition-for-pc/9nxp44l49shj">此處</a> 購買遊戲。\n若你使用 Game Pass,請在 <a href="https://www.minecraft.net/msaprofile/mygames/editprofile">此處</a> 建立遊戲檔案。若已購買,請檢查登入的帳戶是否正確。
|
||||
account.methods.microsoft.error.no_character=你可能未建立遊戲檔案。請點擊 <a href="https://www.minecraft.net/msaprofile/mygames/editprofile">此處</a> 建立遊戲檔案。
|
||||
account.methods.microsoft.error.banned=你的帳戶可能被 Xbox Live 封禁。\n你可以點擊 <a href="https://enforcement.xbox.com/enforcement/showenforcementhistory">此處</a> 按鈕查詢帳戶封禁狀態。
|
||||
account.methods.microsoft.error.unknown=登入失敗。錯誤碼:%d。
|
||||
account.methods.microsoft.error.wrong_verify_method=登入失敗。請在 Microsoft 帳戶登入頁面嘗試使用密碼登入,不要使用其他登入方式。
|
||||
@@ -359,16 +362,16 @@ download.failed.empty=沒有可供安裝的版本,按一下此處返回。
|
||||
download.failed.no_code=下載失敗
|
||||
download.failed.refresh=載入版本清單失敗,按一下此處重試。
|
||||
download.game=新遊戲
|
||||
download.provider.bmclapi=BMCLAPI
|
||||
download.provider.bmclapi.desc=bangbang93, https://bmclapi2.bangbang93.com/
|
||||
download.provider.bmclapi=BMCLAPI & MCIM
|
||||
download.provider.bmclapi.desc=適用於中國大陸的鏡像源
|
||||
download.provider.mojang=官方伺服器
|
||||
download.provider.mojang.desc=OptiFine 由 BMCLAPI 提供
|
||||
download.provider.official=盡量使用官方源
|
||||
download.provider.official.desc=最新,但可能載入慢
|
||||
download.provider.balanced=選取載入速度快的下載源
|
||||
download.provider.balanced.desc=平衡,但可能不是最新
|
||||
download.provider.mojang.desc=注意:OptiFine 的下載由 BMCLAPI 提供
|
||||
download.provider.official=盡量使用官方來源
|
||||
download.provider.official.desc=優先從官方來源獲取資訊
|
||||
download.provider.balanced=選取載入速度快的下載來源
|
||||
download.provider.balanced.desc=根據你的網路連線狀況選用最佳來源
|
||||
download.provider.mirror=盡量使用鏡像源
|
||||
download.provider.mirror.desc=載入快,但可能不是最新
|
||||
download.provider.mirror.desc=推薦中國大陸使用者使用
|
||||
download.java=下載 Java
|
||||
download.java.process=下載 Java
|
||||
download.javafx=正在下載必要的執行時元件
|
||||
@@ -419,7 +422,6 @@ fatal.illegal_char=由於您的執行路徑中存在無效字元『=』,您將
|
||||
fatal.unsupported_platform=Minecraft 尚未對您的平臺提供完善支援,所以可能影響遊戲體驗或無法啟動遊戲。\n\
|
||||
若無法啟動 Minecraft 1.17 及更高版本,可以嘗試在「(全域/實例特定) 遊戲設定 → 進階設定 → 除錯選項」中將「繪製器」切換為「Mesa LLVMpipe」,以獲得更好的相容性。
|
||||
fatal.unsupported_platform.loongarch=Hello Minecraft! Launcher 已為龍芯提供支援。\n如果遇到問題,你可以點擊右上角幫助按鈕進行求助。
|
||||
fatal.unsupported_platform.macos_arm64=Hello Minecraft! Launcher 已為 Apple Silicon 平臺提供支援。使用 ARM 原生 Java 啟動遊戲以獲得更流暢的遊戲體驗。\n如果你在遊戲中遭遇問題,使用 x86-64 架構的 Java 啟動遊戲可能有更好的相容性。
|
||||
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
|
||||
fatal.wine_warning=HMCL is running under Wine or one of its distributions.\nAs a cross-platform application, HMCL is best experienced natively on your host OS.\nPress "OK" to continue, but please note that some issues may arise.\n\n您目前正在使用 Wine 或其衍生版本執行 HMCL。\nHMCL 是跨平臺的,我們建議您直接在主機系統上執行。\n按下「確定」以繼續,但請注意,此操作可能會引發一些問題。
|
||||
@@ -566,6 +568,7 @@ install.new_game.already_exists=此實例已經存在,請重新命名
|
||||
install.new_game.current_game_version=目前遊戲實例
|
||||
install.new_game.installation=安裝新實例
|
||||
install.new_game.malformed=名稱無效
|
||||
install.new_game.malformed_json=無效的 Minecraft 實例 JSON 文件。
|
||||
install.select=請選取安裝方式
|
||||
install.success=安裝成功
|
||||
|
||||
@@ -1025,8 +1028,8 @@ world.locked=使用中
|
||||
world.locked.failed=該世界正在使用中,請關閉遊戲後重試。
|
||||
world.game_version=遊戲版本
|
||||
world.manage=世界管理
|
||||
world.manage.button=世界管理
|
||||
world.manage.title=世界管理 - %s
|
||||
world.manage.button=管理此世界
|
||||
world.manage.title=世界 - %s
|
||||
world.name=世界名稱
|
||||
world.name.enter=輸入世界名稱
|
||||
world.show_all=全部顯示
|
||||
@@ -1098,6 +1101,7 @@ search.page_n=%d / %s
|
||||
|
||||
selector.choose=選取
|
||||
selector.choose_file=選取檔案
|
||||
selector.choose_directory=選取目錄
|
||||
selector.custom=自訂
|
||||
|
||||
settings=設定
|
||||
@@ -1126,6 +1130,15 @@ settings.advanced.dont_patch_natives=不嘗試自動取代本機庫
|
||||
settings.advanced.environment_variables=環境變數
|
||||
settings.advanced.game_dir.default=預設 (".minecraft/")
|
||||
settings.advanced.game_dir.independent=各實例獨立 (".minecraft/versions/<實例名>/",除 assets、libraries 外)
|
||||
settings.advanced.graphics_backend=圖形 API
|
||||
settings.advanced.graphics_backend.default=預設
|
||||
settings.advanced.graphics_backend.default.desc=遵循遊戲設定
|
||||
settings.advanced.graphics_backend.opengl=OpenGL
|
||||
settings.advanced.graphics_backend.opengl.desc=使用 OpenGL 渲染
|
||||
settings.advanced.graphics_backend.vulkan=Vulkan
|
||||
settings.advanced.graphics_backend.vulkan.desc=使用 Vulkan 渲染
|
||||
settings.advanced.graphics_backend.vulkan.desc.global=使用 Vulkan 渲染 (適用於 Minecraft 26.2 及更高版本)
|
||||
settings.advanced.graphics_backend.vulkan.desc.unsupported=使用 Vulkan 渲染 (不適用於當前遊戲版本)
|
||||
settings.advanced.java_permanent_generation_space=記憶體永久儲存區域
|
||||
settings.advanced.java_permanent_generation_space.prompt=單位 MiB
|
||||
settings.advanced.jvm=Java 虛擬機設定
|
||||
@@ -1145,7 +1158,6 @@ settings.advanced.natives_directory.choose=選取本機庫路徑
|
||||
settings.advanced.natives_directory.custom=自訂 (由你提供遊戲需要的本機庫)
|
||||
settings.advanced.natives_directory.default=預設 (由啟動器提供遊戲本機庫)
|
||||
settings.advanced.natives_directory.default.version_id=<實例名稱>
|
||||
settings.advanced.natives_directory.hint=本選項提供給 Apple Silicon 等未受遊戲官方支援的平台來自訂遊戲本機庫。如果你不知道本選項的含義,請你不要修改本選項,否則會導致遊戲無法啟動。\n\n如果你要修改本選項,你需要保證自訂目錄下有遊戲所需的本機庫檔案,如 lwjgl.dll (liblwjgl.so), openal.dll (libopenal.so) 等檔案。啟動器不會幫你補全缺少的本機庫檔案。\n\n注意:建議指定的本機庫檔案路徑使用全英文字元,否則可能導致遊戲啟動失敗。
|
||||
settings.advanced.no_jvm_args=不新增預設的 Java 虛擬機參數
|
||||
settings.advanced.no_optimizing_jvm_args=不自動新增 Java 虛擬機最佳化參數
|
||||
settings.advanced.precall_command=遊戲啟動前執行指令
|
||||
@@ -1163,15 +1175,49 @@ settings.advanced.process_priority.high=高
|
||||
settings.advanced.process_priority.high.desc=優先保證遊戲執行,但可能會導致其他程式卡頓
|
||||
settings.advanced.post_exit_command=遊戲結束後執行指令
|
||||
settings.advanced.post_exit_command.prompt=將在遊戲結束後呼叫使用
|
||||
settings.advanced.renderer=繪製器
|
||||
settings.advanced.renderer=繪製器/驅動
|
||||
settings.advanced.renderer.default=預設
|
||||
settings.advanced.renderer.default.desc=OpenGL
|
||||
settings.advanced.renderer.d3d12=Mesa D3D12
|
||||
settings.advanced.renderer.d3d12.desc=DirectX 12 (效能與相容性較差,用於除錯)
|
||||
settings.advanced.renderer.default.desc=使用系統預設繪製器
|
||||
# Vulkan Renderers
|
||||
settings.advanced.renderer.lavapipe=Mesa Lavapipe
|
||||
settings.advanced.renderer.lavapipe.desc=軟體 Vulkan 繪製器
|
||||
settings.advanced.renderer.dozen=Mesa Dozen
|
||||
settings.advanced.renderer.dozen.desc=基於 DirectX 12 的 Vulkan 繪製器 (實驗性)
|
||||
settings.advanced.renderer.nvidia_vulkan=NVIDIA
|
||||
settings.advanced.renderer.nvidia_vulkan.desc=NVIDIA GPU 的官方 Vulkan 驅動
|
||||
settings.advanced.renderer.nvidia_nvk=NVIDIA (NVK)
|
||||
settings.advanced.renderer.nvidia_nvk.desc=NVIDIA GPU 的開源 Vulkan 驅動
|
||||
settings.advanced.renderer.amdvlk=AMD
|
||||
settings.advanced.renderer.amdvlk.desc=AMD GPU 的官方 Vulkan 驅動
|
||||
settings.advanced.renderer.amd_radv=AMD (RADV)
|
||||
settings.advanced.renderer.amd_radv.desc=AMD GPU 的開源 Vulkan 驅動
|
||||
settings.advanced.renderer.intel_vulkan=Intel
|
||||
settings.advanced.renderer.intel_vulkan.desc=Intel GPU 的官方 Vulkan 驅動
|
||||
settings.advanced.renderer.intel_anv=Intel (ANV)
|
||||
settings.advanced.renderer.intel_anv.desc=Intel GPU 的開源 Vulkan 驅動
|
||||
settings.advanced.renderer.intel_hasvk=Intel (HASVK)
|
||||
settings.advanced.renderer.intel_hasvk.desc=Intel 舊 GPU 的開源 Vulkan 驅動
|
||||
settings.advanced.renderer.qualcomm=高通
|
||||
settings.advanced.renderer.qualcomm.desc=高通 Adreno GPU 的官方 Vulkan 驅動
|
||||
settings.advanced.renderer.turnip=高通 (Turnip)
|
||||
settings.advanced.renderer.turnip.desc=高通 Adreno GPU 的開源 Vulkan 驅動
|
||||
settings.advanced.renderer.moltenvk=MoltenVK
|
||||
settings.advanced.renderer.moltenvk.desc=基於 Metal 的 Vulkan 繪製器
|
||||
settings.advanced.renderer.kosmickrisp=KosmicKrisp
|
||||
settings.advanced.renderer.kosmickrisp.desc=基於 Metal 4 的 Vulkan 繪製器 (實驗性)
|
||||
settings.advanced.renderer.powervr=PowerVR
|
||||
settings.advanced.renderer.powervr.desc=PowerVR GPU 的開源 Vulkan 驅動 (實驗性)
|
||||
settings.advanced.renderer.panvk=PanVK
|
||||
settings.advanced.renderer.panvk.desc=Arm Mali GPU 的開源 Vulkan 驅動 (實驗性)
|
||||
settings.advanced.renderer.v3dv=V3DV
|
||||
settings.advanced.renderer.v3dv.desc=樹莓派 4/5 GPU 的開源 Vulkan 驅動 (實驗性)
|
||||
# OpenGL Renderers
|
||||
settings.advanced.renderer.llvmpipe=Mesa LLVMpipe
|
||||
settings.advanced.renderer.llvmpipe.desc=軟繪製器 (效能較差,相容性最好)
|
||||
settings.advanced.renderer.llvmpipe.desc=軟體 OpenGL 繪製器
|
||||
settings.advanced.renderer.d3d12=Mesa D3D12
|
||||
settings.advanced.renderer.d3d12.desc=基於 DirectX 12 的 OpenGL 繪製器
|
||||
settings.advanced.renderer.zink=Mesa Zink
|
||||
settings.advanced.renderer.zink.desc=Vulkan (效能最好,相容性較差)
|
||||
settings.advanced.renderer.zink.desc=基於 Vulkan 的 OpenGL 繪製器
|
||||
settings.advanced.server_ip=伺服器位址
|
||||
settings.advanced.server_ip.prompt=預設,啟動遊戲後直接進入對應伺服器
|
||||
settings.advanced.unsupported_system_options=不適用於目前系統的選項
|
||||
@@ -1228,6 +1274,7 @@ settings.launcher.download_source=下載來源
|
||||
settings.launcher.download_source.auto=自動選取下載來源
|
||||
settings.launcher.default_addon_source=遊戲內容預設下載來源
|
||||
settings.launcher.enable_game_list=在首頁內顯示遊戲清單
|
||||
settings.launcher.misc=雜項
|
||||
settings.launcher.font=字體
|
||||
settings.launcher.font.anti_aliasing=反鋸齒
|
||||
settings.launcher.font.anti_aliasing.auto=自動
|
||||
|
||||
@@ -47,6 +47,8 @@ about.thanks_to.shulkersakura.statement=提供 HMCL 的徽标
|
||||
about.thanks_to.users=HMCL 用户群成员
|
||||
about.thanks_to.users.statement=感谢用户群成员赞助充电、积极催更、反馈问题、出谋划策
|
||||
about.thanks_to.yushijinhun.statement=提供 authlib-injector 相关支持
|
||||
about.thanks_to.8mi-tech=八蓝米信息科技 (泉州) 有限公司
|
||||
about.thanks_to.8mi-tech.statement=提供了游戏部分静态资源的加速下载服务
|
||||
about.open_source=开源
|
||||
about.open_source.statement=GPL v3 (https://github.com/HMCL-dev/HMCL)
|
||||
|
||||
@@ -110,8 +112,9 @@ account.methods.microsoft.close_page=已完成微软账户授权。其余登录
|
||||
account.methods.microsoft.deauthorize=解除账户授权
|
||||
account.methods.microsoft.error.add_family=请点击 <a href="https://support.microsoft.com/account-billing/837badbc-999e-54d2-2617-d19206b9540a">此处</a> 更改你的账户出生日期,使年龄满 18 岁以上,或将账户加入到家庭中。\n你可以点击右上角帮助按钮进行求助。
|
||||
account.methods.microsoft.error.country_unavailable=你所在的国家或地区不受 Xbox Live 的支持。
|
||||
account.methods.microsoft.error.missing_xbox_account=请点击 <a href="https://www.minecraft.net/msaprofile/mygames/editprofile">此处</a> 关联 Xbox 账户。\n你可以点击右上角帮助按钮进行求助。
|
||||
account.methods.microsoft.error.no_character=请确认你已经购买了 Minecraft: Java 版。\n若已购买,则可能未创建游戏档案。请点击 <a href="https://www.minecraft.net/msaprofile/mygames/editprofile">此处</a> 创建游戏档案。\n你可以点击右上角帮助按钮进行求助。
|
||||
account.methods.microsoft.error.missing_xbox_account=请确认你已经购买了 Minecraft: Java 版。若已购买,请点击 <a href="https://www.minecraft.net/msaprofile/mygames/editprofile">此处</a> 创建游戏档案。\n你可以点击右上角帮助按钮进行求助。
|
||||
account.methods.microsoft.error.no_license=未找到 Minecraft 购买记录。请点击 <a href="https://www.xbox.com/games/store/minecraft-java-bedrock-edition-for-pc/9nxp44l49shj">此处</a> 购买游戏。\n若你使用 Game Pass,请在 <a href="https://www.minecraft.net/msaprofile/mygames/editprofile">此处</a> 创建游戏档案。若已购买,请检查登录的账户是否正确。\n你可以点击右上角帮助按钮进行求助。
|
||||
account.methods.microsoft.error.no_character=你尚未创建游戏档案。请点击 <a href="https://www.minecraft.net/msaprofile/mygames/editprofile">此处</a> 创建游戏档案。\n你可以点击右上角帮助按钮进行求助。
|
||||
account.methods.microsoft.error.banned=你的账户可能被 Xbox Live 封禁。\n你可以点击 <a href="https://enforcement.xbox.com/enforcement/showenforcementhistory">此处</a> 按钮查询账户封禁状态。
|
||||
account.methods.microsoft.error.unknown=未知问题。错误码:%d。\n你可以点击右上角帮助按钮进行求助。
|
||||
account.methods.microsoft.error.wrong_verify_method=登录失败。请在微软账户登录页面使用密码登录,不要使用其他登录方式。\n你可以点击右上角帮助按钮进行求助。
|
||||
@@ -361,16 +364,16 @@ download.failed.empty=[没有可供安装的版本,点击此处返回]\n(你
|
||||
download.failed.no_code=下载失败
|
||||
download.failed.refresh=[加载版本列表失败,点击此处重试]\n(你可以点击右上角帮助按钮进行求助)
|
||||
download.game=新游戏
|
||||
download.provider.bmclapi=BMCLAPI
|
||||
download.provider.bmclapi.desc=bangbang93, https://bmclapi2.bangbang93.com
|
||||
download.provider.bmclapi=BMCLAPI & MCIM
|
||||
download.provider.bmclapi.desc=适用于中国大陆的镜像源
|
||||
download.provider.mojang=官方
|
||||
download.provider.mojang.desc=OptiFine 自动安装使用 BMCLAPI 下载源
|
||||
download.provider.mojang.desc=注:OptiFine 自动安装使用 BMCLAPI 下载源
|
||||
download.provider.official=尽量使用官方源
|
||||
download.provider.official.desc=最新,但可能加载慢
|
||||
download.provider.official.desc=优先从官方源获取信息
|
||||
download.provider.balanced=选择加载速度快的下载源
|
||||
download.provider.balanced.desc=平衡,但可能不是最新
|
||||
download.provider.balanced.desc=根据你的网络连接情况选择最佳源
|
||||
download.provider.mirror=尽量使用镜像源
|
||||
download.provider.mirror.desc=加载快,但可能不是最新
|
||||
download.provider.mirror.desc=推荐中国大陆用户使用
|
||||
download.java=下载 Java
|
||||
download.java.process=下载 Java
|
||||
download.javafx=正在下载必要的运行时组件……
|
||||
@@ -423,7 +426,6 @@ fatal.unsupported_platform=Minecraft 尚未对你的平台提供完善支持,
|
||||
若无法启动 Minecraft 1.17 及更高版本,可以尝试在“(全局/实例特定) 游戏设置 → 高级设置 → 调试选项”中将“渲染器”切换为“Mesa LLVMpipe”,以获得更好的兼容性。\n\
|
||||
如遇到问题,你可以点击右上角帮助按钮进行求助。
|
||||
fatal.unsupported_platform.loongarch=Hello Minecraft! Launcher 已为龙芯提供支持。\n如果遇到问题,你可以点击右上角帮助按钮进行求助。
|
||||
fatal.unsupported_platform.macos_arm64=Hello Minecraft! Launcher 已为 Apple Silicon 平台提供支持。使用 ARM 原生 Java 启动游戏以获得更流畅的游戏体验。\n如果你在游戏中遇到问题,使用 x86-64 架构的 Java 启动游戏可能有更好的兼容性。\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如遇到问题,你可以点击右上角帮助按钮进行求助。
|
||||
# When translating the following string into non-Latin languages, please retain the English translation
|
||||
fatal.wine_warning=HMCL is running under Wine or one of its distributions.\nAs a cross-platform application, HMCL is best experienced natively on your host OS.\nPress "OK" to continue, but please note that some issues may arise.\n\n您目前正在使用 Wine 或其发行版运行 HMCL。\nHMCL 是跨平台的,我们建议您直接在宿主系统上运行。\n按下“确定”以继续,但请注意,此操作可能会引发一些问题。
|
||||
@@ -571,6 +573,7 @@ install.new_game.already_exists=此实例已经存在,请换一个名字
|
||||
install.new_game.current_game_version=当前游戏实例
|
||||
install.new_game.installation=安装新游戏
|
||||
install.new_game.malformed=名字不合法
|
||||
install.new_game.malformed_json=不是有效的 Minecraft 实例 JSON 文件。
|
||||
install.select=请选择安装方式
|
||||
install.success=安装成功
|
||||
|
||||
@@ -1015,7 +1018,7 @@ world.info.player.game_type=游戏模式
|
||||
world.info.player.game_type.adventure=冒险
|
||||
world.info.player.game_type.creative=创造
|
||||
world.info.player.game_type.hardcore=极限
|
||||
world.info.player.game_type.spectator=旁观者
|
||||
world.info.player.game_type.spectator=旁观
|
||||
world.info.player.game_type.survival=生存
|
||||
world.info.player.health=生命值
|
||||
world.info.player.last_death_location=上次死亡位置
|
||||
@@ -1030,8 +1033,8 @@ world.load.fail=世界加载失败
|
||||
world.locked=使用中
|
||||
world.locked.failed=该世界正在使用中,请关闭游戏后重试。
|
||||
world.manage=世界管理
|
||||
world.manage.button=世界管理
|
||||
world.manage.title=世界管理 - %s
|
||||
world.manage.button=管理此世界
|
||||
world.manage.title=世界 - %s
|
||||
world.name=世界名称
|
||||
world.name.enter=输入世界名称
|
||||
world.show_all=显示全部
|
||||
@@ -1103,6 +1106,7 @@ search.page_n=%d / %s
|
||||
|
||||
selector.choose=选择
|
||||
selector.choose_file=选择文件
|
||||
selector.choose_directory=选择文件夹
|
||||
selector.custom=自定义
|
||||
|
||||
settings=设置
|
||||
@@ -1131,6 +1135,15 @@ settings.advanced.dont_patch_natives=不尝试自动替换本地库
|
||||
settings.advanced.environment_variables=环境变量
|
||||
settings.advanced.game_dir.default=默认 (".minecraft/")
|
||||
settings.advanced.game_dir.independent=各实例独立 (存放在 ".minecraft/versions/<实例名>/",除 assets、libraries 外)
|
||||
settings.advanced.graphics_backend=图形 API
|
||||
settings.advanced.graphics_backend.default=默认
|
||||
settings.advanced.graphics_backend.default.desc=遵循游戏设置
|
||||
settings.advanced.graphics_backend.opengl=OpenGL
|
||||
settings.advanced.graphics_backend.opengl.desc=使用 OpenGL 渲染
|
||||
settings.advanced.graphics_backend.vulkan=Vulkan
|
||||
settings.advanced.graphics_backend.vulkan.desc=使用 Vulkan 渲染
|
||||
settings.advanced.graphics_backend.vulkan.desc.global=使用 Vulkan 渲染 (适用于 Minecraft 26.2 及更高版本)
|
||||
settings.advanced.graphics_backend.vulkan.desc.unsupported=使用 Vulkan 渲染 (不适用于当前游戏版本)
|
||||
settings.advanced.java_permanent_generation_space=内存永久保存区域
|
||||
settings.advanced.java_permanent_generation_space.prompt=单位 MiB
|
||||
settings.advanced.jvm=Java 虚拟机设置
|
||||
@@ -1150,7 +1163,6 @@ settings.advanced.natives_directory.choose=选择本地库路径
|
||||
settings.advanced.natives_directory.custom=自定义 (由你提供游戏需要的本地库)
|
||||
settings.advanced.natives_directory.default=默认 (由启动器提供游戏本地库)
|
||||
settings.advanced.natives_directory.default.version_id=<实例名称>
|
||||
settings.advanced.natives_directory.hint=本选项提供给 Apple Silicon 等未受游戏官方支持的平台来自定义游戏本地库。如果你不知道本选项的含义,请不要修改本选项,否则会导致游戏无法启动!\n\n如果你要修改本选项,你需要保证自定义文件夹下有游戏所需的本地库文件,如 lwjgl.dll (liblwjgl.so)、openal.dll (libopenal.so) 等文件。启动器不会帮你补全缺少的本地库文件!\n\n注意:指定的本地库文件路径建议只包含英文大小写字母、数字和下划线,否则可能会导致启动游戏失败。
|
||||
settings.advanced.no_jvm_args=不添加默认的 Java 虚拟机参数
|
||||
settings.advanced.no_optimizing_jvm_args=不自动添加 Java 虚拟机优化参数
|
||||
settings.advanced.precall_command=游戏启动前执行命令
|
||||
@@ -1168,15 +1180,49 @@ settings.advanced.process_priority.high=高
|
||||
settings.advanced.process_priority.high.desc=优先保证游戏运行,但可能会导致其他程序卡顿
|
||||
settings.advanced.post_exit_command=游戏结束后执行命令
|
||||
settings.advanced.post_exit_command.prompt=将在游戏结束后调用
|
||||
settings.advanced.renderer=渲染器
|
||||
settings.advanced.renderer=渲染器/驱动
|
||||
settings.advanced.renderer.default=默认
|
||||
settings.advanced.renderer.default.desc=OpenGL
|
||||
settings.advanced.renderer.d3d12=Mesa D3D12
|
||||
settings.advanced.renderer.d3d12.desc=DirectX 12 (性能与兼容性较差,用于调试)
|
||||
settings.advanced.renderer.default.desc=使用系统默认渲染器
|
||||
# Vulkan Renderers
|
||||
settings.advanced.renderer.lavapipe=Mesa Lavapipe
|
||||
settings.advanced.renderer.lavapipe.desc=软件 Vulkan 渲染器
|
||||
settings.advanced.renderer.dozen=Mesa Dozen
|
||||
settings.advanced.renderer.dozen.desc=基于 DirectX 12 的 Vulkan 渲染器 (实验性)
|
||||
settings.advanced.renderer.nvidia_vulkan=NVIDIA
|
||||
settings.advanced.renderer.nvidia_vulkan.desc=NVIDIA GPU 的官方 Vulkan 驱动
|
||||
settings.advanced.renderer.nvidia_nvk=NVIDIA (NVK)
|
||||
settings.advanced.renderer.nvidia_nvk.desc=NVIDIA GPU 的开源 Vulkan 驱动
|
||||
settings.advanced.renderer.amdvlk=AMD
|
||||
settings.advanced.renderer.amdvlk.desc=AMD GPU 的官方 Vulkan 驱动
|
||||
settings.advanced.renderer.amd_radv=AMD (RADV)
|
||||
settings.advanced.renderer.amd_radv.desc=AMD GPU 的开源 Vulkan 驱动
|
||||
settings.advanced.renderer.intel_vulkan=Intel
|
||||
settings.advanced.renderer.intel_vulkan.desc=Intel GPU 的官方 Vulkan 驱动
|
||||
settings.advanced.renderer.intel_anv=Intel (ANV)
|
||||
settings.advanced.renderer.intel_anv.desc=Intel GPU 的开源 Vulkan 驱动
|
||||
settings.advanced.renderer.intel_hasvk=Intel (HASVK)
|
||||
settings.advanced.renderer.intel_hasvk.desc=Intel 旧 GPU 的开源 Vulkan 驱动
|
||||
settings.advanced.renderer.qualcomm=高通
|
||||
settings.advanced.renderer.qualcomm.desc=高通 Adreno GPU 的官方 Vulkan 驱动
|
||||
settings.advanced.renderer.turnip=高通 (Turnip)
|
||||
settings.advanced.renderer.turnip.desc=高通 Adreno GPU 的开源 Vulkan 驱动
|
||||
settings.advanced.renderer.moltenvk=MoltenVK
|
||||
settings.advanced.renderer.moltenvk.desc=基于 Metal 的 Vulkan 渲染器
|
||||
settings.advanced.renderer.kosmickrisp=KosmicKrisp
|
||||
settings.advanced.renderer.kosmickrisp.desc=基于 Metal 4 的 Vulkan 渲染器 (实验性)
|
||||
settings.advanced.renderer.powervr=PowerVR
|
||||
settings.advanced.renderer.powervr.desc=PowerVR GPU 的开源 Vulkan 驱动 (实验性)
|
||||
settings.advanced.renderer.panvk=PanVK
|
||||
settings.advanced.renderer.panvk.desc=Arm Mali GPU 的开源 Vulkan 驱动 (实验性)
|
||||
settings.advanced.renderer.v3dv=V3DV
|
||||
settings.advanced.renderer.v3dv.desc=树莓派 4/5 GPU 的开源 Vulkan 驱动 (实验性)
|
||||
# OpenGL Renderers
|
||||
settings.advanced.renderer.llvmpipe=Mesa LLVMpipe
|
||||
settings.advanced.renderer.llvmpipe.desc=软渲染器 (性能较差,兼容性最好)
|
||||
settings.advanced.renderer.llvmpipe.desc=软件 OpenGL 渲染器
|
||||
settings.advanced.renderer.d3d12=Mesa D3D12
|
||||
settings.advanced.renderer.d3d12.desc=基于 DirectX 12 的 OpenGL 渲染器
|
||||
settings.advanced.renderer.zink=Mesa Zink
|
||||
settings.advanced.renderer.zink.desc=Vulkan (性能最好,兼容性较差)
|
||||
settings.advanced.renderer.zink.desc=基于 Vulkan 的 OpenGL 渲染器
|
||||
settings.advanced.server_ip=服务器地址
|
||||
settings.advanced.server_ip.prompt=默认,启动游戏后可以直接进入对应服务器
|
||||
settings.advanced.unsupported_system_options=不适用于当前系统的选项
|
||||
@@ -1233,6 +1279,7 @@ settings.launcher.download_source=下载源
|
||||
settings.launcher.download_source.auto=自动选择下载源
|
||||
settings.launcher.default_addon_source=游戏内容默认下载源
|
||||
settings.launcher.enable_game_list=在主页内显示版本列表
|
||||
settings.launcher.misc=杂项
|
||||
settings.launcher.font=字体
|
||||
settings.launcher.font.anti_aliasing=抗锯齿
|
||||
settings.launcher.font.anti_aliasing.auto=自动
|
||||
|
||||
@@ -44,6 +44,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
}
|
||||
|
||||
.dialog-card {
|
||||
border-radius: 4px;
|
||||
background-color: var(--monet-surface-container-high);
|
||||
padding: 24px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.14), 0 0 0 1px rgba(0, 0, 0, 0.02);
|
||||
|
||||
@@ -781,17 +781,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl:3.4.1": {
|
||||
"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.1:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl:3.4.1:natives-linux-arm64",
|
||||
"downloads": {
|
||||
@@ -803,17 +792,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-jemalloc:3.4.1": {
|
||||
"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.1:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-jemalloc:3.4.1:natives-linux-arm64",
|
||||
"downloads": {
|
||||
@@ -825,17 +803,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-openal:3.4.1": {
|
||||
"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.1:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-openal:3.4.1:natives-linux-arm64",
|
||||
"downloads": {
|
||||
@@ -847,17 +814,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-opengl:3.4.1": {
|
||||
"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.1:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-opengl:3.4.1:natives-linux-arm64",
|
||||
"downloads": {
|
||||
@@ -869,17 +825,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-glfw:3.4.1": {
|
||||
"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.1:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-glfw:3.4.1:natives-linux-arm64",
|
||||
"downloads": {
|
||||
@@ -891,17 +836,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-stb:3.4.1": {
|
||||
"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.1:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-stb:3.4.1:natives-linux-arm64",
|
||||
"downloads": {
|
||||
@@ -913,17 +847,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-tinyfd:3.4.1": {
|
||||
"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.1:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-tinyfd:3.4.1:natives-linux-arm64",
|
||||
"downloads": {
|
||||
@@ -935,17 +858,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-freetype:3.4.1": {
|
||||
"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.1:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-freetype:3.4.1:natives-linux-arm64",
|
||||
"downloads": {
|
||||
@@ -957,6 +869,39 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-shaderc:3.4.1:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-shaderc:3.4.1:natives-linux-arm64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-shaderc/3.4.1/lwjgl-shaderc-3.4.1-natives-linux-arm64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-shaderc/3.4.1/lwjgl-shaderc-3.4.1-natives-linux-arm64.jar",
|
||||
"sha1": "bc5c99b2eb7e2109c6db21cfced15a6851f8111b",
|
||||
"size": 3397476
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-spvc:3.4.1:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-spvc:3.4.1:natives-linux-arm64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-spvc/3.4.1/lwjgl-spvc-3.4.1-natives-linux-arm64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-spvc/3.4.1/lwjgl-spvc-3.4.1-natives-linux-arm64.jar",
|
||||
"sha1": "e2eaa8f516d43fe11b8253e6243b3d8e1315c9c8",
|
||||
"size": 1139919
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-vma:3.4.1:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-vma:3.4.1:natives-linux-arm64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-vma/3.4.1/lwjgl-vma-3.4.1-natives-linux-arm64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-vma/3.4.1/lwjgl-vma-3.4.1-natives-linux-arm64.jar",
|
||||
"sha1": "de40d43c5947c4a4f91376af8f2c00e5396cc109",
|
||||
"size": 59715
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl.lwjgl:lwjgl-platform:2.9.0:natives": {
|
||||
"name": "org.glavo.hmcl:lwjgl2-natives:2.9.3",
|
||||
"downloads": {
|
||||
@@ -2689,6 +2634,9 @@
|
||||
"org.lwjgl:lwjgl-stb:3.4.1:natives-linux": null,
|
||||
"org.lwjgl:lwjgl-tinyfd:3.4.1:natives-linux": null,
|
||||
"org.lwjgl:lwjgl-freetype:3.4.1:natives-linux": null,
|
||||
"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,
|
||||
"net.java.dev.jna:jna:5.8.0": {
|
||||
"name": "net.java.dev.jna:jna:5.13.0",
|
||||
"downloads": {
|
||||
@@ -4359,6 +4307,83 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-shaderc:3.4.1": {
|
||||
"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.1:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-shaderc:3.4.1:natives-linux-riscv64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-shaderc/3.4.1/lwjgl-shaderc-3.4.1-natives-linux-riscv64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-shaderc/3.4.1/lwjgl-shaderc-3.4.1-natives-linux-riscv64.jar",
|
||||
"sha1": "5598b0b86d4b3ca0ae78817719d5814efb9c7a77",
|
||||
"size": 3147886
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-spvc:3.4.1": {
|
||||
"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.1:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-spvc:3.4.1:natives-linux-riscv64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-spvc/3.4.1/lwjgl-spvc-3.4.1-natives-linux-riscv64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-spvc/3.4.1/lwjgl-spvc-3.4.1-natives-linux-riscv64.jar",
|
||||
"sha1": "ea09f614c961b14962e4cc55a05c6767f9440f3f",
|
||||
"size": 1215586
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-vma:3.4.1": {
|
||||
"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.1:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-vma:3.4.1:natives-linux-riscv64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-vma/3.4.1/lwjgl-vma-3.4.1-natives-linux-riscv64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-vma/3.4.1/lwjgl-vma-3.4.1-natives-linux-riscv64.jar",
|
||||
"sha1": "9616440ee3cb2cfeacd99a61eefded856b5d7efe",
|
||||
"size": 57091
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-vulkan:3.4.1": {
|
||||
"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
|
||||
}
|
||||
}
|
||||
},
|
||||
"com.github.oshi:oshi-core:6.6.5": {
|
||||
"name": "com.github.oshi:oshi-core:6.8.0",
|
||||
"downloads": {
|
||||
@@ -4378,13 +4403,13 @@
|
||||
},
|
||||
"windows-x86_64": {
|
||||
"mesa-loader": {
|
||||
"name": "org.glavo:mesa-loader-windows:25.0.3:x64",
|
||||
"name": "org.glavo:mesa-loader-windows:26.0.4:x64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/glavo/mesa-loader-windows/25.0.3/mesa-loader-windows-25.0.3-x64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/glavo/mesa-loader-windows/25.0.3/mesa-loader-windows-25.0.3-x64.jar",
|
||||
"sha1": "b2552fcc8c98e95b4559c39f2ff87cdb3aaaa513",
|
||||
"size": 27971214
|
||||
"path": "org/glavo/mesa-loader-windows/26.0.4/mesa-loader-windows-26.0.4-x64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/glavo/mesa-loader-windows/26.0.4/mesa-loader-windows-26.0.4-x64.jar",
|
||||
"sha1": "f8da709c59ef61f531c91434ca0e3b4f39202981",
|
||||
"size": 49873846
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -4402,13 +4427,13 @@
|
||||
},
|
||||
"windows-x86": {
|
||||
"mesa-loader": {
|
||||
"name": "org.glavo:mesa-loader-windows:25.0.3:x86",
|
||||
"name": "org.glavo:mesa-loader-windows:26.0.4:x86",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/glavo/mesa-loader-windows/25.0.3/mesa-loader-windows-25.0.3-x86.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/glavo/mesa-loader-windows/25.0.3/mesa-loader-windows-25.0.3-x86.jar",
|
||||
"sha1": "6fd0fd7da88ca6636f735e1e0f6feb7f62f59715",
|
||||
"size": 23311299
|
||||
"path": "org/glavo/mesa-loader-windows/26.0.4/mesa-loader-windows-26.0.4-x86.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/glavo/mesa-loader-windows/26.0.4/mesa-loader-windows-26.0.4-x86.jar",
|
||||
"sha1": "ac6afaa8baa7c17468267c09e77e1296ee92d5ed",
|
||||
"size": 41742113
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4791,13 +4816,13 @@
|
||||
"com.mojang:text2speech:1.12.4:natives": null,
|
||||
"com.mojang:text2speech:1.13.9:natives-windows": null,
|
||||
"mesa-loader": {
|
||||
"name": "org.glavo:mesa-loader-windows:25.0.3:arm64",
|
||||
"name": "org.glavo:mesa-loader-windows:26.0.4:arm64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/glavo/mesa-loader-windows/25.0.3/mesa-loader-windows-25.0.3-arm64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/glavo/mesa-loader-windows/25.0.3/mesa-loader-windows-25.0.3-arm64.jar",
|
||||
"sha1": "40ca5de3351bf48b7b9efa5d2598d5b6b4d1ab8e",
|
||||
"size": 24905270
|
||||
"path": "org/glavo/mesa-loader-windows/26.0.4/mesa-loader-windows-26.0.4-arm64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/glavo/mesa-loader-windows/26.0.4/mesa-loader-windows-26.0.4-arm64.jar",
|
||||
"sha1": "5c761a344700a07eaded51c5cf0cde36ee614706",
|
||||
"size": 43504189
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6325,6 +6350,83 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-shaderc:3.4.1": {
|
||||
"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.1:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-shaderc:3.4.1:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-shaderc/3.4.1/lwjgl-shaderc-3.4.1-natives-freebsd.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-shaderc/3.4.1/lwjgl-shaderc-3.4.1-natives-freebsd.jar",
|
||||
"sha1": "872460f4b007ed3a13a2d311e90bda5370406fc8",
|
||||
"size": 2953522
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-spvc:3.4.1": {
|
||||
"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.1:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-spvc:3.4.1:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-spvc/3.4.1/lwjgl-spvc-3.4.1-natives-freebsd.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-spvc/3.4.1/lwjgl-spvc-3.4.1-natives-freebsd.jar",
|
||||
"sha1": "7a3d0e4a74f022b3196ecd33d9ebffafec7767f4",
|
||||
"size": 1094646
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-vma:3.4.1": {
|
||||
"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.1:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-vma:3.4.1:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-vma/3.4.1/lwjgl-vma-3.4.1-natives-freebsd.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-vma/3.4.1/lwjgl-vma-3.4.1-natives-freebsd.jar",
|
||||
"sha1": "85851480bef99188bf9a84b36cbe862cb4a52016",
|
||||
"size": 62862
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-vulkan:3.4.1": {
|
||||
"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
|
||||
}
|
||||
}
|
||||
},
|
||||
"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,
|
||||
|
||||
Loaded 100 of 159 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user