From 310a344f961e616a60ea61ba981d6c7f39920cec Mon Sep 17 00:00:00 2001 From: Glavo Date: Sun, 30 Nov 2025 15:25:27 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=87=B3=20Material=20Design?= =?UTF-8?q?=203=20=E9=A2=9C=E8=89=B2=E7=B3=BB=E7=BB=9F=20(#4835)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HMCL/build.gradle.kts | 1 + .../com/jfoenix/controls/JFXToggleButton.java | 373 +++++++++++++++ .../com/jfoenix/skins/JFXCheckBoxSkin.java | 23 +- .../com/jfoenix/skins/JFXColorPalette.java | 6 - .../skins/JFXCustomColorPickerDialog.java | 21 +- .../com/jfoenix/skins/JFXRadioButtonSkin.java | 86 ++-- .../jfoenix/skins/JFXToggleButtonSkin.java | 186 ++++++++ .../com/jfoenix/transitions/CacheMemento.java | 68 +++ .../transitions/JFXAnimationTimer.java | 286 ++++++++++++ .../com/jfoenix/transitions/JFXKeyFrame.java | 104 +++++ .../com/jfoenix/transitions/JFXKeyValue.java | 157 +++++++ .../org/jackhuang/hmcl/setting/Config.java | 31 +- .../jackhuang/hmcl/setting/StyleSheets.java | 69 ++- .../org/jackhuang/hmcl/setting/Theme.java | 163 ------- .../java/org/jackhuang/hmcl/theme/Theme.java | 94 ++++ .../org/jackhuang/hmcl/theme/ThemeColor.java | 193 ++++++++ .../java/org/jackhuang/hmcl/theme/Themes.java | 126 +++++ .../java/org/jackhuang/hmcl/ui/FXUtils.java | 23 +- .../org/jackhuang/hmcl/ui/InstallerItem.java | 7 +- .../org/jackhuang/hmcl/ui/ListPageSkin.java | 5 +- .../main/java/org/jackhuang/hmcl/ui/SVG.java | 23 +- .../hmcl/ui/ToolbarListPageSkin.java | 16 +- .../hmcl/ui/account/AccountListItemSkin.java | 13 +- .../hmcl/ui/account/AccountListPage.java | 3 +- .../hmcl/ui/account/CreateAccountPane.java | 3 +- .../hmcl/ui/construct/AdvancedListBox.java | 5 +- .../hmcl/ui/construct/ClassTitle.java | 2 - .../hmcl/ui/construct/ComponentListCell.java | 6 +- .../jackhuang/hmcl/ui/construct/FileItem.java | 3 +- .../hmcl/ui/construct/FileSelector.java | 3 +- .../hmcl/ui/construct/FloatScrollBarSkin.java | 2 + .../jackhuang/hmcl/ui/construct/HintPane.java | 3 +- .../hmcl/ui/construct/IconedMenuItem.java | 5 +- .../ui/construct/IconedTwoLineListItem.java | 3 +- .../hmcl/ui/construct/ImagePickerItem.java | 5 +- .../hmcl/ui/construct/JFXHyperlink.java | 3 +- .../hmcl/ui/construct/MenuUpDownButton.java | 7 +- .../hmcl/ui/construct/MessageDialogPane.java | 3 +- .../hmcl/ui/construct/MultiFileItem.java | 13 + .../hmcl/ui/construct/RipplerContainer.java | 8 +- .../ui/construct/TaskExecutorDialogPane.java | 2 + .../hmcl/ui/construct/TaskListPane.java | 3 +- .../hmcl/ui/decorator/DecoratorSkin.java | 23 +- .../hmcl/ui/export/ExportWizardProvider.java | 2 +- .../org/jackhuang/hmcl/ui/main/AboutPage.java | 20 +- .../jackhuang/hmcl/ui/main/FeedbackPage.java | 6 +- .../hmcl/ui/main/JavaManagementPage.java | 7 +- .../hmcl/ui/main/JavaRestorePage.java | 7 +- .../org/jackhuang/hmcl/ui/main/MainPage.java | 9 +- .../hmcl/ui/main/PersonalizationPage.java | 28 +- .../jackhuang/hmcl/ui/main/SettingsPage.java | 4 +- .../jackhuang/hmcl/ui/main/SettingsView.java | 8 +- .../hmcl/ui/profile/ProfileListItemSkin.java | 3 +- .../terracotta/TerracottaControllerPage.java | 7 +- .../hmcl/ui/versions/DownloadPage.java | 7 +- .../hmcl/ui/versions/GameListItemSkin.java | 7 +- .../hmcl/ui/versions/ModListPageSkin.java | 7 +- .../hmcl/ui/versions/SchematicsPage.java | 7 +- .../hmcl/ui/versions/VersionIconDialog.java | 3 +- .../hmcl/ui/versions/VersionPage.java | 3 +- .../hmcl/ui/versions/WorldBackupsPage.java | 5 +- .../hmcl/ui/versions/WorldInfoPage.java | 3 +- .../hmcl/ui/versions/WorldListItemSkin.java | 3 +- .../src/main/resources/assets/about/deps.json | 5 + .../main/resources/assets/about/thanks.json | 5 +- HMCL/src/main/resources/assets/css/blue.css | 86 ++-- HMCL/src/main/resources/assets/css/root.css | 435 +++++++++++------- .../resources/assets/img/github-white.png | Bin 0 -> 851 bytes .../resources/assets/img/github-white@2x.png | Bin 0 -> 1648 bytes .../resources/assets/lang/I18N.properties | 6 +- .../resources/assets/lang/I18N_zh.properties | 6 +- .../assets/lang/I18N_zh_CN.properties | 6 +- .../hmcl/setting/ThemeColorTest.java | 44 ++ gradle/libs.versions.toml | 2 + 74 files changed, 2297 insertions(+), 623 deletions(-) create mode 100644 HMCL/src/main/java/com/jfoenix/controls/JFXToggleButton.java create mode 100644 HMCL/src/main/java/com/jfoenix/skins/JFXToggleButtonSkin.java create mode 100644 HMCL/src/main/java/com/jfoenix/transitions/CacheMemento.java create mode 100644 HMCL/src/main/java/com/jfoenix/transitions/JFXAnimationTimer.java create mode 100644 HMCL/src/main/java/com/jfoenix/transitions/JFXKeyFrame.java create mode 100644 HMCL/src/main/java/com/jfoenix/transitions/JFXKeyValue.java delete mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/setting/Theme.java create mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/theme/Theme.java create mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/theme/ThemeColor.java create mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/theme/Themes.java create mode 100644 HMCL/src/main/resources/assets/img/github-white.png create mode 100644 HMCL/src/main/resources/assets/img/github-white@2x.png create mode 100644 HMCL/src/test/java/org/jackhuang/hmcl/setting/ThemeColorTest.java diff --git a/HMCL/build.gradle.kts b/HMCL/build.gradle.kts index 7718b3969..6202b8282 100644 --- a/HMCL/build.gradle.kts +++ b/HMCL/build.gradle.kts @@ -58,6 +58,7 @@ dependencies { implementation("libs:JFoenix") implementation(libs.twelvemonkeys.imageio.webp) implementation(libs.java.info) + implementation(libs.monet.fx) if (launcherExe.isBlank()) { implementation(libs.hmclauncher) diff --git a/HMCL/src/main/java/com/jfoenix/controls/JFXToggleButton.java b/HMCL/src/main/java/com/jfoenix/controls/JFXToggleButton.java new file mode 100644 index 000000000..60187da4e --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/controls/JFXToggleButton.java @@ -0,0 +1,373 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package com.jfoenix.controls; + +import com.jfoenix.skins.JFXToggleButtonSkin; +import javafx.css.*; +import javafx.css.converter.BooleanConverter; +import javafx.css.converter.PaintConverter; +import javafx.scene.control.Skin; +import javafx.scene.control.ToggleButton; +import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; +import org.jackhuang.hmcl.ui.animation.AnimationUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * JFXToggleButton is the material design implementation of a toggle button. + * important CSS Selectors: + *

+ * .jfx-toggle-button{ + * -fx-toggle-color: color-value; + * -fx-untoggle-color: color-value; + * -fx-toggle-line-color: color-value; + * -fx-untoggle-line-color: color-value; + * } + *

+ * To change the rippler color when toggled: + *

+ * .jfx-toggle-button .jfx-rippler{ + * -fx-rippler-fill: color-value; + * } + *

+ * .jfx-toggle-button:selected .jfx-rippler{ + * -fx-rippler-fill: color-value; + * } + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXToggleButton extends ToggleButton { + + /** + * {@inheritDoc} + */ + public JFXToggleButton() { + initialize(); + } + + /** + * {@inheritDoc} + */ + @Override + protected Skin createDefaultSkin() { + return new JFXToggleButtonSkin(this); + } + + private void initialize() { + this.getStyleClass().add(DEFAULT_STYLE_CLASS); + // it's up for the user to add this behavior +// toggleColor.addListener((o, oldVal, newVal) -> { +// // update line color in case not set by the user +// if(newVal instanceof Color) +// toggleLineColor.set(((Color)newVal).desaturate().desaturate().brighter()); +// }); + } + + /*************************************************************************** + * * + * styleable Properties * + * * + **************************************************************************/ + + /** + * Initialize the style class to 'jfx-toggle-button'. + *

+ * This is the selector class from which CSS can be used to style + * this control. + */ + private static final String DEFAULT_STYLE_CLASS = "jfx-toggle-button"; + + /** + * default color used when the button is toggled + */ + private final StyleableObjectProperty toggleColor = new SimpleStyleableObjectProperty<>(StyleableProperties.TOGGLE_COLOR, + JFXToggleButton.this, + "toggleColor", + Color.valueOf( + "#009688")); + + public Paint getToggleColor() { + return toggleColor == null ? Color.valueOf("#009688") : toggleColor.get(); + } + + public StyleableObjectProperty toggleColorProperty() { + return this.toggleColor; + } + + public void setToggleColor(Paint color) { + this.toggleColor.set(color); + } + + /** + * default color used when the button is not toggled + */ + private StyleableObjectProperty untoggleColor = new SimpleStyleableObjectProperty<>(StyleableProperties.UNTOGGLE_COLOR, + JFXToggleButton.this, + "unToggleColor", + Color.valueOf( + "#FAFAFA")); + + public Paint getUnToggleColor() { + return untoggleColor == null ? Color.valueOf("#FAFAFA") : untoggleColor.get(); + } + + public StyleableObjectProperty unToggleColorProperty() { + return this.untoggleColor; + } + + public void setUnToggleColor(Paint color) { + this.untoggleColor.set(color); + } + + /** + * default line color used when the button is toggled + */ + private final StyleableObjectProperty toggleLineColor = new SimpleStyleableObjectProperty<>( + StyleableProperties.TOGGLE_LINE_COLOR, + JFXToggleButton.this, + "toggleLineColor", + Color.valueOf("#77C2BB")); + + public Paint getToggleLineColor() { + return toggleLineColor == null ? Color.valueOf("#77C2BB") : toggleLineColor.get(); + } + + public StyleableObjectProperty toggleLineColorProperty() { + return this.toggleLineColor; + } + + public void setToggleLineColor(Paint color) { + this.toggleLineColor.set(color); + } + + /** + * default line color used when the button is not toggled + */ + private final StyleableObjectProperty untoggleLineColor = new SimpleStyleableObjectProperty<>( + StyleableProperties.UNTOGGLE_LINE_COLOR, + JFXToggleButton.this, + "unToggleLineColor", + Color.valueOf("#999999")); + + public Paint getUnToggleLineColor() { + return untoggleLineColor == null ? Color.valueOf("#999999") : untoggleLineColor.get(); + } + + public StyleableObjectProperty unToggleLineColorProperty() { + return this.untoggleLineColor; + } + + public void setUnToggleLineColor(Paint color) { + this.untoggleLineColor.set(color); + } + + /** + * Default size of the toggle button. + */ + private final StyleableDoubleProperty size = new SimpleStyleableDoubleProperty( + StyleableProperties.SIZE, + JFXToggleButton.this, + "size", + 10.0); + + public double getSize() { + return size.get(); + } + + public StyleableDoubleProperty sizeProperty() { + return this.size; + } + + public void setSize(double size) { + this.size.set(size); + } + + /** + * Disable the visual indicator for focus + */ + private final StyleableBooleanProperty disableVisualFocus = new SimpleStyleableBooleanProperty(StyleableProperties.DISABLE_VISUAL_FOCUS, + JFXToggleButton.this, + "disableVisualFocus", + false); + + public final StyleableBooleanProperty disableVisualFocusProperty() { + return this.disableVisualFocus; + } + + public final Boolean isDisableVisualFocus() { + return disableVisualFocus != null && this.disableVisualFocusProperty().get(); + } + + public final void setDisableVisualFocus(final Boolean disabled) { + this.disableVisualFocusProperty().set(disabled); + } + + + /** + * disable animation on button action + */ + private final StyleableBooleanProperty disableAnimation = new SimpleStyleableBooleanProperty(StyleableProperties.DISABLE_ANIMATION, + JFXToggleButton.this, + "disableAnimation", + !AnimationUtils.isAnimationEnabled()); + + public final StyleableBooleanProperty disableAnimationProperty() { + return this.disableAnimation; + } + + public final Boolean isDisableAnimation() { + return disableAnimation != null && this.disableAnimationProperty().get(); + } + + public final void setDisableAnimation(final Boolean disabled) { + this.disableAnimationProperty().set(disabled); + } + + private static final class StyleableProperties { + private static final CssMetaData TOGGLE_COLOR = + new CssMetaData<>("-jfx-toggle-color", + PaintConverter.getInstance(), Color.valueOf("#009688")) { + @Override + public boolean isSettable(JFXToggleButton control) { + return control.toggleColor == null || !control.toggleColor.isBound(); + } + + @Override + public StyleableProperty getStyleableProperty(JFXToggleButton control) { + return control.toggleColorProperty(); + } + }; + + private static final CssMetaData UNTOGGLE_COLOR = + new CssMetaData<>("-jfx-untoggle-color", + PaintConverter.getInstance(), Color.valueOf("#FAFAFA")) { + @Override + public boolean isSettable(JFXToggleButton control) { + return control.untoggleColor == null || !control.untoggleColor.isBound(); + } + + @Override + public StyleableProperty getStyleableProperty(JFXToggleButton control) { + return control.unToggleColorProperty(); + } + }; + + private static final CssMetaData TOGGLE_LINE_COLOR = + new CssMetaData<>("-jfx-toggle-line-color", + PaintConverter.getInstance(), Color.valueOf("#77C2BB")) { + @Override + public boolean isSettable(JFXToggleButton control) { + return control.toggleLineColor == null || !control.toggleLineColor.isBound(); + } + + @Override + public StyleableProperty getStyleableProperty(JFXToggleButton control) { + return control.toggleLineColorProperty(); + } + }; + + private static final CssMetaData UNTOGGLE_LINE_COLOR = + new CssMetaData<>("-jfx-untoggle-line-color", + PaintConverter.getInstance(), Color.valueOf("#999999")) { + @Override + public boolean isSettable(JFXToggleButton control) { + return control.untoggleLineColor == null || !control.untoggleLineColor.isBound(); + } + + @Override + public StyleableProperty getStyleableProperty(JFXToggleButton control) { + return control.unToggleLineColorProperty(); + } + }; + + private static final CssMetaData SIZE = + new CssMetaData<>("-jfx-size", + StyleConverter.getSizeConverter(), 10.0) { + @Override + public boolean isSettable(JFXToggleButton control) { + return !control.size.isBound(); + } + + @Override + public StyleableProperty getStyleableProperty(JFXToggleButton control) { + return control.sizeProperty(); + } + }; + private static final CssMetaData DISABLE_VISUAL_FOCUS = + new CssMetaData<>("-jfx-disable-visual-focus", + BooleanConverter.getInstance(), false) { + @Override + public boolean isSettable(JFXToggleButton control) { + return control.disableVisualFocus == null || !control.disableVisualFocus.isBound(); + } + + @Override + public StyleableBooleanProperty getStyleableProperty(JFXToggleButton control) { + return control.disableVisualFocusProperty(); + } + }; + + private static final CssMetaData DISABLE_ANIMATION = + new CssMetaData<>("-jfx-disable-animation", + BooleanConverter.getInstance(), false) { + @Override + public boolean isSettable(JFXToggleButton control) { + return control.disableAnimation == null || !control.disableAnimation.isBound(); + } + + @Override + public StyleableBooleanProperty getStyleableProperty(JFXToggleButton control) { + return control.disableAnimationProperty(); + } + }; + + private static final List> CHILD_STYLEABLES; + + static { + final List> styleables = + new ArrayList<>(ToggleButton.getClassCssMetaData()); + Collections.addAll(styleables, + SIZE, + TOGGLE_COLOR, + UNTOGGLE_COLOR, + TOGGLE_LINE_COLOR, + UNTOGGLE_LINE_COLOR, + DISABLE_VISUAL_FOCUS, + DISABLE_ANIMATION + ); + CHILD_STYLEABLES = Collections.unmodifiableList(styleables); + } + } + + @Override + public List> getControlCssMetaData() { + return getClassCssMetaData(); + } + + public static List> getClassCssMetaData() { + return StyleableProperties.CHILD_STYLEABLES; + } + +} diff --git a/HMCL/src/main/java/com/jfoenix/skins/JFXCheckBoxSkin.java b/HMCL/src/main/java/com/jfoenix/skins/JFXCheckBoxSkin.java index 6c0903de8..f6aaa1d68 100644 --- a/HMCL/src/main/java/com/jfoenix/skins/JFXCheckBoxSkin.java +++ b/HMCL/src/main/java/com/jfoenix/skins/JFXCheckBoxSkin.java @@ -32,9 +32,9 @@ import javafx.scene.layout.BorderWidths; import javafx.scene.layout.CornerRadii; import javafx.scene.layout.StackPane; import javafx.scene.paint.Color; -import javafx.scene.paint.Paint; import javafx.scene.shape.SVGPath; import javafx.util.Duration; +import org.jackhuang.hmcl.theme.Themes; public class JFXCheckBoxSkin extends CheckBoxSkin { private final StackPane box = new StackPane(); @@ -54,7 +54,7 @@ public class JFXCheckBoxSkin extends CheckBoxSkin { this.box.setPrefSize(18.0, 18.0); this.box.setMaxSize(18.0, 18.0); this.box.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, new CornerRadii(2.0), Insets.EMPTY))); - this.box.setBorder(new Border(new BorderStroke(control.getUnCheckedColor(), BorderStrokeStyle.SOLID, new CornerRadii(2.0), new BorderWidths(this.lineThick)))); + this.box.setBorder(new Border(new BorderStroke(Themes.getColorScheme().getOnSurfaceVariant(), BorderStrokeStyle.SOLID, new CornerRadii(2.0), new BorderWidths(this.lineThick)))); StackPane boxContainer = new StackPane(); boxContainer.getChildren().add(this.box); boxContainer.setPadding(new Insets(this.padding)); @@ -64,7 +64,7 @@ public class JFXCheckBoxSkin extends CheckBoxSkin { shape.setContent("M384 690l452-452 60 60-512 512-238-238 60-60z"); this.mark.setShape(shape); this.mark.setMaxSize(15.0, 12.0); - this.mark.setStyle("-fx-background-color:WHITE; -fx-border-color:WHITE; -fx-border-width:2px;"); + this.mark.setStyle("-fx-background-color:-monet-on-primary; -fx-border-color:-monet-on-primary; -fx-border-width:2px;"); this.mark.setVisible(false); this.mark.setScaleX(0.0); this.mark.setScaleY(0.0); @@ -107,13 +107,12 @@ public class JFXCheckBoxSkin extends CheckBoxSkin { private void updateColors() { var control = (JFXCheckBox) getSkinnable(); - final Paint color = control.isSelected() - ? control.getCheckedColor() - : control.getUnCheckedColor(); - JFXNodeUtils.updateBackground(box.getBackground(), box, control.isSelected() ? control.getCheckedColor() : Color.TRANSPARENT); - rippler.setRipplerFill(color); + boolean isSelected = control.isSelected(); + JFXNodeUtils.updateBackground(box.getBackground(), box, isSelected ? control.getCheckedColor() : Color.TRANSPARENT); + rippler.setRipplerFill(isSelected ? control.getCheckedColor() : control.getUnCheckedColor()); final BorderStroke borderStroke = box.getBorder().getStrokes().get(0); - box.setBorder(new Border(new BorderStroke(color, + box.setBorder(new Border(new BorderStroke( + isSelected ? control.getCheckedColor() : Themes.getColorScheme().getOnSurfaceVariant(), borderStroke.getTopStyle(), borderStroke.getRadii(), borderStroke.getWidths()))); @@ -185,7 +184,11 @@ public class JFXCheckBoxSkin extends CheckBoxSkin { this.select.setRate(selection ? 1.0 : -1.0); this.transition.play(); this.select.play(); - this.box.setBorder(new Border(new BorderStroke(selection ? control.getCheckedColor() : control.getUnCheckedColor(), BorderStrokeStyle.SOLID, new CornerRadii(2.0), new BorderWidths(this.lineThick)))); + this.box.setBorder(new Border(new BorderStroke( + selection ? control.getCheckedColor() : Themes.getColorScheme().getOnSurfaceVariant(), + BorderStrokeStyle.SOLID, + new CornerRadii(2.0), + new BorderWidths(this.lineThick)))); } private void createFillTransition() { diff --git a/HMCL/src/main/java/com/jfoenix/skins/JFXColorPalette.java b/HMCL/src/main/java/com/jfoenix/skins/JFXColorPalette.java index b27384d3d..60e41ea80 100644 --- a/HMCL/src/main/java/com/jfoenix/skins/JFXColorPalette.java +++ b/HMCL/src/main/java/com/jfoenix/skins/JFXColorPalette.java @@ -28,7 +28,6 @@ import javafx.collections.ObservableList; import javafx.event.ActionEvent; import javafx.event.Event; import javafx.geometry.Bounds; -import javafx.geometry.Insets; import javafx.geometry.NodeOrientation; import javafx.geometry.Pos; import javafx.scene.Node; @@ -116,11 +115,6 @@ final class JFXColorPalette extends Region { colorPicker.getCustomColors().addListener((Change change) -> buildCustomColors()); VBox paletteBox = new VBox(); paletteBox.getStyleClass().add("color-palette"); - paletteBox.setBackground(new Background(new BackgroundFill(Color.WHITE, CornerRadii.EMPTY, Insets.EMPTY))); - paletteBox.setBorder(new Border(new BorderStroke(Color.valueOf("#9E9E9E"), - BorderStrokeStyle.SOLID, - CornerRadii.EMPTY, - BorderWidths.DEFAULT))); paletteBox.getChildren().addAll(colorPickerGrid); if (colorPicker.getPreDefinedColors() == null) { paletteBox.getChildren().addAll(customColorLabel, customColorGrid, customColorLink); diff --git a/HMCL/src/main/java/com/jfoenix/skins/JFXCustomColorPickerDialog.java b/HMCL/src/main/java/com/jfoenix/skins/JFXCustomColorPickerDialog.java index 82d251bf1..109e01d13 100644 --- a/HMCL/src/main/java/com/jfoenix/skins/JFXCustomColorPickerDialog.java +++ b/HMCL/src/main/java/com/jfoenix/skins/JFXCustomColorPickerDialog.java @@ -41,6 +41,7 @@ import javafx.scene.layout.*; import javafx.scene.paint.Color; import javafx.stage.*; import javafx.util.Duration; +import org.jackhuang.hmcl.setting.StyleSheets; import java.util.concurrent.atomic.AtomicInteger; @@ -48,8 +49,6 @@ import java.util.concurrent.atomic.AtomicInteger; * @author Shadi Shaheen */ public class JFXCustomColorPickerDialog extends StackPane { - - public static final String rgbFieldStyle = "-fx-background-color:TRANSPARENT;-fx-font-weight: BOLD;-fx-prompt-text-fill: #808080; -fx-alignment: top-left ; -fx-max-width: 300;"; private final Stage dialog = new Stage(); // used for concurrency control and preventing FX-thread over use private final AtomicInteger concurrencyController = new AtomicInteger(-1); @@ -81,15 +80,7 @@ public class JFXCustomColorPickerDialog extends StackPane { pickerDecorator.setOnCloseButtonAction(this::updateColor); pickerDecorator.setPickOnBounds(false); customScene = new Scene(pickerDecorator, Color.TRANSPARENT); - if (owner != null) { - final Scene ownerScene = owner.getScene(); - if (ownerScene != null) { - if (ownerScene.getUserAgentStylesheet() != null) { - customScene.setUserAgentStylesheet(ownerScene.getUserAgentStylesheet()); - } - customScene.getStylesheets().addAll(ownerScene.getStylesheets()); - } - } + StyleSheets.init(customScene); curvedColorPicker = new JFXCustomColorPicker(); StackPane pane = new StackPane(curvedColorPicker); @@ -104,17 +95,15 @@ public class JFXCustomColorPickerDialog extends StackPane { JFXTextField hsbField = new JFXTextField(); JFXTextField hexField = new JFXTextField(); - rgbField.setStyle(rgbFieldStyle); + rgbField.getStyleClass().add("custom-color-field"); rgbField.setPromptText("RGB Color"); rgbField.textProperty().addListener((o, oldVal, newVal) -> updateColorFromUserInput(newVal)); - hsbField.setStyle( - "-fx-background-color:TRANSPARENT;-fx-font-weight: BOLD;-fx-prompt-text-fill: #808080; -fx-alignment: top-left ; -fx-max-width: 300;"); + hsbField.getStyleClass().add("custom-color-field"); hsbField.setPromptText("HSB Color"); hsbField.textProperty().addListener((o, oldVal, newVal) -> updateColorFromUserInput(newVal)); - hexField.setStyle( - "-fx-background-color:TRANSPARENT;-fx-font-weight: BOLD;-fx-prompt-text-fill: #808080; -fx-alignment: top-left ; -fx-max-width: 300;"); + hexField.getStyleClass().add("custom-color-field"); hexField.setPromptText("#HEX Color"); hexField.textProperty().addListener((o, oldVal, newVal) -> updateColorFromUserInput(newVal)); diff --git a/HMCL/src/main/java/com/jfoenix/skins/JFXRadioButtonSkin.java b/HMCL/src/main/java/com/jfoenix/skins/JFXRadioButtonSkin.java index 9aec0a2ad..229b434fa 100644 --- a/HMCL/src/main/java/com/jfoenix/skins/JFXRadioButtonSkin.java +++ b/HMCL/src/main/java/com/jfoenix/skins/JFXRadioButtonSkin.java @@ -23,6 +23,7 @@ import javafx.scene.paint.Color; import javafx.scene.shape.Circle; import javafx.scene.text.Text; import javafx.util.Duration; +import org.jackhuang.hmcl.ui.animation.AnimationUtils; public class JFXRadioButtonSkin extends RadioButtonSkin { private static final double PADDING = 15.0; @@ -68,37 +69,10 @@ public class JFXRadioButtonSkin extends RadioButtonSkin { }); control.pressedProperty().addListener((o, oldVal, newVal) -> this.rippler.hideOverlay()); - this.registerChangeListener(control.selectedColorProperty(), ignored -> { - this.updateAnimation(); - boolean isSelected = this.getSkinnable().isSelected(); - Color unSelectedColor = ((JFXRadioButton) this.getSkinnable()).getUnSelectedColor(); - Color selectedColor = ((JFXRadioButton) this.getSkinnable()).getSelectedColor(); - this.rippler.setRipplerFill(isSelected ? selectedColor : unSelectedColor); - if (isSelected) { - this.radio.strokeProperty().set(selectedColor); - } - }); - - this.registerChangeListener(control.unSelectedColorProperty(), ignored -> { - this.updateAnimation(); - boolean isSelected = this.getSkinnable().isSelected(); - Color unSelectedColor = ((JFXRadioButton) this.getSkinnable()).getUnSelectedColor(); - Color selectedColor = ((JFXRadioButton) this.getSkinnable()).getSelectedColor(); - this.rippler.setRipplerFill(isSelected ? selectedColor : unSelectedColor); - if (!isSelected) { - this.radio.strokeProperty().set(unSelectedColor); - } - - }); + this.registerChangeListener(control.selectedColorProperty(), ignored -> updateColors()); + this.registerChangeListener(control.unSelectedColorProperty(), ignored -> updateColors()); this.registerChangeListener(control.selectedProperty(), ignored -> { - boolean isSelected = this.getSkinnable().isSelected(); - Color unSelectedColor = ((JFXRadioButton) this.getSkinnable()).getUnSelectedColor(); - Color selectedColor = ((JFXRadioButton) this.getSkinnable()).getSelectedColor(); - this.rippler.setRipplerFill(isSelected ? selectedColor : unSelectedColor); - if (this.timeline == null) { - this.updateAnimation(); - } - + updateColors(); this.playAnimation(); }); } @@ -133,38 +107,46 @@ public class JFXRadioButtonSkin extends RadioButtonSkin { } private void initializeComponents() { - Color unSelectedColor = ((JFXRadioButton) this.getSkinnable()).getUnSelectedColor(); - Color selectedColor = ((JFXRadioButton) this.getSkinnable()).getSelectedColor(); - this.radio.setStroke(unSelectedColor); - this.rippler.setRipplerFill(this.getSkinnable().isSelected() ? selectedColor : unSelectedColor); - this.updateAnimation(); + this.updateColors(); this.playAnimation(); } private void playAnimation() { - this.timeline.setRate(this.getSkinnable().isSelected() ? 1.0 : -1.0); - this.timeline.play(); - } - - private void updateAnimation() { - Color unSelectedColor = ((JFXRadioButton) this.getSkinnable()).getUnSelectedColor(); - Color selectedColor = ((JFXRadioButton) this.getSkinnable()).getSelectedColor(); - this.timeline = new Timeline( - new KeyFrame(Duration.ZERO, - new KeyValue(this.dot.scaleXProperty(), 0, Interpolator.EASE_BOTH), - new KeyValue(this.dot.scaleYProperty(), 0, Interpolator.EASE_BOTH), - new KeyValue(this.radio.strokeProperty(), unSelectedColor, Interpolator.EASE_BOTH)), - new KeyFrame(Duration.millis(200.0), - new KeyValue(this.dot.scaleXProperty(), 1, Interpolator.EASE_BOTH), - new KeyValue(this.dot.scaleYProperty(), 1, Interpolator.EASE_BOTH), - new KeyValue(this.radio.strokeProperty(), selectedColor, Interpolator.EASE_BOTH)) - ); + if (AnimationUtils.isAnimationEnabled()) { + if (this.timeline == null) { + this.timeline = new Timeline( + new KeyFrame(Duration.ZERO, + new KeyValue(this.dot.scaleXProperty(), 0, Interpolator.EASE_BOTH), + new KeyValue(this.dot.scaleYProperty(), 0, Interpolator.EASE_BOTH)), + new KeyFrame(Duration.millis(200.0), + new KeyValue(this.dot.scaleXProperty(), 1, Interpolator.EASE_BOTH), + new KeyValue(this.dot.scaleYProperty(), 1, Interpolator.EASE_BOTH)) + ); + } else { + this.timeline.stop(); + } + this.timeline.setRate(this.getSkinnable().isSelected() ? 1.0 : -1.0); + this.timeline.play(); + } else { + double endScale = this.getSkinnable().isSelected() ? 1.0 : 0.0; + this.dot.setScaleX(endScale); + this.dot.setScaleY(endScale); + } } private void removeRadio() { this.getChildren().removeIf(node -> "radio".equals(node.getStyleClass().get(0))); } + private void updateColors() { + var control = (JFXRadioButton) getSkinnable(); + boolean isSelected = control.isSelected(); + Color unSelectedColor = control.getUnSelectedColor(); + Color selectedColor = control.getSelectedColor(); + rippler.setRipplerFill(isSelected ? selectedColor : unSelectedColor); + radio.setStroke(isSelected ? selectedColor : unSelectedColor); + } + protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { return super.computePrefWidth(height, topInset, rightInset, bottomInset, leftInset) + this.snapSizeX(this.radio.minWidth(-1.0)) + this.labelOffset + 2.0 * PADDING; } diff --git a/HMCL/src/main/java/com/jfoenix/skins/JFXToggleButtonSkin.java b/HMCL/src/main/java/com/jfoenix/skins/JFXToggleButtonSkin.java new file mode 100644 index 000000000..0548204d7 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/skins/JFXToggleButtonSkin.java @@ -0,0 +1,186 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package com.jfoenix.skins; + +import com.jfoenix.controls.JFXRippler; +import com.jfoenix.controls.JFXRippler.RipplerMask; +import com.jfoenix.controls.JFXRippler.RipplerPos; +import com.jfoenix.controls.JFXToggleButton; +import com.jfoenix.effects.JFXDepthManager; +import com.jfoenix.transitions.JFXAnimationTimer; +import com.jfoenix.transitions.JFXKeyFrame; +import com.jfoenix.transitions.JFXKeyValue; +import javafx.animation.Interpolator; +import javafx.geometry.Insets; +import javafx.scene.Cursor; +import javafx.scene.control.skin.ToggleButtonSkin; +import javafx.scene.layout.StackPane; +import javafx.scene.shape.Circle; +import javafx.scene.shape.Line; +import javafx.scene.shape.StrokeLineCap; +import javafx.util.Duration; + +/** + *

Material Design ToggleButton Skin

+ * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXToggleButtonSkin extends ToggleButtonSkin { + + + private Runnable releaseManualRippler = null; + + private JFXAnimationTimer timer; + private final Circle circle; + private final Line line; + + public JFXToggleButtonSkin(JFXToggleButton toggleButton) { + super(toggleButton); + + double circleRadius = toggleButton.getSize(); + + line = new Line(); + line.setStroke(getSkinnable().isSelected() ? toggleButton.getToggleLineColor() : toggleButton.getUnToggleLineColor()); + line.setStartX(0); + line.setStartY(0); + line.setEndX(circleRadius * 2 + 2); + line.setEndY(0); + line.setStrokeWidth(circleRadius * 1.5); + line.setStrokeLineCap(StrokeLineCap.ROUND); + line.setSmooth(true); + + circle = new Circle(); + circle.setFill(getSkinnable().isSelected() ? toggleButton.getToggleColor() : toggleButton.getUnToggleColor()); + circle.setCenterX(-circleRadius); + circle.setCenterY(0); + circle.setRadius(circleRadius); + circle.setSmooth(true); + JFXDepthManager.setDepth(circle, 1); + + StackPane circlePane = new StackPane(); + circlePane.getChildren().add(circle); + circlePane.setPadding(new Insets(circleRadius * 1.5)); + + JFXRippler rippler = new JFXRippler(circlePane, RipplerMask.CIRCLE, RipplerPos.BACK); + rippler.setRipplerFill(getSkinnable().isSelected() ? toggleButton.getToggleLineColor() : toggleButton.getUnToggleLineColor()); + rippler.setTranslateX(computeTranslation(circleRadius, line)); + + final StackPane main = new StackPane(); + main.getChildren().setAll(line, rippler); + main.setCursor(Cursor.HAND); + + // show focus traversal effect + getSkinnable().armedProperty().addListener((o, oldVal, newVal) -> { + if (newVal) { + releaseManualRippler = rippler.createManualRipple(); + } else if (releaseManualRippler != null) { + releaseManualRippler.run(); + } + }); + toggleButton.focusedProperty().addListener((o, oldVal, newVal) -> { + if (!toggleButton.isDisableVisualFocus()) { + if (newVal) { + if (!getSkinnable().isPressed()) { + rippler.setOverlayVisible(true); + } + } else { + rippler.setOverlayVisible(false); + } + } + }); + toggleButton.pressedProperty().addListener(observable -> rippler.setOverlayVisible(false)); + + // add change listener to selected property + getSkinnable().selectedProperty().addListener(observable -> { + rippler.setRipplerFill(toggleButton.isSelected() ? toggleButton.getToggleLineColor() : toggleButton.getUnToggleLineColor()); + if (!toggleButton.isDisableAnimation()) { + timer.reverseAndContinue(); + } else { + rippler.setTranslateX(computeTranslation(circleRadius, line)); + } + }); + + getSkinnable().setGraphic(main); + + timer = new JFXAnimationTimer( + new JFXKeyFrame(Duration.millis(100), + JFXKeyValue.builder() + .setTarget(rippler.translateXProperty()) + .setEndValueSupplier(() -> computeTranslation(circleRadius, line)) + .setInterpolator(Interpolator.EASE_BOTH) + .setAnimateCondition(() -> !((JFXToggleButton) getSkinnable()).isDisableAnimation()) + .build(), + + JFXKeyValue.builder() + .setTarget(line.strokeProperty()) + .setEndValueSupplier(() -> getSkinnable().isSelected() ? + ((JFXToggleButton) getSkinnable()).getToggleLineColor() + : ((JFXToggleButton) getSkinnable()).getUnToggleLineColor()) + .setInterpolator(Interpolator.EASE_BOTH) + .setAnimateCondition(() -> !((JFXToggleButton) getSkinnable()).isDisableAnimation()) + .build(), + + JFXKeyValue.builder() + .setTarget(circle.fillProperty()) + .setEndValueSupplier(() -> getSkinnable().isSelected() ? + ((JFXToggleButton) getSkinnable()).getToggleColor() + : ((JFXToggleButton) getSkinnable()).getUnToggleColor()) + .setInterpolator(Interpolator.EASE_BOTH) + .setAnimateCondition(() -> !((JFXToggleButton) getSkinnable()).isDisableAnimation()) + .build() + ) + ); + timer.setCacheNodes(circle, line); + + registerChangeListener(toggleButton.toggleColorProperty(), observableValue -> { + if (getSkinnable().isSelected()) { + circle.setFill(((JFXToggleButton) getSkinnable()).getToggleColor()); + } + }); + registerChangeListener(toggleButton.unToggleColorProperty(), observableValue -> { + if (!getSkinnable().isSelected()) { + circle.setFill(((JFXToggleButton) getSkinnable()).getUnToggleColor()); + } + }); + registerChangeListener(toggleButton.toggleLineColorProperty(), observableValue -> { + if (getSkinnable().isSelected()) { + line.setStroke(((JFXToggleButton) getSkinnable()).getToggleLineColor()); + } + }); + registerChangeListener(toggleButton.unToggleColorProperty(), observableValue -> { + if (!getSkinnable().isSelected()) { + line.setStroke(((JFXToggleButton) getSkinnable()).getUnToggleLineColor()); + } + }); + } + + private double computeTranslation(double circleRadius, Line line) { + return (getSkinnable().isSelected() ? 1 : -1) * ((line.getLayoutBounds().getWidth() / 2) - circleRadius + 2); + } + + @Override + public void dispose() { + super.dispose(); + timer.dispose(); + timer = null; + } +} diff --git a/HMCL/src/main/java/com/jfoenix/transitions/CacheMemento.java b/HMCL/src/main/java/com/jfoenix/transitions/CacheMemento.java new file mode 100644 index 000000000..cbc25fc7b --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/transitions/CacheMemento.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package com.jfoenix.transitions; + +import javafx.scene.CacheHint; +import javafx.scene.Node; +import javafx.scene.layout.Region; + +import java.util.concurrent.atomic.AtomicBoolean; + +public final class CacheMemento { + private boolean cache; + private boolean cacheShape; + private boolean snapToPixel; + private CacheHint cacheHint = CacheHint.DEFAULT; + private final Node node; + private final AtomicBoolean isCached = new AtomicBoolean(false); + + public CacheMemento(Node node) { + this.node = node; + } + + /** + * this method will cache the node only if it wasn't cached before + */ + public void cache() { + if (!isCached.getAndSet(true)) { + this.cache = node.isCache(); + this.cacheHint = node.getCacheHint(); + node.setCache(true); + node.setCacheHint(CacheHint.SPEED); + if (node instanceof Region region) { + this.cacheShape = region.isCacheShape(); + this.snapToPixel = region.isSnapToPixel(); + region.setCacheShape(true); + region.setSnapToPixel(true); + } + } + } + + public void restore() { + if (isCached.getAndSet(false)) { + node.setCache(cache); + node.setCacheHint(cacheHint); + if (node instanceof Region region) { + region.setCacheShape(cacheShape); + region.setSnapToPixel(snapToPixel); + } + } + } +} diff --git a/HMCL/src/main/java/com/jfoenix/transitions/JFXAnimationTimer.java b/HMCL/src/main/java/com/jfoenix/transitions/JFXAnimationTimer.java new file mode 100644 index 000000000..623a998ea --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/transitions/JFXAnimationTimer.java @@ -0,0 +1,286 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package com.jfoenix.transitions; + +import javafx.animation.AnimationTimer; +import javafx.beans.value.WritableValue; +import javafx.scene.Node; +import javafx.util.Duration; + +import java.util.*; +import java.util.function.Supplier; + +/** + * Custom AnimationTimer that can be created the same way as a timeline, + * however it doesn't behave the same yet. it only animates in one direction, + * it doesn't support animation 0 -> 1 -> 0.5 + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2017-09-21 + */ + +public class JFXAnimationTimer extends AnimationTimer { + + private Set animationHandlers = new HashSet<>(); + private long startTime = -1; + private boolean running = false; + private List caches = new ArrayList<>(); + private double totalElapsedMilliseconds; + + public JFXAnimationTimer(JFXKeyFrame... keyFrames) { + for (JFXKeyFrame keyFrame : keyFrames) { + Duration duration = keyFrame.getDuration(); + final Set> keyValuesSet = keyFrame.getValues(); + if (!keyValuesSet.isEmpty()) { + animationHandlers.add(new AnimationHandler(duration, keyFrame.getAnimateCondition(), keyFrame.getValues())); + } + } + } + + private final HashMap mutableFrames = new HashMap<>(); + + public void addKeyFrame(JFXKeyFrame keyFrame) throws Exception { + if (isRunning()) { + throw new Exception("Can't update animation timer while running"); + } + Duration duration = keyFrame.getDuration(); + final Set> keyValuesSet = keyFrame.getValues(); + if (!keyValuesSet.isEmpty()) { + final AnimationHandler handler = new AnimationHandler(duration, keyFrame.getAnimateCondition(), keyFrame.getValues()); + animationHandlers.add(handler); + mutableFrames.put(keyFrame, handler); + } + } + + public void removeKeyFrame(JFXKeyFrame keyFrame) throws Exception { + if (isRunning()) { + throw new Exception("Can't update animation timer while running"); + } + AnimationHandler handler = mutableFrames.get(keyFrame); + animationHandlers.remove(handler); + } + + @Override + public void start() { + super.start(); + running = true; + startTime = -1; + for (AnimationHandler animationHandler : animationHandlers) { + animationHandler.init(); + } + for (CacheMemento cache : caches) { + cache.cache(); + } + } + + @Override + public void handle(long now) { + startTime = startTime == -1 ? now : startTime; + totalElapsedMilliseconds = (now - startTime) / 1000000.0; + boolean stop = true; + for (AnimationHandler handler : animationHandlers) { + handler.animate(totalElapsedMilliseconds); + if (!handler.finished) { + stop = false; + } + } + if (stop) { + this.stop(); + } + } + + /** + * this method will pause the timer and reverse the animation if the timer already + * started otherwise it will start the animation. + */ + public void reverseAndContinue() { + if (isRunning()) { + super.stop(); + for (AnimationHandler handler : animationHandlers) { + handler.reverse(totalElapsedMilliseconds); + } + startTime = -1; + super.start(); + } else { + start(); + } + } + + @Override + public void stop() { + super.stop(); + running = false; + for (AnimationHandler handler : animationHandlers) { + handler.clear(); + } + for (CacheMemento cache : caches) { + cache.restore(); + } + if (onFinished != null) { + onFinished.run(); + } + } + + public void applyEndValues() { + if (isRunning()) { + super.stop(); + } + for (AnimationHandler handler : animationHandlers) { + handler.applyEndValues(); + } + startTime = -1; + } + + public boolean isRunning() { + return running; + } + + private Runnable onFinished = null; + + public void setOnFinished(Runnable onFinished) { + this.onFinished = onFinished; + } + + public void setCacheNodes(Node... nodesToCache) { + caches.clear(); + if (nodesToCache != null) { + for (Node node : nodesToCache) { + caches.add(new CacheMemento(node)); + } + } + } + + public void dispose() { + caches.clear(); + for (AnimationHandler handler : animationHandlers) { + handler.dispose(); + } + animationHandlers.clear(); + } + + static class AnimationHandler { + private final double duration; + private double currentDuration; + private final Set> keyValues; + private Supplier animationCondition = null; + private boolean finished = false; + + private final HashMap, Object> initialValuesMap = new HashMap<>(); + private final HashMap, Object> endValuesMap = new HashMap<>(); + + AnimationHandler(Duration duration, Supplier animationCondition, Set> keyValues) { + this.duration = duration.toMillis(); + currentDuration = this.duration; + this.keyValues = keyValues; + this.animationCondition = animationCondition; + } + + public void init() { + finished = animationCondition != null && !animationCondition.get(); + for (JFXKeyValue keyValue : keyValues) { + if (keyValue.getTarget() != null) { + // replaced putIfAbsent for mobile compatibility + if (!initialValuesMap.containsKey(keyValue.getTarget())) { + initialValuesMap.put(keyValue.getTarget(), keyValue.getTarget().getValue()); + } + if (!endValuesMap.containsKey(keyValue.getTarget())) { + endValuesMap.put(keyValue.getTarget(), keyValue.getEndValue()); + } + } + } + } + + void reverse(double now) { + finished = animationCondition != null && !animationCondition.get(); + currentDuration = duration - (currentDuration - now); + // update initial values + for (JFXKeyValue keyValue : keyValues) { + final WritableValue target = keyValue.getTarget(); + if (target != null) { + initialValuesMap.put(target, target.getValue()); + endValuesMap.put(target, keyValue.getEndValue()); + } + } + } + + // now in milliseconds + @SuppressWarnings({"unchecked"}) + public void animate(double now) { + // if animate condition for the key frame is not met then do nothing + if (finished) { + return; + } + if (now <= currentDuration) { + for (JFXKeyValue keyValue : keyValues) { + if (keyValue.isValid()) { + @SuppressWarnings("rawtypes") final WritableValue target = keyValue.getTarget(); + final Object endValue = endValuesMap.get(target); + if (endValue != null && target != null && !target.getValue().equals(endValue)) { + target.setValue(keyValue.getInterpolator().interpolate(initialValuesMap.get(target), endValue, now / currentDuration)); + } + } + } + } else { + if (!finished) { + finished = true; + for (JFXKeyValue keyValue : keyValues) { + if (keyValue.isValid()) { + @SuppressWarnings("rawtypes") final WritableValue target = keyValue.getTarget(); + if (target != null) { + // set updated end value instead of cached + final Object endValue = keyValue.getEndValue(); + if (endValue != null) { + target.setValue(endValue); + } + } + } + } + currentDuration = duration; + } + } + } + + @SuppressWarnings("unchecked") + public void applyEndValues() { + for (JFXKeyValue keyValue : keyValues) { + if (keyValue.isValid()) { + @SuppressWarnings("rawtypes") final WritableValue target = keyValue.getTarget(); + if (target != null) { + final Object endValue = keyValue.getEndValue(); + if (endValue != null && !target.getValue().equals(endValue)) { + target.setValue(endValue); + } + } + } + } + } + + public void clear() { + initialValuesMap.clear(); + endValuesMap.clear(); + } + + void dispose() { + clear(); + keyValues.clear(); + } + } +} diff --git a/HMCL/src/main/java/com/jfoenix/transitions/JFXKeyFrame.java b/HMCL/src/main/java/com/jfoenix/transitions/JFXKeyFrame.java new file mode 100644 index 000000000..8c46455c1 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/transitions/JFXKeyFrame.java @@ -0,0 +1,104 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package com.jfoenix.transitions; + +import javafx.util.Duration; + +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.function.Supplier; + +/** + * @author Shadi Shaheen + * @version 1.0 + * @since 2017-09-21 + */ + +public class JFXKeyFrame { + + private Duration duration; + private Set> keyValues = new CopyOnWriteArraySet<>(); + private Supplier animateCondition = null; + + public JFXKeyFrame(Duration duration, JFXKeyValue... keyValues) { + this.duration = duration; + for (final JFXKeyValue keyValue : keyValues) { + if (keyValue != null) { + this.keyValues.add(keyValue); + } + } + } + + private JFXKeyFrame() { + + } + + public final Duration getDuration() { + return duration; + } + + public final Set> getValues() { + return keyValues; + } + + public Supplier getAnimateCondition() { + return animateCondition; + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private Duration duration; + private final Set> keyValues = new CopyOnWriteArraySet<>(); + private Supplier animateCondition = null; + + private Builder() { + } + + public Builder setDuration(Duration duration) { + this.duration = duration; + return this; + } + + public Builder setKeyValues(JFXKeyValue... keyValues) { + for (final JFXKeyValue keyValue : keyValues) { + if (keyValue != null) { + this.keyValues.add(keyValue); + } + } + return this; + } + + public Builder setAnimateCondition(Supplier animateCondition) { + this.animateCondition = animateCondition; + return this; + } + + public JFXKeyFrame build() { + JFXKeyFrame jFXKeyFrame = new JFXKeyFrame(); + jFXKeyFrame.duration = this.duration; + jFXKeyFrame.keyValues = this.keyValues; + jFXKeyFrame.animateCondition = this.animateCondition; + return jFXKeyFrame; + } + } +} diff --git a/HMCL/src/main/java/com/jfoenix/transitions/JFXKeyValue.java b/HMCL/src/main/java/com/jfoenix/transitions/JFXKeyValue.java new file mode 100644 index 000000000..6532a5f30 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/transitions/JFXKeyValue.java @@ -0,0 +1,157 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package com.jfoenix.transitions; + +import javafx.animation.Interpolator; +import javafx.beans.value.WritableValue; + +import java.util.function.Supplier; + +/** + * @author Shadi Shaheen + * @version 1.0 + * @since 2017-09-21 + */ + +public final class JFXKeyValue { + + private WritableValue target; + private Supplier> targetSupplier; + private Supplier endValueSupplier; + private T endValue; + private Supplier animateCondition = () -> true; + private Interpolator interpolator; + + private JFXKeyValue() { + } + + // this builder is created to ensure type inference from method arguments + public static Builder builder() { + return new Builder(); + } + + public T getEndValue() { + return endValue == null ? endValueSupplier.get() : endValue; + } + + public WritableValue getTarget() { + return target == null ? targetSupplier.get() : target; + } + + public Interpolator getInterpolator() { + return interpolator; + } + + public boolean isValid() { + return animateCondition == null || animateCondition.get(); + } + + public static final class Builder { + public JFXKeyValueBuilder setTarget(WritableValue target) { + JFXKeyValueBuilder builder = new JFXKeyValueBuilder<>(); + builder.setTarget(target); + return builder; + } + + public JFXKeyValueBuilder setTargetSupplier(Supplier> targetSupplier) { + JFXKeyValueBuilder builder = new JFXKeyValueBuilder<>(); + builder.setTargetSupplier(targetSupplier); + return builder; + } + + public JFXKeyValueBuilder setEndValueSupplier(Supplier endValueSupplier) { + JFXKeyValueBuilder builder = new JFXKeyValueBuilder<>(); + builder.setEndValueSupplier(endValueSupplier); + return builder; + } + + public JFXKeyValueBuilder setEndValue(T endValue) { + JFXKeyValueBuilder builder = new JFXKeyValueBuilder<>(); + builder.setEndValue(endValue); + return builder; + } + + public JFXKeyValueBuilder setAnimateCondition(Supplier animateCondition) { + JFXKeyValueBuilder builder = new JFXKeyValueBuilder<>(); + builder.setAnimateCondition(animateCondition); + return builder; + } + + public JFXKeyValueBuilder setInterpolator(Interpolator interpolator) { + JFXKeyValueBuilder builder = new JFXKeyValueBuilder<>(); + builder.setInterpolator(interpolator); + return builder; + } + } + + public static final class JFXKeyValueBuilder { + + private WritableValue target; + private Supplier> targetSupplier; + private Supplier endValueSupplier; + private T endValue; + private Supplier animateCondition = () -> true; + private Interpolator interpolator = Interpolator.EASE_BOTH; + + private JFXKeyValueBuilder() { + } + + public JFXKeyValueBuilder setTarget(WritableValue target) { + this.target = target; + return this; + } + + public JFXKeyValueBuilder setTargetSupplier(Supplier> targetSupplier) { + this.targetSupplier = targetSupplier; + return this; + } + + public JFXKeyValueBuilder setEndValueSupplier(Supplier endValueSupplier) { + this.endValueSupplier = endValueSupplier; + return this; + } + + public JFXKeyValueBuilder setEndValue(T endValue) { + this.endValue = endValue; + return this; + } + + public JFXKeyValueBuilder setAnimateCondition(Supplier animateCondition) { + this.animateCondition = animateCondition; + return this; + } + + public JFXKeyValueBuilder setInterpolator(Interpolator interpolator) { + this.interpolator = interpolator; + return this; + } + + public JFXKeyValue build() { + JFXKeyValue jFXKeyValue = new JFXKeyValue<>(); + jFXKeyValue.target = this.target; + jFXKeyValue.interpolator = this.interpolator; + jFXKeyValue.targetSupplier = this.targetSupplier; + jFXKeyValue.endValue = this.endValue; + jFXKeyValue.endValueSupplier = this.endValueSupplier; + jFXKeyValue.animateCondition = this.animateCondition; + return jFXKeyValue; + } + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java index 0cf008a42..33bb333aa 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java @@ -34,12 +34,12 @@ import org.hildan.fxgson.factories.JavaFxPropertyTypeAdapterFactory; import org.jackhuang.hmcl.Metadata; import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer; import org.jackhuang.hmcl.java.JavaRuntime; +import org.jackhuang.hmcl.theme.ThemeColor; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.util.gson.*; import org.jackhuang.hmcl.util.i18n.SupportedLocale; import org.jetbrains.annotations.Nullable; -import java.lang.reflect.*; import java.net.Proxy; import java.nio.file.Path; import java.util.*; @@ -276,19 +276,34 @@ public final class Config extends ObservableSetting { // UI + @SerializedName("themeBrightness") + private final StringProperty themeBrightness = new SimpleStringProperty("light"); + + public StringProperty themeBrightnessProperty() { + return themeBrightness; + } + + public String getThemeBrightness() { + return themeBrightness.get(); + } + + public void setThemeBrightness(String themeBrightness) { + this.themeBrightness.set(themeBrightness); + } + @SerializedName("theme") - private final ObjectProperty theme = new SimpleObjectProperty<>(); + private final ObjectProperty themeColor = new SimpleObjectProperty<>(ThemeColor.DEFAULT); - public ObjectProperty themeProperty() { - return theme; + public ObjectProperty themeColorProperty() { + return themeColor; } - public Theme getTheme() { - return theme.get(); + public ThemeColor getThemeColor() { + return themeColor.get(); } - public void setTheme(Theme theme) { - this.theme.set(theme); + public void setThemeColor(ThemeColor themeColor) { + this.themeColor.set(themeColor); } @SerializedName("fontFamily") diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/StyleSheets.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/StyleSheets.java index e4041a5ab..69765f48d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/StyleSheets.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/StyleSheets.java @@ -22,6 +22,12 @@ import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.scene.Scene; import javafx.scene.paint.Color; +import org.glavo.monetfx.Brightness; +import org.glavo.monetfx.ColorRole; +import org.glavo.monetfx.ColorScheme; +import org.jackhuang.hmcl.theme.Theme; +import org.jackhuang.hmcl.theme.ThemeColor; +import org.jackhuang.hmcl.theme.Themes; import org.jackhuang.hmcl.ui.FXUtils; import java.io.IOException; @@ -33,7 +39,6 @@ import java.util.Arrays; import java.util.Base64; import java.util.Locale; -import static org.jackhuang.hmcl.setting.ConfigHolder.config; import static org.jackhuang.hmcl.util.logging.Logger.LOG; /** @@ -54,7 +59,7 @@ public final class StyleSheets { stylesheets = FXCollections.observableList(Arrays.asList(array)); FontManager.fontProperty().addListener(o -> stylesheets.set(FONT_STYLE_SHEET_INDEX, getFontStyleSheet())); - config().themeProperty().addListener(o -> stylesheets.set(THEME_STYLE_SHEET_INDEX, getThemeStyleSheet())); + Themes.colorSchemeProperty().addListener(o -> stylesheets.set(THEME_STYLE_SHEET_INDEX, getThemeStyleSheet())); } private static String toStyleSheetUri(String styleSheet, String fallback) { @@ -126,31 +131,55 @@ public final class StyleSheets { return toStyleSheetUri(builder.toString(), defaultCss); } - private static String rgba(Color color, double opacity) { - return String.format("rgba(%d, %d, %d, %.1f)", - (int) Math.ceil(color.getRed() * 256), - (int) Math.ceil(color.getGreen() * 256), - (int) Math.ceil(color.getBlue() * 256), - opacity); + private static void addColor(StringBuilder builder, String name, Color color) { + builder.append(" ").append(name) + .append(": ").append(ThemeColor.getColorDisplayName(color)).append(";\n"); + } + + private static void addColor(StringBuilder builder, String name, Color color, double opacity) { + builder.append(" ").append(name) + .append(": ").append(ThemeColor.getColorDisplayNameWithOpacity(color, opacity)).append(";\n"); + } + + private static void addColor(StringBuilder builder, ColorScheme scheme, ColorRole role, double opacity) { + builder.append(" ").append(role.getVariableName()).append("-transparent-").append((int) (100 * opacity)) + .append(": ").append(ThemeColor.getColorDisplayNameWithOpacity(scheme.getColor(role), opacity)) + .append(";\n"); } private static String getThemeStyleSheet() { final String blueCss = "/assets/css/blue.css"; - Theme theme = config().getTheme(); - if (theme == null || theme.getPaint().equals(Theme.BLUE.getPaint())) + if (Theme.DEFAULT.equals(Themes.getTheme())) return blueCss; - return toStyleSheetUri(".root {" + - "-fx-base-color:" + theme.getColor() + ';' + - "-fx-base-darker-color: derive(-fx-base-color, -10%);" + - "-fx-base-check-color: derive(-fx-base-color, 30%);" + - "-fx-rippler-color:" + rgba(theme.getPaint(), 0.3) + ';' + - "-fx-base-rippler-color: derive(" + rgba(theme.getPaint(), 0.3) + ", 100%);" + - "-fx-base-disabled-text-fill:" + rgba(theme.getForegroundColor(), 0.7) + ";" + - "-fx-base-text-fill:" + Theme.getColorDisplayName(theme.getForegroundColor()) + ";" + - "-theme-thumb:" + rgba(theme.getPaint(), 0.7) + ";" + - '}', blueCss); + ColorScheme scheme = Themes.getColorScheme(); + + StringBuilder builder = new StringBuilder(); + builder.append("* {\n"); + for (ColorRole colorRole : ColorRole.ALL) { + addColor(builder, colorRole.getVariableName(), scheme.getColor(colorRole)); + } + + addColor(builder, "-monet-primary-seed", scheme.getPrimaryColorSeed()); + + addColor(builder, scheme, ColorRole.PRIMARY, 0.5); + addColor(builder, scheme, ColorRole.SECONDARY_CONTAINER, 0.5); + addColor(builder, scheme, ColorRole.SURFACE, 0.5); + addColor(builder, scheme, ColorRole.SURFACE, 0.8); + addColor(builder, scheme, ColorRole.ON_SURFACE_VARIANT, 0.38); + addColor(builder, scheme, ColorRole.SURFACE_CONTAINER_LOW, 0.8); + addColor(builder, scheme, ColorRole.SECONDARY_CONTAINER, 0.8); + + // If we use -monet-error-container as the tag color, there may be a conflict between the tag and background colors on the mod management page + if (scheme.getBrightness() == Brightness.LIGHT) { + addColor(builder, "-warning-tag-background", Color.web("#f1aeb5")); + } else { + addColor(builder, "-warning-tag-background", Color.web("#2c0b0e")); + } + + builder.append("}\n"); + return toStyleSheetUri(builder.toString(), blueCss); } public static void init(Scene scene) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Theme.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Theme.java deleted file mode 100644 index 45f2bb267..000000000 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Theme.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Hello Minecraft! Launcher - * Copyright (C) 2020 huangyuhui and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.jackhuang.hmcl.setting; - -import com.google.gson.annotations.JsonAdapter; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonWriter; -import javafx.beans.binding.Bindings; -import javafx.beans.binding.ObjectBinding; -import javafx.scene.paint.Color; - -import java.io.IOException; -import java.util.Locale; -import java.util.Objects; -import java.util.Optional; - -import static org.jackhuang.hmcl.setting.ConfigHolder.config; - -@JsonAdapter(Theme.TypeAdapter.class) -public final class Theme { - public static final Theme BLUE = new Theme("blue", "#5C6BC0"); - public static final Color BLACK = Color.web("#292929"); - public static final Color[] SUGGESTED_COLORS = new Color[]{ - Color.web("#3D6DA3"), // blue - Color.web("#283593"), // dark blue - Color.web("#43A047"), // green - Color.web("#E67E22"), // orange - Color.web("#9C27B0"), // purple - Color.web("#B71C1C") // red - }; - - public static Theme getTheme() { - Theme theme = config().getTheme(); - return theme == null ? BLUE : theme; - } - - private final Color paint; - private final String color; - private final String name; - - Theme(String name, String color) { - this.name = name; - this.color = Objects.requireNonNull(color); - this.paint = Color.web(color); - } - - public String getName() { - return name; - } - - public String getColor() { - return color; - } - - public Color getPaint() { - return paint; - } - - public boolean isCustom() { - return name.startsWith("#"); - } - - public boolean isLight() { - return paint.grayscale().getRed() >= 0.5; - } - - public Color getForegroundColor() { - return isLight() ? Color.BLACK : Color.WHITE; - } - - public static Theme custom(String color) { - if (!color.startsWith("#")) - throw new IllegalArgumentException(); - return new Theme(color, color); - } - - public static Optional getTheme(String name) { - if (name == null) - return Optional.empty(); - else if (name.startsWith("#")) - try { - Color.web(name); - return Optional.of(custom(name)); - } catch (IllegalArgumentException ignore) { - } - else { - String color = null; - switch (name.toLowerCase(Locale.ROOT)) { - case "blue": - return Optional.of(BLUE); - case "darker_blue": - color = "#283593"; - break; - case "green": - color = "#43A047"; - break; - case "orange": - color = "#E67E22"; - break; - case "purple": - color = "#9C27B0"; - break; - case "red": - color = "#F44336"; - } - if (color != null) - return Optional.of(new Theme(name, color)); - } - - return Optional.empty(); - } - - public static String getColorDisplayName(Color c) { - return c != null ? String.format("#%02X%02X%02X", Math.round(c.getRed() * 255.0D), Math.round(c.getGreen() * 255.0D), Math.round(c.getBlue() * 255.0D)) : null; - } - - private static ObjectBinding FOREGROUND_FILL; - - public static ObjectBinding foregroundFillBinding() { - if (FOREGROUND_FILL == null) - FOREGROUND_FILL = Bindings.createObjectBinding( - () -> Theme.getTheme().getForegroundColor(), - config().themeProperty() - ); - - return FOREGROUND_FILL; - } - - public static Color blackFill() { - return BLACK; - } - - public static Color whiteFill() { - return Color.WHITE; - } - - public static class TypeAdapter extends com.google.gson.TypeAdapter { - @Override - public void write(JsonWriter out, Theme value) throws IOException { - out.value(value.getName().toLowerCase(Locale.ROOT)); - } - - @Override - public Theme read(JsonReader in) throws IOException { - return getTheme(in.nextString()).orElse(Theme.BLUE); - } - } -} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/theme/Theme.java b/HMCL/src/main/java/org/jackhuang/hmcl/theme/Theme.java new file mode 100644 index 000000000..e8d337116 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/theme/Theme.java @@ -0,0 +1,94 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2025 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.theme; + +import org.glavo.monetfx.*; + +import java.util.*; + +/// @author Glavo +public final class Theme { + + public static final Theme DEFAULT = new Theme(ThemeColor.DEFAULT, Brightness.DEFAULT, ColorStyle.FIDELITY, Contrast.DEFAULT); + + private final ThemeColor primaryColorSeed; + private final Brightness brightness; + private final ColorStyle colorStyle; + private final Contrast contrast; + + public Theme(ThemeColor primaryColorSeed, + Brightness brightness, + ColorStyle colorStyle, + Contrast contrast + ) { + this.primaryColorSeed = primaryColorSeed; + this.brightness = brightness; + this.colorStyle = colorStyle; + this.contrast = contrast; + } + + public ColorScheme toColorScheme() { + return ColorScheme.newBuilder() + .setPrimaryColorSeed(primaryColorSeed.color()) + .setColorStyle(colorStyle) + .setBrightness(brightness) + .setSpecVersion(ColorSpecVersion.SPEC_2025) + .setContrast(contrast) + .build(); + } + + public ThemeColor primaryColorSeed() { + return primaryColorSeed; + } + + public Brightness brightness() { + return brightness; + } + + public ColorStyle colorStyle() { + return colorStyle; + } + + public Contrast contrast() { + return contrast; + } + + @Override + public boolean equals(Object obj) { + return obj == this || obj instanceof Theme that + && this.primaryColorSeed.color().equals(that.primaryColorSeed.color()) + && this.brightness.equals(that.brightness) + && this.colorStyle.equals(that.colorStyle) + && this.contrast.equals(that.contrast); + } + + @Override + public int hashCode() { + return Objects.hash(primaryColorSeed, brightness, colorStyle, contrast); + } + + @Override + public String toString() { + return "Theme[" + + "primaryColorSeed=" + primaryColorSeed + ", " + + "brightness=" + brightness + ", " + + "colorStyle=" + colorStyle + ", " + + "contrast=" + contrast + ']'; + } + +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/theme/ThemeColor.java b/HMCL/src/main/java/org/jackhuang/hmcl/theme/ThemeColor.java new file mode 100644 index 000000000..1ce2264b4 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/theme/ThemeColor.java @@ -0,0 +1,193 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2025 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.theme; + +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javafx.beans.WeakListener; +import javafx.beans.property.Property; +import javafx.scene.control.ColorPicker; +import javafx.scene.paint.Color; +import org.jackhuang.hmcl.util.gson.JsonSerializable; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.util.List; +import java.util.Objects; + +/// @author Glavo +@JsonAdapter(ThemeColor.TypeAdapter.class) +@JsonSerializable +public record ThemeColor(@NotNull String name, @NotNull Color color) { + + public static final ThemeColor DEFAULT = new ThemeColor("blue", Color.web("#5C6BC0")); + + public static final List STANDARD_COLORS = List.of( + DEFAULT, + new ThemeColor("darker_blue", Color.web("#283593")), + new ThemeColor("green", Color.web("#43A047")), + new ThemeColor("orange", Color.web("#E67E22")), + new ThemeColor("purple", Color.web("#9C27B0")), + new ThemeColor("red", Color.web("#B71C1C")) + ); + + public static String getColorDisplayName(Color c) { + return c != null ? String.format("#%02X%02X%02X", + Math.round(c.getRed() * 255.0D), + Math.round(c.getGreen() * 255.0D), + Math.round(c.getBlue() * 255.0D)) + : null; + } + + public static String getColorDisplayNameWithOpacity(Color c, double opacity) { + return c != null ? String.format("#%02X%02X%02X%02X", + Math.round(c.getRed() * 255.0D), + Math.round(c.getGreen() * 255.0D), + Math.round(c.getBlue() * 255.0D), + Math.round(opacity * 255.0)) + : null; + } + + public static @Nullable ThemeColor of(String name) { + if (name == null) + return null; + + if (!name.startsWith("#")) { + for (ThemeColor color : STANDARD_COLORS) { + if (name.equalsIgnoreCase(color.name())) + return color; + } + } + + try { + return new ThemeColor(name, Color.web(name)); + } catch (IllegalArgumentException e) { + return null; + } + } + + @Contract("null -> null; !null -> !null") + public static ThemeColor of(Color color) { + return color != null ? new ThemeColor(getColorDisplayName(color), color) : null; + } + + private static final class BidirectionalBinding implements InvalidationListener, WeakListener { + private final WeakReference colorPickerRef; + private final WeakReference> propertyRef; + private final int hashCode; + + private boolean updating = false; + + private BidirectionalBinding(ColorPicker colorPicker, Property property) { + this.colorPickerRef = new WeakReference<>(colorPicker); + this.propertyRef = new WeakReference<>(property); + this.hashCode = System.identityHashCode(colorPicker) ^ System.identityHashCode(property); + } + + @Override + public void invalidated(Observable sourceProperty) { + if (!updating) { + final ColorPicker colorPicker = colorPickerRef.get(); + final Property property = propertyRef.get(); + + if (colorPicker == null || property == null) { + if (colorPicker != null) { + colorPicker.valueProperty().removeListener(this); + } + + if (property != null) { + property.removeListener(this); + } + } else { + updating = true; + try { + if (property == sourceProperty) { + ThemeColor newValue = property.getValue(); + colorPicker.setValue(newValue != null ? newValue.color() : null); + } else { + Color newValue = colorPicker.getValue(); + property.setValue(newValue != null ? ThemeColor.of(newValue) : null); + } + } finally { + updating = false; + } + } + } + } + + @Override + public boolean wasGarbageCollected() { + return colorPickerRef.get() == null || propertyRef.get() == null; + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof BidirectionalBinding that)) + return false; + + final ColorPicker colorPicker = this.colorPickerRef.get(); + final Property property = this.propertyRef.get(); + + final ColorPicker thatColorPicker = that.colorPickerRef.get(); + final Property thatProperty = that.propertyRef.get(); + + if (colorPicker == null || property == null || thatColorPicker == null || thatProperty == null) + return false; + + return colorPicker == thatColorPicker && property == thatProperty; + } + } + + public static void bindBidirectional(ColorPicker colorPicker, Property property) { + var binding = new BidirectionalBinding(colorPicker, property); + + colorPicker.valueProperty().removeListener(binding); + property.removeListener(binding); + + ThemeColor themeColor = property.getValue(); + colorPicker.setValue(themeColor != null ? themeColor.color() : null); + + colorPicker.valueProperty().addListener(binding); + property.addListener(binding); + } + + static final class TypeAdapter extends com.google.gson.TypeAdapter { + @Override + public void write(JsonWriter out, ThemeColor value) throws IOException { + out.value(value.name()); + } + + @Override + public ThemeColor read(JsonReader in) throws IOException { + return Objects.requireNonNullElse(of(in.nextString()), ThemeColor.DEFAULT); + } + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/theme/Themes.java b/HMCL/src/main/java/org/jackhuang/hmcl/theme/Themes.java new file mode 100644 index 000000000..f692aefe0 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/theme/Themes.java @@ -0,0 +1,126 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2025 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.theme; + +import javafx.beans.Observable; +import javafx.beans.binding.Bindings; +import javafx.beans.binding.BooleanBinding; +import javafx.beans.binding.ObjectBinding; +import javafx.beans.binding.ObjectExpression; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.scene.paint.Color; +import org.glavo.monetfx.Brightness; +import org.glavo.monetfx.ColorScheme; +import org.glavo.monetfx.Contrast; +import org.glavo.monetfx.beans.property.ColorSchemeProperty; +import org.glavo.monetfx.beans.property.ReadOnlyColorSchemeProperty; +import org.glavo.monetfx.beans.property.SimpleColorSchemeProperty; +import org.jackhuang.hmcl.ui.FXUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Objects; + +import static org.jackhuang.hmcl.setting.ConfigHolder.config; + +/// @author Glavo +public final class Themes { + + private static final ObjectExpression theme = new ObjectBinding<>() { + { + List observables = new ArrayList<>(); + + observables.add(config().themeBrightnessProperty()); + observables.add(config().themeColorProperty()); + if (FXUtils.DARK_MODE != null) { + observables.add(FXUtils.DARK_MODE); + } + bind(observables.toArray(new Observable[0])); + } + + private Brightness getBrightness() { + String themeBrightness = config().getThemeBrightness(); + if (themeBrightness == null) + return Brightness.DEFAULT; + + return switch (themeBrightness.toLowerCase(Locale.ROOT).trim()) { + case "auto" -> { + if (FXUtils.DARK_MODE != null) { + yield FXUtils.DARK_MODE.get() ? Brightness.DARK : Brightness.LIGHT; + } else { + yield Brightness.DEFAULT; + } + } + case "dark" -> Brightness.DARK; + case "light" -> Brightness.LIGHT; + default -> Brightness.DEFAULT; + }; + } + + @Override + protected Theme computeValue() { + ThemeColor themeColor = Objects.requireNonNullElse(config().getThemeColor(), ThemeColor.DEFAULT); + + return new Theme(themeColor, getBrightness(), Theme.DEFAULT.colorStyle(), Contrast.DEFAULT); + } + }; + private static final ColorSchemeProperty colorScheme = new SimpleColorSchemeProperty(); + private static final BooleanBinding darkMode = Bindings.createBooleanBinding( + () -> colorScheme.get().getBrightness() == Brightness.DARK, + colorScheme + ); + + static { + ChangeListener listener = (observable, oldValue, newValue) -> { + if (!Objects.equals(oldValue, newValue)) { + colorScheme.set(newValue != null ? newValue.toColorScheme() : Theme.DEFAULT.toColorScheme()); + } + }; + listener.changed(theme, null, theme.get()); + theme.addListener(listener); + } + + public static ObjectExpression themeProperty() { + return theme; + } + + public static Theme getTheme() { + return themeProperty().get(); + } + + public static ReadOnlyColorSchemeProperty colorSchemeProperty() { + return colorScheme; + } + + public static ColorScheme getColorScheme() { + return colorScheme.get(); + } + + public static ObservableValue titleFillProperty() { + return colorSchemeProperty().getOnPrimaryContainer(); + } + + public static BooleanBinding darkModeProperty() { + return darkMode; + } + + private Themes() { + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java index c8a072b06..0eae9c9da 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java @@ -57,7 +57,6 @@ import javafx.util.Callback; import javafx.util.Duration; import javafx.util.StringConverter; import org.jackhuang.hmcl.setting.StyleSheets; -import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.task.CacheFileTask; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; @@ -135,6 +134,7 @@ public final class FXUtils { public static final @Nullable ObservableMap PREFERENCES; public static final @Nullable ObservableBooleanValue DARK_MODE; public static final @Nullable Boolean REDUCED_MOTION; + public static final @Nullable ReadOnlyObjectProperty ACCENT_COLOR; public static final @Nullable MethodHandle TEXT_TRUNCATED_PROPERTY; @@ -151,6 +151,7 @@ public final class FXUtils { ObservableMap preferences = null; ObservableBooleanValue darkMode = null; + ReadOnlyObjectProperty accentColorProperty = null; Boolean reducedMotion = null; if (JAVAFX_MAJOR_VERSION >= 22) { try { @@ -162,14 +163,19 @@ public final class FXUtils { preferences = preferences0; @SuppressWarnings("unchecked") - var colorSchemeProperty = - (ReadOnlyObjectProperty>) - lookup.findVirtual(preferencesClass, "colorSchemeProperty", MethodType.methodType(ReadOnlyObjectProperty.class)) - .invoke(preferences); + var colorSchemeProperty = (ReadOnlyObjectProperty>) + lookup.findVirtual(preferencesClass, "colorSchemeProperty", MethodType.methodType(ReadOnlyObjectProperty.class)) + .invoke(preferences); darkMode = Bindings.createBooleanBinding(() -> "DARK".equals(colorSchemeProperty.get().name()), colorSchemeProperty); + @SuppressWarnings("unchecked") + var accentColorProperty0 = (ReadOnlyObjectProperty) + lookup.findVirtual(preferencesClass, "accentColorProperty", MethodType.methodType(ReadOnlyObjectProperty.class)) + .invoke(preferences); + accentColorProperty = accentColorProperty0; + if (JAVAFX_MAJOR_VERSION >= 24) { reducedMotion = (boolean) lookup.findVirtual(preferencesClass, "isReducedMotion", MethodType.methodType(boolean.class)) @@ -182,6 +188,7 @@ public final class FXUtils { PREFERENCES = preferences; DARK_MODE = darkMode; REDUCED_MOTION = reducedMotion; + ACCENT_COLOR = accentColorProperty; MethodHandle textTruncatedProperty = null; if (JAVAFX_MAJOR_VERSION >= 23) { @@ -1214,7 +1221,7 @@ public final class FXUtils { public static JFXButton newToggleButton4(SVG icon) { JFXButton button = new JFXButton(); button.getStyleClass().add("toggle-icon4"); - button.setGraphic(icon.createIcon(Theme.blackFill(), -1)); + button.setGraphic(icon.createIcon()); return button; } @@ -1447,11 +1454,11 @@ public final class FXUtils { for (int i = 0; i < children.getLength(); i++) { org.w3c.dom.Node node = children.item(i); - if (node instanceof Element) { - Element element = (Element) node; + if (node instanceof Element element) { if ("a".equals(element.getTagName())) { String href = element.getAttribute("href"); Text text = new Text(element.getTextContent()); + text.getStyleClass().add("hyperlink"); onClicked(text, () -> { String link = href; try { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java index 1925bbeb7..9c237a7bd 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java @@ -36,7 +36,6 @@ import javafx.scene.image.ImageView; import javafx.scene.input.MouseButton; import javafx.scene.layout.*; import org.jackhuang.hmcl.download.LibraryAnalyzer; -import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.setting.VersionIconType; import org.jackhuang.hmcl.ui.construct.RipplerContainer; import org.jackhuang.hmcl.util.i18n.I18n; @@ -396,7 +395,7 @@ public class InstallerItem extends Control { pane.getChildren().add(buttonsContainer); JFXButton removeButton = new JFXButton(); - removeButton.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), -1)); + removeButton.setGraphic(SVG.CLOSE.createIcon()); removeButton.getStyleClass().add("toggle-icon4"); if (control.id.equals(MINECRAFT.getPatchId())) { removeButton.setVisible(false); @@ -417,8 +416,8 @@ public class InstallerItem extends Control { JFXButton installButton = new JFXButton(); installButton.graphicProperty().bind(Bindings.createObjectBinding(() -> control.resolvedStateProperty.get() instanceof InstallableState ? - SVG.ARROW_FORWARD.createIcon(Theme.blackFill(), -1) : - SVG.UPDATE.createIcon(Theme.blackFill(), -1), + SVG.ARROW_FORWARD.createIcon() : + SVG.UPDATE.createIcon(), control.resolvedStateProperty )); installButton.getStyleClass().add("toggle-icon4"); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/ListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ListPageSkin.java index 1927b3172..d64ef4abd 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/ListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ListPageSkin.java @@ -27,7 +27,6 @@ import javafx.scene.layout.BorderPane; import javafx.scene.layout.Pane; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; -import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.ui.construct.SpinnerPane; public class ListPageSkin extends SkinBase> { @@ -67,7 +66,7 @@ public class ListPageSkin extends SkinBase> { JFXButton btnAdd = FXUtils.newRaisedButton(""); FXUtils.setLimitWidth(btnAdd, 40); FXUtils.setLimitHeight(btnAdd, 40); - btnAdd.setGraphic(SVG.ADD.createIcon(Theme.whiteFill(), -1)); + btnAdd.setGraphic(SVG.ADD.createIcon()); btnAdd.setOnAction(e -> skinnable.add()); JFXButton btnRefresh = new JFXButton(); @@ -75,7 +74,7 @@ public class ListPageSkin extends SkinBase> { FXUtils.setLimitHeight(btnRefresh, 40); btnRefresh.getStyleClass().add("jfx-button-raised-round"); btnRefresh.setButtonType(JFXButton.ButtonType.RAISED); - btnRefresh.setGraphic(SVG.REFRESH.createIcon(Theme.whiteFill(), -1)); + btnRefresh.setGraphic(SVG.REFRESH.createIcon()); btnRefresh.setOnAction(e -> skinnable.refresh()); vBox.getChildren().setAll(btnAdd); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java index 48c968fd7..2f1ea2aa1 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java @@ -153,24 +153,25 @@ public enum SVG { return new Group(path); } - public Node createIcon(ObservableValue 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 color) { + SVGPath p = new SVGPath(); + p.setContent(path); + p.getStyleClass().add("svg"); + p.fillProperty().bind(color); + return createIcon(p, -1); + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/ToolbarListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ToolbarListPageSkin.java index d2a36b737..53a61026d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/ToolbarListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ToolbarListPageSkin.java @@ -28,7 +28,6 @@ import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; -import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.ui.construct.ComponentList; import org.jackhuang.hmcl.ui.construct.SpinnerPane; @@ -85,20 +84,10 @@ public abstract class ToolbarListPageSkin return stackPane; } - public static JFXButton createToolbarButton(String text, SVG svg, Runnable onClick) { - JFXButton ret = new JFXButton(); - ret.getStyleClass().add("jfx-tool-bar-button"); - ret.textFillProperty().bind(Theme.foregroundFillBinding()); - ret.setGraphic(wrap(svg.createIcon(Theme.foregroundFillBinding(), -1))); - ret.setText(text); - ret.setOnAction(e -> onClick.run()); - return ret; - } - public static JFXButton createToolbarButton2(String text, SVG svg, Runnable onClick) { JFXButton ret = new JFXButton(); ret.getStyleClass().add("jfx-tool-bar-button"); - ret.setGraphic(wrap(svg.createIcon(Theme.blackFill(), -1))); + ret.setGraphic(wrap(svg.createIcon())); ret.setText(text); ret.setOnAction(e -> { onClick.run(); @@ -110,8 +99,7 @@ public abstract class ToolbarListPageSkin public static JFXButton createDecoratorButton(String tooltip, SVG svg, Runnable onClick) { JFXButton ret = new JFXButton(); ret.getStyleClass().add("jfx-decorator-button"); - ret.textFillProperty().bind(Theme.foregroundFillBinding()); - ret.setGraphic(wrap(svg.createIcon(Theme.foregroundFillBinding(), -1))); + ret.setGraphic(wrap(svg.createIcon())); FXUtils.installFastTooltip(ret, tooltip); ret.setOnAction(e -> onClick.run()); return ret; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemSkin.java index 1ce5cd280..8ea3ce741 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemSkin.java @@ -36,7 +36,6 @@ import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer; import org.jackhuang.hmcl.auth.microsoft.MicrosoftAccount; import org.jackhuang.hmcl.game.TexturesLoader; import org.jackhuang.hmcl.setting.Accounts; -import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.Controllers; @@ -117,10 +116,10 @@ public final class AccountListItemSkin extends SkinBase { }); btnMove.getStyleClass().add("toggle-icon4"); if (skinnable.getAccount().isPortable()) { - btnMove.setGraphic(SVG.PUBLIC.createIcon(Theme.blackFill(), -1)); + btnMove.setGraphic(SVG.PUBLIC.createIcon()); FXUtils.installFastTooltip(btnMove, i18n("account.move_to_global")); } else { - btnMove.setGraphic(SVG.OUTPUT.createIcon(Theme.blackFill(), -1)); + btnMove.setGraphic(SVG.OUTPUT.createIcon()); FXUtils.installFastTooltip(btnMove, i18n("account.move_to_portable")); } spinnerMove.setContent(btnMove); @@ -146,7 +145,7 @@ public final class AccountListItemSkin extends SkinBase { .start(); }); btnRefresh.getStyleClass().add("toggle-icon4"); - btnRefresh.setGraphic(SVG.REFRESH.createIcon(Theme.blackFill(), -1)); + btnRefresh.setGraphic(SVG.REFRESH.createIcon()); FXUtils.installFastTooltip(btnRefresh, i18n("button.refresh")); spinnerRefresh.setContent(btnRefresh); right.getChildren().add(spinnerRefresh); @@ -163,7 +162,7 @@ public final class AccountListItemSkin extends SkinBase { } }); btnUpload.getStyleClass().add("toggle-icon4"); - btnUpload.setGraphic(SVG.CHECKROOM.createIcon(Theme.blackFill(), -1)); + btnUpload.setGraphic(SVG.CHECKROOM.createIcon()); FXUtils.installFastTooltip(btnUpload, i18n("account.skin.upload")); btnUpload.disableProperty().bind(Bindings.not(skinnable.canUploadSkin())); spinnerUpload.setContent(btnUpload); @@ -175,7 +174,7 @@ public final class AccountListItemSkin extends SkinBase { spinnerCopyUUID.getStyleClass().add("small-spinner-pane"); btnUpload.getStyleClass().add("toggle-icon4"); btnCopyUUID.setOnAction(e -> FXUtils.copyText(skinnable.getAccount().getUUID().toString())); - btnCopyUUID.setGraphic(SVG.CONTENT_COPY.createIcon(Theme.blackFill(), -1)); + btnCopyUUID.setGraphic(SVG.CONTENT_COPY.createIcon()); FXUtils.installFastTooltip(btnCopyUUID, i18n("account.copy_uuid")); spinnerCopyUUID.setContent(btnCopyUUID); right.getChildren().add(spinnerCopyUUID); @@ -184,7 +183,7 @@ public final class AccountListItemSkin extends SkinBase { btnRemove.setOnAction(e -> Controllers.confirm(i18n("button.remove.confirm"), i18n("button.remove"), skinnable::remove, null)); btnRemove.getStyleClass().add("toggle-icon4"); BorderPane.setAlignment(btnRemove, Pos.CENTER); - btnRemove.setGraphic(SVG.DELETE.createIcon(Theme.blackFill(), -1)); + btnRemove.setGraphic(SVG.DELETE.createIcon()); FXUtils.installFastTooltip(btnRemove, i18n("button.delete")); right.getChildren().add(btnRemove); root.setRight(right); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java index c66eb2609..b1fbbdbdc 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java @@ -39,7 +39,6 @@ import javafx.scene.layout.VBox; import org.jackhuang.hmcl.auth.Account; import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer; import org.jackhuang.hmcl.setting.Accounts; -import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; @@ -158,7 +157,7 @@ public final class AccountListPage extends DecoratorAnimatedPage implements Deco e.consume(); }); btnRemove.getStyleClass().add("toggle-icon4"); - btnRemove.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), 14)); + btnRemove.setGraphic(SVG.CLOSE.createIcon(14)); item.setRightGraphic(btnRemove); ObservableValue title = BindingMapping.of(server, AuthlibInjectorServer::getName); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/CreateAccountPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/CreateAccountPane.java index e2c00c369..19d6eb52d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/CreateAccountPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/CreateAccountPane.java @@ -50,7 +50,6 @@ import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService; import org.jackhuang.hmcl.game.OAuthServer; import org.jackhuang.hmcl.game.TexturesLoader; import org.jackhuang.hmcl.setting.Accounts; -import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.TaskExecutor; @@ -465,7 +464,7 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware { linksContainer.setMinWidth(USE_PREF_SIZE); JFXButton btnAddServer = new JFXButton(); - btnAddServer.setGraphic(SVG.ADD.createIcon(Theme.blackFill(), 20)); + btnAddServer.setGraphic(SVG.ADD.createIcon(20)); btnAddServer.getStyleClass().add("toggle-icon4"); btnAddServer.setOnAction(e -> { Controllers.dialog(new AddAuthlibInjectorServerPane()); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/AdvancedListBox.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/AdvancedListBox.java index cd48a22a0..400dd56ad 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/AdvancedListBox.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/AdvancedListBox.java @@ -26,7 +26,6 @@ import javafx.scene.input.MouseEvent; import javafx.scene.layout.Pane; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; -import javafx.scene.paint.Paint; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; import org.jackhuang.hmcl.ui.animation.ContainerAnimations; @@ -109,8 +108,8 @@ public class AdvancedListBox extends ScrollPane { item.activeProperty().bind(tabHeader.getSelectionModel().selectedItemProperty().isEqualTo(tab)); item.setOnAction(e -> tabHeader.select(tab)); - Node unselectedIcon = unselectedGraphic.createIcon((Paint) null, 20); - Node selectedIcon = selectedGraphic.createIcon((Paint) null, 20); + Node unselectedIcon = unselectedGraphic.createIcon(20); + Node selectedIcon = selectedGraphic.createIcon(20); TransitionPane leftGraphic = new TransitionPane(); leftGraphic.setAlignment(Pos.CENTER); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ClassTitle.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ClassTitle.java index c4e9bb3f8..7499e1193 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ClassTitle.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ClassTitle.java @@ -21,7 +21,6 @@ import javafx.scene.Node; import javafx.scene.layout.BorderPane; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; -import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; import javafx.scene.text.Text; import org.jackhuang.hmcl.util.Lang; @@ -44,7 +43,6 @@ public class ClassTitle extends StackPane { Rectangle rectangle = new Rectangle(); rectangle.widthProperty().bind(vbox.widthProperty()); rectangle.setHeight(1.0); - rectangle.setFill(Color.GRAY); vbox.getChildren().add(rectangle); getChildren().setAll(vbox); getStyleClass().add("class-title"); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentListCell.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentListCell.java index bb8ce317f..67f94a921 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentListCell.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentListCell.java @@ -30,7 +30,7 @@ import javafx.scene.input.MouseEvent; import javafx.scene.layout.*; import javafx.scene.shape.Rectangle; import javafx.util.Duration; -import org.jackhuang.hmcl.setting.Theme; +import org.jackhuang.hmcl.theme.Themes; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; import org.jackhuang.hmcl.ui.animation.AnimationUtils; @@ -79,7 +79,7 @@ final class ComponentListCell extends StackPane { VBox groupNode = new VBox(); - Node expandIcon = SVG.KEYBOARD_ARROW_DOWN.createIcon(Theme.blackFill(), 20); + Node expandIcon = SVG.KEYBOARD_ARROW_DOWN.createIcon(20); expandIcon.setMouseTransparent(true); HBox.setMargin(expandIcon, new Insets(0, 8, 0, 8)); @@ -99,12 +99,14 @@ final class ComponentListCell extends StackPane { if (!overrideHeaderLeft) { Label label = new Label(); label.textProperty().bind(list.titleProperty()); + label.getStyleClass().add("title-label"); labelVBox.getChildren().add(label); if (list.isHasSubtitle()) { Label subtitleLabel = new Label(); subtitleLabel.textProperty().bind(list.subtitleProperty()); subtitleLabel.getStyleClass().add("subtitle-label"); + subtitleLabel.textFillProperty().bind(Themes.colorSchemeProperty().getOnSurfaceVariant()); labelVBox.getChildren().add(subtitleLabel); } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FileItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FileItem.java index ccf722f3b..de52a2979 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FileItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FileItem.java @@ -28,7 +28,6 @@ import javafx.scene.layout.BorderPane; import javafx.scene.layout.VBox; import javafx.stage.DirectoryChooser; import org.jackhuang.hmcl.Metadata; -import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; @@ -60,7 +59,7 @@ public class FileItem extends BorderPane { setLeft(left); JFXButton right = new JFXButton(); - right.setGraphic(SVG.EDIT.createIcon(Theme.blackFill(), 16)); + right.setGraphic(SVG.EDIT.createIcon(16)); right.getStyleClass().add("toggle-icon4"); right.setOnAction(e -> onExplore()); FXUtils.installFastTooltip(right, i18n("button.edit")); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FileSelector.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FileSelector.java index 96cffb9a0..a0dd9d53e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FileSelector.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FileSelector.java @@ -27,7 +27,6 @@ import javafx.geometry.Pos; import javafx.scene.layout.HBox; import javafx.stage.DirectoryChooser; import javafx.stage.FileChooser; -import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; @@ -82,7 +81,7 @@ public class FileSelector extends HBox { FXUtils.bindString(customField, valueProperty()); JFXButton selectButton = new JFXButton(); - selectButton.setGraphic(SVG.FOLDER_OPEN.createIcon(Theme.blackFill(), 15)); + selectButton.setGraphic(SVG.FOLDER_OPEN.createIcon(15)); selectButton.setOnAction(e -> { if (directory) { DirectoryChooser chooser = new DirectoryChooser(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FloatScrollBarSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FloatScrollBarSkin.java index c14b73c03..0cfe681d6 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FloatScrollBarSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FloatScrollBarSkin.java @@ -28,6 +28,8 @@ import javafx.scene.layout.Region; import javafx.scene.shape.Rectangle; import org.jackhuang.hmcl.util.Lang; +// Referenced in root.css +@SuppressWarnings("unused") public class FloatScrollBarSkin implements Skin { private ScrollBar scrollBar; private Region group; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/HintPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/HintPane.java index aa95e093f..1cd7d295d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/HintPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/HintPane.java @@ -26,7 +26,6 @@ import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import javafx.scene.text.Text; import javafx.scene.text.TextFlow; -import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; @@ -67,7 +66,7 @@ public class HintPane extends VBox { throw new IllegalArgumentException("Unrecognized message box message type " + type); } - HBox hbox = new HBox(svg.createIcon(Theme.blackFill(), 16), new Text(type.getDisplayName())); + HBox hbox = new HBox(svg.createIcon(16), new Text(type.getDisplayName())); hbox.setAlignment(Pos.CENTER_LEFT); flow.getChildren().setAll(label); getChildren().setAll(hbox, flow); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/IconedMenuItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/IconedMenuItem.java index 076e095e0..ef95289ba 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/IconedMenuItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/IconedMenuItem.java @@ -18,14 +18,13 @@ package org.jackhuang.hmcl.ui.construct; import com.jfoenix.controls.JFXPopup; -import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; -public class IconedMenuItem extends IconedItem { +public final class IconedMenuItem extends IconedItem { public IconedMenuItem(SVG icon, String text, Runnable action, JFXPopup popup) { - super(icon != null ? FXUtils.limitingSize(icon.createIcon(Theme.blackFill(), 14), 14, 14) : null, text); + super(icon != null ? icon.createIcon(14) : null, text); getStyleClass().setAll("iconed-menu-item"); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/IconedTwoLineListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/IconedTwoLineListItem.java index 3687dfea7..9cdcae0db 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/IconedTwoLineListItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/IconedTwoLineListItem.java @@ -15,7 +15,6 @@ import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; -import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; import org.jackhuang.hmcl.util.StringUtils; @@ -111,7 +110,7 @@ public class IconedTwoLineListItem extends HBox { if (externalLinkButton == null) { externalLinkButton = new JFXButton(); externalLinkButton.getStyleClass().add("toggle-icon4"); - externalLinkButton.setGraphic(SVG.OPEN_IN_NEW.createIcon(Theme.blackFill(), -1)); + externalLinkButton.setGraphic(SVG.OPEN_IN_NEW.createIcon()); externalLinkButton.setOnAction(e -> FXUtils.openLink(externalLink.get())); } return externalLinkButton; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ImagePickerItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ImagePickerItem.java index c05764740..c22ad5859 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ImagePickerItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ImagePickerItem.java @@ -32,7 +32,6 @@ import javafx.scene.image.ImageView; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; -import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; @@ -54,12 +53,12 @@ public final class ImagePickerItem extends BorderPane { imageView.setPreserveRatio(true); JFXButton selectButton = new JFXButton(); - selectButton.setGraphic(SVG.EDIT.createIcon(Theme.blackFill(), 20)); + selectButton.setGraphic(SVG.EDIT.createIcon(20)); selectButton.onActionProperty().bind(onSelectButtonClicked); selectButton.getStyleClass().add("toggle-icon4"); JFXButton deleteButton = new JFXButton(); - deleteButton.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), 20)); + deleteButton.setGraphic(SVG.CLOSE.createIcon(20)); deleteButton.onActionProperty().bind(onDeleteButtonClicked); deleteButton.getStyleClass().add("toggle-icon4"); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/JFXHyperlink.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/JFXHyperlink.java index 0fefaff46..d67b4422e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/JFXHyperlink.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/JFXHyperlink.java @@ -18,7 +18,6 @@ package org.jackhuang.hmcl.ui.construct; import javafx.scene.control.Hyperlink; -import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; @@ -27,7 +26,7 @@ public final class JFXHyperlink extends Hyperlink { public JFXHyperlink(String text) { super(text); - setGraphic(SVG.OPEN_IN_NEW.createIcon(Theme.blackFill(), 16)); + setGraphic(SVG.OPEN_IN_NEW.createIcon(16)); } public void setExternalLink(String externalLink) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MenuUpDownButton.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MenuUpDownButton.java index f603769e3..f6bbcc5d2 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MenuUpDownButton.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MenuUpDownButton.java @@ -29,7 +29,6 @@ import javafx.scene.control.Label; import javafx.scene.control.Skin; import javafx.scene.control.SkinBase; import javafx.scene.layout.HBox; -import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; @@ -39,6 +38,7 @@ public class MenuUpDownButton extends Control { private final StringProperty text = new SimpleStringProperty(this, "text"); public MenuUpDownButton() { + this.getStyleClass().add("menu-up-down-button"); } @Override @@ -78,11 +78,10 @@ public class MenuUpDownButton extends Control { HBox content = new HBox(8); content.setAlignment(Pos.CENTER); Label label = new Label(); - label.setStyle("-fx-text-fill: black;"); label.textProperty().bind(control.text); - Node up = SVG.ARROW_DROP_UP.createIcon(Theme.blackFill(), 16); - Node down = SVG.ARROW_DROP_DOWN.createIcon(Theme.blackFill(), 16); + Node up = SVG.ARROW_DROP_UP.createIcon(16); + Node down = SVG.ARROW_DROP_DOWN.createIcon(16); JFXButton button = new JFXButton(); button.setGraphic(content); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java index fe20460db..320111eb3 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java @@ -28,7 +28,6 @@ import javafx.scene.layout.Priority; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import javafx.scene.text.TextFlow; -import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; @@ -91,7 +90,7 @@ public final class MessageDialogPane extends HBox { default: throw new IllegalArgumentException("Unrecognized message box message type " + type); } - graphic.setGraphic(svg.createIcon(Theme.blackFill(), 40)); + graphic.setGraphic(svg.createIcon(40)); VBox vbox = new VBox(); HBox.setHgrow(vbox, Priority.ALWAYS); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MultiFileItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MultiFileItem.java index dc7c61d10..2797b9dff 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MultiFileItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MultiFileItem.java @@ -31,12 +31,15 @@ import javafx.scene.control.Toggle; import javafx.scene.control.ToggleGroup; import javafx.scene.layout.BorderPane; import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; import javafx.scene.paint.Paint; import javafx.stage.FileChooser; +import org.jackhuang.hmcl.theme.ThemeColor; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.util.StringUtils; import java.util.Collection; +import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.function.Consumer; @@ -308,11 +311,21 @@ public final class MultiFileItem extends VBox { super(title, data); } + public PaintOption setCustomColors(List colors) { + colorPicker.getCustomColors().setAll(colors); + return this; + } + public PaintOption bindBidirectional(Property property) { FXUtils.bindPaint(colorPicker, property); return this; } + public PaintOption bindThemeColorBidirectional(Property property) { + ThemeColor.bindBidirectional(colorPicker, property); + return this; + } + @Override protected Node createItem(ToggleGroup group) { BorderPane pane = new BorderPane(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/RipplerContainer.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/RipplerContainer.java index fb93b0801..806be70ab 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/RipplerContainer.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/RipplerContainer.java @@ -38,6 +38,7 @@ import javafx.scene.paint.Color; import javafx.scene.paint.Paint; import javafx.scene.shape.Rectangle; import javafx.util.Duration; +import org.jackhuang.hmcl.theme.Themes; import org.jackhuang.hmcl.ui.animation.AnimationUtils; import org.jackhuang.hmcl.ui.animation.Motion; import org.jackhuang.hmcl.util.Lang; @@ -50,7 +51,7 @@ public class RipplerContainer extends StackPane { private static final Duration DURATION = Duration.millis(200); private final ObjectProperty container = new SimpleObjectProperty<>(this, "container", null); - private final StyleableObjectProperty ripplerFill = new SimpleStyleableObjectProperty<>(StyleableProperties.RIPPLER_FILL,this, "ripplerFill", null); + private final StyleableObjectProperty ripplerFill = new SimpleStyleableObjectProperty<>(StyleableProperties.RIPPLER_FILL, this, "ripplerFill", null); private final BooleanProperty selected = new SimpleBooleanProperty(this, "selected", false); private final StackPane buttonContainer = new StackPane(); @@ -136,7 +137,10 @@ public class RipplerContainer extends StackPane { } private void interpolateBackground(double frac) { - setBackground(new Background(new BackgroundFill(Color.rgb(0, 0, 0, frac * 0.04), CornerRadii.EMPTY, Insets.EMPTY))); + Color onSurface = Themes.getColorScheme().getOnSurface(); + setBackground(new Background(new BackgroundFill( + Color.color(onSurface.getRed(), onSurface.getGreen(), onSurface.getBlue(), frac * 0.04), + CornerRadii.EMPTY, Insets.EMPTY))); } protected void updateChildren() { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java index b3031f80d..32581f9f1 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java @@ -50,6 +50,8 @@ public class TaskExecutorDialogPane extends BorderPane { private final TaskListPane taskListPane; public TaskExecutorDialogPane(@NotNull TaskCancellationAction cancel) { + this.getStyleClass().add("task-executor-dialog-layout"); + FXUtils.setLimitWidth(this, 500); FXUtils.setLimitHeight(this, 300); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java index 6a0d930ac..7b05008e4 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java @@ -64,7 +64,6 @@ import org.jackhuang.hmcl.mod.multimc.MultiMCModpackInstallTask; import org.jackhuang.hmcl.mod.server.ServerModpackCompletionTask; import org.jackhuang.hmcl.mod.server.ServerModpackExportTask; import org.jackhuang.hmcl.mod.server.ServerModpackLocalInstallTask; -import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.TaskExecutor; import org.jackhuang.hmcl.task.TaskListener; @@ -310,7 +309,7 @@ public final class TaskListPane extends StackPane { } private void updateLeftIcon(StageNode.Status status) { - left.getChildren().setAll(status.svg.createIcon(Theme.blackFill(), STATUS_ICON_SIZE)); + left.getChildren().setAll(status.svg.createIcon(STATUS_ICON_SIZE)); } @Override diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorSkin.java index 14f28853e..c910c4a13 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorSkin.java @@ -45,7 +45,7 @@ import javafx.stage.Stage; import javafx.util.Duration; import org.jackhuang.hmcl.Metadata; -import org.jackhuang.hmcl.setting.Theme; +import org.jackhuang.hmcl.theme.Themes; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; import org.jackhuang.hmcl.ui.animation.ContainerAnimations; @@ -233,19 +233,19 @@ public class DecoratorSkin extends SkinBase { { JFXButton btnHelp = new JFXButton(); btnHelp.setFocusTraversable(false); - btnHelp.setGraphic(SVG.HELP.createIcon(Theme.foregroundFillBinding(), -1)); + btnHelp.setGraphic(SVG.HELP.createIcon(Themes.titleFillProperty())); btnHelp.getStyleClass().add("jfx-decorator-button"); btnHelp.setOnAction(e -> FXUtils.openLink(Metadata.CONTACT_URL)); JFXButton btnMin = new JFXButton(); btnMin.setFocusTraversable(false); - btnMin.setGraphic(SVG.MINIMIZE.createIcon(Theme.foregroundFillBinding(), -1)); + btnMin.setGraphic(SVG.MINIMIZE.createIcon(Themes.titleFillProperty())); btnMin.getStyleClass().add("jfx-decorator-button"); btnMin.setOnAction(e -> skinnable.minimize()); JFXButton btnClose = new JFXButton(); btnClose.setFocusTraversable(false); - btnClose.setGraphic(SVG.CLOSE.createIcon(Theme.foregroundFillBinding(), -1)); + btnClose.setGraphic(SVG.CLOSE.createIcon(Themes.titleFillProperty())); btnClose.getStyleClass().add("jfx-decorator-button"); btnClose.setOnAction(e -> skinnable.close()); @@ -265,6 +265,8 @@ public class DecoratorSkin extends SkinBase { private Node createNavBar(Decorator skinnable, double leftPaneWidth, boolean canBack, boolean canClose, boolean showCloseAsHome, boolean canRefresh, String title, Node titleNode) { BorderPane navBar = new BorderPane(); + navBar.getStyleClass().add("navigation-bar"); + { HBox navLeft = new HBox(); navLeft.setAlignment(Pos.CENTER_LEFT); @@ -273,9 +275,8 @@ public class DecoratorSkin extends SkinBase { if (canBack) { JFXButton backNavButton = new JFXButton(); backNavButton.setFocusTraversable(false); - backNavButton.setGraphic(SVG.ARROW_BACK.createIcon(Theme.foregroundFillBinding(), -1)); + backNavButton.setGraphic(SVG.ARROW_BACK.createIcon(Themes.titleFillProperty())); backNavButton.getStyleClass().add("jfx-decorator-button"); - backNavButton.ripplerFillProperty().set(Theme.whiteFill()); backNavButton.onActionProperty().bind(skinnable.onBackNavButtonActionProperty()); backNavButton.visibleProperty().set(canBack); @@ -285,14 +286,13 @@ public class DecoratorSkin extends SkinBase { if (canClose) { JFXButton closeNavButton = new JFXButton(); closeNavButton.setFocusTraversable(false); - closeNavButton.setGraphic(SVG.CLOSE.createIcon(Theme.foregroundFillBinding(), -1)); + closeNavButton.setGraphic(SVG.CLOSE.createIcon(Themes.titleFillProperty())); closeNavButton.getStyleClass().add("jfx-decorator-button"); - closeNavButton.ripplerFillProperty().set(Theme.whiteFill()); closeNavButton.onActionProperty().bind(skinnable.onCloseNavButtonActionProperty()); if (showCloseAsHome) - closeNavButton.setGraphic(SVG.HOME.createIcon(Theme.foregroundFillBinding(), -1)); + closeNavButton.setGraphic(SVG.HOME.createIcon(Themes.titleFillProperty())); else - closeNavButton.setGraphic(SVG.CLOSE.createIcon(Theme.foregroundFillBinding(), -1)); + closeNavButton.setGraphic(SVG.CLOSE.createIcon(Themes.titleFillProperty())); navLeft.getChildren().add(closeNavButton); } @@ -333,9 +333,8 @@ public class DecoratorSkin extends SkinBase { HBox navRight = new HBox(); navRight.setAlignment(Pos.CENTER_RIGHT); JFXButton refreshNavButton = new JFXButton(); - refreshNavButton.setGraphic(SVG.REFRESH.createIcon(Theme.foregroundFillBinding(), -1)); + refreshNavButton.setGraphic(SVG.REFRESH.createIcon(Themes.titleFillProperty())); refreshNavButton.getStyleClass().add("jfx-decorator-button"); - refreshNavButton.ripplerFillProperty().set(Theme.whiteFill()); refreshNavButton.onActionProperty().bind(skinnable.onRefreshNavButtonActionProperty()); Rectangle separator = new Rectangle(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ExportWizardProvider.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ExportWizardProvider.java index cb7407ee9..eac1a55a5 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ExportWizardProvider.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ExportWizardProvider.java @@ -126,7 +126,7 @@ public final class ExportWizardProvider implements WizardProvider { exported.setBackgroundImageType(config().getBackgroundImageType()); exported.setBackgroundImage(config().getBackgroundImage()); - exported.setTheme(config().getTheme()); + exported.setThemeColor(config().getThemeColor()); exported.setDownloadType(config().getDownloadType()); exported.setPreferredLoginType(config().getPreferredLoginType()); exported.getAuthlibInjectorServers().setAll(config().getAuthlibInjectorServers()); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/AboutPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/AboutPage.java index 153ad7a55..241093807 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/AboutPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/AboutPage.java @@ -21,12 +21,14 @@ import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; +import javafx.beans.binding.Bindings; import javafx.geometry.Insets; import javafx.scene.control.ScrollPane; import javafx.scene.image.Image; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import org.jackhuang.hmcl.Metadata; +import org.jackhuang.hmcl.theme.Themes; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.construct.ComponentList; import org.jackhuang.hmcl.ui.construct.IconedTwoLineListItem; @@ -105,6 +107,12 @@ public final class AboutPage extends StackPane { getChildren().setAll(scrollPane); } + private static Image loadImage(String url) { + return url.startsWith("/") + ? FXUtils.newBuiltinImage(url) + : new Image(url); + } + private static ComponentList loadIconedTwoLineList(String path) { ComponentList componentList = new ComponentList(); @@ -122,10 +130,14 @@ public final class AboutPage extends StackPane { IconedTwoLineListItem item = new IconedTwoLineListItem(); if (obj.has("image")) { - String image = obj.get("image").getAsString(); - item.setImage(image.startsWith("/") - ? FXUtils.newBuiltinImage(image) - : new Image(image)); + JsonElement image = obj.get("image"); + if (image.isJsonPrimitive()) { + item.setImage(loadImage(image.getAsString())); + } else if (image.isJsonObject()) { + item.imageProperty().bind(Bindings.when(Themes.darkModeProperty()) + .then(loadImage(image.getAsJsonObject().get("dark").getAsString())) + .otherwise(loadImage(image.getAsJsonObject().get("light").getAsString()))); + } } if (obj.has("title")) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/FeedbackPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/FeedbackPage.java index 4b39d6197..bd6f10e37 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/FeedbackPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/FeedbackPage.java @@ -17,9 +17,11 @@ */ package org.jackhuang.hmcl.ui.main; +import javafx.beans.binding.Bindings; import javafx.geometry.Insets; import javafx.scene.control.ScrollPane; import javafx.scene.layout.VBox; +import org.jackhuang.hmcl.theme.Themes; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.construct.ComponentList; import org.jackhuang.hmcl.ui.construct.IconedTwoLineListItem; @@ -50,7 +52,9 @@ public class FeedbackPage extends SpinnerPane { users.setExternalLink(Metadata.GROUPS_URL); IconedTwoLineListItem github = new IconedTwoLineListItem(); - github.setImage(FXUtils.newBuiltinImage("/assets/img/github.png")); + github.imageProperty().bind(Bindings.when(Themes.darkModeProperty()) + .then(FXUtils.newBuiltinImage("/assets/img/github-white.png")) + .otherwise(FXUtils.newBuiltinImage("/assets/img/github.png"))); github.setTitle(i18n("feedback.github")); github.setSubtitle(i18n("feedback.github.statement")); github.setExternalLink("https://github.com/HMCL-dev/HMCL/issues/new/choose"); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaManagementPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaManagementPage.java index 0f5bec1a3..9dce27bed 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaManagementPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaManagementPage.java @@ -35,7 +35,6 @@ import org.jackhuang.hmcl.java.JavaManager; import org.jackhuang.hmcl.java.JavaRuntime; import org.jackhuang.hmcl.setting.ConfigHolder; import org.jackhuang.hmcl.setting.DownloadProviders; -import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.*; @@ -263,7 +262,7 @@ public final class JavaManagementPage extends ListPageBase control.onReveal()); FXUtils.installFastTooltip(revealButton, i18n("reveal.in_file_manager")); @@ -276,12 +275,12 @@ public final class JavaManagementPage extends ListPageBase skinnable.onReveal()); FXUtils.installFastTooltip(revealButton, i18n("reveal.in_file_manager")); @@ -169,7 +168,7 @@ public final class JavaRestorePage extends ListPageBase skinnable.onRemove()); FXUtils.installFastTooltip(removeButton, i18n("java.disabled.management.remove")); @@ -177,7 +176,7 @@ public final class JavaRestorePage extends ListPageBase skinnable.onRestore()); FXUtils.installFastTooltip(restoreButton, i18n("java.disabled.management.restore")); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java index 67f33d904..9a08f8605 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java @@ -50,7 +50,6 @@ import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.setting.DownloadProviders; import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.setting.Profiles; -import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.Controllers; @@ -152,7 +151,7 @@ public final class MainPage extends StackPane implements DecoratorPage { } }); btnHide.getStyleClass().add("announcement-close-button"); - btnHide.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), 20)); + btnHide.setGraphic(SVG.CLOSE.createIcon(20)); titleBar.setRight(btnHide); TextFlow body = FXUtils.segmentToTextFlow(content, Controllers::onHyperlinkAction); @@ -188,7 +187,7 @@ public final class MainPage extends StackPane implements DecoratorPage { StackPane.setMargin(hBox, new Insets(9, 12, 9, 16)); { Label lblIcon = new Label(); - lblIcon.setGraphic(SVG.UPDATE.createIcon(Theme.whiteFill(), 20)); + lblIcon.setGraphic(SVG.UPDATE.createIcon(20)); TwoLineListItem prompt = new TwoLineListItem(); prompt.setSubtitle(i18n("update.bubble.subtitle")); @@ -200,7 +199,7 @@ public final class MainPage extends StackPane implements DecoratorPage { } JFXButton closeUpdateButton = new JFXButton(); - closeUpdateButton.setGraphic(SVG.CLOSE.createIcon(Theme.whiteFill(), 10)); + closeUpdateButton.setGraphic(SVG.CLOSE.createIcon(10)); StackPane.setAlignment(closeUpdateButton, Pos.TOP_RIGHT); closeUpdateButton.getStyleClass().add("toggle-icon-tiny"); StackPane.setMargin(closeUpdateButton, new Insets(5)); @@ -277,7 +276,7 @@ public final class MainPage extends StackPane implements DecoratorPage { menuButton.setOnAction(e -> onMenu()); menuButton.setClip(new Rectangle(211, -100, 100, 200)); StackPane graphic = new StackPane(); - Node svg = SVG.ARROW_DROP_UP.createIcon(Theme.foregroundFillBinding(), 30); + Node svg = SVG.ARROW_DROP_UP.createIcon(30); StackPane.setAlignment(svg, Pos.CENTER_RIGHT); graphic.getChildren().setAll(svg); graphic.setTranslateX(6); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/PersonalizationPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/PersonalizationPage.java index 3f0d75845..cb73a814a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/PersonalizationPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/PersonalizationPage.java @@ -32,12 +32,11 @@ import javafx.scene.control.ColorPicker; import javafx.scene.control.Label; import javafx.scene.control.ScrollPane; import javafx.scene.layout.*; -import javafx.scene.paint.Color; import javafx.scene.text.Font; import javafx.scene.text.FontSmoothingType; import org.jackhuang.hmcl.setting.EnumBackgroundImage; import org.jackhuang.hmcl.setting.FontManager; -import org.jackhuang.hmcl.setting.Theme; +import org.jackhuang.hmcl.theme.ThemeColor; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; import org.jackhuang.hmcl.ui.construct.*; @@ -78,6 +77,22 @@ public class PersonalizationPage extends StackPane { getChildren().setAll(scrollPane); ComponentList themeList = new ComponentList(); + { + BorderPane brightnessPane = new BorderPane(); + themeList.getContent().add(brightnessPane); + + Label left = new Label(i18n("settings.launcher.brightness")); + BorderPane.setAlignment(left, Pos.CENTER_LEFT); + + brightnessPane.setLeft(left); + + JFXComboBox cboBrightness = new JFXComboBox<>( + FXCollections.observableArrayList("auto", "light", "dark")); + cboBrightness.setConverter(FXUtils.stringConverter(name -> i18n("settings.launcher.brightness." + name))); + cboBrightness.valueProperty().bindBidirectional(config().themeBrightnessProperty()); + brightnessPane.setRight(cboBrightness); + } + { BorderPane themePane = new BorderPane(); themeList.getContent().add(themePane); @@ -90,10 +105,9 @@ public class PersonalizationPage extends StackPane { themeColorPickerContainer.setMinHeight(30); themePane.setRight(themeColorPickerContainer); - ColorPicker picker = new JFXColorPicker(Color.web(Theme.getTheme().getColor())); - picker.getCustomColors().setAll(Theme.SUGGESTED_COLORS); - picker.setOnAction(e -> - config().setTheme(Theme.custom(Theme.getColorDisplayName(picker.getValue())))); + ColorPicker picker = new JFXColorPicker(); + picker.getCustomColors().setAll(ThemeColor.STANDARD_COLORS.stream().map(ThemeColor::color).toList()); + ThemeColor.bindBidirectional(picker, config().themeColorProperty()); themeColorPickerContainer.getChildren().setAll(picker); Platform.runLater(() -> JFXDepthManager.setDepth(picker, 0)); } @@ -264,7 +278,7 @@ public class PersonalizationPage extends StackPane { JFXButton clearButton = new JFXButton(); clearButton.getStyleClass().add("toggle-icon4"); - clearButton.setGraphic(SVG.RESTORE.createIcon(Theme.blackFill(), -1)); + clearButton.setGraphic(SVG.RESTORE.createIcon()); clearButton.setOnAction(e -> cboFont.setValue(null)); hBox.getChildren().setAll(cboFont, clearButton); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java index ddc700a71..f4dee1870 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java @@ -93,13 +93,13 @@ public final class SettingsPage extends SettingsView { lblUpdateSub.getStyleClass().setAll("subtitle-label"); lblUpdate.setText(i18n("update")); - lblUpdate.getStyleClass().setAll(); + lblUpdate.getStyleClass().setAll("title-label"); } else { lblUpdateSub.setText(i18n("update.latest")); lblUpdateSub.getStyleClass().setAll("subtitle-label"); lblUpdate.setText(i18n("update")); - lblUpdate.getStyleClass().setAll(); + lblUpdate.getStyleClass().setAll("title-label"); } }; UpdateChecker.latestVersionProperty().addListener(new WeakInvalidationListener(updateListener)); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsView.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsView.java index 6b9022164..e4329ed78 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsView.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsView.java @@ -27,11 +27,8 @@ import javafx.scene.Cursor; import javafx.scene.control.Label; import javafx.scene.control.ScrollPane; import javafx.scene.layout.*; -import javafx.scene.text.Text; import javafx.scene.text.TextAlignment; -import javafx.scene.text.TextFlow; import org.jackhuang.hmcl.setting.EnumCommonDirectory; -import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; import org.jackhuang.hmcl.ui.construct.ComponentList; @@ -115,6 +112,7 @@ public abstract class SettingsView extends StackPane { VBox headerLeft = new VBox(); lblUpdate = new Label(i18n("update")); + lblUpdate.getStyleClass().add("title-label"); lblUpdateSub = new Label(); lblUpdateSub.getStyleClass().add("subtitle-label"); @@ -126,7 +124,7 @@ public abstract class SettingsView extends StackPane { btnUpdate = new JFXButton(); btnUpdate.setOnAction(e -> onUpdate()); btnUpdate.getStyleClass().add("toggle-icon4"); - btnUpdate.setGraphic(SVG.UPDATE.createIcon(Theme.blackFill(), 20)); + btnUpdate.setGraphic(SVG.UPDATE.createIcon(20)); updatePane.setHeaderRight(btnUpdate); } @@ -138,7 +136,7 @@ public abstract class SettingsView extends StackPane { chkUpdateStable = new JFXRadioButton(i18n("update.channel.stable")); chkUpdateDev = new JFXRadioButton(i18n("update.channel.dev")); - TextFlow noteWrapper = new TextFlow(new Text(i18n("update.note"))); + Label noteWrapper = new Label(i18n("update.note")); VBox.setMargin(noteWrapper, new Insets(10, 0, 0, 0)); content.getChildren().setAll(chkUpdateStable, chkUpdateDev, noteWrapper); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/profile/ProfileListItemSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/profile/ProfileListItemSkin.java index 77c15b170..2366242f4 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/profile/ProfileListItemSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/profile/ProfileListItemSkin.java @@ -24,7 +24,6 @@ import javafx.scene.Node; import javafx.scene.control.SkinBase; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; -import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; import org.jackhuang.hmcl.ui.construct.RipplerContainer; @@ -64,7 +63,7 @@ public class ProfileListItemSkin extends SkinBase { btnRemove.setOnAction(e -> skinnable.remove()); btnRemove.getStyleClass().add("toggle-icon4"); BorderPane.setAlignment(btnRemove, Pos.CENTER); - btnRemove.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), 14)); + btnRemove.setGraphic(SVG.CLOSE.createIcon(14)); right.getChildren().add(btnRemove); root.setRight(right); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/terracotta/TerracottaControllerPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/terracotta/TerracottaControllerPage.java index e4b5ca2d2..ea8d364a2 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/terracotta/TerracottaControllerPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/terracotta/TerracottaControllerPage.java @@ -46,7 +46,6 @@ import javafx.scene.text.TextFlow; import org.jackhuang.hmcl.game.LauncherHelper; import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.setting.Profiles; -import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.terracotta.TerracottaManager; @@ -559,7 +558,7 @@ public class TerracottaControllerPage extends StackPane { Label description = new Label(link.description().getText(I18n.getLocale().getCandidateLocales())); HBox placeholder = new HBox(); HBox.setHgrow(placeholder, Priority.ALWAYS); - Node icon = SVG.OPEN_IN_NEW.createIcon(Theme.blackFill(), 16); + Node icon = SVG.OPEN_IN_NEW.createIcon(16); node.getChildren().setAll(description, placeholder, icon); String url = link.link(); @@ -670,11 +669,11 @@ public class TerracottaControllerPage extends StackPane { } public void setLeftIcon(SVG left) { - this.left.set(left.createIcon(Theme.blackFill(), 28)); + this.left.set(left.createIcon(28)); } public void setRightIcon(SVG right) { - this.right.set(right.createIcon(Theme.blackFill(), 28)); + this.right.set(right.createIcon(28)); } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java index c62da41ee..db518f389 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java @@ -38,7 +38,6 @@ import org.jackhuang.hmcl.mod.ModLoaderType; import org.jackhuang.hmcl.mod.RemoteMod; import org.jackhuang.hmcl.mod.RemoteModRepository; import org.jackhuang.hmcl.setting.Profile; -import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.task.FileDownloadTask; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; @@ -394,15 +393,15 @@ public class DownloadPage extends Control implements DecoratorPage { switch (dataItem.getVersionType()) { case Alpha: content.addTag(i18n("mods.channel.alpha")); - graphicPane.getChildren().setAll(SVG.ALPHA_CIRCLE.createIcon(Theme.blackFill(), 24)); + graphicPane.getChildren().setAll(SVG.ALPHA_CIRCLE.createIcon(24)); break; case Beta: content.addTag(i18n("mods.channel.beta")); - graphicPane.getChildren().setAll(SVG.BETA_CIRCLE.createIcon(Theme.blackFill(), 24)); + graphicPane.getChildren().setAll(SVG.BETA_CIRCLE.createIcon(24)); break; case Release: content.addTag(i18n("mods.channel.release")); - graphicPane.getChildren().setAll(SVG.RELEASE_CIRCLE.createIcon(Theme.blackFill(), 24)); + graphicPane.getChildren().setAll(SVG.RELEASE_CIRCLE.createIcon(24)); break; } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListItemSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListItemSkin.java index 6cad34694..f49a901e5 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListItemSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListItemSkin.java @@ -26,7 +26,6 @@ import javafx.scene.control.SkinBase; import javafx.scene.input.MouseButton; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; -import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; import org.jackhuang.hmcl.ui.construct.IconedMenuItem; @@ -81,7 +80,7 @@ public class GameListItemSkin extends SkinBase { JFXButton btnUpgrade = new JFXButton(); btnUpgrade.setOnAction(e -> skinnable.update()); btnUpgrade.getStyleClass().add("toggle-icon4"); - btnUpgrade.setGraphic(FXUtils.limitingSize(SVG.UPDATE.createIcon(Theme.blackFill(), 24), 24, 24)); + btnUpgrade.setGraphic(FXUtils.limitingSize(SVG.UPDATE.createIcon(24), 24, 24)); FXUtils.installFastTooltip(btnUpgrade, i18n("version.update")); right.getChildren().add(btnUpgrade); } @@ -91,7 +90,7 @@ public class GameListItemSkin extends SkinBase { btnLaunch.setOnAction(e -> skinnable.launch()); btnLaunch.getStyleClass().add("toggle-icon4"); BorderPane.setAlignment(btnLaunch, Pos.CENTER); - btnLaunch.setGraphic(FXUtils.limitingSize(SVG.ROCKET_LAUNCH.createIcon(Theme.blackFill(), 24), 24, 24)); + btnLaunch.setGraphic(FXUtils.limitingSize(SVG.ROCKET_LAUNCH.createIcon(24), 24, 24)); FXUtils.installFastTooltip(btnLaunch, i18n("version.launch.test")); right.getChildren().add(btnLaunch); } @@ -106,7 +105,7 @@ public class GameListItemSkin extends SkinBase { }); btnManage.getStyleClass().add("toggle-icon4"); BorderPane.setAlignment(btnManage, Pos.CENTER); - btnManage.setGraphic(FXUtils.limitingSize(SVG.MORE_VERT.createIcon(Theme.blackFill(), 24), 24, 24)); + btnManage.setGraphic(FXUtils.limitingSize(SVG.MORE_VERT.createIcon(24), 24, 24)); FXUtils.installFastTooltip(btnManage, i18n("settings.game.management")); right.getChildren().add(btnManage); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java index 09fb47b00..6ed6b48f8 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java @@ -46,7 +46,6 @@ import org.jackhuang.hmcl.mod.RemoteModRepository; import org.jackhuang.hmcl.mod.curse.CurseForgeRemoteModRepository; import org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository; import org.jackhuang.hmcl.setting.Profile; -import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.setting.VersionIconType; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; @@ -591,15 +590,15 @@ final class ModListPageSkin extends SkinBase { imageView.setImage(VersionIconType.COMMAND.getIcon()); restoreButton.getStyleClass().add("toggle-icon4"); - restoreButton.setGraphic(FXUtils.limitingSize(SVG.RESTORE.createIcon(Theme.blackFill(), 24), 24, 24)); + restoreButton.setGraphic(FXUtils.limitingSize(SVG.RESTORE.createIcon(24), 24, 24)); FXUtils.installFastTooltip(restoreButton, i18n("mods.restore")); revealButton.getStyleClass().add("toggle-icon4"); - revealButton.setGraphic(FXUtils.limitingSize(SVG.FOLDER.createIcon(Theme.blackFill(), 24), 24, 24)); + revealButton.setGraphic(FXUtils.limitingSize(SVG.FOLDER.createIcon(24), 24, 24)); infoButton.getStyleClass().add("toggle-icon4"); - infoButton.setGraphic(FXUtils.limitingSize(SVG.INFO.createIcon(Theme.blackFill(), 24), 24, 24)); + infoButton.setGraphic(FXUtils.limitingSize(SVG.INFO.createIcon(24), 24, 24)); container.getChildren().setAll(checkBox, imageView, content, restoreButton, revealButton, infoButton); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/SchematicsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/SchematicsPage.java index 8c529b80d..5be8c8cdd 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/SchematicsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/SchematicsPage.java @@ -33,7 +33,6 @@ import javafx.scene.layout.StackPane; import javafx.stage.FileChooser; import org.jackhuang.hmcl.schematic.LitematicFile; import org.jackhuang.hmcl.setting.Profile; -import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.*; @@ -240,7 +239,7 @@ public final class SchematicsPage extends ListPageBase impl StackPane icon = new StackPane(); icon.setPrefSize(size, size); icon.setMaxSize(size, size); - icon.getChildren().add(getIcon().createIcon(Theme.blackFill(), size)); + icon.getChildren().add(getIcon().createIcon(size)); return icon; } @@ -580,12 +579,12 @@ public final class SchematicsPage extends ListPageBase impl JFXButton btnReveal = new JFXButton(); FXUtils.installFastTooltip(btnReveal, i18n("reveal.in_file_manager")); btnReveal.getStyleClass().add("toggle-icon4"); - btnReveal.setGraphic(SVG.FOLDER_OPEN.createIcon(Theme.blackFill(), -1)); + btnReveal.setGraphic(SVG.FOLDER_OPEN.createIcon()); btnReveal.setOnAction(event -> item.onReveal()); JFXButton btnDelete = new JFXButton(); btnDelete.getStyleClass().add("toggle-icon4"); - btnDelete.setGraphic(SVG.DELETE_FOREVER.createIcon(Theme.blackFill(), -1)); + btnDelete.setGraphic(SVG.DELETE_FOREVER.createIcon()); btnDelete.setOnAction(event -> Controllers.confirm(i18n("button.remove.confirm"), i18n("button.remove"), item::onDelete, null)); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java index aefb20b83..3c3521936 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java @@ -23,7 +23,6 @@ import javafx.scene.layout.FlowPane; import javafx.stage.FileChooser; import org.jackhuang.hmcl.event.Event; import org.jackhuang.hmcl.setting.Profile; -import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.setting.VersionIconType; import org.jackhuang.hmcl.setting.VersionSetting; import org.jackhuang.hmcl.ui.Controllers; @@ -93,7 +92,7 @@ public class VersionIconDialog extends DialogPane { } private Node createCustomIcon() { - Node shape = SVG.ADD_CIRCLE.createIcon(Theme.blackFill(), 32); + Node shape = SVG.ADD_CIRCLE.createIcon(32); shape.setMouseTransparent(true); RipplerContainer container = new RipplerContainer(shape); FXUtils.setLimitWidth(container, 36); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java index 276df89c5..331f334e3 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java @@ -27,7 +27,6 @@ import javafx.scene.Node; import javafx.scene.layout.Priority; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; -import javafx.scene.paint.Paint; import org.jackhuang.hmcl.event.EventBus; import org.jackhuang.hmcl.event.EventPriority; import org.jackhuang.hmcl.event.RefreshedVersionsEvent; @@ -312,7 +311,7 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage } public static Node wrap(SVG svg) { - return wrap(svg.createIcon((Paint) null, 20)); + return wrap(svg.createIcon(20)); } public interface VersionLoadable { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldBackupsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldBackupsPage.java index 586495d1a..b1d910b69 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldBackupsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldBackupsPage.java @@ -31,7 +31,6 @@ import javafx.scene.layout.HBox; import javafx.scene.layout.StackPane; import org.jackhuang.hmcl.game.World; import org.jackhuang.hmcl.game.WorldLockedException; -import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.*; @@ -258,14 +257,14 @@ public final class WorldBackupsPage extends ListPageBase skinnable.onReveal()); JFXButton btnDelete = new JFXButton(); right.getChildren().add(btnDelete); FXUtils.installFastTooltip(btnDelete, i18n("world.backup.delete")); btnDelete.getStyleClass().add("toggle-icon4"); - btnDelete.setGraphic(SVG.DELETE.createIcon(Theme.blackFill(), -1)); + btnDelete.setGraphic(SVG.DELETE.createIcon()); btnDelete.setOnAction(event -> Controllers.confirm(i18n("button.remove.confirm"), i18n("button.remove"), skinnable::onDelete, null)); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java index 34d109304..468309869 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java @@ -33,7 +33,6 @@ import javafx.scene.layout.HBox; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import org.jackhuang.hmcl.game.World; -import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.FXUtils; @@ -161,7 +160,7 @@ public final class WorldInfoPage extends SpinnerPane { blur.setIterations(3); FXUtils.onChangeAndOperate(visibility, isVisibility -> { SVG icon = isVisibility ? SVG.VISIBILITY : SVG.VISIBILITY_OFF; - visibilityButton.getChildren().setAll(icon.createIcon(Theme.blackFill(), 12)); + visibilityButton.getChildren().setAll(icon.createIcon(12)); randomSeedLabel.setEffect(isVisibility ? null : blur); }); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListItemSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListItemSkin.java index 89b8006b5..3e1b75208 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListItemSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListItemSkin.java @@ -28,7 +28,6 @@ import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.layout.StackPane; import org.jackhuang.hmcl.game.World; -import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; import org.jackhuang.hmcl.ui.construct.*; @@ -89,7 +88,7 @@ public final class WorldListItemSkin extends SkinBase { JFXButton btnMore = new JFXButton(); right.getChildren().add(btnMore); btnMore.getStyleClass().add("toggle-icon4"); - btnMore.setGraphic(SVG.MORE_VERT.createIcon(Theme.blackFill(), -1)); + btnMore.setGraphic(SVG.MORE_VERT.createIcon()); btnMore.setOnAction(event -> showPopupMenu(JFXPopup.PopupHPosition.RIGHT, 0, root.getHeight())); } diff --git a/HMCL/src/main/resources/assets/about/deps.json b/HMCL/src/main/resources/assets/about/deps.json index d6d705f82..d72871ec0 100644 --- a/HMCL/src/main/resources/assets/about/deps.json +++ b/HMCL/src/main/resources/assets/about/deps.json @@ -78,5 +78,10 @@ "title": "EasyTier", "subtitle": "Copyright 2024-present Easytier Programme within The Commons Conservancy", "externalLink": "https://github.com/EasyTier/EasyTier" + }, + { + "title": "MonetFX", + "subtitle": "Copyright © 2025 Glavo.\nLicensed under the Apache 2.0 License.", + "externalLink": "https://github.com/Glavo/MonetFX" } ] \ No newline at end of file diff --git a/HMCL/src/main/resources/assets/about/thanks.json b/HMCL/src/main/resources/assets/about/thanks.json index 2782c38bf..8e4540d9c 100644 --- a/HMCL/src/main/resources/assets/about/thanks.json +++ b/HMCL/src/main/resources/assets/about/thanks.json @@ -71,7 +71,10 @@ "externalLink" : "https://github.com/mcmod-info-mirror" }, { - "image" : "/assets/img/github.png", + "image" : { + "light" : "/assets/img/github.png", + "dark" : "/assets/img/github-white.png" + }, "titleLocalized" : "about.thanks_to.contributors", "subtitleLocalized" : "about.thanks_to.contributors.statement", "externalLink" : "https://github.com/HMCL-dev/HMCL/graphs/contributors" diff --git a/HMCL/src/main/resources/assets/css/blue.css b/HMCL/src/main/resources/assets/css/blue.css index c02cbb1b8..6c2c590bc 100644 --- a/HMCL/src/main/resources/assets/css/blue.css +++ b/HMCL/src/main/resources/assets/css/blue.css @@ -1,28 +1,60 @@ -/** - * Hello Minecraft! Launcher - * Copyright (C) 2020 huangyuhui and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -.root { - -fx-base-color: #5C6BC0; - -fx-base-darker-color: derive(-fx-base-color, -10%); - -fx-base-check-color: derive(-fx-base-color, 30%); - -fx-rippler-color: rgba(92, 107, 192, 0.3); - -fx-base-rippler-color: derive(rgba(92, 107, 192, 0.3), 100%); - -fx-base-disabled-text-fill: rgba(256, 256, 256, 0.7); - -fx-base-text-fill: white; - - -theme-thumb: rgba(92, 107, 192, 0.7); +* { + -monet-primary: #4352A5; + -monet-on-primary: #FFFFFF; + -monet-primary-container: #5C6BC0; + -monet-on-primary-container: #F8F6FF; + -monet-primary-fixed: #DEE0FF; + -monet-primary-fixed-dim: #BAC3FF; + -monet-on-primary-fixed: #00105B; + -monet-on-primary-fixed-variant: #2F3F92; + -monet-secondary: #575C7F; + -monet-on-secondary: #FFFFFF; + -monet-secondary-container: #D0D5FD; + -monet-on-secondary-container: #565B7D; + -monet-secondary-fixed: #DEE0FF; + -monet-secondary-fixed-dim: #BFC4EC; + -monet-on-secondary-fixed: #141938; + -monet-on-secondary-fixed-variant: #3F4566; + -monet-tertiary: #775200; + -monet-on-tertiary: #FFFFFF; + -monet-tertiary-container: #976900; + -monet-on-tertiary-container: #FFF6EE; + -monet-tertiary-fixed: #FFDEAC; + -monet-tertiary-fixed-dim: #F6BD58; + -monet-on-tertiary-fixed: #281900; + -monet-on-tertiary-fixed-variant: #5F4100; + -monet-error: #BA1A1A; + -monet-on-error: #FFFFFF; + -monet-error-container: #FFDAD6; + -monet-on-error-container: #93000A; + -monet-surface: #FBF8FF; + -monet-on-surface: #1B1B21; + -monet-surface-dim: #DBD9E1; + -monet-surface-bright: #FBF8FF; + -monet-surface-container-lowest: #FFFFFF; + -monet-surface-container-low: #F5F2FA; + -monet-surface-container: #EFEDF5; + -monet-surface-container-high: #E9E7EF; + -monet-surface-container-highest: #E3E1E9; + -monet-surface-variant: #E2E1EF; + -monet-on-surface-variant: #454651; + -monet-background: #FBF8FF; + -monet-on-background: #1B1B21; + -monet-outline: #767683; + -monet-outline-variant: #C6C5D3; + -monet-shadow: #000000; + -monet-scrim: #000000; + -monet-inverse-surface: #303036; + -monet-inverse-on-surface: #F2EFF7; + -monet-inverse-primary: #BAC3FF; + -monet-surface-tint: #4858AB; + -monet-primary-seed: #5C6BC0; + -monet-primary-transparent-50: #4352A580; + -monet-secondary-container-transparent-50: #D0D5FD80; + -monet-surface-transparent-50: #FBF8FF80; + -monet-surface-transparent-80: #FBF8FFCC; + -monet-on-surface-variant-transparent-38: #45465161; + -monet-surface-container-low-transparent-80: #F5F2FACC; + -monet-secondary-container-transparent-80: #D0D5FDCC; + -warning-tag-background: #F1AEB5; } \ No newline at end of file diff --git a/HMCL/src/main/resources/assets/css/root.css b/HMCL/src/main/resources/assets/css/root.css index 0d7df39db..aa3a9823d 100644 --- a/HMCL/src/main/resources/assets/css/root.css +++ b/HMCL/src/main/resources/assets/css/root.css @@ -15,31 +15,33 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ + +/* TODO: Monet */ .root { + -fx-base-check-color: derive(-monet-primary-container, 30%); + -fx-base-text-fill: -monet-on-primary-container; } .svg { - -fx-fill: black; + -fx-fill: -monet-on-surface; +} + +.label { + -fx-text-fill: -monet-on-surface; } .scroll-bar .thumb { - -fx-fill: -theme-thumb; + -fx-fill: -monet-surface-tint; -fx-arc-width: 0; -fx-arc-height: 0; } -.disabled Label { - -fx-text-fill: rgba(0, 0, 0, 0.5); -} - .title-label { - -fx-font-size: 16.0px; - -fx-padding: 14.0px; - -fx-text-fill: rgba(0.0, 0.0, 0.0, 0.87); + -fx-text-fill: -monet-on-surface; } .subtitle-label { - -fx-text-fill: rgba(0, 0, 0, 0.5); + -fx-text-fill: -monet-on-surface-variant; } .hint { @@ -49,24 +51,18 @@ -fx-padding: 6; } -/* - Colors are picked from bootstrap - - https://getbootstrap.com/docs/5.3/components/alerts/ - */ - .hint.info { - -fx-background-color: #cfe2ff; - -fx-border-color: #9ec5fe; + -fx-background-color: -monet-primary-fixed-dim; + -fx-border-color: -monet-outline; } .hint.info Text, .hint.info .svg { - -fx-fill: #052c65; + -fx-fill: -monet-on-primary-fixed; } .hint.success { -fx-background-color: #d1e7dd; - -fx-border-color: #a3cfbb; + -fx-border-color: -monet-outline; } .hint.success Text, .hint.success .svg { @@ -74,21 +70,21 @@ } .hint.error { - -fx-background-color: #f8d7da; - -fx-border-color: #f1aeb5; + -fx-background-color: -monet-error-container; + -fx-border-color: -monet-outline; } .hint.error Text, .hint.error .svg { - -fx-fill: #58151c; + -fx-fill: -monet-on-error-container; } .hint.warning { - -fx-background-color: #fff3cd; - -fx-border-color: #ffe69c; + -fx-background-color: -monet-tertiary-container; + -fx-border-color: -monet-outline; } .hint.warning Text, .hint.warning .svg { - -fx-fill: #664d03; + -fx-fill: -monet-on-tertiary-container; } .skin-pane .jfx-text-field { @@ -103,7 +99,7 @@ } .memory-used { - -fx-background-color: -fx-base-darker-color; + -fx-background-color: -monet-primary; } .memory-used:disabled { @@ -111,7 +107,7 @@ } .memory-allocate { - -fx-background-color: derive(-fx-base-color, 100%); + -fx-background-color: -monet-primary-transparent-50; } .memory-allocate:disabled { @@ -119,7 +115,7 @@ } .memory-total { - -fx-background-color: -fx-base-rippler-color; + -fx-background-color: -monet-surface-container; } .memory-total:disabled { @@ -127,7 +123,7 @@ } .update-label { - -fx-text-fill: red; + -fx-text-fill: -monet-tertiary; } .radio-button-title-label { @@ -141,11 +137,18 @@ } .announcement .title { + -fx-text-fill: -monet-on-surface; -fx-font-size: 14px; -fx-font-weight: BOLD; } -.announcement JFXHyperlink, .announcement Text { +.announcement Text { + -fx-fill: -monet-on-surface; + -fx-font-size: 13px; +} + +.announcement .hyperlink { + -fx-fill: #0070E0; -fx-font-size: 13px; } @@ -178,16 +181,16 @@ } .advanced-list-item:selected > .rippler-container > .container { - -fx-background-color: -fx-base-rippler-color; + -fx-background-color: -monet-secondary-container-transparent-50; } .advanced-list-item:selected > .rippler-container > .container > .two-line-list-item > .first-line > .title { - -fx-text-fill: -fx-base-color; + -fx-text-fill: -monet-on-secondary-container; -fx-font-weight: bold; } .advanced-list-item:selected .svg { - -fx-fill: -fx-base-color; + -fx-fill: -monet-on-secondary-container; } .navigation-drawer-item > .rippler-container > .container > VBox { @@ -207,20 +210,20 @@ } .profile-list-item:selected > .rippler-container > BorderPane { - -fx-background-color: -fx-base-rippler-color; + -fx-background-color: -monet-secondary-container-transparent-50; } .profile-list-item:selected > .rippler-container > BorderPane > .two-line-list-item > .first-line > .title { - -fx-text-fill: -fx-base-color; + -fx-text-fill: -monet-on-secondary-container; -fx-font-weight: bold; } .profile-list-item:selected .svg { - -fx-fill: -fx-base-color; + -fx-fill: -monet-on-secondary-container; } .notice-pane > .label { - -fx-text-fill: #0079FF; + -fx-text-fill: -monet-on-surface; -fx-font-size: 20; -fx-wrap-text: true; -fx-text-alignment: CENTER; @@ -231,6 +234,14 @@ -fx-padding: 8 16 8 16; } +.class-title Text { + -fx-fill: -monet-on-surface; +} + +.class-title Rectangle { + -fx-fill: -monet-on-surface-variant; +} + .advanced-list-box-item { } @@ -240,7 +251,7 @@ } .iconed-item { - -jfx-rippler-fill: -fx-base-color; + -jfx-rippler-fill: -monet-secondary-container; } .iconed-item .iconed-item-container { @@ -251,7 +262,7 @@ } .iconed-menu-item { - -jfx-rippler-fill: -fx-base-color; + -jfx-rippler-fill: -monet-secondary-container; } .iconed-menu-item .iconed-item-container { @@ -266,7 +277,7 @@ } .popup-menu .scroll-bar .thumb { - -fx-fill: rgba(0, 0, 0, 0.5); + -fx-fill: -monet-surface-tint; } .popup-menu-content { @@ -274,28 +285,28 @@ } .two-line-list-item > .first-line > .title { - -fx-text-fill: #292929; - -fx-font-size: 15px; + -fx-text-fill: -monet-on-surface; + -fx-font-size: 15px; -fx-padding: 0 8 0 0; } .two-line-list-item > HBox > .subtitle { - -fx-text-fill: rgba(0, 0, 0, 0.5); + -fx-text-fill: -monet-on-surface-variant; -fx-font-weight: normal; -fx-font-size: 12px; } .two-line-list-item > .first-line > .tag { - -fx-text-fill: -fx-base-color; - -fx-background-color: -fx-base-rippler-color; + -fx-text-fill: -monet-on-secondary-container; + -fx-background-color: -monet-secondary-container; -fx-padding: 2; -fx-font-weight: normal; -fx-font-size: 12px; } .two-line-list-item > .first-line > .tag-warning { - -fx-text-fill: #d34336;; - -fx-background-color: #f1aeb5; + -fx-text-fill: -monet-on-error-container; + -fx-background-color: -warning-tag-background; -fx-padding: 2; -fx-font-weight: normal; -fx-font-size: 12px; @@ -328,7 +339,7 @@ .bubble > HBox > .two-line-list-item > .first-line > .title, .bubble > HBox > .two-line-list-item > HBox > .subtitle { - -fx-text-fill: white; + -fx-text-fill: white; } .sponsor-pane { @@ -340,7 +351,7 @@ } .installer-item:list-item { - -fx-border-color: #e0e0e0; + -fx-border-color: -monet-outline-variant; -fx-border-width: 0 0 1 0; -fx-alignment: center-left; } @@ -354,7 +365,7 @@ } .installer-item:card { - -fx-background-color: white; + -fx-background-color: -monet-surface; -fx-background-radius: 4; -fx-alignment: center; -fx-pref-width: 180px; @@ -369,23 +380,23 @@ ******************************************************************************/ .launch-pane > .jfx-button { - -fx-background-color: -fx-base-color; + -fx-background-color: -monet-primary-container; -fx-cursor: hand; } .launch-pane > .jfx-button > StackPane > .jfx-rippler { - -jfx-rippler-fill: white; + -jfx-rippler-fill: -monet-on-primary-container; -jfx-mask-type: CIRCLE; -fx-padding: 0.0; } .launch-pane > .jfx-button, .jfx-button * { - -fx-text-fill: -fx-base-text-fill; + -fx-text-fill: -monet-on-primary-container; -fx-font-size: 14px; } .launch-pane > Rectangle { - -fx-fill: -fx-base-darker-color; + -fx-fill: -monet-primary-container; } /******************************************************************************* @@ -395,7 +406,8 @@ ******************************************************************************/ .tooltip { - -fx-text-fill: WHITE; + -fx-text-fill: -monet-inverse-on-surface; + -fx-background-color: -monet-inverse-surface; } /******************************************************************************* @@ -409,7 +421,7 @@ } .tab-selected-line { - -fx-background-color: derive(-fx-base-color, -30%); + -fx-background-color: derive(-monet-primary-container, -30%); } .tab-rippler { @@ -446,7 +458,8 @@ .jfx-dialog-layout { -fx-padding: 24.0px 24.0px 16.0px 24.0px; - -fx-text-fill: rgba(0.0, 0.0, 0.0, 0.87); + -fx-background-color: -monet-surface-container-high; + -fx-text-fill: -monet-on-surface; } .jfx-layout-heading { @@ -460,6 +473,10 @@ -fx-wrap-text: true; } +.jfx-layout-body Text { + -fx-fill: -monet-on-surface; +} + .jfx-layout-actions { -fx-pref-width: 400; -fx-padding: 10.0px 0.0 0.0 0.0; @@ -467,19 +484,26 @@ } .dialog-error { - -fx-text-fill: #d32f2f; + -fx-text-fill: -monet-error; -fx-padding: 0.7em 0.8em; } .dialog-accept { - -fx-text-fill: #03A9F4; + -fx-text-fill: -monet-primary; -fx-padding: 0.7em 0.8em; } .dialog-cancel { + -fx-text-fill: -monet-on-surface-variant; -fx-padding: 0.7em 0.8em; } +.task-executor-dialog-layout { + -fx-background-color: -monet-surface-container-high; + -fx-text-fill: -monet-on-surface; +} + + /******************************************************************************* * * * JFX Pop Up * @@ -491,7 +515,7 @@ } .jfx-popup-container { - -fx-background-color: WHITE; + -fx-background-color: -monet-surface; -fx-background-radius: 4; } @@ -500,15 +524,15 @@ } .jfx-snackbar-content { - -fx-background-color: #323232; + -fx-background-color: -monet-inverse-surface; } .jfx-snackbar-toast { - -fx-text-fill: WHITE; + -fx-text-fill: -monet-inverse-on-surface; } .jfx-snackbar-action { - -fx-text-fill: #ff4081; + -fx-text-fill: -monet-inverse-primary; } /******************************************************************************* @@ -542,7 +566,7 @@ } .jfx-tool-bar.background { - -fx-background-color: -fx-base-color; + -fx-background-color: -monet-primary-container; } /*.jfx-tool-bar HBox {*/ @@ -565,11 +589,11 @@ } .jfx-tool-bar Label { - -fx-text-fill: WHITE; + -fx-text-fill: -monet-on-surface; } .jfx-tool-bar.gray-background Label { - /* -fx-text-fill: BLACK; */ + -fx-text-fill: BLACK; } .jfx-tool-bar .jfx-options-burger { @@ -581,7 +605,7 @@ } .jfx-tool-bar .jfx-rippler { - -jfx-rippler-fill: WHITE; + -fx-text-fill: -monet-on-surface; } .jfx-tool-bar-second { @@ -599,17 +623,18 @@ } .jfx-tool-bar-second .jfx-rippler { - -jfx-rippler-fill: WHITE; + -jfx-rippler-fill: -monet-on-surface; } .jfx-tool-bar-button { + -fx-text-fill: -monet-on-surface; -fx-toggle-icon4-size: 37px; -fx-pref-height: -fx-toggle-icon4-size; -fx-max-height: -fx-toggle-icon4-size; -fx-min-height: -fx-toggle-icon4-size; -fx-background-radius: 5px; -fx-background-color: transparent; - -jfx-toggle-color: white; + -jfx-toggle-color: -monet-on-surface; -jfx-untoggle-color: transparent; } @@ -619,7 +644,7 @@ } .jfx-tool-bar-button .jfx-rippler { - -jfx-rippler-fill: white; + -jfx-rippler-fill: -monet-on-surface; -jfx-mask-type: CIRCLE; } @@ -628,8 +653,6 @@ -fx-background-radius: 5px; -fx-max-height: 40px; -fx-background-color: transparent; - -jfx-toggle-color: rgba(128, 128, 255, 0.2); - -jfx-untoggle-color: transparent; } .jfx-decorator-button .icon { @@ -638,7 +661,6 @@ } .jfx-decorator-button .jfx-rippler { - -jfx-rippler-fill: white; -jfx-mask-type: CIRCLE; -fx-padding: 0.0; } @@ -649,17 +671,10 @@ * * ******************************************************************************/ - .jfx-radio-button { - -jfx-selected-color: -fx-base-check-color; - } - -.jfx-radio-button .radio { - -fx-stroke-width: 2.0px; - -fx-fill: transparent; -} - -.jfx-radio-button .dot { - -fx-fill: -fx-base-check-color; +.jfx-radio-button { + -jfx-selected-color: -monet-primary; + -jfx-unselected-color: -monet-on-surface-variant; + -fx-text-fill: -monet-on-surface; } /******************************************************************************* @@ -669,8 +684,8 @@ ******************************************************************************/ .svg-slider .thumb { - -fx-stroke: black; - -fx-fill: black; + -fx-stroke: -monet-on-surface; + -fx-fill: -monet-on-surface; } .jfx-slider:disabled { @@ -687,8 +702,12 @@ -jfx-indicator-position: right; } +.jfx-slider .track { + -fx-background-color: -monet-on-surface-variant-transparent-38; +} + .jfx-slider .thumb { - -fx-background-color: -fx-base-color; + -fx-background-color: -monet-primary; } /******************************************************************************* @@ -713,42 +732,46 @@ } .jfx-button .jfx-rippler { - -jfx-rippler-fill: -fx-base-check-color; + -jfx-rippler-fill: -monet-on-primary; -jfx-mask-type: CIRCLE; -fx-padding: 0.0; } .jfx-button-raised { - -fx-background-color: -fx-base-color; + -fx-background-color: -monet-primary; } .jfx-button-raised .jfx-rippler { - -jfx-rippler-fill: white; -jfx-mask-type: CIRCLE; -fx-padding: 0.0; } .jfx-button-raised, .jfx-button-raised * { - -fx-text-fill: -fx-base-text-fill; + -fx-text-fill: -monet-on-primary; -fx-font-size: 14px; } .jfx-button-border { - -fx-border-color: gray; + -fx-background-color: transparent; + -fx-border-color: -monet-outline; -fx-border-radius: 5px; -fx-border-width: 0.2px; -fx-padding: 8px; } .jfx-button-border, .jfx-button-border * { - -fx-text-fill: -fx-base-darker-color; + -fx-text-fill: -monet-primary; } .jfx-button-raised-round { - -fx-background-color: -fx-base-color; + -fx-background-color: -monet-primary-container; -fx-background-radius: 50px; } +.menu-up-down-button .label { + -fx-text-fill: -monet-on-surface; +} + /******************************************************************************* * * * JFX Check Box * @@ -757,7 +780,9 @@ .jfx-check-box { -fx-font-weight: BOLD; - -jfx-checked-color: -fx-base-check-color; + -fx-text-fill: -monet-on-surface; + -jfx-checked-color: -monet-primary; + -jfx-unchecked-color: transparent; } /******************************************************************************* @@ -776,11 +801,11 @@ } .jfx-progress-bar > .track { - -fx-background-color: #E0E0E0; + -fx-background-color: -monet-secondary-container; } .jfx-progress-bar > .bar { - -fx-background-color: -fx-base-check-color; + -fx-background-color: -monet-primary; } /******************************************************************************* @@ -790,20 +815,17 @@ *******************************************************************************/ .jfx-text-field, .jfx-password-field, .jfx-text-area { - -fx-background-color: #f1f3f4; + -fx-background-color: -monet-surface-container-highest; + -fx-text-fill: -monet-on-surface; -fx-font-weight: BOLD; - -fx-prompt-text-fill: #808080; + -fx-prompt-text-fill: -monet-on-surface-variant; -fx-alignment: top-left; -fx-max-width: 1000000000; - -jfx-focus-color: -fx-base-check-color; + -jfx-focus-color: -monet-primary; -fx-padding: 8; -jfx-unfocus-color: transparent; } -.jfx-text-area .viewport { - -fx-background-color: #ffffff; -} - /******************************************************************************* * * * JFX List View * @@ -815,7 +837,6 @@ } .jfx-list-cell, .list-cell { - /*-fx-background-color: WHITE;*/ -fx-background-color: transparent; } @@ -825,13 +846,13 @@ } .jfx-list-cell .jfx-rippler { - -jfx-rippler-fill: -fx-base-color; + -jfx-rippler-fill: -monet-primary-container; } -.list-cell:odd:selected > .jfx-rippler > StackPane, -.list-cell:even:selected > .jfx-rippler > StackPane { - -fx-background-color: derive(-fx-base-check-color, 30%); -} +/*.list-cell:odd:selected > .jfx-rippler > StackPane,*/ +/*.list-cell:even:selected > .jfx-rippler > StackPane {*/ +/* -fx-background-color: derive(-fx-base-check-color, 30%);*/ +/*}*/ .jfx-list-view { -fx-background-insets: 0.0; @@ -893,7 +914,7 @@ } .card { - -fx-background-color: rgba(255, 255, 255, 0.8); + -fx-background-color: -monet-surface-container-low-transparent-80; -fx-background-radius: 4; -fx-padding: 8px; @@ -901,7 +922,7 @@ } .card-non-transparent { - -fx-background-color: white; + -fx-background-color: -monet-surface-container-low; -fx-background-radius: 4; -fx-padding: 8px; @@ -909,7 +930,7 @@ } .card:selected { - -fx-background-color: derive(-fx-base-color, 60%); + -fx-background-color: -monet-secondary-container-transparent-80; } .card-list { @@ -918,30 +939,39 @@ } .md-list-cell { - -fx-border-color: #e0e0e0; + -fx-border-color: -monet-outline-variant; -fx-border-width: 0 0 1 0; } .md-list-cell:selected { - -fx-background-color: derive(-fx-base-color, 60%); + -fx-background-color: -monet-secondary-container; } .mod-info-list-cell:warning { - -fx-background-color: #F8D7DA; + -fx-background-color: -monet-error-container; } .options-sublist { - -fx-background-color: white; + -fx-background-color: -monet-surface; } .options-list-item { - -fx-background-color: white; - -fx-border-color: #e0e0e0; + -fx-background-color: -monet-surface; + -fx-border-color: -monet-outline-variant; -fx-border-width: 1 0 0 0; -fx-padding: 10 16 10 16; -fx-font-size: 12; } +.options-list-item .svg { + -fx-fill: -monet-on-surface; + -fx-border-color: -monet-outline-variant; +} + +.options-list-item .label { + -fx-text-fill: -monet-on-surface; +} + .options-list-item.no-padding { -fx-padding: 0; } @@ -970,14 +1000,53 @@ * * *******************************************************************************/ -.jfx-toggle-button { - -jfx-toggle-color: -fx-base-check-color; +.jfx-toggle-button, +.jfx-toggle-button:armed, +.jfx-toggle-button:hover, +.jfx-toggle-button:focused, +.jfx-toggle-button:selected, +.jfx-toggle-button:focused:selected { + -fx-background-color: TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT; + -fx-background-radius: 3px; + -fx-background-insets: 0px; + + -jfx-toggle-color: -monet-primary; + -jfx-toggle-line-color: -monet-primary-container; + -jfx-untoggle-color: -monet-outline; + -jfx-untoggle-line-color: -monet-surface-container-highest; + -jfx-size: 10; } + +.jfx-toggle-button Line { + -fx-stroke: -jfx-untoggle-line-color; +} +.jfx-toggle-button:selected Line{ + -fx-stroke: -jfx-toggle-line-color; +} + +.jfx-toggle-button Circle{ + -fx-fill: -jfx-untoggle-color; +} + +.jfx-toggle-button:selected Circle{ + -fx-fill: -jfx-toggle-color; +} + +.jfx-toggle-button .jfx-rippler { + -jfx-rippler-fill: -jfx-untoggle-line-color; +} + +.jfx-toggle-button:selected .jfx-rippler { + -jfx-rippler-fill: -jfx-toggle-line-color; +} + + .toggle-label { -fx-font-size: 14.0px; } +/* unused */ .toggle-icon1 .icon { -fx-fill: #4285F4; -fx-padding: 20.0; @@ -1004,6 +1073,7 @@ -fx-background-color: transparent; } +/* unused */ .toggle-icon2 .icon { -fx-fill: RED; } @@ -1013,6 +1083,7 @@ -jfx-mask-type: CIRCLE; } +/* unused */ .toggle-icon3 { -fx-toggle-icon4-size: 35px; -fx-pref-width: -fx-toggle-icon4-size; @@ -1037,6 +1108,7 @@ -jfx-mask-type: CIRCLE; } +/* used */ .toggle-icon4 { -fx-toggle-icon4-size: 30px; -fx-pref-width: -fx-toggle-icon4-size; @@ -1047,7 +1119,7 @@ -fx-min-height: -fx-toggle-icon4-size; -fx-background-radius: 50px; -fx-background-color: transparent; - -jfx-toggle-color: -fx-base-check-color; + -jfx-toggle-color: -monet-primary; -jfx-untoggle-color: transparent; } @@ -1057,7 +1129,7 @@ } .toggle-icon4 .jfx-rippler { - -jfx-rippler-fill: -fx-base-check-color; + -jfx-rippler-fill: -monet-primary; -jfx-mask-type: CIRCLE; } @@ -1177,7 +1249,7 @@ .jfx-spinner > .arc { -fx-stroke-width: 3.0; -fx-fill: transparent; - -fx-stroke: -fx-base-color; + -fx-stroke: -monet-primary-container; } .first-spinner { @@ -1269,11 +1341,31 @@ .jfx-combo-box { -jfx-focus-color: transparent; -jfx-unfocus-color: transparent; - -fx-background-color: #f1f3f4; + -fx-background-color: -monet-surface-container-highest; -fx-padding: 4; -fx-max-width: 1000000000; } +.jfx-combo-box .text { + -fx-fill: -monet-on-surface; +} + +.combo-box-popup .list-view { + -fx-background-color: -monet-surface-container; +} + +.combo-box-popup .list-view .jfx-list-cell { + -fx-background-color: -monet-surface-container; + -fx-text-fill: -monet-on-surface; + -fx-background-insets: 0.0; +} + +.combo-box-popup .list-view .list-cell:odd:selected > .jfx-rippler > StackPane, +.combo-box-popup .list-view .list-cell:even:selected > .jfx-rippler > StackPane { + -fx-background-color: -monet-secondary-container; + -fx-text-fill: -monet-on-secondary-container; +} + .jfx-combo-box-warning { -jfx-focus-color: #D34336; -jfx-unfocus-color: #D34336; @@ -1283,12 +1375,58 @@ -fx-fill: #D34336; } -.combo-box-popup .list-view .jfx-list-cell { - -fx-background-insets: 0.0; +/*.combo-box-popup .list-view .jfx-list-cell .jfx-rippler {*/ +/* -jfx-rippler-fill: -monet-primary-container;*/ +/*}*/ + +/******************************************************************************* +* * +* JFX Color Picker * +* * +*******************************************************************************/ + +.jfx-color-picker:armed, +.jfx-color-picker:hover, +.jfx-color-picker:focused, +.jfx-color-picker { + -fx-background-color: TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT; + -fx-background-radius: 3px; + -fx-background-insets: 0px; + -fx-min-height: 25px; } -.combo-box-popup .list-view .jfx-list-cell .jfx-rippler { - -jfx-rippler-fill: -fx-base-color; +.jfx-color-picker > .jfx-rippler > .color-box { + -fx-background-color: -monet-primary-seed; + -fx-background-radius: 3px; + -fx-background-insets: 0px; +} + +.jfx-color-picker > .jfx-rippler > .color-box:disabled { + -fx-background-color: transparent; +} + + +.color-palette-region { +} + +.color-palette-region .jfx-button { + -fx-text-fill: -monet-on-surface; +} + +.color-palette { + -fx-background-color: -monet-surface; +} + +.custom-color-dialog .jfx-tab-pane { + -fx-background-color: -monet-surface; +} + +.custom-color-dialog .custom-color-field { + -jfx-unfocus-color: -monet-on-surface; + -fx-background-color: TRANSPARENT; + -fx-font-weight: BOLD; + -fx-alignment: top-left; + -fx-max-width: 300; } /******************************************************************************* @@ -1298,7 +1436,7 @@ *******************************************************************************/ .jfx-decorator { - -fx-decorator-color: -fx-base-color; + -fx-decorator-color: -monet-primary-container; } .jfx-decorator-drawer { @@ -1306,7 +1444,7 @@ } .jfx-decorator-title { - -fx-text-fill: -fx-base-text-fill; + -fx-text-fill: -monet-on-primary-container; -fx-font-size: 14; } @@ -1314,16 +1452,6 @@ -fx-text-fill: black; } -.jfx-decorator-tab .tab-container .tab-label { - -fx-text-fill: -fx-base-disabled-text-fill; - -fx-font-size: 14; -} - -.jfx-decorator-tab .tab-container:selected .tab-label { - -fx-text-fill: -fx-base-text-fill; - -fx-font-weight: BOLD; -} - .window { -fx-background-color: transparent; -fx-padding: 8; @@ -1335,15 +1463,11 @@ } .content-background { - -fx-background-color: rgba(244, 244, 244, 0.5); + -fx-background-color: -monet-surface-transparent-50; } .gray-background { - -fx-background-color: rgba(244, 244, 244, 0.5); -} - -.white-background { - -fx-background-color: rgb(255, 255, 255); + -fx-background-color: -monet-surface-transparent-50; } /******************************************************************************* @@ -1351,6 +1475,7 @@ * Scroll Bar * * * ******************************************************************************/ +/* unsued? */ .scroll-bar:vertical > .track-background, .scroll-bar:horizontal > .track-background { -fx-background-color: #F1F1F1; @@ -1406,7 +1531,7 @@ .scroll-pane > .viewport { -fx-background-color: null; - } +} .scroll-pane .scroll-bar { -fx-skin: "org.jackhuang.hmcl.ui.construct.FloatScrollBarSkin"; @@ -1423,28 +1548,28 @@ *******************************************************************************/ .error-label { - -fx-text-fill: #D34336; + -fx-text-fill: -monet-error; -fx-font-size: 0.75em; -fx-font-weight: bold; } .error-icon { - -fx-fill: #D34336; + -fx-fill: -monet-error; -fx-font-size: 1.0em; } .jfx-text-field:error, .jfx-password-field:error, .jfx-text-area:error, .jfx-combo-box:error { - -jfx-focus-color: #D34336; - -jfx-unfocus-color: #D34336; + -jfx-focus-color: -monet-error; + -jfx-unfocus-color: -monet-error; } .jfx-text-field .error-label, .jfx-password-field .error-label, .jfx-text-area .error-label { - -fx-text-fill: #D34336; + -fx-text-fill: -monet-error; -fx-font-size: 0.75em; } .jfx-text-field .error-icon, .jfx-password-field .error-icon, .jfx-text-area .error-icon { - -fx-fill: #D34336; + -fx-fill: -monet-error; -fx-font-size: 1.0em; } diff --git a/HMCL/src/main/resources/assets/img/github-white.png b/HMCL/src/main/resources/assets/img/github-white.png new file mode 100644 index 0000000000000000000000000000000000000000..e7cc199bc7c9630f30ca3355e0281202941b0b31 GIT binary patch literal 851 zcmV-Z1FZasP)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmYH(US!H(UYB$E3Lc000McNliru=?fGMAOlfh_jmvR0=P*;K~y-)jg`-9R96(o zKkvoNXrpPwsMQeLPK*lHLY1yW(NQ;=xG}aCT2K)5FDP_TbSX$TVvA4$r3lhZRw)!3 z1zoxD2TdV@;Ex%TA*M}9s3tM3^XVebn>T63=l3q&Ip?15_uY5Tz2`;Lz_~|`0w3`q z@9{P;L5V@`@{n18h`JE5#Uozvku#<-<0G%wV~ej|vM9OHE(aD+Yh|w5W#b|uBDQ+o zNwYBNc~}2WLfl}V)l9l-)jl`4fWF@THzL16zw1rWo9s;{#vJnRy0m8wSxM|(n-U2l zPx&GVf8|<>e($(CO)YU}Qjt0X+~%Jl|BP`=8%M%!E~O2k*9>4C%R_z- zZ83Z341qrwCJUofg5AlUnEm8KpqHDO0>5*SYGUWvhlAmZ#@%5iU)fQ64fQd0m0SJ7KA+X}hs)*>i%zJw-0c=` zsru3}FBpnt&T~FZ`oKIU+$V@RFR40Yhx;8)`ra|4)F*T;(yK3-Gnwx2j;h}nyUTC= z$**m#*@Hsr{LkTzg>+XeT2?C_NH>pE1=@XBLoZuQWr&g8medh1c-&9@+?Lk}ls#;; zu(9LbWDB+GupM;;Zd7{ literal 0 HcmV?d00001 diff --git a/HMCL/src/main/resources/assets/img/github-white@2x.png b/HMCL/src/main/resources/assets/img/github-white@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..94427900199d67675a7c2926a2f8b52f750f89a2 GIT binary patch literal 1648 zcmV-$29NoPP)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmYH(US!H(UYB$E3Lc000McNliru=?fGMA34vn1oi*`1=UGJK~z}7wV7LNR8#XBaih|@; zo_zAjtAX-J>QF!}`d3YAQlFS z)#;8%)^QT!kfeU9)LhL}sRr~iJfQPBs)MRkTdL78f+Ge}%>izcirnS^iy1_Seg@#V zEaV97DS>y(eU7n++%!obT=>~dTUy}V(#mcsaivTFk;^i!WCY$V^(-Yf1qujJ$o4b? zZ<2PlQJCabAjAX?IR@S>hbT*e0zy>si{tPBr>N+K0zypUx5okx@H^!RC?G^JCm$O; zz)8m0Qb73l>aoHDe1#7i_$)Tk6*K%g_1qztCfHr-xf}eOCv(jZ$mPUt(`cqMf}Ggwj&PAp0Z&sFZ9~lA zew^oPJlV4ih%9y*;JbJ=gU9p{fhpWGc;8MRBw3QA3e7jpwlc<-B$u%+nXd{-vXG=j z8ft|1DU*VtTZSmxpe<0fge*x#dckJuM7cA=Cgdf{Xq=~_P>`!sVl%%`eKTIar4crk z5>-i-EX}lGcuOCtHDdxneX2U6B}=npi2_dBWV3Zxj@$zA8XZQ2(-eqjX)zklGRA=e z7@{zMAB-3+Ot(~NQ|a$ly(9RN6wn?84f_5nw^U}l?QJ^jz-NV|u$SYyW-z%`X{ppW zt3l@+TfZbV+8o4UOHbGkxZ@~+|8&KuFR4h{9g{@)?Z{mQ99|X4+(kbR=7IY;M(gOSLvP)UO$i z5%B9dqrOvTEY<3^(U_xU@;Xru$fG41Vl>{?8R6xGO+nJhTHKjYK!{h2WjVlcyh3co zPMTSqMuRCDeg>Cq{eOsrn4O>#c7vtlq(=eKhXq`;_1(to5P`v*f&|01Ga=RL5aD7p z8)#18`DeU@!?ZR+eTMgVpMz2RR?jA?$WLZF5H515;9bu4(pPvZ{B?*Z;Zl!!AA=CN zRL6YRx9}sIc!{SNX=pGA;ieEj3t7h@ZuZ*LxJ)S#oY(gl9`a?Vo5d6^#7%ohfSu$U z31qXL7CK2zHble|Q9@mW@elk6@v`9p-QpQ@n+Y*7!HJKSx}Np-T$hzRj2LhTA0mfO z;->C4Z9q>XHzx)Ekk?}tff&s95ktCIjSvOwjcULISz_PmL(H}L28x#N@W%Iu#55YB zf;5g$>uwFT+@*t7>e z7!h%w%+k&Zo9m3oWlKcB|D-gvpACqj1eq_itdA44?h!urL<~R3_*5t)L0~UKNa#Qm zMzw4IVl|T(L^f_b44@DX=@ZyTQ8NAVLOzekV?l26E8no6Z#l(@UJju>fgoQnJlQ`6 zL)mKR?S)QoNm>N%vyEX%ZzrN3tGH=!)EJe)Oa%Tm%xdCI@=|(F5pHJkQ(X7_24x8e zjG{g+^e1>ejmj#V2jAhJln9KD`P0_O2TA{~GCH& and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.setting; + +import javafx.scene.paint.Color; +import org.jackhuang.hmcl.theme.ThemeColor; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +/// @author Glavo +public final class ThemeColorTest { + + @Test + public void testOf() { + assertEquals(new ThemeColor("#AABBCC", Color.web("#AABBCC")), ThemeColor.of("#AABBCC")); + assertEquals(new ThemeColor("blue", Color.web("#5C6BC0")), ThemeColor.of("blue")); + assertEquals(new ThemeColor("darker_blue", Color.web("#283593")), ThemeColor.of("darker_blue")); + assertEquals(new ThemeColor("green", Color.web("#43A047")), ThemeColor.of("green")); + assertEquals(new ThemeColor("orange", Color.web("#E67E22")), ThemeColor.of("orange")); + assertEquals(new ThemeColor("purple", Color.web("#9C27B0")), ThemeColor.of("purple")); + assertEquals(new ThemeColor("red", Color.web("#B71C1C")), ThemeColor.of("red")); + + assertNull(ThemeColor.of((String) null)); + assertNull(ThemeColor.of("")); + assertNull(ThemeColor.of("unknown")); + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5d1cb4c56..7724d2e4c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,6 +17,7 @@ jna = "5.18.1" pci-ids = "0.4.0" java-info = "1.0" authlib-injector = "1.2.6" +monet-fx = "0.4.0" # testing junit = "6.0.1" @@ -46,6 +47,7 @@ jna-platform = { module = "net.java.dev.jna:jna-platform", version.ref = "jna" } pci-ids = { module = "org.glavo:pci-ids", version.ref = "pci-ids" } java-info = { module = "org.glavo:java-info", version.ref = "java-info" } authlib-injector = { module = "org.glavo.hmcl:authlib-injector", version.ref = "authlib-injector" } +monet-fx = { module = "org.glavo:MonetFX", version.ref = "monet-fx" } # testing junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" }