Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b74b53fbf9 | ||
|
|
22d982a089 | ||
|
|
07369a3aba | ||
|
|
e69464105a | ||
|
|
38d764c7d1 | ||
|
|
69b415e5ef | ||
|
|
64c63b2af0 | ||
|
|
3f67b6bda0 | ||
|
|
4cd783d6bb | ||
|
|
4118030d9c | ||
|
|
bcc5cda5c0 | ||
|
|
1147892830 | ||
|
|
a4fff896ea | ||
|
|
5e2da98061 |
No files matched your search
@@ -1,10 +1,11 @@
|
||||
name: Check Codes Comment
|
||||
|
||||
# Disabled on this Gitea fork: the upstream flow depends on reviewdog
|
||||
# (missing from git.nest.potatolab.uk) and a checkstyle-artifact produced by
|
||||
# check-codes.yml. Re-enable only after those actions are mirrored or the
|
||||
# comment path is rewritten without reviewdog.
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["Check Codes"]
|
||||
types:
|
||||
- completed
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
@@ -14,27 +15,7 @@ permissions:
|
||||
jobs:
|
||||
comment:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'failure'
|
||||
if: false
|
||||
steps:
|
||||
- name: Checkout Script
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
sparse-checkout: |
|
||||
.github/scripts/check-codes-comment.js
|
||||
sparse-checkout-cone-mode: false
|
||||
- name: Download Artifact
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: checkstyle-artifact
|
||||
path: ${{ runner.temp }}/checkstyle-artifact
|
||||
run-id: ${{ github.event.workflow_run.id }}
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Generate Review Comment
|
||||
uses: actions/github-script@v9
|
||||
env:
|
||||
REPORTS_PATH: ${{ runner.temp }}/checkstyle-artifact/ALL_REPORTS.jsonl
|
||||
PULL_REQUEST_NUMBER_PATH: ${{ runner.temp }}/checkstyle-artifact/PULL_REQUEST_NUMBER
|
||||
with:
|
||||
script: |
|
||||
const script = require("./.github/scripts/check-codes-comment.js");
|
||||
await script({ github, context, core });
|
||||
- name: Disabled
|
||||
run: echo "check-codes-comment is disabled on this fork"
|
||||
@@ -1,14 +1,17 @@
|
||||
name: Check Codes
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- '**.java'
|
||||
- '**.properties'
|
||||
- '.github/workflows/check-codes.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- '**.java'
|
||||
- '**.properties'
|
||||
- '.github/workflows/check-codes.yml'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -25,31 +28,8 @@ jobs:
|
||||
uses: gradle/actions/setup-gradle@v5
|
||||
with:
|
||||
cache-cleanup: never
|
||||
# Keep only Gradle checks. reviewdog/action-setup is not mirrored on
|
||||
# git.nest.potatolab.uk (404), so PR-comment artifact steps are omitted
|
||||
# for Gitea Actions / act runners.
|
||||
- name: Check Codes
|
||||
run: ./gradlew checkstyle checkTranslations --no-daemon --parallel --stacktrace
|
||||
- if: failure() && github.event_name == 'pull_request'
|
||||
name: Setup reviewdog
|
||||
uses: reviewdog/action-setup@d8edfce3dd5e1ec6978745e801f9c50b5ef80252 # v1.4.0
|
||||
with:
|
||||
reviewdog_version: v0.21.0
|
||||
- if: failure() && github.event_name == 'pull_request'
|
||||
name: Generate Artifact
|
||||
run: |
|
||||
echo "${{ github.event.number }}" > PULL_REQUEST_NUMBER
|
||||
> ALL_REPORTS.jsonl
|
||||
find . -path "*/build/reports/checkstyle/*.xml" | while read -r xml_file; do
|
||||
if [ -s "$xml_file" ]; then
|
||||
echo "Processing $xml_file..."
|
||||
reviewdog -f=checkstyle -name="checkstyle" -reporter=rdjsonl < "$xml_file" >> ALL_REPORTS.jsonl
|
||||
else
|
||||
echo "Skipping empty or missing report: $xml_file"
|
||||
fi
|
||||
done
|
||||
- if: failure() && github.event_name == 'pull_request'
|
||||
name: Upload Artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: checkstyle-artifact
|
||||
path: |
|
||||
ALL_REPORTS.jsonl
|
||||
PULL_REQUEST_NUMBER
|
||||
@@ -23,6 +23,10 @@ jobs:
|
||||
uses: gradle/actions/setup-gradle@v5
|
||||
with:
|
||||
cache-cleanup: never
|
||||
- name: Install Xvfb
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y xvfb
|
||||
- name: Build with Gradle
|
||||
run: xvfb-run --auto-servernum ./gradlew build --no-daemon --parallel
|
||||
env:
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
name: PR Size Label
|
||||
|
||||
# Disabled on this Gitea fork: act resolves actions/github-script from
|
||||
# git.nest.potatolab.uk and that mirror 404s. Re-enable after mirroring
|
||||
# actions/github-script or replacing the labeler with a plain shell step.
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- synchronize
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
@@ -16,16 +14,7 @@ permissions:
|
||||
jobs:
|
||||
label:
|
||||
runs-on: ubuntu-latest
|
||||
if: false
|
||||
steps:
|
||||
- name: Checkout scripts
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
sparse-checkout: |
|
||||
.github/scripts/pr-size-label.js
|
||||
sparse-checkout-cone-mode: false
|
||||
- name: Label PR by filtered changed lines
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
const script = require("./.github/scripts/pr-size-label.js");
|
||||
await script({ github, context, core });
|
||||
- name: Disabled
|
||||
run: echo "pr-size-label is disabled on this fork"
|
||||
@@ -23,6 +23,10 @@ import javafx.scene.control.Label;
|
||||
import javafx.scene.effect.DropShadow;
|
||||
import javafx.scene.layout.Background;
|
||||
import javafx.scene.layout.BackgroundFill;
|
||||
import javafx.scene.layout.Border;
|
||||
import javafx.scene.layout.BorderStroke;
|
||||
import javafx.scene.layout.BorderStrokeStyle;
|
||||
import javafx.scene.layout.BorderWidths;
|
||||
import javafx.scene.layout.CornerRadii;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Color;
|
||||
@@ -30,8 +34,20 @@ import javafx.scene.shape.Shape;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
|
||||
/// JFoenix button skin with Anthropic skill tactile press feedback.
|
||||
///
|
||||
/// Upstream skin rewrote Modena's cyan `:pressed` fill to transparent and thereby
|
||||
/// also ate author CSS press backgrounds. This build keeps CSS fills, maps the
|
||||
/// Modena cyan to a skill wash, and paints an unmistakable armed overlay
|
||||
/// (background + border + opacity) on every JFXButton.
|
||||
public class JFXButtonSkin extends ButtonSkin {
|
||||
private static final Color SKILL_PRESS_FILL = Color.web("#D97757", 0.40);
|
||||
private static final Color SKILL_PRESS_BORDER = Color.web("#C96442");
|
||||
private static final Color SKILL_PRESS_DIM = Color.web("#141413", 0.28);
|
||||
|
||||
private final StackPane buttonContainer = new StackPane();
|
||||
/// Full-bleed press veil — opacity toggled from armed/pressed, independent of CSS.
|
||||
private final StackPane pressOverlay = new StackPane();
|
||||
private final JFXRippler buttonRippler = new JFXRippler(new StackPane()) {
|
||||
protected Node getMask() {
|
||||
StackPane mask = new StackPane();
|
||||
@@ -64,6 +80,17 @@ public class JFXButtonSkin extends ButtonSkin {
|
||||
|
||||
public JFXButtonSkin(JFXButton button) {
|
||||
super(button);
|
||||
|
||||
this.pressOverlay.setMouseTransparent(true);
|
||||
this.pressOverlay.setOpacity(0);
|
||||
this.pressOverlay.setBackground(new Background(new BackgroundFill(
|
||||
SKILL_PRESS_FILL, this.defaultRadii, Insets.EMPTY)));
|
||||
this.pressOverlay.setBorder(new Border(new BorderStroke(
|
||||
SKILL_PRESS_BORDER,
|
||||
BorderStrokeStyle.SOLID,
|
||||
this.defaultRadii,
|
||||
new BorderWidths(2.0))));
|
||||
|
||||
this.getSkinnable().armedProperty().addListener((o, oldVal, newVal) -> {
|
||||
if (newVal) {
|
||||
this.releaseManualRippler = this.buttonRippler.createManualRipple();
|
||||
@@ -81,21 +108,23 @@ public class JFXButtonSkin extends ButtonSkin {
|
||||
this.clickedAnimation.play();
|
||||
}
|
||||
}
|
||||
|
||||
this.updatePressOverlay(button);
|
||||
});
|
||||
this.buttonContainer.getChildren().add(this.buttonRippler);
|
||||
this.buttonContainer.getChildren().addAll(this.buttonRippler, this.pressOverlay);
|
||||
button.buttonTypeProperty().addListener((o, oldVal, newVal) -> this.updateButtonType(newVal));
|
||||
button.setOnMousePressed((e) -> {
|
||||
if (this.clickedAnimation != null) {
|
||||
this.clickedAnimation.setRate(1.0F);
|
||||
this.clickedAnimation.play();
|
||||
}
|
||||
this.updatePressOverlay(button);
|
||||
});
|
||||
button.setOnMouseReleased((e) -> {
|
||||
if (this.clickedAnimation != null) {
|
||||
this.clickedAnimation.setRate(-1.0F);
|
||||
this.clickedAnimation.play();
|
||||
}
|
||||
this.updatePressOverlay(button);
|
||||
});
|
||||
|
||||
ReadOnlyBooleanProperty focusVisibleProperty = FXUtils.focusVisibleProperty(button);
|
||||
@@ -111,20 +140,34 @@ public class JFXButtonSkin extends ButtonSkin {
|
||||
this.buttonRippler.hideOverlay();
|
||||
}
|
||||
});
|
||||
button.pressedProperty().addListener((o, oldVal, newVal) -> this.buttonRippler.hideOverlay());
|
||||
button.pressedProperty().addListener((o, oldVal, newVal) -> {
|
||||
this.buttonRippler.hideOverlay();
|
||||
this.updatePressOverlay(button);
|
||||
});
|
||||
button.setPickOnBounds(false);
|
||||
this.buttonContainer.setPickOnBounds(false);
|
||||
this.buttonContainer.shapeProperty().bind(this.getSkinnable().shapeProperty());
|
||||
this.buttonContainer.borderProperty().bind(this.getSkinnable().borderProperty());
|
||||
// Mirror CSS background onto the container. Never call button.setBackground() here —
|
||||
// that used to stomp author :pressed fills (and wiped Modena cyan to transparent).
|
||||
this.buttonContainer.backgroundProperty().bind(Bindings.createObjectBinding(() -> {
|
||||
if (button.getBackground() == null || this.isJavaDefaultBackground(button.getBackground()) || this.isJavaDefaultClickedBackground(button.getBackground())) {
|
||||
button.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, this.defaultRadii, null)));
|
||||
Background background = button.getBackground();
|
||||
if (background == null || background.getFills().isEmpty() || this.isJavaDefaultBackground(background)) {
|
||||
return new Background(new BackgroundFill(Color.TRANSPARENT, this.defaultRadii, Insets.EMPTY));
|
||||
}
|
||||
if (this.isJavaDefaultClickedBackground(background)) {
|
||||
CornerRadii radii = background.getFills().get(0).getRadii();
|
||||
return new Background(new BackgroundFill(SKILL_PRESS_FILL,
|
||||
radii != null ? radii : this.defaultRadii, Insets.EMPTY));
|
||||
}
|
||||
|
||||
try {
|
||||
return new Background(new BackgroundFill(this.getSkinnable().getBackground() != null ? this.getSkinnable().getBackground().getFills().get(0).getFill() : Color.TRANSPARENT, this.getSkinnable().getBackground() != null ? this.getSkinnable().getBackground().getFills().get(0).getRadii() : this.defaultRadii, Insets.EMPTY));
|
||||
BackgroundFill fill = background.getFills().get(0);
|
||||
return new Background(new BackgroundFill(
|
||||
fill.getFill(),
|
||||
fill.getRadii() != null ? fill.getRadii() : this.defaultRadii,
|
||||
Insets.EMPTY));
|
||||
} catch (Exception var3) {
|
||||
return this.getSkinnable().getBackground();
|
||||
return background;
|
||||
}
|
||||
}, this.getSkinnable().backgroundProperty()));
|
||||
button.ripplerFillProperty().addListener((o, oldVal, newVal) -> this.buttonRippler.setRipplerFill(newVal));
|
||||
@@ -134,6 +177,40 @@ public class JFXButtonSkin extends ButtonSkin {
|
||||
|
||||
this.updateButtonType(button.getButtonType());
|
||||
this.updateChildren();
|
||||
this.updatePressOverlay(button);
|
||||
}
|
||||
|
||||
/// Shows a hard-to-miss press veil: orange wash + warm border; dimming on raised CTAs.
|
||||
private void updatePressOverlay(JFXButton button) {
|
||||
boolean active = !button.isDisabled() && (button.isArmed() || button.isPressed());
|
||||
boolean raised = button.getButtonType() == JFXButton.ButtonType.RAISED
|
||||
|| button.getStyleClass().contains("jfx-button-raised")
|
||||
|| button.getStyleClass().contains("launch-button")
|
||||
|| button.getStyleClass().contains("menu-button");
|
||||
|
||||
CornerRadii radii = this.defaultRadii;
|
||||
Background bg = button.getBackground();
|
||||
if (bg != null && !bg.getFills().isEmpty() && bg.getFills().get(0).getRadii() != null) {
|
||||
radii = bg.getFills().get(0).getRadii();
|
||||
}
|
||||
|
||||
Color fill = raised ? SKILL_PRESS_DIM : SKILL_PRESS_FILL;
|
||||
this.pressOverlay.setBackground(new Background(new BackgroundFill(fill, radii, Insets.EMPTY)));
|
||||
this.pressOverlay.setBorder(new Border(new BorderStroke(
|
||||
SKILL_PRESS_BORDER,
|
||||
BorderStrokeStyle.SOLID,
|
||||
radii,
|
||||
new BorderWidths(active ? 2.0 : 0.0))));
|
||||
this.pressOverlay.setOpacity(active ? 1.0 : 0.0);
|
||||
// Drive opacity via style class so CSS keeps ownership (no setOpacity stomping).
|
||||
final String armedClass = "anthropic-armed";
|
||||
if (active) {
|
||||
if (!button.getStyleClass().contains(armedClass)) {
|
||||
button.getStyleClass().add(armedClass);
|
||||
}
|
||||
} else {
|
||||
button.getStyleClass().remove(armedClass);
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateChildren() {
|
||||
@@ -186,6 +263,7 @@ public class JFXButtonSkin extends ButtonSkin {
|
||||
|
||||
private boolean isJavaDefaultClickedBackground(Background background) {
|
||||
try {
|
||||
// Modena default armed cyan — replace with skill wash instead of wiping to transparent.
|
||||
return "0x039ed3ff".equals(background.getFills().get(0).getFill().toString());
|
||||
} catch (Exception var3) {
|
||||
return false;
|
||||
|
||||
@@ -19,8 +19,8 @@ public final class Metadata {
|
||||
private Metadata() {
|
||||
}
|
||||
|
||||
public static final String NAME = "HMCL";
|
||||
public static final String FULL_NAME = "Hello Minecraft! Launcher";
|
||||
public static final String NAME = "TDLM";
|
||||
public static final String FULL_NAME = "TDLM";
|
||||
public static final String VERSION = System.getProperty("hmcl.version.override", JarUtils.getAttribute("hmcl.version", "@develop@"));
|
||||
|
||||
/// Explicit Application User Model ID used for Windows taskbar grouping and pinning.
|
||||
|
||||
@@ -455,9 +455,9 @@ public class HMCLGameInstance extends DefaultGameInstance {
|
||||
return GameInstanceIconType.OPTIFINE.getIcon();
|
||||
|
||||
GameVersionNumber version = getVersion();
|
||||
if (version.isAprilFools())
|
||||
return GameInstanceIconType.APRIL_FOOLS.getIcon();
|
||||
else if (version instanceof GameVersionNumber.LegacySnapshot || version instanceof GameVersionNumber.Release release && release.getEaType() != GameVersionNumber.Release.ReleaseType.GA)
|
||||
if (version instanceof GameVersionNumber.LegacySnapshot
|
||||
|| version.isAprilFools()
|
||||
|| version instanceof GameVersionNumber.Release release && release.getEaType() != GameVersionNumber.Release.ReleaseType.GA)
|
||||
return GameInstanceIconType.COMMAND.getIcon();
|
||||
else if (version instanceof GameVersionNumber.Old)
|
||||
return GameInstanceIconType.CRAFT_TABLE.getIcon();
|
||||
|
||||
@@ -61,8 +61,12 @@ public final class JavaManager {
|
||||
"Semeru"
|
||||
};
|
||||
|
||||
public static final HMCLJavaRepository REPOSITORY = new HMCLJavaRepository(Metadata.HMCL_USER_HOME.resolve("java"));
|
||||
public static final HMCLJavaRepository LOCAL_REPOSITORY = new HMCLJavaRepository(Metadata.HMCL_LOCAL_HOME.resolve("java"));
|
||||
/// Managed Mojang/Disco Java installs for this workspace (beside `.minecraft`), not the user-global HMCL profile.
|
||||
public static final HMCLJavaRepository REPOSITORY =
|
||||
new HMCLJavaRepository(Metadata.CURRENT_DIRECTORY.resolve("java"));
|
||||
/// Optional portable installs under the launcher-local `.hmcl` directory.
|
||||
public static final HMCLJavaRepository LOCAL_REPOSITORY =
|
||||
new HMCLJavaRepository(Metadata.HMCL_LOCAL_HOME.resolve("java"));
|
||||
|
||||
public static String getMojangJavaPlatform(Platform platform) {
|
||||
if (platform.getOperatingSystem() == OperatingSystem.WINDOWS) {
|
||||
|
||||
@@ -55,8 +55,12 @@ public final class Accounts {
|
||||
public static final MicrosoftAccountFactory FACTORY_MICROSOFT = new MicrosoftAccountFactory(new MicrosoftService(OAUTH_CALLBACK));
|
||||
/// Account factories offered when creating a new account from the UI.
|
||||
///
|
||||
/// Offline login remains loadable for existing accounts, but is no longer offered for creation.
|
||||
public static final List<AccountFactory<?>> FACTORIES = List.of(FACTORY_MICROSOFT, FACTORY_AUTHLIB_INJECTOR);
|
||||
/// Primary: Offline and Microsoft. Authlib-injector remains available for external Yggdrasil servers.
|
||||
public static final List<AccountFactory<?>> FACTORIES = List.of(
|
||||
FACTORY_OFFLINE,
|
||||
FACTORY_MICROSOFT,
|
||||
FACTORY_AUTHLIB_INJECTOR
|
||||
);
|
||||
|
||||
// ==== login type / account factory mapping ====
|
||||
private static final Map<String, AccountFactory<?>> type2factory = new HashMap<>();
|
||||
|
||||
@@ -36,7 +36,22 @@ public final class FontManager {
|
||||
|
||||
public static final double DEFAULT_FONT_SIZE = 12.0f;
|
||||
|
||||
/// Bundled Anthropic UI title face (Source Han Sans SC Bold subset).
|
||||
public static final String TITLE_FONT_FAMILY = "Source Han Sans SC";
|
||||
|
||||
/// Bundled Anthropic UI body face (Source Han Serif SC Regular subset).
|
||||
public static final String BODY_FONT_FAMILY = "Source Han Serif SC";
|
||||
|
||||
private static final String BUNDLED_TITLE_FONT = "/assets/fonts/SourceHanSansSC-Bold.otf";
|
||||
private static final String BUNDLED_BODY_FONT = "/assets/fonts/SourceHanSerifSC-Regular.otf";
|
||||
|
||||
private static volatile boolean bundledFontsLoaded;
|
||||
private static volatile @Nullable String loadedTitleFamily;
|
||||
private static volatile @Nullable String loadedBodyFamily;
|
||||
|
||||
private static final Lazy<Font> DEFAULT_FONT = new Lazy<>(() -> {
|
||||
ensureBundledFontsLoaded();
|
||||
|
||||
Font font;
|
||||
|
||||
// Recommended
|
||||
@@ -70,6 +85,11 @@ public final class FontManager {
|
||||
return font;
|
||||
}
|
||||
|
||||
// Anthropic UI hard-bind: bundled Source Han Serif body face
|
||||
if (loadedBodyFamily != null) {
|
||||
return Font.font(loadedBodyFamily, DEFAULT_FONT_SIZE);
|
||||
}
|
||||
|
||||
// Default
|
||||
|
||||
String fcMatchPattern;
|
||||
@@ -83,8 +103,62 @@ public final class FontManager {
|
||||
private static final ObjectProperty<FontReference> font = new SimpleObjectProperty<>();
|
||||
|
||||
static {
|
||||
ensureBundledFontsLoaded();
|
||||
updateFont();
|
||||
LOG.info("Font: " + (font.get() != null ? font.get().family() : "System"));
|
||||
LOG.info("Font: " + (font.get() != null ? font.get().family() : "System")
|
||||
+ ", title=" + Objects.requireNonNullElse(loadedTitleFamily, "unavailable")
|
||||
+ ", body=" + Objects.requireNonNullElse(loadedBodyFamily, "unavailable"));
|
||||
}
|
||||
|
||||
/// Registers the bundled Source Han subsets so CSS family names resolve.
|
||||
public static void ensureBundledFontsLoaded() {
|
||||
if (bundledFontsLoaded) {
|
||||
return;
|
||||
}
|
||||
synchronized (FontManager.class) {
|
||||
if (bundledFontsLoaded) {
|
||||
return;
|
||||
}
|
||||
loadedTitleFamily = loadBundledFont(BUNDLED_TITLE_FONT, TITLE_FONT_FAMILY);
|
||||
loadedBodyFamily = loadBundledFont(BUNDLED_BODY_FONT, BODY_FONT_FAMILY);
|
||||
bundledFontsLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the resolved title font family after bundled registration, or the hard-bind name.
|
||||
public static String getTitleFontFamily() {
|
||||
ensureBundledFontsLoaded();
|
||||
return Objects.requireNonNullElse(loadedTitleFamily, TITLE_FONT_FAMILY);
|
||||
}
|
||||
|
||||
/// Returns the resolved body font family after bundled registration, or the hard-bind name.
|
||||
public static String getBodyFontFamily() {
|
||||
ensureBundledFontsLoaded();
|
||||
return Objects.requireNonNullElse(loadedBodyFamily, BODY_FONT_FAMILY);
|
||||
}
|
||||
|
||||
private static @Nullable String loadBundledFont(String resourcePath, String expectedFamily) {
|
||||
try (var input = FontManager.class.getResourceAsStream(resourcePath)) {
|
||||
if (input == null) {
|
||||
LOG.warning("Bundled font missing: " + resourcePath);
|
||||
return null;
|
||||
}
|
||||
Font loaded = Font.loadFont(input, DEFAULT_FONT_SIZE);
|
||||
if (loaded == null) {
|
||||
LOG.warning("Failed to decode bundled font: " + resourcePath);
|
||||
return null;
|
||||
}
|
||||
String family = loaded.getFamily();
|
||||
LOG.info("Loaded bundled font " + resourcePath + " as family '" + family + "'");
|
||||
if (!expectedFamily.equalsIgnoreCase(family)) {
|
||||
LOG.warning("Bundled font family mismatch for " + resourcePath
|
||||
+ ": expected '" + expectedFamily + "', got '" + family + "'");
|
||||
}
|
||||
return family;
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to load bundled font " + resourcePath, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void updateFont() {
|
||||
@@ -96,7 +170,7 @@ public final class FontManager {
|
||||
|
||||
if (fontFamily == null) {
|
||||
Font defaultFont = DEFAULT_FONT.get();
|
||||
font.set(defaultFont != null ? new FontReference(defaultFont) : null);
|
||||
font.set(defaultFont != null ? new FontReference(defaultFont) : new FontReference(BODY_FONT_FAMILY));
|
||||
} else {
|
||||
font.set(new FontReference(fontFamily));
|
||||
}
|
||||
|
||||
@@ -22,7 +22,8 @@ public enum GameInstanceIconType {
|
||||
NEO_FORGE("/assets/img/neoforge.png"),
|
||||
FURNACE("/assets/img/furnace.png"),
|
||||
QUILT("/assets/img/quilt.png"),
|
||||
APRIL_FOOLS("/assets/img/april_fools.png"),
|
||||
/// Retained for settings migration; UI no longer offers this icon.
|
||||
APRIL_FOOLS("/assets/img/command.png"),
|
||||
CLEANROOM("/assets/img/cleanroom.png"),
|
||||
LEGACY_FABRIC("/assets/img/legacyfabric.png")
|
||||
;
|
||||
|
||||
@@ -179,15 +179,6 @@ public final class LauncherSettings extends ObservableSetting implements JsonSch
|
||||
|
||||
|
||||
|
||||
/// Whether April Fools features are disabled.
|
||||
@SerializedName("disableAprilFools")
|
||||
private final BooleanProperty disableAprilFools = new SimpleBooleanProperty(false);
|
||||
|
||||
/// Returns the April Fools disable property.
|
||||
public BooleanProperty disableAprilFoolsProperty() {
|
||||
return disableAprilFools;
|
||||
}
|
||||
|
||||
/// The common Minecraft directory selection mode.
|
||||
@SerializedName("commonDirectoryType")
|
||||
private final ObjectProperty<EnumCommonDirectory> commonDirectoryType = new RawPreservingObjectProperty<>(EnumCommonDirectory.DEFAULT);
|
||||
|
||||
@@ -9,15 +9,12 @@ import javafx.beans.property.DoubleProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleDoubleProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableMap;
|
||||
import org.jackhuang.hmcl.util.gson.JsonSchema;
|
||||
import org.jackhuang.hmcl.util.gson.JsonSerializable;
|
||||
import org.jackhuang.hmcl.util.gson.ObservableSetting;
|
||||
import org.jetbrains.annotations.NotNullByDefault;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import javafx.scene.paint.Color;
|
||||
import org.glavo.monetfx.Brightness;
|
||||
import org.glavo.monetfx.ColorRole;
|
||||
import org.glavo.monetfx.ColorScheme;
|
||||
import org.jackhuang.hmcl.theme.ResolvedTheme;
|
||||
import org.jackhuang.hmcl.theme.ThemeColor;
|
||||
import org.jackhuang.hmcl.theme.Themes;
|
||||
|
||||
@@ -26,6 +25,8 @@ public final class StyleSheets {
|
||||
private static final int FONT_STYLE_SHEET_INDEX = 0;
|
||||
private static final int THEME_STYLE_SHEET_INDEX = 1;
|
||||
private static final int BRIGHTNESS_SHEET_INDEX = 2;
|
||||
/// Loaded after root.css so solid :pressed fills beat Monet / Modena cyan.
|
||||
private static final int PRESS_FEEDBACK_SHEET_INDEX = 4;
|
||||
|
||||
private static final ObservableList<String> stylesheets;
|
||||
|
||||
@@ -34,9 +35,15 @@ public final class StyleSheets {
|
||||
getFontStyleSheet(),
|
||||
getThemeStyleSheet(),
|
||||
getBrightnessStyleSheet(),
|
||||
"/assets/css/root.css"
|
||||
"/assets/css/root.css",
|
||||
"/assets/css/press-feedback.css"
|
||||
};
|
||||
stylesheets = FXCollections.observableList(Arrays.asList(array));
|
||||
// Keep the constant honest for future inserts ahead of press-feedback.
|
||||
if (stylesheets.size() <= PRESS_FEEDBACK_SHEET_INDEX
|
||||
|| !"/assets/css/press-feedback.css".equals(stylesheets.get(PRESS_FEEDBACK_SHEET_INDEX))) {
|
||||
throw new IllegalStateException("press-feedback.css must remain the last stylesheet");
|
||||
}
|
||||
|
||||
FontManager.fontProperty().addListener(o -> stylesheets.set(FONT_STYLE_SHEET_INDEX, getFontStyleSheet()));
|
||||
Themes.colorSchemeProperty().addListener(o -> {
|
||||
@@ -50,51 +57,72 @@ public final class StyleSheets {
|
||||
}
|
||||
|
||||
private static String getFontStyleSheet() {
|
||||
final String defaultCss = "/assets/css/font.css";
|
||||
FontManager.ensureBundledFontsLoaded();
|
||||
|
||||
final FontManager.FontReference font = FontManager.getFont();
|
||||
|
||||
if (font == null || "System".equals(font.family()))
|
||||
return defaultCss;
|
||||
|
||||
String fontFamily = font.family();
|
||||
String style = font.style();
|
||||
String bodyFamily = FontManager.getBodyFontFamily();
|
||||
String titleFamily = FontManager.getTitleFontFamily();
|
||||
String weight = null;
|
||||
String posture = null;
|
||||
|
||||
if (style != null) {
|
||||
style = style.toLowerCase(Locale.ROOT);
|
||||
// Explicit user / env override replaces body face only; titles stay Source Han Sans Bold.
|
||||
if (font != null && !"System".equals(font.family())
|
||||
&& !FontManager.BODY_FONT_FAMILY.equalsIgnoreCase(font.family())
|
||||
&& !FontManager.TITLE_FONT_FAMILY.equalsIgnoreCase(font.family())) {
|
||||
bodyFamily = font.family();
|
||||
String style = font.style();
|
||||
if (style != null) {
|
||||
style = style.toLowerCase(Locale.ROOT);
|
||||
|
||||
if (style.contains("thin"))
|
||||
weight = "100";
|
||||
else if (style.contains("extralight") || style.contains("extra light") || style.contains("ultralight") | style.contains("ultra light"))
|
||||
weight = "200";
|
||||
else if (style.contains("medium"))
|
||||
weight = "500";
|
||||
else if (style.contains("semibold") || style.contains("semi bold") || style.contains("demibold") || style.contains("demi bold"))
|
||||
weight = "600";
|
||||
else if (style.contains("extrabold") || style.contains("extra bold") || style.contains("ultrabold") || style.contains("ultra bold"))
|
||||
weight = "800";
|
||||
else if (style.contains("black") || style.contains("heavy"))
|
||||
weight = "900";
|
||||
else if (style.contains("light"))
|
||||
weight = "lighter";
|
||||
else if (style.contains("bold"))
|
||||
weight = "bold";
|
||||
if (style.contains("thin"))
|
||||
weight = "100";
|
||||
else if (style.contains("extralight") || style.contains("extra light") || style.contains("ultralight") | style.contains("ultra light"))
|
||||
weight = "200";
|
||||
else if (style.contains("medium"))
|
||||
weight = "500";
|
||||
else if (style.contains("semibold") || style.contains("semi bold") || style.contains("demibold") || style.contains("demi bold"))
|
||||
weight = "600";
|
||||
else if (style.contains("extrabold") || style.contains("extra bold") || style.contains("ultrabold") || style.contains("ultra bold"))
|
||||
weight = "800";
|
||||
else if (style.contains("black") || style.contains("heavy"))
|
||||
weight = "900";
|
||||
else if (style.contains("light"))
|
||||
weight = "lighter";
|
||||
else if (style.contains("bold"))
|
||||
weight = "bold";
|
||||
|
||||
posture = style.contains("italic") || style.contains("oblique") ? "italic" : null;
|
||||
posture = style.contains("italic") || style.contains("oblique") ? "italic" : null;
|
||||
}
|
||||
}
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append(".root {");
|
||||
builder.append("-fx-font-family:\"").append(fontFamily).append("\";");
|
||||
|
||||
builder.append("-fx-font-family:\"").append(bodyFamily).append("\", \"Source Han Serif\", serif;");
|
||||
if (weight != null)
|
||||
builder.append("-fx-font-weight:").append(weight).append(";");
|
||||
|
||||
if (posture != null)
|
||||
builder.append("-fx-font-style:").append(posture).append(";");
|
||||
builder.append("}\n");
|
||||
|
||||
builder.append('}');
|
||||
builder.append("""
|
||||
.jfx-decorator-title,
|
||||
.jfx-tool-bar-second .label,
|
||||
.title-label,
|
||||
.class-title,
|
||||
.class-title Text,
|
||||
.jfx-layout-heading,
|
||||
.launch-pane > .launch-button .label,
|
||||
.announcement .title,
|
||||
.method-title,
|
||||
.radio-button-title-label,
|
||||
.two-line-list-item > .title,
|
||||
.advanced-list-item > .rippler-container > .container > .title,
|
||||
.jfx-button-raised,
|
||||
.jfx-button-raised * {
|
||||
""");
|
||||
builder.append("-fx-font-family:\"").append(titleFamily).append("\", \"Source Han Sans\", \"思源黑体\", sans-serif;");
|
||||
builder.append("-fx-font-weight:bold;");
|
||||
builder.append("}\n");
|
||||
|
||||
return toStyleSheetUri(builder.toString());
|
||||
}
|
||||
@@ -121,13 +149,133 @@ public final class StyleSheets {
|
||||
.append(";\n");
|
||||
}
|
||||
|
||||
private static void putHex(StringBuilder builder, String name, String hex) {
|
||||
builder.append(" ").append(name).append(": ").append(hex).append(";\n");
|
||||
}
|
||||
|
||||
/// Maps anthropic-style-cn skill base.css light/dark pairs onto Monet roles so existing CSS flips end-to-end.
|
||||
private static void applySkillTokenOverrides(StringBuilder builder, boolean dark) {
|
||||
// Skill accent (CTA / focus) — never Material blue.
|
||||
final String accent = "#D97757";
|
||||
final String accentWarm = "#C96442";
|
||||
final String error = "#C0453A";
|
||||
final String onAccent = "#FAF9F5";
|
||||
|
||||
if (dark) {
|
||||
putHex(builder, "-monet-surface", "#1A1916");
|
||||
putHex(builder, "-monet-background", "#1A1916");
|
||||
putHex(builder, "-monet-surface-bright", "#2C2B26");
|
||||
putHex(builder, "-monet-surface-dim", "#141413");
|
||||
putHex(builder, "-monet-surface-container-lowest", "#141413");
|
||||
putHex(builder, "-monet-surface-container-low", "#222119");
|
||||
putHex(builder, "-monet-surface-container", "#222119");
|
||||
putHex(builder, "-monet-surface-container-high", "#2C2B26");
|
||||
putHex(builder, "-monet-surface-container-highest", "#2C2B26");
|
||||
putHex(builder, "-monet-surface-variant", "#2C2B26");
|
||||
putHex(builder, "-monet-on-surface", "#EAE7DC");
|
||||
putHex(builder, "-monet-on-background", "#EAE7DC");
|
||||
putHex(builder, "-monet-on-surface-variant", "#9D9A91");
|
||||
putHex(builder, "-monet-outline", "#3A3830");
|
||||
putHex(builder, "-monet-outline-variant", "#2E2D27");
|
||||
putHex(builder, "-monet-inverse-surface", "#F5F3EC");
|
||||
putHex(builder, "-monet-inverse-on-surface", "#141413");
|
||||
putHex(builder, "-monet-secondary-container", "#2C2B26");
|
||||
putHex(builder, "-monet-on-secondary-container", "#EAE7DC");
|
||||
putHex(builder, "-anthropic-shadow", "rgba(0, 0, 0, 0.50)");
|
||||
putHex(builder, "-anthropic-shadow-strong", "rgba(0, 0, 0, 0.65)");
|
||||
putHex(builder, "-anthropic-scrollbar-track", "#222119");
|
||||
putHex(builder, "-anthropic-scrollbar-thumb", "#5C5A54");
|
||||
putHex(builder, "-anthropic-hover-wash", "rgba(234, 231, 220, 0.08)");
|
||||
putHex(builder, "-anthropic-press-wash", "rgba(234, 231, 220, 0.14)");
|
||||
} else {
|
||||
putHex(builder, "-monet-surface", "#ECE9E0");
|
||||
putHex(builder, "-monet-background", "#ECE9E0");
|
||||
putHex(builder, "-monet-surface-bright", "#FDFCF8");
|
||||
putHex(builder, "-monet-surface-dim", "#D8D5CC");
|
||||
putHex(builder, "-monet-surface-container-lowest", "#FDFCF8");
|
||||
putHex(builder, "-monet-surface-container-low", "#F5F3EC");
|
||||
putHex(builder, "-monet-surface-container", "#F5F3EC");
|
||||
putHex(builder, "-monet-surface-container-high", "#FDFCF8");
|
||||
putHex(builder, "-monet-surface-container-highest", "#FDFCF8");
|
||||
putHex(builder, "-monet-surface-variant", "#E8E6DC");
|
||||
putHex(builder, "-monet-on-surface", "#141413");
|
||||
putHex(builder, "-monet-on-background", "#141413");
|
||||
putHex(builder, "-monet-on-surface-variant", "#6B6860");
|
||||
putHex(builder, "-monet-outline", "#D8D5CC");
|
||||
putHex(builder, "-monet-outline-variant", "#E8E6DC");
|
||||
putHex(builder, "-monet-inverse-surface", "#141413");
|
||||
putHex(builder, "-monet-inverse-on-surface", "#FAF9F5");
|
||||
putHex(builder, "-monet-secondary-container", "#E8E6DC");
|
||||
putHex(builder, "-monet-on-secondary-container", "#141413");
|
||||
putHex(builder, "-anthropic-shadow", "rgba(20, 20, 19, 0.10)");
|
||||
putHex(builder, "-anthropic-shadow-strong", "rgba(20, 20, 19, 0.16)");
|
||||
putHex(builder, "-anthropic-scrollbar-track", "#E8E6DC");
|
||||
putHex(builder, "-anthropic-scrollbar-thumb", "#9B9890");
|
||||
putHex(builder, "-anthropic-hover-wash", "rgba(20, 20, 19, 0.06)");
|
||||
putHex(builder, "-anthropic-press-wash", "rgba(20, 20, 19, 0.12)");
|
||||
}
|
||||
|
||||
putHex(builder, "-monet-primary", accent);
|
||||
putHex(builder, "-monet-primary-seed", accent);
|
||||
putHex(builder, "-monet-on-primary", onAccent);
|
||||
putHex(builder, "-monet-primary-container", accentWarm);
|
||||
putHex(builder, "-monet-on-primary-container", onAccent);
|
||||
putHex(builder, "-monet-primary-fixed", dark ? "#4A2E24" : "#F6E0D6");
|
||||
putHex(builder, "-monet-primary-fixed-dim", dark ? "#C96442" : accent);
|
||||
putHex(builder, "-monet-on-primary-fixed", dark ? "#FAF9F5" : "#141413");
|
||||
putHex(builder, "-monet-on-primary-fixed-variant", dark ? "#EAE7DC" : "#6B6860");
|
||||
putHex(builder, "-monet-inverse-primary", accent);
|
||||
putHex(builder, "-monet-surface-tint", accent);
|
||||
|
||||
// Secondary / tertiary: warm orange family only — kill Monet teal/purple companions.
|
||||
putHex(builder, "-monet-secondary", accentWarm);
|
||||
putHex(builder, "-monet-on-secondary", onAccent);
|
||||
putHex(builder, "-monet-secondary-fixed", dark ? "#3A2A22" : "#F0E4DC");
|
||||
putHex(builder, "-monet-secondary-fixed-dim", dark ? accentWarm : accent);
|
||||
putHex(builder, "-monet-on-secondary-fixed", dark ? "#EAE7DC" : "#141413");
|
||||
putHex(builder, "-monet-on-secondary-fixed-variant", dark ? "#9D9A91" : "#6B6860");
|
||||
putHex(builder, "-monet-tertiary", accent);
|
||||
putHex(builder, "-monet-on-tertiary", onAccent);
|
||||
putHex(builder, "-monet-tertiary-container", accentWarm);
|
||||
putHex(builder, "-monet-on-tertiary-container", onAccent);
|
||||
putHex(builder, "-monet-tertiary-fixed", dark ? "#4A2E24" : "#F6E0D6");
|
||||
putHex(builder, "-monet-tertiary-fixed-dim", dark ? accentWarm : accent);
|
||||
putHex(builder, "-monet-on-tertiary-fixed", dark ? "#FAF9F5" : "#141413");
|
||||
putHex(builder, "-monet-on-tertiary-fixed-variant", dark ? "#EAE7DC" : "#6B6860");
|
||||
|
||||
putHex(builder, "-monet-error", error);
|
||||
putHex(builder, "-monet-on-error", onAccent);
|
||||
putHex(builder, "-monet-error-container", dark ? "#4A1A1F" : "#FCEBEB");
|
||||
putHex(builder, "-monet-on-error-container", dark ? "#F9DEDC" : "#410E0B");
|
||||
|
||||
putHex(builder, "-anthropic-accent", accent);
|
||||
putHex(builder, "-anthropic-accent-warm", accentWarm);
|
||||
putHex(builder, "-anthropic-focus-ring", accent);
|
||||
putHex(builder, "-anthropic-focus-glow", "rgba(217, 119, 87, 0.35)");
|
||||
putHex(builder, "-anthropic-primary-hover", dark ? "#E08A6C" : accentWarm);
|
||||
putHex(builder, "-anthropic-primary-press", dark ? "#EFA08A" : "#B4573A");
|
||||
putHex(builder, "-anthropic-primary-container-hover", dark ? "#D97757" : "#B4573A");
|
||||
putHex(builder, "-anthropic-primary-container-press", dark ? "#E08A6C" : "#9A4A32");
|
||||
putHex(builder, "-anthropic-selected-wash", dark ? "rgba(217, 119, 87, 0.22)" : "rgba(217, 119, 87, 0.14)");
|
||||
// Stronger wash for tiny icon buttons where 6–8% alpha is invisible.
|
||||
putHex(builder, "-anthropic-icon-hover-wash", dark ? "rgba(234, 231, 220, 0.14)" : "rgba(20, 20, 19, 0.10)");
|
||||
putHex(builder, "-anthropic-icon-press-wash", dark ? "rgba(217, 119, 87, 0.32)" : "rgba(217, 119, 87, 0.22)");
|
||||
|
||||
// Transparent helpers consumed by root.css
|
||||
putHex(builder, "-monet-primary-transparent-50", accent + "80");
|
||||
putHex(builder, "-monet-primary-transparent-80", accent + "CC");
|
||||
putHex(builder, "-monet-surface-transparent-50", dark ? "#1A191680" : "#ECE9E080");
|
||||
putHex(builder, "-monet-surface-transparent-80", dark ? "#1A1916CC" : "#ECE9E0CC");
|
||||
putHex(builder, "-monet-surface-container-low-transparent-80", dark ? "#222119CC" : "#F5F3ECCC");
|
||||
putHex(builder, "-monet-secondary-container-transparent-50", dark ? "#2C2B2680" : "#E8E6DC80");
|
||||
putHex(builder, "-monet-secondary-container-transparent-80", dark ? "#2C2B26CC" : "#E8E6DCCC");
|
||||
putHex(builder, "-monet-on-surface-variant-transparent-38", dark ? "#9D9A9161" : "#6B686061");
|
||||
putHex(builder, "-monet-inverse-surface-transparent-80", dark ? "#F5F3ECCC" : "#141413CC");
|
||||
}
|
||||
|
||||
private static String getThemeStyleSheet() {
|
||||
final String blueCss = "/assets/css/blue.css";
|
||||
|
||||
if (ResolvedTheme.DEFAULT.equals(Themes.getTheme()))
|
||||
return blueCss;
|
||||
|
||||
ColorScheme scheme = Themes.getColorScheme();
|
||||
boolean dark = scheme.getBrightness() == Brightness.DARK;
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("* {\n");
|
||||
@@ -147,6 +295,9 @@ public final class StyleSheets {
|
||||
addColor(builder, scheme, ColorRole.SECONDARY_CONTAINER, 0.8);
|
||||
addColor(builder, scheme, ColorRole.INVERSE_SURFACE, 0.8);
|
||||
|
||||
// Skill authority: overwrite Monet surfaces/text/borders/accent for light+dark.
|
||||
applySkillTokenOverrides(builder, dark);
|
||||
|
||||
builder.append("}\n");
|
||||
return toStyleSheetUri(builder.toString());
|
||||
}
|
||||
|
||||
@@ -24,10 +24,19 @@ public record ResolvedTheme(ThemeColor primaryColorSeed,
|
||||
|
||||
/// Converts these resolved values to a MonetFX color scheme.
|
||||
///
|
||||
/// Warm secondary / tertiary / neutral seeds keep Monet from inventing teal or purple
|
||||
/// companions next to the orange accent; CSS + [SkillPalette] still pin exact skill hex.
|
||||
///
|
||||
/// @return the generated color scheme
|
||||
public ColorScheme toColorScheme() {
|
||||
boolean darkNeutrals = brightness == Brightness.DARK;
|
||||
return ColorScheme.newBuilder()
|
||||
.setPrimaryColorSeed(primaryColorSeed.color())
|
||||
.setSecondaryColorSeed(SkillPalette.ACCENT_WARM)
|
||||
.setTertiaryColorSeed(SkillPalette.ACCENT)
|
||||
.setNeutralColorSeed(darkNeutrals ? SkillPalette.DARK_SURFACE : SkillPalette.LIGHT_SURFACE)
|
||||
.setNeutralVariantColorSeed(darkNeutrals ? SkillPalette.DARK_OUTLINE : SkillPalette.LIGHT_OUTLINE)
|
||||
.setErrorColorSeed(SkillPalette.ERROR)
|
||||
.setColorStyle(colorStyle)
|
||||
.setBrightness(brightness)
|
||||
.setSpecVersion(ColorSpecVersion.SPEC_2025)
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
// SPDX-FileCopyrightText: Copyright (C) 2025 The HMCL Authors
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH AdditionRef-HMCL
|
||||
package org.jackhuang.hmcl.theme;
|
||||
|
||||
import javafx.scene.paint.Color;
|
||||
import org.glavo.monetfx.Brightness;
|
||||
import org.glavo.monetfx.ColorScheme;
|
||||
import org.jetbrains.annotations.NotNullByDefault;
|
||||
|
||||
/// Anthropic-style-cn skill light/dark palette used for JavaFX chrome that bypasses CSS.
|
||||
///
|
||||
/// Monet's generated scheme from an orange seed still drifts pink / teal; these exact
|
||||
/// skill tokens keep title bar, frame, and theme-color backgrounds aligned with
|
||||
/// [org.jackhuang.hmcl.setting.StyleSheets] CSS overrides.
|
||||
@NotNullByDefault
|
||||
public final class SkillPalette {
|
||||
public static final String ACCENT_HEX = "#D97757";
|
||||
public static final String ACCENT_WARM_HEX = "#C96442";
|
||||
public static final String ERROR_HEX = "#C0453A";
|
||||
public static final String ON_ACCENT_HEX = "#FAF9F5";
|
||||
|
||||
public static final Color ACCENT = Color.web(ACCENT_HEX);
|
||||
public static final Color ACCENT_WARM = Color.web(ACCENT_WARM_HEX);
|
||||
public static final Color ERROR = Color.web(ERROR_HEX);
|
||||
public static final Color ON_ACCENT = Color.web(ON_ACCENT_HEX);
|
||||
|
||||
public static final Color LIGHT_SURFACE = Color.web("#ECE9E0");
|
||||
public static final Color LIGHT_SURFACE_RAISED = Color.web("#F5F3EC");
|
||||
public static final Color LIGHT_SURFACE_OVERLAY = Color.web("#FDFCF8");
|
||||
public static final Color LIGHT_SURFACE_VARIANT = Color.web("#E8E6DC");
|
||||
public static final Color LIGHT_ON_SURFACE = Color.web("#141413");
|
||||
public static final Color LIGHT_ON_SURFACE_VARIANT = Color.web("#6B6860");
|
||||
public static final Color LIGHT_OUTLINE = Color.web("#D8D5CC");
|
||||
public static final Color LIGHT_OUTLINE_VARIANT = Color.web("#E8E6DC");
|
||||
|
||||
public static final Color DARK_SURFACE = Color.web("#1A1916");
|
||||
public static final Color DARK_SURFACE_RAISED = Color.web("#222119");
|
||||
public static final Color DARK_SURFACE_OVERLAY = Color.web("#2C2B26");
|
||||
public static final Color DARK_SURFACE_VARIANT = Color.web("#2C2B26");
|
||||
public static final Color DARK_ON_SURFACE = Color.web("#EAE7DC");
|
||||
public static final Color DARK_ON_SURFACE_VARIANT = Color.web("#9D9A91");
|
||||
public static final Color DARK_OUTLINE = Color.web("#3A3830");
|
||||
public static final Color DARK_OUTLINE_VARIANT = Color.web("#2E2D27");
|
||||
|
||||
private SkillPalette() {
|
||||
}
|
||||
|
||||
public static boolean isDark(ColorScheme scheme) {
|
||||
return scheme.getBrightness() == Brightness.DARK;
|
||||
}
|
||||
|
||||
public static boolean isDark() {
|
||||
return isDark(Themes.getColorScheme());
|
||||
}
|
||||
|
||||
public static Color surface(boolean dark) {
|
||||
return dark ? DARK_SURFACE : LIGHT_SURFACE;
|
||||
}
|
||||
|
||||
public static Color surfaceContainer(boolean dark) {
|
||||
return dark ? DARK_SURFACE_RAISED : LIGHT_SURFACE_RAISED;
|
||||
}
|
||||
|
||||
public static Color surfaceContainerHigh(boolean dark) {
|
||||
return dark ? DARK_SURFACE_OVERLAY : LIGHT_SURFACE_OVERLAY;
|
||||
}
|
||||
|
||||
public static Color onSurface(boolean dark) {
|
||||
return dark ? DARK_ON_SURFACE : LIGHT_ON_SURFACE;
|
||||
}
|
||||
|
||||
public static Color onSurfaceVariant(boolean dark) {
|
||||
return dark ? DARK_ON_SURFACE_VARIANT : LIGHT_ON_SURFACE_VARIANT;
|
||||
}
|
||||
|
||||
public static Color outline(boolean dark) {
|
||||
return dark ? DARK_OUTLINE : LIGHT_OUTLINE;
|
||||
}
|
||||
|
||||
public static Color surface() {
|
||||
return surface(isDark());
|
||||
}
|
||||
|
||||
public static Color surfaceContainer() {
|
||||
return surfaceContainer(isDark());
|
||||
}
|
||||
|
||||
public static Color onSurface() {
|
||||
return onSurface(isDark());
|
||||
}
|
||||
}
|
||||
@@ -26,10 +26,13 @@ import java.util.Objects;
|
||||
@JsonSerializable
|
||||
public record ThemeColor(@NotNull String name, @NotNull Color color) {
|
||||
|
||||
public static final ThemeColor DEFAULT = new ThemeColor("blue", Color.web("#5C6BC0"));
|
||||
/// Default Anthropic accent seed (skill: `--color-accent-orange`).
|
||||
public static final ThemeColor DEFAULT = new ThemeColor("anthropic_orange", Color.web("#D97757"));
|
||||
|
||||
public static final List<ThemeColor> STANDARD_COLORS = List.of(
|
||||
DEFAULT,
|
||||
new ThemeColor("stone", Color.web("#3D453C")),
|
||||
new ThemeColor("blue", Color.web("#5C6BC0")),
|
||||
new ThemeColor("darker_blue", Color.web("#283593")),
|
||||
new ThemeColor("green", Color.web("#43A047")),
|
||||
new ThemeColor("orange", Color.web("#E67E22")),
|
||||
|
||||
@@ -29,7 +29,6 @@ import javafx.stage.Stage;
|
||||
import javafx.stage.WindowEvent;
|
||||
import org.glavo.monetfx.Brightness;
|
||||
import org.glavo.monetfx.ColorScheme;
|
||||
import org.glavo.monetfx.ColorRole;
|
||||
import org.glavo.monetfx.ColorStyle;
|
||||
import org.glavo.monetfx.Contrast;
|
||||
import org.glavo.monetfx.beans.property.ColorSchemeProperty;
|
||||
@@ -800,7 +799,8 @@ public final class Themes {
|
||||
opacity = background.opacity();
|
||||
}
|
||||
|
||||
Paint themeColorPaint = appearance.toResolvedTheme(context).toColorScheme().getColor(ColorRole.SURFACE_CONTAINER);
|
||||
boolean darkFallback = appearance.toResolvedTheme(context).toColorScheme().getBrightness() == Brightness.DARK;
|
||||
Paint themeColorPaint = SkillPalette.surfaceContainer(darkFallback);
|
||||
BackgroundType fallbackType = Objects.requireNonNullElse(
|
||||
settings().backgroundFallbackTypeProperty().get(),
|
||||
BackgroundType.BUILTIN);
|
||||
@@ -960,7 +960,7 @@ public final class Themes {
|
||||
|
||||
/// Creates a JavaFX background from the current theme color scheme surface container.
|
||||
private static LauncherBackground createThemeColorBackground(double opacity) {
|
||||
return createPaintBackground(getColorScheme().getColor(ColorRole.SURFACE_CONTAINER), opacity);
|
||||
return createPaintBackground(SkillPalette.surfaceContainer(), opacity);
|
||||
}
|
||||
|
||||
/// Creates a JavaFX image background with a separately stored node opacity.
|
||||
@@ -1103,10 +1103,11 @@ public final class Themes {
|
||||
);
|
||||
|
||||
/// The title text fill derived from the current color scheme.
|
||||
///
|
||||
/// Title chrome uses surface tones (Anthropic-like calm bar), so fill stays on-surface
|
||||
/// whether or not the bar is translucent.
|
||||
private static final ObjectBinding<Color> titleFill = Bindings.createObjectBinding(
|
||||
() -> titleBarTransparent.get()
|
||||
? getColorScheme().getOnSurface()
|
||||
: getColorScheme().getOnPrimaryContainer(),
|
||||
() -> SkillPalette.onSurface(),
|
||||
colorSchemeProperty(),
|
||||
titleBarTransparent
|
||||
);
|
||||
|
||||
@@ -8,7 +8,6 @@ import com.jfoenix.validation.base.ValidatorBase;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.ReadOnlyStringProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
@@ -22,6 +21,7 @@ import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.game.GameInstanceID;
|
||||
import org.jackhuang.hmcl.game.GameJavaVersion;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.game.LauncherHelper;
|
||||
import org.jackhuang.hmcl.game.ModpackHelper;
|
||||
@@ -45,8 +45,6 @@ import org.jackhuang.hmcl.ui.instances.Instances;
|
||||
import org.jackhuang.hmcl.upgrade.IntegrityChecker;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.function.ExceptionalRunnable;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
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;
|
||||
@@ -57,7 +55,6 @@ import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
@@ -78,8 +75,6 @@ public final class Controllers {
|
||||
public static final String JAVA_INTERPRETED_MODE_TIP = "javaInterpretedMode";
|
||||
/// The shown-tip key for the software-rendering warning.
|
||||
public static final String SOFTWARE_RENDERING = "softwareRendering";
|
||||
/// The shown-tip key storing the year of the April Fools prompt.
|
||||
public static final String APRIL_FOOLS = "aprilFools";
|
||||
|
||||
/// The coordinator for the main window's scene graph and navigation stack.
|
||||
private static @Nullable Decorator decorator;
|
||||
@@ -259,8 +254,6 @@ public final class Controllers {
|
||||
public static void initialize(Stage stage) {
|
||||
LOG.info("Start initializing application");
|
||||
|
||||
LOG.info("April Fools: " + AprilFools.isEnabled());
|
||||
|
||||
if (System.getProperty("prism.lcdtext") == null) {
|
||||
@Nullable String fontAntiAliasing = SettingsManager.userSettings().fontAntiAliasingProperty().get();
|
||||
if ("lcd".equalsIgnoreCase(fontAntiAliasing)) {
|
||||
@@ -280,7 +273,10 @@ public final class Controllers {
|
||||
// Dedicated server client: always use the fixed default cache directory.
|
||||
settings().commonDirectoryTypeProperty().set(EnumCommonDirectory.DEFAULT);
|
||||
|
||||
Lang.thread(JavaManager::initialize, "Search Java", true);
|
||||
Lang.thread(() -> {
|
||||
JavaManager.initialize();
|
||||
FXUtils.runInFX(Controllers::tryEnsureRecommendedMojangJava);
|
||||
}, "Search Java", true);
|
||||
|
||||
StyleSheets.init(mainScene);
|
||||
|
||||
@@ -363,54 +359,42 @@ public final class Controllers {
|
||||
Controllers.dialog(agreementPane);
|
||||
}
|
||||
|
||||
aprilFools:
|
||||
if (AprilFools.isEnabled()) {
|
||||
int currentYear = LocalDate.now().getYear();
|
||||
if (state().getShownTips().get(APRIL_FOOLS) instanceof Number year && year.intValue() >= currentYear)
|
||||
break aprilFools;
|
||||
tryInstallBundledModpack(GameDirectoryManager.getSelectedRepository());
|
||||
}
|
||||
|
||||
if (!I18n.getLocale().getLocale().getLanguage().equals("zh"))
|
||||
break aprilFools;
|
||||
/// Downloads Mojang Runtime Java 25 when missing on supported platforms.
|
||||
///
|
||||
/// No confirm dialog: if already installed, returns quietly; otherwise shows a progress dialog.
|
||||
private static void tryEnsureRecommendedMojangJava() {
|
||||
org.jackhuang.hmcl.util.platform.Platform platform =
|
||||
org.jackhuang.hmcl.util.platform.Platform.SYSTEM_PLATFORM;
|
||||
GameJavaVersion target = GameJavaVersion.JAVA_25;
|
||||
|
||||
@Nullable SupportedLocale lzh = SupportedLocale.getSupportedLocales().stream()
|
||||
.filter(locale -> "lzh".equals(locale.getName()))
|
||||
.findFirst().orElse(null);
|
||||
|
||||
if (lzh == null) {
|
||||
LOG.warning("No supported locale found for lzh");
|
||||
break aprilFools;
|
||||
}
|
||||
|
||||
Runnable updateShowTips = () -> state().getShownTips().put(APRIL_FOOLS, currentYear);
|
||||
|
||||
Controllers.confirmWithCountdown(i18n("launcher.april_fools.switch_lzh"), null, 10,
|
||||
MessageType.QUESTION, () -> {
|
||||
Controllers.confirm(i18n("launcher.april_fools.switch_lzh.confirm"), null, MessageType.QUESTION, () -> {
|
||||
LOG.info("Switching locale to " + lzh);
|
||||
|
||||
updateShowTips.run();
|
||||
settings().languageProperty().set(lzh);
|
||||
|
||||
Controllers.onApplicationStop();
|
||||
|
||||
try {
|
||||
FileSaver.waitForAllSaves();
|
||||
} catch (InterruptedException ignored) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
try {
|
||||
Restarter.restartSelf();
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to restart self", e);
|
||||
}
|
||||
|
||||
Platform.exit();
|
||||
}, updateShowTips);
|
||||
}, updateShowTips);
|
||||
if (!GameJavaVersion.getSupportedVersions(platform).contains(target)) {
|
||||
return;
|
||||
}
|
||||
if (JavaManager.getMojangJavaPlatform(platform) == null) {
|
||||
return;
|
||||
}
|
||||
if (JavaManager.REPOSITORY.isInstalled(platform, target)) {
|
||||
return;
|
||||
}
|
||||
|
||||
tryInstallBundledModpack(GameDirectoryManager.getSelectedRepository());
|
||||
LOG.info("Recommended Mojang Java " + target.majorVersion()
|
||||
+ " (" + target.component() + ") missing; starting automatic download");
|
||||
|
||||
Controllers.taskDialog(
|
||||
JavaManager.getDownloadJavaTask(DownloadProviders.getDownloadProvider(), platform, target)
|
||||
.whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
if (exception != null) {
|
||||
LOG.warning("Failed to download recommended Mojang Java " + target.majorVersion(), exception);
|
||||
} else {
|
||||
LOG.info("Installed Mojang Java " + target.majorVersion() + " at " + result.getBinary());
|
||||
}
|
||||
}),
|
||||
i18n("java.download"),
|
||||
TaskCancellationAction.NO_CANCEL
|
||||
);
|
||||
}
|
||||
|
||||
/// Installs the package found by [Metadata#findBundledModpackFile()] asynchronously, if present.
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
// SPDX-FileCopyrightText: Copyright (C) 2020 The HMCL Authors
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH AdditionRef-HMCL
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
/// Golden-ratio layout metrics for Anthropic tool-mode chrome.
|
||||
///
|
||||
/// Primary splits use φ ≈ 1.618 (sidebar:content = 1:φ). Spacing uses the φ ladder
|
||||
/// from an 8px base so hierarchy stays dense — not landing-page whitespace.
|
||||
public final class LayoutMetrics {
|
||||
/// Golden ratio.
|
||||
public static final double PHI = 1.618033988749895;
|
||||
|
||||
/// Sidebar share of a page: 1 / (1 + φ) ≈ 0.382 so sidebar:content = 1:φ.
|
||||
public static final double SIDEBAR_FRACTION = 1.0 / (1.0 + PHI);
|
||||
|
||||
/// Design-minimum page width used when the node has not been laid out yet.
|
||||
public static final double DESIGN_PAGE_WIDTH = 800.0;
|
||||
|
||||
/// Tool-density clamp: keep the rail usable without eating the content column.
|
||||
public static final double SIDEBAR_MIN = 220.0;
|
||||
|
||||
/// φ share of [#DESIGN_PAGE_WIDTH] — exact 1:φ at the launcher minimum width.
|
||||
public static final double SIDEBAR_MAX = Math.rint(DESIGN_PAGE_WIDTH * SIDEBAR_FRACTION); // 306
|
||||
|
||||
/// φ spacing ladder from an 8px base (…, 5, 8, 13, 21, 34).
|
||||
public static final double SPACE_XS = 5.0;
|
||||
public static final double SPACE_SM = 8.0;
|
||||
public static final double SPACE_MD = 13.0;
|
||||
public static final double SPACE_LG = 21.0;
|
||||
public static final double SPACE_XL = 34.0;
|
||||
|
||||
/// Title-bar / touch target height (a11y floor from the skill).
|
||||
public static final double TITLE_BAR = 44.0;
|
||||
|
||||
/// Primary CTA height on the home page (Fibonacci neighbour of title bar / φ body).
|
||||
public static final double LAUNCH_HEIGHT = 55.0;
|
||||
|
||||
/// Primary CTA width (Fibonacci; pairs with the φ sidebar rail).
|
||||
public static final double LAUNCH_WIDTH = 233.0;
|
||||
|
||||
private LayoutMetrics() {
|
||||
}
|
||||
|
||||
/// Resolves the fixed sidebar width for a page of the given width.
|
||||
///
|
||||
/// @param pageWidth the animated page width in pixels; values ≤ 1 fall back to the design minimum
|
||||
/// @return a width clamped to [{@link #SIDEBAR_MIN}, {@link #SIDEBAR_MAX}] on the 1:φ fraction
|
||||
public static double sidebarWidth(double pageWidth) {
|
||||
double width = pageWidth > 1.0 ? pageWidth : DESIGN_PAGE_WIDTH;
|
||||
return Math.rint(Math.max(SIDEBAR_MIN, Math.min(SIDEBAR_MAX, width * SIDEBAR_FRACTION)));
|
||||
}
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright (C) 2026 The HMCL Authors
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH AdditionRef-HMCL
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXListView;
|
||||
import com.jfoenix.controls.JFXSpinner;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.ui.construct.ComponentList;
|
||||
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
|
||||
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.TaskManager;
|
||||
import org.jetbrains.annotations.NotNullByDefault;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
/// Displays active tasks and pending completion actions.
|
||||
@NotNullByDefault
|
||||
public final class TaskManagerPage extends Control implements DecoratorPage {
|
||||
|
||||
/// Navigation metadata for the task list.
|
||||
private final ReadOnlyObjectWrapper<State> state =
|
||||
new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("task.list")));
|
||||
|
||||
/// Creates a page backed by the application-wide task manager.
|
||||
public TaskManagerPage() {
|
||||
getStyleClass().add("content-background");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadOnlyObjectProperty<State> stateProperty() {
|
||||
return state.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SkinBase<TaskManagerPage> createDefaultSkin() {
|
||||
return new Skin(this);
|
||||
}
|
||||
|
||||
/// Binds a list view to active dialogs and pending completion actions.
|
||||
@NotNullByDefault
|
||||
private static final class Skin extends SkinBase<TaskManagerPage> {
|
||||
|
||||
/// Creates the task list and its empty-state message.
|
||||
private Skin(TaskManagerPage control) {
|
||||
super(control);
|
||||
StackPane container = new StackPane();
|
||||
container.setPadding(new Insets(10));
|
||||
container.getStyleClass().add("notice-pane");
|
||||
|
||||
ComponentList root = new ComponentList();
|
||||
root.getStyleClass().add("no-padding");
|
||||
|
||||
JFXListView<TaskExecutorDialogPane> list = new JFXListView<>();
|
||||
list.setItems(TaskManager.getInstance().getTasks());
|
||||
list.setCellFactory(view -> new TaskCell());
|
||||
list.setPadding(Insets.EMPTY);
|
||||
list.getStyleClass().add("no-horizontal-scrollbar");
|
||||
FXUtils.ignoreEvent(list, KeyEvent.KEY_PRESSED, event -> event.getCode() == KeyCode.ESCAPE);
|
||||
|
||||
StackPane placeholder = new StackPane(new Label(i18n("task.list.empty")));
|
||||
placeholder.getStyleClass().add("notice-pane");
|
||||
list.setPlaceholder(placeholder);
|
||||
|
||||
ComponentList.setVgrow(list, Priority.ALWAYS);
|
||||
root.getContent().setAll(list);
|
||||
container.getChildren().setAll(root);
|
||||
getChildren().setAll(container);
|
||||
}
|
||||
}
|
||||
|
||||
/// Displays a live task title and an action to restore progress or continue a completed task.
|
||||
@NotNullByDefault
|
||||
private static final class TaskCell extends ListCell<@Nullable TaskExecutorDialogPane> {
|
||||
|
||||
/// The list item whose title is bound to the current dialog.
|
||||
private final TwoLineListItem content = new TwoLineListItem();
|
||||
|
||||
/// The row's shared hover and ripple feedback.
|
||||
private final RipplerContainer graphic;
|
||||
|
||||
/// Centers running and pending indicators in a fixed-size slot to keep row titles aligned.
|
||||
private final StackPane statusIcon = new StackPane();
|
||||
|
||||
/// Restores progress or invokes the pending completion action.
|
||||
private final JFXButton actionButton = FXUtils.newToggleButton4(SVG.ARROW_FORWARD);
|
||||
|
||||
/// Describes the current row action.
|
||||
private final Tooltip actionTooltip = new Tooltip();
|
||||
|
||||
/// Creates a row that activates the current task through its body or trailing action.
|
||||
private TaskCell() {
|
||||
content.getStyleClass().add("md-list-cell");
|
||||
content.setPadding(new Insets(8));
|
||||
|
||||
statusIcon.setMinSize(32, 32);
|
||||
statusIcon.setPrefSize(32, 32);
|
||||
statusIcon.setMaxSize(32, 32);
|
||||
statusIcon.setMouseTransparent(true);
|
||||
content.setMinWidth(0);
|
||||
TwoLineListItem.setMargin(statusIcon, new Insets(0, 8, 0, 0));
|
||||
content.setLeading(statusIcon);
|
||||
|
||||
FXUtils.installFastTooltip(actionButton, actionTooltip);
|
||||
actionButton.setOnAction(event -> activateTask());
|
||||
TwoLineListItem.setMargin(actionButton, new Insets(0, 0, 0, 8));
|
||||
content.setTrailing(actionButton);
|
||||
|
||||
graphic = new RipplerContainer(content);
|
||||
graphic.setCursor(Cursor.HAND);
|
||||
graphic.setOnAction(event -> activateTask());
|
||||
}
|
||||
|
||||
/// Invokes a pending action or restores the current task's progress dialog.
|
||||
private void activateTask() {
|
||||
@Nullable TaskExecutorDialogPane pane = getItem();
|
||||
if (pane != null) {
|
||||
if (pane.pendingActionProperty().get() != null)
|
||||
pane.runPendingAction();
|
||||
else
|
||||
pane.restore();
|
||||
}
|
||||
}
|
||||
|
||||
/// Rebinds a reused cell and updates its progress or pending-action presentation.
|
||||
@Override
|
||||
protected void updateItem(@Nullable TaskExecutorDialogPane pane, boolean empty) {
|
||||
super.updateItem(pane, empty);
|
||||
graphic.resetFeedback();
|
||||
content.titleProperty().unbind();
|
||||
content.setSubtitle(null);
|
||||
if (empty || pane == null) {
|
||||
content.setTitle("");
|
||||
statusIcon.getChildren().clear();
|
||||
setGraphic(null);
|
||||
} else {
|
||||
content.titleProperty().bind(pane.titleProperty());
|
||||
@Nullable TaskExecutorDialogPane.PendingAction action = pane.pendingActionProperty().get();
|
||||
actionButton.getStyleClass().removeAll("toggle-icon4", "jfx-button-border");
|
||||
if (action != null) {
|
||||
actionButton.getStyleClass().add("jfx-button-border");
|
||||
statusIcon.getChildren().setAll(SVG.PENDING.createIcon(24));
|
||||
content.setSubtitle(action.description());
|
||||
actionButton.setGraphic(null);
|
||||
actionButton.setText(action.text());
|
||||
actionButton.setAccessibleText(action.text());
|
||||
actionTooltip.setText(action.text());
|
||||
} else {
|
||||
actionButton.getStyleClass().add("toggle-icon4");
|
||||
if (pane.queuedProperty().get()) {
|
||||
statusIcon.getChildren().setAll(SVG.PENDING.createIcon(24));
|
||||
content.setSubtitle(i18n("task.queued"));
|
||||
} else if (statusIcon.getChildren().isEmpty() || !(statusIcon.getChildren().get(0) instanceof JFXSpinner)) {
|
||||
JFXSpinner spinner = new JFXSpinner();
|
||||
spinner.setRadius(12);
|
||||
statusIcon.getChildren().setAll(spinner);
|
||||
}
|
||||
actionButton.setGraphic(SVG.ARROW_FORWARD.createIcon());
|
||||
actionButton.setText(null);
|
||||
actionButton.setAccessibleText(i18n("task.show"));
|
||||
actionTooltip.setText(i18n("task.show"));
|
||||
}
|
||||
setGraphic(graphic);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,14 +6,13 @@ import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.WeakInvalidationListener;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.AccountFactory;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.setting.SettingsManager;
|
||||
@@ -24,7 +23,6 @@ import org.jackhuang.hmcl.ui.WeakListenerHolder;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListItem;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.RegionUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -32,33 +30,13 @@ import org.jetbrains.annotations.UnmodifiableView;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.SettingsManager.userSettings;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.createSelectedItemPropertyFor;
|
||||
|
||||
/// Displays account creation methods and the configured account list.
|
||||
public final class AccountListPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
/// Whether offline and third-party account creation is restricted.
|
||||
static final BooleanProperty RESTRICTED = new SimpleBooleanProperty(true);
|
||||
|
||||
static {
|
||||
String property = System.getProperty("hmcl.offline.auth.restricted", "auto");
|
||||
|
||||
if ("false".equals(property)
|
||||
|| "auto".equals(property) && "CN".equals(RegionUtils.MACHINE_REGION)
|
||||
|| SettingsManager.userSettings().enableOfflineAccountProperty().get())
|
||||
RESTRICTED.set(false);
|
||||
else
|
||||
userSettings().enableOfflineAccountProperty().addListener(new ChangeListener<Boolean>() {
|
||||
@Override
|
||||
public void changed(ObservableValue<? extends Boolean> o, Boolean oldValue, Boolean newValue) {
|
||||
if (newValue) {
|
||||
userSettings().enableOfflineAccountProperty().removeListener(this);
|
||||
RESTRICTED.set(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
/// Offline / authlib-injector creation is always allowed for this fork.
|
||||
static final BooleanProperty RESTRICTED = new SimpleBooleanProperty(false);
|
||||
|
||||
/// The account entries derived from the configured accounts.
|
||||
private final @UnmodifiableView ObservableList<AccountListItem> items;
|
||||
@@ -78,6 +56,12 @@ public final class AccountListPage extends DecoratorAnimatedPage implements Deco
|
||||
items = MappedObservableList.create(accounts, AccountListItem::new);
|
||||
selectedAccount = createSelectedItemPropertyFor(items, Account.class);
|
||||
|
||||
AdvancedListItem offlineItem = new AdvancedListItem();
|
||||
offlineItem.getStyleClass().add("navigation-drawer-item");
|
||||
offlineItem.setTitle(i18n("account.methods.offline"));
|
||||
offlineItem.setLeftIcon(SVG.PERSON);
|
||||
offlineItem.setOnAction(e -> openCreateAccount(Accounts.FACTORY_OFFLINE));
|
||||
|
||||
AdvancedListItem microsoftItem = new AdvancedListItem();
|
||||
microsoftItem.getStyleClass().add("navigation-drawer-item");
|
||||
microsoftItem.setTitle(i18n("account.methods.microsoft"));
|
||||
@@ -91,14 +75,14 @@ public final class AccountListPage extends DecoratorAnimatedPage implements Deco
|
||||
});
|
||||
|
||||
InvalidationListener buildSidebarListener = observable -> {
|
||||
boolean restricted = RESTRICTED.get();
|
||||
|
||||
getSidebar().clear();
|
||||
|
||||
getSidebar()
|
||||
.startCategory(i18n("account.create").toUpperCase(Locale.ROOT))
|
||||
.add(offlineItem)
|
||||
.add(microsoftItem);
|
||||
|
||||
// External Yggdrasil / authlib-injector servers stay listed for future use.
|
||||
for (AuthlibInjectorServer server : authServers) {
|
||||
AdvancedListItem item = new AdvancedListItem();
|
||||
item.getStyleClass().add("navigation-drawer-item");
|
||||
@@ -111,24 +95,13 @@ public final class AccountListPage extends DecoratorAnimatedPage implements Deco
|
||||
}
|
||||
});
|
||||
|
||||
ObservableValue<String> title = BindingMapping.of(server, AuthlibInjectorServer::getName);
|
||||
var title = BindingMapping.of(server, AuthlibInjectorServer::getName);
|
||||
item.titleProperty().bind(title);
|
||||
item.setSubtitle(server.getDisplayHostUrl());
|
||||
|
||||
Tooltip tooltip = new Tooltip();
|
||||
if (restricted) {
|
||||
tooltip.setText(i18n("account.login.restricted"));
|
||||
item.setActionDisabled(true);
|
||||
} else {
|
||||
tooltip.textProperty().bind(Bindings.format("%s (%s)", title, server.getUrl()));
|
||||
item.setOnAction(e -> {
|
||||
if (SettingsManager.isUserGameAccountsReadOnly()) {
|
||||
confirmOverwriteUserAccounts(() -> Controllers.dialog(new CreateAccountPane(server)));
|
||||
} else {
|
||||
Controllers.dialog(new CreateAccountPane(server));
|
||||
}
|
||||
});
|
||||
}
|
||||
tooltip.textProperty().bind(Bindings.format("%s (%s)", title, server.getUrl()));
|
||||
item.setOnAction(e -> openCreateAccount(Accounts.getAccountFactoryByAuthlibInjectorServer(server)));
|
||||
FXUtils.installFastTooltip(item, tooltip);
|
||||
|
||||
getSidebar().add(item);
|
||||
@@ -139,7 +112,6 @@ public final class AccountListPage extends DecoratorAnimatedPage implements Deco
|
||||
|
||||
WeakInvalidationListener weakBuildSidebarListener = holder.weak(buildSidebarListener);
|
||||
authServers.addListener(weakBuildSidebarListener);
|
||||
RESTRICTED.addListener(weakBuildSidebarListener);
|
||||
|
||||
AdvancedListItem addAuthServerItem = new AdvancedListItem();
|
||||
addAuthServerItem.getStyleClass().add("navigation-drawer-item");
|
||||
@@ -172,6 +144,14 @@ public final class AccountListPage extends DecoratorAnimatedPage implements Deco
|
||||
setContent(scrollPane);
|
||||
}
|
||||
|
||||
private void openCreateAccount(AccountFactory<?> factory) {
|
||||
if (SettingsManager.isUserGameAccountsReadOnly()) {
|
||||
confirmOverwriteUserAccounts(() -> Controllers.dialog(new CreateAccountPane(factory)));
|
||||
} else {
|
||||
Controllers.dialog(new CreateAccountPane(factory));
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the account selection shared with the launcher.
|
||||
public ObjectProperty<@Nullable Account> selectedAccountProperty() {
|
||||
return selectedAccount;
|
||||
|
||||
@@ -81,20 +81,15 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
||||
|
||||
public CreateAccountPane(AccountFactory<?> factory) {
|
||||
if (factory == null) {
|
||||
if (AccountListPage.RESTRICTED.get()) {
|
||||
showMethodSwitcher = false;
|
||||
factory = Accounts.FACTORY_MICROSOFT;
|
||||
} else {
|
||||
showMethodSwitcher = true;
|
||||
String preferred = settings().preferredLoginTypeProperty().get();
|
||||
try {
|
||||
factory = Accounts.getAccountFactory(preferred);
|
||||
if (factory == Accounts.FACTORY_OFFLINE) {
|
||||
factory = Accounts.FACTORY_MICROSOFT;
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
factory = Accounts.FACTORY_MICROSOFT;
|
||||
showMethodSwitcher = true;
|
||||
String preferred = settings().preferredLoginTypeProperty().get();
|
||||
try {
|
||||
factory = Accounts.getAccountFactory(preferred);
|
||||
if (!Accounts.FACTORIES.contains(factory)) {
|
||||
factory = Accounts.FACTORY_OFFLINE;
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
factory = Accounts.FACTORY_OFFLINE;
|
||||
}
|
||||
} else {
|
||||
showMethodSwitcher = false;
|
||||
|
||||
@@ -121,7 +121,7 @@ public enum ContainerAnimations implements TransitionPane.AnimationProducer {
|
||||
/// Animates forward navigation with temporary incoming-page caching.
|
||||
@Override
|
||||
public Timeline animate(Pane container, Node previousNode, Node nextNode) {
|
||||
double offset = container.getWidth() > 0 ? container.getWidth() * 0.2 : 50;
|
||||
double offset = container.getWidth() > 0 ? container.getWidth() * 0.06 : 16;
|
||||
return new Timeline(
|
||||
new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(nextNode.cacheProperty(), isCacheAllowed(nextNode), Interpolator.DISCRETE),
|
||||
@@ -147,12 +147,12 @@ public enum ContainerAnimations implements TransitionPane.AnimationProducer {
|
||||
}
|
||||
},
|
||||
|
||||
/// @see <a href="https://m3.material.io/styles/motion/transitions/transition-patterns">Transitions - Material Design 3</a>
|
||||
/// Soft reverse of [#FORWARD] (reduced travel distance for Anthropic-calm chrome).
|
||||
BACKWARD {
|
||||
/// Animates backward navigation with temporary incoming-page caching.
|
||||
@Override
|
||||
public Timeline animate(Pane container, Node previousNode, Node nextNode) {
|
||||
double offset = container.getWidth() > 0 ? container.getWidth() * 0.2 : 50;
|
||||
double offset = container.getWidth() > 0 ? container.getWidth() * 0.06 : 16;
|
||||
return new Timeline(
|
||||
new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(nextNode.cacheProperty(), isCacheAllowed(nextNode), Interpolator.DISCRETE),
|
||||
@@ -210,8 +210,8 @@ public enum ContainerAnimations implements TransitionPane.AnimationProducer {
|
||||
new KeyValue(left.translateXProperty(), 0, DEFAULT_INTERPOLATOR),
|
||||
new KeyValue(center.translateXProperty(), 0, DEFAULT_INTERPOLATOR)));
|
||||
timeline.getKeyFrames().add(new KeyFrame(DEFAULT_DURATION_HALF,
|
||||
new KeyValue(left.translateXProperty(), -30, DEFAULT_INTERPOLATOR),
|
||||
new KeyValue(center.translateXProperty(), 30, DEFAULT_INTERPOLATOR)));
|
||||
new KeyValue(left.translateXProperty(), -10, DEFAULT_INTERPOLATOR),
|
||||
new KeyValue(center.translateXProperty(), 10, DEFAULT_INTERPOLATOR)));
|
||||
timeline.getKeyFrames().add(new KeyFrame(DEFAULT_DURATION,
|
||||
new KeyValue(left.cacheProperty(), left.isCache(), Interpolator.DISCRETE),
|
||||
new KeyValue(center.cacheProperty(), center.isCache(), Interpolator.DISCRETE)));
|
||||
@@ -233,8 +233,8 @@ public enum ContainerAnimations implements TransitionPane.AnimationProducer {
|
||||
new KeyValue(left.cacheProperty(), isCacheAllowed(left), Interpolator.DISCRETE),
|
||||
new KeyValue(center.cacheProperty(), cacheCenter, Interpolator.DISCRETE)));
|
||||
timeline.getKeyFrames().add(new KeyFrame(DEFAULT_DURATION_HALF,
|
||||
new KeyValue(left.translateXProperty(), -30, DEFAULT_INTERPOLATOR),
|
||||
new KeyValue(center.translateXProperty(), 30, DEFAULT_INTERPOLATOR)));
|
||||
new KeyValue(left.translateXProperty(), -10, DEFAULT_INTERPOLATOR),
|
||||
new KeyValue(center.translateXProperty(), 10, DEFAULT_INTERPOLATOR)));
|
||||
timeline.getKeyFrames().add(new KeyFrame(DEFAULT_DURATION,
|
||||
new KeyValue(left.cacheProperty(), left.isCache(), Interpolator.DISCRETE),
|
||||
new KeyValue(center.cacheProperty(), center.isCache(), Interpolator.DISCRETE),
|
||||
|
||||
@@ -22,6 +22,18 @@ public final class Motion {
|
||||
/// @see <a href="https://flutter.github.io/assets-for-api-docs/assets/animation/curve_linear.mp4">curve_linear.mp4</a>
|
||||
public static final Interpolator LINEAR = Interpolator.LINEAR;
|
||||
|
||||
/// Anthropic skill default ease: `cubic-bezier(0.16, 1, 0.3, 1)` — transform/opacity only.
|
||||
public static final Interpolator ANTHROPIC = new Cubic(0.16, 1.0, 0.3, 1.0);
|
||||
|
||||
/// Skill `--duration-fast` (150ms).
|
||||
public static final Duration FAST = Duration.millis(150);
|
||||
|
||||
/// Skill `--duration-normal` (250ms).
|
||||
public static final Duration NORMAL = Duration.millis(250);
|
||||
|
||||
/// Skill `--duration-slow` (400ms).
|
||||
public static final Duration SLOW = Duration.millis(400);
|
||||
|
||||
/// The emphasizedAccelerate easing curve in the Material specification.
|
||||
///
|
||||
/// See also:
|
||||
|
||||
@@ -22,11 +22,11 @@ import org.jetbrains.annotations.Nullable;
|
||||
@NotNullByDefault
|
||||
public class TransitionPane extends StackPane {
|
||||
|
||||
/// The default easing curve for page transitions.
|
||||
public static final Interpolator DEFAULT_INTERPOLATOR = Motion.EASE;
|
||||
/// Skill ease: cubic-bezier(0.16, 1, 0.3, 1) — no Material overshoot.
|
||||
public static final Interpolator DEFAULT_INTERPOLATOR = Motion.ANTHROPIC;
|
||||
|
||||
/// The default duration for page transitions.
|
||||
public static final Duration DEFAULT_DURATION = Motion.SHORT4;
|
||||
/// Skill `--duration-fast` (150ms).
|
||||
public static final Duration DEFAULT_DURATION = Motion.FAST;
|
||||
|
||||
/// Half the default duration for transitions with two phases.
|
||||
public static final Duration DEFAULT_DURATION_HALF = DEFAULT_DURATION.divide(2);
|
||||
|
||||
@@ -188,8 +188,9 @@ public final class TabController implements PageAware {
|
||||
/// Mirrors the original slide-up and fade-in transition vertically.
|
||||
TAB_DOWN(-1.0);
|
||||
|
||||
private static final Duration DURATION = Motion.MEDIUM4;
|
||||
private static final Duration DURATION = Motion.NORMAL;
|
||||
private static final Duration HALF_DURATION = DURATION.divide(2);
|
||||
private static final Interpolator EASING = Motion.ANTHROPIC;
|
||||
|
||||
private final double direction;
|
||||
|
||||
@@ -200,22 +201,23 @@ public final class TabController implements PageAware {
|
||||
@Override
|
||||
public Animation animate(Pane container, Node previousNode, Node nextNode) {
|
||||
double extent = container.getHeight();
|
||||
double offset = (extent > 0 ? extent * 0.2 : 50) * direction;
|
||||
// Small vertical drift + fade — avoids Material emphasized/overshoot motion.
|
||||
double offset = (extent > 0 ? extent * 0.06 : 16) * direction;
|
||||
var previousTranslation = previousNode.translateYProperty();
|
||||
var nextTranslation = nextNode.translateYProperty();
|
||||
return new Timeline(
|
||||
new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(nextNode.cacheProperty(), isCacheAllowed(nextNode), Interpolator.DISCRETE),
|
||||
new KeyValue(previousTranslation, 0, Motion.EASE_IN_OUT_CUBIC_EMPHASIZED),
|
||||
new KeyValue(previousNode.opacityProperty(), 1, Motion.EASE_IN_OUT_CUBIC_EMPHASIZED),
|
||||
new KeyValue(nextNode.opacityProperty(), 0, Motion.EASE_IN_OUT_CUBIC_EMPHASIZED),
|
||||
new KeyValue(nextTranslation, offset, Motion.EASE_IN_OUT_CUBIC_EMPHASIZED)),
|
||||
new KeyValue(previousTranslation, 0, EASING),
|
||||
new KeyValue(previousNode.opacityProperty(), 1, EASING),
|
||||
new KeyValue(nextNode.opacityProperty(), 0, EASING),
|
||||
new KeyValue(nextTranslation, offset, EASING)),
|
||||
new KeyFrame(HALF_DURATION,
|
||||
new KeyValue(previousNode.opacityProperty(), 0, Motion.EASE_IN_OUT_CUBIC_EMPHASIZED)),
|
||||
new KeyValue(previousNode.opacityProperty(), 0, EASING)),
|
||||
new KeyFrame(DURATION,
|
||||
new KeyValue(nextNode.cacheProperty(), nextNode.isCache(), Interpolator.DISCRETE),
|
||||
new KeyValue(nextNode.opacityProperty(), 1, Motion.EASE_IN_OUT_CUBIC_EMPHASIZED),
|
||||
new KeyValue(nextTranslation, 0, Motion.EASE_IN_OUT_CUBIC_EMPHASIZED)));
|
||||
new KeyValue(nextNode.opacityProperty(), 1, EASING),
|
||||
new KeyValue(nextTranslation, 0, EASING)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -83,8 +83,8 @@ public final class Decorator {
|
||||
/// The minimum width, in pixels, of the visible window content.
|
||||
private static final double MIN_CONTENT_WIDTH = 800.0;
|
||||
|
||||
/// The minimum height, in pixels, including the 450-pixel background and 40-pixel title bar.
|
||||
private static final double MIN_CONTENT_HEIGHT = 450.0 + 40.0;
|
||||
/// The minimum height, in pixels, including the 450-pixel background and 44-pixel title bar.
|
||||
private static final double MIN_CONTENT_HEIGHT = 450.0 + 44.0;
|
||||
|
||||
/// The minimum visible overlap, in pixels, used to accept a persisted window position.
|
||||
private static final double MIN_VISIBLE_WINDOW_AREA = 20.0;
|
||||
@@ -975,13 +975,13 @@ public final class Decorator {
|
||||
|
||||
Timeline timeline = new Timeline(
|
||||
new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(root.opacityProperty(), 0, Motion.EASE),
|
||||
new KeyValue(root.scaleXProperty(), 0.8, Motion.EASE),
|
||||
new KeyValue(root.scaleYProperty(), 0.8, Motion.EASE)),
|
||||
new KeyFrame(Duration.millis(600),
|
||||
new KeyValue(root.opacityProperty(), 1, Motion.EASE),
|
||||
new KeyValue(root.scaleXProperty(), 1, Motion.EASE),
|
||||
new KeyValue(root.scaleYProperty(), 1, Motion.EASE)));
|
||||
new KeyValue(root.opacityProperty(), 0, Motion.ANTHROPIC),
|
||||
new KeyValue(root.scaleXProperty(), 0.985, Motion.ANTHROPIC),
|
||||
new KeyValue(root.scaleYProperty(), 0.985, Motion.ANTHROPIC)),
|
||||
new KeyFrame(Motion.NORMAL,
|
||||
new KeyValue(root.opacityProperty(), 1, Motion.ANTHROPIC),
|
||||
new KeyValue(root.scaleXProperty(), 1, Motion.ANTHROPIC),
|
||||
new KeyValue(root.scaleYProperty(), 1, Motion.ANTHROPIC)));
|
||||
playWindowAnimation(timeline, () -> {
|
||||
resetRootTransform();
|
||||
|
||||
@@ -1003,15 +1003,11 @@ public final class Decorator {
|
||||
if (!nativeDecorationEnabled && AnimationUtils.playWindowAnimation() && OperatingSystem.CURRENT_OS != OperatingSystem.MACOS) {
|
||||
Timeline timeline = new Timeline(
|
||||
new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(root.opacityProperty(), 1, Motion.EASE),
|
||||
new KeyValue(root.translateYProperty(), 0, Motion.EASE),
|
||||
new KeyValue(root.scaleXProperty(), 1, Motion.EASE),
|
||||
new KeyValue(root.scaleYProperty(), 1, Motion.EASE)),
|
||||
new KeyFrame(Motion.SHORT4,
|
||||
new KeyValue(root.opacityProperty(), 0, Motion.EASE),
|
||||
new KeyValue(root.translateYProperty(), 200, Motion.EASE),
|
||||
new KeyValue(root.scaleXProperty(), 0.4, Motion.EASE),
|
||||
new KeyValue(root.scaleYProperty(), 0.4, Motion.EASE)));
|
||||
new KeyValue(root.opacityProperty(), 1, Motion.ANTHROPIC),
|
||||
new KeyValue(root.translateYProperty(), 0, Motion.ANTHROPIC)),
|
||||
new KeyFrame(Motion.FAST,
|
||||
new KeyValue(root.opacityProperty(), 0, Motion.ANTHROPIC),
|
||||
new KeyValue(root.translateYProperty(), 24, Motion.ANTHROPIC)));
|
||||
playWindowAnimation(timeline, () -> {
|
||||
if (getStage() == currentStage) {
|
||||
currentStage.setIconified(true);
|
||||
@@ -1032,13 +1028,9 @@ public final class Decorator {
|
||||
if (!nativeDecorationEnabled && AnimationUtils.playWindowAnimation()) {
|
||||
Timeline timeline = new Timeline(
|
||||
new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(root.opacityProperty(), 1, Motion.EASE),
|
||||
new KeyValue(root.scaleXProperty(), 1, Motion.EASE),
|
||||
new KeyValue(root.scaleYProperty(), 1, Motion.EASE)),
|
||||
new KeyFrame(Duration.millis(200),
|
||||
new KeyValue(root.opacityProperty(), 0, Motion.EASE),
|
||||
new KeyValue(root.scaleXProperty(), 0.8, Motion.EASE),
|
||||
new KeyValue(root.scaleYProperty(), 0.8, Motion.EASE)));
|
||||
new KeyValue(root.opacityProperty(), 1, Motion.ANTHROPIC)),
|
||||
new KeyFrame(Motion.FAST,
|
||||
new KeyValue(root.opacityProperty(), 0, Motion.ANTHROPIC)));
|
||||
playWindowAnimation(timeline, Launcher::stopApplication);
|
||||
} else {
|
||||
stopWindowAnimation();
|
||||
@@ -1172,15 +1164,11 @@ public final class Decorator {
|
||||
|
||||
Timeline timeline = new Timeline(
|
||||
new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(root.opacityProperty(), 0, Motion.EASE),
|
||||
new KeyValue(root.translateYProperty(), 200, Motion.EASE),
|
||||
new KeyValue(root.scaleXProperty(), 0.4, Motion.EASE),
|
||||
new KeyValue(root.scaleYProperty(), 0.4, Motion.EASE)),
|
||||
new KeyFrame(Motion.SHORT4,
|
||||
new KeyValue(root.opacityProperty(), 1, Motion.EASE),
|
||||
new KeyValue(root.translateYProperty(), 0, Motion.EASE),
|
||||
new KeyValue(root.scaleXProperty(), 1, Motion.EASE),
|
||||
new KeyValue(root.scaleYProperty(), 1, Motion.EASE)));
|
||||
new KeyValue(root.opacityProperty(), 0, Motion.ANTHROPIC),
|
||||
new KeyValue(root.translateYProperty(), 24, Motion.ANTHROPIC)),
|
||||
new KeyFrame(Motion.FAST,
|
||||
new KeyValue(root.opacityProperty(), 1, Motion.ANTHROPIC),
|
||||
new KeyValue(root.translateYProperty(), 0, Motion.ANTHROPIC)));
|
||||
playWindowAnimation(timeline, this::resetRootTransform);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,16 +2,19 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH AdditionRef-HMCL
|
||||
package org.jackhuang.hmcl.ui.decorator;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.LayoutMetrics;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
|
||||
import org.jetbrains.annotations.NotNullByDefault;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/// Lays out a fixed-width sidebar and a growing content area as independent animation targets.
|
||||
/// Lays out a φ-proportioned sidebar and a growing content area as independent animation targets.
|
||||
///
|
||||
/// Sidebar:content ≈ 1:φ (see [LayoutMetrics]), clamped for tool-mode density.
|
||||
@NotNullByDefault
|
||||
public class DecoratorAnimatedPage extends BorderPane {
|
||||
|
||||
@@ -25,7 +28,12 @@ public class DecoratorAnimatedPage extends BorderPane {
|
||||
/// Creates both animation targets without requiring CSS or skin initialization.
|
||||
public DecoratorAnimatedPage() {
|
||||
getStyleClass().add("gray-background");
|
||||
FXUtils.setLimitWidth(left, 200);
|
||||
var sidebarWidth = Bindings.createDoubleBinding(
|
||||
() -> LayoutMetrics.sidebarWidth(getWidth()),
|
||||
widthProperty());
|
||||
left.minWidthProperty().bind(sidebarWidth);
|
||||
left.prefWidthProperty().bind(sidebarWidth);
|
||||
left.maxWidthProperty().bind(sidebarWidth);
|
||||
sidebar.setMinSize(0, 0);
|
||||
left.setCenter(sidebar);
|
||||
setLeft(left);
|
||||
@@ -42,11 +50,11 @@ public class DecoratorAnimatedPage extends BorderPane {
|
||||
return left.getBottom();
|
||||
}
|
||||
|
||||
/// Replaces the bottom content, reserving its preferred height and a 12-pixel bottom gap.
|
||||
/// Replaces the bottom content, reserving its preferred height and a φ-md bottom gap.
|
||||
/// Passing null removes it without replacing the sidebar animation target.
|
||||
public void setSidebarBottom(@Nullable Node node) {
|
||||
if (node != null) {
|
||||
BorderPane.setMargin(node, new Insets(0, 0, 12, 0));
|
||||
BorderPane.setMargin(node, new Insets(0, 0, LayoutMetrics.SPACE_MD, 0));
|
||||
}
|
||||
left.setBottom(node);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
package org.jackhuang.hmcl.ui.decorator;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXSpinner;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
@@ -24,22 +23,19 @@ import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.scene.shape.SVGPath;
|
||||
import javafx.util.Duration;
|
||||
import org.glavo.monetfx.ColorRole;
|
||||
import org.glavo.monetfx.ColorScheme;
|
||||
import org.jackhuang.hmcl.theme.SkillPalette;
|
||||
import org.jackhuang.hmcl.theme.Themes;
|
||||
import org.jackhuang.hmcl.ui.*;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
import org.jackhuang.hmcl.ui.wizard.Navigation;
|
||||
import org.jackhuang.hmcl.util.TaskManager;
|
||||
import org.jetbrains.annotations.NotNullByDefault;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.animation.TransitionPane.DEFAULT_DURATION;
|
||||
import static org.jackhuang.hmcl.ui.animation.TransitionPane.DEFAULT_INTERPOLATOR;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
/// Renders the clipped background, title bar, and navigation content owned by a [Decorator].
|
||||
@NotNullByDefault
|
||||
@@ -85,7 +81,7 @@ final class MainWindowPane extends StackPane {
|
||||
() -> Themes.windowTransparentProperty().get()
|
||||
? null
|
||||
: new Background(new BackgroundFill(
|
||||
Themes.getColorScheme().getColor(ColorRole.SURFACE_CONTAINER),
|
||||
SkillPalette.surfaceContainer(),
|
||||
CornerRadii.EMPTY,
|
||||
Insets.EMPTY)),
|
||||
Themes.windowTransparentProperty(),
|
||||
@@ -170,42 +166,14 @@ final class MainWindowPane extends StackPane {
|
||||
return backgroundNode;
|
||||
}
|
||||
|
||||
/// Creates the task, minimize, and application-close buttons.
|
||||
/// Creates the minimize and application-close buttons.
|
||||
///
|
||||
/// @return a size-limited top-right button row
|
||||
private HBox createWindowButtons() {
|
||||
HBox buttons = new HBox();
|
||||
buttons.setAlignment(Pos.TOP_RIGHT);
|
||||
decorator.capableDraggingWindow(buttons);
|
||||
buttons.setMaxSize(Region.USE_PREF_SIZE, 40);
|
||||
|
||||
JFXButton tasksButton = new JFXButton();
|
||||
tasksButton.setFocusTraversable(false);
|
||||
tasksButton.setAccessibleText(i18n("task.list"));
|
||||
tasksButton.getStyleClass().add("jfx-decorator-button");
|
||||
tasksButton.setOnAction(event -> {
|
||||
if (!(decorator.getNavigator().getCurrentPage() instanceof TaskManagerPage))
|
||||
Controllers.navigate(new TaskManagerPage());
|
||||
});
|
||||
decorator.forbidDraggingWindow(tasksButton);
|
||||
|
||||
SVGPath tasksEmptyIcon = SVG.LIST_ALT.createIcon(Themes.titleFillProperty());
|
||||
|
||||
holder.onWeakChangeAndOperate(TaskManager.getInstance().statusProperty(), status -> {
|
||||
switch (status) {
|
||||
case IDLE -> tasksButton.setGraphic(tasksEmptyIcon);
|
||||
case PENDING -> tasksButton.setGraphic(SVG.PENDING.createIcon(Themes.titleFillProperty()));
|
||||
case RUNNING -> {
|
||||
JFXSpinner tasksSpinner = new JFXSpinner();
|
||||
tasksSpinner.getStyleClass().add("small-spinner");
|
||||
tasksSpinner.setRadius(9);
|
||||
tasksSpinner.setMouseTransparent(true);
|
||||
FXUtils.setLimitWidth(tasksSpinner, 20);
|
||||
FXUtils.setLimitHeight(tasksSpinner, 20);
|
||||
tasksButton.setGraphic(tasksSpinner);
|
||||
}
|
||||
}
|
||||
});
|
||||
buttons.setMaxSize(Region.USE_PREF_SIZE, 44);
|
||||
|
||||
JFXButton minimizeButton = new JFXButton();
|
||||
minimizeButton.setFocusTraversable(false);
|
||||
@@ -221,24 +189,27 @@ final class MainWindowPane extends StackPane {
|
||||
closeButton.setOnAction(event -> decorator.closeWindow());
|
||||
decorator.forbidDraggingWindow(closeButton);
|
||||
|
||||
buttons.getChildren().setAll(tasksButton, minimizeButton, closeButton);
|
||||
buttons.getChildren().setAll(minimizeButton, closeButton);
|
||||
return buttons;
|
||||
}
|
||||
|
||||
/// Recomputes the title-bar background for the current theme and transparency preference.
|
||||
///
|
||||
/// Uses surface / surface-container tones so the chrome stays calm and brand-forward
|
||||
/// instead of a saturated primary strip.
|
||||
private void updateTitleBarBackground() {
|
||||
@Nullable ColorScheme colorScheme = Themes.getColorScheme();
|
||||
if (colorScheme == null) {
|
||||
titleBar.setBackground(null);
|
||||
} else if (Themes.titleBarTransparentProperty().get()) {
|
||||
Color surface = colorScheme.getSurface();
|
||||
Color surface = SkillPalette.surface(SkillPalette.isDark(colorScheme));
|
||||
titleBar.setBackground(new Background(new BackgroundFill(
|
||||
new Color(surface.getRed(), surface.getGreen(), surface.getBlue(), 0.5),
|
||||
CornerRadii.EMPTY,
|
||||
Insets.EMPTY)));
|
||||
} else {
|
||||
titleBar.setBackground(new Background(new BackgroundFill(
|
||||
colorScheme.getPrimaryContainer(),
|
||||
SkillPalette.surfaceContainer(SkillPalette.isDark(colorScheme)),
|
||||
CornerRadii.EMPTY,
|
||||
Insets.EMPTY)));
|
||||
}
|
||||
@@ -358,7 +329,7 @@ final class MainWindowPane extends StackPane {
|
||||
/// {@inheritDoc}
|
||||
@Override
|
||||
public Timeline animate(Pane container, Node previousNode, Node nextNode) {
|
||||
return createTimeline(previousNode, nextNode, 50, -50);
|
||||
return createTimeline(previousNode, nextNode, 18, -18);
|
||||
}
|
||||
|
||||
/// {@inheritDoc}
|
||||
@@ -382,7 +353,7 @@ final class MainWindowPane extends StackPane {
|
||||
/// {@inheritDoc}
|
||||
@Override
|
||||
public Timeline animate(Pane container, Node previousNode, Node nextNode) {
|
||||
return createTimeline(previousNode, nextNode, -50, 50);
|
||||
return createTimeline(previousNode, nextNode, -18, 18);
|
||||
}
|
||||
|
||||
/// {@inheritDoc}
|
||||
|
||||
@@ -145,7 +145,6 @@ public final class ModpackSelectionPage extends VBox implements WizardPage {
|
||||
}, "", new URLValidator());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void cleanup(SettingsMap settings) {
|
||||
}
|
||||
|
||||
@@ -132,7 +132,6 @@ public final class VersionsPage extends Control implements WizardPage, Refreshab
|
||||
ALL,
|
||||
RELEASE,
|
||||
SNAPSHOTS,
|
||||
APRIL_FOOLS,
|
||||
OLD
|
||||
}
|
||||
|
||||
@@ -234,13 +233,8 @@ public final class VersionsPage extends Control implements WizardPage, Refreshab
|
||||
imageView.setImage(GameInstanceIconType.GRASS.getIcon());
|
||||
}
|
||||
case SNAPSHOT, PENDING, UNOBFUSCATED -> {
|
||||
if (versionType == ComponentRemoteVersion.Type.SNAPSHOT && gameVersion.isAprilFools()) {
|
||||
tags.add(new Tag(i18n("instance.game.april_fools")));
|
||||
imageView.setImage(GameInstanceIconType.APRIL_FOOLS.getIcon());
|
||||
} else {
|
||||
tags.add(new Tag(i18n("instance.game.snapshot")));
|
||||
imageView.setImage(GameInstanceIconType.COMMAND.getIcon());
|
||||
}
|
||||
tags.add(new Tag(i18n("instance.game.snapshot")));
|
||||
imageView.setImage(GameInstanceIconType.COMMAND.getIcon());
|
||||
}
|
||||
default -> {
|
||||
tags.add(new Tag(i18n("instance.game.old")));
|
||||
@@ -334,7 +328,6 @@ public final class VersionsPage extends Control implements WizardPage, Refreshab
|
||||
VersionTypeFilter.ALL,
|
||||
VersionTypeFilter.RELEASE,
|
||||
VersionTypeFilter.SNAPSHOTS,
|
||||
VersionTypeFilter.APRIL_FOOLS,
|
||||
VersionTypeFilter.OLD
|
||||
);
|
||||
categoryField.getSelectionModel().select(VersionTypeFilter.RELEASE);
|
||||
@@ -461,8 +454,6 @@ public final class VersionsPage extends Control implements WizardPage, Refreshab
|
||||
case SNAPSHOTS -> versionType == ComponentRemoteVersion.Type.SNAPSHOT
|
||||
|| versionType == ComponentRemoteVersion.Type.PENDING
|
||||
|| versionType == ComponentRemoteVersion.Type.UNOBFUSCATED;
|
||||
case APRIL_FOOLS -> versionType == ComponentRemoteVersion.Type.SNAPSHOT
|
||||
&& it.getGameVersion().isAprilFools();
|
||||
case OLD -> versionType == ComponentRemoteVersion.Type.OLD;
|
||||
// case ALL,
|
||||
default -> true;
|
||||
|
||||
@@ -45,7 +45,6 @@ public class GameInstanceIconDialog extends JFXDialogLayout {
|
||||
createIcon(GameInstanceIconType.CHEST),
|
||||
createIcon(GameInstanceIconType.CHICKEN),
|
||||
createIcon(GameInstanceIconType.COMMAND),
|
||||
createIcon(GameInstanceIconType.APRIL_FOOLS),
|
||||
createIcon(GameInstanceIconType.OPTIFINE),
|
||||
createIcon(GameInstanceIconType.CRAFT_TABLE),
|
||||
createIcon(GameInstanceIconType.FABRIC),
|
||||
|
||||
@@ -19,8 +19,6 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public final class HMCLLocalizedDownloadListPage extends DownloadListPage {
|
||||
|
||||
|
||||
|
||||
public static DownloadListPage ofResourcePack(DownloadPage.DownloadCallback callback, boolean instanceSelection) {
|
||||
return new HMCLLocalizedDownloadListPage(callback, instanceSelection, RemoteAddon.Type.RESOURCE_PACK, true, true);
|
||||
}
|
||||
|
||||
@@ -10,14 +10,11 @@ import org.jackhuang.hmcl.game.GameComponentType;
|
||||
import org.jackhuang.hmcl.game.HMCLGameInstance;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.*;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class InstallerListPage extends ListPageBase<InstallerItem> {
|
||||
private final WeakListenerHolder listenerHolder = new WeakListenerHolder();
|
||||
private @Nullable HMCLGameInstance gameInstance;
|
||||
@@ -116,7 +113,6 @@ public class InstallerListPage extends ListPageBase<InstallerItem> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class InstallerListPageSkin extends ToolbarListPageSkin<InstallerItem, InstallerListPage> {
|
||||
|
||||
InstallerListPageSkin() {
|
||||
|
||||
@@ -78,7 +78,6 @@ public final class Instances {
|
||||
installServerModpack();
|
||||
}
|
||||
|
||||
|
||||
public static void deleteInstance(HMCLGameInstance gameInstance) {
|
||||
HMCLGameRepository repository = gameInstance.getRepository();
|
||||
GameInstanceID instanceId = gameInstance.getId();
|
||||
@@ -127,7 +126,6 @@ public final class Instances {
|
||||
new Validator(i18n("install.new_game.already_exists"), newVersionName -> !repository.instanceIdConflicts(newVersionName) || newVersionName.equals(instanceId.toString())));
|
||||
}
|
||||
|
||||
|
||||
public static void openFolder(HMCLGameInstance gameInstance) {
|
||||
FXUtils.openFolder(gameInstance.getRunDirectory());
|
||||
}
|
||||
|
||||
@@ -309,7 +309,6 @@ public final class ModListPage extends ListPageBase<ModListPage.ModInfoObject> i
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void rollback(LocalModFile from, LocalModFile to) {
|
||||
try {
|
||||
modManager.rollback(from, to);
|
||||
|
||||
@@ -187,7 +187,6 @@ public final class ResourcePackListPage extends ListPageBase<ResourcePackListPag
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void onOpenFolder() {
|
||||
if (resourcePackDirectory != null) {
|
||||
FXUtils.openFolder(resourcePackDirectory);
|
||||
|
||||
@@ -149,7 +149,6 @@ public final class WorldListPage extends ListPageBase<World> {
|
||||
installWorld(res.get(0));
|
||||
}
|
||||
|
||||
|
||||
private void installWorld(Path zipFile) {
|
||||
// Only accept one world file because user is required to confirm the new world name
|
||||
// Or too many input dialogs are popped.
|
||||
|
||||
@@ -15,6 +15,7 @@ import javafx.scene.layout.*;
|
||||
import org.jackhuang.hmcl.setting.ProxyType;
|
||||
import org.jackhuang.hmcl.task.FetchTask;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.LayoutMetrics;
|
||||
import org.jackhuang.hmcl.ui.WeakListenerHolder;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.util.Holder;
|
||||
@@ -30,8 +31,8 @@ public class DownloadSettingsPage extends StackPane {
|
||||
private final WeakListenerHolder holder = new WeakListenerHolder();
|
||||
|
||||
public DownloadSettingsPage() {
|
||||
VBox content = new VBox(10);
|
||||
content.setPadding(new Insets(10));
|
||||
VBox content = new VBox(LayoutMetrics.SPACE_MD);
|
||||
content.setPadding(new Insets(LayoutMetrics.SPACE_SM, LayoutMetrics.SPACE_MD, LayoutMetrics.SPACE_MD, LayoutMetrics.SPACE_MD));
|
||||
content.setFillWidth(true);
|
||||
ScrollPane scrollPane = new ScrollPane(content);
|
||||
FXUtils.smoothScrolling(scrollPane);
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.jackhuang.hmcl.setting.GameDirectoryManager;
|
||||
import org.jackhuang.hmcl.theme.Themes;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.LayoutMetrics;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
@@ -79,7 +80,8 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
|
||||
state.setValue(new State(null, titleNode, false, false, true));
|
||||
|
||||
setPadding(new Insets(20));
|
||||
// Tool-density φ ladder (skill): md/lg insets — not landing-page whitespace.
|
||||
setPadding(new Insets(LayoutMetrics.SPACE_MD, LayoutMetrics.SPACE_MD, LayoutMetrics.SPACE_LG, LayoutMetrics.SPACE_MD));
|
||||
|
||||
|
||||
|
||||
@@ -186,8 +188,6 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void launch() {
|
||||
HMCLGameRepository repository = GameDirectoryManager.getSelectedRepository();
|
||||
Instances.launch(repository.getSelectedInstance());
|
||||
@@ -198,9 +198,6 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
Instances.installServerModpack();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public ReadOnlyObjectWrapper<State> stateProperty() {
|
||||
return state;
|
||||
@@ -237,5 +234,4 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
return instances;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -21,30 +21,19 @@ import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.Paint;
|
||||
import javafx.scene.text.Font;
|
||||
import javafx.scene.text.FontSmoothingType;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.glavo.monetfx.Brightness;
|
||||
import org.glavo.monetfx.ColorStyle;
|
||||
import org.glavo.uuid.UUIDs;
|
||||
import org.jackhuang.hmcl.setting.*;
|
||||
import org.jackhuang.hmcl.theme.*;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
||||
import org.jackhuang.hmcl.util.Holder;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.SafeStringConverter;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jetbrains.annotations.NotNullByDefault;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
@@ -55,7 +44,6 @@ import java.util.function.Supplier;
|
||||
import static org.jackhuang.hmcl.setting.SettingsManager.settings;
|
||||
import static org.jackhuang.hmcl.setting.SettingsManager.userSettings;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
/// Configures launcher appearance, background, and launcher font settings.
|
||||
@NotNullByDefault
|
||||
@@ -1029,7 +1017,7 @@ public class PersonalizationPage extends StackPane {
|
||||
}
|
||||
}
|
||||
|
||||
vbox.getChildren().add(new Label("Hello Minecraft! Launcher"));
|
||||
vbox.getChildren().add(new Label("TDLM"));
|
||||
|
||||
fontPane.getContent().add(vbox);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.LayoutMetrics;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
@@ -34,7 +34,6 @@ import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
@@ -49,103 +48,94 @@ public final class SettingsPage extends ScrollPane {
|
||||
public SettingsPage() {
|
||||
this.setFitToWidth(true);
|
||||
|
||||
VBox rootPane = new VBox(10);
|
||||
rootPane.setPadding(new Insets(10));
|
||||
// Title:body φ ladder — tight tool density (sm gap, md/lg insets).
|
||||
VBox rootPane = new VBox(LayoutMetrics.SPACE_SM);
|
||||
rootPane.setPadding(new Insets(LayoutMetrics.SPACE_SM, LayoutMetrics.SPACE_MD, LayoutMetrics.SPACE_MD, LayoutMetrics.SPACE_MD));
|
||||
this.setContent(rootPane);
|
||||
FXUtils.smoothScrolling(this);
|
||||
|
||||
{
|
||||
ComponentList languagePaneList = new ComponentList();
|
||||
|
||||
{
|
||||
ComponentList languagePaneList = new ComponentList();
|
||||
var chooseLanguagePane = new LineSelectButton<SupportedLocale>();
|
||||
chooseLanguagePane.setTitle(i18n("settings.launcher.language"));
|
||||
chooseLanguagePane.setSubtitle(i18n("settings.take_effect_after_restart"));
|
||||
|
||||
{
|
||||
var chooseLanguagePane = new LineSelectButton<SupportedLocale>();
|
||||
chooseLanguagePane.setTitle(i18n("settings.launcher.language"));
|
||||
chooseLanguagePane.setSubtitle(i18n("settings.take_effect_after_restart"));
|
||||
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(settings().languageProperty());
|
||||
|
||||
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(settings().languageProperty());
|
||||
languagePaneList.getContent().add(chooseLanguagePane);
|
||||
|
||||
languagePaneList.getContent().add(chooseLanguagePane);
|
||||
|
||||
}
|
||||
|
||||
rootPane.getChildren().addAll(ComponentList.createComponentListTitle(i18n("settings.launcher.language")), languagePaneList);
|
||||
}
|
||||
|
||||
rootPane.getChildren().addAll(ComponentList.createComponentListTitle(i18n("settings.launcher.language")), languagePaneList);
|
||||
}
|
||||
|
||||
{
|
||||
ComponentList miscPaneList = new ComponentList();
|
||||
|
||||
{
|
||||
ComponentList miscPaneList = new ComponentList();
|
||||
BorderPane debugPane = new BorderPane();
|
||||
|
||||
{
|
||||
LineSwitch disableAprilFools = new LineSwitch();
|
||||
disableAprilFools.setTitle(i18n("settings.launcher.disable_april_fools"));
|
||||
disableAprilFools.setSubtitle(i18n("settings.take_effect_after_restart"));
|
||||
disableAprilFools.selectedProperty().bindBidirectional(settings().disableAprilFoolsProperty());
|
||||
miscPaneList.getContent().add(disableAprilFools);
|
||||
}
|
||||
Label left = new Label(i18n("settings.launcher.debug"));
|
||||
BorderPane.setAlignment(left, Pos.CENTER_LEFT);
|
||||
debugPane.setLeft(left);
|
||||
|
||||
{
|
||||
BorderPane debugPane = new BorderPane();
|
||||
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);
|
||||
|
||||
Label left = new Label(i18n("settings.launcher.debug"));
|
||||
BorderPane.setAlignment(left, Pos.CENTER_LEFT);
|
||||
debugPane.setLeft(left);
|
||||
SpinnerPane exportLogPane = new SpinnerPane();
|
||||
|
||||
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);
|
||||
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());
|
||||
});
|
||||
|
||||
SpinnerPane exportLogPane = new SpinnerPane();
|
||||
HBox buttonBox = new HBox();
|
||||
buttonBox.setSpacing(10);
|
||||
buttonBox.getChildren().addAll(openLogFolderButton, exportLogPane);
|
||||
BorderPane.setAlignment(buttonBox, Pos.CENTER_RIGHT);
|
||||
debugPane.setRight(buttonBox);
|
||||
|
||||
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);
|
||||
miscPaneList.getContent().add(debugPane);
|
||||
}
|
||||
|
||||
rootPane.getChildren().addAll(ComponentList.createComponentListTitle(i18n("settings.launcher.misc")), miscPaneList);
|
||||
}
|
||||
}
|
||||
|
||||
private void openLogFolder() {
|
||||
FXUtils.openFolder(LOG.getLogFile().getParent());
|
||||
}
|
||||
|
||||
|
||||
private static String getEntryName(Set<String> entryNames, String name) {
|
||||
if (entryNames.add(name)) {
|
||||
return name;
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright (C) 2026 The HMCL Authors
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH AdditionRef-HMCL
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import org.jackhuang.hmcl.util.i18n.LocaleUtils;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.Month;
|
||||
import java.util.List;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.SettingsManager.settings;
|
||||
|
||||
/// April Fools' Day utilities.
|
||||
///
|
||||
/// This class provides methods to check if it is April Fools' Day or near April Fools' Day.
|
||||
/// It also provides a method to check if April Fools is enabled.
|
||||
///
|
||||
/// @author Glavo
|
||||
public final class AprilFools {
|
||||
|
||||
private static final boolean ENABLED;
|
||||
|
||||
static {
|
||||
var date = LocalDate.now();
|
||||
|
||||
// Some countries/regions may oppose April Fools' Day for various reasons.
|
||||
// Therefore, we use a regional whitelist to avoid risks.
|
||||
// Currently, we have only listed a limited set of countries/regions for testing.
|
||||
// We will investigate more countries/regions in the future to expand this list.
|
||||
boolean supportedRegion = List.of(
|
||||
"CN", "TW", "HK", "MO", "JP", "KR", "VN", "SG", "MY",
|
||||
"ES", "DE", "FR", "GB", "RU", "UA", "US"
|
||||
).contains(LocaleUtils.SYSTEM_DEFAULT.getCountry());
|
||||
|
||||
boolean aprilFoolsMode;
|
||||
String value = System.getProperty("hmcl.april_fools", System.getenv("HMCL_APRIL_FOOLS"));
|
||||
if ("true".equalsIgnoreCase(value))
|
||||
aprilFoolsMode = true;
|
||||
else if ("false".equalsIgnoreCase(value) || !supportedRegion)
|
||||
aprilFoolsMode = false;
|
||||
else
|
||||
aprilFoolsMode = date.getMonth() == Month.APRIL && date.getDayOfMonth() == 1;
|
||||
|
||||
ENABLED = aprilFoolsMode && !settings().disableAprilFoolsProperty().get();
|
||||
}
|
||||
|
||||
/// Whether April Fools is enabled.
|
||||
///
|
||||
/// This method returns true if April Fools is enabled.
|
||||
public static boolean isEnabled() {
|
||||
return ENABLED;
|
||||
}
|
||||
|
||||
private AprilFools() {
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
|
||||
@@ -1,35 +1,48 @@
|
||||
/*
|
||||
* Anthropic UI — dark brightness tokens (skill charcoal #1A1916 family).
|
||||
* Loaded after the generated Monet theme sheet; keep AA contrast on surfaces/text.
|
||||
*/
|
||||
* {
|
||||
-anthropic-shadow: rgba(0, 0, 0, 0.50);
|
||||
-anthropic-shadow-strong: rgba(0, 0, 0, 0.65);
|
||||
-anthropic-scrollbar-track: #222119;
|
||||
-anthropic-scrollbar-thumb: #5C5A54;
|
||||
-anthropic-hairline: -monet-outline-variant;
|
||||
-anthropic-focus-ring: #D97757;
|
||||
}
|
||||
|
||||
.hint {
|
||||
-fx-hyperlink-fill: -monet-inverse-primary;
|
||||
-fx-hyperlink-fill: -monet-primary;
|
||||
}
|
||||
|
||||
.tag {
|
||||
-fixed-warning-tag-background: #2c0b0e;
|
||||
-fixed-warning-tag-background: #4A1A1F;
|
||||
}
|
||||
|
||||
.log-window {
|
||||
-fixed-log-background: #282828;
|
||||
-fixed-log-toggle-selected: #DEE2E6;
|
||||
-fixed-log-toggle-unselected: #6C757D;
|
||||
-fixed-log-text-fill: #e3e3e3;
|
||||
-fixed-log-trace-background: #495057;
|
||||
-fixed-log-debug-background: #343A40;
|
||||
-fixed-log-info-background: #212529;
|
||||
-fixed-log-warn-background: #413c26;
|
||||
-fixed-log-warn-text-fill: #fdf3aa;
|
||||
-fixed-log-error-background: #4e3534;
|
||||
-fixed-log-error-text-fill: #f9dedc;
|
||||
-fixed-log-background: -monet-surface;
|
||||
-fixed-log-toggle-selected: -monet-on-surface;
|
||||
-fixed-log-toggle-unselected: -monet-on-surface-variant;
|
||||
-fixed-log-text-fill: -monet-on-surface;
|
||||
-fixed-log-trace-background: -monet-surface-container-high;
|
||||
-fixed-log-debug-background: -monet-surface-container;
|
||||
-fixed-log-info-background: -monet-surface;
|
||||
-fixed-log-warn-background: #3F3A26;
|
||||
-fixed-log-warn-text-fill: #F7E8A0;
|
||||
-fixed-log-error-background: #4E3534;
|
||||
-fixed-log-error-text-fill: #F9DEDC;
|
||||
-fixed-log-fatal-background: #842029;
|
||||
-fixed-log-selected: #3e3e3e;
|
||||
-fixed-log-selected: -monet-surface-container-highest;
|
||||
}
|
||||
|
||||
.addon-channel-icon.alpha .svg {
|
||||
-fx-fill: #ff496e;
|
||||
-fx-fill: #E08A8A;
|
||||
}
|
||||
|
||||
.addon-channel-icon.beta .svg {
|
||||
-fx-fill: #ffa347;
|
||||
-fx-fill: #E08A6C;
|
||||
}
|
||||
|
||||
.addon-channel-icon.release .svg {
|
||||
-fx-fill: #1bd96a;
|
||||
-fx-fill: #A3B58A;
|
||||
}
|
||||
@@ -1,3 +1,16 @@
|
||||
/*
|
||||
* Anthropic UI — light brightness tokens (skill-aligned warm neutrals).
|
||||
* Loaded after the generated Monet theme sheet; keep AA contrast on surfaces/text.
|
||||
*/
|
||||
* {
|
||||
-anthropic-shadow: rgba(20, 20, 19, 0.10);
|
||||
-anthropic-shadow-strong: rgba(20, 20, 19, 0.16);
|
||||
-anthropic-scrollbar-track: #E8E6DC;
|
||||
-anthropic-scrollbar-thumb: #9B9890;
|
||||
-anthropic-hairline: -monet-outline-variant;
|
||||
-anthropic-focus-ring: #D97757;
|
||||
}
|
||||
|
||||
.hint {
|
||||
-fx-hyperlink-fill: -monet-primary;
|
||||
}
|
||||
@@ -7,29 +20,29 @@
|
||||
}
|
||||
|
||||
.log-window {
|
||||
-fixed-log-background: #ffffff;
|
||||
-fixed-log-toggle-selected: #000000;
|
||||
-fixed-log-toggle-unselected: #6C757D;
|
||||
-fixed-log-text-fill: #1f1f1f;
|
||||
-fixed-log-trace-background: #EEE9E0;
|
||||
-fixed-log-debug-background: #EEE9E0;
|
||||
-fixed-log-info-background: #FFFFFF;
|
||||
-fixed-log-warn-background: #fef6d5;
|
||||
-fixed-log-warn-text-fill: #3e2f00;
|
||||
-fixed-log-error-background: #fcebeb;
|
||||
-fixed-log-error-text-fill: #410e0b;
|
||||
-fixed-log-background: -monet-surface;
|
||||
-fixed-log-toggle-selected: -monet-on-surface;
|
||||
-fixed-log-toggle-unselected: -monet-on-surface-variant;
|
||||
-fixed-log-text-fill: -monet-on-surface;
|
||||
-fixed-log-trace-background: -monet-surface-container;
|
||||
-fixed-log-debug-background: -monet-surface-container-low;
|
||||
-fixed-log-info-background: -monet-surface;
|
||||
-fixed-log-warn-background: #F7F0D8;
|
||||
-fixed-log-warn-text-fill: #3E2F00;
|
||||
-fixed-log-error-background: #FCEBEB;
|
||||
-fixed-log-error-text-fill: #410E0B;
|
||||
-fixed-log-fatal-background: #F7A699;
|
||||
-fixed-log-selected: #f2f2f2;
|
||||
-fixed-log-selected: -monet-surface-container-highest;
|
||||
}
|
||||
|
||||
.addon-channel-icon.alpha .svg {
|
||||
-fx-fill: #cb2245;
|
||||
-fx-fill: #C0453A;
|
||||
}
|
||||
|
||||
.addon-channel-icon.beta .svg {
|
||||
-fx-fill: #e08325;
|
||||
-fx-fill: #C96442;
|
||||
}
|
||||
|
||||
.addon-channel-icon.release .svg {
|
||||
-fx-fill: #00af5c;
|
||||
-fx-fill: #5F7A4A;
|
||||
}
|
||||
@@ -1,8 +1,53 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: Copyright (C) 2025 The HMCL Authors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later WITH AdditionRef-HMCL
|
||||
* Anthropic skill type roles (CN+Latin):
|
||||
* - Sans = UI / nav / buttons / chrome titles (Source Han Sans)
|
||||
* - Serif = body / narrative copy (Source Han Serif)
|
||||
*/
|
||||
.root {
|
||||
/* https://github.com/HMCL-dev/HMCL/pull/3423 */
|
||||
-fx-font-family: -fx-base-font-family;
|
||||
}
|
||||
-fx-font-family: "Source Han Serif SC", "Source Han Serif", "思源宋体", serif;
|
||||
}
|
||||
|
||||
/* UI chrome + interactive — sans */
|
||||
.jfx-decorator-title,
|
||||
.jfx-tool-bar-second .label,
|
||||
.jfx-tool-bar Label,
|
||||
.title-label,
|
||||
.class-title,
|
||||
.class-title Text,
|
||||
.jfx-layout-heading,
|
||||
.launch-pane > .launch-button .label,
|
||||
.announcement .title,
|
||||
.method-title,
|
||||
.radio-button-title-label,
|
||||
.two-line-list-item > .title,
|
||||
.advanced-list-item > .rippler-container > .container > .title,
|
||||
.iconed-item .iconed-item-container,
|
||||
.iconed-menu-item .iconed-item-container,
|
||||
.options-list-item .label,
|
||||
.jfx-button,
|
||||
.jfx-button *,
|
||||
.jfx-button-raised,
|
||||
.jfx-button-raised *,
|
||||
.jfx-button-border,
|
||||
.jfx-button-border *,
|
||||
.tab-label,
|
||||
.dialog-accept,
|
||||
.dialog-cancel,
|
||||
.line-component .title-label,
|
||||
.menu-container .title-label {
|
||||
-fx-font-family: "Source Han Sans SC", "Source Han Sans", "思源黑体", sans-serif;
|
||||
-fx-font-weight: bold;
|
||||
}
|
||||
|
||||
/* Body / secondary copy — serif */
|
||||
.subtitle-label,
|
||||
.two-line-list-item > .subtitle,
|
||||
.advanced-list-item > .rippler-container > .container > .subtitle,
|
||||
.announcement text,
|
||||
.method-desc,
|
||||
.options-list-item .label.subtitle,
|
||||
.hint,
|
||||
.html {
|
||||
-fx-font-family: "Source Han Serif SC", "Source Han Serif", "思源宋体", serif;
|
||||
-fx-font-weight: normal;
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Anthropic skill — hard press feedback (loaded LAST, beats Monet/root).
|
||||
* Forced :pressed / :armed change background AND border AND opacity.
|
||||
* Solid hex only (no fragile lookups) so Modena cyan cannot win on specificity.
|
||||
*/
|
||||
|
||||
.jfx-button:pressed,
|
||||
.jfx-button:armed,
|
||||
.toggle-icon4:pressed,
|
||||
.toggle-icon4:armed,
|
||||
.toggle-icon-tiny:pressed,
|
||||
.toggle-icon-tiny:armed,
|
||||
.announcement-close-button:pressed,
|
||||
.announcement-close-button:armed,
|
||||
.jfx-decorator-button:pressed,
|
||||
.jfx-decorator-button:armed,
|
||||
.jfx-tool-bar-button:pressed,
|
||||
.jfx-tool-bar-button:armed,
|
||||
.jfx-tool-bar .jfx-decorator-button:pressed,
|
||||
.jfx-tool-bar .jfx-decorator-button:armed {
|
||||
-fx-background-color: rgba(217, 119, 87, 0.42);
|
||||
-fx-border-color: #C96442;
|
||||
-fx-border-width: 2px;
|
||||
-fx-border-radius: 6px;
|
||||
-fx-background-radius: 6px;
|
||||
-fx-opacity: 0.84;
|
||||
-fx-scale-x: 0.94;
|
||||
-fx-scale-y: 0.94;
|
||||
}
|
||||
|
||||
.jfx-button:hover,
|
||||
.toggle-icon4:hover,
|
||||
.toggle-icon-tiny:hover,
|
||||
.announcement-close-button:hover,
|
||||
.jfx-decorator-button:hover,
|
||||
.jfx-tool-bar-button:hover {
|
||||
-fx-background-color: rgba(217, 119, 87, 0.16);
|
||||
-fx-border-color: rgba(201, 100, 66, 0.55);
|
||||
-fx-border-width: 1px;
|
||||
-fx-opacity: 1;
|
||||
}
|
||||
|
||||
.jfx-button-raised:pressed,
|
||||
.jfx-button-raised:armed,
|
||||
.jfx-button-raised-round:pressed,
|
||||
.jfx-button-raised-round:armed,
|
||||
.launch-pane > .jfx-button:pressed,
|
||||
.launch-pane > .jfx-button:armed {
|
||||
-fx-background-color: #9A4A32;
|
||||
-fx-border-color: #F5F3EC;
|
||||
-fx-border-width: 2px;
|
||||
-fx-opacity: 0.90;
|
||||
-fx-scale-x: 0.96;
|
||||
-fx-scale-y: 0.96;
|
||||
}
|
||||
|
||||
.jfx-button-border:pressed,
|
||||
.jfx-button-border:armed {
|
||||
-fx-background-color: rgba(217, 119, 87, 0.28);
|
||||
-fx-border-color: #C96442;
|
||||
-fx-border-width: 2px;
|
||||
-fx-opacity: 0.88;
|
||||
-fx-scale-x: 0.96;
|
||||
-fx-scale-y: 0.96;
|
||||
}
|
||||
|
||||
.toggle-icon4:pressed,
|
||||
.toggle-icon4:armed,
|
||||
.toggle-icon-tiny:pressed,
|
||||
.toggle-icon-tiny:armed,
|
||||
.announcement-close-button:pressed,
|
||||
.announcement-close-button:armed {
|
||||
-fx-background-radius: 50px;
|
||||
-fx-border-radius: 50px;
|
||||
}
|
||||
|
||||
.jfx-button:disabled:pressed,
|
||||
.jfx-button:disabled:armed,
|
||||
.toggle-icon4:disabled:pressed,
|
||||
.toggle-icon4:disabled:armed,
|
||||
.toggle-icon-tiny:disabled:pressed,
|
||||
.toggle-icon-tiny:disabled:armed,
|
||||
.jfx-decorator-button:disabled:pressed,
|
||||
.jfx-decorator-button:disabled:armed,
|
||||
.jfx-tool-bar-button:disabled:pressed,
|
||||
.jfx-tool-bar-button:disabled:armed {
|
||||
-fx-opacity: 0.45;
|
||||
-fx-background-color: transparent;
|
||||
-fx-border-width: 0;
|
||||
-fx-scale-x: 1;
|
||||
-fx-scale-y: 1;
|
||||
}
|
||||
|
||||
/* Skin-driven armed class — mirrors :pressed when Modena/CSS lose the race. */
|
||||
.jfx-button.anthropic-armed,
|
||||
.toggle-icon4.anthropic-armed,
|
||||
.toggle-icon-tiny.anthropic-armed,
|
||||
.jfx-decorator-button.anthropic-armed,
|
||||
.jfx-tool-bar-button.anthropic-armed,
|
||||
.announcement-close-button.anthropic-armed {
|
||||
-fx-background-color: rgba(217, 119, 87, 0.42);
|
||||
-fx-border-color: #C96442;
|
||||
-fx-border-width: 2px;
|
||||
-fx-opacity: 0.84;
|
||||
}
|
||||
|
||||
.jfx-button-raised.anthropic-armed,
|
||||
.launch-pane > .jfx-button.anthropic-armed {
|
||||
-fx-background-color: #9A4A32;
|
||||
-fx-border-color: #F5F3EC;
|
||||
-fx-border-width: 2px;
|
||||
-fx-opacity: 0.90;
|
||||
}
|
||||
@@ -120,7 +120,7 @@
|
||||
.radio-button-title-label {
|
||||
-fx-font-size: 16px;
|
||||
-fx-padding: 14.0 0.0 -20.0 0.0;
|
||||
-fx-text-fill: rgba(0.0, 0.0, 0.0, 0.87);
|
||||
-fx-text-fill: -monet-on-surface;
|
||||
}
|
||||
|
||||
.announcement {
|
||||
@@ -144,7 +144,8 @@
|
||||
}
|
||||
|
||||
.rippler-container {
|
||||
-jfx-feedback-fill: -monet-on-surface;
|
||||
/* Anthropic chrome: no Material ripple / press ink */
|
||||
-jfx-feedback-fill: transparent;
|
||||
}
|
||||
|
||||
.rippler-container:press-feedback-enabled {
|
||||
@@ -185,6 +186,18 @@
|
||||
|
||||
.advanced-list-item {
|
||||
-jfx-selected-background-fill: -monet-secondary-container;
|
||||
-fx-background-radius: 6px;
|
||||
}
|
||||
|
||||
.advanced-list-item:hover > .rippler-container {
|
||||
-fx-background-color: -anthropic-hover-wash;
|
||||
-fx-background-radius: 6px;
|
||||
}
|
||||
|
||||
.advanced-list-item:pressed > .rippler-container,
|
||||
.advanced-list-item:armed > .rippler-container {
|
||||
-fx-background-color: -anthropic-press-wash;
|
||||
-fx-background-radius: 6px;
|
||||
}
|
||||
|
||||
.advanced-list-item:selected > .rippler-container > .container > .title {
|
||||
@@ -229,8 +242,9 @@
|
||||
}
|
||||
|
||||
.class-title {
|
||||
-fx-font-size: 12px;
|
||||
-fx-padding: 8 16 8 16;
|
||||
/* Title block of title:body ≈ 1:φ — md/sm padding under tool density */
|
||||
-fx-font-size: 11px;
|
||||
-fx-padding: 13 13 8 13;
|
||||
}
|
||||
|
||||
.class-title Text {
|
||||
@@ -245,7 +259,7 @@
|
||||
}
|
||||
|
||||
.advanced-list-box-content {
|
||||
-fx-padding: 12 0 0 0;
|
||||
-fx-padding: 13 0 0 0;
|
||||
-fx-spacing: 0;
|
||||
}
|
||||
|
||||
@@ -258,8 +272,9 @@
|
||||
}
|
||||
|
||||
.iconed-item .iconed-item-container {
|
||||
-fx-padding: 10 16 10 16;
|
||||
-fx-spacing: 10;
|
||||
/* Body row under φ title:body — sm/md denser than landing */
|
||||
-fx-padding: 8 13 8 13;
|
||||
-fx-spacing: 13;
|
||||
-fx-font-size: 14;
|
||||
-fx-alignment: CENTER_LEFT;
|
||||
}
|
||||
@@ -274,14 +289,14 @@
|
||||
}
|
||||
|
||||
.iconed-menu-item .iconed-item-container {
|
||||
-fx-padding: 8 16 8 16;
|
||||
-fx-spacing: 10;
|
||||
-fx-padding: 8 13 8 13;
|
||||
-fx-spacing: 13;
|
||||
-fx-font-size: 12;
|
||||
-fx-alignment: CENTER_LEFT;
|
||||
}
|
||||
|
||||
.menu-container {
|
||||
-fx-padding: 8 16 8 16;
|
||||
-fx-padding: 8 13 8 13;
|
||||
}
|
||||
|
||||
.popup-menu .scroll-bar .thumb {
|
||||
@@ -392,7 +407,7 @@
|
||||
}
|
||||
|
||||
.installer-item-wrapper:card {
|
||||
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 10, 0.12, -1, 2);
|
||||
-fx-effect: dropshadow(gaussian, -anthropic-shadow, 8, 0.08, 0, 1);
|
||||
}
|
||||
|
||||
.installer-item-wrapper .installer-item:list-item {
|
||||
@@ -421,57 +436,82 @@
|
||||
******************************************************************************/
|
||||
|
||||
.launch-pane {
|
||||
-fx-max-height: 57px;
|
||||
-fx-min-height: 57px;
|
||||
-fx-max-width: 230px;
|
||||
-fx-min-width: 230px;
|
||||
/* φ/Fibonacci CTA: 55×233 pairs with title bar 44 and sidebar rail */
|
||||
-fx-max-height: 55px;
|
||||
-fx-min-height: 55px;
|
||||
-fx-max-width: 233px;
|
||||
-fx-min-width: 233px;
|
||||
}
|
||||
|
||||
.launch-pane > .jfx-button {
|
||||
-fx-translate-y: 1px;
|
||||
-fx-max-height: 55px;
|
||||
-fx-min-height: 55px;
|
||||
-fx-background-color: -monet-primary-container;
|
||||
-fx-max-height: 53px;
|
||||
-fx-min-height: 53px;
|
||||
-fx-background-color: -monet-primary;
|
||||
-fx-opacity: 1; /* Override the opacity of disabled button */
|
||||
-fx-cursor: hand;
|
||||
-fx-scale-x: 1;
|
||||
-fx-scale-y: 1;
|
||||
}
|
||||
|
||||
.launch-pane > .jfx-button:hover {
|
||||
-fx-background-color: -anthropic-primary-hover;
|
||||
-fx-scale-x: 1.02;
|
||||
-fx-scale-y: 1.02;
|
||||
}
|
||||
|
||||
.launch-pane > .jfx-button:pressed,
|
||||
.launch-pane > .jfx-button:armed {
|
||||
-fx-background-color: #9A4A32;
|
||||
-fx-border-color: #F5F3EC;
|
||||
-fx-border-width: 2px;
|
||||
-fx-opacity: 0.90;
|
||||
-fx-scale-x: 0.96;
|
||||
-fx-scale-y: 0.96;
|
||||
}
|
||||
|
||||
.launch-pane > .jfx-button:focused {
|
||||
-fx-border-color: -anthropic-focus-ring;
|
||||
-fx-border-width: 1.5px;
|
||||
}
|
||||
|
||||
.launch-pane > .jfx-button.launch-button {
|
||||
-fx-max-width: 200px;
|
||||
-fx-min-width: 200px;
|
||||
-fx-border-width: 0 3px 0 0;
|
||||
-fx-max-width: 201px;
|
||||
-fx-min-width: 201px;
|
||||
-fx-border-width: 0 1px 0 0;
|
||||
-fx-border-color: linear-gradient(
|
||||
to bottom,
|
||||
transparent 0%,
|
||||
transparent 20%,
|
||||
-monet-on-surface-variant 20%,
|
||||
-monet-on-surface-variant 80%,
|
||||
transparent 80%,
|
||||
to bottom,
|
||||
transparent 0%,
|
||||
transparent 22%,
|
||||
-monet-on-primary 22%,
|
||||
-monet-on-primary 78%,
|
||||
transparent 78%,
|
||||
transparent 100%
|
||||
);
|
||||
-fx-background-radius: 4px 0 0 4px;
|
||||
-fx-background-radius: 8px 0 0 8px;
|
||||
}
|
||||
|
||||
.launch-pane > .jfx-button.menu-button {
|
||||
-fx-max-width: 27px;
|
||||
-fx-min-width: 27px;
|
||||
-fx-max-width: 32px;
|
||||
-fx-min-width: 32px;
|
||||
-fx-font-size: 15px;
|
||||
-fx-background-radius: 0 4px 4px 0;
|
||||
-fx-background-radius: 0 8px 8px 0;
|
||||
}
|
||||
|
||||
.launch-pane > .jfx-button > StackPane > .jfx-rippler {
|
||||
-jfx-rippler-fill: -monet-on-primary-container;
|
||||
-jfx-rippler-disabled: true;
|
||||
-jfx-rippler-fill: transparent;
|
||||
-jfx-mask-type: CIRCLE;
|
||||
-fx-padding: 0;
|
||||
}
|
||||
|
||||
.launch-pane > .launch-button .label {
|
||||
-fx-text-fill: -monet-on-primary-container;
|
||||
-fx-font-size: 14px;
|
||||
-fx-text-fill: -monet-on-primary;
|
||||
-fx-font-size: 15px;
|
||||
}
|
||||
|
||||
.launch-pane .svg {
|
||||
-fx-fill: -monet-on-primary-container;
|
||||
-fx-fill: -monet-on-primary;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
@@ -498,7 +538,8 @@
|
||||
}
|
||||
|
||||
.jfx-tab-pane .jfx-rippler {
|
||||
-jfx-rippler-fill: white;
|
||||
-jfx-rippler-disabled: true;
|
||||
-jfx-rippler-fill: transparent;
|
||||
}
|
||||
|
||||
.tab-label {
|
||||
@@ -520,8 +561,8 @@
|
||||
******************************************************************************/
|
||||
|
||||
.dialog-trigger {
|
||||
-fx-background-color: WHITE;
|
||||
-jfx-button-type: RAISED;
|
||||
-fx-background-color: -monet-surface-container-highest;
|
||||
-jfx-button-type: FLAT;
|
||||
-fx-font-size: 14.0px;
|
||||
}
|
||||
|
||||
@@ -657,7 +698,7 @@
|
||||
******************************************************************************/
|
||||
|
||||
.icon {
|
||||
-fx-fill: #FE774D;
|
||||
-fx-fill: -monet-primary;
|
||||
-fx-padding: 10.0;
|
||||
-fx-cursor: hand;
|
||||
}
|
||||
@@ -671,17 +712,18 @@
|
||||
.jfx-tool-bar {
|
||||
-fx-font-size: 13.0;
|
||||
-fx-pref-width: 100.0%;
|
||||
-fx-min-height: 40px;
|
||||
-fx-max-height: 40px;
|
||||
-fx-pref-height: 40px;
|
||||
-fx-min-height: 44px;
|
||||
-fx-max-height: 44px;
|
||||
-fx-pref-height: 44px;
|
||||
}
|
||||
|
||||
.jfx-tool-bar .jfx-decorator-title {
|
||||
-fx-font-weight: BOLD;
|
||||
-fx-font-size: 16px;
|
||||
}
|
||||
|
||||
.jfx-tool-bar.background {
|
||||
-fx-background-color: -monet-primary-container;
|
||||
-fx-background-color: -monet-surface-container;
|
||||
}
|
||||
|
||||
/*.jfx-tool-bar HBox {*/
|
||||
@@ -690,21 +732,21 @@
|
||||
/*}*/
|
||||
|
||||
.jfx-tool-bar .jfx-decorator-button {
|
||||
-fx-min-width: 40px;
|
||||
-fx-max-width: 40px;
|
||||
-fx-pref-width: 40px;
|
||||
-fx-min-height: 40px;
|
||||
-fx-max-height: 40px;
|
||||
-fx-pref-height: 40px;
|
||||
-fx-min-width: 44px;
|
||||
-fx-max-width: 44px;
|
||||
-fx-pref-width: 44px;
|
||||
-fx-min-height: 44px;
|
||||
-fx-max-height: 44px;
|
||||
-fx-pref-height: 44px;
|
||||
-fx-cursor: hand;
|
||||
}
|
||||
|
||||
.jfx-tool-bar .jfx-decorator-button .svg {
|
||||
-fx-fill: -monet-on-primary-container;
|
||||
-fx-fill: -monet-on-surface;
|
||||
}
|
||||
|
||||
.jfx-decorator-button .jfx-spinner .arc {
|
||||
-fx-stroke: -monet-on-primary-container;
|
||||
-fx-stroke: -monet-on-surface;
|
||||
}
|
||||
|
||||
.jfx-tool-bar Label {
|
||||
@@ -712,7 +754,7 @@
|
||||
}
|
||||
|
||||
.jfx-tool-bar.gray-background Label {
|
||||
-fx-text-fill: BLACK;
|
||||
-fx-text-fill: -monet-on-surface;
|
||||
}
|
||||
|
||||
.jfx-tool-bar Text {
|
||||
@@ -732,15 +774,15 @@
|
||||
}
|
||||
|
||||
.jfx-tool-bar-second {
|
||||
-fx-pref-height: 42;
|
||||
-fx-padding: 2 2 2 2;
|
||||
-fx-background-color: -monet-primary-container;
|
||||
-fx-pref-height: 48;
|
||||
-fx-padding: 4 8 4 8;
|
||||
-fx-background-color: -monet-surface-container;
|
||||
-fx-alignment: CENTER-LEFT;
|
||||
-fx-spacing: 8;
|
||||
}
|
||||
|
||||
.jfx-tool-bar-second .label {
|
||||
-fx-text-fill: -monet-on-primary-container;
|
||||
-fx-text-fill: -monet-on-surface;
|
||||
-fx-font-size: 16;
|
||||
-fx-font-weight: bold;
|
||||
}
|
||||
@@ -758,12 +800,31 @@
|
||||
-fx-min-height: -fx-toggle-icon4-size;
|
||||
-fx-background-radius: 5px;
|
||||
-fx-background-color: transparent;
|
||||
-fx-opacity: 1;
|
||||
-fx-scale-x: 1;
|
||||
-fx-scale-y: 1;
|
||||
-jfx-toggle-color: -monet-on-surface;
|
||||
-jfx-untoggle-color: transparent;
|
||||
}
|
||||
|
||||
.jfx-tool-bar-button:hover {
|
||||
-fx-background-color: -anthropic-icon-hover-wash;
|
||||
-fx-scale-x: 1.04;
|
||||
-fx-scale-y: 1.04;
|
||||
}
|
||||
|
||||
.jfx-tool-bar-button:pressed,
|
||||
.jfx-tool-bar-button:armed {
|
||||
-fx-background-color: rgba(217, 119, 87, 0.42);
|
||||
-fx-border-color: #C96442;
|
||||
-fx-border-width: 2px;
|
||||
-fx-opacity: 0.84;
|
||||
-fx-scale-x: 0.94;
|
||||
-fx-scale-y: 0.94;
|
||||
}
|
||||
|
||||
.jfx-tool-bar-button .icon {
|
||||
-fx-fill: rgb(204.0, 204.0, 51.0);
|
||||
-fx-fill: -monet-on-surface;
|
||||
-fx-padding: 10.0;
|
||||
}
|
||||
|
||||
@@ -774,13 +835,35 @@
|
||||
|
||||
.jfx-decorator-button {
|
||||
-fx-max-width: 40px;
|
||||
-fx-background-radius: 5px;
|
||||
-fx-background-radius: 8px;
|
||||
-fx-max-height: 40px;
|
||||
-fx-background-color: transparent;
|
||||
}
|
||||
|
||||
.jfx-decorator-button:hover {
|
||||
-fx-background-color: -anthropic-hover-wash;
|
||||
-fx-scale-x: 1.04;
|
||||
-fx-scale-y: 1.04;
|
||||
}
|
||||
|
||||
.jfx-decorator-button:pressed,
|
||||
.jfx-decorator-button:armed {
|
||||
-fx-background-color: rgba(217, 119, 87, 0.42);
|
||||
-fx-border-color: #C96442;
|
||||
-fx-border-width: 2px;
|
||||
-fx-opacity: 0.84;
|
||||
-fx-scale-x: 0.94;
|
||||
-fx-scale-y: 0.94;
|
||||
}
|
||||
|
||||
.jfx-decorator-button:focused {
|
||||
-fx-border-color: -anthropic-focus-ring;
|
||||
-fx-border-width: 1.5px;
|
||||
-fx-border-radius: 8px;
|
||||
}
|
||||
|
||||
.jfx-decorator-button .icon {
|
||||
-fx-fill: rgb(204.0, 204.0, 51.0);
|
||||
-fx-fill: -monet-on-surface;
|
||||
-fx-padding: 0.0;
|
||||
}
|
||||
|
||||
@@ -848,6 +931,11 @@
|
||||
* *
|
||||
*******************************************************************************/
|
||||
|
||||
.jfx-rippler {
|
||||
-jfx-rippler-disabled: true;
|
||||
-jfx-rippler-fill: transparent;
|
||||
}
|
||||
|
||||
.jfx-rippler:hover {
|
||||
-fx-cursor: hand;
|
||||
}
|
||||
@@ -861,20 +949,80 @@
|
||||
.jfx-button {
|
||||
-jfx-disable-visual-focus: true;
|
||||
-fx-cursor: hand;
|
||||
-fx-background-radius: 6px;
|
||||
-fx-background-color: transparent;
|
||||
-fx-scale-x: 1;
|
||||
-fx-scale-y: 1;
|
||||
}
|
||||
|
||||
.jfx-button .jfx-rippler {
|
||||
-jfx-rippler-fill: -monet-on-surface;
|
||||
-jfx-rippler-disabled: true;
|
||||
-jfx-rippler-fill: transparent;
|
||||
-jfx-mask-type: CIRCLE;
|
||||
-fx-padding: 0.0;
|
||||
}
|
||||
|
||||
/* Calm tactile feedback: tone + border + opacity (no Material ripple). */
|
||||
.jfx-button:hover {
|
||||
-fx-background-color: -anthropic-hover-wash;
|
||||
-fx-border-color: rgba(201, 100, 66, 0.45);
|
||||
-fx-border-width: 1px;
|
||||
-fx-scale-x: 1.02;
|
||||
-fx-scale-y: 1.02;
|
||||
}
|
||||
|
||||
.jfx-button:pressed,
|
||||
.jfx-button:armed {
|
||||
-fx-background-color: rgba(217, 119, 87, 0.42);
|
||||
-fx-border-color: #C96442;
|
||||
-fx-border-width: 2px;
|
||||
-fx-opacity: 0.84;
|
||||
-fx-scale-x: 0.94;
|
||||
-fx-scale-y: 0.94;
|
||||
}
|
||||
|
||||
.jfx-button:focused {
|
||||
-fx-border-color: -anthropic-focus-ring;
|
||||
-fx-border-width: 2px;
|
||||
-fx-border-radius: 6px;
|
||||
-fx-border-insets: 0;
|
||||
-fx-effect: dropshadow(gaussian, -anthropic-focus-glow, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
.jfx-button:disabled,
|
||||
.jfx-button:disabled:hover,
|
||||
.jfx-button:disabled:pressed,
|
||||
.jfx-button:disabled:armed {
|
||||
-fx-scale-x: 1;
|
||||
-fx-scale-y: 1;
|
||||
-fx-opacity: 0.45;
|
||||
-fx-background-color: transparent;
|
||||
}
|
||||
|
||||
.jfx-button-raised {
|
||||
-fx-background-color: -monet-primary;
|
||||
-fx-background-radius: 6px;
|
||||
}
|
||||
|
||||
.jfx-button-raised:hover {
|
||||
-fx-background-color: -anthropic-primary-hover;
|
||||
-fx-scale-x: 1.02;
|
||||
-fx-scale-y: 1.02;
|
||||
}
|
||||
|
||||
.jfx-button-raised:pressed,
|
||||
.jfx-button-raised:armed {
|
||||
-fx-background-color: #9A4A32;
|
||||
-fx-border-color: #F5F3EC;
|
||||
-fx-border-width: 2px;
|
||||
-fx-opacity: 0.90;
|
||||
-fx-scale-x: 0.96;
|
||||
-fx-scale-y: 0.96;
|
||||
}
|
||||
|
||||
.jfx-button-raised .jfx-rippler {
|
||||
-jfx-rippler-fill: -monet-on-primary;
|
||||
-jfx-rippler-disabled: true;
|
||||
-jfx-rippler-fill: transparent;
|
||||
-jfx-mask-type: CIRCLE;
|
||||
-fx-padding: 0.0;
|
||||
}
|
||||
@@ -887,11 +1035,33 @@
|
||||
.jfx-button-border {
|
||||
-fx-background-color: transparent;
|
||||
-fx-border-color: -monet-outline;
|
||||
-fx-border-radius: 5px;
|
||||
-fx-border-width: 0.2px;
|
||||
-fx-border-radius: 6px;
|
||||
-fx-border-width: 1px;
|
||||
-fx-padding: 8px;
|
||||
}
|
||||
|
||||
.jfx-button-border:hover {
|
||||
-fx-background-color: -anthropic-hover-wash;
|
||||
-fx-border-color: -monet-primary;
|
||||
-fx-scale-x: 1.02;
|
||||
-fx-scale-y: 1.02;
|
||||
}
|
||||
|
||||
.jfx-button-border:pressed,
|
||||
.jfx-button-border:armed {
|
||||
-fx-background-color: rgba(217, 119, 87, 0.28);
|
||||
-fx-border-color: #C96442;
|
||||
-fx-border-width: 2px;
|
||||
-fx-opacity: 0.88;
|
||||
-fx-scale-x: 0.96;
|
||||
-fx-scale-y: 0.96;
|
||||
}
|
||||
|
||||
.jfx-button-border:focused {
|
||||
-fx-border-color: -anthropic-focus-ring;
|
||||
-fx-border-width: 1.5px;
|
||||
}
|
||||
|
||||
.jfx-button-border, .jfx-button-border * {
|
||||
-fx-text-fill: -monet-primary;
|
||||
}
|
||||
@@ -901,6 +1071,22 @@
|
||||
-fx-background-radius: 50px;
|
||||
}
|
||||
|
||||
.jfx-button-raised-round:hover {
|
||||
-fx-background-color: -anthropic-primary-container-hover;
|
||||
-fx-scale-x: 1.03;
|
||||
-fx-scale-y: 1.03;
|
||||
}
|
||||
|
||||
.jfx-button-raised-round:pressed,
|
||||
.jfx-button-raised-round:armed {
|
||||
-fx-background-color: #9A4A32;
|
||||
-fx-border-color: #F5F3EC;
|
||||
-fx-border-width: 2px;
|
||||
-fx-opacity: 0.90;
|
||||
-fx-scale-x: 0.96;
|
||||
-fx-scale-y: 0.96;
|
||||
}
|
||||
|
||||
.menu-up-down-button .label {
|
||||
-fx-text-fill: -monet-on-surface;
|
||||
}
|
||||
@@ -993,7 +1179,7 @@
|
||||
|
||||
.list-cell:selected, .jfx-list-cell:selected,
|
||||
.list-cell:hover, .jfx-list-cell:hover {
|
||||
-fx-text-fill: black;
|
||||
-fx-text-fill: -monet-on-surface;
|
||||
}
|
||||
|
||||
.jfx-list-cell .jfx-rippler {
|
||||
@@ -1040,31 +1226,31 @@
|
||||
|
||||
.options-list {
|
||||
-fx-background-color: transparent;
|
||||
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 5, 0.06, -0.5, 1);
|
||||
-fx-effect: dropshadow(gaussian, -anthropic-shadow, 4, 0.04, 0, 1);
|
||||
}
|
||||
|
||||
.depth-0 {
|
||||
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0), 0, 0, 0, 0);
|
||||
-fx-effect: dropshadow(gaussian, transparent, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
.depth-1 {
|
||||
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 10, 0.12, -1, 2);
|
||||
-fx-effect: dropshadow(gaussian, -anthropic-shadow, 8, 0.08, 0, 1);
|
||||
}
|
||||
|
||||
.depth-2 {
|
||||
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 15, 0.16, 0, 4);
|
||||
-fx-effect: dropshadow(gaussian, -anthropic-shadow-strong, 12, 0.10, 0, 2);
|
||||
}
|
||||
|
||||
.depth-3 {
|
||||
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 20, 0.19, 0, 6);
|
||||
-fx-effect: dropshadow(gaussian, -anthropic-shadow-strong, 16, 0.12, 0, 3);
|
||||
}
|
||||
|
||||
.depth-4 {
|
||||
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 25, 0.25, 0, 8);
|
||||
-fx-effect: dropshadow(gaussian, -anthropic-shadow-strong, 20, 0.14, 0, 4);
|
||||
}
|
||||
|
||||
.depth-5 {
|
||||
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 30, 0.3, 0, 10);
|
||||
-fx-effect: dropshadow(gaussian, -anthropic-shadow-strong, 24, 0.16, 0, 5);
|
||||
}
|
||||
|
||||
.padding-8 {
|
||||
@@ -1073,30 +1259,30 @@
|
||||
|
||||
.card {
|
||||
-fx-background-color: -monet-surface-container-low-transparent-80;
|
||||
-fx-background-radius: 4;
|
||||
-fx-padding: 8px;
|
||||
-fx-background-radius: 8;
|
||||
-fx-padding: 13px;
|
||||
|
||||
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 10, 0.12, -1, 2);
|
||||
-fx-effect: dropshadow(gaussian, -anthropic-shadow, 6, 0.06, 0, 1);
|
||||
}
|
||||
|
||||
.card-no-padding {
|
||||
-fx-background-color: -monet-surface-container-low-transparent-80;
|
||||
-fx-background-radius: 4;
|
||||
-fx-background-radius: 8;
|
||||
|
||||
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 10, 0.12, -1, 2);
|
||||
-fx-effect: dropshadow(gaussian, -anthropic-shadow, 6, 0.06, 0, 1);
|
||||
}
|
||||
|
||||
.card-non-transparent {
|
||||
-fx-background-color: -monet-surface-container-low;
|
||||
-fx-background-radius: 4;
|
||||
-fx-padding: 8px;
|
||||
-fx-background-radius: 8;
|
||||
-fx-padding: 13px;
|
||||
|
||||
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 5, 0.06, -0.5, 1);
|
||||
-fx-effect: dropshadow(gaussian, -anthropic-shadow, 4, 0.04, 0, 1);
|
||||
}
|
||||
|
||||
.card-list {
|
||||
-fx-padding: 10;
|
||||
-fx-spacing: 10;
|
||||
-fx-padding: 13;
|
||||
-fx-spacing: 13;
|
||||
}
|
||||
|
||||
.md-list-cell {
|
||||
@@ -1132,7 +1318,8 @@
|
||||
-fx-background-color: -monet-surface;
|
||||
-fx-border-color: -monet-outline-variant;
|
||||
-fx-border-width: 1 0 0 0;
|
||||
-fx-padding: 10 16 10 16;
|
||||
/* Body of title:body — sm vertical / md horizontal */
|
||||
-fx-padding: 8 13 8 13;
|
||||
-fx-font-size: 12;
|
||||
}
|
||||
|
||||
@@ -1324,9 +1511,11 @@
|
||||
|
||||
.jfx-decorator-button {
|
||||
-fx-pref-width: 50px;
|
||||
-fx-background-radius: 50px;
|
||||
-fx-background-radius: 8px;
|
||||
-fx-pref-height: 50px;
|
||||
-fx-background-color: transparent;
|
||||
-fx-border-width: 0;
|
||||
-fx-opacity: 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -1340,17 +1529,38 @@
|
||||
-fx-min-height: -fx-toggle-icon4-size;
|
||||
-fx-background-radius: 50px;
|
||||
-fx-background-color: transparent;
|
||||
-fx-opacity: 1;
|
||||
-fx-scale-x: 1;
|
||||
-fx-scale-y: 1;
|
||||
-jfx-toggle-color: -monet-primary;
|
||||
-jfx-untoggle-color: transparent;
|
||||
}
|
||||
|
||||
.toggle-icon4:hover {
|
||||
-fx-background-color: -anthropic-icon-hover-wash;
|
||||
-fx-scale-x: 1.06;
|
||||
-fx-scale-y: 1.06;
|
||||
}
|
||||
|
||||
.toggle-icon4:pressed,
|
||||
.toggle-icon4:armed {
|
||||
-fx-background-color: rgba(217, 119, 87, 0.42);
|
||||
-fx-border-color: #C96442;
|
||||
-fx-border-width: 2px;
|
||||
-fx-border-radius: 50px;
|
||||
-fx-opacity: 0.84;
|
||||
-fx-scale-x: 0.90;
|
||||
-fx-scale-y: 0.90;
|
||||
}
|
||||
|
||||
.toggle-icon4 .icon {
|
||||
-fx-fill: rgb(204.0, 204.0, 51.0);
|
||||
-fx-fill: -monet-on-surface;
|
||||
-fx-padding: 10.0;
|
||||
}
|
||||
|
||||
.toggle-icon4 .jfx-rippler {
|
||||
-jfx-rippler-fill: -monet-primary;
|
||||
-jfx-rippler-disabled: true;
|
||||
-jfx-rippler-fill: transparent;
|
||||
-jfx-mask-type: CIRCLE;
|
||||
}
|
||||
|
||||
@@ -1365,12 +1575,33 @@
|
||||
-fx-padding: 0;
|
||||
-fx-background-radius: 25px;
|
||||
-fx-background-color: transparent;
|
||||
-fx-opacity: 1;
|
||||
-fx-scale-x: 1;
|
||||
-fx-scale-y: 1;
|
||||
-jfx-toggle-color: -monet-primary;
|
||||
-jfx-untoggle-color: transparent;
|
||||
}
|
||||
|
||||
.toggle-icon-tiny:hover {
|
||||
-fx-background-color: -anthropic-icon-hover-wash;
|
||||
-fx-opacity: 0.92;
|
||||
-fx-scale-x: 1.08;
|
||||
-fx-scale-y: 1.08;
|
||||
}
|
||||
|
||||
.toggle-icon-tiny:pressed,
|
||||
.toggle-icon-tiny:armed {
|
||||
-fx-background-color: rgba(217, 119, 87, 0.42);
|
||||
-fx-border-color: #C96442;
|
||||
-fx-border-width: 2px;
|
||||
-fx-border-radius: 50px;
|
||||
-fx-opacity: 0.80;
|
||||
-fx-scale-x: 0.86;
|
||||
-fx-scale-y: 0.86;
|
||||
}
|
||||
|
||||
.toggle-icon-tiny .icon {
|
||||
-fx-fill: rgb(204.0, 204.0, 51.0);
|
||||
-fx-fill: -monet-on-surface;
|
||||
-fx-padding: 0;
|
||||
}
|
||||
|
||||
@@ -1379,7 +1610,8 @@
|
||||
}
|
||||
|
||||
.toggle-icon-tiny .jfx-rippler {
|
||||
-jfx-rippler-fill: -monet-primary;
|
||||
-jfx-rippler-disabled: true;
|
||||
-jfx-rippler-fill: transparent;
|
||||
-jfx-mask-type: CIRCLE;
|
||||
}
|
||||
|
||||
@@ -1393,10 +1625,31 @@
|
||||
-fx-min-height: -fx-announcement-close-button-size;
|
||||
-fx-background-radius: 50px;
|
||||
-fx-background-color: transparent;
|
||||
-fx-opacity: 1;
|
||||
-fx-scale-x: 1;
|
||||
-fx-scale-y: 1;
|
||||
}
|
||||
|
||||
.announcement-close-button:hover {
|
||||
-fx-background-color: -anthropic-icon-hover-wash;
|
||||
-fx-scale-x: 1.06;
|
||||
-fx-scale-y: 1.06;
|
||||
}
|
||||
|
||||
.announcement-close-button:pressed,
|
||||
.announcement-close-button:armed {
|
||||
-fx-background-color: rgba(217, 119, 87, 0.42);
|
||||
-fx-border-color: #C96442;
|
||||
-fx-border-width: 2px;
|
||||
-fx-border-radius: 50px;
|
||||
-fx-opacity: 0.84;
|
||||
-fx-scale-x: 0.90;
|
||||
-fx-scale-y: 0.90;
|
||||
}
|
||||
|
||||
.announcement-close-button .jfx-rippler {
|
||||
-jfx-rippler-fill: -monet-primary;
|
||||
-jfx-rippler-disabled: true;
|
||||
-jfx-rippler-fill: transparent;
|
||||
-jfx-mask-type: CIRCLE;
|
||||
}
|
||||
|
||||
@@ -1598,12 +1851,12 @@
|
||||
}
|
||||
|
||||
.jfx-combo-box-warning {
|
||||
-jfx-focus-color: #D34336;
|
||||
-jfx-unfocus-color: #D34336;
|
||||
-jfx-focus-color: -monet-error;
|
||||
-jfx-unfocus-color: -monet-error;
|
||||
}
|
||||
|
||||
.jfx-combo-box-warning .text {
|
||||
-fx-fill: #D34336;
|
||||
-fx-fill: -monet-error;
|
||||
}
|
||||
|
||||
/*.combo-box-popup .list-view .jfx-list-cell .jfx-rippler {*/
|
||||
@@ -1781,15 +2034,16 @@
|
||||
*******************************************************************************/
|
||||
|
||||
.jfx-decorator {
|
||||
-fx-decorator-color: -monet-primary-container;
|
||||
-fx-decorator-color: -monet-surface-container;
|
||||
}
|
||||
|
||||
.jfx-decorator-drawer {
|
||||
-fx-background-color: rgba(244, 244, 244, 0.5);
|
||||
-fx-background-color: -monet-surface-transparent-80;
|
||||
}
|
||||
|
||||
.jfx-decorator-title {
|
||||
-fx-font-size: 14;
|
||||
-fx-font-size: 16;
|
||||
-fx-font-weight: bold;
|
||||
}
|
||||
|
||||
.debug-border {
|
||||
@@ -1798,11 +2052,11 @@
|
||||
}
|
||||
|
||||
.content-background {
|
||||
-fx-background-color: -monet-surface-transparent-50;
|
||||
-fx-background-color: -monet-surface-transparent-80;
|
||||
}
|
||||
|
||||
.gray-background {
|
||||
-fx-background-color: -monet-surface-transparent-50;
|
||||
-fx-background-color: -monet-surface-container-low-transparent-80;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
@@ -1812,12 +2066,12 @@
|
||||
******************************************************************************/
|
||||
|
||||
.scroll-bar:vertical > .track-background, .scroll-bar:horizontal > .track-background {
|
||||
-fx-background-color: #F1F1F1;
|
||||
-fx-background-color: -anthropic-scrollbar-track;
|
||||
-fx-background-insets: 0.0;
|
||||
}
|
||||
|
||||
.scroll-bar:vertical .thumb, .scroll-bar:horizontal .thumb {
|
||||
-fx-background-color: #BCBCBC;
|
||||
-fx-background-color: -anthropic-scrollbar-thumb;
|
||||
-fx-background-insets: 0.0;
|
||||
-fx-background-radius: 1.0;
|
||||
}
|
||||
@@ -1831,7 +2085,7 @@
|
||||
}
|
||||
|
||||
.scroll-bar > .increment-button > .increment-arrow, .scroll-bar > .decrement-button > .decrement-arrow {
|
||||
-fx-background-color: rgb(150.0, 150.0, 150.0);
|
||||
-fx-background-color: -monet-on-surface-variant;
|
||||
}
|
||||
|
||||
.scroll-bar > .increment-button > .increment-arrow {
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
Copyright 2014-2025 Adobe (http://www.adobe.com/), with Reserved Font
|
||||
Name 'Source'. Source is a trademark of Adobe in the United States
|
||||
and/or other countries.
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License,
|
||||
Version 1.1.
|
||||
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font
|
||||
creation efforts of academic and linguistic communities, and to
|
||||
provide a free and open framework in which fonts may be shared and
|
||||
improved in partnership with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply to
|
||||
any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software
|
||||
components as distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to,
|
||||
deleting, or substituting -- in part or in whole -- any of the
|
||||
components of the Original Version, by changing formats or by porting
|
||||
the Font Software to a new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed,
|
||||
modify, redistribute, and sell modified and unmodified copies of the
|
||||
Font Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components, in
|
||||
Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the
|
||||
corresponding Copyright Holder. This restriction only applies to the
|
||||
primary font name as presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created using
|
||||
the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 523 B |
|
Before Width: | Height: | Size: 768 B |
|
Before Width: | Height: | Size: 441 B |
|
Before Width: | Height: | Size: 677 B |
|
Before Width: | Height: | Size: 656 B |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 468 B |
|
Before Width: | Height: | Size: 689 B |
|
Before Width: | Height: | Size: 276 B |
|
Before Width: | Height: | Size: 300 B |
|
Before Width: | Height: | Size: 236 B |
|
Before Width: | Height: | Size: 279 B |
|
Before Width: | Height: | Size: 264 B |
|
Before Width: | Height: | Size: 306 B |
|
Before Width: | Height: | Size: 185 B |
|
Before Width: | Height: | Size: 193 B |
|
Before Width: | Height: | Size: 222 B |
|
Before Width: | Height: | Size: 248 B |
|
Before Width: | Height: | Size: 253 B |
|
Before Width: | Height: | Size: 274 B |
|
Before Width: | Height: | Size: 602 B |
|
Before Width: | Height: | Size: 1003 B |
|
Before Width: | Height: | Size: 374 B |
|
Before Width: | Height: | Size: 627 B |
@@ -782,7 +782,6 @@ instance.empty.launch=No available instances.\nInstall the bundled server modpac
|
||||
instance.empty.launch.goto_download=Install Modpack
|
||||
instance.empty.hint=There are no Minecraft instances here.\nYou can try switching to another game directory or clicking here to install the server modpack.
|
||||
instance.game.all=All
|
||||
instance.game.april_fools=April Fools
|
||||
instance.game.old=Historical
|
||||
instance.game.release=Release
|
||||
instance.game.releases=Releases
|
||||
@@ -911,8 +910,6 @@ launcher.agreement=ToS and EULA
|
||||
launcher.agreement.accept=Accept
|
||||
launcher.agreement.decline=Decline
|
||||
launcher.agreement.hint=You must agree to the EULA to use this software.
|
||||
launcher.april_fools.switch_lzh=HMCL Launcher now supports Classical Chinese, do you want to switch to Classical Chinese mode?\nYou can switch back to English in "置設 → 貫用 → 文".
|
||||
launcher.april_fools.switch_lzh.confirm=Confirm switching to Classical Chinese mode?\nClick "OK" to exit HMCL and restart it in Classical Chinese mode; click "Cancel" to keep the current language and enter HMCL homepage.
|
||||
launcher.background=Background Image
|
||||
launcher.background.choose=Choose background image
|
||||
launcher.background.builtin=Built-in
|
||||
@@ -939,9 +936,9 @@ launcher.cache_directory.disabled=Disabled
|
||||
launcher.cache_directory.invalid=Failed to create a cache directory, falling back to default.
|
||||
launcher.crash=HMCL has encountered a fatal error! Please copy the following log and ask for help on GitHub or other Minecraft forums.
|
||||
launcher.crash.java_internal_error=HMCL has encountered a fatal error because your Java is corrupted. Please uninstall your Java and download a suitable Java <a href="https://bell-sw.com/pages/downloads/#downloads">here</a>.
|
||||
launcher.title=Hello Minecraft! Launcher v%s
|
||||
launcher.title.modified=Hello Minecraft! Launcher v%s (Modified)
|
||||
launcher.title.unofficial=Hello Minecraft! Launcher v%s (Unofficial Build)
|
||||
launcher.title=TDLM v%s
|
||||
launcher.title.modified=TDLM v%s (Modified)
|
||||
launcher.title.unofficial=TDLM v%s (Unofficial Build)
|
||||
launcher.update_java=Please update your Java version.
|
||||
|
||||
libraries.download=Downloading Libraries
|
||||
@@ -1538,7 +1535,6 @@ settings.launcher.brightness.auto=Follow System Settings
|
||||
settings.launcher.brightness.dark=Dark Mode
|
||||
settings.launcher.brightness.light=Light Mode
|
||||
settings.launcher.debug=Debug
|
||||
settings.launcher.disable_april_fools=Do not enable April Fools features
|
||||
settings.launcher.disable_auto_game_options=Do not switch game language
|
||||
settings.launcher.download=Download
|
||||
settings.launcher.download.threads=Download Threads
|
||||
@@ -1643,8 +1639,6 @@ system.architecture=Architecture
|
||||
system.operating_system=Operating System
|
||||
|
||||
task.background=Run in Background
|
||||
task.list=Tasks
|
||||
task.list.empty=No tasks
|
||||
task.queued=Waiting for other tasks to finish
|
||||
task.show=Show Progress
|
||||
|
||||
|
||||
@@ -602,7 +602,6 @@ instance.empty.add=裝伺服器整合包
|
||||
# instance.empty.launch.goto_download=
|
||||
instance.empty.hint=無畢裝者。\n君且更他戲夾,抑擊斯以裝伺服器整合包。
|
||||
instance.game.all=全覽
|
||||
instance.game.april_fools=愚人節
|
||||
instance.game.old=古典版
|
||||
instance.game.release=當版
|
||||
instance.game.releases=當版
|
||||
@@ -731,8 +730,6 @@ launcher.agreement=客契與免責告示
|
||||
launcher.agreement.accept=許
|
||||
launcher.agreement.decline=拒
|
||||
launcher.agreement.hint=將用而須許其客契與免責告示。
|
||||
# launcher.april_fools.switch_lzh=
|
||||
# launcher.april_fools.switch_lzh.confirm=
|
||||
launcher.background=繪圖
|
||||
launcher.background.choose=擇繪圖
|
||||
# launcher.background.builtin=
|
||||
@@ -759,9 +756,9 @@ launcher.cache_directory.disabled=禁 (恒用戲案夾之徑)
|
||||
launcher.cache_directory.invalid=自訂之快取夾不能立。既復初。
|
||||
launcher.crash=HMCL 有謬而弗能正。宜鈔下文而報謬于右下之鈕。
|
||||
launcher.crash.java_internal_error=HMCL 不能行,以爪哇壞也。宜去是爪哇,而擊<a href="https://bell-sw.com/pages/downloads/#downloads">此</a>以置爪哇之適者。
|
||||
launcher.title=Hello Minecraft! Launcher v%s
|
||||
launcher.title.modified=Hello Minecraft! Launcher %s (已改)
|
||||
launcher.title.unofficial=Hello Minecraft! Launcher %s (非官版)
|
||||
launcher.title=TDLM v%s
|
||||
launcher.title.modified=TDLM %s (已改)
|
||||
launcher.title.unofficial=TDLM %s (非官版)
|
||||
launcher.update_java=宜迭更爪哇。\n君可求助於 https://docs.hmcl.net/help.html。
|
||||
|
||||
libraries.download=引之所依
|
||||
@@ -1351,7 +1348,6 @@ settings.launcher.appearance=外觀
|
||||
# settings.launcher.brightness.dark=
|
||||
# settings.launcher.brightness.light=
|
||||
settings.launcher.debug=勘誤
|
||||
# settings.launcher.disable_april_fools=
|
||||
settings.launcher.disable_auto_game_options=弗自改戲文
|
||||
settings.launcher.download=引
|
||||
settings.launcher.download.threads=綫程數
|
||||
@@ -1455,8 +1451,6 @@ system.architecture=大構
|
||||
system.operating_system=械網
|
||||
|
||||
# task.background=
|
||||
# task.list=
|
||||
# task.list.empty=
|
||||
# task.queued=
|
||||
# task.show=
|
||||
|
||||
|
||||
@@ -605,7 +605,6 @@ instance.empty.launch=没有可启动的游戏。\n请安装自带的服务器
|
||||
instance.empty.launch.goto_download=安装整合包
|
||||
instance.empty.hint=没有已安装的游戏。\n你可以切换其他游戏文件夹,或者点击此处安装服务器整合包。
|
||||
instance.game.all=全部
|
||||
instance.game.april_fools=愚人节
|
||||
instance.game.old=远古版
|
||||
instance.game.release=正式版
|
||||
instance.game.releases=正式版
|
||||
@@ -734,8 +733,6 @@ launcher.agreement=用户协议与免责声明
|
||||
launcher.agreement.accept=同意
|
||||
launcher.agreement.decline=拒绝
|
||||
launcher.agreement.hint=同意本软件的用户协议与免责声明以使用本软件。
|
||||
launcher.april_fools.switch_lzh=HMCL 启动器现已支持文言文,我们诚邀你参与体验。是否要将界面语言切换至中文 (文言)?\n在切换语言后,你可以在“置設 → 貫用 → 文”中将语言切换回中文 (简体)。
|
||||
launcher.april_fools.switch_lzh.confirm=确认切换语言?\n点击“确定”后 HMCL 将自动重启并将界面语言切换至中文 (文言);点击“取消”将继续使用中文 (简体) 进入 HMCL 主页。
|
||||
launcher.background=背景图片
|
||||
launcher.background.choose=选择背景图片
|
||||
launcher.background.builtin=内置
|
||||
@@ -762,9 +759,9 @@ launcher.cache_directory.disabled=禁用 (总是使用游戏文件夹路径)
|
||||
launcher.cache_directory.invalid=无法创建自定义的缓存文件夹。已经恢复到默认设置。
|
||||
launcher.crash=HMCL 遇到了无法处理的错误。请复制下列内容并点击右下角的按钮反馈问题。
|
||||
launcher.crash.java_internal_error=HMCL 由于当前 Java 损坏而无法继续运行。请卸载当前 Java,点击 <a href="https://bell-sw.com/pages/downloads/#downloads">此处</a> 安装合适的 Java 版本。
|
||||
launcher.title=Hello Minecraft! Launcher %s
|
||||
launcher.title.modified=Hello Minecraft! Launcher %s (已修改)
|
||||
launcher.title.unofficial=Hello Minecraft! Launcher %s (非官方构建)
|
||||
launcher.title=TDLM %s
|
||||
launcher.title.modified=TDLM %s (已修改)
|
||||
launcher.title.unofficial=TDLM %s (非官方构建)
|
||||
launcher.update_java=请更新你的 Java。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。
|
||||
|
||||
libraries.download=下载依赖库
|
||||
@@ -1355,7 +1352,6 @@ settings.launcher.brightness.auto=跟随系统设置
|
||||
settings.launcher.brightness.dark=深色模式
|
||||
settings.launcher.brightness.light=浅色模式
|
||||
settings.launcher.debug=调试
|
||||
settings.launcher.disable_april_fools=不启用愚人节功能
|
||||
settings.launcher.disable_auto_game_options=不自动切换游戏语言
|
||||
settings.launcher.download=下载
|
||||
settings.launcher.download.threads=下载线程数
|
||||
@@ -1460,8 +1456,6 @@ system.architecture=架构
|
||||
system.operating_system=操作系统
|
||||
|
||||
task.background=在后台运行
|
||||
task.list=任务列表
|
||||
task.list.empty=当前没有任务。
|
||||
task.queued=等待其他任务完成
|
||||
task.show=显示进度
|
||||
|
||||
|
||||
@@ -599,7 +599,6 @@ instance.empty.launch=沒有可啟動的遊戲。\n請安裝內建的伺服器
|
||||
instance.empty.launch.goto_download=安裝整合包
|
||||
instance.empty.hint=沒有已安裝的遊戲。\n你可以切換其他遊戲目錄,或者點擊此處安裝伺服器整合包。
|
||||
instance.game.all=全部
|
||||
instance.game.april_fools=愚人節
|
||||
instance.game.old=遠古版
|
||||
instance.game.release=正式版
|
||||
instance.game.releases=正式版
|
||||
@@ -728,8 +727,6 @@ launcher.agreement=使用者協議與免責宣告
|
||||
launcher.agreement.accept=同意
|
||||
launcher.agreement.decline=拒絕
|
||||
launcher.agreement.hint=同意本軟體的使用者協議與免責宣告以使用本軟體。
|
||||
launcher.april_fools.switch_lzh=HMCL 啟動器現已支援文言文,我們誠邀你參與體驗。是否要將介面語言切換至中文 (文言)?\n在切換語言後,你可以在「置設 → 貫用 → 文」中將語言切換回中文 (繁體)。
|
||||
launcher.april_fools.switch_lzh.confirm=確認切換語言?\n點擊「確定」後 HMCL 將自動重新啟動並將介面語言切換至中文 (文言);點擊「取消」將繼續使用中文 (繁體) 進入 HMCL 主頁。
|
||||
launcher.background=背景圖片
|
||||
launcher.background.choose=選取背景圖片
|
||||
launcher.background.builtin=內建
|
||||
@@ -756,9 +753,9 @@ launcher.cache_directory.disabled=停用
|
||||
launcher.cache_directory.invalid=無法建立自訂的快取目錄。已還原至預設設定。
|
||||
launcher.crash=HMCL 遇到了無法處理的錯誤。請複製下列內容並透過 GitHub 或其他 Minecraft 論壇回報問題。
|
||||
launcher.crash.java_internal_error=HMCL 由於目前 Java 損壞而無法繼續執行。請移除目前 Java,點擊 <a href="https://bell-sw.com/pages/downloads/#downloads">此處</a> 安裝合適的 Java 版本。
|
||||
launcher.title=Hello Minecraft! Launcher %s
|
||||
launcher.title.modified=Hello Minecraft! Launcher %s (已修改)
|
||||
launcher.title.unofficial=Hello Minecraft! Launcher %s (非官方建置)
|
||||
launcher.title=TDLM %s
|
||||
launcher.title.modified=TDLM %s (已修改)
|
||||
launcher.title.unofficial=TDLM %s (非官方建置)
|
||||
launcher.update_java=請更新你的 Java
|
||||
|
||||
libraries.download=下載依賴庫
|
||||
@@ -1349,7 +1346,6 @@ settings.launcher.brightness.auto=跟隨系統設定
|
||||
settings.launcher.brightness.dark=深色模式
|
||||
settings.launcher.brightness.light=淺色模式
|
||||
settings.launcher.debug=除錯
|
||||
settings.launcher.disable_april_fools=不啟用愚人節功能
|
||||
settings.launcher.disable_auto_game_options=不自動切換遊戲語言
|
||||
settings.launcher.download=下載
|
||||
settings.launcher.download.threads=下載執行緒數
|
||||
@@ -1454,8 +1450,6 @@ system.architecture=架構
|
||||
system.operating_system=作業系統
|
||||
|
||||
task.background=在背景執行
|
||||
task.list=任務列表
|
||||
task.list.empty=目前沒有任務。
|
||||
task.queued=等待其他任務完成
|
||||
task.show=顯示進度
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import com.google.gson.JsonParser;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.collections.ObservableList;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.game.GameInstanceID;
|
||||
import org.jackhuang.hmcl.game.GameInstanceManifest;
|
||||
import org.jackhuang.hmcl.game.GameRepositoryDraft;
|
||||
|
||||
@@ -15,6 +15,8 @@ public final class ThemeColorTest {
|
||||
@Test
|
||||
public void testOf() {
|
||||
assertEquals(new ThemeColor("#AABBCC", Color.web("#AABBCC")), ThemeColor.of("#AABBCC"));
|
||||
assertEquals(new ThemeColor("anthropic_orange", Color.web("#D97757")), ThemeColor.of("anthropic_orange"));
|
||||
assertEquals(new ThemeColor("stone", Color.web("#3D453C")), ThemeColor.of("stone"));
|
||||
assertEquals(new ThemeColor("blue", Color.web("#5C6BC0")), ThemeColor.of("blue"));
|
||||
assertEquals(new ThemeColor("darker_blue", Color.web("#283593")), ThemeColor.of("darker_blue"));
|
||||
assertEquals(new ThemeColor("green", Color.web("#43A047")), ThemeColor.of("green"));
|
||||
|
||||
@@ -2,14 +2,12 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH AdditionRef-HMCL
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.controls.JFXSpinner;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.ButtonBase;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.shape.SVGPath;
|
||||
import org.jackhuang.hmcl.setting.LauncherSettings;
|
||||
import org.jackhuang.hmcl.setting.SettingsManager;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
@@ -17,8 +15,6 @@ import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.task.TaskListener;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.DialogUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.TaskManagerPage;
|
||||
import org.jackhuang.hmcl.ui.decorator.Decorator;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.TaskManager;
|
||||
@@ -34,7 +30,6 @@ import org.junit.jupiter.params.provider.ValueSource;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
@@ -73,7 +68,7 @@ final class TaskExecutorDialogPaneTest {
|
||||
field.set(null, previousSettings);
|
||||
}
|
||||
|
||||
/// Verifies dialog reuse and the task button's idle, running, and pending presentations.
|
||||
/// Verifies dialog reuse and TaskManager status transitions without a task-list chrome button.
|
||||
@Test
|
||||
void restoresThroughMainWindow() throws Exception {
|
||||
onFxThread(() -> {
|
||||
@@ -86,25 +81,17 @@ final class TaskExecutorDialogPaneTest {
|
||||
TaskExecutorDialogPane pane = new TaskExecutorDialogPane(TaskCancellationAction.NORMAL);
|
||||
TaskExecutorDialogPane second = new TaskExecutorDialogPane(TaskCancellationAction.NORMAL);
|
||||
try {
|
||||
ButtonBase tasksButton = decorator.getRoot().lookupAll(".jfx-decorator-button").stream()
|
||||
.filter(node -> i18n("task.list").equals(node.getAccessibleText()))
|
||||
.map(ButtonBase.class::cast).findFirst().orElseThrow();
|
||||
Node icon = Objects.requireNonNull(tasksButton.getGraphic());
|
||||
ControlledExecutor executor = new ControlledExecutor();
|
||||
pane.setExecutor(executor, false);
|
||||
executor.start();
|
||||
Controllers.dialog(pane);
|
||||
assertEquals(TaskManager.Status.IDLE, TaskManager.getInstance().statusProperty().get());
|
||||
assertSame(icon, tasksButton.getGraphic());
|
||||
Node progress = pane.getCenter();
|
||||
pane.runInBackground();
|
||||
assertEquals(TaskManager.Status.RUNNING, TaskManager.getInstance().statusProperty().get());
|
||||
Node spinner = Objects.requireNonNull(tasksButton.getGraphic());
|
||||
assertInstanceOf(JFXSpinner.class, spinner);
|
||||
pane.restore();
|
||||
pane.restore();
|
||||
assertEquals(TaskManager.Status.IDLE, TaskManager.getInstance().statusProperty().get());
|
||||
assertSame(icon, tasksButton.getGraphic());
|
||||
assertTrue(DialogUtils.isShowing(pane));
|
||||
assertSame(progress, pane.getCenter());
|
||||
@Nullable JFXDialogPane stack = (JFXDialogPane) pane.getProperties()
|
||||
@@ -119,38 +106,26 @@ final class TaskExecutorDialogPaneTest {
|
||||
secondExecutor.start();
|
||||
Controllers.dialog(second);
|
||||
second.runInBackground();
|
||||
Node nextSpinner = Objects.requireNonNull(tasksButton.getGraphic());
|
||||
assertInstanceOf(JFXSpinner.class, nextSpinner);
|
||||
assertNotSame(spinner, nextSpinner);
|
||||
pane.runInBackground();
|
||||
pane.restore();
|
||||
assertEquals(TaskManager.Status.RUNNING, TaskManager.getInstance().statusProperty().get());
|
||||
assertSame(nextSpinner, tasksButton.getGraphic());
|
||||
|
||||
AtomicInteger actions = new AtomicInteger();
|
||||
pane.runInBackground();
|
||||
assertFalse(pane.runOrDeferCompletionAction("Install", "Ready to install", actions::incrementAndGet));
|
||||
assertEquals(TaskManager.Status.RUNNING, TaskManager.getInstance().statusProperty().get());
|
||||
assertSame(nextSpinner, tasksButton.getGraphic());
|
||||
second.restore();
|
||||
assertEquals(TaskManager.Status.PENDING, TaskManager.getInstance().statusProperty().get());
|
||||
SVGPath pendingIcon = assertInstanceOf(SVGPath.class, tasksButton.getGraphic());
|
||||
assertEquals(SVG.PENDING.getPath(), pendingIcon.getContent());
|
||||
second.runInBackground();
|
||||
assertEquals(TaskManager.Status.RUNNING, TaskManager.getInstance().statusProperty().get());
|
||||
Node resumedSpinner = assertInstanceOf(JFXSpinner.class, tasksButton.getGraphic());
|
||||
assertNotSame(nextSpinner, resumedSpinner);
|
||||
pane.runPendingAction();
|
||||
assertEquals(1, actions.get());
|
||||
assertEquals(TaskManager.Status.RUNNING, TaskManager.getInstance().statusProperty().get());
|
||||
assertSame(resumedSpinner, tasksButton.getGraphic());
|
||||
|
||||
assertFalse(second.runOrDeferCompletionAction("Install", "Ready to install", actions::incrementAndGet));
|
||||
assertEquals(TaskManager.Status.PENDING, TaskManager.getInstance().statusProperty().get());
|
||||
assertEquals(SVG.PENDING.getPath(), assertInstanceOf(SVGPath.class, tasksButton.getGraphic()).getContent());
|
||||
second.setExecutor(null);
|
||||
assertEquals(TaskManager.Status.IDLE, TaskManager.getInstance().statusProperty().get());
|
||||
assertSame(icon, tasksButton.getGraphic());
|
||||
} finally {
|
||||
DialogUtils.close(pane);
|
||||
pane.setExecutor(null);
|
||||
@@ -351,15 +326,10 @@ final class TaskExecutorDialogPaneTest {
|
||||
});
|
||||
}
|
||||
|
||||
/// Verifies a visible task list switches from progress to a retained, single-use install action.
|
||||
/// Verifies a background task retains a single-use install action until runPendingAction.
|
||||
@Test
|
||||
void backgroundCompletionWaitsForInstallAction() throws Exception {
|
||||
AtomicInteger installations = new AtomicInteger();
|
||||
TaskManagerPage page = onFxThread(() -> {
|
||||
TaskManagerPage result = new TaskManagerPage();
|
||||
new Scene(result, 600, 400);
|
||||
return result;
|
||||
});
|
||||
TaskExecutorDialogPane pane = onFxThread(() -> {
|
||||
TaskExecutorDialogPane dialog = new TaskExecutorDialogPane(TaskCancellationAction.NORMAL);
|
||||
dialog.setTitle("Example modpack");
|
||||
@@ -370,9 +340,6 @@ final class TaskExecutorDialogPaneTest {
|
||||
DialogUtils.show(container, dialog);
|
||||
executor.start();
|
||||
dialog.runInBackground();
|
||||
page.applyCss();
|
||||
page.layout();
|
||||
assertFalse(page.lookupAll(".jfx-spinner").isEmpty());
|
||||
assertFalse(dialog.runOrDeferCompletionAction(i18n("button.install"), i18n("modpack.install.ready"),
|
||||
installations::incrementAndGet));
|
||||
executor.stop(true);
|
||||
@@ -384,17 +351,7 @@ final class TaskExecutorDialogPaneTest {
|
||||
assertFalse(DialogUtils.isShowing(pane));
|
||||
assertTrue(TaskManager.getInstance().getTasks().contains(pane));
|
||||
assertEquals(TaskManager.Status.PENDING, TaskManager.getInstance().statusProperty().get());
|
||||
page.applyCss();
|
||||
page.layout();
|
||||
ButtonBase install = page.lookupAll(".jfx-button").stream()
|
||||
.filter(ButtonBase.class::isInstance)
|
||||
.map(ButtonBase.class::cast)
|
||||
.filter(button -> i18n("button.install").equals(button.getText()))
|
||||
.findFirst().orElseThrow();
|
||||
assertTrue(page.lookupAll(".label").stream().anyMatch(node -> node instanceof Label label
|
||||
&& i18n("modpack.install.ready").equals(label.getText())));
|
||||
assertTrue(page.lookupAll(".jfx-spinner").isEmpty());
|
||||
install.fire();
|
||||
assertNotNull(pane.pendingActionProperty().get());
|
||||
pane.runPendingAction();
|
||||
assertEquals(1, installations.get());
|
||||
assertNull(pane.pendingActionProperty().get());
|
||||
|
||||
@@ -18,9 +18,6 @@
|
||||
"language": {
|
||||
"type": "string"
|
||||
},
|
||||
"disableAprilFools": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"commonDirectoryType": {
|
||||
"enum": [
|
||||
"DEFAULT",
|
||||
|
||||