diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Settings.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Settings.java index 3ff5bae75..f68c8cc6e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Settings.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Settings.java @@ -448,7 +448,7 @@ public class Settings { public void invalidated() { super.invalidated(); - SETTINGS.setTheme(get().name().toLowerCase()); + SETTINGS.setTheme(get().getName().toLowerCase()); } }; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Theme.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Theme.java index 5f4602698..e445e5fdd 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Theme.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Theme.java @@ -17,29 +17,92 @@ */ package org.jackhuang.hmcl.setting; -import java.util.Optional; +import javafx.scene.paint.Color; +import org.jackhuang.hmcl.util.FileUtils; +import org.jackhuang.hmcl.util.IOUtils; +import org.jackhuang.hmcl.util.Logging; -public enum Theme { - BLUE, - DARK_BLUE, - GREEN, - ORANGE, - PURPLE, - RED; +import java.io.File; +import java.io.IOException; +import java.util.Optional; +import java.util.logging.Level; + +public class Theme { + public static final Theme BLUE = new Theme("blue", "#5C6BC0"); + public static final Theme DARK_BLUE = new Theme("dark_blue", "#283593"); + public static final Theme GREEN = new Theme("green", "#43A047"); + public static final Theme ORANGE = new Theme("orange", "#E67E22"); + public static final Theme PURPLE = new Theme("purple", "#9C27B0"); + public static final Theme RED = new Theme("red", "#F44336"); + + public static final Theme[] VALUES = new Theme[]{BLUE, DARK_BLUE, GREEN, ORANGE, PURPLE, RED}; + + private final String color; + private final String name; + + Theme(String name, String color) { + this.name = name; + this.color = color; + } + + public String getName() { + return name; + } + + public String getColor() { + return color; + } + + public boolean isCustom() { + return name.startsWith("#"); + } public String[] getStylesheets() { + String name = isCustom() ? BLUE.getName() : this.name; + String css = Theme.class.getResource("/assets/css/" + name + ".css").toExternalForm(); + try { + File temp = File.createTempFile("hmcl", ".css"); + FileUtils.writeText(temp, IOUtils.readFullyAsString(Theme.class.getResourceAsStream("/assets/css/custom.css")).replace("%base-color%", color)); + css = temp.toURI().toString(); + } catch (IOException e) { + Logging.LOG.log(Level.SEVERE, "Unable to create theme stylesheet", e); + } + return new String[]{ Theme.class.getResource("/css/jfoenix-fonts.css").toExternalForm(), Theme.class.getResource("/css/jfoenix-design.css").toExternalForm(), - Theme.class.getResource("/assets/css/" + name().toLowerCase() + ".css").toExternalForm(), + css, Theme.class.getResource("/assets/css/root.css").toExternalForm() }; } + public static Theme custom(String color) { + if (!color.startsWith("#")) + throw new IllegalArgumentException(); + return new Theme(color, color); + } + public static Optional getTheme(String name) { - for (Theme theme : values()) - if (theme.name().equalsIgnoreCase(name)) + if (name == null) + return Optional.empty(); + + for (Theme theme : VALUES) + if (theme.name.equalsIgnoreCase(name)) return Optional.of(theme); + + if (name.startsWith("#")) + try { + Color.web(name); + return Optional.of(custom(name)); + } catch (IllegalArgumentException ignore) { + } + 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)).toUpperCase() : null; + } + + } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java index 20b0c8791..c734d4d83 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java @@ -103,7 +103,7 @@ public final class Controllers { decorator.setCustomMaximize(false); scene = new Scene(decorator, 804, 521); - scene.getStylesheets().addAll(Settings.INSTANCE.getTheme().getStylesheets()); + scene.getStylesheets().setAll(Settings.INSTANCE.getTheme().getStylesheets()); stage.setMinWidth(804); stage.setMaxWidth(804); stage.setMinHeight(521); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SettingsPage.java index 0ae0e7244..364e56644 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SettingsPage.java @@ -30,19 +30,18 @@ import javafx.scene.control.ToggleGroup; import javafx.scene.layout.GridPane; import javafx.scene.layout.Pane; import javafx.scene.layout.StackPane; +import javafx.scene.paint.Color; import javafx.scene.text.Font; import org.jackhuang.hmcl.Main; import org.jackhuang.hmcl.setting.*; -import org.jackhuang.hmcl.ui.construct.FileItem; -import org.jackhuang.hmcl.ui.construct.FontComboBox; -import org.jackhuang.hmcl.ui.construct.MultiFileItem; -import org.jackhuang.hmcl.ui.construct.Validator; +import org.jackhuang.hmcl.ui.construct.*; import org.jackhuang.hmcl.ui.wizard.DecoratorPage; import org.jackhuang.hmcl.util.Lang; import java.net.Proxy; import java.util.Arrays; import java.util.Collections; +import java.util.Set; public final class SettingsPage extends StackPane implements DecoratorPage { private final StringProperty title = new SimpleStringProperty(this, "title", Main.i18n("settings.launcher")); @@ -78,7 +77,7 @@ public final class SettingsPage extends StackPane implements DecoratorPage { @FXML private MultiFileItem backgroundItem; @FXML - private MultiFileItem themeItem; + private MultiColorItem themeItem; @FXML private JFXRadioButton chkNoProxy; @FXML @@ -203,10 +202,29 @@ public final class SettingsPage extends StackPane implements DecoratorPage { themeItem.createChildren(Main.i18n("color.red"), Theme.RED) )); - themeItem.getGroup().getToggles().stream().filter(it -> Settings.INSTANCE.getTheme() == it.getUserData()).findFirst().ifPresent(it -> it.setSelected(true)); - themeItem.setToggleSelectedListener(newValue -> Settings.INSTANCE.setTheme((Theme) newValue.getUserData())); - Settings.INSTANCE.themeProperty().setChangedListenerAndOperate(it -> - themeItem.setSubtitle(Main.i18n("color." + it.name().toLowerCase()))); + if (Settings.INSTANCE.getTheme().isCustom()) + themeItem.setColor(Color.web(Settings.INSTANCE.getTheme().getColor())); + + themeItem.setToggleSelectedListener(newValue -> { + if (newValue.getUserData() != null) { + Settings.INSTANCE.setTheme((Theme) newValue.getUserData()); + themeItem.setOnColorPickerChanged(null); + } else { + themeItem.setOnColorPickerChanged(color -> + Settings.INSTANCE.setTheme(Theme.custom(Theme.getColorDisplayName(color)))); + } + }); + + themeItem.getGroup().getToggles().stream().filter(it -> Settings.INSTANCE.getTheme() == it.getUserData() || Settings.INSTANCE.getTheme().isCustom() && themeItem.isCustomToggle(it)).findFirst().ifPresent(it -> it.setSelected(true)); + + Settings.INSTANCE.themeProperty().setChangedListenerAndOperate(it -> { + if (it.getName().startsWith("#") || it.getName().startsWith("0x")) + themeItem.setSubtitle(it.getName()); + else + themeItem.setSubtitle(Main.i18n("color." + it.getName().toLowerCase())); + + Controllers.getScene().getStylesheets().setAll(it.getStylesheets()); + }); } private void initBackgroundItemSubtitle() { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionSettingsController.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionSettingsController.java index deaae9e97..13829715c 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionSettingsController.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionSettingsController.java @@ -25,7 +25,6 @@ import javafx.scene.control.Label; import javafx.scene.control.ScrollPane; import javafx.scene.control.Toggle; import javafx.scene.image.Image; -import javafx.scene.image.ImageView; import javafx.scene.layout.VBox; import javafx.stage.FileChooser; import org.jackhuang.hmcl.Main; @@ -35,6 +34,7 @@ import org.jackhuang.hmcl.setting.VersionSetting; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.construct.ComponentList; +import org.jackhuang.hmcl.ui.construct.ImagePickerItem; import org.jackhuang.hmcl.ui.construct.MultiFileItem; import org.jackhuang.hmcl.util.*; @@ -73,8 +73,7 @@ public final class VersionSettingsController { @FXML private MultiFileItem javaItem; @FXML private MultiFileItem gameDirItem; @FXML private JFXToggleButton chkShowLogs; - @FXML private JFXButton btnIconSelection; - @FXML private ImageView iconView; + @FXML private ImagePickerItem iconPickerItem; @FXML private void initialize() { @@ -100,8 +99,6 @@ public final class VersionSettingsController { globalItem.createChildren(Main.i18n("settings.type.global"), true), globalItem.createChildren(Main.i18n("settings.type.special"), false) )); - - FXUtils.installTooltip(btnIconSelection, Main.i18n("button.edit")); } public void loadVersionSetting(Profile profile, String versionId) { @@ -248,9 +245,9 @@ public final class VersionSettingsController { private void loadIcon() { File iconFile = profile.getRepository().getVersionIcon(versionId); if (iconFile.exists()) - iconView.setImage(new Image("file:" + iconFile.getAbsolutePath())); + iconPickerItem.setImage(new Image("file:" + iconFile.getAbsolutePath())); else - iconView.setImage(FXUtils.DEFAULT_ICON); - FXUtils.limitSize(iconView, 32, 32); + iconPickerItem.setImage(FXUtils.DEFAULT_ICON); + FXUtils.limitSize(iconPickerItem.getImageView(), 32, 32); } } 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 new file mode 100644 index 000000000..29fab5f77 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ImagePickerItem.java @@ -0,0 +1,100 @@ +package org.jackhuang.hmcl.ui.construct; + +import com.jfoenix.controls.JFXButton; +import javafx.beans.DefaultProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.event.EventHandler; +import javafx.geometry.Pos; +import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; +import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.ui.SVG; + +@DefaultProperty("image") +public final class ImagePickerItem extends BorderPane { + + private final ImageView imageView; + private final JFXButton selectButton; + private final Label label; + + private final StringProperty title = new SimpleStringProperty(this, "title"); + private final ObjectProperty> onSelectButtonClicked = new SimpleObjectProperty<>(this, "onSelectButtonClicked"); + private final ObjectProperty image = new SimpleObjectProperty<>(this, "image"); + + public ImagePickerItem() { + imageView = new ImageView(); + imageView.setSmooth(false); + imageView.setPreserveRatio(true); + + selectButton = new JFXButton(); + selectButton.setGraphic(SVG.pencil("black", 15, 15)); + selectButton.onMouseClickedProperty().bind(onSelectButtonClicked); + selectButton.getStyleClass().add("toggle-icon4"); + + FXUtils.installTooltip(selectButton, Main.i18n("button.edit")); + + HBox hBox = new HBox(); + hBox.getChildren().setAll(imageView, selectButton); + hBox.setAlignment(Pos.CENTER_RIGHT); + hBox.setSpacing(8); + setRight(hBox); + + VBox vBox = new VBox(); + label = new Label(); + label.textProperty().bind(title); + vBox.getChildren().setAll(label); + vBox.setAlignment(Pos.CENTER_LEFT); + setLeft(vBox); + + imageView.imageProperty().bind(image); + } + + public String getTitle() { + return title.get(); + } + + public StringProperty titleProperty() { + return title; + } + + public void setTitle(String title) { + this.title.set(title); + } + + public EventHandler getOnSelectButtonClicked() { + return onSelectButtonClicked.get(); + } + + public ObjectProperty> onSelectButtonClickedProperty() { + return onSelectButtonClicked; + } + + public void setOnSelectButtonClicked(EventHandler onSelectButtonClicked) { + this.onSelectButtonClicked.set(onSelectButtonClicked); + } + + public Image getImage() { + return image.get(); + } + + public ObjectProperty imageProperty() { + return image; + } + + public void setImage(Image image) { + this.image.set(image); + } + + public ImageView getImageView() { + return imageView; + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MultiColorItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MultiColorItem.java new file mode 100644 index 000000000..42285ea61 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MultiColorItem.java @@ -0,0 +1,148 @@ +package org.jackhuang.hmcl.ui.construct; + +import com.jfoenix.controls.JFXColorPicker; +import com.jfoenix.controls.JFXRadioButton; +import javafx.beans.NamedArg; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.control.Toggle; +import javafx.scene.control.ToggleGroup; +import javafx.scene.control.Tooltip; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; +import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.ui.FXUtils; + +import java.util.Collection; +import java.util.Optional; +import java.util.function.Consumer; + +public class MultiColorItem extends ComponentList { + private final StringProperty customTitle = new SimpleStringProperty(this, "customTitle", Main.i18n("selector.custom")); + private final StringProperty chooserTitle = new SimpleStringProperty(this, "chooserTitle", Main.i18n("selector.choose_file")); + + private final ToggleGroup group = new ToggleGroup(); + private final JFXColorPicker colorPicker = new JFXColorPicker(); + private final JFXRadioButton radioCustom = new JFXRadioButton(); + private final BorderPane custom = new BorderPane(); + private final VBox pane = new VBox(); + private final boolean hasCustom; + + private Consumer toggleSelectedListener; + private Consumer colorConsumer; + + public MultiColorItem(@NamedArg(value = "hasCustom", defaultValue = "true") boolean hasCustom) { + this.hasCustom = hasCustom; + + radioCustom.textProperty().bind(customTitleProperty()); + radioCustom.setToggleGroup(group); + colorPicker.disableProperty().bind(radioCustom.selectedProperty().not()); + + colorPicker.setOnAction(e -> Optional.ofNullable(colorConsumer).ifPresent(c -> c.accept(colorPicker.getValue()))); + + custom.setLeft(radioCustom); + custom.setStyle("-fx-padding: 3;"); + HBox right = new HBox(); + right.setSpacing(3); + right.getChildren().addAll(colorPicker); + custom.setRight(right); + FXUtils.setLimitHeight(custom, 40); + + pane.setStyle("-fx-padding: 0 0 10 0;"); + pane.setSpacing(8); + + if (hasCustom) + pane.getChildren().add(custom); + addChildren(pane); + + group.selectedToggleProperty().addListener((a, b, newValue) -> { + if (toggleSelectedListener != null) + toggleSelectedListener.accept(newValue); + }); + } + + public Node createChildren(String title) { + return createChildren(title, null); + } + + public Node createChildren(String title, Object userData) { + return createChildren(title, "", userData); + } + + public Node createChildren(String title, String subtitle, Object userData) { + BorderPane pane = new BorderPane(); + pane.setStyle("-fx-padding: 3;"); + + JFXRadioButton left = new JFXRadioButton(title); + left.setToggleGroup(group); + left.setUserData(userData); + pane.setLeft(left); + + Label right = new Label(subtitle); + right.getStyleClass().add("subtitle-label"); + right.setStyle("-fx-font-size: 10;"); + pane.setRight(right); + + return pane; + } + + public void loadChildren(Collection list) { + pane.getChildren().setAll(list); + + if (hasCustom) + pane.getChildren().add(custom); + } + + public ToggleGroup getGroup() { + return group; + } + + public String getCustomTitle() { + return customTitle.get(); + } + + public StringProperty customTitleProperty() { + return customTitle; + } + + public void setCustomTitle(String customTitle) { + this.customTitle.set(customTitle); + } + + public String getChooserTitle() { + return chooserTitle.get(); + } + + public StringProperty chooserTitleProperty() { + return chooserTitle; + } + + public void setChooserTitle(String chooserTitle) { + this.chooserTitle.set(chooserTitle); + } + + public void setCustomUserData(Object userData) { + radioCustom.setUserData(userData); + } + + public boolean isCustomToggle(Toggle toggle) { + return radioCustom == toggle; + } + + public void setToggleSelectedListener(Consumer consumer) { + toggleSelectedListener = consumer; + } + + public void setOnColorPickerChanged(Consumer consumer) { + colorConsumer = consumer; + } + + public void setColor(Color color) { + colorPicker.setValue(color); + } +} diff --git a/HMCL/src/main/resources/assets/css/custom.css b/HMCL/src/main/resources/assets/css/custom.css new file mode 100644 index 000000000..cbf891256 --- /dev/null +++ b/HMCL/src/main/resources/assets/css/custom.css @@ -0,0 +1,5 @@ +.root { + -fx-base-color: %base-color%; + -fx-base-check-color: derive(-fx-base-color, 30%); + -fx-base-text-fill: white; +} \ No newline at end of file diff --git a/HMCL/src/main/resources/assets/fxml/setting.fxml b/HMCL/src/main/resources/assets/fxml/setting.fxml index 70eb1c8ea..51a48c25c 100644 --- a/HMCL/src/main/resources/assets/fxml/setting.fxml +++ b/HMCL/src/main/resources/assets/fxml/setting.fxml @@ -108,7 +108,7 @@ - + diff --git a/HMCL/src/main/resources/assets/fxml/version/version-settings.fxml b/HMCL/src/main/resources/assets/fxml/version/version-settings.fxml index 2d74e2682..75e5d8eff 100644 --- a/HMCL/src/main/resources/assets/fxml/version/version-settings.fxml +++ b/HMCL/src/main/resources/assets/fxml/version/version-settings.fxml @@ -19,28 +19,9 @@ - - - - - - - - - - - - - - - - - - - - - + + +