更新至 Material Design 3 颜色系统 (#4835)

This commit is contained in:
Glavo
2025-11-30 15:25:27 +08:00
committed by GitHub
parent 3b78ec9664
commit 310a344f96
74 changed files with 2297 additions and 623 deletions

View File

@@ -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)

View File

@@ -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:
* <p>
* .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;
* }
* <p>
* To change the rippler color when toggled:
* <p>
* .jfx-toggle-button .jfx-rippler{
* -fx-rippler-fill: color-value;
* }
* <p>
* .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'.
* <p>
* 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<Paint> 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<Paint> toggleColorProperty() {
return this.toggleColor;
}
public void setToggleColor(Paint color) {
this.toggleColor.set(color);
}
/**
* default color used when the button is not toggled
*/
private StyleableObjectProperty<Paint> 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<Paint> 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<Paint> 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<Paint> 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<Paint> 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<Paint> 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<JFXToggleButton, Paint> 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<Paint> getStyleableProperty(JFXToggleButton control) {
return control.toggleColorProperty();
}
};
private static final CssMetaData<JFXToggleButton, Paint> 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<Paint> getStyleableProperty(JFXToggleButton control) {
return control.unToggleColorProperty();
}
};
private static final CssMetaData<JFXToggleButton, Paint> 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<Paint> getStyleableProperty(JFXToggleButton control) {
return control.toggleLineColorProperty();
}
};
private static final CssMetaData<JFXToggleButton, Paint> 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<Paint> getStyleableProperty(JFXToggleButton control) {
return control.unToggleLineColorProperty();
}
};
private static final CssMetaData<JFXToggleButton, Number> SIZE =
new CssMetaData<>("-jfx-size",
StyleConverter.getSizeConverter(), 10.0) {
@Override
public boolean isSettable(JFXToggleButton control) {
return !control.size.isBound();
}
@Override
public StyleableProperty<Number> getStyleableProperty(JFXToggleButton control) {
return control.sizeProperty();
}
};
private static final CssMetaData<JFXToggleButton, Boolean> 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<JFXToggleButton, Boolean> 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<CssMetaData<? extends Styleable, ?>> CHILD_STYLEABLES;
static {
final List<CssMetaData<? extends Styleable, ?>> 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<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {
return getClassCssMetaData();
}
public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
return StyleableProperties.CHILD_STYLEABLES;
}
}

View File

@@ -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() {

View File

@@ -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);

View File

@@ -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));

View File

@@ -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;
}

View File

@@ -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;
/**
* <h1>Material Design ToggleButton Skin</h1>
*
* @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;
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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<AnimationHandler> animationHandlers = new HashSet<>();
private long startTime = -1;
private boolean running = false;
private List<CacheMemento> caches = new ArrayList<>();
private double totalElapsedMilliseconds;
public JFXAnimationTimer(JFXKeyFrame... keyFrames) {
for (JFXKeyFrame keyFrame : keyFrames) {
Duration duration = keyFrame.getDuration();
final Set<JFXKeyValue<?>> keyValuesSet = keyFrame.getValues();
if (!keyValuesSet.isEmpty()) {
animationHandlers.add(new AnimationHandler(duration, keyFrame.getAnimateCondition(), keyFrame.getValues()));
}
}
}
private final HashMap<JFXKeyFrame, AnimationHandler> 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<JFXKeyValue<?>> 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<JFXKeyValue<?>> keyValues;
private Supplier<Boolean> animationCondition = null;
private boolean finished = false;
private final HashMap<WritableValue<?>, Object> initialValuesMap = new HashMap<>();
private final HashMap<WritableValue<?>, Object> endValuesMap = new HashMap<>();
AnimationHandler(Duration duration, Supplier<Boolean> animationCondition, Set<JFXKeyValue<?>> 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();
}
}
}

View File

@@ -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<JFXKeyValue<?>> keyValues = new CopyOnWriteArraySet<>();
private Supplier<Boolean> 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<JFXKeyValue<?>> getValues() {
return keyValues;
}
public Supplier<Boolean> getAnimateCondition() {
return animateCondition;
}
public static Builder builder() {
return new Builder();
}
public static final class Builder {
private Duration duration;
private final Set<JFXKeyValue<?>> keyValues = new CopyOnWriteArraySet<>();
private Supplier<Boolean> 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<Boolean> 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;
}
}
}

View File

@@ -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<T> {
private WritableValue<T> target;
private Supplier<WritableValue<T>> targetSupplier;
private Supplier<T> endValueSupplier;
private T endValue;
private Supplier<Boolean> 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<T> 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 <T> JFXKeyValueBuilder<T> setTarget(WritableValue<T> target) {
JFXKeyValueBuilder<T> builder = new JFXKeyValueBuilder<>();
builder.setTarget(target);
return builder;
}
public <T> JFXKeyValueBuilder<T> setTargetSupplier(Supplier<WritableValue<T>> targetSupplier) {
JFXKeyValueBuilder<T> builder = new JFXKeyValueBuilder<>();
builder.setTargetSupplier(targetSupplier);
return builder;
}
public <T> JFXKeyValueBuilder<T> setEndValueSupplier(Supplier<T> endValueSupplier) {
JFXKeyValueBuilder<T> builder = new JFXKeyValueBuilder<>();
builder.setEndValueSupplier(endValueSupplier);
return builder;
}
public <T> JFXKeyValueBuilder<T> setEndValue(T endValue) {
JFXKeyValueBuilder<T> builder = new JFXKeyValueBuilder<>();
builder.setEndValue(endValue);
return builder;
}
public <T> JFXKeyValueBuilder<T> setAnimateCondition(Supplier<Boolean> animateCondition) {
JFXKeyValueBuilder<T> builder = new JFXKeyValueBuilder<>();
builder.setAnimateCondition(animateCondition);
return builder;
}
public <T> JFXKeyValueBuilder<T> setInterpolator(Interpolator interpolator) {
JFXKeyValueBuilder<T> builder = new JFXKeyValueBuilder<>();
builder.setInterpolator(interpolator);
return builder;
}
}
public static final class JFXKeyValueBuilder<T> {
private WritableValue<T> target;
private Supplier<WritableValue<T>> targetSupplier;
private Supplier<T> endValueSupplier;
private T endValue;
private Supplier<Boolean> animateCondition = () -> true;
private Interpolator interpolator = Interpolator.EASE_BOTH;
private JFXKeyValueBuilder() {
}
public JFXKeyValueBuilder<T> setTarget(WritableValue<T> target) {
this.target = target;
return this;
}
public JFXKeyValueBuilder<T> setTargetSupplier(Supplier<WritableValue<T>> targetSupplier) {
this.targetSupplier = targetSupplier;
return this;
}
public JFXKeyValueBuilder<T> setEndValueSupplier(Supplier<T> endValueSupplier) {
this.endValueSupplier = endValueSupplier;
return this;
}
public JFXKeyValueBuilder<T> setEndValue(T endValue) {
this.endValue = endValue;
return this;
}
public JFXKeyValueBuilder<T> setAnimateCondition(Supplier<Boolean> animateCondition) {
this.animateCondition = animateCondition;
return this;
}
public JFXKeyValueBuilder<T> setInterpolator(Interpolator interpolator) {
this.interpolator = interpolator;
return this;
}
public JFXKeyValue<T> build() {
JFXKeyValue<T> 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;
}
}
}

View File

@@ -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> theme = new SimpleObjectProperty<>();
private final ObjectProperty<ThemeColor> themeColor = new SimpleObjectProperty<>(ThemeColor.DEFAULT);
public ObjectProperty<Theme> themeProperty() {
return theme;
public ObjectProperty<ThemeColor> 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")

View File

@@ -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) {

View File

@@ -1,163 +0,0 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.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<Theme> 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<Color> FOREGROUND_FILL;
public static ObjectBinding<Color> 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<Theme> {
@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);
}
}
}

View File

@@ -0,0 +1,94 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.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 + ']';
}
}

View File

@@ -0,0 +1,193 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.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<ThemeColor> 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<ColorPicker> colorPickerRef;
private final WeakReference<Property<ThemeColor>> propertyRef;
private final int hashCode;
private boolean updating = false;
private BidirectionalBinding(ColorPicker colorPicker, Property<ThemeColor> 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<ThemeColor> 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<ThemeColor> 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<ThemeColor> 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<ThemeColor> {
@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);
}
}
}

View File

@@ -0,0 +1,126 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.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> theme = new ObjectBinding<>() {
{
List<Observable> 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<Theme> 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<Theme> 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<Color> titleFillProperty() {
return colorSchemeProperty().getOnPrimaryContainer();
}
public static BooleanBinding darkModeProperty() {
return darkMode;
}
private Themes() {
}
}

View File

@@ -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<String, Object> PREFERENCES;
public static final @Nullable ObservableBooleanValue DARK_MODE;
public static final @Nullable Boolean REDUCED_MOTION;
public static final @Nullable ReadOnlyObjectProperty<Color> ACCENT_COLOR;
public static final @Nullable MethodHandle TEXT_TRUNCATED_PROPERTY;
@@ -151,6 +151,7 @@ public final class FXUtils {
ObservableMap<String, Object> preferences = null;
ObservableBooleanValue darkMode = null;
ReadOnlyObjectProperty<Color> 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<Color>)
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 {

View File

@@ -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");

View File

@@ -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<ListPage<?>> {
@@ -67,7 +66,7 @@ public class ListPageSkin extends SkinBase<ListPage<?>> {
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<ListPage<?>> {
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);

View File

@@ -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);
}
}

View File

@@ -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<T extends ListPageBase<? extends Node>
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<T extends ListPageBase<? extends Node>
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;

View File

@@ -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<AccountListItem> {
});
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<AccountListItem> {
.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<AccountListItem> {
}
});
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<AccountListItem> {
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<AccountListItem> {
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);

View File

@@ -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<String> title = BindingMapping.of(server, AuthlibInjectorServer::getName);

View File

@@ -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());

View File

@@ -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);

View File

@@ -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");

View File

@@ -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);
}
}

View File

@@ -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"));

View File

@@ -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();

View File

@@ -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<ScrollBar> {
private ScrollBar scrollBar;
private Region group;

View File

@@ -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);

View File

@@ -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");

View File

@@ -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;

View File

@@ -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");

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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);

View File

@@ -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<T> extends VBox {
super(title, data);
}
public PaintOption<T> setCustomColors(List<Color> colors) {
colorPicker.getCustomColors().setAll(colors);
return this;
}
public PaintOption<T> bindBidirectional(Property<Paint> property) {
FXUtils.bindPaint(colorPicker, property);
return this;
}
public PaintOption<T> bindThemeColorBidirectional(Property<ThemeColor> property) {
ThemeColor.bindBidirectional(colorPicker, property);
return this;
}
@Override
protected Node createItem(ToggleGroup group) {
BorderPane pane = new BorderPane();

View File

@@ -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<Node> container = new SimpleObjectProperty<>(this, "container", null);
private final StyleableObjectProperty<Paint> ripplerFill = new SimpleStyleableObjectProperty<>(StyleableProperties.RIPPLER_FILL,this, "ripplerFill", null);
private final StyleableObjectProperty<Paint> 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() {

View File

@@ -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);

View File

@@ -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

View File

@@ -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<Decorator> {
{
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<Decorator> {
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<Decorator> {
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<Decorator> {
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<Decorator> {
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();

View File

@@ -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());

View File

@@ -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"))

View File

@@ -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");

View File

@@ -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<JavaManagementPage.Ja
{
JFXButton revealButton = new JFXButton();
revealButton.getStyleClass().add("toggle-icon4");
revealButton.setGraphic(FXUtils.limitingSize(SVG.FOLDER_OPEN.createIcon(Theme.blackFill(), 24), 24, 24));
revealButton.setGraphic(FXUtils.limitingSize(SVG.FOLDER_OPEN.createIcon(24), 24, 24));
revealButton.setOnAction(e -> control.onReveal());
FXUtils.installFastTooltip(revealButton, i18n("reveal.in_file_manager"));
@@ -276,12 +275,12 @@ public final class JavaManagementPage extends ListPageBase<JavaManagementPage.Ja
null
));
if (java.isManaged()) {
removeButton.setGraphic(FXUtils.limitingSize(SVG.DELETE_FOREVER.createIcon(Theme.blackFill(), 24), 24, 24));
removeButton.setGraphic(FXUtils.limitingSize(SVG.DELETE_FOREVER.createIcon(24), 24, 24));
FXUtils.installFastTooltip(removeButton, i18n("java.uninstall"));
if (JavaRuntime.CURRENT_JAVA != null && java.getBinary().equals(JavaRuntime.CURRENT_JAVA.getBinary()))
removeButton.setDisable(true);
} else {
removeButton.setGraphic(FXUtils.limitingSize(SVG.DELETE.createIcon(Theme.blackFill(), 24), 24, 24));
removeButton.setGraphic(FXUtils.limitingSize(SVG.DELETE.createIcon(24), 24, 24));
FXUtils.installFastTooltip(removeButton, i18n("java.disable"));
}

View File

@@ -32,7 +32,6 @@ import javafx.scene.control.*;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import org.jackhuang.hmcl.java.JavaManager;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.ui.*;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
@@ -160,7 +159,7 @@ public final class JavaRestorePage extends ListPageBase<JavaRestorePage.Disabled
{
JFXButton revealButton = new JFXButton();
revealButton.getStyleClass().add("toggle-icon4");
revealButton.setGraphic(FXUtils.limitingSize(SVG.FOLDER_OPEN.createIcon(Theme.blackFill(), 24), 24, 24));
revealButton.setGraphic(FXUtils.limitingSize(SVG.FOLDER_OPEN.createIcon(24), 24, 24));
revealButton.setOnAction(e -> skinnable.onReveal());
FXUtils.installFastTooltip(revealButton, i18n("reveal.in_file_manager"));
@@ -169,7 +168,7 @@ public final class JavaRestorePage extends ListPageBase<JavaRestorePage.Disabled
JFXButton removeButton = new JFXButton();
removeButton.getStyleClass().add("toggle-icon4");
removeButton.setGraphic(FXUtils.limitingSize(SVG.DELETE.createIcon(Theme.blackFill(), 24), 24, 24));
removeButton.setGraphic(FXUtils.limitingSize(SVG.DELETE.createIcon(24), 24, 24));
removeButton.setOnAction(e -> skinnable.onRemove());
FXUtils.installFastTooltip(removeButton, i18n("java.disabled.management.remove"));
@@ -177,7 +176,7 @@ public final class JavaRestorePage extends ListPageBase<JavaRestorePage.Disabled
} else {
JFXButton restoreButton = new JFXButton();
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));
restoreButton.setOnAction(e -> skinnable.onRestore());
FXUtils.installFastTooltip(restoreButton, i18n("java.disabled.management.restore"));

View File

@@ -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);

View File

@@ -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<String> 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);

View File

@@ -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));

View File

@@ -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);

View File

@@ -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<ProfileListItem> {
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);

View File

@@ -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));
}
}

View File

@@ -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;
}

View File

@@ -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<GameListItem> {
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<GameListItem> {
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<GameListItem> {
});
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);
}

View File

@@ -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<ModListPage> {
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);

View File

@@ -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<SchematicsPage.Item> 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<SchematicsPage.Item> 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));

View File

@@ -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);

View File

@@ -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 {

View File

@@ -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<WorldBackupsPage.Backup
right.getChildren().add(btnReveal);
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 -> 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));
}

View File

@@ -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);
});
}

View File

@@ -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<WorldListItem> {
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()));
}

View File

@@ -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"
}
]

View File

@@ -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"

View File

@@ -1,28 +1,60 @@
/**
* Hello Minecraft! Launcher
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
.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;
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 851 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -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

View File

@@ -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=版本清單來源

View File

@@ -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=版本列表源

View File

@@ -0,0 +1,44 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.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"));
}
}

View File

@@ -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" }