Skip to content

Commit 5d54281

Browse files
committed
Merge branch 'main' into feat/resourcepack-desc
2 parents d4d64c9 + f5768f2 commit 5d54281

35 files changed

Lines changed: 1194 additions & 138 deletions

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,5 @@ language-subtag-registry
6969
# AI
7070
/.gemini/
7171
!/.gemini/config.yaml
72+
.codex
73+
/.gradle-*/

HMCL/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ dependencies {
5656
implementation(project(":HMCLCore"))
5757
implementation(project(":HMCLBoot"))
5858
implementation("libs:JFoenix")
59-
implementation(libs.twelvemonkeys.imageio.webp)
59+
implementation(libs.jwebp)
6060
implementation(libs.fxsvgimage)
6161
implementation(libs.java.info)
6262
implementation(libs.monet.fx)
@@ -195,7 +195,7 @@ tasks.shadowJar {
195195
exclude("META-INF/services/javax.imageio.spi.ImageInputStreamSpi")
196196

197197
listOf(
198-
"aix-*", "sunos-*", "openbsd-*", "dragonflybsd-*", "freebsd-*", "linux-*", "darwin-*",
198+
"aix-*", "sunos-*", "openbsd-*", "dragonflybsd-*", "freebsd-*", "linux-*",
199199
"*-ppc", "*-ppc64le", "*-s390x", "*-armel",
200200
).forEach { exclude("com/sun/jna/$it/**") }
201201

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
//
2+
// Source code recreated from a .class file by IntelliJ IDEA
3+
// (powered by Fernflower decompiler)
4+
//
5+
6+
package com.jfoenix.skins;
7+
8+
import com.jfoenix.adapters.skins.ComboBoxListViewSkin;
9+
import com.jfoenix.concurrency.JFXUtilities;
10+
import com.jfoenix.controls.JFXComboBox;
11+
import com.jfoenix.transitions.CachedTransition;
12+
import javafx.animation.*;
13+
import javafx.application.Platform;
14+
import javafx.beans.binding.Bindings;
15+
import javafx.beans.binding.BooleanBinding;
16+
import javafx.beans.property.ObjectProperty;
17+
import javafx.beans.property.SimpleObjectProperty;
18+
import javafx.geometry.Insets;
19+
import javafx.geometry.Pos;
20+
import javafx.scene.control.ComboBoxBase;
21+
import javafx.scene.layout.*;
22+
import javafx.scene.paint.Color;
23+
import javafx.scene.paint.Paint;
24+
import javafx.scene.text.Text;
25+
import javafx.scene.transform.Scale;
26+
import javafx.util.Duration;
27+
28+
public class JFXComboBoxListViewSkin<T> extends ComboBoxListViewSkin<T> {
29+
protected final ObjectProperty<Paint> promptTextFill;
30+
private boolean invalid = true;
31+
private final StackPane customPane;
32+
private final StackPane line = new StackPane();
33+
private final StackPane focusedLine = new StackPane();
34+
private final Text promptText = new Text();
35+
private final double initScale = 0.05;
36+
private final Scale scale;
37+
private final Timeline linesAnimation;
38+
private ParallelTransition transition;
39+
private CachedTransition promptTextUpTransition;
40+
private CachedTransition promptTextDownTransition;
41+
private CachedTransition promptTextColorTransition;
42+
private final Scale promptTextScale;
43+
private Paint oldPromptTextFill;
44+
private final BooleanBinding usePromptText;
45+
46+
public JFXComboBoxListViewSkin(JFXComboBox<T> comboBox) {
47+
super(comboBox);
48+
this.scale = new Scale(this.initScale, 1.0F);
49+
this.linesAnimation = new Timeline(new KeyFrame(Duration.ZERO, new KeyValue(this.scale.xProperty(), this.initScale, Interpolator.EASE_BOTH), new KeyValue(this.focusedLine.opacityProperty(), 0, Interpolator.EASE_BOTH)), new KeyFrame(Duration.millis(1.0F), new KeyValue(this.focusedLine.opacityProperty(), 1, Interpolator.EASE_BOTH)), new KeyFrame(Duration.millis(160.0F), new KeyValue(this.scale.xProperty(), 1, Interpolator.EASE_BOTH)));
50+
this.promptTextScale = new Scale(1.0F, 1.0F, 0.0F, 0.0F);
51+
this.promptTextFill = new SimpleObjectProperty(Color.valueOf("#B2B2B2"));
52+
this.usePromptText = Bindings.createBooleanBinding(this::usePromptText, ((JFXComboBox) this.getSkinnable()).valueProperty(), this.getSkinnable().promptTextProperty());
53+
this.getArrowButton().setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, null, null)));
54+
this.promptText.textProperty().bind(comboBox.promptTextProperty());
55+
this.promptText.fillProperty().bind(this.promptTextFill);
56+
this.promptText.getStyleClass().addAll("text", "prompt-text");
57+
this.promptText.getTransforms().add(this.promptTextScale);
58+
if (!comboBox.isLabelFloat()) {
59+
this.promptText.visibleProperty().bind(this.usePromptText);
60+
}
61+
62+
this.customPane = new StackPane();
63+
this.customPane.setMouseTransparent(true);
64+
this.customPane.getStyleClass().add("combo-box-button-container");
65+
this.customPane.backgroundProperty().bindBidirectional(this.getSkinnable().backgroundProperty());
66+
this.customPane.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, null, null)));
67+
this.customPane.getChildren().add(this.promptText);
68+
this.getChildren().add(0, this.customPane);
69+
StackPane.setAlignment(this.promptText, Pos.CENTER_LEFT);
70+
this.line.getStyleClass().add("input-line");
71+
this.focusedLine.getStyleClass().add("input-focused-line");
72+
this.getChildren().add(this.line);
73+
this.getChildren().add(this.focusedLine);
74+
this.line.setPrefHeight(1.0F);
75+
this.line.setTranslateY(1.0F);
76+
this.line.setManaged(false);
77+
this.line.setBackground(new Background(new BackgroundFill(((JFXComboBox) this.getSkinnable()).getUnFocusColor(), CornerRadii.EMPTY, Insets.EMPTY)));
78+
if (this.getSkinnable().isDisabled()) {
79+
this.line.setBorder(new Border(new BorderStroke(((JFXComboBox) this.getSkinnable()).getUnFocusColor(), BorderStrokeStyle.DASHED, CornerRadii.EMPTY, new BorderWidths(1.0F))));
80+
this.line.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY)));
81+
}
82+
83+
this.focusedLine.setPrefHeight(2.0F);
84+
this.focusedLine.setTranslateY(0.0F);
85+
this.focusedLine.setBackground(new Background(new BackgroundFill(((JFXComboBox) this.getSkinnable()).getFocusColor(), CornerRadii.EMPTY, Insets.EMPTY)));
86+
this.focusedLine.setOpacity(0.0F);
87+
this.focusedLine.getTransforms().add(this.scale);
88+
this.focusedLine.setManaged(false);
89+
if (comboBox.isEditable()) {
90+
comboBox.getEditor().setStyle("-fx-background-color:TRANSPARENT;-fx-padding: 4 8 4 8");
91+
comboBox.getEditor().promptTextProperty().unbind();
92+
comboBox.getEditor().setPromptText(null);
93+
comboBox.getEditor().textProperty().addListener((o, oldVal, newVal) -> {
94+
this.usePromptText.invalidate();
95+
comboBox.setValue(this.getConverter().fromString(newVal));
96+
});
97+
}
98+
99+
comboBox.labelFloatProperty().addListener((o, oldVal, newVal) -> {
100+
if (newVal) {
101+
this.promptText.visibleProperty().unbind();
102+
JFXUtilities.runInFX(() -> this.createFloatingAnimation());
103+
} else {
104+
this.promptText.visibleProperty().bind(this.usePromptText);
105+
}
106+
107+
this.createFocusTransition();
108+
});
109+
comboBox.focusColorProperty().addListener((o, oldVal, newVal) -> {
110+
if (newVal != null) {
111+
this.focusedLine.setBackground(new Background(new BackgroundFill(newVal, CornerRadii.EMPTY, Insets.EMPTY)));
112+
if (((JFXComboBox) this.getSkinnable()).isLabelFloat()) {
113+
this.promptTextColorTransition = new CachedTransition(this.customPane, new Timeline(new KeyFrame(Duration.millis(1300.0F), new KeyValue(this.promptTextFill, newVal, Interpolator.EASE_BOTH)))) {
114+
{
115+
this.setDelay(Duration.millis(0.0F));
116+
this.setCycleDuration(Duration.millis(160.0F));
117+
}
118+
119+
protected void starting() {
120+
super.starting();
121+
JFXComboBoxListViewSkin.this.oldPromptTextFill = JFXComboBoxListViewSkin.this.promptTextFill.get();
122+
}
123+
};
124+
this.transition = null;
125+
}
126+
}
127+
128+
});
129+
comboBox.unFocusColorProperty().addListener((o, oldVal, newVal) -> {
130+
if (newVal != null) {
131+
this.line.setBackground(new Background(new BackgroundFill(newVal, CornerRadii.EMPTY, Insets.EMPTY)));
132+
}
133+
134+
});
135+
comboBox.disabledProperty().addListener((o, oldVal, newVal) -> {
136+
this.line.setBorder(newVal ? new Border(new BorderStroke(((JFXComboBox) this.getSkinnable()).getUnFocusColor(), BorderStrokeStyle.DASHED, CornerRadii.EMPTY, new BorderWidths(this.line.getHeight()))) : Border.EMPTY);
137+
this.line.setBackground(new Background(new BackgroundFill(newVal ? Color.TRANSPARENT : ((JFXComboBox) this.getSkinnable()).getUnFocusColor(), CornerRadii.EMPTY, Insets.EMPTY)));
138+
});
139+
comboBox.focusedProperty().addListener((o, oldVal, newVal) -> {
140+
if (newVal) {
141+
this.focus();
142+
} else {
143+
this.unFocus();
144+
}
145+
146+
});
147+
comboBox.valueProperty().addListener((o, oldVal, newVal) -> {
148+
if (((JFXComboBox) this.getSkinnable()).isLabelFloat()) {
149+
this.animateFloatingLabel(newVal != null && !newVal.toString().isEmpty());
150+
}
151+
152+
});
153+
}
154+
155+
protected void layoutChildren(double x, double y, double w, double h) {
156+
super.layoutChildren(x, y, w, h);
157+
this.customPane.resizeRelocate(x, y, w, h);
158+
if (this.invalid) {
159+
this.invalid = false;
160+
if (!this.getSkinnable().isEditable()) {
161+
Text javaPromptText = (Text) super.getDisplayNode().lookup(".text");
162+
if (javaPromptText != null) {
163+
this.promptTextFill.set(javaPromptText.getFill());
164+
}
165+
}
166+
167+
this.createFloatingAnimation();
168+
if (((ComboBoxBase) this.getSkinnable()).getValue() != null) {
169+
this.animateFloatingLabel(true);
170+
}
171+
}
172+
173+
this.focusedLine.resizeRelocate(x, this.getSkinnable().getHeight(), w, this.focusedLine.prefHeight(-1.0F));
174+
this.line.resizeRelocate(x, this.getSkinnable().getHeight(), w, this.line.prefHeight(-1.0F));
175+
this.scale.setPivotX(w / (double) 2.0F);
176+
}
177+
178+
private void createFloatingAnimation() {
179+
this.promptTextUpTransition = new CachedTransition(this.customPane, new Timeline(new KeyFrame(Duration.millis(1300.0F), new KeyValue(this.promptText.translateYProperty(), -this.customPane.getHeight() + 6.05, Interpolator.EASE_BOTH), new KeyValue(this.promptTextScale.xProperty(), 0.85, Interpolator.EASE_BOTH), new KeyValue(this.promptTextScale.yProperty(), 0.85, Interpolator.EASE_BOTH)))) {
180+
{
181+
this.setDelay(Duration.millis(0.0F));
182+
this.setCycleDuration(Duration.millis(240.0F));
183+
}
184+
};
185+
this.promptTextColorTransition = new CachedTransition(this.customPane, new Timeline(new KeyFrame(Duration.millis(1300.0F), new KeyValue(this.promptTextFill, ((JFXComboBox) this.getSkinnable()).getFocusColor(), Interpolator.EASE_BOTH)))) {
186+
{
187+
this.setDelay(Duration.millis(0.0F));
188+
this.setCycleDuration(Duration.millis(160.0F));
189+
}
190+
191+
protected void starting() {
192+
super.starting();
193+
JFXComboBoxListViewSkin.this.oldPromptTextFill = JFXComboBoxListViewSkin.this.promptTextFill.get();
194+
}
195+
};
196+
this.promptTextDownTransition = new CachedTransition(this.customPane, new Timeline(new KeyFrame(Duration.millis(1300.0F), new KeyValue(this.promptText.translateYProperty(), 0, Interpolator.EASE_BOTH), new KeyValue(this.promptTextScale.xProperty(), 1, Interpolator.EASE_BOTH), new KeyValue(this.promptTextScale.yProperty(), 1, Interpolator.EASE_BOTH)))) {
197+
{
198+
this.setDelay(Duration.millis(0.0F));
199+
this.setCycleDuration(Duration.millis(240.0F));
200+
}
201+
};
202+
this.promptTextDownTransition.setOnFinished((finish) -> {
203+
this.promptText.setTranslateY(0.0F);
204+
this.promptTextScale.setX(1.0F);
205+
this.promptTextScale.setY(1.0F);
206+
});
207+
}
208+
209+
private void focus() {
210+
if (this.transition == null) {
211+
this.createFocusTransition();
212+
}
213+
214+
this.transition.play();
215+
}
216+
217+
private void animateFloatingLabel(boolean up) {
218+
if (this.promptText == null) {
219+
Platform.runLater(() -> this.animateFloatingLabel(up));
220+
} else {
221+
if (this.transition != null) {
222+
this.transition.stop();
223+
this.transition.getChildren().remove(this.promptTextUpTransition);
224+
this.transition.getChildren().remove(this.promptTextColorTransition);
225+
this.transition = null;
226+
}
227+
228+
if (up && this.promptText.getTranslateY() == (double) 0.0F) {
229+
this.promptTextDownTransition.stop();
230+
this.promptTextUpTransition.play();
231+
if (this.getSkinnable().isFocused()) {
232+
this.promptTextColorTransition.play();
233+
}
234+
} else if (!up) {
235+
this.promptTextUpTransition.stop();
236+
if (this.getSkinnable().isFocused()) {
237+
this.promptTextFill.set(this.oldPromptTextFill);
238+
}
239+
240+
this.promptTextDownTransition.play();
241+
}
242+
}
243+
244+
}
245+
246+
private void createFocusTransition() {
247+
this.transition = new ParallelTransition();
248+
if (((JFXComboBox) this.getSkinnable()).isLabelFloat()) {
249+
this.transition.getChildren().add(this.promptTextUpTransition);
250+
this.transition.getChildren().add(this.promptTextColorTransition);
251+
}
252+
253+
this.transition.getChildren().add(this.linesAnimation);
254+
}
255+
256+
private void unFocus() {
257+
if (this.transition != null) {
258+
this.transition.stop();
259+
}
260+
261+
this.scale.setX(this.initScale);
262+
this.focusedLine.setOpacity(0.0F);
263+
if (((JFXComboBox) this.getSkinnable()).isLabelFloat() && this.oldPromptTextFill != null) {
264+
this.promptTextFill.set(this.oldPromptTextFill);
265+
if (this.usePromptText()) {
266+
this.promptTextDownTransition.play();
267+
}
268+
}
269+
270+
}
271+
272+
private boolean usePromptText() {
273+
Object txt = ((JFXComboBox) this.getSkinnable()).getValue();
274+
String promptTxt = this.getSkinnable().getPromptText();
275+
return (txt == null || txt.toString().isEmpty()) && promptTxt != null && !promptTxt.isEmpty() && !this.promptTextFill.get().equals(Color.TRANSPARENT);
276+
}
277+
}

HMCL/src/main/java/org/jackhuang/hmcl/Launcher.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.jackhuang.hmcl.task.Schedulers;
4040
import org.jackhuang.hmcl.ui.Controllers;
4141
import org.jackhuang.hmcl.ui.FXUtils;
42+
import org.jackhuang.hmcl.theme.Themes;
4243
import org.jackhuang.hmcl.upgrade.UpdateChecker;
4344
import org.jackhuang.hmcl.upgrade.UpdateHandler;
4445
import org.jackhuang.hmcl.util.CrashReporter;
@@ -138,6 +139,9 @@ public void start(Stage primaryStage) {
138139
Platform.setImplicitExit(false);
139140
Controllers.initialize(primaryStage);
140141

142+
if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS)
143+
Themes.applyNativeDarkMode(primaryStage);
144+
141145
UpdateChecker.init();
142146

143147
primaryStage.show();

HMCL/src/main/java/org/jackhuang/hmcl/theme/Themes.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,9 @@
3636
import org.glavo.monetfx.beans.property.ReadOnlyColorSchemeProperty;
3737
import org.glavo.monetfx.beans.property.SimpleColorSchemeProperty;
3838
import org.jackhuang.hmcl.ui.FXUtils;
39-
import org.jackhuang.hmcl.util.io.FileUtils;
39+
import org.jackhuang.hmcl.ui.MacOSNativeUtils;
4040
import org.jackhuang.hmcl.ui.WindowsNativeUtils;
41+
import org.jackhuang.hmcl.util.io.FileUtils;
4142
import org.jackhuang.hmcl.util.platform.NativeUtils;
4243
import org.jackhuang.hmcl.util.platform.OSVersion;
4344
import org.jackhuang.hmcl.util.platform.OperatingSystem;
@@ -233,6 +234,11 @@ public void handle(WindowEvent event) {
233234
}
234235
});
235236
}
237+
} else if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS && MacOSNativeUtils.isSupported()) {
238+
MacOSNativeUtils.setAppearance(darkModeProperty().get());
239+
240+
ChangeListener<Boolean> listener = FXUtils.onWeakChange(Themes.darkModeProperty(), MacOSNativeUtils::setAppearance);
241+
stage.getProperties().put("Themes.applyNativeDarkMode.listener", listener);
236242
}
237243
}
238244

HMCL/src/main/java/org/jackhuang/hmcl/ui/HTMLRenderer.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
* @author Glavo
3838
*/
3939
public final class HTMLRenderer {
40+
private static final String INDENT = " ";
4041
private static URI resolveLink(Node linkNode) {
4142
String href = linkNode.absUrl("href");
4243
if (href.isEmpty())
@@ -57,6 +58,7 @@ private static URI resolveLink(Node linkNode) {
5758
private boolean underline;
5859
private boolean strike;
5960
private boolean highlight;
61+
private int indentLevel;
6062
private String headerLevel;
6163
private Node hyperlink;
6264

@@ -218,7 +220,7 @@ public void appendNode(Node node) {
218220
appendImage(node);
219221
break;
220222
case "li":
221-
appendText("\n \u2022 ");
223+
appendText("\n" + INDENT.repeat(indentLevel) + " \u2022 ");
222224
break;
223225
case "dt":
224226
appendText(" ");
@@ -237,11 +239,14 @@ public void appendNode(Node node) {
237239
}
238240

239241
if (node.childNodeSize() > 0) {
242+
boolean isLiNode = "li".equals(name);
243+
if (isLiNode) indentLevel++;
240244
pushNode(node);
241245
for (Node childNode : node.childNodes()) {
242246
appendNode(childNode);
243247
}
244248
popNode();
249+
if (isLiNode) indentLevel--;
245250
}
246251

247252
switch (name) {

0 commit comments

Comments
 (0)