Compare commits

..
17 Commits
Author SHA1 Message Date
Glavo e0a91579f2 更新内置主题相关译文 (#6429) 2026-07-17 21:29:51 +08:00
快乐凤爪王 b68f404c3c Refactor: Style of ModInfoDialog (#6423) 2026-07-17 21:28:02 +08:00
Glavo c1f50f9ba3 删除 Gemini 审查配置文件 (#6430) 2026-07-17 21:17:31 +08:00
辞庐 d8bb2131f2 允许拖入 nbt 文件 (#6427) 2026-07-17 21:08:45 +08:00
Glavo aca761385b Add support for Minecraft 26.3-snapshot-4+ to unofficial supported platforms (#6425) 2026-07-17 21:00:14 +08:00
3gf8jv4dv 15faccf376 chore(workflow): add pr-size-label workflow file (#6381) 2026-07-17 20:23:48 +08:00
Glavo 241c9c419c 修复 FXUtils 意外的被提前初始化的问题 (#6421) 2026-07-16 21:29:12 +08:00
辞庐 4b36f8ce03 fix: 任务窗口速度与取消未对齐 (#6140) 2026-07-16 21:28:31 +08:00
Glavo 5c0baad782 提早加载配置文件 (#6420) 2026-07-16 20:50:20 +08:00
Apostrophe 850a56405c 修复游戏 Java 路径显示滞后问题 (#6419) 2026-07-16 19:59:17 +08:00
快乐凤爪王 e8487c467b Refactor: Replace deprecated URL constructor (#6409) 2026-07-16 03:37:54 +08:00
Glavo 9866d88a16 完善游戏设置页面帮助文本 (#6415) 2026-07-15 21:15:00 +08:00
辞庐 af2d46ae89 Revert "Refactor: Remove redundant downloadSourceComboBox from ModDownloadListPageSkin" (#6410)
Reverts HMCL-dev/HMCL#6370
2026-07-15 19:56:20 +08:00
ToobLac 6f7c561ecb [Bugfix] 修复 1.21.1 安装信雅互联仍显示不支持 Fabric 模组的问题 (#6404)
Fixes #6391
2026-07-14 21:41:39 +08:00
ToobLac 93abe31acf [Chores] 处理升级到 9.6.1 后 Gradle 弃用提示 (#6405) 2026-07-14 21:29:58 +08:00
Glavo a6f8659349 Bump Gradle Wrapper Neo to 0.2.0 (#6397) 2026-07-14 00:06:56 +08:00
Glavo 8728c89093 Bump version root to 3.17 (#6392) 2026-07-13 20:34:38 +08:00
18 changed files with 512 additions and 235 deletions

No files matched your search

-3
View File
@@ -1,3 +0,0 @@
code_review:
pull_request_opened:
code_review: false
+147
View File
@@ -0,0 +1,147 @@
name: PR Size Label
on:
pull_request_target:
types:
- opened
- reopened
- synchronize
workflow_dispatch:
permissions:
contents: read
issues: write
pull-requests: write
jobs:
Label:
runs-on: ubuntu-latest
steps:
- name: Label PR by filtered changed lines
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const owner = context.repo.owner
const repo = context.repo.repo
const ignoredFilePatterns = [
/(?:^|\/)\.cnb\/.+\.ya?ml$/i,
/(?:^|\/)\.gemini\/.+\.ya?ml$/i,
/(?:^|\/)\.gitee\/.+\.ya?ml$/i,
/(?:^|\/)\.github\/.+\.ya?ml$/i,
/(?:^|\/)\.idea\/.+\.xml$/i,
/(?:^|\/)\.editorconfig$/i,
/(?:^|\/)\.gitattributes$/i,
/(?:^|\/)\.gitignore$/i,
/(?:^|\/).+\.kts$/i,
/(?:^|\/).+\.md$/i,
/(?:^|\/)LICENSE(?:_.*)?$/i,
/(?:^|\/)gradlew(?:\.bat|\.ps1)?$/i,
/(?:^|\/)gradle\/wrapper\/gradle-wrapper\.properties$/i,
/(?:^|\/)lang\/.+\.properties$/i,
/(?:^|\/)l10n\/.+\.properties$/i,
/(?:^|\/)checkstyle\.xml$/i,
/(?:^|\/).+\.(?:png|jpe?g|gif|ico|svg|json|ttf|woff2?|webp|avif|jxl|heic|mp4|webm|mp3|ogg|wav|txt|toml)$/i
]
async function processPR(issue_number) {
const files = await github.paginate(github.rest.pulls.listFiles, {
owner,
repo,
pull_number: issue_number,
per_page: 100,
})
const countedFiles = []
const ignoredFiles = []
let totalChanges = 0
for (const file of files) {
const ignored = ignoredFilePatterns.some(pattern => pattern.test(file.filename))
if (ignored) {
ignoredFiles.push(file.filename)
continue
}
const fileChanges = file.changes ?? ((file.additions || 0) + (file.deletions || 0))
totalChanges += fileChanges
countedFiles.push(`${file.filename} (${fileChanges})`)
}
let targetLabel
if (totalChanges <= 9) targetLabel = '1+'
else if (totalChanges <= 39) targetLabel = '10+'
else if (totalChanges <= 99) targetLabel = '40+'
else if (totalChanges <= 499) targetLabel = '100+'
else if (totalChanges <= 999) targetLabel = '500+'
else if (totalChanges <= 1999) targetLabel = '1000+'
else if (totalChanges <= 4999) targetLabel = '2000+'
else targetLabel = '5000+'
const currentLabels = await github.paginate(github.rest.issues.listLabelsOnIssue, {
owner,
repo,
issue_number,
per_page: 100,
})
const sizeLabels = currentLabels
.map(label => label.name)
.filter(name => /^\d+\+$/.test(name))
for (const name of sizeLabels) {
if (name !== targetLabel) {
await github.rest.issues.removeLabel({
owner,
repo,
issue_number,
name,
}).catch(() => {})
}
}
if (!currentLabels.some(label => label.name === targetLabel)) {
await github.rest.issues.addLabels({
owner,
repo,
issue_number,
labels: [targetLabel],
})
}
core.info(`Pull request #${issue_number} has ${totalChanges} counted changed lines, applied ${targetLabel}.`)
core.info(`Ignored files: ${ignoredFiles.length ? ignoredFiles.join(', ') : 'none'}`)
core.info(`Counted files: ${countedFiles.length ? countedFiles.join(', ') : 'none'}`)
}
if (context.eventName === 'workflow_dispatch') {
core.info('Manual trigger detected. Fetching all open pull requests...')
const openPRs = await github.paginate(github.rest.pulls.list, {
owner,
repo,
state: 'open',
per_page: 100,
})
const unlabelledPRs = openPRs.filter(pr => {
return !pr.labels.some(label => /^\d+\+$/.test(label.name))
})
core.info(`Found ${openPRs.length} open pull request(s), ${unlabelledPRs.length} of them lack a size label.`)
for (const pr of unlabelledPRs) {
core.info(`----------------------------------------`)
core.info(`Processing unlabelled pull request #${pr.number}: ${pr.title}`)
await processPR(pr.number)
}
} else {
const pr = context.payload.pull_request
if (!pr) {
core.info('No pull request found in payload, skipping.')
return
}
await processPR(pr.number)
}
+7 -7
View File
@@ -52,7 +52,7 @@ if (buildNumber != null) {
}
}
val embedResources by configurations.registering
val embedResources = configurations.register("embedResources")
dependencies {
implementation(project(":HMCLCore"))
@@ -166,7 +166,7 @@ val hmclProperties = buildList {
}
val hmclPropertiesFile = layout.buildDirectory.file("hmcl.properties")
val createPropertiesFile by tasks.registering {
val createPropertiesFile = tasks.register("createPropertiesFile") {
outputs.file(hmclPropertiesFile)
hmclProperties.forEach { (k, v) -> inputs.property(k, v) }
@@ -258,7 +258,7 @@ tasks.processResources {
fun artifactFile(ext: String) = jarPath.resolveSibling(jarPath.nameWithoutExtension + '.' + ext)
val makeExecutables by tasks.registering {
val makeExecutables = tasks.register("makeExecutables") {
val extensions = listOf("exe", "sh")
dependsOn(tasks.jar)
@@ -286,7 +286,7 @@ val makeExecutables by tasks.registering {
}
}
val makeDeb by tasks.registering(CreateDeb::class) {
val makeDeb = tasks.register("makeDeb", CreateDeb::class) {
dependsOn(makeExecutables)
val debFile = layout.file(provider { artifactFile("deb") })
@@ -438,19 +438,19 @@ tasks.register<CheckTranslations>("checkTranslations") {
val generatedDir = layout.buildDirectory.dir("generated")
val upsideDownTranslate by tasks.registering(UpsideDownTranslate::class) {
val upsideDownTranslate = tasks.register<UpsideDownTranslate>("upsideDownTranslate") {
inputFile.set(layout.projectDirectory.file("src/main/resources/assets/lang/I18N.properties"))
outputFile.set(generatedDir.map { it.file("generated/i18n/I18N_en_Qabs.properties") })
}
val createLanguageList by tasks.registering(CreateLanguageList::class) {
val createLanguageList = tasks.register<CreateLanguageList>("createLanguageList") {
resourceBundleDir.set(layout.projectDirectory.dir("src/main/resources/assets/lang"))
resourceBundleBaseName.set("I18N")
additionalLanguages.set(listOf("en-Qabs"))
outputFile.set(generatedDir.map { it.file("languages.json") })
}
val createLocaleNamesResourceBundle by tasks.registering(CreateLocaleNamesResourceBundle::class) {
val createLocaleNamesResourceBundle = tasks.register<CreateLocaleNamesResourceBundle>("createLocaleNamesResourceBundle") {
dependsOn(createLanguageList)
languagesFile.set(createLanguageList.flatMap { it.outputFile })
@@ -52,8 +52,6 @@ public final class EntryPoint {
checkWine();
setupJavaFXVMOptions();
if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS) {
System.getProperties().putIfAbsent("apple.awt.application.appearance", "system");
if (!isInsideMacAppBundle())
@@ -74,71 +72,6 @@ public final class EntryPoint {
System.exit(exitCode);
}
private static void setupJavaFXVMOptions() {
if ("true".equalsIgnoreCase(System.getenv("HMCL_FORCE_GPU"))) {
LOG.info("HMCL_FORCE_GPU: true");
System.getProperties().putIfAbsent("prism.forceGPU", "true");
}
String animationFrameRate = System.getenv("HMCL_ANIMATION_FRAME_RATE");
if (animationFrameRate != null) {
LOG.info("HMCL_ANIMATION_FRAME_RATE: " + animationFrameRate);
try {
if (Integer.parseInt(animationFrameRate) <= 0)
throw new NumberFormatException(animationFrameRate);
System.getProperties().putIfAbsent("javafx.animation.pulse", animationFrameRate);
} catch (NumberFormatException e) {
LOG.warning("Invalid animation frame rate: " + animationFrameRate);
}
}
String uiScale = System.getProperty("hmcl.uiScale", System.getenv("HMCL_UI_SCALE"));
if (uiScale != null) {
uiScale = uiScale.trim();
LOG.info("HMCL_UI_SCALE: " + uiScale);
try {
float scaleValue;
if (uiScale.endsWith("%")) {
scaleValue = Integer.parseInt(uiScale.substring(0, uiScale.length() - 1)) / 100.0f;
} else if (uiScale.endsWith("dpi") || uiScale.endsWith("DPI")) {
scaleValue = Integer.parseInt(uiScale.substring(0, uiScale.length() - 3)) / 96.0f;
} else {
scaleValue = Float.parseFloat(uiScale);
}
float lowerBound;
float upperBound;
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
// JavaFX behavior may be abnormal when the DPI scaling factor is too high
lowerBound = 0.25f;
upperBound = 4f;
} else {
lowerBound = 0.01f;
upperBound = 10f;
}
if (scaleValue >= lowerBound && scaleValue <= upperBound) {
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
System.getProperties().putIfAbsent("glass.win.uiScale", uiScale);
} else if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS) {
LOG.warning("macOS does not support setting UI scale, so it will be ignored");
} else {
System.getProperties().putIfAbsent("glass.gtk.uiScale", uiScale);
}
} else {
LOG.warning("UI scale out of range: " + uiScale);
}
} catch (Throwable e) {
LOG.warning("Invalid UI scale: " + uiScale);
}
}
}
private static void createHMCLDirectories() {
if (!Files.isDirectory(Metadata.HMCL_LOCAL_HOME)) {
try {
@@ -225,15 +158,8 @@ public final class EntryPoint {
private static void checkWine() {
if (OperatingSystem.isRunningUnderWine()) {
SwingUtils.initLookAndFeel();
LOG.warning("HMCL is running under Wine or its distributions!");
int result = JOptionPane.showOptionDialog(null, i18n("fatal.wine_warning"), i18n("message.warning"), JOptionPane.OK_CANCEL_OPTION,
JOptionPane.WARNING_MESSAGE, null, null, null);
if (result == JOptionPane.CANCEL_OPTION || result == JOptionPane.CLOSED_OPTION) {
exit(1);
}
showWarning(i18n("fatal.wine_warning"));
}
}
@@ -273,10 +199,21 @@ public final class EntryPoint {
}
}
static void showWarning(String message) {
SwingUtils.initLookAndFeel();
int result = JOptionPane.showOptionDialog(null, message, i18n("message.warning"), JOptionPane.OK_CANCEL_OPTION,
JOptionPane.WARNING_MESSAGE, null, null, null);
if (result == JOptionPane.CANCEL_OPTION || result == JOptionPane.CLOSED_OPTION) {
exit(1);
}
}
/**
* Indicates that a fatal error has occurred, and that the application cannot start.
*/
private static void showErrorAndExit(String message) {
static void showErrorAndExit(String message) {
SwingUtils.showErrorDialog(message);
exit(1);
}
@@ -29,8 +29,6 @@ import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.input.Clipboard;
import javafx.scene.input.DataFormat;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.util.Duration;
@@ -49,6 +47,9 @@ import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.io.JarUtils;
import org.jackhuang.hmcl.util.platform.*;
import javax.swing.*;
import java.awt.*;
import java.awt.datatransfer.StringSelection;
import java.io.File;
import java.io.IOException;
import java.lang.management.ManagementFactory;
@@ -60,7 +61,6 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
@@ -98,18 +98,7 @@ public final class Launcher extends Application {
}
try {
try {
SettingsManager.init();
initializeSettingsRuntime();
} catch (SambaException e) {
showAlert(AlertType.WARNING, i18n("fatal.samba"));
} catch (IOException e) {
LOG.error("Failed to load config", e);
checkConfigInTempDir();
checkConfigOwner();
showAlert(AlertType.ERROR, i18n("fatal.config_loading_failure", SettingsManager.localConfigDirectory()));
EntryPoint.exit(1);
}
initializeSettingsRuntime();
// https://lapcatsoftware.com/articles/app-translocation.html
if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS
@@ -274,48 +263,6 @@ public final class Launcher extends Application {
}
}
private static void checkConfigOwner() {
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS)
return;
String userName = System.getProperty("user.name");
Path configDirectory = SettingsManager.localConfigDirectory();
if (!Files.exists(configDirectory)) {
return;
}
String owner;
try {
owner = Files.getOwner(configDirectory).getName();
} catch (IOException ioe) {
LOG.warning("Failed to get file owner", ioe);
return;
}
if (Files.isWritable(configDirectory) || userName.equals("root") || userName.equals(owner))
return;
ArrayList<String> files = new ArrayList<>();
files.add(configDirectory.toString());
if (Files.exists(Metadata.HMCL_USER_HOME))
files.add(Metadata.HMCL_USER_HOME.toString());
Path mcDir = Paths.get(".minecraft").toAbsolutePath().normalize();
if (Files.exists(mcDir))
files.add(mcDir.toString());
String command = new CommandBuilder().addAll("sudo", "chown", "-R", userName).addAll(files).toString();
ButtonType copyAndExit = new ButtonType(i18n("button.copy_and_exit"));
if (showAlert(AlertType.ERROR,
i18n("fatal.config_loading_failure.unix", owner, command),
copyAndExit, ButtonType.CLOSE) == copyAndExit) {
Clipboard.getSystemClipboard()
.setContent(Collections.singletonMap(DataFormat.PLAIN_TEXT, command));
}
EntryPoint.exit(1);
}
@Override
public void stop() throws Exception {
Controllers.onApplicationStop();
@@ -376,6 +323,19 @@ public final class Launcher extends Application {
Lang.thread(SystemInfo::initialize, "Detection System Information", true);
try {
SettingsManager.init();
} catch (SambaException e) {
EntryPoint.showWarning(i18n("fatal.samba"));
} catch (IOException e) {
LOG.error("Failed to load config", e);
checkConfigOwner();
SwingUtils.showErrorDialog(i18n("fatal.config_loading_failure", SettingsManager.localConfigDirectory()));
EntryPoint.exit(1);
}
setupJavaFXVMOptions();
launch(Launcher.class, args);
} catch (Throwable e) { // Fucking JavaFX will suppress the exception and will break our crash reporter.
CRASH_REPORTER.uncaughtException(Thread.currentThread(), e);
@@ -408,5 +368,127 @@ public final class Launcher extends Application {
});
}
private static void setupJavaFXVMOptions() {
if ("true".equalsIgnoreCase(System.getenv("HMCL_FORCE_GPU"))) {
LOG.info("HMCL_FORCE_GPU: true");
System.getProperties().putIfAbsent("prism.forceGPU", "true");
}
setUpAnimationFrameRate:
{
String animationFrameRate = System.getenv("HMCL_ANIMATION_FRAME_RATE");
if (animationFrameRate != null) {
LOG.info("HMCL_ANIMATION_FRAME_RATE: " + animationFrameRate);
try {
if (Integer.parseInt(animationFrameRate) <= 0)
throw new NumberFormatException(animationFrameRate);
System.getProperties().putIfAbsent("javafx.animation.pulse", animationFrameRate);
} catch (NumberFormatException e) {
LOG.warning("Invalid animation frame rate: " + animationFrameRate);
}
break setUpAnimationFrameRate;
}
}
String uiScale = System.getProperty("hmcl.uiScale", System.getenv("HMCL_UI_SCALE"));
if (uiScale != null) {
uiScale = uiScale.trim();
LOG.info("HMCL_UI_SCALE: " + uiScale);
try {
float scaleValue;
if (uiScale.endsWith("%")) {
scaleValue = Integer.parseInt(uiScale.substring(0, uiScale.length() - 1)) / 100.0f;
} else if (uiScale.endsWith("dpi") || uiScale.endsWith("DPI")) {
scaleValue = Integer.parseInt(uiScale.substring(0, uiScale.length() - 3)) / 96.0f;
} else {
scaleValue = Float.parseFloat(uiScale);
}
float lowerBound;
float upperBound;
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
// JavaFX behavior may be abnormal when the DPI scaling factor is too high
lowerBound = 0.25f;
upperBound = 4f;
} else {
lowerBound = 0.01f;
upperBound = 10f;
}
if (scaleValue >= lowerBound && scaleValue <= upperBound) {
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
System.getProperties().putIfAbsent("glass.win.uiScale", uiScale);
} else if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS) {
LOG.warning("macOS does not support setting UI scale, so it will be ignored");
} else {
System.getProperties().putIfAbsent("glass.gtk.uiScale", uiScale);
}
} else {
LOG.warning("UI scale out of range: " + uiScale);
}
} catch (Throwable e) {
LOG.warning("Invalid UI scale: " + uiScale);
}
}
}
private static void checkConfigOwner() {
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS)
return;
String userName = System.getProperty("user.name");
Path configDirectory = SettingsManager.localConfigDirectory();
if (!Files.exists(configDirectory)) {
return;
}
String owner;
try {
owner = Files.getOwner(configDirectory).getName();
} catch (IOException ioe) {
LOG.warning("Failed to get file owner", ioe);
return;
}
if (Files.isWritable(configDirectory) || userName.equals("root") || userName.equals(owner))
return;
ArrayList<String> files = new ArrayList<>();
files.add(configDirectory.toString());
if (Files.exists(Metadata.HMCL_USER_HOME))
files.add(Metadata.HMCL_USER_HOME.toString());
Path mcDir = Paths.get(".minecraft").toAbsolutePath().normalize();
if (Files.exists(mcDir))
files.add(mcDir.toString());
String command = new CommandBuilder().addAll("sudo", "chown", "-R", userName).addAll(files).toString();
SwingUtils.initLookAndFeel();
Object[] options = {i18n("button.copy_and_exit"), i18n("button.cancel")};
int result = JOptionPane.showOptionDialog(null,
i18n("fatal.config_loading_failure.unix", owner, command),
i18n("message.error"),
JOptionPane.DEFAULT_OPTION,
JOptionPane.ERROR_MESSAGE,
null,
options,
options[0]);
if (result == 0) {
try {
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(command), null);
} catch (Throwable e) {
LOG.warning("Failed to copy command to clipboard", e);
}
}
EntryPoint.exit(1);
}
public static final CrashReporter CRASH_REPORTER = new CrashReporter(true);
}
@@ -34,8 +34,6 @@ import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Type;
import java.util.Objects;
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
/// Persistent configuration for a game directory.
@JsonAdapter(GameDirectory.Serializer.class)
@NotNullByDefault
@@ -118,7 +116,7 @@ public final class GameDirectory implements Observable {
this.path = new SimpleObjectProperty<>(this, "path", Objects.requireNonNull(path));
this.legacyGameSettings = new SimpleObjectProperty<>(this, "legacyGameSettings", legacyGameSettings);
addPropertyChangedListener(onInvalidating(this::invalidate));
addPropertyChangedListener(arg -> invalidate());
}
/// Returns a debug string containing the game directory path and display metadata.
@@ -480,17 +480,19 @@ public final class LauncherSettings extends ObservableSetting implements JsonSch
/// Whether UI animations are disabled.
@SerializedName("animationDisabled")
private final BooleanProperty animationDisabled = new SimpleBooleanProperty(
FXUtils.REDUCED_MOTION == Boolean.TRUE
|| !JavaRuntime.CURRENT_JIT_ENABLED
|| !FXUtils.GPU_ACCELERATION_ENABLED
);
private final ObjectProperty<@Nullable Boolean> animationDisabled = new SimpleObjectProperty<>();
/// Returns the UI animation disable property.
public BooleanProperty animationDisabledProperty() {
public ObjectProperty<@Nullable Boolean> animationDisabledProperty() {
return animationDisabled;
}
public boolean isAnimationDisabled() {
// Avoid accessing FXUtils too early during startup
return Objects.requireNonNullElseGet(animationDisabled.get(), () ->
FXUtils.REDUCED_MOTION == Boolean.TRUE || !JavaRuntime.CURRENT_JIT_ENABLED || !FXUtils.GPU_ACCELERATION_ENABLED);
}
// Networks
/// Whether HMCL automatically selects the number of download threads.
@@ -22,6 +22,8 @@ import com.jfoenix.controls.JFXDialogLayout;
import com.jfoenix.controls.JFXSpinner;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import org.glavo.url.WebURL;
import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
@@ -34,8 +36,6 @@ import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Node;
import java.net.URL;
import static org.jackhuang.hmcl.Metadata.CHANGELOG_URL;
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
@@ -59,7 +59,7 @@ public final class UpgradeDialog extends JFXDialogLayout {
// Downgrade update, no need to display changelog
return null;
Document document = Jsoup.parse(new URL(url), 30 * 1000);
Document document = Jsoup.parse(WebURL.toURL(url), 30 * 1000);
Node node = document.selectFirst("h1[data-version=\"%s\"]".formatted(targetVersion));
if (node == null || !"h1".equals(node.nodeName())) {
@@ -37,7 +37,7 @@ public final class AnimationUtils {
public static void init() {
}
private static final boolean ENABLED = !settings().animationDisabledProperty().get();
private static final boolean ENABLED = !settings().isAnimationDisabled();
private static final boolean PLAY_WINDOW_ANIMATION = ENABLED && !OperatingSystem.CURRENT_OS.isLinuxOrBSD();
public static boolean isAnimationEnabled() {
@@ -85,8 +85,8 @@ public final class RadioChoiceList<T extends @UnknownNullability Object> extends
group.selectedToggleProperty().addListener((observable, oldValue, newValue) -> {
Choice<T> choice = newValue != null ? choiceByToggle.get(newValue) : null;
selectedChoice.set(choice);
selectedValue.set(choice != null ? choice.getValue() : null);
selectedChoice.set(choice);
});
selectedValue.addListener((observable, oldValue, newValue) -> {
@@ -21,11 +21,12 @@ import com.jfoenix.controls.JFXButton;
import javafx.application.Platform;
import javafx.beans.property.StringProperty;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.task.*;
import javafx.scene.layout.*;
import org.jackhuang.hmcl.task.FetchTask;
import org.jackhuang.hmcl.task.TaskExecutor;
import org.jackhuang.hmcl.task.TaskListener;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.util.TaskCancellationAction;
import org.jackhuang.hmcl.util.i18n.I18n;
@@ -67,16 +68,18 @@ public class TaskExecutorDialogPane extends BorderPane {
center.getChildren().setAll(lblTitle, taskListPane);
}
BorderPane bottom = new BorderPane();
this.setBottom(bottom);
HBox bottom = new HBox();
bottom.setAlignment(Pos.CENTER_LEFT);
bottom.setPadding(new Insets(0, 8, 8, 8));
bottom.setSpacing(8);
this.setBottom(bottom);
{
lblProgress = new Label();
bottom.setLeft(lblProgress);
Region spacer = new Region();
HBox.setHgrow(spacer, Priority.ALWAYS);
btnCancel = new JFXButton(i18n("button.cancel"));
btnCancel.getStyleClass().add("dialog-cancel");
bottom.setRight(btnCancel);
bottom.getChildren().setAll(lblProgress, spacer, btnCancel);
}
setCancel(cancel);
@@ -1171,7 +1171,8 @@ public class PersonalizationPage extends StackPane {
LineToggleButton animationButton = new LineToggleButton();
appearanceList.getContent().add(animationButton);
animationButton.selectedProperty().bindBidirectional(settings().animationDisabledProperty());
animationButton.setSelected(settings().isAnimationDisabled());
FXUtils.onChange(animationButton.selectedProperty(), value -> settings().animationDisabledProperty().set(value));
animationButton.setTitle(i18n("settings.launcher.turn_off_animations"));
animationButton.setSubtitle(i18n("settings.take_effect_after_restart"));
@@ -29,7 +29,7 @@ import java.nio.file.Path;
/// @author Glavo
public enum NBTFileType {
COMPRESSED("dat", "dat_old") {
COMPRESSED("nbt", "dat", "dat_old") {
@Override
public Tag read(Path file) throws IOException {
return NBTCodec.of().readTag(file);
@@ -379,11 +379,14 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
ModInfoDialog(ModInfoObject modInfo) {
HBox titleContainer = new HBox();
titleContainer.setSpacing(8);
titleContainer.setPadding(new Insets(0, 0, 12, 0));
Stage stage = Controllers.getStage();
prefWidthProperty().bind(stage.widthProperty().multiply(0.7));
maxWidthProperty().bind(stage.widthProperty().multiply(0.7));
var imageContainer = new ImageContainer(40);
titleContainer.setAlignment(Pos.CENTER_LEFT);
modInfo.loadIcon(imageContainer, null);
TwoLineListItem title = new TwoLineListItem();
@@ -392,13 +395,13 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
else
title.setTitle(modInfo.getModInfo().getName());
StringJoiner subtitle = new StringJoiner(" | ");
subtitle.add(FileUtils.getName(modInfo.getModInfo().getFile()));
StringJoiner subtitle = new StringJoiner("\n");
subtitle.add(i18n("archive.file.name") + ": " + FileUtils.getName(modInfo.getModInfo().getFile()));
if (StringUtils.isNotBlank(modInfo.getModInfo().getGameVersion())) {
subtitle.add(modInfo.getModInfo().getGameVersion());
subtitle.add(i18n("mods.game.version") + ": " + modInfo.getModInfo().getGameVersion());
}
if (StringUtils.isNotBlank(modInfo.getModInfo().getVersion())) {
subtitle.add(modInfo.getModInfo().getVersion());
subtitle.add(i18n("archive.version") + ": " + modInfo.getModInfo().getVersion());
}
if (StringUtils.isNotBlank(modInfo.getModInfo().getAuthors())) {
subtitle.add(i18n("archive.author") + ": " + modInfo.getModInfo().getAuthors());
@@ -902,6 +902,17 @@
}
}
},
"org.lwjgl:lwjgl-sdl:3.4.1:natives-linux": {
"name": "org.lwjgl:lwjgl-sdl:3.4.1:natives-linux-arm64",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-sdl/3.4.1/lwjgl-sdl-3.4.1-natives-linux-arm64.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-sdl/3.4.1/lwjgl-sdl-3.4.1-natives-linux-arm64.jar",
"sha1": "b5a62eb82113b0227077fb270128487a80a20299",
"size": 1434288
}
}
},
"org.lwjgl.lwjgl:lwjgl-platform:2.9.0:natives": {
"name": "org.glavo.hmcl:lwjgl2-natives:2.9.3",
"downloads": {
@@ -2637,6 +2648,7 @@
"org.lwjgl:lwjgl-shaderc:3.4.1:natives-linux": null,
"org.lwjgl:lwjgl-spvc:3.4.1:natives-linux": null,
"org.lwjgl:lwjgl-vma:3.4.1:natives-linux": null,
"org.lwjgl:lwjgl-sdl:3.4.1:natives-linux": null,
"net.java.dev.jna:jna:5.8.0": {
"name": "net.java.dev.jna:jna:5.13.0",
"downloads": {
@@ -4384,6 +4396,28 @@
}
}
},
"org.lwjgl:lwjgl-sdl:3.4.1": {
"name": "org.lwjgl:lwjgl-sdl:3.4.1",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-sdl/3.4.1/lwjgl-sdl-3.4.1.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-sdl/3.4.1/lwjgl-sdl-3.4.1.jar",
"sha1": "ec28db6e949478044c649fea25eae43b1b161971",
"size": 983498
}
}
},
"org.lwjgl:lwjgl-sdl:3.4.1:natives-linux": {
"name": "org.lwjgl:lwjgl-sdl:3.4.1:natives-linux-riscv64",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-sdl/3.4.1/lwjgl-sdl-3.4.1-natives-linux-riscv64.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-sdl/3.4.1/lwjgl-sdl-3.4.1-natives-linux-riscv64.jar",
"sha1": "d443cd6e73467f437d0be30a3ece7b6fe162a10a",
"size": 1207531
}
}
},
"com.github.oshi:oshi-core:6.6.5": {
"name": "com.github.oshi:oshi-core:6.8.0",
"downloads": {
@@ -6427,6 +6461,28 @@
}
}
},
"org.lwjgl:lwjgl-sdl:3.4.1": {
"name": "org.lwjgl:lwjgl-sdl:3.4.1",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-sdl/3.4.1/lwjgl-sdl-3.4.1.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-sdl/3.4.1/lwjgl-sdl-3.4.1.jar",
"sha1": "ec28db6e949478044c649fea25eae43b1b161971",
"size": 983498
}
}
},
"org.lwjgl:lwjgl-sdl:3.4.1:natives-linux": {
"name": "org.lwjgl:lwjgl-sdl:3.4.1:natives-freebsd",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-sdl/3.4.1/lwjgl-sdl-3.4.1-natives-freebsd.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-sdl/3.4.1/lwjgl-sdl-3.4.1-natives-freebsd.jar",
"sha1": "1616d4bd1f5ae03faa9c815c728f74d186526eee",
"size": 1241210
}
}
},
"net.java.jinput:jinput-platform:2.0.5:natives": null,
"com.mojang:text2speech:1.10.3:natives": null,
"com.mojang:text2speech:1.11.3:natives": null,
@@ -1,82 +1,119 @@
{
"$schema" : "https://schemas.glavo.site/hmcl/theme-pack/1.0.0",
"id" : "hmcl.classic",
"version" : "1.0.0",
"name" : {
"default" : "Classic",
"zh-Hans" : "经典主题",
"zh-Hant" : "經典主題"
"$schema": "https://schemas.glavo.site/hmcl/theme-pack/1.0.0",
"id": "hmcl.classic",
"version": "1.0.0",
"name": {
"default": "Classic",
"es": "Clásico",
"ja": "クラシック",
"ru": "Классическая",
"uk": "Класична",
"zh-Hans": "经典主题",
"zh-Hant": "經典主題",
"lzh": "舊觀"
},
"description" : {
"default" : "Classic HMCL launcher themes",
"zh-Hans" : "HMCL 启动器经典主题",
"zh-Hant" : "HMCL 啟動器經典主題"
"description": {
"default": "Classic HMCL launcher themes",
"es": "Temas clásicos del launcher HMCL",
"ja": "HMCL ランチャーのクラシックテーマ",
"ru": "Классические темы лаунчера HMCL",
"uk": "Класичні теми лаунчера HMCL",
"zh-Hans": "HMCL 启动器经典主题",
"zh-Hant": "HMCL 啟動器經典主題",
"lzh": "HMCL 啟動器之舊觀"
},
"authors" : [
"authors": [
{
"name" : "HMCL"
"name": {
"default": "HMCL",
"en-Qabs": "ꞀƆWH"
}
}
],
"themes" : [
"themes": [
{
"id" : "2021-08-26",
"name" : "HMCL 3.15",
"description" : {
"default" : "HMCL 3.15 classic appearance",
"zh-Hans" : "HMCL 3.15 经典外观",
"zh-Hant" : "HMCL 3.15 經典外觀"
"id": "2021-08-26",
"name": {
"default": "Current Theme",
"es": "Tema actual",
"ja": "現在のテーマ",
"ru": "Текущая тема",
"uk": "Поточна тема",
"zh-Hans": "当前主题",
"zh-Hant": "當前主題",
"lzh": "今觀"
},
"color" : "#5C6BC0",
"brightness" : "light",
"colorStyle" : "fidelity",
"background" : {
"type" : "builtin",
"id" : "2021-08-26",
"opacity" : 1.0
"description": {
"default": "HMCL current theme",
"es": "Tema actual de HMCL",
"ja": "HMCL の現在のテーマ",
"ru": "Текущая тема HMCL",
"uk": "Поточна тема HMCL",
"zh-Hans": "HMCL 当前主题",
"zh-Hant": "HMCL 當前主題",
"lzh": "HMCL 之今觀"
},
"titleBar" : {
"transparent" : false
"color": "#5C6BC0",
"brightness": "light",
"colorStyle": "fidelity",
"background": {
"type": "builtin",
"id": "2021-08-26",
"opacity": 1.0
},
"titleBar": {
"transparent": false
}
},
{
"id" : "2016-02-25",
"name" : "HMCL 3.3",
"description" : {
"default" : "HMCL 3.3 classic appearance",
"zh-Hans" : "HMCL 3.3 经典外观",
"zh-Hant" : "HMCL 3.3 經典外觀"
"id": "2016-02-25",
"name": "HMCL 3.3",
"description": {
"default": "HMCL 3.3 classic appearance",
"es": "Apariencia clásica de HMCL 3.3",
"ja": "HMCL 3.3 のクラシックな外観",
"ru": "Классический вид HMCL 3.3",
"uk": "Класичний вигляд HMCL 3.3",
"zh-Hans": "HMCL 3.3 经典外观",
"zh-Hant": "HMCL 3.3 經典外觀",
"lzh": "HMCL 3.3 之舊觀"
},
"color" : "#5C6BC0",
"brightness" : "light",
"colorStyle" : "fidelity",
"background" : {
"type" : "builtin",
"id" : "2016-02-25",
"opacity" : 1.0
"color": "#5C6BC0",
"brightness": "light",
"colorStyle": "fidelity",
"background": {
"type": "builtin",
"id": "2016-02-25",
"opacity": 1.0
},
"titleBar" : {
"transparent" : true
"titleBar": {
"transparent": true
}
},
{
"id" : "2015-06-22",
"name" : {
"default" : "HMCL 2.3"
"id": "2015-06-22",
"name": {
"default": "HMCL 2.3"
},
"description" : {
"default" : "HMCL 2.3 classic appearance",
"zh-Hans" : "HMCL 2.3 经典外观",
"zh-Hant" : "HMCL 2.3 經典外觀"
"description": {
"default": "HMCL 2.3 classic appearance",
"es": "Apariencia clásica de HMCL 2.3",
"ja": "HMCL 2.3 のクラシックな外観",
"ru": "Классический вид HMCL 2.3",
"uk": "Класичний вигляд HMCL 2.3",
"zh-Hans": "HMCL 2.3 经典外观",
"zh-Hant": "HMCL 2.3 經典外觀",
"lzh": "HMCL 2.3 之舊觀"
},
"color" : "#E67E22",
"brightness" : "light",
"colorStyle" : "fidelity",
"background" : {
"type" : "builtin",
"id" : "2015-06-22"
"color": "#E67E22",
"brightness": "light",
"colorStyle": "fidelity",
"background": {
"type": "builtin",
"id": "2015-06-22"
},
"titleBar" : {
"transparent" : false
"titleBar": {
"transparent": false
}
}
]
@@ -4,17 +4,31 @@
"version": "1.0.0",
"name": {
"default": "Default",
"en-Qabs": "ʇꞁnɐɟǝᗡ",
"es": "Predeterminado",
"ja": "デフォルト",
"ru": "По умолчанию",
"uk": "За замовчуванням",
"zh-Hans": "默认主题",
"zh-Hant": "預設主題"
"zh-Hant": "預設主題",
"lzh": "預設主題"
},
"description": {
"default": "HMCL Default",
"es": "HMCL Predeterminado",
"ja": "HMCL デフォルト",
"ru": "HMCL По умолчанию",
"uk": "HMCL За замовчуванням",
"zh-Hans": "HMCL 默认主题",
"zh-Hant": "HMCL 預設主題"
"zh-Hant": "HMCL 預設主題",
"lzh": "HMCL 預設主題"
},
"authors": [
{
"name": "HMCL"
"name": {
"default": "HMCL",
"en-Qabs": "ꞀƆWH"
}
}
],
"theme": {}
+1 -1
View File
@@ -15,4 +15,4 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
versionRoot=3.16
versionRoot=3.17