diff --git a/HMCL/build.gradle.kts b/HMCL/build.gradle.kts
index 7718b3969..6202b8282 100644
--- a/HMCL/build.gradle.kts
+++ b/HMCL/build.gradle.kts
@@ -58,6 +58,7 @@ dependencies {
implementation("libs:JFoenix")
implementation(libs.twelvemonkeys.imageio.webp)
implementation(libs.java.info)
+ implementation(libs.monet.fx)
if (launcherExe.isBlank()) {
implementation(libs.hmclauncher)
diff --git a/HMCL/src/main/java/com/jfoenix/controls/JFXToggleButton.java b/HMCL/src/main/java/com/jfoenix/controls/JFXToggleButton.java
new file mode 100644
index 000000000..60187da4e
--- /dev/null
+++ b/HMCL/src/main/java/com/jfoenix/controls/JFXToggleButton.java
@@ -0,0 +1,373 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.jfoenix.controls;
+
+import com.jfoenix.skins.JFXToggleButtonSkin;
+import javafx.css.*;
+import javafx.css.converter.BooleanConverter;
+import javafx.css.converter.PaintConverter;
+import javafx.scene.control.Skin;
+import javafx.scene.control.ToggleButton;
+import javafx.scene.paint.Color;
+import javafx.scene.paint.Paint;
+import org.jackhuang.hmcl.ui.animation.AnimationUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * JFXToggleButton is the material design implementation of a toggle button.
+ * important CSS Selectors:
+ *
+ * .jfx-toggle-button{
+ * -fx-toggle-color: color-value;
+ * -fx-untoggle-color: color-value;
+ * -fx-toggle-line-color: color-value;
+ * -fx-untoggle-line-color: color-value;
+ * }
+ *
+ * To change the rippler color when toggled:
+ *
+ * .jfx-toggle-button .jfx-rippler{
+ * -fx-rippler-fill: color-value;
+ * }
+ *
+ * .jfx-toggle-button:selected .jfx-rippler{
+ * -fx-rippler-fill: color-value;
+ * }
+ *
+ * @author Shadi Shaheen
+ * @version 1.0
+ * @since 2016-03-09
+ */
+public class JFXToggleButton extends ToggleButton {
+
+ /**
+ * {@inheritDoc}
+ */
+ public JFXToggleButton() {
+ initialize();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected Skin> createDefaultSkin() {
+ return new JFXToggleButtonSkin(this);
+ }
+
+ private void initialize() {
+ this.getStyleClass().add(DEFAULT_STYLE_CLASS);
+ // it's up for the user to add this behavior
+// toggleColor.addListener((o, oldVal, newVal) -> {
+// // update line color in case not set by the user
+// if(newVal instanceof Color)
+// toggleLineColor.set(((Color)newVal).desaturate().desaturate().brighter());
+// });
+ }
+
+ /***************************************************************************
+ * *
+ * styleable Properties *
+ * *
+ **************************************************************************/
+
+ /**
+ * Initialize the style class to 'jfx-toggle-button'.
+ *
+ * This is the selector class from which CSS can be used to style
+ * this control.
+ */
+ private static final String DEFAULT_STYLE_CLASS = "jfx-toggle-button";
+
+ /**
+ * default color used when the button is toggled
+ */
+ private final StyleableObjectProperty toggleColor = new SimpleStyleableObjectProperty<>(StyleableProperties.TOGGLE_COLOR,
+ JFXToggleButton.this,
+ "toggleColor",
+ Color.valueOf(
+ "#009688"));
+
+ public Paint getToggleColor() {
+ return toggleColor == null ? Color.valueOf("#009688") : toggleColor.get();
+ }
+
+ public StyleableObjectProperty toggleColorProperty() {
+ return this.toggleColor;
+ }
+
+ public void setToggleColor(Paint color) {
+ this.toggleColor.set(color);
+ }
+
+ /**
+ * default color used when the button is not toggled
+ */
+ private StyleableObjectProperty untoggleColor = new SimpleStyleableObjectProperty<>(StyleableProperties.UNTOGGLE_COLOR,
+ JFXToggleButton.this,
+ "unToggleColor",
+ Color.valueOf(
+ "#FAFAFA"));
+
+ public Paint getUnToggleColor() {
+ return untoggleColor == null ? Color.valueOf("#FAFAFA") : untoggleColor.get();
+ }
+
+ public StyleableObjectProperty unToggleColorProperty() {
+ return this.untoggleColor;
+ }
+
+ public void setUnToggleColor(Paint color) {
+ this.untoggleColor.set(color);
+ }
+
+ /**
+ * default line color used when the button is toggled
+ */
+ private final StyleableObjectProperty toggleLineColor = new SimpleStyleableObjectProperty<>(
+ StyleableProperties.TOGGLE_LINE_COLOR,
+ JFXToggleButton.this,
+ "toggleLineColor",
+ Color.valueOf("#77C2BB"));
+
+ public Paint getToggleLineColor() {
+ return toggleLineColor == null ? Color.valueOf("#77C2BB") : toggleLineColor.get();
+ }
+
+ public StyleableObjectProperty toggleLineColorProperty() {
+ return this.toggleLineColor;
+ }
+
+ public void setToggleLineColor(Paint color) {
+ this.toggleLineColor.set(color);
+ }
+
+ /**
+ * default line color used when the button is not toggled
+ */
+ private final StyleableObjectProperty untoggleLineColor = new SimpleStyleableObjectProperty<>(
+ StyleableProperties.UNTOGGLE_LINE_COLOR,
+ JFXToggleButton.this,
+ "unToggleLineColor",
+ Color.valueOf("#999999"));
+
+ public Paint getUnToggleLineColor() {
+ return untoggleLineColor == null ? Color.valueOf("#999999") : untoggleLineColor.get();
+ }
+
+ public StyleableObjectProperty unToggleLineColorProperty() {
+ return this.untoggleLineColor;
+ }
+
+ public void setUnToggleLineColor(Paint color) {
+ this.untoggleLineColor.set(color);
+ }
+
+ /**
+ * Default size of the toggle button.
+ */
+ private final StyleableDoubleProperty size = new SimpleStyleableDoubleProperty(
+ StyleableProperties.SIZE,
+ JFXToggleButton.this,
+ "size",
+ 10.0);
+
+ public double getSize() {
+ return size.get();
+ }
+
+ public StyleableDoubleProperty sizeProperty() {
+ return this.size;
+ }
+
+ public void setSize(double size) {
+ this.size.set(size);
+ }
+
+ /**
+ * Disable the visual indicator for focus
+ */
+ private final StyleableBooleanProperty disableVisualFocus = new SimpleStyleableBooleanProperty(StyleableProperties.DISABLE_VISUAL_FOCUS,
+ JFXToggleButton.this,
+ "disableVisualFocus",
+ false);
+
+ public final StyleableBooleanProperty disableVisualFocusProperty() {
+ return this.disableVisualFocus;
+ }
+
+ public final Boolean isDisableVisualFocus() {
+ return disableVisualFocus != null && this.disableVisualFocusProperty().get();
+ }
+
+ public final void setDisableVisualFocus(final Boolean disabled) {
+ this.disableVisualFocusProperty().set(disabled);
+ }
+
+
+ /**
+ * disable animation on button action
+ */
+ private final StyleableBooleanProperty disableAnimation = new SimpleStyleableBooleanProperty(StyleableProperties.DISABLE_ANIMATION,
+ JFXToggleButton.this,
+ "disableAnimation",
+ !AnimationUtils.isAnimationEnabled());
+
+ public final StyleableBooleanProperty disableAnimationProperty() {
+ return this.disableAnimation;
+ }
+
+ public final Boolean isDisableAnimation() {
+ return disableAnimation != null && this.disableAnimationProperty().get();
+ }
+
+ public final void setDisableAnimation(final Boolean disabled) {
+ this.disableAnimationProperty().set(disabled);
+ }
+
+ private static final class StyleableProperties {
+ private static final CssMetaData TOGGLE_COLOR =
+ new CssMetaData<>("-jfx-toggle-color",
+ PaintConverter.getInstance(), Color.valueOf("#009688")) {
+ @Override
+ public boolean isSettable(JFXToggleButton control) {
+ return control.toggleColor == null || !control.toggleColor.isBound();
+ }
+
+ @Override
+ public StyleableProperty getStyleableProperty(JFXToggleButton control) {
+ return control.toggleColorProperty();
+ }
+ };
+
+ private static final CssMetaData UNTOGGLE_COLOR =
+ new CssMetaData<>("-jfx-untoggle-color",
+ PaintConverter.getInstance(), Color.valueOf("#FAFAFA")) {
+ @Override
+ public boolean isSettable(JFXToggleButton control) {
+ return control.untoggleColor == null || !control.untoggleColor.isBound();
+ }
+
+ @Override
+ public StyleableProperty getStyleableProperty(JFXToggleButton control) {
+ return control.unToggleColorProperty();
+ }
+ };
+
+ private static final CssMetaData TOGGLE_LINE_COLOR =
+ new CssMetaData<>("-jfx-toggle-line-color",
+ PaintConverter.getInstance(), Color.valueOf("#77C2BB")) {
+ @Override
+ public boolean isSettable(JFXToggleButton control) {
+ return control.toggleLineColor == null || !control.toggleLineColor.isBound();
+ }
+
+ @Override
+ public StyleableProperty getStyleableProperty(JFXToggleButton control) {
+ return control.toggleLineColorProperty();
+ }
+ };
+
+ private static final CssMetaData UNTOGGLE_LINE_COLOR =
+ new CssMetaData<>("-jfx-untoggle-line-color",
+ PaintConverter.getInstance(), Color.valueOf("#999999")) {
+ @Override
+ public boolean isSettable(JFXToggleButton control) {
+ return control.untoggleLineColor == null || !control.untoggleLineColor.isBound();
+ }
+
+ @Override
+ public StyleableProperty getStyleableProperty(JFXToggleButton control) {
+ return control.unToggleLineColorProperty();
+ }
+ };
+
+ private static final CssMetaData SIZE =
+ new CssMetaData<>("-jfx-size",
+ StyleConverter.getSizeConverter(), 10.0) {
+ @Override
+ public boolean isSettable(JFXToggleButton control) {
+ return !control.size.isBound();
+ }
+
+ @Override
+ public StyleableProperty getStyleableProperty(JFXToggleButton control) {
+ return control.sizeProperty();
+ }
+ };
+ private static final CssMetaData DISABLE_VISUAL_FOCUS =
+ new CssMetaData<>("-jfx-disable-visual-focus",
+ BooleanConverter.getInstance(), false) {
+ @Override
+ public boolean isSettable(JFXToggleButton control) {
+ return control.disableVisualFocus == null || !control.disableVisualFocus.isBound();
+ }
+
+ @Override
+ public StyleableBooleanProperty getStyleableProperty(JFXToggleButton control) {
+ return control.disableVisualFocusProperty();
+ }
+ };
+
+ private static final CssMetaData DISABLE_ANIMATION =
+ new CssMetaData<>("-jfx-disable-animation",
+ BooleanConverter.getInstance(), false) {
+ @Override
+ public boolean isSettable(JFXToggleButton control) {
+ return control.disableAnimation == null || !control.disableAnimation.isBound();
+ }
+
+ @Override
+ public StyleableBooleanProperty getStyleableProperty(JFXToggleButton control) {
+ return control.disableAnimationProperty();
+ }
+ };
+
+ private static final List> CHILD_STYLEABLES;
+
+ static {
+ final List> styleables =
+ new ArrayList<>(ToggleButton.getClassCssMetaData());
+ Collections.addAll(styleables,
+ SIZE,
+ TOGGLE_COLOR,
+ UNTOGGLE_COLOR,
+ TOGGLE_LINE_COLOR,
+ UNTOGGLE_LINE_COLOR,
+ DISABLE_VISUAL_FOCUS,
+ DISABLE_ANIMATION
+ );
+ CHILD_STYLEABLES = Collections.unmodifiableList(styleables);
+ }
+ }
+
+ @Override
+ public List> getControlCssMetaData() {
+ return getClassCssMetaData();
+ }
+
+ public static List> getClassCssMetaData() {
+ return StyleableProperties.CHILD_STYLEABLES;
+ }
+
+}
diff --git a/HMCL/src/main/java/com/jfoenix/skins/JFXCheckBoxSkin.java b/HMCL/src/main/java/com/jfoenix/skins/JFXCheckBoxSkin.java
index 6c0903de8..f6aaa1d68 100644
--- a/HMCL/src/main/java/com/jfoenix/skins/JFXCheckBoxSkin.java
+++ b/HMCL/src/main/java/com/jfoenix/skins/JFXCheckBoxSkin.java
@@ -32,9 +32,9 @@ import javafx.scene.layout.BorderWidths;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
-import javafx.scene.paint.Paint;
import javafx.scene.shape.SVGPath;
import javafx.util.Duration;
+import org.jackhuang.hmcl.theme.Themes;
public class JFXCheckBoxSkin extends CheckBoxSkin {
private final StackPane box = new StackPane();
@@ -54,7 +54,7 @@ public class JFXCheckBoxSkin extends CheckBoxSkin {
this.box.setPrefSize(18.0, 18.0);
this.box.setMaxSize(18.0, 18.0);
this.box.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, new CornerRadii(2.0), Insets.EMPTY)));
- this.box.setBorder(new Border(new BorderStroke(control.getUnCheckedColor(), BorderStrokeStyle.SOLID, new CornerRadii(2.0), new BorderWidths(this.lineThick))));
+ this.box.setBorder(new Border(new BorderStroke(Themes.getColorScheme().getOnSurfaceVariant(), BorderStrokeStyle.SOLID, new CornerRadii(2.0), new BorderWidths(this.lineThick))));
StackPane boxContainer = new StackPane();
boxContainer.getChildren().add(this.box);
boxContainer.setPadding(new Insets(this.padding));
@@ -64,7 +64,7 @@ public class JFXCheckBoxSkin extends CheckBoxSkin {
shape.setContent("M384 690l452-452 60 60-512 512-238-238 60-60z");
this.mark.setShape(shape);
this.mark.setMaxSize(15.0, 12.0);
- this.mark.setStyle("-fx-background-color:WHITE; -fx-border-color:WHITE; -fx-border-width:2px;");
+ this.mark.setStyle("-fx-background-color:-monet-on-primary; -fx-border-color:-monet-on-primary; -fx-border-width:2px;");
this.mark.setVisible(false);
this.mark.setScaleX(0.0);
this.mark.setScaleY(0.0);
@@ -107,13 +107,12 @@ public class JFXCheckBoxSkin extends CheckBoxSkin {
private void updateColors() {
var control = (JFXCheckBox) getSkinnable();
- final Paint color = control.isSelected()
- ? control.getCheckedColor()
- : control.getUnCheckedColor();
- JFXNodeUtils.updateBackground(box.getBackground(), box, control.isSelected() ? control.getCheckedColor() : Color.TRANSPARENT);
- rippler.setRipplerFill(color);
+ boolean isSelected = control.isSelected();
+ JFXNodeUtils.updateBackground(box.getBackground(), box, isSelected ? control.getCheckedColor() : Color.TRANSPARENT);
+ rippler.setRipplerFill(isSelected ? control.getCheckedColor() : control.getUnCheckedColor());
final BorderStroke borderStroke = box.getBorder().getStrokes().get(0);
- box.setBorder(new Border(new BorderStroke(color,
+ box.setBorder(new Border(new BorderStroke(
+ isSelected ? control.getCheckedColor() : Themes.getColorScheme().getOnSurfaceVariant(),
borderStroke.getTopStyle(),
borderStroke.getRadii(),
borderStroke.getWidths())));
@@ -185,7 +184,11 @@ public class JFXCheckBoxSkin extends CheckBoxSkin {
this.select.setRate(selection ? 1.0 : -1.0);
this.transition.play();
this.select.play();
- this.box.setBorder(new Border(new BorderStroke(selection ? control.getCheckedColor() : control.getUnCheckedColor(), BorderStrokeStyle.SOLID, new CornerRadii(2.0), new BorderWidths(this.lineThick))));
+ this.box.setBorder(new Border(new BorderStroke(
+ selection ? control.getCheckedColor() : Themes.getColorScheme().getOnSurfaceVariant(),
+ BorderStrokeStyle.SOLID,
+ new CornerRadii(2.0),
+ new BorderWidths(this.lineThick))));
}
private void createFillTransition() {
diff --git a/HMCL/src/main/java/com/jfoenix/skins/JFXColorPalette.java b/HMCL/src/main/java/com/jfoenix/skins/JFXColorPalette.java
index b27384d3d..60e41ea80 100644
--- a/HMCL/src/main/java/com/jfoenix/skins/JFXColorPalette.java
+++ b/HMCL/src/main/java/com/jfoenix/skins/JFXColorPalette.java
@@ -28,7 +28,6 @@ import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.Event;
import javafx.geometry.Bounds;
-import javafx.geometry.Insets;
import javafx.geometry.NodeOrientation;
import javafx.geometry.Pos;
import javafx.scene.Node;
@@ -116,11 +115,6 @@ final class JFXColorPalette extends Region {
colorPicker.getCustomColors().addListener((Change extends Color> change) -> buildCustomColors());
VBox paletteBox = new VBox();
paletteBox.getStyleClass().add("color-palette");
- paletteBox.setBackground(new Background(new BackgroundFill(Color.WHITE, CornerRadii.EMPTY, Insets.EMPTY)));
- paletteBox.setBorder(new Border(new BorderStroke(Color.valueOf("#9E9E9E"),
- BorderStrokeStyle.SOLID,
- CornerRadii.EMPTY,
- BorderWidths.DEFAULT)));
paletteBox.getChildren().addAll(colorPickerGrid);
if (colorPicker.getPreDefinedColors() == null) {
paletteBox.getChildren().addAll(customColorLabel, customColorGrid, customColorLink);
diff --git a/HMCL/src/main/java/com/jfoenix/skins/JFXCustomColorPickerDialog.java b/HMCL/src/main/java/com/jfoenix/skins/JFXCustomColorPickerDialog.java
index 82d251bf1..109e01d13 100644
--- a/HMCL/src/main/java/com/jfoenix/skins/JFXCustomColorPickerDialog.java
+++ b/HMCL/src/main/java/com/jfoenix/skins/JFXCustomColorPickerDialog.java
@@ -41,6 +41,7 @@ import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.stage.*;
import javafx.util.Duration;
+import org.jackhuang.hmcl.setting.StyleSheets;
import java.util.concurrent.atomic.AtomicInteger;
@@ -48,8 +49,6 @@ import java.util.concurrent.atomic.AtomicInteger;
* @author Shadi Shaheen
*/
public class JFXCustomColorPickerDialog extends StackPane {
-
- public static final String rgbFieldStyle = "-fx-background-color:TRANSPARENT;-fx-font-weight: BOLD;-fx-prompt-text-fill: #808080; -fx-alignment: top-left ; -fx-max-width: 300;";
private final Stage dialog = new Stage();
// used for concurrency control and preventing FX-thread over use
private final AtomicInteger concurrencyController = new AtomicInteger(-1);
@@ -81,15 +80,7 @@ public class JFXCustomColorPickerDialog extends StackPane {
pickerDecorator.setOnCloseButtonAction(this::updateColor);
pickerDecorator.setPickOnBounds(false);
customScene = new Scene(pickerDecorator, Color.TRANSPARENT);
- if (owner != null) {
- final Scene ownerScene = owner.getScene();
- if (ownerScene != null) {
- if (ownerScene.getUserAgentStylesheet() != null) {
- customScene.setUserAgentStylesheet(ownerScene.getUserAgentStylesheet());
- }
- customScene.getStylesheets().addAll(ownerScene.getStylesheets());
- }
- }
+ StyleSheets.init(customScene);
curvedColorPicker = new JFXCustomColorPicker();
StackPane pane = new StackPane(curvedColorPicker);
@@ -104,17 +95,15 @@ public class JFXCustomColorPickerDialog extends StackPane {
JFXTextField hsbField = new JFXTextField();
JFXTextField hexField = new JFXTextField();
- rgbField.setStyle(rgbFieldStyle);
+ rgbField.getStyleClass().add("custom-color-field");
rgbField.setPromptText("RGB Color");
rgbField.textProperty().addListener((o, oldVal, newVal) -> updateColorFromUserInput(newVal));
- hsbField.setStyle(
- "-fx-background-color:TRANSPARENT;-fx-font-weight: BOLD;-fx-prompt-text-fill: #808080; -fx-alignment: top-left ; -fx-max-width: 300;");
+ hsbField.getStyleClass().add("custom-color-field");
hsbField.setPromptText("HSB Color");
hsbField.textProperty().addListener((o, oldVal, newVal) -> updateColorFromUserInput(newVal));
- hexField.setStyle(
- "-fx-background-color:TRANSPARENT;-fx-font-weight: BOLD;-fx-prompt-text-fill: #808080; -fx-alignment: top-left ; -fx-max-width: 300;");
+ hexField.getStyleClass().add("custom-color-field");
hexField.setPromptText("#HEX Color");
hexField.textProperty().addListener((o, oldVal, newVal) -> updateColorFromUserInput(newVal));
diff --git a/HMCL/src/main/java/com/jfoenix/skins/JFXRadioButtonSkin.java b/HMCL/src/main/java/com/jfoenix/skins/JFXRadioButtonSkin.java
index 9aec0a2ad..229b434fa 100644
--- a/HMCL/src/main/java/com/jfoenix/skins/JFXRadioButtonSkin.java
+++ b/HMCL/src/main/java/com/jfoenix/skins/JFXRadioButtonSkin.java
@@ -23,6 +23,7 @@ import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.text.Text;
import javafx.util.Duration;
+import org.jackhuang.hmcl.ui.animation.AnimationUtils;
public class JFXRadioButtonSkin extends RadioButtonSkin {
private static final double PADDING = 15.0;
@@ -68,37 +69,10 @@ public class JFXRadioButtonSkin extends RadioButtonSkin {
});
control.pressedProperty().addListener((o, oldVal, newVal) -> this.rippler.hideOverlay());
- this.registerChangeListener(control.selectedColorProperty(), ignored -> {
- this.updateAnimation();
- boolean isSelected = this.getSkinnable().isSelected();
- Color unSelectedColor = ((JFXRadioButton) this.getSkinnable()).getUnSelectedColor();
- Color selectedColor = ((JFXRadioButton) this.getSkinnable()).getSelectedColor();
- this.rippler.setRipplerFill(isSelected ? selectedColor : unSelectedColor);
- if (isSelected) {
- this.radio.strokeProperty().set(selectedColor);
- }
- });
-
- this.registerChangeListener(control.unSelectedColorProperty(), ignored -> {
- this.updateAnimation();
- boolean isSelected = this.getSkinnable().isSelected();
- Color unSelectedColor = ((JFXRadioButton) this.getSkinnable()).getUnSelectedColor();
- Color selectedColor = ((JFXRadioButton) this.getSkinnable()).getSelectedColor();
- this.rippler.setRipplerFill(isSelected ? selectedColor : unSelectedColor);
- if (!isSelected) {
- this.radio.strokeProperty().set(unSelectedColor);
- }
-
- });
+ this.registerChangeListener(control.selectedColorProperty(), ignored -> updateColors());
+ this.registerChangeListener(control.unSelectedColorProperty(), ignored -> updateColors());
this.registerChangeListener(control.selectedProperty(), ignored -> {
- boolean isSelected = this.getSkinnable().isSelected();
- Color unSelectedColor = ((JFXRadioButton) this.getSkinnable()).getUnSelectedColor();
- Color selectedColor = ((JFXRadioButton) this.getSkinnable()).getSelectedColor();
- this.rippler.setRipplerFill(isSelected ? selectedColor : unSelectedColor);
- if (this.timeline == null) {
- this.updateAnimation();
- }
-
+ updateColors();
this.playAnimation();
});
}
@@ -133,38 +107,46 @@ public class JFXRadioButtonSkin extends RadioButtonSkin {
}
private void initializeComponents() {
- Color unSelectedColor = ((JFXRadioButton) this.getSkinnable()).getUnSelectedColor();
- Color selectedColor = ((JFXRadioButton) this.getSkinnable()).getSelectedColor();
- this.radio.setStroke(unSelectedColor);
- this.rippler.setRipplerFill(this.getSkinnable().isSelected() ? selectedColor : unSelectedColor);
- this.updateAnimation();
+ this.updateColors();
this.playAnimation();
}
private void playAnimation() {
- this.timeline.setRate(this.getSkinnable().isSelected() ? 1.0 : -1.0);
- this.timeline.play();
- }
-
- private void updateAnimation() {
- Color unSelectedColor = ((JFXRadioButton) this.getSkinnable()).getUnSelectedColor();
- Color selectedColor = ((JFXRadioButton) this.getSkinnable()).getSelectedColor();
- this.timeline = new Timeline(
- new KeyFrame(Duration.ZERO,
- new KeyValue(this.dot.scaleXProperty(), 0, Interpolator.EASE_BOTH),
- new KeyValue(this.dot.scaleYProperty(), 0, Interpolator.EASE_BOTH),
- new KeyValue(this.radio.strokeProperty(), unSelectedColor, Interpolator.EASE_BOTH)),
- new KeyFrame(Duration.millis(200.0),
- new KeyValue(this.dot.scaleXProperty(), 1, Interpolator.EASE_BOTH),
- new KeyValue(this.dot.scaleYProperty(), 1, Interpolator.EASE_BOTH),
- new KeyValue(this.radio.strokeProperty(), selectedColor, Interpolator.EASE_BOTH))
- );
+ if (AnimationUtils.isAnimationEnabled()) {
+ if (this.timeline == null) {
+ this.timeline = new Timeline(
+ new KeyFrame(Duration.ZERO,
+ new KeyValue(this.dot.scaleXProperty(), 0, Interpolator.EASE_BOTH),
+ new KeyValue(this.dot.scaleYProperty(), 0, Interpolator.EASE_BOTH)),
+ new KeyFrame(Duration.millis(200.0),
+ new KeyValue(this.dot.scaleXProperty(), 1, Interpolator.EASE_BOTH),
+ new KeyValue(this.dot.scaleYProperty(), 1, Interpolator.EASE_BOTH))
+ );
+ } else {
+ this.timeline.stop();
+ }
+ this.timeline.setRate(this.getSkinnable().isSelected() ? 1.0 : -1.0);
+ this.timeline.play();
+ } else {
+ double endScale = this.getSkinnable().isSelected() ? 1.0 : 0.0;
+ this.dot.setScaleX(endScale);
+ this.dot.setScaleY(endScale);
+ }
}
private void removeRadio() {
this.getChildren().removeIf(node -> "radio".equals(node.getStyleClass().get(0)));
}
+ private void updateColors() {
+ var control = (JFXRadioButton) getSkinnable();
+ boolean isSelected = control.isSelected();
+ Color unSelectedColor = control.getUnSelectedColor();
+ Color selectedColor = control.getSelectedColor();
+ rippler.setRipplerFill(isSelected ? selectedColor : unSelectedColor);
+ radio.setStroke(isSelected ? selectedColor : unSelectedColor);
+ }
+
protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
return super.computePrefWidth(height, topInset, rightInset, bottomInset, leftInset) + this.snapSizeX(this.radio.minWidth(-1.0)) + this.labelOffset + 2.0 * PADDING;
}
diff --git a/HMCL/src/main/java/com/jfoenix/skins/JFXToggleButtonSkin.java b/HMCL/src/main/java/com/jfoenix/skins/JFXToggleButtonSkin.java
new file mode 100644
index 000000000..0548204d7
--- /dev/null
+++ b/HMCL/src/main/java/com/jfoenix/skins/JFXToggleButtonSkin.java
@@ -0,0 +1,186 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.jfoenix.skins;
+
+import com.jfoenix.controls.JFXRippler;
+import com.jfoenix.controls.JFXRippler.RipplerMask;
+import com.jfoenix.controls.JFXRippler.RipplerPos;
+import com.jfoenix.controls.JFXToggleButton;
+import com.jfoenix.effects.JFXDepthManager;
+import com.jfoenix.transitions.JFXAnimationTimer;
+import com.jfoenix.transitions.JFXKeyFrame;
+import com.jfoenix.transitions.JFXKeyValue;
+import javafx.animation.Interpolator;
+import javafx.geometry.Insets;
+import javafx.scene.Cursor;
+import javafx.scene.control.skin.ToggleButtonSkin;
+import javafx.scene.layout.StackPane;
+import javafx.scene.shape.Circle;
+import javafx.scene.shape.Line;
+import javafx.scene.shape.StrokeLineCap;
+import javafx.util.Duration;
+
+/**
+ * Material Design ToggleButton Skin
+ *
+ * @author Shadi Shaheen
+ * @version 1.0
+ * @since 2016-03-09
+ */
+public class JFXToggleButtonSkin extends ToggleButtonSkin {
+
+
+ private Runnable releaseManualRippler = null;
+
+ private JFXAnimationTimer timer;
+ private final Circle circle;
+ private final Line line;
+
+ public JFXToggleButtonSkin(JFXToggleButton toggleButton) {
+ super(toggleButton);
+
+ double circleRadius = toggleButton.getSize();
+
+ line = new Line();
+ line.setStroke(getSkinnable().isSelected() ? toggleButton.getToggleLineColor() : toggleButton.getUnToggleLineColor());
+ line.setStartX(0);
+ line.setStartY(0);
+ line.setEndX(circleRadius * 2 + 2);
+ line.setEndY(0);
+ line.setStrokeWidth(circleRadius * 1.5);
+ line.setStrokeLineCap(StrokeLineCap.ROUND);
+ line.setSmooth(true);
+
+ circle = new Circle();
+ circle.setFill(getSkinnable().isSelected() ? toggleButton.getToggleColor() : toggleButton.getUnToggleColor());
+ circle.setCenterX(-circleRadius);
+ circle.setCenterY(0);
+ circle.setRadius(circleRadius);
+ circle.setSmooth(true);
+ JFXDepthManager.setDepth(circle, 1);
+
+ StackPane circlePane = new StackPane();
+ circlePane.getChildren().add(circle);
+ circlePane.setPadding(new Insets(circleRadius * 1.5));
+
+ JFXRippler rippler = new JFXRippler(circlePane, RipplerMask.CIRCLE, RipplerPos.BACK);
+ rippler.setRipplerFill(getSkinnable().isSelected() ? toggleButton.getToggleLineColor() : toggleButton.getUnToggleLineColor());
+ rippler.setTranslateX(computeTranslation(circleRadius, line));
+
+ final StackPane main = new StackPane();
+ main.getChildren().setAll(line, rippler);
+ main.setCursor(Cursor.HAND);
+
+ // show focus traversal effect
+ getSkinnable().armedProperty().addListener((o, oldVal, newVal) -> {
+ if (newVal) {
+ releaseManualRippler = rippler.createManualRipple();
+ } else if (releaseManualRippler != null) {
+ releaseManualRippler.run();
+ }
+ });
+ toggleButton.focusedProperty().addListener((o, oldVal, newVal) -> {
+ if (!toggleButton.isDisableVisualFocus()) {
+ if (newVal) {
+ if (!getSkinnable().isPressed()) {
+ rippler.setOverlayVisible(true);
+ }
+ } else {
+ rippler.setOverlayVisible(false);
+ }
+ }
+ });
+ toggleButton.pressedProperty().addListener(observable -> rippler.setOverlayVisible(false));
+
+ // add change listener to selected property
+ getSkinnable().selectedProperty().addListener(observable -> {
+ rippler.setRipplerFill(toggleButton.isSelected() ? toggleButton.getToggleLineColor() : toggleButton.getUnToggleLineColor());
+ if (!toggleButton.isDisableAnimation()) {
+ timer.reverseAndContinue();
+ } else {
+ rippler.setTranslateX(computeTranslation(circleRadius, line));
+ }
+ });
+
+ getSkinnable().setGraphic(main);
+
+ timer = new JFXAnimationTimer(
+ new JFXKeyFrame(Duration.millis(100),
+ JFXKeyValue.builder()
+ .setTarget(rippler.translateXProperty())
+ .setEndValueSupplier(() -> computeTranslation(circleRadius, line))
+ .setInterpolator(Interpolator.EASE_BOTH)
+ .setAnimateCondition(() -> !((JFXToggleButton) getSkinnable()).isDisableAnimation())
+ .build(),
+
+ JFXKeyValue.builder()
+ .setTarget(line.strokeProperty())
+ .setEndValueSupplier(() -> getSkinnable().isSelected() ?
+ ((JFXToggleButton) getSkinnable()).getToggleLineColor()
+ : ((JFXToggleButton) getSkinnable()).getUnToggleLineColor())
+ .setInterpolator(Interpolator.EASE_BOTH)
+ .setAnimateCondition(() -> !((JFXToggleButton) getSkinnable()).isDisableAnimation())
+ .build(),
+
+ JFXKeyValue.builder()
+ .setTarget(circle.fillProperty())
+ .setEndValueSupplier(() -> getSkinnable().isSelected() ?
+ ((JFXToggleButton) getSkinnable()).getToggleColor()
+ : ((JFXToggleButton) getSkinnable()).getUnToggleColor())
+ .setInterpolator(Interpolator.EASE_BOTH)
+ .setAnimateCondition(() -> !((JFXToggleButton) getSkinnable()).isDisableAnimation())
+ .build()
+ )
+ );
+ timer.setCacheNodes(circle, line);
+
+ registerChangeListener(toggleButton.toggleColorProperty(), observableValue -> {
+ if (getSkinnable().isSelected()) {
+ circle.setFill(((JFXToggleButton) getSkinnable()).getToggleColor());
+ }
+ });
+ registerChangeListener(toggleButton.unToggleColorProperty(), observableValue -> {
+ if (!getSkinnable().isSelected()) {
+ circle.setFill(((JFXToggleButton) getSkinnable()).getUnToggleColor());
+ }
+ });
+ registerChangeListener(toggleButton.toggleLineColorProperty(), observableValue -> {
+ if (getSkinnable().isSelected()) {
+ line.setStroke(((JFXToggleButton) getSkinnable()).getToggleLineColor());
+ }
+ });
+ registerChangeListener(toggleButton.unToggleColorProperty(), observableValue -> {
+ if (!getSkinnable().isSelected()) {
+ line.setStroke(((JFXToggleButton) getSkinnable()).getUnToggleLineColor());
+ }
+ });
+ }
+
+ private double computeTranslation(double circleRadius, Line line) {
+ return (getSkinnable().isSelected() ? 1 : -1) * ((line.getLayoutBounds().getWidth() / 2) - circleRadius + 2);
+ }
+
+ @Override
+ public void dispose() {
+ super.dispose();
+ timer.dispose();
+ timer = null;
+ }
+}
diff --git a/HMCL/src/main/java/com/jfoenix/transitions/CacheMemento.java b/HMCL/src/main/java/com/jfoenix/transitions/CacheMemento.java
new file mode 100644
index 000000000..cbc25fc7b
--- /dev/null
+++ b/HMCL/src/main/java/com/jfoenix/transitions/CacheMemento.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.jfoenix.transitions;
+
+import javafx.scene.CacheHint;
+import javafx.scene.Node;
+import javafx.scene.layout.Region;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public final class CacheMemento {
+ private boolean cache;
+ private boolean cacheShape;
+ private boolean snapToPixel;
+ private CacheHint cacheHint = CacheHint.DEFAULT;
+ private final Node node;
+ private final AtomicBoolean isCached = new AtomicBoolean(false);
+
+ public CacheMemento(Node node) {
+ this.node = node;
+ }
+
+ /**
+ * this method will cache the node only if it wasn't cached before
+ */
+ public void cache() {
+ if (!isCached.getAndSet(true)) {
+ this.cache = node.isCache();
+ this.cacheHint = node.getCacheHint();
+ node.setCache(true);
+ node.setCacheHint(CacheHint.SPEED);
+ if (node instanceof Region region) {
+ this.cacheShape = region.isCacheShape();
+ this.snapToPixel = region.isSnapToPixel();
+ region.setCacheShape(true);
+ region.setSnapToPixel(true);
+ }
+ }
+ }
+
+ public void restore() {
+ if (isCached.getAndSet(false)) {
+ node.setCache(cache);
+ node.setCacheHint(cacheHint);
+ if (node instanceof Region region) {
+ region.setCacheShape(cacheShape);
+ region.setSnapToPixel(snapToPixel);
+ }
+ }
+ }
+}
diff --git a/HMCL/src/main/java/com/jfoenix/transitions/JFXAnimationTimer.java b/HMCL/src/main/java/com/jfoenix/transitions/JFXAnimationTimer.java
new file mode 100644
index 000000000..623a998ea
--- /dev/null
+++ b/HMCL/src/main/java/com/jfoenix/transitions/JFXAnimationTimer.java
@@ -0,0 +1,286 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.jfoenix.transitions;
+
+import javafx.animation.AnimationTimer;
+import javafx.beans.value.WritableValue;
+import javafx.scene.Node;
+import javafx.util.Duration;
+
+import java.util.*;
+import java.util.function.Supplier;
+
+/**
+ * Custom AnimationTimer that can be created the same way as a timeline,
+ * however it doesn't behave the same yet. it only animates in one direction,
+ * it doesn't support animation 0 -> 1 -> 0.5
+ *
+ * @author Shadi Shaheen
+ * @version 1.0
+ * @since 2017-09-21
+ */
+
+public class JFXAnimationTimer extends AnimationTimer {
+
+ private Set animationHandlers = new HashSet<>();
+ private long startTime = -1;
+ private boolean running = false;
+ private List caches = new ArrayList<>();
+ private double totalElapsedMilliseconds;
+
+ public JFXAnimationTimer(JFXKeyFrame... keyFrames) {
+ for (JFXKeyFrame keyFrame : keyFrames) {
+ Duration duration = keyFrame.getDuration();
+ final Set> keyValuesSet = keyFrame.getValues();
+ if (!keyValuesSet.isEmpty()) {
+ animationHandlers.add(new AnimationHandler(duration, keyFrame.getAnimateCondition(), keyFrame.getValues()));
+ }
+ }
+ }
+
+ private final HashMap mutableFrames = new HashMap<>();
+
+ public void addKeyFrame(JFXKeyFrame keyFrame) throws Exception {
+ if (isRunning()) {
+ throw new Exception("Can't update animation timer while running");
+ }
+ Duration duration = keyFrame.getDuration();
+ final Set> keyValuesSet = keyFrame.getValues();
+ if (!keyValuesSet.isEmpty()) {
+ final AnimationHandler handler = new AnimationHandler(duration, keyFrame.getAnimateCondition(), keyFrame.getValues());
+ animationHandlers.add(handler);
+ mutableFrames.put(keyFrame, handler);
+ }
+ }
+
+ public void removeKeyFrame(JFXKeyFrame keyFrame) throws Exception {
+ if (isRunning()) {
+ throw new Exception("Can't update animation timer while running");
+ }
+ AnimationHandler handler = mutableFrames.get(keyFrame);
+ animationHandlers.remove(handler);
+ }
+
+ @Override
+ public void start() {
+ super.start();
+ running = true;
+ startTime = -1;
+ for (AnimationHandler animationHandler : animationHandlers) {
+ animationHandler.init();
+ }
+ for (CacheMemento cache : caches) {
+ cache.cache();
+ }
+ }
+
+ @Override
+ public void handle(long now) {
+ startTime = startTime == -1 ? now : startTime;
+ totalElapsedMilliseconds = (now - startTime) / 1000000.0;
+ boolean stop = true;
+ for (AnimationHandler handler : animationHandlers) {
+ handler.animate(totalElapsedMilliseconds);
+ if (!handler.finished) {
+ stop = false;
+ }
+ }
+ if (stop) {
+ this.stop();
+ }
+ }
+
+ /**
+ * this method will pause the timer and reverse the animation if the timer already
+ * started otherwise it will start the animation.
+ */
+ public void reverseAndContinue() {
+ if (isRunning()) {
+ super.stop();
+ for (AnimationHandler handler : animationHandlers) {
+ handler.reverse(totalElapsedMilliseconds);
+ }
+ startTime = -1;
+ super.start();
+ } else {
+ start();
+ }
+ }
+
+ @Override
+ public void stop() {
+ super.stop();
+ running = false;
+ for (AnimationHandler handler : animationHandlers) {
+ handler.clear();
+ }
+ for (CacheMemento cache : caches) {
+ cache.restore();
+ }
+ if (onFinished != null) {
+ onFinished.run();
+ }
+ }
+
+ public void applyEndValues() {
+ if (isRunning()) {
+ super.stop();
+ }
+ for (AnimationHandler handler : animationHandlers) {
+ handler.applyEndValues();
+ }
+ startTime = -1;
+ }
+
+ public boolean isRunning() {
+ return running;
+ }
+
+ private Runnable onFinished = null;
+
+ public void setOnFinished(Runnable onFinished) {
+ this.onFinished = onFinished;
+ }
+
+ public void setCacheNodes(Node... nodesToCache) {
+ caches.clear();
+ if (nodesToCache != null) {
+ for (Node node : nodesToCache) {
+ caches.add(new CacheMemento(node));
+ }
+ }
+ }
+
+ public void dispose() {
+ caches.clear();
+ for (AnimationHandler handler : animationHandlers) {
+ handler.dispose();
+ }
+ animationHandlers.clear();
+ }
+
+ static class AnimationHandler {
+ private final double duration;
+ private double currentDuration;
+ private final Set> keyValues;
+ private Supplier animationCondition = null;
+ private boolean finished = false;
+
+ private final HashMap, Object> initialValuesMap = new HashMap<>();
+ private final HashMap, Object> endValuesMap = new HashMap<>();
+
+ AnimationHandler(Duration duration, Supplier animationCondition, Set> keyValues) {
+ this.duration = duration.toMillis();
+ currentDuration = this.duration;
+ this.keyValues = keyValues;
+ this.animationCondition = animationCondition;
+ }
+
+ public void init() {
+ finished = animationCondition != null && !animationCondition.get();
+ for (JFXKeyValue> keyValue : keyValues) {
+ if (keyValue.getTarget() != null) {
+ // replaced putIfAbsent for mobile compatibility
+ if (!initialValuesMap.containsKey(keyValue.getTarget())) {
+ initialValuesMap.put(keyValue.getTarget(), keyValue.getTarget().getValue());
+ }
+ if (!endValuesMap.containsKey(keyValue.getTarget())) {
+ endValuesMap.put(keyValue.getTarget(), keyValue.getEndValue());
+ }
+ }
+ }
+ }
+
+ void reverse(double now) {
+ finished = animationCondition != null && !animationCondition.get();
+ currentDuration = duration - (currentDuration - now);
+ // update initial values
+ for (JFXKeyValue> keyValue : keyValues) {
+ final WritableValue> target = keyValue.getTarget();
+ if (target != null) {
+ initialValuesMap.put(target, target.getValue());
+ endValuesMap.put(target, keyValue.getEndValue());
+ }
+ }
+ }
+
+ // now in milliseconds
+ @SuppressWarnings({"unchecked"})
+ public void animate(double now) {
+ // if animate condition for the key frame is not met then do nothing
+ if (finished) {
+ return;
+ }
+ if (now <= currentDuration) {
+ for (JFXKeyValue> keyValue : keyValues) {
+ if (keyValue.isValid()) {
+ @SuppressWarnings("rawtypes") final WritableValue target = keyValue.getTarget();
+ final Object endValue = endValuesMap.get(target);
+ if (endValue != null && target != null && !target.getValue().equals(endValue)) {
+ target.setValue(keyValue.getInterpolator().interpolate(initialValuesMap.get(target), endValue, now / currentDuration));
+ }
+ }
+ }
+ } else {
+ if (!finished) {
+ finished = true;
+ for (JFXKeyValue> keyValue : keyValues) {
+ if (keyValue.isValid()) {
+ @SuppressWarnings("rawtypes") final WritableValue target = keyValue.getTarget();
+ if (target != null) {
+ // set updated end value instead of cached
+ final Object endValue = keyValue.getEndValue();
+ if (endValue != null) {
+ target.setValue(endValue);
+ }
+ }
+ }
+ }
+ currentDuration = duration;
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public void applyEndValues() {
+ for (JFXKeyValue> keyValue : keyValues) {
+ if (keyValue.isValid()) {
+ @SuppressWarnings("rawtypes") final WritableValue target = keyValue.getTarget();
+ if (target != null) {
+ final Object endValue = keyValue.getEndValue();
+ if (endValue != null && !target.getValue().equals(endValue)) {
+ target.setValue(endValue);
+ }
+ }
+ }
+ }
+ }
+
+ public void clear() {
+ initialValuesMap.clear();
+ endValuesMap.clear();
+ }
+
+ void dispose() {
+ clear();
+ keyValues.clear();
+ }
+ }
+}
diff --git a/HMCL/src/main/java/com/jfoenix/transitions/JFXKeyFrame.java b/HMCL/src/main/java/com/jfoenix/transitions/JFXKeyFrame.java
new file mode 100644
index 000000000..8c46455c1
--- /dev/null
+++ b/HMCL/src/main/java/com/jfoenix/transitions/JFXKeyFrame.java
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.jfoenix.transitions;
+
+import javafx.util.Duration;
+
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.function.Supplier;
+
+/**
+ * @author Shadi Shaheen
+ * @version 1.0
+ * @since 2017-09-21
+ */
+
+public class JFXKeyFrame {
+
+ private Duration duration;
+ private Set> keyValues = new CopyOnWriteArraySet<>();
+ private Supplier animateCondition = null;
+
+ public JFXKeyFrame(Duration duration, JFXKeyValue>... keyValues) {
+ this.duration = duration;
+ for (final JFXKeyValue> keyValue : keyValues) {
+ if (keyValue != null) {
+ this.keyValues.add(keyValue);
+ }
+ }
+ }
+
+ private JFXKeyFrame() {
+
+ }
+
+ public final Duration getDuration() {
+ return duration;
+ }
+
+ public final Set> getValues() {
+ return keyValues;
+ }
+
+ public Supplier getAnimateCondition() {
+ return animateCondition;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static final class Builder {
+ private Duration duration;
+ private final Set> keyValues = new CopyOnWriteArraySet<>();
+ private Supplier animateCondition = null;
+
+ private Builder() {
+ }
+
+ public Builder setDuration(Duration duration) {
+ this.duration = duration;
+ return this;
+ }
+
+ public Builder setKeyValues(JFXKeyValue>... keyValues) {
+ for (final JFXKeyValue> keyValue : keyValues) {
+ if (keyValue != null) {
+ this.keyValues.add(keyValue);
+ }
+ }
+ return this;
+ }
+
+ public Builder setAnimateCondition(Supplier animateCondition) {
+ this.animateCondition = animateCondition;
+ return this;
+ }
+
+ public JFXKeyFrame build() {
+ JFXKeyFrame jFXKeyFrame = new JFXKeyFrame();
+ jFXKeyFrame.duration = this.duration;
+ jFXKeyFrame.keyValues = this.keyValues;
+ jFXKeyFrame.animateCondition = this.animateCondition;
+ return jFXKeyFrame;
+ }
+ }
+}
diff --git a/HMCL/src/main/java/com/jfoenix/transitions/JFXKeyValue.java b/HMCL/src/main/java/com/jfoenix/transitions/JFXKeyValue.java
new file mode 100644
index 000000000..6532a5f30
--- /dev/null
+++ b/HMCL/src/main/java/com/jfoenix/transitions/JFXKeyValue.java
@@ -0,0 +1,157 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.jfoenix.transitions;
+
+import javafx.animation.Interpolator;
+import javafx.beans.value.WritableValue;
+
+import java.util.function.Supplier;
+
+/**
+ * @author Shadi Shaheen
+ * @version 1.0
+ * @since 2017-09-21
+ */
+
+public final class JFXKeyValue {
+
+ private WritableValue target;
+ private Supplier> targetSupplier;
+ private Supplier endValueSupplier;
+ private T endValue;
+ private Supplier animateCondition = () -> true;
+ private Interpolator interpolator;
+
+ private JFXKeyValue() {
+ }
+
+ // this builder is created to ensure type inference from method arguments
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public T getEndValue() {
+ return endValue == null ? endValueSupplier.get() : endValue;
+ }
+
+ public WritableValue getTarget() {
+ return target == null ? targetSupplier.get() : target;
+ }
+
+ public Interpolator getInterpolator() {
+ return interpolator;
+ }
+
+ public boolean isValid() {
+ return animateCondition == null || animateCondition.get();
+ }
+
+ public static final class Builder {
+ public JFXKeyValueBuilder setTarget(WritableValue target) {
+ JFXKeyValueBuilder builder = new JFXKeyValueBuilder<>();
+ builder.setTarget(target);
+ return builder;
+ }
+
+ public JFXKeyValueBuilder setTargetSupplier(Supplier> targetSupplier) {
+ JFXKeyValueBuilder builder = new JFXKeyValueBuilder<>();
+ builder.setTargetSupplier(targetSupplier);
+ return builder;
+ }
+
+ public JFXKeyValueBuilder setEndValueSupplier(Supplier endValueSupplier) {
+ JFXKeyValueBuilder builder = new JFXKeyValueBuilder<>();
+ builder.setEndValueSupplier(endValueSupplier);
+ return builder;
+ }
+
+ public JFXKeyValueBuilder setEndValue(T endValue) {
+ JFXKeyValueBuilder builder = new JFXKeyValueBuilder<>();
+ builder.setEndValue(endValue);
+ return builder;
+ }
+
+ public JFXKeyValueBuilder setAnimateCondition(Supplier animateCondition) {
+ JFXKeyValueBuilder builder = new JFXKeyValueBuilder<>();
+ builder.setAnimateCondition(animateCondition);
+ return builder;
+ }
+
+ public JFXKeyValueBuilder setInterpolator(Interpolator interpolator) {
+ JFXKeyValueBuilder builder = new JFXKeyValueBuilder<>();
+ builder.setInterpolator(interpolator);
+ return builder;
+ }
+ }
+
+ public static final class JFXKeyValueBuilder {
+
+ private WritableValue target;
+ private Supplier> targetSupplier;
+ private Supplier endValueSupplier;
+ private T endValue;
+ private Supplier animateCondition = () -> true;
+ private Interpolator interpolator = Interpolator.EASE_BOTH;
+
+ private JFXKeyValueBuilder() {
+ }
+
+ public JFXKeyValueBuilder setTarget(WritableValue target) {
+ this.target = target;
+ return this;
+ }
+
+ public JFXKeyValueBuilder setTargetSupplier(Supplier> targetSupplier) {
+ this.targetSupplier = targetSupplier;
+ return this;
+ }
+
+ public JFXKeyValueBuilder setEndValueSupplier(Supplier endValueSupplier) {
+ this.endValueSupplier = endValueSupplier;
+ return this;
+ }
+
+ public JFXKeyValueBuilder setEndValue(T endValue) {
+ this.endValue = endValue;
+ return this;
+ }
+
+ public JFXKeyValueBuilder setAnimateCondition(Supplier animateCondition) {
+ this.animateCondition = animateCondition;
+ return this;
+ }
+
+ public JFXKeyValueBuilder setInterpolator(Interpolator interpolator) {
+ this.interpolator = interpolator;
+ return this;
+ }
+
+ public JFXKeyValue build() {
+ JFXKeyValue jFXKeyValue = new JFXKeyValue<>();
+ jFXKeyValue.target = this.target;
+ jFXKeyValue.interpolator = this.interpolator;
+ jFXKeyValue.targetSupplier = this.targetSupplier;
+ jFXKeyValue.endValue = this.endValue;
+ jFXKeyValue.endValueSupplier = this.endValueSupplier;
+ jFXKeyValue.animateCondition = this.animateCondition;
+ return jFXKeyValue;
+ }
+ }
+}
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java
index 0cf008a42..33bb333aa 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java
@@ -34,12 +34,12 @@ import org.hildan.fxgson.factories.JavaFxPropertyTypeAdapterFactory;
import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
import org.jackhuang.hmcl.java.JavaRuntime;
+import org.jackhuang.hmcl.theme.ThemeColor;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.util.gson.*;
import org.jackhuang.hmcl.util.i18n.SupportedLocale;
import org.jetbrains.annotations.Nullable;
-import java.lang.reflect.*;
import java.net.Proxy;
import java.nio.file.Path;
import java.util.*;
@@ -276,19 +276,34 @@ public final class Config extends ObservableSetting {
// UI
+ @SerializedName("themeBrightness")
+ private final StringProperty themeBrightness = new SimpleStringProperty("light");
+
+ public StringProperty themeBrightnessProperty() {
+ return themeBrightness;
+ }
+
+ public String getThemeBrightness() {
+ return themeBrightness.get();
+ }
+
+ public void setThemeBrightness(String themeBrightness) {
+ this.themeBrightness.set(themeBrightness);
+ }
+
@SerializedName("theme")
- private final ObjectProperty theme = new SimpleObjectProperty<>();
+ private final ObjectProperty themeColor = new SimpleObjectProperty<>(ThemeColor.DEFAULT);
- public ObjectProperty themeProperty() {
- return theme;
+ public ObjectProperty themeColorProperty() {
+ return themeColor;
}
- public Theme getTheme() {
- return theme.get();
+ public ThemeColor getThemeColor() {
+ return themeColor.get();
}
- public void setTheme(Theme theme) {
- this.theme.set(theme);
+ public void setThemeColor(ThemeColor themeColor) {
+ this.themeColor.set(themeColor);
}
@SerializedName("fontFamily")
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/StyleSheets.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/StyleSheets.java
index e4041a5ab..69765f48d 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/StyleSheets.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/StyleSheets.java
@@ -22,6 +22,12 @@ import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
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.Theme;
+import org.jackhuang.hmcl.theme.ThemeColor;
+import org.jackhuang.hmcl.theme.Themes;
import org.jackhuang.hmcl.ui.FXUtils;
import java.io.IOException;
@@ -33,7 +39,6 @@ import java.util.Arrays;
import java.util.Base64;
import java.util.Locale;
-import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
/**
@@ -54,7 +59,7 @@ public final class StyleSheets {
stylesheets = FXCollections.observableList(Arrays.asList(array));
FontManager.fontProperty().addListener(o -> stylesheets.set(FONT_STYLE_SHEET_INDEX, getFontStyleSheet()));
- config().themeProperty().addListener(o -> stylesheets.set(THEME_STYLE_SHEET_INDEX, getThemeStyleSheet()));
+ Themes.colorSchemeProperty().addListener(o -> stylesheets.set(THEME_STYLE_SHEET_INDEX, getThemeStyleSheet()));
}
private static String toStyleSheetUri(String styleSheet, String fallback) {
@@ -126,31 +131,55 @@ public final class StyleSheets {
return toStyleSheetUri(builder.toString(), defaultCss);
}
- private static String rgba(Color color, double opacity) {
- return String.format("rgba(%d, %d, %d, %.1f)",
- (int) Math.ceil(color.getRed() * 256),
- (int) Math.ceil(color.getGreen() * 256),
- (int) Math.ceil(color.getBlue() * 256),
- opacity);
+ private static void addColor(StringBuilder builder, String name, Color color) {
+ builder.append(" ").append(name)
+ .append(": ").append(ThemeColor.getColorDisplayName(color)).append(";\n");
+ }
+
+ private static void addColor(StringBuilder builder, String name, Color color, double opacity) {
+ builder.append(" ").append(name)
+ .append(": ").append(ThemeColor.getColorDisplayNameWithOpacity(color, opacity)).append(";\n");
+ }
+
+ private static void addColor(StringBuilder builder, ColorScheme scheme, ColorRole role, double opacity) {
+ builder.append(" ").append(role.getVariableName()).append("-transparent-").append((int) (100 * opacity))
+ .append(": ").append(ThemeColor.getColorDisplayNameWithOpacity(scheme.getColor(role), opacity))
+ .append(";\n");
}
private static String getThemeStyleSheet() {
final String blueCss = "/assets/css/blue.css";
- Theme theme = config().getTheme();
- if (theme == null || theme.getPaint().equals(Theme.BLUE.getPaint()))
+ if (Theme.DEFAULT.equals(Themes.getTheme()))
return blueCss;
- return toStyleSheetUri(".root {" +
- "-fx-base-color:" + theme.getColor() + ';' +
- "-fx-base-darker-color: derive(-fx-base-color, -10%);" +
- "-fx-base-check-color: derive(-fx-base-color, 30%);" +
- "-fx-rippler-color:" + rgba(theme.getPaint(), 0.3) + ';' +
- "-fx-base-rippler-color: derive(" + rgba(theme.getPaint(), 0.3) + ", 100%);" +
- "-fx-base-disabled-text-fill:" + rgba(theme.getForegroundColor(), 0.7) + ";" +
- "-fx-base-text-fill:" + Theme.getColorDisplayName(theme.getForegroundColor()) + ";" +
- "-theme-thumb:" + rgba(theme.getPaint(), 0.7) + ";" +
- '}', blueCss);
+ ColorScheme scheme = Themes.getColorScheme();
+
+ StringBuilder builder = new StringBuilder();
+ builder.append("* {\n");
+ for (ColorRole colorRole : ColorRole.ALL) {
+ addColor(builder, colorRole.getVariableName(), scheme.getColor(colorRole));
+ }
+
+ addColor(builder, "-monet-primary-seed", scheme.getPrimaryColorSeed());
+
+ addColor(builder, scheme, ColorRole.PRIMARY, 0.5);
+ addColor(builder, scheme, ColorRole.SECONDARY_CONTAINER, 0.5);
+ addColor(builder, scheme, ColorRole.SURFACE, 0.5);
+ addColor(builder, scheme, ColorRole.SURFACE, 0.8);
+ addColor(builder, scheme, ColorRole.ON_SURFACE_VARIANT, 0.38);
+ addColor(builder, scheme, ColorRole.SURFACE_CONTAINER_LOW, 0.8);
+ addColor(builder, scheme, ColorRole.SECONDARY_CONTAINER, 0.8);
+
+ // If we use -monet-error-container as the tag color, there may be a conflict between the tag and background colors on the mod management page
+ if (scheme.getBrightness() == Brightness.LIGHT) {
+ addColor(builder, "-warning-tag-background", Color.web("#f1aeb5"));
+ } else {
+ addColor(builder, "-warning-tag-background", Color.web("#2c0b0e"));
+ }
+
+ builder.append("}\n");
+ return toStyleSheetUri(builder.toString(), blueCss);
}
public static void init(Scene scene) {
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Theme.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Theme.java
deleted file mode 100644
index 45f2bb267..000000000
--- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Theme.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * Hello Minecraft! Launcher
- * Copyright (C) 2020 huangyuhui and contributors
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-package org.jackhuang.hmcl.setting;
-
-import com.google.gson.annotations.JsonAdapter;
-import com.google.gson.stream.JsonReader;
-import com.google.gson.stream.JsonWriter;
-import javafx.beans.binding.Bindings;
-import javafx.beans.binding.ObjectBinding;
-import javafx.scene.paint.Color;
-
-import java.io.IOException;
-import java.util.Locale;
-import java.util.Objects;
-import java.util.Optional;
-
-import static org.jackhuang.hmcl.setting.ConfigHolder.config;
-
-@JsonAdapter(Theme.TypeAdapter.class)
-public final class Theme {
- public static final Theme BLUE = new Theme("blue", "#5C6BC0");
- public static final Color BLACK = Color.web("#292929");
- public static final Color[] SUGGESTED_COLORS = new Color[]{
- Color.web("#3D6DA3"), // blue
- Color.web("#283593"), // dark blue
- Color.web("#43A047"), // green
- Color.web("#E67E22"), // orange
- Color.web("#9C27B0"), // purple
- Color.web("#B71C1C") // red
- };
-
- public static Theme getTheme() {
- Theme theme = config().getTheme();
- return theme == null ? BLUE : theme;
- }
-
- private final Color paint;
- private final String color;
- private final String name;
-
- Theme(String name, String color) {
- this.name = name;
- this.color = Objects.requireNonNull(color);
- this.paint = Color.web(color);
- }
-
- public String getName() {
- return name;
- }
-
- public String getColor() {
- return color;
- }
-
- public Color getPaint() {
- return paint;
- }
-
- public boolean isCustom() {
- return name.startsWith("#");
- }
-
- public boolean isLight() {
- return paint.grayscale().getRed() >= 0.5;
- }
-
- public Color getForegroundColor() {
- return isLight() ? Color.BLACK : Color.WHITE;
- }
-
- public static Theme custom(String color) {
- if (!color.startsWith("#"))
- throw new IllegalArgumentException();
- return new Theme(color, color);
- }
-
- public static Optional getTheme(String name) {
- if (name == null)
- return Optional.empty();
- else if (name.startsWith("#"))
- try {
- Color.web(name);
- return Optional.of(custom(name));
- } catch (IllegalArgumentException ignore) {
- }
- else {
- String color = null;
- switch (name.toLowerCase(Locale.ROOT)) {
- case "blue":
- return Optional.of(BLUE);
- case "darker_blue":
- color = "#283593";
- break;
- case "green":
- color = "#43A047";
- break;
- case "orange":
- color = "#E67E22";
- break;
- case "purple":
- color = "#9C27B0";
- break;
- case "red":
- color = "#F44336";
- }
- if (color != null)
- return Optional.of(new Theme(name, color));
- }
-
- return Optional.empty();
- }
-
- public static String getColorDisplayName(Color c) {
- return c != null ? String.format("#%02X%02X%02X", Math.round(c.getRed() * 255.0D), Math.round(c.getGreen() * 255.0D), Math.round(c.getBlue() * 255.0D)) : null;
- }
-
- private static ObjectBinding FOREGROUND_FILL;
-
- public static ObjectBinding foregroundFillBinding() {
- if (FOREGROUND_FILL == null)
- FOREGROUND_FILL = Bindings.createObjectBinding(
- () -> Theme.getTheme().getForegroundColor(),
- config().themeProperty()
- );
-
- return FOREGROUND_FILL;
- }
-
- public static Color blackFill() {
- return BLACK;
- }
-
- public static Color whiteFill() {
- return Color.WHITE;
- }
-
- public static class TypeAdapter extends com.google.gson.TypeAdapter {
- @Override
- public void write(JsonWriter out, Theme value) throws IOException {
- out.value(value.getName().toLowerCase(Locale.ROOT));
- }
-
- @Override
- public Theme read(JsonReader in) throws IOException {
- return getTheme(in.nextString()).orElse(Theme.BLUE);
- }
- }
-}
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/theme/Theme.java b/HMCL/src/main/java/org/jackhuang/hmcl/theme/Theme.java
new file mode 100644
index 000000000..e8d337116
--- /dev/null
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/theme/Theme.java
@@ -0,0 +1,94 @@
+/*
+ * Hello Minecraft! Launcher
+ * Copyright (C) 2025 huangyuhui and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.jackhuang.hmcl.theme;
+
+import org.glavo.monetfx.*;
+
+import java.util.*;
+
+/// @author Glavo
+public final class Theme {
+
+ public static final Theme DEFAULT = new Theme(ThemeColor.DEFAULT, Brightness.DEFAULT, ColorStyle.FIDELITY, Contrast.DEFAULT);
+
+ private final ThemeColor primaryColorSeed;
+ private final Brightness brightness;
+ private final ColorStyle colorStyle;
+ private final Contrast contrast;
+
+ public Theme(ThemeColor primaryColorSeed,
+ Brightness brightness,
+ ColorStyle colorStyle,
+ Contrast contrast
+ ) {
+ this.primaryColorSeed = primaryColorSeed;
+ this.brightness = brightness;
+ this.colorStyle = colorStyle;
+ this.contrast = contrast;
+ }
+
+ public ColorScheme toColorScheme() {
+ return ColorScheme.newBuilder()
+ .setPrimaryColorSeed(primaryColorSeed.color())
+ .setColorStyle(colorStyle)
+ .setBrightness(brightness)
+ .setSpecVersion(ColorSpecVersion.SPEC_2025)
+ .setContrast(contrast)
+ .build();
+ }
+
+ public ThemeColor primaryColorSeed() {
+ return primaryColorSeed;
+ }
+
+ public Brightness brightness() {
+ return brightness;
+ }
+
+ public ColorStyle colorStyle() {
+ return colorStyle;
+ }
+
+ public Contrast contrast() {
+ return contrast;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj == this || obj instanceof Theme that
+ && this.primaryColorSeed.color().equals(that.primaryColorSeed.color())
+ && this.brightness.equals(that.brightness)
+ && this.colorStyle.equals(that.colorStyle)
+ && this.contrast.equals(that.contrast);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(primaryColorSeed, brightness, colorStyle, contrast);
+ }
+
+ @Override
+ public String toString() {
+ return "Theme[" +
+ "primaryColorSeed=" + primaryColorSeed + ", " +
+ "brightness=" + brightness + ", " +
+ "colorStyle=" + colorStyle + ", " +
+ "contrast=" + contrast + ']';
+ }
+
+}
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/theme/ThemeColor.java b/HMCL/src/main/java/org/jackhuang/hmcl/theme/ThemeColor.java
new file mode 100644
index 000000000..1ce2264b4
--- /dev/null
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/theme/ThemeColor.java
@@ -0,0 +1,193 @@
+/*
+ * Hello Minecraft! Launcher
+ * Copyright (C) 2025 huangyuhui and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.jackhuang.hmcl.theme;
+
+import com.google.gson.annotations.JsonAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+import javafx.beans.InvalidationListener;
+import javafx.beans.Observable;
+import javafx.beans.WeakListener;
+import javafx.beans.property.Property;
+import javafx.scene.control.ColorPicker;
+import javafx.scene.paint.Color;
+import org.jackhuang.hmcl.util.gson.JsonSerializable;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.util.List;
+import java.util.Objects;
+
+/// @author Glavo
+@JsonAdapter(ThemeColor.TypeAdapter.class)
+@JsonSerializable
+public record ThemeColor(@NotNull String name, @NotNull Color color) {
+
+ public static final ThemeColor DEFAULT = new ThemeColor("blue", Color.web("#5C6BC0"));
+
+ public static final List STANDARD_COLORS = List.of(
+ DEFAULT,
+ new ThemeColor("darker_blue", Color.web("#283593")),
+ new ThemeColor("green", Color.web("#43A047")),
+ new ThemeColor("orange", Color.web("#E67E22")),
+ new ThemeColor("purple", Color.web("#9C27B0")),
+ new ThemeColor("red", Color.web("#B71C1C"))
+ );
+
+ public static String getColorDisplayName(Color c) {
+ return c != null ? String.format("#%02X%02X%02X",
+ Math.round(c.getRed() * 255.0D),
+ Math.round(c.getGreen() * 255.0D),
+ Math.round(c.getBlue() * 255.0D))
+ : null;
+ }
+
+ public static String getColorDisplayNameWithOpacity(Color c, double opacity) {
+ return c != null ? String.format("#%02X%02X%02X%02X",
+ Math.round(c.getRed() * 255.0D),
+ Math.round(c.getGreen() * 255.0D),
+ Math.round(c.getBlue() * 255.0D),
+ Math.round(opacity * 255.0))
+ : null;
+ }
+
+ public static @Nullable ThemeColor of(String name) {
+ if (name == null)
+ return null;
+
+ if (!name.startsWith("#")) {
+ for (ThemeColor color : STANDARD_COLORS) {
+ if (name.equalsIgnoreCase(color.name()))
+ return color;
+ }
+ }
+
+ try {
+ return new ThemeColor(name, Color.web(name));
+ } catch (IllegalArgumentException e) {
+ return null;
+ }
+ }
+
+ @Contract("null -> null; !null -> !null")
+ public static ThemeColor of(Color color) {
+ return color != null ? new ThemeColor(getColorDisplayName(color), color) : null;
+ }
+
+ private static final class BidirectionalBinding implements InvalidationListener, WeakListener {
+ private final WeakReference colorPickerRef;
+ private final WeakReference> propertyRef;
+ private final int hashCode;
+
+ private boolean updating = false;
+
+ private BidirectionalBinding(ColorPicker colorPicker, Property property) {
+ this.colorPickerRef = new WeakReference<>(colorPicker);
+ this.propertyRef = new WeakReference<>(property);
+ this.hashCode = System.identityHashCode(colorPicker) ^ System.identityHashCode(property);
+ }
+
+ @Override
+ public void invalidated(Observable sourceProperty) {
+ if (!updating) {
+ final ColorPicker colorPicker = colorPickerRef.get();
+ final Property property = propertyRef.get();
+
+ if (colorPicker == null || property == null) {
+ if (colorPicker != null) {
+ colorPicker.valueProperty().removeListener(this);
+ }
+
+ if (property != null) {
+ property.removeListener(this);
+ }
+ } else {
+ updating = true;
+ try {
+ if (property == sourceProperty) {
+ ThemeColor newValue = property.getValue();
+ colorPicker.setValue(newValue != null ? newValue.color() : null);
+ } else {
+ Color newValue = colorPicker.getValue();
+ property.setValue(newValue != null ? ThemeColor.of(newValue) : null);
+ }
+ } finally {
+ updating = false;
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean wasGarbageCollected() {
+ return colorPickerRef.get() == null || propertyRef.get() == null;
+ }
+
+ @Override
+ public int hashCode() {
+ return hashCode;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (!(o instanceof BidirectionalBinding that))
+ return false;
+
+ final ColorPicker colorPicker = this.colorPickerRef.get();
+ final Property property = this.propertyRef.get();
+
+ final ColorPicker thatColorPicker = that.colorPickerRef.get();
+ final Property> thatProperty = that.propertyRef.get();
+
+ if (colorPicker == null || property == null || thatColorPicker == null || thatProperty == null)
+ return false;
+
+ return colorPicker == thatColorPicker && property == thatProperty;
+ }
+ }
+
+ public static void bindBidirectional(ColorPicker colorPicker, Property property) {
+ var binding = new BidirectionalBinding(colorPicker, property);
+
+ colorPicker.valueProperty().removeListener(binding);
+ property.removeListener(binding);
+
+ ThemeColor themeColor = property.getValue();
+ colorPicker.setValue(themeColor != null ? themeColor.color() : null);
+
+ colorPicker.valueProperty().addListener(binding);
+ property.addListener(binding);
+ }
+
+ static final class TypeAdapter extends com.google.gson.TypeAdapter {
+ @Override
+ public void write(JsonWriter out, ThemeColor value) throws IOException {
+ out.value(value.name());
+ }
+
+ @Override
+ public ThemeColor read(JsonReader in) throws IOException {
+ return Objects.requireNonNullElse(of(in.nextString()), ThemeColor.DEFAULT);
+ }
+ }
+}
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/theme/Themes.java b/HMCL/src/main/java/org/jackhuang/hmcl/theme/Themes.java
new file mode 100644
index 000000000..f692aefe0
--- /dev/null
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/theme/Themes.java
@@ -0,0 +1,126 @@
+/*
+ * Hello Minecraft! Launcher
+ * Copyright (C) 2025 huangyuhui and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.jackhuang.hmcl.theme;
+
+import javafx.beans.Observable;
+import javafx.beans.binding.Bindings;
+import javafx.beans.binding.BooleanBinding;
+import javafx.beans.binding.ObjectBinding;
+import javafx.beans.binding.ObjectExpression;
+import javafx.beans.value.ChangeListener;
+import javafx.beans.value.ObservableValue;
+import javafx.scene.paint.Color;
+import org.glavo.monetfx.Brightness;
+import org.glavo.monetfx.ColorScheme;
+import org.glavo.monetfx.Contrast;
+import org.glavo.monetfx.beans.property.ColorSchemeProperty;
+import org.glavo.monetfx.beans.property.ReadOnlyColorSchemeProperty;
+import org.glavo.monetfx.beans.property.SimpleColorSchemeProperty;
+import org.jackhuang.hmcl.ui.FXUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+
+import static org.jackhuang.hmcl.setting.ConfigHolder.config;
+
+/// @author Glavo
+public final class Themes {
+
+ private static final ObjectExpression theme = new ObjectBinding<>() {
+ {
+ List observables = new ArrayList<>();
+
+ observables.add(config().themeBrightnessProperty());
+ observables.add(config().themeColorProperty());
+ if (FXUtils.DARK_MODE != null) {
+ observables.add(FXUtils.DARK_MODE);
+ }
+ bind(observables.toArray(new Observable[0]));
+ }
+
+ private Brightness getBrightness() {
+ String themeBrightness = config().getThemeBrightness();
+ if (themeBrightness == null)
+ return Brightness.DEFAULT;
+
+ return switch (themeBrightness.toLowerCase(Locale.ROOT).trim()) {
+ case "auto" -> {
+ if (FXUtils.DARK_MODE != null) {
+ yield FXUtils.DARK_MODE.get() ? Brightness.DARK : Brightness.LIGHT;
+ } else {
+ yield Brightness.DEFAULT;
+ }
+ }
+ case "dark" -> Brightness.DARK;
+ case "light" -> Brightness.LIGHT;
+ default -> Brightness.DEFAULT;
+ };
+ }
+
+ @Override
+ protected Theme computeValue() {
+ ThemeColor themeColor = Objects.requireNonNullElse(config().getThemeColor(), ThemeColor.DEFAULT);
+
+ return new Theme(themeColor, getBrightness(), Theme.DEFAULT.colorStyle(), Contrast.DEFAULT);
+ }
+ };
+ private static final ColorSchemeProperty colorScheme = new SimpleColorSchemeProperty();
+ private static final BooleanBinding darkMode = Bindings.createBooleanBinding(
+ () -> colorScheme.get().getBrightness() == Brightness.DARK,
+ colorScheme
+ );
+
+ static {
+ ChangeListener listener = (observable, oldValue, newValue) -> {
+ if (!Objects.equals(oldValue, newValue)) {
+ colorScheme.set(newValue != null ? newValue.toColorScheme() : Theme.DEFAULT.toColorScheme());
+ }
+ };
+ listener.changed(theme, null, theme.get());
+ theme.addListener(listener);
+ }
+
+ public static ObjectExpression themeProperty() {
+ return theme;
+ }
+
+ public static Theme getTheme() {
+ return themeProperty().get();
+ }
+
+ public static ReadOnlyColorSchemeProperty colorSchemeProperty() {
+ return colorScheme;
+ }
+
+ public static ColorScheme getColorScheme() {
+ return colorScheme.get();
+ }
+
+ public static ObservableValue titleFillProperty() {
+ return colorSchemeProperty().getOnPrimaryContainer();
+ }
+
+ public static BooleanBinding darkModeProperty() {
+ return darkMode;
+ }
+
+ private Themes() {
+ }
+}
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java
index c8a072b06..0eae9c9da 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java
@@ -57,7 +57,6 @@ import javafx.util.Callback;
import javafx.util.Duration;
import javafx.util.StringConverter;
import org.jackhuang.hmcl.setting.StyleSheets;
-import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.task.CacheFileTask;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
@@ -135,6 +134,7 @@ public final class FXUtils {
public static final @Nullable ObservableMap PREFERENCES;
public static final @Nullable ObservableBooleanValue DARK_MODE;
public static final @Nullable Boolean REDUCED_MOTION;
+ public static final @Nullable ReadOnlyObjectProperty ACCENT_COLOR;
public static final @Nullable MethodHandle TEXT_TRUNCATED_PROPERTY;
@@ -151,6 +151,7 @@ public final class FXUtils {
ObservableMap preferences = null;
ObservableBooleanValue darkMode = null;
+ ReadOnlyObjectProperty accentColorProperty = null;
Boolean reducedMotion = null;
if (JAVAFX_MAJOR_VERSION >= 22) {
try {
@@ -162,14 +163,19 @@ public final class FXUtils {
preferences = preferences0;
@SuppressWarnings("unchecked")
- var colorSchemeProperty =
- (ReadOnlyObjectProperty extends Enum>>)
- lookup.findVirtual(preferencesClass, "colorSchemeProperty", MethodType.methodType(ReadOnlyObjectProperty.class))
- .invoke(preferences);
+ var colorSchemeProperty = (ReadOnlyObjectProperty extends Enum>>)
+ lookup.findVirtual(preferencesClass, "colorSchemeProperty", MethodType.methodType(ReadOnlyObjectProperty.class))
+ .invoke(preferences);
darkMode = Bindings.createBooleanBinding(() ->
"DARK".equals(colorSchemeProperty.get().name()), colorSchemeProperty);
+ @SuppressWarnings("unchecked")
+ var accentColorProperty0 = (ReadOnlyObjectProperty)
+ lookup.findVirtual(preferencesClass, "accentColorProperty", MethodType.methodType(ReadOnlyObjectProperty.class))
+ .invoke(preferences);
+ accentColorProperty = accentColorProperty0;
+
if (JAVAFX_MAJOR_VERSION >= 24) {
reducedMotion = (boolean)
lookup.findVirtual(preferencesClass, "isReducedMotion", MethodType.methodType(boolean.class))
@@ -182,6 +188,7 @@ public final class FXUtils {
PREFERENCES = preferences;
DARK_MODE = darkMode;
REDUCED_MOTION = reducedMotion;
+ ACCENT_COLOR = accentColorProperty;
MethodHandle textTruncatedProperty = null;
if (JAVAFX_MAJOR_VERSION >= 23) {
@@ -1214,7 +1221,7 @@ public final class FXUtils {
public static JFXButton newToggleButton4(SVG icon) {
JFXButton button = new JFXButton();
button.getStyleClass().add("toggle-icon4");
- button.setGraphic(icon.createIcon(Theme.blackFill(), -1));
+ button.setGraphic(icon.createIcon());
return button;
}
@@ -1447,11 +1454,11 @@ public final class FXUtils {
for (int i = 0; i < children.getLength(); i++) {
org.w3c.dom.Node node = children.item(i);
- if (node instanceof Element) {
- Element element = (Element) node;
+ if (node instanceof Element element) {
if ("a".equals(element.getTagName())) {
String href = element.getAttribute("href");
Text text = new Text(element.getTextContent());
+ text.getStyleClass().add("hyperlink");
onClicked(text, () -> {
String link = href;
try {
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java
index 1925bbeb7..9c237a7bd 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java
@@ -36,7 +36,6 @@ import javafx.scene.image.ImageView;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.*;
import org.jackhuang.hmcl.download.LibraryAnalyzer;
-import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.setting.VersionIconType;
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
import org.jackhuang.hmcl.util.i18n.I18n;
@@ -396,7 +395,7 @@ public class InstallerItem extends Control {
pane.getChildren().add(buttonsContainer);
JFXButton removeButton = new JFXButton();
- removeButton.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), -1));
+ removeButton.setGraphic(SVG.CLOSE.createIcon());
removeButton.getStyleClass().add("toggle-icon4");
if (control.id.equals(MINECRAFT.getPatchId())) {
removeButton.setVisible(false);
@@ -417,8 +416,8 @@ public class InstallerItem extends Control {
JFXButton installButton = new JFXButton();
installButton.graphicProperty().bind(Bindings.createObjectBinding(() ->
control.resolvedStateProperty.get() instanceof InstallableState ?
- SVG.ARROW_FORWARD.createIcon(Theme.blackFill(), -1) :
- SVG.UPDATE.createIcon(Theme.blackFill(), -1),
+ SVG.ARROW_FORWARD.createIcon() :
+ SVG.UPDATE.createIcon(),
control.resolvedStateProperty
));
installButton.getStyleClass().add("toggle-icon4");
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/ListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ListPageSkin.java
index 1927b3172..d64ef4abd 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/ListPageSkin.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ListPageSkin.java
@@ -27,7 +27,6 @@ import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
-import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
public class ListPageSkin extends SkinBase> {
@@ -67,7 +66,7 @@ public class ListPageSkin extends SkinBase> {
JFXButton btnAdd = FXUtils.newRaisedButton("");
FXUtils.setLimitWidth(btnAdd, 40);
FXUtils.setLimitHeight(btnAdd, 40);
- btnAdd.setGraphic(SVG.ADD.createIcon(Theme.whiteFill(), -1));
+ btnAdd.setGraphic(SVG.ADD.createIcon());
btnAdd.setOnAction(e -> skinnable.add());
JFXButton btnRefresh = new JFXButton();
@@ -75,7 +74,7 @@ public class ListPageSkin extends SkinBase> {
FXUtils.setLimitHeight(btnRefresh, 40);
btnRefresh.getStyleClass().add("jfx-button-raised-round");
btnRefresh.setButtonType(JFXButton.ButtonType.RAISED);
- btnRefresh.setGraphic(SVG.REFRESH.createIcon(Theme.whiteFill(), -1));
+ btnRefresh.setGraphic(SVG.REFRESH.createIcon());
btnRefresh.setOnAction(e -> skinnable.refresh());
vBox.getChildren().setAll(btnAdd);
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java
index 48c968fd7..2f1ea2aa1 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java
@@ -153,24 +153,25 @@ public enum SVG {
return new Group(path);
}
- public Node createIcon(ObservableValue extends Paint> fill, double size) {
+ public Node createIcon(double size) {
SVGPath p = new SVGPath();
- p.getStyleClass().add("svg");
p.setContent(path);
- if (fill != null)
- p.fillProperty().bind(fill);
-
+ p.getStyleClass().add("svg");
return createIcon(p, size);
}
- public Node createIcon(Paint fill, double size) {
+ public Node createIcon() {
SVGPath p = new SVGPath();
- p.getStyleClass().add("svg");
p.setContent(path);
- if (fill != null)
- p.fillProperty().set(fill);
-
- return createIcon(p, size);
+ p.getStyleClass().add("svg");
+ return createIcon(p, -1);
}
+ public Node createIcon(ObservableValue extends Paint> color) {
+ SVGPath p = new SVGPath();
+ p.setContent(path);
+ p.getStyleClass().add("svg");
+ p.fillProperty().bind(color);
+ return createIcon(p, -1);
+ }
}
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/ToolbarListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ToolbarListPageSkin.java
index d2a36b737..53a61026d 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/ToolbarListPageSkin.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ToolbarListPageSkin.java
@@ -28,7 +28,6 @@ import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
-import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.construct.ComponentList;
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
@@ -85,20 +84,10 @@ public abstract class ToolbarListPageSkin
return stackPane;
}
- public static JFXButton createToolbarButton(String text, SVG svg, Runnable onClick) {
- JFXButton ret = new JFXButton();
- ret.getStyleClass().add("jfx-tool-bar-button");
- ret.textFillProperty().bind(Theme.foregroundFillBinding());
- ret.setGraphic(wrap(svg.createIcon(Theme.foregroundFillBinding(), -1)));
- ret.setText(text);
- ret.setOnAction(e -> onClick.run());
- return ret;
- }
-
public static JFXButton createToolbarButton2(String text, SVG svg, Runnable onClick) {
JFXButton ret = new JFXButton();
ret.getStyleClass().add("jfx-tool-bar-button");
- ret.setGraphic(wrap(svg.createIcon(Theme.blackFill(), -1)));
+ ret.setGraphic(wrap(svg.createIcon()));
ret.setText(text);
ret.setOnAction(e -> {
onClick.run();
@@ -110,8 +99,7 @@ public abstract class ToolbarListPageSkin
public static JFXButton createDecoratorButton(String tooltip, SVG svg, Runnable onClick) {
JFXButton ret = new JFXButton();
ret.getStyleClass().add("jfx-decorator-button");
- ret.textFillProperty().bind(Theme.foregroundFillBinding());
- ret.setGraphic(wrap(svg.createIcon(Theme.foregroundFillBinding(), -1)));
+ ret.setGraphic(wrap(svg.createIcon()));
FXUtils.installFastTooltip(ret, tooltip);
ret.setOnAction(e -> onClick.run());
return ret;
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemSkin.java
index 1ce5cd280..8ea3ce741 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemSkin.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemSkin.java
@@ -36,7 +36,6 @@ import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
import org.jackhuang.hmcl.auth.microsoft.MicrosoftAccount;
import org.jackhuang.hmcl.game.TexturesLoader;
import org.jackhuang.hmcl.setting.Accounts;
-import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.Controllers;
@@ -117,10 +116,10 @@ public final class AccountListItemSkin extends SkinBase {
});
btnMove.getStyleClass().add("toggle-icon4");
if (skinnable.getAccount().isPortable()) {
- btnMove.setGraphic(SVG.PUBLIC.createIcon(Theme.blackFill(), -1));
+ btnMove.setGraphic(SVG.PUBLIC.createIcon());
FXUtils.installFastTooltip(btnMove, i18n("account.move_to_global"));
} else {
- btnMove.setGraphic(SVG.OUTPUT.createIcon(Theme.blackFill(), -1));
+ btnMove.setGraphic(SVG.OUTPUT.createIcon());
FXUtils.installFastTooltip(btnMove, i18n("account.move_to_portable"));
}
spinnerMove.setContent(btnMove);
@@ -146,7 +145,7 @@ public final class AccountListItemSkin extends SkinBase {
.start();
});
btnRefresh.getStyleClass().add("toggle-icon4");
- btnRefresh.setGraphic(SVG.REFRESH.createIcon(Theme.blackFill(), -1));
+ btnRefresh.setGraphic(SVG.REFRESH.createIcon());
FXUtils.installFastTooltip(btnRefresh, i18n("button.refresh"));
spinnerRefresh.setContent(btnRefresh);
right.getChildren().add(spinnerRefresh);
@@ -163,7 +162,7 @@ public final class AccountListItemSkin extends SkinBase {
}
});
btnUpload.getStyleClass().add("toggle-icon4");
- btnUpload.setGraphic(SVG.CHECKROOM.createIcon(Theme.blackFill(), -1));
+ btnUpload.setGraphic(SVG.CHECKROOM.createIcon());
FXUtils.installFastTooltip(btnUpload, i18n("account.skin.upload"));
btnUpload.disableProperty().bind(Bindings.not(skinnable.canUploadSkin()));
spinnerUpload.setContent(btnUpload);
@@ -175,7 +174,7 @@ public final class AccountListItemSkin extends SkinBase {
spinnerCopyUUID.getStyleClass().add("small-spinner-pane");
btnUpload.getStyleClass().add("toggle-icon4");
btnCopyUUID.setOnAction(e -> FXUtils.copyText(skinnable.getAccount().getUUID().toString()));
- btnCopyUUID.setGraphic(SVG.CONTENT_COPY.createIcon(Theme.blackFill(), -1));
+ btnCopyUUID.setGraphic(SVG.CONTENT_COPY.createIcon());
FXUtils.installFastTooltip(btnCopyUUID, i18n("account.copy_uuid"));
spinnerCopyUUID.setContent(btnCopyUUID);
right.getChildren().add(spinnerCopyUUID);
@@ -184,7 +183,7 @@ public final class AccountListItemSkin extends SkinBase {
btnRemove.setOnAction(e -> Controllers.confirm(i18n("button.remove.confirm"), i18n("button.remove"), skinnable::remove, null));
btnRemove.getStyleClass().add("toggle-icon4");
BorderPane.setAlignment(btnRemove, Pos.CENTER);
- btnRemove.setGraphic(SVG.DELETE.createIcon(Theme.blackFill(), -1));
+ btnRemove.setGraphic(SVG.DELETE.createIcon());
FXUtils.installFastTooltip(btnRemove, i18n("button.delete"));
right.getChildren().add(btnRemove);
root.setRight(right);
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java
index c66eb2609..b1fbbdbdc 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java
@@ -39,7 +39,6 @@ import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
import org.jackhuang.hmcl.setting.Accounts;
-import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
@@ -158,7 +157,7 @@ public final class AccountListPage extends DecoratorAnimatedPage implements Deco
e.consume();
});
btnRemove.getStyleClass().add("toggle-icon4");
- btnRemove.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), 14));
+ btnRemove.setGraphic(SVG.CLOSE.createIcon(14));
item.setRightGraphic(btnRemove);
ObservableValue title = BindingMapping.of(server, AuthlibInjectorServer::getName);
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/CreateAccountPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/CreateAccountPane.java
index e2c00c369..19d6eb52d 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/CreateAccountPane.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/CreateAccountPane.java
@@ -50,7 +50,6 @@ import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService;
import org.jackhuang.hmcl.game.OAuthServer;
import org.jackhuang.hmcl.game.TexturesLoader;
import org.jackhuang.hmcl.setting.Accounts;
-import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskExecutor;
@@ -465,7 +464,7 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
linksContainer.setMinWidth(USE_PREF_SIZE);
JFXButton btnAddServer = new JFXButton();
- btnAddServer.setGraphic(SVG.ADD.createIcon(Theme.blackFill(), 20));
+ btnAddServer.setGraphic(SVG.ADD.createIcon(20));
btnAddServer.getStyleClass().add("toggle-icon4");
btnAddServer.setOnAction(e -> {
Controllers.dialog(new AddAuthlibInjectorServerPane());
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/AdvancedListBox.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/AdvancedListBox.java
index cd48a22a0..400dd56ad 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/AdvancedListBox.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/AdvancedListBox.java
@@ -26,7 +26,6 @@ import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
-import javafx.scene.paint.Paint;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
@@ -109,8 +108,8 @@ public class AdvancedListBox extends ScrollPane {
item.activeProperty().bind(tabHeader.getSelectionModel().selectedItemProperty().isEqualTo(tab));
item.setOnAction(e -> tabHeader.select(tab));
- Node unselectedIcon = unselectedGraphic.createIcon((Paint) null, 20);
- Node selectedIcon = selectedGraphic.createIcon((Paint) null, 20);
+ Node unselectedIcon = unselectedGraphic.createIcon(20);
+ Node selectedIcon = selectedGraphic.createIcon(20);
TransitionPane leftGraphic = new TransitionPane();
leftGraphic.setAlignment(Pos.CENTER);
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ClassTitle.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ClassTitle.java
index c4e9bb3f8..7499e1193 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ClassTitle.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ClassTitle.java
@@ -21,7 +21,6 @@ import javafx.scene.Node;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
-import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import org.jackhuang.hmcl.util.Lang;
@@ -44,7 +43,6 @@ public class ClassTitle extends StackPane {
Rectangle rectangle = new Rectangle();
rectangle.widthProperty().bind(vbox.widthProperty());
rectangle.setHeight(1.0);
- rectangle.setFill(Color.GRAY);
vbox.getChildren().add(rectangle);
getChildren().setAll(vbox);
getStyleClass().add("class-title");
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentListCell.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentListCell.java
index bb8ce317f..67f94a921 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentListCell.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentListCell.java
@@ -30,7 +30,7 @@ import javafx.scene.input.MouseEvent;
import javafx.scene.layout.*;
import javafx.scene.shape.Rectangle;
import javafx.util.Duration;
-import org.jackhuang.hmcl.setting.Theme;
+import org.jackhuang.hmcl.theme.Themes;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
@@ -79,7 +79,7 @@ final class ComponentListCell extends StackPane {
VBox groupNode = new VBox();
- Node expandIcon = SVG.KEYBOARD_ARROW_DOWN.createIcon(Theme.blackFill(), 20);
+ Node expandIcon = SVG.KEYBOARD_ARROW_DOWN.createIcon(20);
expandIcon.setMouseTransparent(true);
HBox.setMargin(expandIcon, new Insets(0, 8, 0, 8));
@@ -99,12 +99,14 @@ final class ComponentListCell extends StackPane {
if (!overrideHeaderLeft) {
Label label = new Label();
label.textProperty().bind(list.titleProperty());
+ label.getStyleClass().add("title-label");
labelVBox.getChildren().add(label);
if (list.isHasSubtitle()) {
Label subtitleLabel = new Label();
subtitleLabel.textProperty().bind(list.subtitleProperty());
subtitleLabel.getStyleClass().add("subtitle-label");
+ subtitleLabel.textFillProperty().bind(Themes.colorSchemeProperty().getOnSurfaceVariant());
labelVBox.getChildren().add(subtitleLabel);
}
}
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FileItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FileItem.java
index ccf722f3b..de52a2979 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FileItem.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FileItem.java
@@ -28,7 +28,6 @@ import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.stage.DirectoryChooser;
import org.jackhuang.hmcl.Metadata;
-import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
@@ -60,7 +59,7 @@ public class FileItem extends BorderPane {
setLeft(left);
JFXButton right = new JFXButton();
- right.setGraphic(SVG.EDIT.createIcon(Theme.blackFill(), 16));
+ right.setGraphic(SVG.EDIT.createIcon(16));
right.getStyleClass().add("toggle-icon4");
right.setOnAction(e -> onExplore());
FXUtils.installFastTooltip(right, i18n("button.edit"));
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FileSelector.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FileSelector.java
index 96cffb9a0..a0dd9d53e 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FileSelector.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FileSelector.java
@@ -27,7 +27,6 @@ import javafx.geometry.Pos;
import javafx.scene.layout.HBox;
import javafx.stage.DirectoryChooser;
import javafx.stage.FileChooser;
-import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
@@ -82,7 +81,7 @@ public class FileSelector extends HBox {
FXUtils.bindString(customField, valueProperty());
JFXButton selectButton = new JFXButton();
- selectButton.setGraphic(SVG.FOLDER_OPEN.createIcon(Theme.blackFill(), 15));
+ selectButton.setGraphic(SVG.FOLDER_OPEN.createIcon(15));
selectButton.setOnAction(e -> {
if (directory) {
DirectoryChooser chooser = new DirectoryChooser();
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FloatScrollBarSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FloatScrollBarSkin.java
index c14b73c03..0cfe681d6 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FloatScrollBarSkin.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FloatScrollBarSkin.java
@@ -28,6 +28,8 @@ import javafx.scene.layout.Region;
import javafx.scene.shape.Rectangle;
import org.jackhuang.hmcl.util.Lang;
+// Referenced in root.css
+@SuppressWarnings("unused")
public class FloatScrollBarSkin implements Skin {
private ScrollBar scrollBar;
private Region group;
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/HintPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/HintPane.java
index aa95e093f..1cd7d295d 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/HintPane.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/HintPane.java
@@ -26,7 +26,6 @@ import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
-import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
@@ -67,7 +66,7 @@ public class HintPane extends VBox {
throw new IllegalArgumentException("Unrecognized message box message type " + type);
}
- HBox hbox = new HBox(svg.createIcon(Theme.blackFill(), 16), new Text(type.getDisplayName()));
+ HBox hbox = new HBox(svg.createIcon(16), new Text(type.getDisplayName()));
hbox.setAlignment(Pos.CENTER_LEFT);
flow.getChildren().setAll(label);
getChildren().setAll(hbox, flow);
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/IconedMenuItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/IconedMenuItem.java
index 076e095e0..ef95289ba 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/IconedMenuItem.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/IconedMenuItem.java
@@ -18,14 +18,13 @@
package org.jackhuang.hmcl.ui.construct;
import com.jfoenix.controls.JFXPopup;
-import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
-public class IconedMenuItem extends IconedItem {
+public final class IconedMenuItem extends IconedItem {
public IconedMenuItem(SVG icon, String text, Runnable action, JFXPopup popup) {
- super(icon != null ? FXUtils.limitingSize(icon.createIcon(Theme.blackFill(), 14), 14, 14) : null, text);
+ super(icon != null ? icon.createIcon(14) : null, text);
getStyleClass().setAll("iconed-menu-item");
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/IconedTwoLineListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/IconedTwoLineListItem.java
index 3687dfea7..9cdcae0db 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/IconedTwoLineListItem.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/IconedTwoLineListItem.java
@@ -15,7 +15,6 @@ import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
-import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.util.StringUtils;
@@ -111,7 +110,7 @@ public class IconedTwoLineListItem extends HBox {
if (externalLinkButton == null) {
externalLinkButton = new JFXButton();
externalLinkButton.getStyleClass().add("toggle-icon4");
- externalLinkButton.setGraphic(SVG.OPEN_IN_NEW.createIcon(Theme.blackFill(), -1));
+ externalLinkButton.setGraphic(SVG.OPEN_IN_NEW.createIcon());
externalLinkButton.setOnAction(e -> FXUtils.openLink(externalLink.get()));
}
return externalLinkButton;
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ImagePickerItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ImagePickerItem.java
index c05764740..c22ad5859 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ImagePickerItem.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ImagePickerItem.java
@@ -32,7 +32,6 @@ import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
-import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
@@ -54,12 +53,12 @@ public final class ImagePickerItem extends BorderPane {
imageView.setPreserveRatio(true);
JFXButton selectButton = new JFXButton();
- selectButton.setGraphic(SVG.EDIT.createIcon(Theme.blackFill(), 20));
+ selectButton.setGraphic(SVG.EDIT.createIcon(20));
selectButton.onActionProperty().bind(onSelectButtonClicked);
selectButton.getStyleClass().add("toggle-icon4");
JFXButton deleteButton = new JFXButton();
- deleteButton.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), 20));
+ deleteButton.setGraphic(SVG.CLOSE.createIcon(20));
deleteButton.onActionProperty().bind(onDeleteButtonClicked);
deleteButton.getStyleClass().add("toggle-icon4");
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/JFXHyperlink.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/JFXHyperlink.java
index 0fefaff46..d67b4422e 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/JFXHyperlink.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/JFXHyperlink.java
@@ -18,7 +18,6 @@
package org.jackhuang.hmcl.ui.construct;
import javafx.scene.control.Hyperlink;
-import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
@@ -27,7 +26,7 @@ public final class JFXHyperlink extends Hyperlink {
public JFXHyperlink(String text) {
super(text);
- setGraphic(SVG.OPEN_IN_NEW.createIcon(Theme.blackFill(), 16));
+ setGraphic(SVG.OPEN_IN_NEW.createIcon(16));
}
public void setExternalLink(String externalLink) {
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MenuUpDownButton.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MenuUpDownButton.java
index f603769e3..f6bbcc5d2 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MenuUpDownButton.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MenuUpDownButton.java
@@ -29,7 +29,6 @@ import javafx.scene.control.Label;
import javafx.scene.control.Skin;
import javafx.scene.control.SkinBase;
import javafx.scene.layout.HBox;
-import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
@@ -39,6 +38,7 @@ public class MenuUpDownButton extends Control {
private final StringProperty text = new SimpleStringProperty(this, "text");
public MenuUpDownButton() {
+ this.getStyleClass().add("menu-up-down-button");
}
@Override
@@ -78,11 +78,10 @@ public class MenuUpDownButton extends Control {
HBox content = new HBox(8);
content.setAlignment(Pos.CENTER);
Label label = new Label();
- label.setStyle("-fx-text-fill: black;");
label.textProperty().bind(control.text);
- Node up = SVG.ARROW_DROP_UP.createIcon(Theme.blackFill(), 16);
- Node down = SVG.ARROW_DROP_DOWN.createIcon(Theme.blackFill(), 16);
+ Node up = SVG.ARROW_DROP_UP.createIcon(16);
+ Node down = SVG.ARROW_DROP_DOWN.createIcon(16);
JFXButton button = new JFXButton();
button.setGraphic(content);
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java
index fe20460db..320111eb3 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java
@@ -28,7 +28,6 @@ import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.TextFlow;
-import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
@@ -91,7 +90,7 @@ public final class MessageDialogPane extends HBox {
default:
throw new IllegalArgumentException("Unrecognized message box message type " + type);
}
- graphic.setGraphic(svg.createIcon(Theme.blackFill(), 40));
+ graphic.setGraphic(svg.createIcon(40));
VBox vbox = new VBox();
HBox.setHgrow(vbox, Priority.ALWAYS);
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MultiFileItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MultiFileItem.java
index dc7c61d10..2797b9dff 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MultiFileItem.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MultiFileItem.java
@@ -31,12 +31,15 @@ import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
+import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.stage.FileChooser;
+import org.jackhuang.hmcl.theme.ThemeColor;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.util.StringUtils;
import java.util.Collection;
+import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
@@ -308,11 +311,21 @@ public final class MultiFileItem extends VBox {
super(title, data);
}
+ public PaintOption setCustomColors(List colors) {
+ colorPicker.getCustomColors().setAll(colors);
+ return this;
+ }
+
public PaintOption bindBidirectional(Property property) {
FXUtils.bindPaint(colorPicker, property);
return this;
}
+ public PaintOption bindThemeColorBidirectional(Property property) {
+ ThemeColor.bindBidirectional(colorPicker, property);
+ return this;
+ }
+
@Override
protected Node createItem(ToggleGroup group) {
BorderPane pane = new BorderPane();
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/RipplerContainer.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/RipplerContainer.java
index fb93b0801..806be70ab 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/RipplerContainer.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/RipplerContainer.java
@@ -38,6 +38,7 @@ import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Rectangle;
import javafx.util.Duration;
+import org.jackhuang.hmcl.theme.Themes;
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
import org.jackhuang.hmcl.ui.animation.Motion;
import org.jackhuang.hmcl.util.Lang;
@@ -50,7 +51,7 @@ public class RipplerContainer extends StackPane {
private static final Duration DURATION = Duration.millis(200);
private final ObjectProperty container = new SimpleObjectProperty<>(this, "container", null);
- private final StyleableObjectProperty ripplerFill = new SimpleStyleableObjectProperty<>(StyleableProperties.RIPPLER_FILL,this, "ripplerFill", null);
+ private final StyleableObjectProperty ripplerFill = new SimpleStyleableObjectProperty<>(StyleableProperties.RIPPLER_FILL, this, "ripplerFill", null);
private final BooleanProperty selected = new SimpleBooleanProperty(this, "selected", false);
private final StackPane buttonContainer = new StackPane();
@@ -136,7 +137,10 @@ public class RipplerContainer extends StackPane {
}
private void interpolateBackground(double frac) {
- setBackground(new Background(new BackgroundFill(Color.rgb(0, 0, 0, frac * 0.04), CornerRadii.EMPTY, Insets.EMPTY)));
+ Color onSurface = Themes.getColorScheme().getOnSurface();
+ setBackground(new Background(new BackgroundFill(
+ Color.color(onSurface.getRed(), onSurface.getGreen(), onSurface.getBlue(), frac * 0.04),
+ CornerRadii.EMPTY, Insets.EMPTY)));
}
protected void updateChildren() {
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java
index b3031f80d..32581f9f1 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java
@@ -50,6 +50,8 @@ public class TaskExecutorDialogPane extends BorderPane {
private final TaskListPane taskListPane;
public TaskExecutorDialogPane(@NotNull TaskCancellationAction cancel) {
+ this.getStyleClass().add("task-executor-dialog-layout");
+
FXUtils.setLimitWidth(this, 500);
FXUtils.setLimitHeight(this, 300);
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java
index 6a0d930ac..7b05008e4 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java
@@ -64,7 +64,6 @@ import org.jackhuang.hmcl.mod.multimc.MultiMCModpackInstallTask;
import org.jackhuang.hmcl.mod.server.ServerModpackCompletionTask;
import org.jackhuang.hmcl.mod.server.ServerModpackExportTask;
import org.jackhuang.hmcl.mod.server.ServerModpackLocalInstallTask;
-import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskExecutor;
import org.jackhuang.hmcl.task.TaskListener;
@@ -310,7 +309,7 @@ public final class TaskListPane extends StackPane {
}
private void updateLeftIcon(StageNode.Status status) {
- left.getChildren().setAll(status.svg.createIcon(Theme.blackFill(), STATUS_ICON_SIZE));
+ left.getChildren().setAll(status.svg.createIcon(STATUS_ICON_SIZE));
}
@Override
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorSkin.java
index 14f28853e..c910c4a13 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorSkin.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorSkin.java
@@ -45,7 +45,7 @@ import javafx.stage.Stage;
import javafx.util.Duration;
import org.jackhuang.hmcl.Metadata;
-import org.jackhuang.hmcl.setting.Theme;
+import org.jackhuang.hmcl.theme.Themes;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
@@ -233,19 +233,19 @@ public class DecoratorSkin extends SkinBase {
{
JFXButton btnHelp = new JFXButton();
btnHelp.setFocusTraversable(false);
- btnHelp.setGraphic(SVG.HELP.createIcon(Theme.foregroundFillBinding(), -1));
+ btnHelp.setGraphic(SVG.HELP.createIcon(Themes.titleFillProperty()));
btnHelp.getStyleClass().add("jfx-decorator-button");
btnHelp.setOnAction(e -> FXUtils.openLink(Metadata.CONTACT_URL));
JFXButton btnMin = new JFXButton();
btnMin.setFocusTraversable(false);
- btnMin.setGraphic(SVG.MINIMIZE.createIcon(Theme.foregroundFillBinding(), -1));
+ btnMin.setGraphic(SVG.MINIMIZE.createIcon(Themes.titleFillProperty()));
btnMin.getStyleClass().add("jfx-decorator-button");
btnMin.setOnAction(e -> skinnable.minimize());
JFXButton btnClose = new JFXButton();
btnClose.setFocusTraversable(false);
- btnClose.setGraphic(SVG.CLOSE.createIcon(Theme.foregroundFillBinding(), -1));
+ btnClose.setGraphic(SVG.CLOSE.createIcon(Themes.titleFillProperty()));
btnClose.getStyleClass().add("jfx-decorator-button");
btnClose.setOnAction(e -> skinnable.close());
@@ -265,6 +265,8 @@ public class DecoratorSkin extends SkinBase {
private Node createNavBar(Decorator skinnable, double leftPaneWidth, boolean canBack, boolean canClose, boolean showCloseAsHome, boolean canRefresh, String title, Node titleNode) {
BorderPane navBar = new BorderPane();
+ navBar.getStyleClass().add("navigation-bar");
+
{
HBox navLeft = new HBox();
navLeft.setAlignment(Pos.CENTER_LEFT);
@@ -273,9 +275,8 @@ public class DecoratorSkin extends SkinBase {
if (canBack) {
JFXButton backNavButton = new JFXButton();
backNavButton.setFocusTraversable(false);
- backNavButton.setGraphic(SVG.ARROW_BACK.createIcon(Theme.foregroundFillBinding(), -1));
+ backNavButton.setGraphic(SVG.ARROW_BACK.createIcon(Themes.titleFillProperty()));
backNavButton.getStyleClass().add("jfx-decorator-button");
- backNavButton.ripplerFillProperty().set(Theme.whiteFill());
backNavButton.onActionProperty().bind(skinnable.onBackNavButtonActionProperty());
backNavButton.visibleProperty().set(canBack);
@@ -285,14 +286,13 @@ public class DecoratorSkin extends SkinBase {
if (canClose) {
JFXButton closeNavButton = new JFXButton();
closeNavButton.setFocusTraversable(false);
- closeNavButton.setGraphic(SVG.CLOSE.createIcon(Theme.foregroundFillBinding(), -1));
+ closeNavButton.setGraphic(SVG.CLOSE.createIcon(Themes.titleFillProperty()));
closeNavButton.getStyleClass().add("jfx-decorator-button");
- closeNavButton.ripplerFillProperty().set(Theme.whiteFill());
closeNavButton.onActionProperty().bind(skinnable.onCloseNavButtonActionProperty());
if (showCloseAsHome)
- closeNavButton.setGraphic(SVG.HOME.createIcon(Theme.foregroundFillBinding(), -1));
+ closeNavButton.setGraphic(SVG.HOME.createIcon(Themes.titleFillProperty()));
else
- closeNavButton.setGraphic(SVG.CLOSE.createIcon(Theme.foregroundFillBinding(), -1));
+ closeNavButton.setGraphic(SVG.CLOSE.createIcon(Themes.titleFillProperty()));
navLeft.getChildren().add(closeNavButton);
}
@@ -333,9 +333,8 @@ public class DecoratorSkin extends SkinBase {
HBox navRight = new HBox();
navRight.setAlignment(Pos.CENTER_RIGHT);
JFXButton refreshNavButton = new JFXButton();
- refreshNavButton.setGraphic(SVG.REFRESH.createIcon(Theme.foregroundFillBinding(), -1));
+ refreshNavButton.setGraphic(SVG.REFRESH.createIcon(Themes.titleFillProperty()));
refreshNavButton.getStyleClass().add("jfx-decorator-button");
- refreshNavButton.ripplerFillProperty().set(Theme.whiteFill());
refreshNavButton.onActionProperty().bind(skinnable.onRefreshNavButtonActionProperty());
Rectangle separator = new Rectangle();
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ExportWizardProvider.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ExportWizardProvider.java
index cb7407ee9..eac1a55a5 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ExportWizardProvider.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ExportWizardProvider.java
@@ -126,7 +126,7 @@ public final class ExportWizardProvider implements WizardProvider {
exported.setBackgroundImageType(config().getBackgroundImageType());
exported.setBackgroundImage(config().getBackgroundImage());
- exported.setTheme(config().getTheme());
+ exported.setThemeColor(config().getThemeColor());
exported.setDownloadType(config().getDownloadType());
exported.setPreferredLoginType(config().getPreferredLoginType());
exported.getAuthlibInjectorServers().setAll(config().getAuthlibInjectorServers());
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/AboutPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/AboutPage.java
index 153ad7a55..241093807 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/AboutPage.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/AboutPage.java
@@ -21,12 +21,14 @@ import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
+import javafx.beans.binding.Bindings;
import javafx.geometry.Insets;
import javafx.scene.control.ScrollPane;
import javafx.scene.image.Image;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.Metadata;
+import org.jackhuang.hmcl.theme.Themes;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.construct.ComponentList;
import org.jackhuang.hmcl.ui.construct.IconedTwoLineListItem;
@@ -105,6 +107,12 @@ public final class AboutPage extends StackPane {
getChildren().setAll(scrollPane);
}
+ private static Image loadImage(String url) {
+ return url.startsWith("/")
+ ? FXUtils.newBuiltinImage(url)
+ : new Image(url);
+ }
+
private static ComponentList loadIconedTwoLineList(String path) {
ComponentList componentList = new ComponentList();
@@ -122,10 +130,14 @@ public final class AboutPage extends StackPane {
IconedTwoLineListItem item = new IconedTwoLineListItem();
if (obj.has("image")) {
- String image = obj.get("image").getAsString();
- item.setImage(image.startsWith("/")
- ? FXUtils.newBuiltinImage(image)
- : new Image(image));
+ JsonElement image = obj.get("image");
+ if (image.isJsonPrimitive()) {
+ item.setImage(loadImage(image.getAsString()));
+ } else if (image.isJsonObject()) {
+ item.imageProperty().bind(Bindings.when(Themes.darkModeProperty())
+ .then(loadImage(image.getAsJsonObject().get("dark").getAsString()))
+ .otherwise(loadImage(image.getAsJsonObject().get("light").getAsString())));
+ }
}
if (obj.has("title"))
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/FeedbackPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/FeedbackPage.java
index 4b39d6197..bd6f10e37 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/FeedbackPage.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/FeedbackPage.java
@@ -17,9 +17,11 @@
*/
package org.jackhuang.hmcl.ui.main;
+import javafx.beans.binding.Bindings;
import javafx.geometry.Insets;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.VBox;
+import org.jackhuang.hmcl.theme.Themes;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.construct.ComponentList;
import org.jackhuang.hmcl.ui.construct.IconedTwoLineListItem;
@@ -50,7 +52,9 @@ public class FeedbackPage extends SpinnerPane {
users.setExternalLink(Metadata.GROUPS_URL);
IconedTwoLineListItem github = new IconedTwoLineListItem();
- github.setImage(FXUtils.newBuiltinImage("/assets/img/github.png"));
+ github.imageProperty().bind(Bindings.when(Themes.darkModeProperty())
+ .then(FXUtils.newBuiltinImage("/assets/img/github-white.png"))
+ .otherwise(FXUtils.newBuiltinImage("/assets/img/github.png")));
github.setTitle(i18n("feedback.github"));
github.setSubtitle(i18n("feedback.github.statement"));
github.setExternalLink("https://github.com/HMCL-dev/HMCL/issues/new/choose");
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaManagementPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaManagementPage.java
index 0f5bec1a3..9dce27bed 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaManagementPage.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaManagementPage.java
@@ -35,7 +35,6 @@ import org.jackhuang.hmcl.java.JavaManager;
import org.jackhuang.hmcl.java.JavaRuntime;
import org.jackhuang.hmcl.setting.ConfigHolder;
import org.jackhuang.hmcl.setting.DownloadProviders;
-import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.*;
@@ -263,7 +262,7 @@ public final class JavaManagementPage extends ListPageBase control.onReveal());
FXUtils.installFastTooltip(revealButton, i18n("reveal.in_file_manager"));
@@ -276,12 +275,12 @@ public final class JavaManagementPage extends ListPageBase skinnable.onReveal());
FXUtils.installFastTooltip(revealButton, i18n("reveal.in_file_manager"));
@@ -169,7 +168,7 @@ public final class JavaRestorePage extends ListPageBase skinnable.onRemove());
FXUtils.installFastTooltip(removeButton, i18n("java.disabled.management.remove"));
@@ -177,7 +176,7 @@ public final class JavaRestorePage extends ListPageBase skinnable.onRestore());
FXUtils.installFastTooltip(restoreButton, i18n("java.disabled.management.restore"));
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java
index 67f33d904..9a08f8605 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java
@@ -50,7 +50,6 @@ import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.setting.DownloadProviders;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.Profiles;
-import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.Controllers;
@@ -152,7 +151,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
}
});
btnHide.getStyleClass().add("announcement-close-button");
- btnHide.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), 20));
+ btnHide.setGraphic(SVG.CLOSE.createIcon(20));
titleBar.setRight(btnHide);
TextFlow body = FXUtils.segmentToTextFlow(content, Controllers::onHyperlinkAction);
@@ -188,7 +187,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
StackPane.setMargin(hBox, new Insets(9, 12, 9, 16));
{
Label lblIcon = new Label();
- lblIcon.setGraphic(SVG.UPDATE.createIcon(Theme.whiteFill(), 20));
+ lblIcon.setGraphic(SVG.UPDATE.createIcon(20));
TwoLineListItem prompt = new TwoLineListItem();
prompt.setSubtitle(i18n("update.bubble.subtitle"));
@@ -200,7 +199,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
}
JFXButton closeUpdateButton = new JFXButton();
- closeUpdateButton.setGraphic(SVG.CLOSE.createIcon(Theme.whiteFill(), 10));
+ closeUpdateButton.setGraphic(SVG.CLOSE.createIcon(10));
StackPane.setAlignment(closeUpdateButton, Pos.TOP_RIGHT);
closeUpdateButton.getStyleClass().add("toggle-icon-tiny");
StackPane.setMargin(closeUpdateButton, new Insets(5));
@@ -277,7 +276,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
menuButton.setOnAction(e -> onMenu());
menuButton.setClip(new Rectangle(211, -100, 100, 200));
StackPane graphic = new StackPane();
- Node svg = SVG.ARROW_DROP_UP.createIcon(Theme.foregroundFillBinding(), 30);
+ Node svg = SVG.ARROW_DROP_UP.createIcon(30);
StackPane.setAlignment(svg, Pos.CENTER_RIGHT);
graphic.getChildren().setAll(svg);
graphic.setTranslateX(6);
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/PersonalizationPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/PersonalizationPage.java
index 3f0d75845..cb73a814a 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/PersonalizationPage.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/PersonalizationPage.java
@@ -32,12 +32,11 @@ import javafx.scene.control.ColorPicker;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.*;
-import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontSmoothingType;
import org.jackhuang.hmcl.setting.EnumBackgroundImage;
import org.jackhuang.hmcl.setting.FontManager;
-import org.jackhuang.hmcl.setting.Theme;
+import org.jackhuang.hmcl.theme.ThemeColor;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.ui.construct.*;
@@ -78,6 +77,22 @@ public class PersonalizationPage extends StackPane {
getChildren().setAll(scrollPane);
ComponentList themeList = new ComponentList();
+ {
+ BorderPane brightnessPane = new BorderPane();
+ themeList.getContent().add(brightnessPane);
+
+ Label left = new Label(i18n("settings.launcher.brightness"));
+ BorderPane.setAlignment(left, Pos.CENTER_LEFT);
+
+ brightnessPane.setLeft(left);
+
+ JFXComboBox cboBrightness = new JFXComboBox<>(
+ FXCollections.observableArrayList("auto", "light", "dark"));
+ cboBrightness.setConverter(FXUtils.stringConverter(name -> i18n("settings.launcher.brightness." + name)));
+ cboBrightness.valueProperty().bindBidirectional(config().themeBrightnessProperty());
+ brightnessPane.setRight(cboBrightness);
+ }
+
{
BorderPane themePane = new BorderPane();
themeList.getContent().add(themePane);
@@ -90,10 +105,9 @@ public class PersonalizationPage extends StackPane {
themeColorPickerContainer.setMinHeight(30);
themePane.setRight(themeColorPickerContainer);
- ColorPicker picker = new JFXColorPicker(Color.web(Theme.getTheme().getColor()));
- picker.getCustomColors().setAll(Theme.SUGGESTED_COLORS);
- picker.setOnAction(e ->
- config().setTheme(Theme.custom(Theme.getColorDisplayName(picker.getValue()))));
+ ColorPicker picker = new JFXColorPicker();
+ picker.getCustomColors().setAll(ThemeColor.STANDARD_COLORS.stream().map(ThemeColor::color).toList());
+ ThemeColor.bindBidirectional(picker, config().themeColorProperty());
themeColorPickerContainer.getChildren().setAll(picker);
Platform.runLater(() -> JFXDepthManager.setDepth(picker, 0));
}
@@ -264,7 +278,7 @@ public class PersonalizationPage extends StackPane {
JFXButton clearButton = new JFXButton();
clearButton.getStyleClass().add("toggle-icon4");
- clearButton.setGraphic(SVG.RESTORE.createIcon(Theme.blackFill(), -1));
+ clearButton.setGraphic(SVG.RESTORE.createIcon());
clearButton.setOnAction(e -> cboFont.setValue(null));
hBox.getChildren().setAll(cboFont, clearButton);
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java
index ddc700a71..f4dee1870 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java
@@ -93,13 +93,13 @@ public final class SettingsPage extends SettingsView {
lblUpdateSub.getStyleClass().setAll("subtitle-label");
lblUpdate.setText(i18n("update"));
- lblUpdate.getStyleClass().setAll();
+ lblUpdate.getStyleClass().setAll("title-label");
} else {
lblUpdateSub.setText(i18n("update.latest"));
lblUpdateSub.getStyleClass().setAll("subtitle-label");
lblUpdate.setText(i18n("update"));
- lblUpdate.getStyleClass().setAll();
+ lblUpdate.getStyleClass().setAll("title-label");
}
};
UpdateChecker.latestVersionProperty().addListener(new WeakInvalidationListener(updateListener));
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsView.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsView.java
index 6b9022164..e4329ed78 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsView.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsView.java
@@ -27,11 +27,8 @@ import javafx.scene.Cursor;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.*;
-import javafx.scene.text.Text;
import javafx.scene.text.TextAlignment;
-import javafx.scene.text.TextFlow;
import org.jackhuang.hmcl.setting.EnumCommonDirectory;
-import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.ui.construct.ComponentList;
@@ -115,6 +112,7 @@ public abstract class SettingsView extends StackPane {
VBox headerLeft = new VBox();
lblUpdate = new Label(i18n("update"));
+ lblUpdate.getStyleClass().add("title-label");
lblUpdateSub = new Label();
lblUpdateSub.getStyleClass().add("subtitle-label");
@@ -126,7 +124,7 @@ public abstract class SettingsView extends StackPane {
btnUpdate = new JFXButton();
btnUpdate.setOnAction(e -> onUpdate());
btnUpdate.getStyleClass().add("toggle-icon4");
- btnUpdate.setGraphic(SVG.UPDATE.createIcon(Theme.blackFill(), 20));
+ btnUpdate.setGraphic(SVG.UPDATE.createIcon(20));
updatePane.setHeaderRight(btnUpdate);
}
@@ -138,7 +136,7 @@ public abstract class SettingsView extends StackPane {
chkUpdateStable = new JFXRadioButton(i18n("update.channel.stable"));
chkUpdateDev = new JFXRadioButton(i18n("update.channel.dev"));
- TextFlow noteWrapper = new TextFlow(new Text(i18n("update.note")));
+ Label noteWrapper = new Label(i18n("update.note"));
VBox.setMargin(noteWrapper, new Insets(10, 0, 0, 0));
content.getChildren().setAll(chkUpdateStable, chkUpdateDev, noteWrapper);
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/profile/ProfileListItemSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/profile/ProfileListItemSkin.java
index 77c15b170..2366242f4 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/profile/ProfileListItemSkin.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/profile/ProfileListItemSkin.java
@@ -24,7 +24,6 @@ import javafx.scene.Node;
import javafx.scene.control.SkinBase;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
-import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
@@ -64,7 +63,7 @@ public class ProfileListItemSkin extends SkinBase {
btnRemove.setOnAction(e -> skinnable.remove());
btnRemove.getStyleClass().add("toggle-icon4");
BorderPane.setAlignment(btnRemove, Pos.CENTER);
- btnRemove.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), 14));
+ btnRemove.setGraphic(SVG.CLOSE.createIcon(14));
right.getChildren().add(btnRemove);
root.setRight(right);
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/terracotta/TerracottaControllerPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/terracotta/TerracottaControllerPage.java
index e4b5ca2d2..ea8d364a2 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/terracotta/TerracottaControllerPage.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/terracotta/TerracottaControllerPage.java
@@ -46,7 +46,6 @@ import javafx.scene.text.TextFlow;
import org.jackhuang.hmcl.game.LauncherHelper;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.Profiles;
-import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.terracotta.TerracottaManager;
@@ -559,7 +558,7 @@ public class TerracottaControllerPage extends StackPane {
Label description = new Label(link.description().getText(I18n.getLocale().getCandidateLocales()));
HBox placeholder = new HBox();
HBox.setHgrow(placeholder, Priority.ALWAYS);
- Node icon = SVG.OPEN_IN_NEW.createIcon(Theme.blackFill(), 16);
+ Node icon = SVG.OPEN_IN_NEW.createIcon(16);
node.getChildren().setAll(description, placeholder, icon);
String url = link.link();
@@ -670,11 +669,11 @@ public class TerracottaControllerPage extends StackPane {
}
public void setLeftIcon(SVG left) {
- this.left.set(left.createIcon(Theme.blackFill(), 28));
+ this.left.set(left.createIcon(28));
}
public void setRightIcon(SVG right) {
- this.right.set(right.createIcon(Theme.blackFill(), 28));
+ this.right.set(right.createIcon(28));
}
}
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java
index c62da41ee..db518f389 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java
@@ -38,7 +38,6 @@ import org.jackhuang.hmcl.mod.ModLoaderType;
import org.jackhuang.hmcl.mod.RemoteMod;
import org.jackhuang.hmcl.mod.RemoteModRepository;
import org.jackhuang.hmcl.setting.Profile;
-import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.task.FileDownloadTask;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
@@ -394,15 +393,15 @@ public class DownloadPage extends Control implements DecoratorPage {
switch (dataItem.getVersionType()) {
case Alpha:
content.addTag(i18n("mods.channel.alpha"));
- graphicPane.getChildren().setAll(SVG.ALPHA_CIRCLE.createIcon(Theme.blackFill(), 24));
+ graphicPane.getChildren().setAll(SVG.ALPHA_CIRCLE.createIcon(24));
break;
case Beta:
content.addTag(i18n("mods.channel.beta"));
- graphicPane.getChildren().setAll(SVG.BETA_CIRCLE.createIcon(Theme.blackFill(), 24));
+ graphicPane.getChildren().setAll(SVG.BETA_CIRCLE.createIcon(24));
break;
case Release:
content.addTag(i18n("mods.channel.release"));
- graphicPane.getChildren().setAll(SVG.RELEASE_CIRCLE.createIcon(Theme.blackFill(), 24));
+ graphicPane.getChildren().setAll(SVG.RELEASE_CIRCLE.createIcon(24));
break;
}
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListItemSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListItemSkin.java
index 6cad34694..f49a901e5 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListItemSkin.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListItemSkin.java
@@ -26,7 +26,6 @@ import javafx.scene.control.SkinBase;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
-import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.ui.construct.IconedMenuItem;
@@ -81,7 +80,7 @@ public class GameListItemSkin extends SkinBase {
JFXButton btnUpgrade = new JFXButton();
btnUpgrade.setOnAction(e -> skinnable.update());
btnUpgrade.getStyleClass().add("toggle-icon4");
- btnUpgrade.setGraphic(FXUtils.limitingSize(SVG.UPDATE.createIcon(Theme.blackFill(), 24), 24, 24));
+ btnUpgrade.setGraphic(FXUtils.limitingSize(SVG.UPDATE.createIcon(24), 24, 24));
FXUtils.installFastTooltip(btnUpgrade, i18n("version.update"));
right.getChildren().add(btnUpgrade);
}
@@ -91,7 +90,7 @@ public class GameListItemSkin extends SkinBase {
btnLaunch.setOnAction(e -> skinnable.launch());
btnLaunch.getStyleClass().add("toggle-icon4");
BorderPane.setAlignment(btnLaunch, Pos.CENTER);
- btnLaunch.setGraphic(FXUtils.limitingSize(SVG.ROCKET_LAUNCH.createIcon(Theme.blackFill(), 24), 24, 24));
+ btnLaunch.setGraphic(FXUtils.limitingSize(SVG.ROCKET_LAUNCH.createIcon(24), 24, 24));
FXUtils.installFastTooltip(btnLaunch, i18n("version.launch.test"));
right.getChildren().add(btnLaunch);
}
@@ -106,7 +105,7 @@ public class GameListItemSkin extends SkinBase {
});
btnManage.getStyleClass().add("toggle-icon4");
BorderPane.setAlignment(btnManage, Pos.CENTER);
- btnManage.setGraphic(FXUtils.limitingSize(SVG.MORE_VERT.createIcon(Theme.blackFill(), 24), 24, 24));
+ btnManage.setGraphic(FXUtils.limitingSize(SVG.MORE_VERT.createIcon(24), 24, 24));
FXUtils.installFastTooltip(btnManage, i18n("settings.game.management"));
right.getChildren().add(btnManage);
}
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java
index 09fb47b00..6ed6b48f8 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java
@@ -46,7 +46,6 @@ import org.jackhuang.hmcl.mod.RemoteModRepository;
import org.jackhuang.hmcl.mod.curse.CurseForgeRemoteModRepository;
import org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository;
import org.jackhuang.hmcl.setting.Profile;
-import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.setting.VersionIconType;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
@@ -591,15 +590,15 @@ final class ModListPageSkin extends SkinBase {
imageView.setImage(VersionIconType.COMMAND.getIcon());
restoreButton.getStyleClass().add("toggle-icon4");
- restoreButton.setGraphic(FXUtils.limitingSize(SVG.RESTORE.createIcon(Theme.blackFill(), 24), 24, 24));
+ restoreButton.setGraphic(FXUtils.limitingSize(SVG.RESTORE.createIcon(24), 24, 24));
FXUtils.installFastTooltip(restoreButton, i18n("mods.restore"));
revealButton.getStyleClass().add("toggle-icon4");
- revealButton.setGraphic(FXUtils.limitingSize(SVG.FOLDER.createIcon(Theme.blackFill(), 24), 24, 24));
+ revealButton.setGraphic(FXUtils.limitingSize(SVG.FOLDER.createIcon(24), 24, 24));
infoButton.getStyleClass().add("toggle-icon4");
- infoButton.setGraphic(FXUtils.limitingSize(SVG.INFO.createIcon(Theme.blackFill(), 24), 24, 24));
+ infoButton.setGraphic(FXUtils.limitingSize(SVG.INFO.createIcon(24), 24, 24));
container.getChildren().setAll(checkBox, imageView, content, restoreButton, revealButton, infoButton);
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/SchematicsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/SchematicsPage.java
index 8c529b80d..5be8c8cdd 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/SchematicsPage.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/SchematicsPage.java
@@ -33,7 +33,6 @@ import javafx.scene.layout.StackPane;
import javafx.stage.FileChooser;
import org.jackhuang.hmcl.schematic.LitematicFile;
import org.jackhuang.hmcl.setting.Profile;
-import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.*;
@@ -240,7 +239,7 @@ public final class SchematicsPage extends ListPageBase impl
StackPane icon = new StackPane();
icon.setPrefSize(size, size);
icon.setMaxSize(size, size);
- icon.getChildren().add(getIcon().createIcon(Theme.blackFill(), size));
+ icon.getChildren().add(getIcon().createIcon(size));
return icon;
}
@@ -580,12 +579,12 @@ public final class SchematicsPage extends ListPageBase impl
JFXButton btnReveal = new JFXButton();
FXUtils.installFastTooltip(btnReveal, i18n("reveal.in_file_manager"));
btnReveal.getStyleClass().add("toggle-icon4");
- btnReveal.setGraphic(SVG.FOLDER_OPEN.createIcon(Theme.blackFill(), -1));
+ btnReveal.setGraphic(SVG.FOLDER_OPEN.createIcon());
btnReveal.setOnAction(event -> item.onReveal());
JFXButton btnDelete = new JFXButton();
btnDelete.getStyleClass().add("toggle-icon4");
- btnDelete.setGraphic(SVG.DELETE_FOREVER.createIcon(Theme.blackFill(), -1));
+ btnDelete.setGraphic(SVG.DELETE_FOREVER.createIcon());
btnDelete.setOnAction(event ->
Controllers.confirm(i18n("button.remove.confirm"), i18n("button.remove"),
item::onDelete, null));
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java
index aefb20b83..3c3521936 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java
@@ -23,7 +23,6 @@ import javafx.scene.layout.FlowPane;
import javafx.stage.FileChooser;
import org.jackhuang.hmcl.event.Event;
import org.jackhuang.hmcl.setting.Profile;
-import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.setting.VersionIconType;
import org.jackhuang.hmcl.setting.VersionSetting;
import org.jackhuang.hmcl.ui.Controllers;
@@ -93,7 +92,7 @@ public class VersionIconDialog extends DialogPane {
}
private Node createCustomIcon() {
- Node shape = SVG.ADD_CIRCLE.createIcon(Theme.blackFill(), 32);
+ Node shape = SVG.ADD_CIRCLE.createIcon(32);
shape.setMouseTransparent(true);
RipplerContainer container = new RipplerContainer(shape);
FXUtils.setLimitWidth(container, 36);
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java
index 276df89c5..331f334e3 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java
@@ -27,7 +27,6 @@ import javafx.scene.Node;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
-import javafx.scene.paint.Paint;
import org.jackhuang.hmcl.event.EventBus;
import org.jackhuang.hmcl.event.EventPriority;
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
@@ -312,7 +311,7 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
}
public static Node wrap(SVG svg) {
- return wrap(svg.createIcon((Paint) null, 20));
+ return wrap(svg.createIcon(20));
}
public interface VersionLoadable {
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldBackupsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldBackupsPage.java
index 586495d1a..b1d910b69 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldBackupsPage.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldBackupsPage.java
@@ -31,7 +31,6 @@ import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.game.World;
import org.jackhuang.hmcl.game.WorldLockedException;
-import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.*;
@@ -258,14 +257,14 @@ public final class WorldBackupsPage extends ListPageBase skinnable.onReveal());
JFXButton btnDelete = new JFXButton();
right.getChildren().add(btnDelete);
FXUtils.installFastTooltip(btnDelete, i18n("world.backup.delete"));
btnDelete.getStyleClass().add("toggle-icon4");
- btnDelete.setGraphic(SVG.DELETE.createIcon(Theme.blackFill(), -1));
+ btnDelete.setGraphic(SVG.DELETE.createIcon());
btnDelete.setOnAction(event -> Controllers.confirm(i18n("button.remove.confirm"), i18n("button.remove"), skinnable::onDelete, null));
}
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java
index 34d109304..468309869 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java
@@ -33,7 +33,6 @@ import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.game.World;
-import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.FXUtils;
@@ -161,7 +160,7 @@ public final class WorldInfoPage extends SpinnerPane {
blur.setIterations(3);
FXUtils.onChangeAndOperate(visibility, isVisibility -> {
SVG icon = isVisibility ? SVG.VISIBILITY : SVG.VISIBILITY_OFF;
- visibilityButton.getChildren().setAll(icon.createIcon(Theme.blackFill(), 12));
+ visibilityButton.getChildren().setAll(icon.createIcon(12));
randomSeedLabel.setEffect(isVisibility ? null : blur);
});
}
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListItemSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListItemSkin.java
index 89b8006b5..3e1b75208 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListItemSkin.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListItemSkin.java
@@ -28,7 +28,6 @@ import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.game.World;
-import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.ui.construct.*;
@@ -89,7 +88,7 @@ public final class WorldListItemSkin extends SkinBase {
JFXButton btnMore = new JFXButton();
right.getChildren().add(btnMore);
btnMore.getStyleClass().add("toggle-icon4");
- btnMore.setGraphic(SVG.MORE_VERT.createIcon(Theme.blackFill(), -1));
+ btnMore.setGraphic(SVG.MORE_VERT.createIcon());
btnMore.setOnAction(event -> showPopupMenu(JFXPopup.PopupHPosition.RIGHT, 0, root.getHeight()));
}
diff --git a/HMCL/src/main/resources/assets/about/deps.json b/HMCL/src/main/resources/assets/about/deps.json
index d6d705f82..d72871ec0 100644
--- a/HMCL/src/main/resources/assets/about/deps.json
+++ b/HMCL/src/main/resources/assets/about/deps.json
@@ -78,5 +78,10 @@
"title": "EasyTier",
"subtitle": "Copyright 2024-present Easytier Programme within The Commons Conservancy",
"externalLink": "https://github.com/EasyTier/EasyTier"
+ },
+ {
+ "title": "MonetFX",
+ "subtitle": "Copyright © 2025 Glavo.\nLicensed under the Apache 2.0 License.",
+ "externalLink": "https://github.com/Glavo/MonetFX"
}
]
\ No newline at end of file
diff --git a/HMCL/src/main/resources/assets/about/thanks.json b/HMCL/src/main/resources/assets/about/thanks.json
index 2782c38bf..8e4540d9c 100644
--- a/HMCL/src/main/resources/assets/about/thanks.json
+++ b/HMCL/src/main/resources/assets/about/thanks.json
@@ -71,7 +71,10 @@
"externalLink" : "https://github.com/mcmod-info-mirror"
},
{
- "image" : "/assets/img/github.png",
+ "image" : {
+ "light" : "/assets/img/github.png",
+ "dark" : "/assets/img/github-white.png"
+ },
"titleLocalized" : "about.thanks_to.contributors",
"subtitleLocalized" : "about.thanks_to.contributors.statement",
"externalLink" : "https://github.com/HMCL-dev/HMCL/graphs/contributors"
diff --git a/HMCL/src/main/resources/assets/css/blue.css b/HMCL/src/main/resources/assets/css/blue.css
index c02cbb1b8..6c2c590bc 100644
--- a/HMCL/src/main/resources/assets/css/blue.css
+++ b/HMCL/src/main/resources/assets/css/blue.css
@@ -1,28 +1,60 @@
-/**
- * Hello Minecraft! Launcher
- * Copyright (C) 2020 huangyuhui and contributors
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-.root {
- -fx-base-color: #5C6BC0;
- -fx-base-darker-color: derive(-fx-base-color, -10%);
- -fx-base-check-color: derive(-fx-base-color, 30%);
- -fx-rippler-color: rgba(92, 107, 192, 0.3);
- -fx-base-rippler-color: derive(rgba(92, 107, 192, 0.3), 100%);
- -fx-base-disabled-text-fill: rgba(256, 256, 256, 0.7);
- -fx-base-text-fill: white;
-
- -theme-thumb: rgba(92, 107, 192, 0.7);
+* {
+ -monet-primary: #4352A5;
+ -monet-on-primary: #FFFFFF;
+ -monet-primary-container: #5C6BC0;
+ -monet-on-primary-container: #F8F6FF;
+ -monet-primary-fixed: #DEE0FF;
+ -monet-primary-fixed-dim: #BAC3FF;
+ -monet-on-primary-fixed: #00105B;
+ -monet-on-primary-fixed-variant: #2F3F92;
+ -monet-secondary: #575C7F;
+ -monet-on-secondary: #FFFFFF;
+ -monet-secondary-container: #D0D5FD;
+ -monet-on-secondary-container: #565B7D;
+ -monet-secondary-fixed: #DEE0FF;
+ -monet-secondary-fixed-dim: #BFC4EC;
+ -monet-on-secondary-fixed: #141938;
+ -monet-on-secondary-fixed-variant: #3F4566;
+ -monet-tertiary: #775200;
+ -monet-on-tertiary: #FFFFFF;
+ -monet-tertiary-container: #976900;
+ -monet-on-tertiary-container: #FFF6EE;
+ -monet-tertiary-fixed: #FFDEAC;
+ -monet-tertiary-fixed-dim: #F6BD58;
+ -monet-on-tertiary-fixed: #281900;
+ -monet-on-tertiary-fixed-variant: #5F4100;
+ -monet-error: #BA1A1A;
+ -monet-on-error: #FFFFFF;
+ -monet-error-container: #FFDAD6;
+ -monet-on-error-container: #93000A;
+ -monet-surface: #FBF8FF;
+ -monet-on-surface: #1B1B21;
+ -monet-surface-dim: #DBD9E1;
+ -monet-surface-bright: #FBF8FF;
+ -monet-surface-container-lowest: #FFFFFF;
+ -monet-surface-container-low: #F5F2FA;
+ -monet-surface-container: #EFEDF5;
+ -monet-surface-container-high: #E9E7EF;
+ -monet-surface-container-highest: #E3E1E9;
+ -monet-surface-variant: #E2E1EF;
+ -monet-on-surface-variant: #454651;
+ -monet-background: #FBF8FF;
+ -monet-on-background: #1B1B21;
+ -monet-outline: #767683;
+ -monet-outline-variant: #C6C5D3;
+ -monet-shadow: #000000;
+ -monet-scrim: #000000;
+ -monet-inverse-surface: #303036;
+ -monet-inverse-on-surface: #F2EFF7;
+ -monet-inverse-primary: #BAC3FF;
+ -monet-surface-tint: #4858AB;
+ -monet-primary-seed: #5C6BC0;
+ -monet-primary-transparent-50: #4352A580;
+ -monet-secondary-container-transparent-50: #D0D5FD80;
+ -monet-surface-transparent-50: #FBF8FF80;
+ -monet-surface-transparent-80: #FBF8FFCC;
+ -monet-on-surface-variant-transparent-38: #45465161;
+ -monet-surface-container-low-transparent-80: #F5F2FACC;
+ -monet-secondary-container-transparent-80: #D0D5FDCC;
+ -warning-tag-background: #F1AEB5;
}
\ No newline at end of file
diff --git a/HMCL/src/main/resources/assets/css/root.css b/HMCL/src/main/resources/assets/css/root.css
index 0d7df39db..aa3a9823d 100644
--- a/HMCL/src/main/resources/assets/css/root.css
+++ b/HMCL/src/main/resources/assets/css/root.css
@@ -15,31 +15,33 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
+
+/* TODO: Monet */
.root {
+ -fx-base-check-color: derive(-monet-primary-container, 30%);
+ -fx-base-text-fill: -monet-on-primary-container;
}
.svg {
- -fx-fill: black;
+ -fx-fill: -monet-on-surface;
+}
+
+.label {
+ -fx-text-fill: -monet-on-surface;
}
.scroll-bar .thumb {
- -fx-fill: -theme-thumb;
+ -fx-fill: -monet-surface-tint;
-fx-arc-width: 0;
-fx-arc-height: 0;
}
-.disabled Label {
- -fx-text-fill: rgba(0, 0, 0, 0.5);
-}
-
.title-label {
- -fx-font-size: 16.0px;
- -fx-padding: 14.0px;
- -fx-text-fill: rgba(0.0, 0.0, 0.0, 0.87);
+ -fx-text-fill: -monet-on-surface;
}
.subtitle-label {
- -fx-text-fill: rgba(0, 0, 0, 0.5);
+ -fx-text-fill: -monet-on-surface-variant;
}
.hint {
@@ -49,24 +51,18 @@
-fx-padding: 6;
}
-/*
- Colors are picked from bootstrap
-
- https://getbootstrap.com/docs/5.3/components/alerts/
- */
-
.hint.info {
- -fx-background-color: #cfe2ff;
- -fx-border-color: #9ec5fe;
+ -fx-background-color: -monet-primary-fixed-dim;
+ -fx-border-color: -monet-outline;
}
.hint.info Text, .hint.info .svg {
- -fx-fill: #052c65;
+ -fx-fill: -monet-on-primary-fixed;
}
.hint.success {
-fx-background-color: #d1e7dd;
- -fx-border-color: #a3cfbb;
+ -fx-border-color: -monet-outline;
}
.hint.success Text, .hint.success .svg {
@@ -74,21 +70,21 @@
}
.hint.error {
- -fx-background-color: #f8d7da;
- -fx-border-color: #f1aeb5;
+ -fx-background-color: -monet-error-container;
+ -fx-border-color: -monet-outline;
}
.hint.error Text, .hint.error .svg {
- -fx-fill: #58151c;
+ -fx-fill: -monet-on-error-container;
}
.hint.warning {
- -fx-background-color: #fff3cd;
- -fx-border-color: #ffe69c;
+ -fx-background-color: -monet-tertiary-container;
+ -fx-border-color: -monet-outline;
}
.hint.warning Text, .hint.warning .svg {
- -fx-fill: #664d03;
+ -fx-fill: -monet-on-tertiary-container;
}
.skin-pane .jfx-text-field {
@@ -103,7 +99,7 @@
}
.memory-used {
- -fx-background-color: -fx-base-darker-color;
+ -fx-background-color: -monet-primary;
}
.memory-used:disabled {
@@ -111,7 +107,7 @@
}
.memory-allocate {
- -fx-background-color: derive(-fx-base-color, 100%);
+ -fx-background-color: -monet-primary-transparent-50;
}
.memory-allocate:disabled {
@@ -119,7 +115,7 @@
}
.memory-total {
- -fx-background-color: -fx-base-rippler-color;
+ -fx-background-color: -monet-surface-container;
}
.memory-total:disabled {
@@ -127,7 +123,7 @@
}
.update-label {
- -fx-text-fill: red;
+ -fx-text-fill: -monet-tertiary;
}
.radio-button-title-label {
@@ -141,11 +137,18 @@
}
.announcement .title {
+ -fx-text-fill: -monet-on-surface;
-fx-font-size: 14px;
-fx-font-weight: BOLD;
}
-.announcement JFXHyperlink, .announcement Text {
+.announcement Text {
+ -fx-fill: -monet-on-surface;
+ -fx-font-size: 13px;
+}
+
+.announcement .hyperlink {
+ -fx-fill: #0070E0;
-fx-font-size: 13px;
}
@@ -178,16 +181,16 @@
}
.advanced-list-item:selected > .rippler-container > .container {
- -fx-background-color: -fx-base-rippler-color;
+ -fx-background-color: -monet-secondary-container-transparent-50;
}
.advanced-list-item:selected > .rippler-container > .container > .two-line-list-item > .first-line > .title {
- -fx-text-fill: -fx-base-color;
+ -fx-text-fill: -monet-on-secondary-container;
-fx-font-weight: bold;
}
.advanced-list-item:selected .svg {
- -fx-fill: -fx-base-color;
+ -fx-fill: -monet-on-secondary-container;
}
.navigation-drawer-item > .rippler-container > .container > VBox {
@@ -207,20 +210,20 @@
}
.profile-list-item:selected > .rippler-container > BorderPane {
- -fx-background-color: -fx-base-rippler-color;
+ -fx-background-color: -monet-secondary-container-transparent-50;
}
.profile-list-item:selected > .rippler-container > BorderPane > .two-line-list-item > .first-line > .title {
- -fx-text-fill: -fx-base-color;
+ -fx-text-fill: -monet-on-secondary-container;
-fx-font-weight: bold;
}
.profile-list-item:selected .svg {
- -fx-fill: -fx-base-color;
+ -fx-fill: -monet-on-secondary-container;
}
.notice-pane > .label {
- -fx-text-fill: #0079FF;
+ -fx-text-fill: -monet-on-surface;
-fx-font-size: 20;
-fx-wrap-text: true;
-fx-text-alignment: CENTER;
@@ -231,6 +234,14 @@
-fx-padding: 8 16 8 16;
}
+.class-title Text {
+ -fx-fill: -monet-on-surface;
+}
+
+.class-title Rectangle {
+ -fx-fill: -monet-on-surface-variant;
+}
+
.advanced-list-box-item {
}
@@ -240,7 +251,7 @@
}
.iconed-item {
- -jfx-rippler-fill: -fx-base-color;
+ -jfx-rippler-fill: -monet-secondary-container;
}
.iconed-item .iconed-item-container {
@@ -251,7 +262,7 @@
}
.iconed-menu-item {
- -jfx-rippler-fill: -fx-base-color;
+ -jfx-rippler-fill: -monet-secondary-container;
}
.iconed-menu-item .iconed-item-container {
@@ -266,7 +277,7 @@
}
.popup-menu .scroll-bar .thumb {
- -fx-fill: rgba(0, 0, 0, 0.5);
+ -fx-fill: -monet-surface-tint;
}
.popup-menu-content {
@@ -274,28 +285,28 @@
}
.two-line-list-item > .first-line > .title {
- -fx-text-fill: #292929;
- -fx-font-size: 15px;
+ -fx-text-fill: -monet-on-surface;
+ -fx-font-size: 15px;
-fx-padding: 0 8 0 0;
}
.two-line-list-item > HBox > .subtitle {
- -fx-text-fill: rgba(0, 0, 0, 0.5);
+ -fx-text-fill: -monet-on-surface-variant;
-fx-font-weight: normal;
-fx-font-size: 12px;
}
.two-line-list-item > .first-line > .tag {
- -fx-text-fill: -fx-base-color;
- -fx-background-color: -fx-base-rippler-color;
+ -fx-text-fill: -monet-on-secondary-container;
+ -fx-background-color: -monet-secondary-container;
-fx-padding: 2;
-fx-font-weight: normal;
-fx-font-size: 12px;
}
.two-line-list-item > .first-line > .tag-warning {
- -fx-text-fill: #d34336;;
- -fx-background-color: #f1aeb5;
+ -fx-text-fill: -monet-on-error-container;
+ -fx-background-color: -warning-tag-background;
-fx-padding: 2;
-fx-font-weight: normal;
-fx-font-size: 12px;
@@ -328,7 +339,7 @@
.bubble > HBox > .two-line-list-item > .first-line > .title,
.bubble > HBox > .two-line-list-item > HBox > .subtitle {
- -fx-text-fill: white;
+ -fx-text-fill: white;
}
.sponsor-pane {
@@ -340,7 +351,7 @@
}
.installer-item:list-item {
- -fx-border-color: #e0e0e0;
+ -fx-border-color: -monet-outline-variant;
-fx-border-width: 0 0 1 0;
-fx-alignment: center-left;
}
@@ -354,7 +365,7 @@
}
.installer-item:card {
- -fx-background-color: white;
+ -fx-background-color: -monet-surface;
-fx-background-radius: 4;
-fx-alignment: center;
-fx-pref-width: 180px;
@@ -369,23 +380,23 @@
******************************************************************************/
.launch-pane > .jfx-button {
- -fx-background-color: -fx-base-color;
+ -fx-background-color: -monet-primary-container;
-fx-cursor: hand;
}
.launch-pane > .jfx-button > StackPane > .jfx-rippler {
- -jfx-rippler-fill: white;
+ -jfx-rippler-fill: -monet-on-primary-container;
-jfx-mask-type: CIRCLE;
-fx-padding: 0.0;
}
.launch-pane > .jfx-button, .jfx-button * {
- -fx-text-fill: -fx-base-text-fill;
+ -fx-text-fill: -monet-on-primary-container;
-fx-font-size: 14px;
}
.launch-pane > Rectangle {
- -fx-fill: -fx-base-darker-color;
+ -fx-fill: -monet-primary-container;
}
/*******************************************************************************
@@ -395,7 +406,8 @@
******************************************************************************/
.tooltip {
- -fx-text-fill: WHITE;
+ -fx-text-fill: -monet-inverse-on-surface;
+ -fx-background-color: -monet-inverse-surface;
}
/*******************************************************************************
@@ -409,7 +421,7 @@
}
.tab-selected-line {
- -fx-background-color: derive(-fx-base-color, -30%);
+ -fx-background-color: derive(-monet-primary-container, -30%);
}
.tab-rippler {
@@ -446,7 +458,8 @@
.jfx-dialog-layout {
-fx-padding: 24.0px 24.0px 16.0px 24.0px;
- -fx-text-fill: rgba(0.0, 0.0, 0.0, 0.87);
+ -fx-background-color: -monet-surface-container-high;
+ -fx-text-fill: -monet-on-surface;
}
.jfx-layout-heading {
@@ -460,6 +473,10 @@
-fx-wrap-text: true;
}
+.jfx-layout-body Text {
+ -fx-fill: -monet-on-surface;
+}
+
.jfx-layout-actions {
-fx-pref-width: 400;
-fx-padding: 10.0px 0.0 0.0 0.0;
@@ -467,19 +484,26 @@
}
.dialog-error {
- -fx-text-fill: #d32f2f;
+ -fx-text-fill: -monet-error;
-fx-padding: 0.7em 0.8em;
}
.dialog-accept {
- -fx-text-fill: #03A9F4;
+ -fx-text-fill: -monet-primary;
-fx-padding: 0.7em 0.8em;
}
.dialog-cancel {
+ -fx-text-fill: -monet-on-surface-variant;
-fx-padding: 0.7em 0.8em;
}
+.task-executor-dialog-layout {
+ -fx-background-color: -monet-surface-container-high;
+ -fx-text-fill: -monet-on-surface;
+}
+
+
/*******************************************************************************
* *
* JFX Pop Up *
@@ -491,7 +515,7 @@
}
.jfx-popup-container {
- -fx-background-color: WHITE;
+ -fx-background-color: -monet-surface;
-fx-background-radius: 4;
}
@@ -500,15 +524,15 @@
}
.jfx-snackbar-content {
- -fx-background-color: #323232;
+ -fx-background-color: -monet-inverse-surface;
}
.jfx-snackbar-toast {
- -fx-text-fill: WHITE;
+ -fx-text-fill: -monet-inverse-on-surface;
}
.jfx-snackbar-action {
- -fx-text-fill: #ff4081;
+ -fx-text-fill: -monet-inverse-primary;
}
/*******************************************************************************
@@ -542,7 +566,7 @@
}
.jfx-tool-bar.background {
- -fx-background-color: -fx-base-color;
+ -fx-background-color: -monet-primary-container;
}
/*.jfx-tool-bar HBox {*/
@@ -565,11 +589,11 @@
}
.jfx-tool-bar Label {
- -fx-text-fill: WHITE;
+ -fx-text-fill: -monet-on-surface;
}
.jfx-tool-bar.gray-background Label {
- /* -fx-text-fill: BLACK; */
+ -fx-text-fill: BLACK;
}
.jfx-tool-bar .jfx-options-burger {
@@ -581,7 +605,7 @@
}
.jfx-tool-bar .jfx-rippler {
- -jfx-rippler-fill: WHITE;
+ -fx-text-fill: -monet-on-surface;
}
.jfx-tool-bar-second {
@@ -599,17 +623,18 @@
}
.jfx-tool-bar-second .jfx-rippler {
- -jfx-rippler-fill: WHITE;
+ -jfx-rippler-fill: -monet-on-surface;
}
.jfx-tool-bar-button {
+ -fx-text-fill: -monet-on-surface;
-fx-toggle-icon4-size: 37px;
-fx-pref-height: -fx-toggle-icon4-size;
-fx-max-height: -fx-toggle-icon4-size;
-fx-min-height: -fx-toggle-icon4-size;
-fx-background-radius: 5px;
-fx-background-color: transparent;
- -jfx-toggle-color: white;
+ -jfx-toggle-color: -monet-on-surface;
-jfx-untoggle-color: transparent;
}
@@ -619,7 +644,7 @@
}
.jfx-tool-bar-button .jfx-rippler {
- -jfx-rippler-fill: white;
+ -jfx-rippler-fill: -monet-on-surface;
-jfx-mask-type: CIRCLE;
}
@@ -628,8 +653,6 @@
-fx-background-radius: 5px;
-fx-max-height: 40px;
-fx-background-color: transparent;
- -jfx-toggle-color: rgba(128, 128, 255, 0.2);
- -jfx-untoggle-color: transparent;
}
.jfx-decorator-button .icon {
@@ -638,7 +661,6 @@
}
.jfx-decorator-button .jfx-rippler {
- -jfx-rippler-fill: white;
-jfx-mask-type: CIRCLE;
-fx-padding: 0.0;
}
@@ -649,17 +671,10 @@
* *
******************************************************************************/
- .jfx-radio-button {
- -jfx-selected-color: -fx-base-check-color;
- }
-
-.jfx-radio-button .radio {
- -fx-stroke-width: 2.0px;
- -fx-fill: transparent;
-}
-
-.jfx-radio-button .dot {
- -fx-fill: -fx-base-check-color;
+.jfx-radio-button {
+ -jfx-selected-color: -monet-primary;
+ -jfx-unselected-color: -monet-on-surface-variant;
+ -fx-text-fill: -monet-on-surface;
}
/*******************************************************************************
@@ -669,8 +684,8 @@
******************************************************************************/
.svg-slider .thumb {
- -fx-stroke: black;
- -fx-fill: black;
+ -fx-stroke: -monet-on-surface;
+ -fx-fill: -monet-on-surface;
}
.jfx-slider:disabled {
@@ -687,8 +702,12 @@
-jfx-indicator-position: right;
}
+.jfx-slider .track {
+ -fx-background-color: -monet-on-surface-variant-transparent-38;
+}
+
.jfx-slider .thumb {
- -fx-background-color: -fx-base-color;
+ -fx-background-color: -monet-primary;
}
/*******************************************************************************
@@ -713,42 +732,46 @@
}
.jfx-button .jfx-rippler {
- -jfx-rippler-fill: -fx-base-check-color;
+ -jfx-rippler-fill: -monet-on-primary;
-jfx-mask-type: CIRCLE;
-fx-padding: 0.0;
}
.jfx-button-raised {
- -fx-background-color: -fx-base-color;
+ -fx-background-color: -monet-primary;
}
.jfx-button-raised .jfx-rippler {
- -jfx-rippler-fill: white;
-jfx-mask-type: CIRCLE;
-fx-padding: 0.0;
}
.jfx-button-raised, .jfx-button-raised * {
- -fx-text-fill: -fx-base-text-fill;
+ -fx-text-fill: -monet-on-primary;
-fx-font-size: 14px;
}
.jfx-button-border {
- -fx-border-color: gray;
+ -fx-background-color: transparent;
+ -fx-border-color: -monet-outline;
-fx-border-radius: 5px;
-fx-border-width: 0.2px;
-fx-padding: 8px;
}
.jfx-button-border, .jfx-button-border * {
- -fx-text-fill: -fx-base-darker-color;
+ -fx-text-fill: -monet-primary;
}
.jfx-button-raised-round {
- -fx-background-color: -fx-base-color;
+ -fx-background-color: -monet-primary-container;
-fx-background-radius: 50px;
}
+.menu-up-down-button .label {
+ -fx-text-fill: -monet-on-surface;
+}
+
/*******************************************************************************
* *
* JFX Check Box *
@@ -757,7 +780,9 @@
.jfx-check-box {
-fx-font-weight: BOLD;
- -jfx-checked-color: -fx-base-check-color;
+ -fx-text-fill: -monet-on-surface;
+ -jfx-checked-color: -monet-primary;
+ -jfx-unchecked-color: transparent;
}
/*******************************************************************************
@@ -776,11 +801,11 @@
}
.jfx-progress-bar > .track {
- -fx-background-color: #E0E0E0;
+ -fx-background-color: -monet-secondary-container;
}
.jfx-progress-bar > .bar {
- -fx-background-color: -fx-base-check-color;
+ -fx-background-color: -monet-primary;
}
/*******************************************************************************
@@ -790,20 +815,17 @@
*******************************************************************************/
.jfx-text-field, .jfx-password-field, .jfx-text-area {
- -fx-background-color: #f1f3f4;
+ -fx-background-color: -monet-surface-container-highest;
+ -fx-text-fill: -monet-on-surface;
-fx-font-weight: BOLD;
- -fx-prompt-text-fill: #808080;
+ -fx-prompt-text-fill: -monet-on-surface-variant;
-fx-alignment: top-left;
-fx-max-width: 1000000000;
- -jfx-focus-color: -fx-base-check-color;
+ -jfx-focus-color: -monet-primary;
-fx-padding: 8;
-jfx-unfocus-color: transparent;
}
-.jfx-text-area .viewport {
- -fx-background-color: #ffffff;
-}
-
/*******************************************************************************
* *
* JFX List View *
@@ -815,7 +837,6 @@
}
.jfx-list-cell, .list-cell {
- /*-fx-background-color: WHITE;*/
-fx-background-color: transparent;
}
@@ -825,13 +846,13 @@
}
.jfx-list-cell .jfx-rippler {
- -jfx-rippler-fill: -fx-base-color;
+ -jfx-rippler-fill: -monet-primary-container;
}
-.list-cell:odd:selected > .jfx-rippler > StackPane,
-.list-cell:even:selected > .jfx-rippler > StackPane {
- -fx-background-color: derive(-fx-base-check-color, 30%);
-}
+/*.list-cell:odd:selected > .jfx-rippler > StackPane,*/
+/*.list-cell:even:selected > .jfx-rippler > StackPane {*/
+/* -fx-background-color: derive(-fx-base-check-color, 30%);*/
+/*}*/
.jfx-list-view {
-fx-background-insets: 0.0;
@@ -893,7 +914,7 @@
}
.card {
- -fx-background-color: rgba(255, 255, 255, 0.8);
+ -fx-background-color: -monet-surface-container-low-transparent-80;
-fx-background-radius: 4;
-fx-padding: 8px;
@@ -901,7 +922,7 @@
}
.card-non-transparent {
- -fx-background-color: white;
+ -fx-background-color: -monet-surface-container-low;
-fx-background-radius: 4;
-fx-padding: 8px;
@@ -909,7 +930,7 @@
}
.card:selected {
- -fx-background-color: derive(-fx-base-color, 60%);
+ -fx-background-color: -monet-secondary-container-transparent-80;
}
.card-list {
@@ -918,30 +939,39 @@
}
.md-list-cell {
- -fx-border-color: #e0e0e0;
+ -fx-border-color: -monet-outline-variant;
-fx-border-width: 0 0 1 0;
}
.md-list-cell:selected {
- -fx-background-color: derive(-fx-base-color, 60%);
+ -fx-background-color: -monet-secondary-container;
}
.mod-info-list-cell:warning {
- -fx-background-color: #F8D7DA;
+ -fx-background-color: -monet-error-container;
}
.options-sublist {
- -fx-background-color: white;
+ -fx-background-color: -monet-surface;
}
.options-list-item {
- -fx-background-color: white;
- -fx-border-color: #e0e0e0;
+ -fx-background-color: -monet-surface;
+ -fx-border-color: -monet-outline-variant;
-fx-border-width: 1 0 0 0;
-fx-padding: 10 16 10 16;
-fx-font-size: 12;
}
+.options-list-item .svg {
+ -fx-fill: -monet-on-surface;
+ -fx-border-color: -monet-outline-variant;
+}
+
+.options-list-item .label {
+ -fx-text-fill: -monet-on-surface;
+}
+
.options-list-item.no-padding {
-fx-padding: 0;
}
@@ -970,14 +1000,53 @@
* *
*******************************************************************************/
-.jfx-toggle-button {
- -jfx-toggle-color: -fx-base-check-color;
+.jfx-toggle-button,
+.jfx-toggle-button:armed,
+.jfx-toggle-button:hover,
+.jfx-toggle-button:focused,
+.jfx-toggle-button:selected,
+.jfx-toggle-button:focused:selected {
+ -fx-background-color: TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT;
+ -fx-background-radius: 3px;
+ -fx-background-insets: 0px;
+
+ -jfx-toggle-color: -monet-primary;
+ -jfx-toggle-line-color: -monet-primary-container;
+ -jfx-untoggle-color: -monet-outline;
+ -jfx-untoggle-line-color: -monet-surface-container-highest;
+ -jfx-size: 10;
}
+
+.jfx-toggle-button Line {
+ -fx-stroke: -jfx-untoggle-line-color;
+}
+.jfx-toggle-button:selected Line{
+ -fx-stroke: -jfx-toggle-line-color;
+}
+
+.jfx-toggle-button Circle{
+ -fx-fill: -jfx-untoggle-color;
+}
+
+.jfx-toggle-button:selected Circle{
+ -fx-fill: -jfx-toggle-color;
+}
+
+.jfx-toggle-button .jfx-rippler {
+ -jfx-rippler-fill: -jfx-untoggle-line-color;
+}
+
+.jfx-toggle-button:selected .jfx-rippler {
+ -jfx-rippler-fill: -jfx-toggle-line-color;
+}
+
+
.toggle-label {
-fx-font-size: 14.0px;
}
+/* unused */
.toggle-icon1 .icon {
-fx-fill: #4285F4;
-fx-padding: 20.0;
@@ -1004,6 +1073,7 @@
-fx-background-color: transparent;
}
+/* unused */
.toggle-icon2 .icon {
-fx-fill: RED;
}
@@ -1013,6 +1083,7 @@
-jfx-mask-type: CIRCLE;
}
+/* unused */
.toggle-icon3 {
-fx-toggle-icon4-size: 35px;
-fx-pref-width: -fx-toggle-icon4-size;
@@ -1037,6 +1108,7 @@
-jfx-mask-type: CIRCLE;
}
+/* used */
.toggle-icon4 {
-fx-toggle-icon4-size: 30px;
-fx-pref-width: -fx-toggle-icon4-size;
@@ -1047,7 +1119,7 @@
-fx-min-height: -fx-toggle-icon4-size;
-fx-background-radius: 50px;
-fx-background-color: transparent;
- -jfx-toggle-color: -fx-base-check-color;
+ -jfx-toggle-color: -monet-primary;
-jfx-untoggle-color: transparent;
}
@@ -1057,7 +1129,7 @@
}
.toggle-icon4 .jfx-rippler {
- -jfx-rippler-fill: -fx-base-check-color;
+ -jfx-rippler-fill: -monet-primary;
-jfx-mask-type: CIRCLE;
}
@@ -1177,7 +1249,7 @@
.jfx-spinner > .arc {
-fx-stroke-width: 3.0;
-fx-fill: transparent;
- -fx-stroke: -fx-base-color;
+ -fx-stroke: -monet-primary-container;
}
.first-spinner {
@@ -1269,11 +1341,31 @@
.jfx-combo-box {
-jfx-focus-color: transparent;
-jfx-unfocus-color: transparent;
- -fx-background-color: #f1f3f4;
+ -fx-background-color: -monet-surface-container-highest;
-fx-padding: 4;
-fx-max-width: 1000000000;
}
+.jfx-combo-box .text {
+ -fx-fill: -monet-on-surface;
+}
+
+.combo-box-popup .list-view {
+ -fx-background-color: -monet-surface-container;
+}
+
+.combo-box-popup .list-view .jfx-list-cell {
+ -fx-background-color: -monet-surface-container;
+ -fx-text-fill: -monet-on-surface;
+ -fx-background-insets: 0.0;
+}
+
+.combo-box-popup .list-view .list-cell:odd:selected > .jfx-rippler > StackPane,
+.combo-box-popup .list-view .list-cell:even:selected > .jfx-rippler > StackPane {
+ -fx-background-color: -monet-secondary-container;
+ -fx-text-fill: -monet-on-secondary-container;
+}
+
.jfx-combo-box-warning {
-jfx-focus-color: #D34336;
-jfx-unfocus-color: #D34336;
@@ -1283,12 +1375,58 @@
-fx-fill: #D34336;
}
-.combo-box-popup .list-view .jfx-list-cell {
- -fx-background-insets: 0.0;
+/*.combo-box-popup .list-view .jfx-list-cell .jfx-rippler {*/
+/* -jfx-rippler-fill: -monet-primary-container;*/
+/*}*/
+
+/*******************************************************************************
+* *
+* JFX Color Picker *
+* *
+*******************************************************************************/
+
+.jfx-color-picker:armed,
+.jfx-color-picker:hover,
+.jfx-color-picker:focused,
+.jfx-color-picker {
+ -fx-background-color: TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT;
+ -fx-background-radius: 3px;
+ -fx-background-insets: 0px;
+ -fx-min-height: 25px;
}
-.combo-box-popup .list-view .jfx-list-cell .jfx-rippler {
- -jfx-rippler-fill: -fx-base-color;
+.jfx-color-picker > .jfx-rippler > .color-box {
+ -fx-background-color: -monet-primary-seed;
+ -fx-background-radius: 3px;
+ -fx-background-insets: 0px;
+}
+
+.jfx-color-picker > .jfx-rippler > .color-box:disabled {
+ -fx-background-color: transparent;
+}
+
+
+.color-palette-region {
+}
+
+.color-palette-region .jfx-button {
+ -fx-text-fill: -monet-on-surface;
+}
+
+.color-palette {
+ -fx-background-color: -monet-surface;
+}
+
+.custom-color-dialog .jfx-tab-pane {
+ -fx-background-color: -monet-surface;
+}
+
+.custom-color-dialog .custom-color-field {
+ -jfx-unfocus-color: -monet-on-surface;
+ -fx-background-color: TRANSPARENT;
+ -fx-font-weight: BOLD;
+ -fx-alignment: top-left;
+ -fx-max-width: 300;
}
/*******************************************************************************
@@ -1298,7 +1436,7 @@
*******************************************************************************/
.jfx-decorator {
- -fx-decorator-color: -fx-base-color;
+ -fx-decorator-color: -monet-primary-container;
}
.jfx-decorator-drawer {
@@ -1306,7 +1444,7 @@
}
.jfx-decorator-title {
- -fx-text-fill: -fx-base-text-fill;
+ -fx-text-fill: -monet-on-primary-container;
-fx-font-size: 14;
}
@@ -1314,16 +1452,6 @@
-fx-text-fill: black;
}
-.jfx-decorator-tab .tab-container .tab-label {
- -fx-text-fill: -fx-base-disabled-text-fill;
- -fx-font-size: 14;
-}
-
-.jfx-decorator-tab .tab-container:selected .tab-label {
- -fx-text-fill: -fx-base-text-fill;
- -fx-font-weight: BOLD;
-}
-
.window {
-fx-background-color: transparent;
-fx-padding: 8;
@@ -1335,15 +1463,11 @@
}
.content-background {
- -fx-background-color: rgba(244, 244, 244, 0.5);
+ -fx-background-color: -monet-surface-transparent-50;
}
.gray-background {
- -fx-background-color: rgba(244, 244, 244, 0.5);
-}
-
-.white-background {
- -fx-background-color: rgb(255, 255, 255);
+ -fx-background-color: -monet-surface-transparent-50;
}
/*******************************************************************************
@@ -1351,6 +1475,7 @@
* Scroll Bar *
* *
******************************************************************************/
+/* unsued? */
.scroll-bar:vertical > .track-background, .scroll-bar:horizontal > .track-background {
-fx-background-color: #F1F1F1;
@@ -1406,7 +1531,7 @@
.scroll-pane > .viewport {
-fx-background-color: null;
- }
+}
.scroll-pane .scroll-bar {
-fx-skin: "org.jackhuang.hmcl.ui.construct.FloatScrollBarSkin";
@@ -1423,28 +1548,28 @@
*******************************************************************************/
.error-label {
- -fx-text-fill: #D34336;
+ -fx-text-fill: -monet-error;
-fx-font-size: 0.75em;
-fx-font-weight: bold;
}
.error-icon {
- -fx-fill: #D34336;
+ -fx-fill: -monet-error;
-fx-font-size: 1.0em;
}
.jfx-text-field:error, .jfx-password-field:error, .jfx-text-area:error, .jfx-combo-box:error {
- -jfx-focus-color: #D34336;
- -jfx-unfocus-color: #D34336;
+ -jfx-focus-color: -monet-error;
+ -jfx-unfocus-color: -monet-error;
}
.jfx-text-field .error-label, .jfx-password-field .error-label, .jfx-text-area .error-label {
- -fx-text-fill: #D34336;
+ -fx-text-fill: -monet-error;
-fx-font-size: 0.75em;
}
.jfx-text-field .error-icon, .jfx-password-field .error-icon, .jfx-text-area .error-icon {
- -fx-fill: #D34336;
+ -fx-fill: -monet-error;
-fx-font-size: 1.0em;
}
diff --git a/HMCL/src/main/resources/assets/img/github-white.png b/HMCL/src/main/resources/assets/img/github-white.png
new file mode 100644
index 000000000..e7cc199bc
Binary files /dev/null and b/HMCL/src/main/resources/assets/img/github-white.png differ
diff --git a/HMCL/src/main/resources/assets/img/github-white@2x.png b/HMCL/src/main/resources/assets/img/github-white@2x.png
new file mode 100644
index 000000000..944279001
Binary files /dev/null and b/HMCL/src/main/resources/assets/img/github-white@2x.png differ
diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties
index 32f4abca5..f0d0a26ee 100644
--- a/HMCL/src/main/resources/assets/lang/I18N.properties
+++ b/HMCL/src/main/resources/assets/lang/I18N.properties
@@ -1352,6 +1352,10 @@ settings.icon=Icon
settings.launcher=Launcher Settings
settings.launcher.appearance=Appearance
+settings.launcher.brightness=Theme Mode
+settings.launcher.brightness.auto=Follow System Settings
+settings.launcher.brightness.dark=Dark Mode
+settings.launcher.brightness.light=Light Mode
settings.launcher.common_path.tooltip=HMCL will put all game assets and dependencies here. If there are existing libraries in the game directory, then HMCL will prefer to use them first.
settings.launcher.debug=Debug
settings.launcher.disable_auto_game_options=Do not switch game language
@@ -1385,7 +1389,7 @@ settings.launcher.proxy.password=Password
settings.launcher.proxy.port=Port
settings.launcher.proxy.socks=SOCKS
settings.launcher.proxy.username=Username
-settings.launcher.theme=Theme
+settings.launcher.theme=Theme Color
settings.launcher.title_transparent=Transparent Titlebar
settings.launcher.turn_off_animations=Disable Animation (Applies After Restart)
settings.launcher.version_list_source=Version List
diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties
index 71882ac12..804988a42 100644
--- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties
+++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties
@@ -1140,6 +1140,10 @@ settings.icon=遊戲圖示
settings.launcher=啟動器設定
settings.launcher.appearance=外觀
+settings.launcher.brightness=主題模式
+settings.launcher.brightness.auto=跟隨系統設定
+settings.launcher.brightness.dark=深色模式
+settings.launcher.brightness.light=淺色模式
settings.launcher.common_path.tooltip=啟動器將所有遊戲資源及相依元件庫檔案放於此集中管理。如果遊戲目錄內有現成的將不會使用公共庫檔案。
settings.launcher.debug=除錯
settings.launcher.disable_auto_game_options=不自動切換遊戲語言
@@ -1173,7 +1177,7 @@ settings.launcher.proxy.password=密碼
settings.launcher.proxy.port=連線埠
settings.launcher.proxy.socks=SOCKS
settings.launcher.proxy.username=帳戶
-settings.launcher.theme=主題
+settings.launcher.theme=主題色
settings.launcher.title_transparent=標題欄透明
settings.launcher.turn_off_animations=關閉動畫 (重啟後生效)
settings.launcher.version_list_source=版本清單來源
diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties
index d4a5e9c68..3efe9933d 100644
--- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties
+++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties
@@ -1150,6 +1150,10 @@ settings.icon=游戏图标
settings.launcher=启动器设置
settings.launcher.appearance=外观
+settings.launcher.brightness=主题模式
+settings.launcher.brightness.auto=跟随系统设置
+settings.launcher.brightness.dark=深色模式
+settings.launcher.brightness.light=浅色模式
settings.launcher.common_path.tooltip=启动器将所有游戏资源及依赖库文件存放于此集中管理。如果游戏文件夹内有现成的将不会使用公共库文件。
settings.launcher.debug=调试
settings.launcher.disable_auto_game_options=不自动切换游戏语言
@@ -1183,7 +1187,7 @@ settings.launcher.proxy.password=密码
settings.launcher.proxy.port=端口
settings.launcher.proxy.socks=SOCKS
settings.launcher.proxy.username=账户
-settings.launcher.theme=主题
+settings.launcher.theme=主题色
settings.launcher.title_transparent=标题栏透明
settings.launcher.turn_off_animations=关闭动画 (重启后生效)
settings.launcher.version_list_source=版本列表源
diff --git a/HMCL/src/test/java/org/jackhuang/hmcl/setting/ThemeColorTest.java b/HMCL/src/test/java/org/jackhuang/hmcl/setting/ThemeColorTest.java
new file mode 100644
index 000000000..dfb2ce309
--- /dev/null
+++ b/HMCL/src/test/java/org/jackhuang/hmcl/setting/ThemeColorTest.java
@@ -0,0 +1,44 @@
+/*
+ * Hello Minecraft! Launcher
+ * Copyright (C) 2025 huangyuhui and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.jackhuang.hmcl.setting;
+
+import javafx.scene.paint.Color;
+import org.jackhuang.hmcl.theme.ThemeColor;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+/// @author Glavo
+public final class ThemeColorTest {
+
+ @Test
+ public void testOf() {
+ assertEquals(new ThemeColor("#AABBCC", Color.web("#AABBCC")), ThemeColor.of("#AABBCC"));
+ 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"));
+ assertEquals(new ThemeColor("orange", Color.web("#E67E22")), ThemeColor.of("orange"));
+ assertEquals(new ThemeColor("purple", Color.web("#9C27B0")), ThemeColor.of("purple"));
+ assertEquals(new ThemeColor("red", Color.web("#B71C1C")), ThemeColor.of("red"));
+
+ assertNull(ThemeColor.of((String) null));
+ assertNull(ThemeColor.of(""));
+ assertNull(ThemeColor.of("unknown"));
+ }
+}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 5d1cb4c56..7724d2e4c 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -17,6 +17,7 @@ jna = "5.18.1"
pci-ids = "0.4.0"
java-info = "1.0"
authlib-injector = "1.2.6"
+monet-fx = "0.4.0"
# testing
junit = "6.0.1"
@@ -46,6 +47,7 @@ jna-platform = { module = "net.java.dev.jna:jna-platform", version.ref = "jna" }
pci-ids = { module = "org.glavo:pci-ids", version.ref = "pci-ids" }
java-info = { module = "org.glavo:java-info", version.ref = "java-info" }
authlib-injector = { module = "org.glavo.hmcl:authlib-injector", version.ref = "authlib-injector" }
+monet-fx = { module = "org.glavo:MonetFX", version.ref = "monet-fx" }
# testing
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" }