From 19079ac1233c7ce04de55fc9b66c472ce2e900da Mon Sep 17 00:00:00 2001 From: Glavo Date: Tue, 3 Feb 2026 21:09:59 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9B=E5=BB=BA=E9=80=9A=E7=94=A8=E7=9A=84?= =?UTF-8?q?=20LineButton=20=E6=8E=A7=E4=BB=B6=E5=B9=B6=E7=AE=80=E5=8C=96?= =?UTF-8?q?=20TerracottaControllerPage=20(#5395)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/construct/LineButton.java | 179 +++++++++++++ .../hmcl/ui/construct/LineButtonBase.java | 70 ++---- .../hmcl/ui/construct/LineComponent.java | 143 +++++++++++ .../ui/construct/LineNavigationButton.java | 112 --------- .../jackhuang/hmcl/ui/construct/LinePane.java | 72 ++---- .../terracotta/TerracottaControllerPage.java | 238 +++++------------- .../hmcl/ui/versions/VersionSettingsPage.java | 2 +- HMCL/src/main/resources/assets/css/root.css | 21 +- 8 files changed, 441 insertions(+), 396 deletions(-) create mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/LineButton.java create mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/LineComponent.java delete mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/LineNavigationButton.java diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/LineButton.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/LineButton.java new file mode 100644 index 000000000..9cd2eff89 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/LineButton.java @@ -0,0 +1,179 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2026 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.ui.construct; + +import javafx.beans.property.*; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.ui.SVG; + +/// @author Glavo +public final class LineButton extends LineButtonBase { + private static final String DEFAULT_STYLE_CLASS = "line-button"; + + public static LineButton createNavigationButton() { + var button = new LineButton(); + button.setRightIcon(SVG.ARROW_FORWARD); + return button; + } + + public LineButton() { + getStyleClass().add(DEFAULT_STYLE_CLASS); + + root.setMouseTransparent(true); + + FXUtils.onClicked(container, this::fire); + } + + public void fire() { + fireEvent(new ActionEvent()); + } + + private ObjectProperty> onAction; + + public ObjectProperty> onActionProperty() { + if (onAction == null) { + onAction = new ObjectPropertyBase<>() { + + @Override + protected void invalidated() { + setEventHandler(ActionEvent.ACTION, get()); + } + + @Override + public Object getBean() { + return LineButton.this; + } + + @Override + public String getName() { + return "onAction"; + } + }; + } + return onAction; + } + + public EventHandler getOnAction() { + return onActionProperty().get(); + } + + public void setOnAction(EventHandler value) { + onActionProperty().set(value); + } + + private StringProperty message; + + public StringProperty messageProperty() { + if (message == null) { + message = new StringPropertyBase() { + @Override + public Object getBean() { + return LineButton.this; + } + + @Override + public String getName() { + return "message"; + } + + @Override + protected void invalidated() { + updateRight(); + } + }; + } + + return message; + } + + public String getMessage() { + return message == null ? "" : message.get(); + } + + public void setMessage(String message) { + messageProperty().set(message); + } + + private SVG rightIcon; + private double rightIconSize; + + public void setRightIcon(SVG rightIcon) { + setRightIcon(rightIcon, SVG.DEFAULT_SIZE); + } + + public void setRightIcon(SVG rightIcon, double size) { + this.rightIcon = rightIcon; + this.rightIconSize = size; + updateRight(); + } + + //region Right + + private Label messageLabel; + private Node rightIconNode; + private SVG currentRightIcon; + private double currentRightIconSize; + + private void updateRight() { + HBox right; + if (root.getRight() instanceof HBox box) { + right = box; + } else { + right = new HBox(); + right.setAlignment(Pos.CENTER_RIGHT); + root.setRight(right); + } + + right.getChildren().clear(); + + String message = getMessage(); + if (message != null && !message.isEmpty()) { + if (messageLabel == null) { + messageLabel = new Label(); + messageLabel.getStyleClass().add("subtitle"); + } + messageLabel.setText(message); + right.getChildren().add(messageLabel); + } else if (messageLabel != null) { + messageLabel.setText(""); + } + + if (rightIcon != currentRightIcon || rightIconSize != currentRightIconSize) { + if (rightIcon != null) { + rightIconNode = rightIcon.createIcon(rightIconSize); + HBox.setMargin(rightIconNode, new Insets(0, 8, 0, 8)); + } else { + rightIconNode = null; + } + currentRightIcon = rightIcon; + currentRightIconSize = rightIconSize; + } + + if (rightIconNode != null) + right.getChildren().add(rightIconNode); + } + + //endregion +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/LineButtonBase.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/LineButtonBase.java index b437e4a1c..879bcaec2 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/LineButtonBase.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/LineButtonBase.java @@ -19,19 +19,15 @@ package org.jackhuang.hmcl.ui.construct; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; -import javafx.beans.property.StringPropertyBase; -import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.Label; import javafx.scene.layout.BorderPane; -import javafx.scene.layout.Region; import javafx.scene.layout.StackPane; -import javafx.scene.layout.VBox; /// @author Glavo -public abstract class LineButtonBase extends StackPane implements NoPaddingComponent { +public abstract class LineButtonBase extends StackPane implements LineComponent { - private static final Insets PADDING = new Insets(8, 8, 8, 16); + private static final String DEFAULT_STYLE_CLASS = "line-button-base"; protected final BorderPane root; protected final RipplerContainer container; @@ -39,9 +35,11 @@ public abstract class LineButtonBase extends StackPane implements NoPaddingCompo private final Label titleLabel; public LineButtonBase() { + this.getStyleClass().addAll(LineComponent.DEFAULT_STYLE_CLASS, LineButtonBase.DEFAULT_STYLE_CLASS); + this.root = new BorderPane(); - root.setPadding(PADDING); - root.setMinHeight(48); + root.setPadding(LineComponent.PADDING); + root.setMinHeight(LineComponent.MIN_HEIGHT); this.container = new RipplerContainer(root); this.getChildren().setAll(container); @@ -53,59 +51,32 @@ public abstract class LineButtonBase extends StackPane implements NoPaddingCompo titleLabel.getStyleClass().add("title"); } + @Override + public BorderPane getRoot() { + return root; + } + private final StringProperty title = new SimpleStringProperty(this, "title"); + @Override public StringProperty titleProperty() { return title; } - public String getTitle() { - return titleProperty().get(); - } - - public void setTitle(String title) { - this.titleProperty().set(title); - } - private StringProperty subtitle; + @Override public StringProperty subtitleProperty() { if (subtitle == null) { - subtitle = new StringPropertyBase() { - private VBox left; - private Label subtitleLabel; - + subtitle = new LineComponent.SubtitleProperty() { @Override - public String getName() { - return "subtitle"; - } - - @Override - public Object getBean() { + public LineButtonBase getBean() { return LineButtonBase.this; } @Override - protected void invalidated() { - String subtitle = get(); - if (subtitle != null && !subtitle.isEmpty()) { - if (left == null) { - left = new VBox(); - left.setMouseTransparent(true); - left.setAlignment(Pos.CENTER_LEFT); - - subtitleLabel = new Label(); - subtitleLabel.setWrapText(true); - subtitleLabel.setMinHeight(Region.USE_PREF_SIZE); - subtitleLabel.getStyleClass().add("subtitle"); - } - subtitleLabel.setText(subtitle); - left.getChildren().setAll(titleLabel, subtitleLabel); - root.setCenter(left); - } else if (left != null) { - subtitleLabel.setText(null); - root.setCenter(titleLabel); - } + public Label getTitleLabel() { + return titleLabel; } }; } @@ -113,11 +84,4 @@ public abstract class LineButtonBase extends StackPane implements NoPaddingCompo return subtitle; } - public String getSubtitle() { - return subtitleProperty().get(); - } - - public void setSubtitle(String subtitle) { - subtitleProperty().set(subtitle); - } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/LineComponent.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/LineComponent.java new file mode 100644 index 000000000..c31a34929 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/LineComponent.java @@ -0,0 +1,143 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2026 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.ui.construct; + +import javafx.beans.property.StringProperty; +import javafx.beans.property.StringPropertyBase; +import javafx.css.PseudoClass; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; +import org.jackhuang.hmcl.ui.SVG; + +/// @author Glavo +public interface LineComponent extends NoPaddingComponent { + String DEFAULT_STYLE_CLASS = "line-component"; + + PseudoClass PSEUDO_LARGER_TITLE = PseudoClass.getPseudoClass("large-title"); + + Insets PADDING = new Insets(8, 8, 8, 16); + Insets ICON_MARGIN = new Insets(0, 16, 0, 0); + double MIN_HEIGHT = 48.0; + + private Node self() { + return (Node) this; + } + + BorderPane getRoot(); + + StringProperty titleProperty(); + + default String getTitle() { + return titleProperty().get(); + } + + default void setTitle(String title) { + titleProperty().set(title); + } + + abstract class SubtitleProperty extends StringPropertyBase { + private VBox left; + private Label subtitleLabel; + + @Override + public String getName() { + return "subtitle"; + } + + @Override + public abstract LineComponent getBean(); + + public abstract Label getTitleLabel(); + + @Override + protected void invalidated() { + String subtitle = get(); + if (subtitle != null && !subtitle.isEmpty()) { + if (left == null) { + left = new VBox(); + left.setMouseTransparent(true); + left.setAlignment(Pos.CENTER_LEFT); + + subtitleLabel = new Label(); + subtitleLabel.setWrapText(true); + subtitleLabel.setMinHeight(Region.USE_PREF_SIZE); + subtitleLabel.getStyleClass().add("subtitle"); + } + subtitleLabel.setText(subtitle); + left.getChildren().setAll(getTitleLabel(), subtitleLabel); + getBean().getRoot().setCenter(left); + } else if (left != null) { + subtitleLabel.setText(null); + getBean().getRoot().setCenter(getTitleLabel()); + } + } + } + + StringProperty subtitleProperty(); + + default String getSubtitle() { + return subtitleProperty().get(); + } + + default void setSubtitle(String subtitle) { + subtitleProperty().set(subtitle); + } + + default void setLeftIcon(Image icon) { + setLeftIcon(icon, -1.0); + } + + default void setLeftIcon(Image icon, double size) { + ImageView imageView = new ImageView(icon); + imageView.getStyleClass().add("left-icon"); + if (size > 0) { + imageView.setFitWidth(size); + imageView.setFitHeight(size); + imageView.setPreserveRatio(true); + imageView.setSmooth(true); + } + imageView.setMouseTransparent(true); + BorderPane.setAlignment(imageView, Pos.CENTER); + BorderPane.setMargin(imageView, ICON_MARGIN); + getRoot().setLeft(imageView); + } + + default void setLeftIcon(SVG svg) { + setLeftIcon(svg, SVG.DEFAULT_SIZE); + } + + default void setLeftIcon(SVG svg, double size) { + Node node = svg.createIcon(size); + node.getStyleClass().add("left-icon"); + node.setMouseTransparent(true); + BorderPane.setAlignment(node, Pos.CENTER); + BorderPane.setMargin(node, ICON_MARGIN); + getRoot().setLeft(node); + } + + default void setLargeTitle(boolean largeTitle) { + self().pseudoClassStateChanged(PSEUDO_LARGER_TITLE, largeTitle); + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/LineNavigationButton.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/LineNavigationButton.java deleted file mode 100644 index 104b083e8..000000000 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/LineNavigationButton.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Hello Minecraft! Launcher - * Copyright (C) 2026 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.ui.construct; - -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.ObjectPropertyBase; -import javafx.beans.property.SimpleStringProperty; -import javafx.beans.property.StringProperty; -import javafx.event.ActionEvent; -import javafx.event.EventHandler; -import javafx.geometry.Insets; -import javafx.geometry.Pos; -import javafx.scene.Node; -import javafx.scene.control.Label; -import javafx.scene.layout.HBox; -import org.jackhuang.hmcl.ui.FXUtils; -import org.jackhuang.hmcl.ui.SVG; - -/// @author Glavo -public final class LineNavigationButton extends LineButtonBase { - private static final String DEFAULT_STYLE_CLASS = "line-navigation-button"; - - public LineNavigationButton() { - getStyleClass().add(DEFAULT_STYLE_CLASS); - - root.setMouseTransparent(true); - - HBox right = new HBox(); - root.setRight(right); - { - right.setAlignment(Pos.CENTER_RIGHT); - - Label valueLabel = new Label(); - valueLabel.getStyleClass().add("subtitle"); - valueLabel.textProperty().bind(messageProperty()); - - Node arrowIcon = SVG.ARROW_FORWARD.createIcon(24); - HBox.setMargin(arrowIcon, new Insets(0, 8, 0, 8)); - - disabledProperty().addListener((observable, oldValue, newValue) -> - arrowIcon.setOpacity(newValue ? 0.4 : 1.0)); - - right.getChildren().setAll(valueLabel, arrowIcon); - } - - FXUtils.onClicked(container, this::fire); - } - - public void fire() { - fireEvent(new ActionEvent()); - } - - private final ObjectProperty> onAction = new ObjectPropertyBase<>() { - - @Override - protected void invalidated() { - setEventHandler(ActionEvent.ACTION, get()); - } - - @Override - public Object getBean() { - return LineNavigationButton.this; - } - - @Override - public String getName() { - return "onAction"; - } - }; - - public ObjectProperty> onActionProperty() { - return onAction; - } - - public EventHandler getOnAction() { - return onActionProperty().get(); - } - - public void setOnAction(EventHandler value) { - onActionProperty().set(value); - } - - private final StringProperty message = new SimpleStringProperty(this, "message", ""); - - public StringProperty messageProperty() { - return message; - } - - public String getMessage() { - return messageProperty().get(); - } - - public void setMessage(String message) { - messageProperty().set(message); - } - -} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/LinePane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/LinePane.java index 101da46eb..a2bbaa30b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/LinePane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/LinePane.java @@ -19,24 +19,21 @@ package org.jackhuang.hmcl.ui.construct; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; -import javafx.beans.property.StringPropertyBase; -import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.Label; import javafx.scene.layout.BorderPane; -import javafx.scene.layout.Region; -import javafx.scene.layout.VBox; /// @author Glavo -public class LinePane extends BorderPane implements NoPaddingComponent { - - private static final Insets PADDING = new Insets(8, 8, 8, 16); +public class LinePane extends BorderPane implements LineComponent { + private static final String DEFAULT_STYLE_CLASS = "line-pane"; private final Label titleLabel; public LinePane() { - this.setPadding(PADDING); - this.setMinHeight(48); + this.getStyleClass().addAll(LineComponent.DEFAULT_STYLE_CLASS, LinePane.DEFAULT_STYLE_CLASS); + + this.setPadding(LineComponent.PADDING); + this.setMinHeight(LineComponent.MIN_HEIGHT); this.titleLabel = new Label(); this.setCenter(titleLabel); @@ -45,71 +42,36 @@ public class LinePane extends BorderPane implements NoPaddingComponent { titleLabel.getStyleClass().add("title"); } + @Override + public BorderPane getRoot() { + return this; + } + private final StringProperty title = new SimpleStringProperty(this, "title"); + @Override public StringProperty titleProperty() { return title; } - public String getTitle() { - return titleProperty().get(); - } - - public void setTitle(String title) { - this.titleProperty().set(title); - } - private StringProperty subtitle; + @Override public StringProperty subtitleProperty() { if (subtitle == null) { - subtitle = new StringPropertyBase() { - private VBox left; - private Label subtitleLabel; - + subtitle = new LineComponent.SubtitleProperty() { @Override - public String getName() { - return "subtitle"; - } - - @Override - public Object getBean() { + public LinePane getBean() { return LinePane.this; } @Override - protected void invalidated() { - String subtitle = get(); - if (subtitle != null && !subtitle.isEmpty()) { - if (left == null) { - left = new VBox(); - left.setMouseTransparent(true); - left.setAlignment(Pos.CENTER_LEFT); - - subtitleLabel = new Label(); - subtitleLabel.setWrapText(true); - subtitleLabel.setMinHeight(Region.USE_PREF_SIZE); - subtitleLabel.getStyleClass().add("subtitle"); - } - subtitleLabel.setText(subtitle); - left.getChildren().setAll(titleLabel, subtitleLabel); - LinePane.this.setCenter(left); - } else if (left != null) { - subtitleLabel.setText(null); - LinePane.this.setCenter(titleLabel); - } + public Label getTitleLabel() { + return titleLabel; } }; } return subtitle; } - - public String getSubtitle() { - return subtitleProperty().get(); - } - - public void setSubtitle(String subtitle) { - subtitleProperty().set(subtitle); - } } 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 36390a2ce..cb9055f89 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 @@ -22,8 +22,6 @@ import javafx.beans.property.DoubleProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.property.SimpleObjectProperty; -import javafx.beans.property.SimpleStringProperty; -import javafx.beans.property.StringProperty; import javafx.beans.value.ChangeListener; import javafx.beans.value.WeakChangeListener; import javafx.collections.FXCollections; @@ -34,13 +32,8 @@ import javafx.scene.Cursor; import javafx.scene.Node; import javafx.scene.control.Label; import javafx.scene.control.ScrollPane; -import javafx.scene.image.Image; -import javafx.scene.image.ImageView; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Priority; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; -import javafx.scene.text.Text; import javafx.scene.text.TextFlow; import org.jackhuang.hmcl.game.LauncherHelper; import org.jackhuang.hmcl.setting.Profile; @@ -57,13 +50,7 @@ import org.jackhuang.hmcl.ui.SVG; import org.jackhuang.hmcl.ui.WeakListenerHolder; import org.jackhuang.hmcl.ui.animation.ContainerAnimations; import org.jackhuang.hmcl.ui.animation.TransitionPane; -import org.jackhuang.hmcl.ui.construct.ComponentList; -import org.jackhuang.hmcl.ui.construct.ComponentSublist; -import org.jackhuang.hmcl.ui.construct.HintPane; -import org.jackhuang.hmcl.ui.construct.MessageDialogPane; -import org.jackhuang.hmcl.ui.construct.RipplerContainer; -import org.jackhuang.hmcl.ui.construct.SpinnerPane; -import org.jackhuang.hmcl.ui.construct.TwoLineListItem; +import org.jackhuang.hmcl.ui.construct.*; import org.jackhuang.hmcl.ui.versions.Versions; import org.jackhuang.hmcl.util.i18n.I18n; import org.jackhuang.hmcl.util.i18n.LocaleUtils; @@ -163,12 +150,12 @@ public class TerracottaControllerPage extends StackPane { body.getStyleClass().add("terracotta-hint"); body.setLineSpacing(4); - LineButton download = LineButton.of(); - download.setLeftImage(FXUtils.newBuiltinImage("/assets/img/terracotta.png")); + var download = createLargeTitleLineButton(); + download.setLeftIcon(FXUtils.newBuiltinImage("/assets/img/terracotta.png")); download.setTitle(i18n(String.format("terracotta.status.uninitialized.%s.title", fork))); download.setSubtitle(i18n("terracotta.status.uninitialized.desc")); - download.setRightIcon(SVG.ARROW_FORWARD); - FXUtils.onClicked(download, () -> { + download.setRightIcon(SVG.ARROW_FORWARD, ICON_SIZE); + download.setOnAction(event -> { TerracottaState.Preparing s = TerracottaManager.download(); if (s != null) { UI_STATE.set(s); @@ -207,12 +194,12 @@ public class TerracottaControllerPage extends StackPane { flow.getStyleClass().add("terracotta-hint"); flow.setLineSpacing(4); - LineButton host = LineButton.of(); - host.setLeftIcon(SVG.HOST); + var host = createLargeTitleLineButton(); + host.setLeftIcon(SVG.HOST, ICON_SIZE); host.setTitle(i18n("terracotta.status.waiting.host.title")); host.setSubtitle(i18n("terracotta.status.waiting.host.desc")); - host.setRightIcon(SVG.ARROW_FORWARD); - FXUtils.onClicked(host, () -> { + host.setRightIcon(SVG.ARROW_FORWARD, ICON_SIZE); + host.setOnAction(event -> { if (LauncherHelper.countMangedProcesses() >= 1) { TerracottaState.HostScanning s1 = TerracottaManager.setScanning(); if (s1 != null) { @@ -239,12 +226,12 @@ public class TerracottaControllerPage extends StackPane { } }); - LineButton guest = LineButton.of(); - guest.setLeftIcon(SVG.ADD_CIRCLE); + var guest = createLargeTitleLineButton(); + guest.setLeftIcon(SVG.ADD_CIRCLE, ICON_SIZE); guest.setTitle(i18n("terracotta.status.waiting.guest.title")); guest.setSubtitle(i18n("terracotta.status.waiting.guest.desc")); - guest.setRightIcon(SVG.ARROW_FORWARD); - FXUtils.onClicked(guest, () -> { + guest.setRightIcon(SVG.ARROW_FORWARD, ICON_SIZE); + guest.setOnAction(event -> { Controllers.prompt(i18n("terracotta.status.waiting.guest.prompt.title"), (code, handler) -> { Task task = TerracottaManager.setGuesting(code); if (task != null) { @@ -263,11 +250,11 @@ public class TerracottaControllerPage extends StackPane { }); if (ThreadLocalRandom.current().nextDouble() < 0.02D) { - LineButton feedback = LineButton.of(); - feedback.setLeftIcon(SVG.FEEDBACK); + var feedback = createLargeTitleLineButton(); + feedback.setLeftIcon(SVG.FEEDBACK, ICON_SIZE); feedback.setTitle(i18n("terracotta.feedback.title")); feedback.setSubtitle(i18n("terracotta.feedback.desc")); - feedback.setRightIcon(SVG.OPEN_IN_NEW); + feedback.setRightIcon(SVG.OPEN_IN_NEW, ICON_SIZE); FXUtils.onClicked(feedback, () -> FXUtils.openLink(TerracottaMetadata.FEEDBACK_LINK)); nodesProperty.setAll(flow, host, guest, feedback); @@ -282,11 +269,11 @@ public class TerracottaControllerPage extends StackPane { body.getStyleClass().add("terracotta-hint"); body.setLineSpacing(4); - LineButton room = LineButton.of(); - room.setLeftIcon(SVG.ARROW_BACK); + var room = createLargeTitleLineButton(); + room.setLeftIcon(SVG.ARROW_BACK, ICON_SIZE); room.setTitle(i18n("terracotta.back")); room.setSubtitle(i18n("terracotta.status.scanning.back")); - FXUtils.onClicked(room, () -> { + room.setOnAction(event -> { TerracottaState.Waiting s = TerracottaManager.setWaiting(); if (s != null) { UI_STATE.set(s); @@ -298,11 +285,11 @@ public class TerracottaControllerPage extends StackPane { statusProperty.set(i18n("terracotta.status.host_starting")); progressProperty.set(-1); - LineButton room = LineButton.of(); - room.setLeftIcon(SVG.ARROW_BACK); + var room = createLargeTitleLineButton(); + room.setLeftIcon(SVG.ARROW_BACK, ICON_SIZE); room.setTitle(i18n("terracotta.back")); room.setSubtitle(i18n("terracotta.status.host_starting.back")); - FXUtils.onClicked(room, () -> { + room.setOnAction(event -> { TerracottaState.Waiting s = TerracottaManager.setWaiting(); if (s != null) { UI_STATE.set(s); @@ -342,17 +329,17 @@ public class TerracottaControllerPage extends StackPane { code.setCursor(Cursor.HAND); FXUtils.onClicked(code, () -> copyCode(cs)); - LineButton copy = LineButton.of(); - copy.setLeftIcon(SVG.CONTENT_COPY); + var copy = createLargeTitleLineButton(); + copy.setLeftIcon(SVG.CONTENT_COPY, ICON_SIZE); copy.setTitle(i18n("terracotta.status.host_ok.code.copy")); copy.setSubtitle(i18n("terracotta.status.host_ok.code.desc")); FXUtils.onClicked(copy, () -> copyCode(cs)); - LineButton back = LineButton.of(); - back.setLeftIcon(SVG.ARROW_BACK); + var back = createLargeTitleLineButton(); + back.setLeftIcon(SVG.ARROW_BACK, ICON_SIZE); back.setTitle(i18n("terracotta.back")); back.setSubtitle(i18n("terracotta.status.host_ok.back")); - FXUtils.onClicked(back, () -> { + back.setOnAction(event -> { TerracottaState.Waiting s = TerracottaManager.setWaiting(); if (s != null) { UI_STATE.set(s); @@ -369,11 +356,11 @@ public class TerracottaControllerPage extends StackPane { statusProperty.set(i18n("terracotta.status.guest_starting")); progressProperty.set(-1); - LineButton room = LineButton.of(); - room.setLeftIcon(SVG.ARROW_BACK); + var room = createLargeTitleLineButton(); + room.setLeftIcon(SVG.ARROW_BACK, ICON_SIZE); room.setTitle(i18n("terracotta.back")); room.setSubtitle(i18n("terracotta.status.guest_starting.back")); - FXUtils.onClicked(room, () -> { + room.setOnAction(event -> { TerracottaState.Waiting s = TerracottaManager.setWaiting(); if (s != null) { UI_STATE.set(s); @@ -384,12 +371,12 @@ public class TerracottaControllerPage extends StackPane { if (state instanceof TerracottaState.GuestStarting) { TerracottaState.GuestStarting.Difficulty difficulty = ((TerracottaState.GuestStarting) state).getDifficulty(); if (difficulty != null && difficulty != TerracottaState.GuestStarting.Difficulty.UNKNOWN) { - LineButton info = LineButton.of(); + var info = createLargeTitleLineButton(); info.setLeftIcon(switch (difficulty) { case UNKNOWN -> throw new AssertionError(); case EASIEST, SIMPLE -> SVG.INFO; case MEDIUM, TOUGH -> SVG.WARNING; - }); + }, ICON_SIZE); String difficultyID = difficulty.name().toLowerCase(Locale.ROOT); info.setTitle(i18n(String.format("terracotta.difficulty.%s", difficultyID))); @@ -412,15 +399,15 @@ public class TerracottaControllerPage extends StackPane { statusProperty.set(i18n("terracotta.status.guest_ok")); progressProperty.set(1); - LineButton tutorial = LineButton.of(); + var tutorial = createLargeTitleLineButton(); tutorial.setTitle(i18n("terracotta.status.guest_ok.title")); tutorial.setSubtitle(i18n("terracotta.status.guest_ok.desc", guestOK.getUrl())); - LineButton back = LineButton.of(); - back.setLeftIcon(SVG.ARROW_BACK); + var back = createLargeTitleLineButton(); + back.setLeftIcon(SVG.ARROW_BACK, ICON_SIZE); back.setTitle(i18n("terracotta.back")); back.setSubtitle(i18n("terracotta.status.guest_ok.back")); - FXUtils.onClicked(back, () -> { + back.setOnAction(event -> { TerracottaState.Waiting s = TerracottaManager.setWaiting(); if (s != null) { UI_STATE.set(s); @@ -438,11 +425,11 @@ public class TerracottaControllerPage extends StackPane { progressProperty.set(1); nodesProperty.setAll(); - LineButton back = LineButton.of(); - back.setLeftIcon(SVG.ARROW_BACK); + var back = createLargeTitleLineButton(); + back.setLeftIcon(SVG.ARROW_BACK, ICON_SIZE); back.setTitle(i18n("terracotta.back")); back.setSubtitle(i18n("terracotta.status.exception.back")); - FXUtils.onClicked(back, () -> { + back.setOnAction(event -> { TerracottaState.Waiting s = TerracottaManager.setWaiting(); if (s != null) { UI_STATE.set(s); @@ -450,8 +437,8 @@ public class TerracottaControllerPage extends StackPane { }); SpinnerPane exportLog = new SpinnerPane(); - LineButton exportLogInner = LineButton.of(); - exportLogInner.setLeftIcon(SVG.OUTPUT); + var exportLogInner = createLargeTitleLineButton(); + exportLogInner.setLeftIcon(SVG.OUTPUT, ICON_SIZE); exportLogInner.setTitle(i18n("terracotta.export_log")); exportLogInner.setSubtitle(i18n("terracotta.export_log.desc")); exportLog.setContent(exportLogInner); @@ -459,7 +446,7 @@ public class TerracottaControllerPage extends StackPane { // FIXME: SpinnerPane loses its content width in loading state. exportLog.minHeightProperty().bind(back.heightProperty()); - FXUtils.onClicked(exportLogInner, () -> { + exportLogInner.setOnAction(event -> { exportLog.setLoading(true); TerracottaManager.exportLogs().thenAcceptAsync(Schedulers.io(), data -> { @@ -493,11 +480,11 @@ public class TerracottaControllerPage extends StackPane { progressProperty.set(1); if (fatal.isRecoverable()) { - LineButton retry = LineButton.of(); - retry.setLeftIcon(SVG.RESTORE); + var retry = createLargeTitleLineButton(); + retry.setLeftIcon(SVG.RESTORE, ICON_SIZE); retry.setTitle(i18n("terracotta.status.fatal.retry")); retry.setSubtitle(message); - FXUtils.onClicked(retry, () -> { + retry.setOnAction(event -> { TerracottaState s = TerracottaManager.recover(); if (s != null) { UI_STATE.set(s); @@ -534,7 +521,8 @@ public class TerracottaControllerPage extends StackPane { children.add(statusPane); children.addAll(nodesProperty); } - + // Prevent the shadow of components from being clipped + StackPane.setMargin(components, new Insets(0, 0, 5, 0)); transition.setContent(components, ContainerAnimations.SLIDE_UP_FADE_IN); }; listener.changed(UI_STATE, null, UI_STATE.get()); @@ -561,34 +549,27 @@ public class TerracottaControllerPage extends StackPane { private ComponentSublist getThirdPartyDownloadNodes() { ComponentSublist locals = new ComponentSublist(); - locals.setComponentPadding(false); - LineButton header = LineButton.of(false); - header.setLeftImage(FXUtils.newBuiltinImage("/assets/img/terracotta.png")); + var header = new LinePane(); + header.setLargeTitle(true); + header.setPadding(Insets.EMPTY); + header.setMinHeight(LinePane.USE_COMPUTED_SIZE); + header.setMouseTransparent(true); + header.setLeftIcon(FXUtils.newBuiltinImage("/assets/img/terracotta.png")); header.setTitle(i18n("terracotta.from_local.title")); header.setSubtitle(i18n("terracotta.from_local.desc")); locals.setHeaderLeft(header); for (TerracottaMetadata.Link link : TerracottaMetadata.PACKAGE_LINKS) { - HBox node = new HBox(); - node.setAlignment(Pos.CENTER_LEFT); - node.setPadding(new Insets(10, 16, 10, 16)); - - 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(16); - node.getChildren().setAll(description, placeholder, icon); - - String url = link.link(); - RipplerContainer container = new RipplerContainer(node); - container.setOnMouseClicked(ev -> Controllers.dialog( + LineButton item = new LineButton(); + item.setRightIcon(SVG.OPEN_IN_NEW); + item.setTitle(link.description().getText(I18n.getLocale().getCandidateLocales())); + item.setOnAction(event -> Controllers.dialog( i18n("terracotta.from_local.guide", TerracottaMetadata.PACKAGE_NAME), i18n("message.info"), MessageDialogPane.MessageType.INFO, - () -> FXUtils.openLink(url) + () -> FXUtils.openLink(link.link()) )); - ComponentList.setNoPadding(container); - locals.getContent().add(container); + locals.getContent().add(item); } return locals; } @@ -597,103 +578,12 @@ public class TerracottaControllerPage extends StackPane { FXUtils.copyText(code, i18n("terracotta.status.host_ok.code.copy.toast")); } - private static final class LineButton extends RipplerContainer { - private final WeakListenerHolder holder = new WeakListenerHolder(); + private static final double ICON_SIZE = 28; - private final ObjectProperty left = new SimpleObjectProperty<>(this, "left"); - private final ObjectProperty right = new SimpleObjectProperty<>(this, "right"); - private final StringProperty title = new SimpleStringProperty(this, "title", ""); - private final StringProperty subTitle = new SimpleStringProperty(this, "subTitle", ""); - - public static LineButton of() { - return of(true); - } - - public static LineButton of(boolean padding) { - HBox container = new HBox(); - if (padding) { - container.setPadding(new Insets(10, 16, 10, 16)); - } - container.setAlignment(Pos.CENTER_LEFT); - container.setCursor(Cursor.HAND); - container.setSpacing(16); - - LineButton button = new LineButton(container); - VBox spacing = new VBox(); - HBox.setHgrow(spacing, Priority.ALWAYS); - button.holder.add(FXUtils.observeWeak(() -> { - List nodes = new ArrayList<>(4); - Node left = button.left.get(); - if (left != null) { - nodes.add(left); - } - - { - // FIXME: It's sucked to have the following TwoLineListItem-liked logic whose subtitle is a TextFlow. - VBox middle = new VBox(); - middle.getStyleClass().add("two-line-list-item"); - middle.setMouseTransparent(true); - { - HBox firstLine = new HBox(); - firstLine.getStyleClass().add("first-line"); - { - Label lblTitle = new Label(button.title.get()); - lblTitle.getStyleClass().add("title"); - firstLine.getChildren().setAll(lblTitle); - } - - HBox secondLine = new HBox(); - secondLine.getStyleClass().add("second-line"); - { - Text text = new Text(button.subTitle.get()); - - TextFlow lblSubtitle = new TextFlow(text); - lblSubtitle.getStyleClass().add("subtitle"); - secondLine.getChildren().setAll(lblSubtitle); - } - - middle.getChildren().setAll(firstLine, secondLine); - } - nodes.add(middle); - } - - nodes.add(spacing); - - Node right = button.right.get(); - if (right != null) { - nodes.add(right); - } - - container.getChildren().setAll(nodes); - }, button.title, button.subTitle, button.left, button.right)); - ComponentList.setNoPadding(button); - - return button; - } - - private LineButton(Node container) { - super(container); - } - - public void setTitle(String title) { - this.title.set(title); - } - - public void setSubtitle(String subtitle) { - this.subTitle.set(subtitle); - } - - public void setLeftImage(Image left) { - this.left.set(new ImageView(left)); - } - - public void setLeftIcon(SVG left) { - this.left.set(left.createIcon(28)); - } - - public void setRightIcon(SVG right) { - this.right.set(right.createIcon(28)); - } + private static LineButton createLargeTitleLineButton() { + var lineButton = new LineButton(); + lineButton.setLargeTitle(true); + return lineButton; } private static final class PlayerProfileUI extends VBox { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java index ee634fdb2..6bcf9659c 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java @@ -424,7 +424,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag serverPane.addRow(0, new Label(i18n("settings.advanced.server_ip")), txtServerIP); } - LineNavigationButton showAdvancedSettingPane = new LineNavigationButton(); + var showAdvancedSettingPane = LineButton.createNavigationButton(); showAdvancedSettingPane.setTitle(i18n("settings.advanced")); showAdvancedSettingPane.setOnAction(event -> { if (lastVersionSetting != null) { diff --git a/HMCL/src/main/resources/assets/css/root.css b/HMCL/src/main/resources/assets/css/root.css index f5bbc4bea..5f695da04 100644 --- a/HMCL/src/main/resources/assets/css/root.css +++ b/HMCL/src/main/resources/assets/css/root.css @@ -1860,10 +1860,29 @@ /******************************************************************************* * * - * Line Button * + * Line Component * * * ******************************************************************************/ +.line-component .title { + -fx-text-fill: -monet-on-surface; +} + +.line-component:large-title .title { + -fx-font-size: 15px; +} + +.line-component .subtitle { + -fx-text-fill: -monet-on-surface-variant; +} + +.line-button .svg { + -fx-opacity: 1; +} + +.line-button .svg:disabled { + -fx-opacity: 0.4; +} .line-select-button .svg { -fx-opacity: 1;