customized theme

This commit is contained in:
huanghongxun 2018-02-22 21:54:20 +08:00
parent d097b84c82
commit ab5bb8ea6c
10 changed files with 365 additions and 53 deletions

View File

@ -448,7 +448,7 @@ public class Settings {
public void invalidated() { public void invalidated() {
super.invalidated(); super.invalidated();
SETTINGS.setTheme(get().name().toLowerCase()); SETTINGS.setTheme(get().getName().toLowerCase());
} }
}; };

View File

@ -17,29 +17,92 @@
*/ */
package org.jackhuang.hmcl.setting; 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 { import java.io.File;
BLUE, import java.io.IOException;
DARK_BLUE, import java.util.Optional;
GREEN, import java.util.logging.Level;
ORANGE,
PURPLE, public class Theme {
RED; 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() { 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[]{ return new String[]{
Theme.class.getResource("/css/jfoenix-fonts.css").toExternalForm(), Theme.class.getResource("/css/jfoenix-fonts.css").toExternalForm(),
Theme.class.getResource("/css/jfoenix-design.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() 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<Theme> getTheme(String name) { public static Optional<Theme> getTheme(String name) {
for (Theme theme : values()) if (name == null)
if (theme.name().equalsIgnoreCase(name)) return Optional.empty();
for (Theme theme : VALUES)
if (theme.name.equalsIgnoreCase(name))
return Optional.of(theme); return Optional.of(theme);
if (name.startsWith("#"))
try {
Color.web(name);
return Optional.of(custom(name));
} catch (IllegalArgumentException ignore) {
}
return Optional.empty(); 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;
}
} }

View File

@ -103,7 +103,7 @@ public final class Controllers {
decorator.setCustomMaximize(false); decorator.setCustomMaximize(false);
scene = new Scene(decorator, 804, 521); scene = new Scene(decorator, 804, 521);
scene.getStylesheets().addAll(Settings.INSTANCE.getTheme().getStylesheets()); scene.getStylesheets().setAll(Settings.INSTANCE.getTheme().getStylesheets());
stage.setMinWidth(804); stage.setMinWidth(804);
stage.setMaxWidth(804); stage.setMaxWidth(804);
stage.setMinHeight(521); stage.setMinHeight(521);

View File

@ -30,19 +30,18 @@ import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.GridPane; import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane; import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane; import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font; import javafx.scene.text.Font;
import org.jackhuang.hmcl.Main; import org.jackhuang.hmcl.Main;
import org.jackhuang.hmcl.setting.*; import org.jackhuang.hmcl.setting.*;
import org.jackhuang.hmcl.ui.construct.FileItem; import org.jackhuang.hmcl.ui.construct.*;
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.wizard.DecoratorPage; import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.Lang;
import java.net.Proxy; import java.net.Proxy;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Set;
public final class SettingsPage extends StackPane implements DecoratorPage { public final class SettingsPage extends StackPane implements DecoratorPage {
private final StringProperty title = new SimpleStringProperty(this, "title", Main.i18n("settings.launcher")); 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 @FXML
private MultiFileItem backgroundItem; private MultiFileItem backgroundItem;
@FXML @FXML
private MultiFileItem themeItem; private MultiColorItem themeItem;
@FXML @FXML
private JFXRadioButton chkNoProxy; private JFXRadioButton chkNoProxy;
@FXML @FXML
@ -203,10 +202,29 @@ public final class SettingsPage extends StackPane implements DecoratorPage {
themeItem.createChildren(Main.i18n("color.red"), Theme.RED) 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)); if (Settings.INSTANCE.getTheme().isCustom())
themeItem.setToggleSelectedListener(newValue -> Settings.INSTANCE.setTheme((Theme) newValue.getUserData())); themeItem.setColor(Color.web(Settings.INSTANCE.getTheme().getColor()));
Settings.INSTANCE.themeProperty().setChangedListenerAndOperate(it ->
themeItem.setSubtitle(Main.i18n("color." + it.name().toLowerCase()))); 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() { private void initBackgroundItemSubtitle() {

View File

@ -25,7 +25,6 @@ import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane; import javafx.scene.control.ScrollPane;
import javafx.scene.control.Toggle; import javafx.scene.control.Toggle;
import javafx.scene.image.Image; import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import javafx.stage.FileChooser; import javafx.stage.FileChooser;
import org.jackhuang.hmcl.Main; 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.Schedulers;
import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.construct.ComponentList; 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.ui.construct.MultiFileItem;
import org.jackhuang.hmcl.util.*; import org.jackhuang.hmcl.util.*;
@ -73,8 +73,7 @@ public final class VersionSettingsController {
@FXML private MultiFileItem javaItem; @FXML private MultiFileItem javaItem;
@FXML private MultiFileItem gameDirItem; @FXML private MultiFileItem gameDirItem;
@FXML private JFXToggleButton chkShowLogs; @FXML private JFXToggleButton chkShowLogs;
@FXML private JFXButton btnIconSelection; @FXML private ImagePickerItem iconPickerItem;
@FXML private ImageView iconView;
@FXML @FXML
private void initialize() { 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.global"), true),
globalItem.createChildren(Main.i18n("settings.type.special"), false) globalItem.createChildren(Main.i18n("settings.type.special"), false)
)); ));
FXUtils.installTooltip(btnIconSelection, Main.i18n("button.edit"));
} }
public void loadVersionSetting(Profile profile, String versionId) { public void loadVersionSetting(Profile profile, String versionId) {
@ -248,9 +245,9 @@ public final class VersionSettingsController {
private void loadIcon() { private void loadIcon() {
File iconFile = profile.getRepository().getVersionIcon(versionId); File iconFile = profile.getRepository().getVersionIcon(versionId);
if (iconFile.exists()) if (iconFile.exists())
iconView.setImage(new Image("file:" + iconFile.getAbsolutePath())); iconPickerItem.setImage(new Image("file:" + iconFile.getAbsolutePath()));
else else
iconView.setImage(FXUtils.DEFAULT_ICON); iconPickerItem.setImage(FXUtils.DEFAULT_ICON);
FXUtils.limitSize(iconView, 32, 32); FXUtils.limitSize(iconPickerItem.getImageView(), 32, 32);
} }
} }

View File

@ -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<EventHandler<? super MouseEvent>> onSelectButtonClicked = new SimpleObjectProperty<>(this, "onSelectButtonClicked");
private final ObjectProperty<Image> 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<? super MouseEvent> getOnSelectButtonClicked() {
return onSelectButtonClicked.get();
}
public ObjectProperty<EventHandler<? super MouseEvent>> onSelectButtonClickedProperty() {
return onSelectButtonClicked;
}
public void setOnSelectButtonClicked(EventHandler<? super MouseEvent> onSelectButtonClicked) {
this.onSelectButtonClicked.set(onSelectButtonClicked);
}
public Image getImage() {
return image.get();
}
public ObjectProperty<Image> imageProperty() {
return image;
}
public void setImage(Image image) {
this.image.set(image);
}
public ImageView getImageView() {
return imageView;
}
}

View File

@ -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<Toggle> toggleSelectedListener;
private Consumer<Color> 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<Node> 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<Toggle> consumer) {
toggleSelectedListener = consumer;
}
public void setOnColorPickerChanged(Consumer<Color> consumer) {
colorConsumer = consumer;
}
public void setColor(Color color) {
colorPicker.setValue(color);
}
}

View File

@ -0,0 +1,5 @@
.root {
-fx-base-color: %base-color%;
-fx-base-check-color: derive(-fx-base-color, 30%);
-fx-base-text-fill: white;
}

View File

@ -108,7 +108,7 @@
</VBox> </VBox>
</ComponentList> </ComponentList>
<MultiFileItem fx:id="themeItem" title="%settings.launcher.theme" hasSubtitle="true" hasCustom="false" /> <MultiColorItem fx:id="themeItem" title="%settings.launcher.theme" hasSubtitle="true" />
<VBox spacing="5"> <VBox spacing="5">
<BorderPane><left><Label text="%settings.launcher.log_font" BorderPane.alignment="CENTER_LEFT" /></left><right><HBox spacing="3"><FontComboBox fontSize="12" enableStyle="false" fx:id="cboFont" /><JFXTextField fx:id="txtFontSize" maxWidth="50" minWidth="50" /></HBox></right></BorderPane> <BorderPane><left><Label text="%settings.launcher.log_font" BorderPane.alignment="CENTER_LEFT" /></left><right><HBox spacing="3"><FontComboBox fontSize="12" enableStyle="false" fx:id="cboFont" /><JFXTextField fx:id="txtFontSize" maxWidth="50" minWidth="50" /></HBox></right></BorderPane>

View File

@ -19,28 +19,9 @@
<MultiFileItem fx:id="globalItem" title="%settings.type" hasSubtitle="true" hasCustom="false" /> <MultiFileItem fx:id="globalItem" title="%settings.type" hasSubtitle="true" hasCustom="false" />
<BorderPane> <!-- Icon --> <ImagePickerItem fx:id="iconPickerItem" title="%settings.icon" onSelectButtonClicked="#onExploreIcon">
<left> <Image url="/assets/img/icon.png"/>
<VBox alignment="CENTER_LEFT"> </ImagePickerItem>
<Label text="%settings.icon" />
</VBox>
</left>
<right>
<HBox alignment="CENTER_RIGHT" spacing="8">
<ImageView fx:id="iconView" smooth="false" preserveRatio="true">
<StackPane.margin>
<Insets right="12"/>
</StackPane.margin>
<Image url="/assets/img/icon.png"/>
</ImageView>
<JFXButton fx:id="btnIconSelection" onMouseClicked="#onExploreIcon" styleClass="toggle-icon4">
<graphic>
<fx:include source="/assets/svg/pencil.fxml" />
</graphic>
</JFXButton>
</HBox>
</right>
</BorderPane>
<MultiFileItem fx:id="javaItem" title="%settings.game.java_directory" chooserTitle="%settings.game.java_directory.choose" <MultiFileItem fx:id="javaItem" title="%settings.game.java_directory" chooserTitle="%settings.game.java_directory.choose"
hasSubtitle="true" customText="%settings.custom" directory="false" /> hasSubtitle="true" customText="%settings.custom" directory="false" />