From 11d47bf9b12f045bd81b0aa6e840c0557e73256f Mon Sep 17 00:00:00 2001 From: huanghongxun Date: Mon, 2 Mar 2020 14:27:14 +0800 Subject: [PATCH] alt: version page look --- .../org/jackhuang/hmcl/ui/Controllers.java | 8 +- .../main/java/org/jackhuang/hmcl/ui/SVG.java | 4 + .../hmcl/ui/construct/Navigator.java | 4 + .../hmcl/ui/construct/TabHeader.java | 504 ++++++++++++++++++ .../ui/decorator/DecoratorController.java | 11 +- .../hmcl/ui/decorator/DecoratorSkin.java | 29 +- .../hmcl/ui/versions/ModListPage.java | 19 +- .../hmcl/ui/versions/VersionPage.java | 315 +++++++---- .../hmcl/ui/versions/VersionRootPage.java | 145 ----- HMCL/src/main/resources/assets/css/root.css | 11 +- 10 files changed, 763 insertions(+), 287 deletions(-) create mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TabHeader.java delete mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionRootPage.java 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 30e646154..cbce9dda5 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java @@ -34,7 +34,7 @@ import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType; import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane; import org.jackhuang.hmcl.ui.decorator.DecoratorController; import org.jackhuang.hmcl.ui.main.RootPage; -import org.jackhuang.hmcl.ui.versions.VersionRootPage; +import org.jackhuang.hmcl.ui.versions.VersionPage; import org.jackhuang.hmcl.util.FutureCallback; import org.jackhuang.hmcl.util.Logging; import org.jackhuang.hmcl.util.io.FileUtils; @@ -50,7 +50,7 @@ public final class Controllers { private static Scene scene; private static Stage stage; - private static VersionRootPage versionPage = null; + private static VersionPage versionPage = null; private static AuthlibInjectorServersPage serversPage = null; private static RootPage rootPage; private static DecoratorController decorator; @@ -64,9 +64,9 @@ public final class Controllers { } // FXThread - public static VersionRootPage getVersionPage() { + public static VersionPage getVersionPage() { if (versionPage == null) - versionPage = new VersionRootPage(); + versionPage = new VersionPage(); return versionPage; } 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 cfd9e679e..790f4dcbc 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java @@ -178,4 +178,8 @@ public final class SVG { public static Node arrowRight(ObjectBinding fill, double width, double height) { return createSVGPath("M4,11V13H16L10.5,18.5L11.92,19.92L19.84,12L11.92,4.08L10.5,5.5L16,11H4Z", fill, width, height); } + + public static Node wrench(ObjectBinding fill, double width, double height) { + return createSVGPath("M22.7,19L13.6,9.9C14.5,7.6 14,4.9 12.1,3C10.1,1 7.1,0.6 4.7,1.7L9,6L6,9L1.6,4.7C0.4,7.1 0.9,10.1 2.9,12.1C4.8,14 7.5,14.5 9.8,13.6L18.9,22.7C19.3,23.1 19.9,23.1 20.3,22.7L22.6,20.4C23.1,20 23.1,19.3 22.7,19Z", fill, width, height); + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/Navigator.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/Navigator.java index 44415de47..730890b5d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/Navigator.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/Navigator.java @@ -131,6 +131,10 @@ public class Navigator extends TransitionPane { return stack.size() > 1; } + public int size() { + return stack.size(); + } + public void setContent(Node content, AnimationProducer animationProducer) { super.setContent(content, animationProducer); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TabHeader.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TabHeader.java new file mode 100644 index 000000000..60298af9e --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TabHeader.java @@ -0,0 +1,504 @@ +/* + * 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.ui.construct; + +import com.jfoenix.controls.JFXRippler; +import javafx.animation.*; +import javafx.application.Platform; +import javafx.beans.binding.Bindings; +import javafx.beans.property.*; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.css.PseudoClass; +import javafx.geometry.Insets; +import javafx.geometry.Side; +import javafx.scene.AccessibleAttribute; +import javafx.scene.Node; +import javafx.scene.control.*; +import javafx.scene.input.MouseButton; +import javafx.scene.layout.*; +import javafx.scene.paint.Color; +import javafx.scene.transform.Rotate; +import javafx.scene.transform.Scale; +import javafx.util.Duration; +import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.util.javafx.MappedObservableList; + +public class TabHeader extends Control { + + public TabHeader(Tab... tabs) { + getStyleClass().setAll("tab-header"); + if (tabs != null) { + getTabs().addAll(tabs); + } + } + + private ObservableList tabs = FXCollections.observableArrayList(); + + public ObservableList getTabs() { + return tabs; + } + + private final ObjectProperty> selectionModel = new SimpleObjectProperty<>(this, "selectionModel", new TabHeaderSelectionModel(this)); + + public SingleSelectionModel getSelectionModel() { + return selectionModel.get(); + } + + public ObjectProperty> selectionModelProperty() { + return selectionModel; + } + + public void setSelectionModel(SingleSelectionModel selectionModel) { + this.selectionModel.set(selectionModel); + } + + static class TabHeaderSelectionModel extends SingleSelectionModel { + private final TabHeader tabHeader; + + public TabHeaderSelectionModel(final TabHeader t) { + if (t == null) { + throw new NullPointerException("TabPane can not be null"); + } + this.tabHeader = t; + + // watching for changes to the items list content + final ListChangeListener itemsContentObserver = c -> { + while (c.next()) { + for (Tab tab : c.getRemoved()) { + if (tab != null && !tabHeader.getTabs().contains(tab)) { + if (tab.isSelected()) { + tab.setSelected(false); + final int tabIndex = c.getFrom(); + + // we always try to select the nearest, non-disabled + // tab from the position of the closed tab. + findNearestAvailableTab(tabIndex, true); + } + } + } + if (c.wasAdded() || c.wasRemoved()) { + // The selected tab index can be out of sync with the list of tab if + // we add or remove tabs before the selected tab. + if (getSelectedIndex() != tabHeader.getTabs().indexOf(getSelectedItem())) { + clearAndSelect(tabHeader.getTabs().indexOf(getSelectedItem())); + } + } + } + if (getSelectedIndex() == -1 && getSelectedItem() == null && tabHeader.getTabs().size() > 0) { + // we go looking for the first non-disabled tab, as opposed to + // just selecting the first tab (fix for RT-36908) + findNearestAvailableTab(0, true); + } else if (tabHeader.getTabs().isEmpty()) { + clearSelection(); + } + }; + if (this.tabHeader.getTabs() != null) { + this.tabHeader.getTabs().addListener(itemsContentObserver); + } + } + + // API Implementation + @Override public void select(int index) { + if (index < 0 || (getItemCount() > 0 && index >= getItemCount()) || + (index == getSelectedIndex() && getModelItem(index).isSelected())) { + return; + } + + // Unselect the old tab + if (getSelectedIndex() >= 0 && getSelectedIndex() < tabHeader.getTabs().size()) { + tabHeader.getTabs().get(getSelectedIndex()).setSelected(false); + } + + setSelectedIndex(index); + + Tab tab = getModelItem(index); + if (tab != null) { + setSelectedItem(tab); + } + + // Select the new tab + if (getSelectedIndex() >= 0 && getSelectedIndex() < tabHeader.getTabs().size()) { + tabHeader.getTabs().get(getSelectedIndex()).setSelected(true); + } + + /* Does this get all the change events */ + tabHeader.notifyAccessibleAttributeChanged(AccessibleAttribute.FOCUS_ITEM); + } + + @Override public void select(Tab tab) { + final int itemCount = getItemCount(); + + for (int i = 0; i < itemCount; i++) { + final Tab value = getModelItem(i); + if (value != null && value.equals(tab)) { + select(i); + return; + } + } + if (tab != null) { + setSelectedItem(tab); + } + } + + @Override protected Tab getModelItem(int index) { + final ObservableList items = tabHeader.getTabs(); + if (items == null) return null; + if (index < 0 || index >= items.size()) return null; + return items.get(index); + } + + @Override protected int getItemCount() { + final ObservableList items = tabHeader.getTabs(); + return items == null ? 0 : items.size(); + } + + private Tab findNearestAvailableTab(int tabIndex, boolean doSelect) { + // we always try to select the nearest, non-disabled + // tab from the position of the closed tab. + final int tabCount = getItemCount(); + int i = 1; + Tab bestTab = null; + while (true) { + // look leftwards + int downPos = tabIndex - i; + if (downPos >= 0) { + Tab _tab = getModelItem(downPos); + if (_tab != null) { + bestTab = _tab; + break; + } + } + + // look rightwards. We subtract one as we need + // to take into account that a tab has been removed + // and if we don't do this we'll miss the tab + // to the right of the tab (as it has moved into + // the removed tabs position). + int upPos = tabIndex + i - 1; + if (upPos < tabCount) { + Tab _tab = getModelItem(upPos); + if (_tab != null) { + bestTab = _tab; + break; + } + } + + if (downPos < 0 && upPos >= tabCount) { + break; + } + i++; + } + + if (doSelect && bestTab != null) { + select(bestTab); + } + + return bestTab; + } + } + + @Override + protected Skin createDefaultSkin() { + return new TabHeaderSkin(this); + } + + public static class TabHeaderSkin extends SkinBase { + + private static final PseudoClass SELECTED_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("selected"); + + private final Color ripplerColor = Color.valueOf("#FFFF8D"); + + private final HeaderContainer header; + private boolean isSelectingTab = false; + private Tab selectedTab; + + protected TabHeaderSkin(TabHeader control) { + super(control); + + header = new HeaderContainer(); + getChildren().setAll(header); + + FXUtils.onChangeAndOperate(control.getSelectionModel().selectedItemProperty(), item -> { + isSelectingTab = true; + selectedTab = item; + Platform.runLater(() -> { + header.setNeedsLayout2(true); + header.layout(); + }); + }); + + this.selectedTab = control.getSelectionModel().getSelectedItem(); + if (this.selectedTab == null && control.getSelectionModel().getSelectedIndex() != -1) { + control.getSelectionModel().select(control.getSelectionModel().getSelectedIndex()); + this.selectedTab = control.getSelectionModel().getSelectedItem(); + } + + if (this.selectedTab == null) { + control.getSelectionModel().selectFirst(); + } + + this.selectedTab = control.getSelectionModel().getSelectedItem(); + } + + protected class HeaderContainer extends StackPane { + private Timeline timeline; + private StackPane selectedTabLine; + private StackPane headersRegion; + private Scale scale = new Scale(1, 1, 0, 0); + private Rotate rotate = new Rotate(0, 0, 1); + private double selectedTabLineOffset; + private ObservableList binding; + + public HeaderContainer() { + getStyleClass().add("tab-header-area"); + setPickOnBounds(false); + + headersRegion = new StackPane() { + @Override + protected double computePrefWidth(double height) { + double width = 0; + for (Node child : getChildren()) { + if (!(child instanceof TabHeaderContainer) || !child.isVisible()) continue; + width += child.prefWidth(height); + } + return snapSize(width) + snappedLeftInset() + snappedRightInset(); + } + + @Override + protected double computePrefHeight(double width) { + double height = 0; + for (Node child : getChildren()) { + if (!(child instanceof TabHeaderContainer) || !child.isVisible()) continue; + height = Math.max(height, child.prefHeight(width)); + } + return snapSize(height) + snappedTopInset() + snappedBottomInset(); + } + + @Override + protected void layoutChildren() { + if (isSelectingTab) { + animateSelectionLine(); + isSelectingTab = false; + } + + double headerHeight = snapSize(prefHeight(-1)); + double tabStartX = 0; + for (Node node : getChildren()) { + if (!(node instanceof TabHeaderContainer)) continue; + TabHeaderContainer child = (TabHeaderContainer) node; + double w = snapSize(child.prefWidth(-1)); + double h = snapSize(child.prefHeight(-1)); + child.resize(w, h); + + child.relocate(tabStartX, headerHeight - h - snappedBottomInset()); + tabStartX += w; + } + + selectedTabLine.resizeRelocate(0, + headerHeight - selectedTabLine.prefHeight(-1), + snapSize(selectedTabLine.prefWidth(-1)), + snapSize(selectedTabLine.prefHeight(-1))); + } + }; + + selectedTabLine = new StackPane(); + selectedTabLine.setManaged(false); + selectedTabLine.getTransforms().addAll(scale, rotate); + selectedTabLine.setCache(true); + selectedTabLine.getStyleClass().addAll("tab-selected-line"); + selectedTabLine.setPrefHeight(2); + selectedTabLine.setPrefWidth(1); + selectedTabLine.setBackground(new Background(new BackgroundFill(ripplerColor, CornerRadii.EMPTY, Insets.EMPTY))); + getChildren().setAll(headersRegion, selectedTabLine); + headersRegion.setPickOnBounds(false); + headersRegion.prefHeightProperty().bind(heightProperty()); + prefWidthProperty().bind(headersRegion.widthProperty()); + + Bindings.bindContent(headersRegion.getChildren(), binding = MappedObservableList.create(getSkinnable().getTabs(), tab -> { + TabHeaderContainer container = new TabHeaderContainer(tab); + container.setVisible(true); + return container; + })); + } + + public void setNeedsLayout2(boolean value) { + setNeedsLayout(value); + } + + private void runTimeline(double newTransX, double newWidth) { + double tempScaleX = 0.0D; + double tempWidth = 0.0D; + double lineWidth = this.selectedTabLine.prefWidth(-1.0D); + if (this.isAnimating()) { + this.timeline.stop(); + tempScaleX = this.scale.getX(); + if (this.rotate.getAngle() != 0.0D) { + this.rotate.setAngle(0.0D); + tempWidth = tempScaleX * lineWidth; + this.selectedTabLine.setTranslateX(this.selectedTabLine.getTranslateX() - tempWidth); + } + } + + double oldScaleX = this.scale.getX(); + double oldWidth = lineWidth * oldScaleX; + double oldTransX = this.selectedTabLine.getTranslateX(); + double newScaleX = newWidth * oldScaleX / oldWidth; + this.selectedTabLineOffset = newTransX; + // newTransX += offsetStart * (double)this.direction; + double transDiff = newTransX - oldTransX; + double midScaleX = tempScaleX != 0.0D ? tempScaleX : (Math.abs(transDiff) / 1.3D + oldWidth) * oldScaleX / oldWidth; + if (transDiff < 0.0D) { + this.selectedTabLine.setTranslateX(this.selectedTabLine.getTranslateX() + oldWidth); + newTransX += newWidth; + this.rotate.setAngle(180.0D); + } + + this.timeline = new Timeline(new KeyFrame(Duration.ZERO, new KeyValue(this.selectedTabLine.translateXProperty(), this.selectedTabLine.getTranslateX(), Interpolator.EASE_BOTH)), new KeyFrame(Duration.seconds(0.12D), new KeyValue(this.scale.xProperty(), midScaleX, Interpolator.EASE_BOTH), new KeyValue(this.selectedTabLine.translateXProperty(), this.selectedTabLine.getTranslateX(), Interpolator.EASE_BOTH)), new KeyFrame(Duration.seconds(0.24D), new KeyValue(this.scale.xProperty(), newScaleX, Interpolator.EASE_BOTH), new KeyValue(this.selectedTabLine.translateXProperty(), newTransX, Interpolator.EASE_BOTH))); + this.timeline.setOnFinished((finish) -> { + if (this.rotate.getAngle() != 0.0D) { + this.rotate.setAngle(0.0D); + this.selectedTabLine.setTranslateX(this.selectedTabLine.getTranslateX() - newWidth); + } + + }); + this.timeline.play(); + } + + private boolean isAnimating() { + return this.timeline != null && this.timeline.getStatus() == Animation.Status.RUNNING; + } + + @Override + protected void layoutChildren() { + super.layoutChildren(); + + if (isSelectingTab) { + animateSelectionLine(); + isSelectingTab = false; + } + } + + private void animateSelectionLine() { + double offset = 0.0D; + double selectedTabOffset = 0.0D; + double selectedTabWidth = 0.0D; + Side side = Side.TOP; + + for (Node node : headersRegion.getChildren()) { + if (node instanceof TabHeaderContainer) { + TabHeaderContainer tabHeader = (TabHeaderContainer)node; + double tabHeaderPrefWidth = this.snapSize(tabHeader.prefWidth(-1.0D)); + if (selectedTab != null && selectedTab.equals(tabHeader.tab)) { + selectedTabOffset = side != Side.LEFT && side != Side.BOTTOM ? offset : -offset - tabHeaderPrefWidth; + selectedTabWidth = tabHeaderPrefWidth; + break; + } + + offset += tabHeaderPrefWidth; + } + } + + this.runTimeline(selectedTabOffset, selectedTabWidth); + } + } + + protected class TabHeaderContainer extends StackPane { + + private final Tab tab; + private final Label tabText; + private final BorderPane inner; + private final JFXRippler rippler; + + public TabHeaderContainer(Tab tab) { + this.tab = tab; + + tabText = new Label(); + tabText.textProperty().bind(tab.textProperty()); + tabText.getStyleClass().add("tab-label"); + inner = new BorderPane(); + inner.setCenter(tabText); + inner.getStyleClass().add("tab-container"); + rippler = new JFXRippler(inner, JFXRippler.RipplerPos.FRONT); + rippler.setRipplerFill(ripplerColor); + getChildren().setAll(rippler); + + FXUtils.onChangeAndOperate(tab.selectedProperty(), selected -> inner.pseudoClassStateChanged(SELECTED_PSEUDOCLASS_STATE, selected)); + + this.setOnMouseClicked(event -> { + if (event.getButton() == MouseButton.PRIMARY) { + this.setOpacity(1); + getSkinnable().getSelectionModel().select(tab); + } + }); + } + } + } + + public static class Tab { + private final StringProperty id = new SimpleStringProperty(this, "id"); + private final StringProperty text = new SimpleStringProperty(this, "text"); + private final ReadOnlyBooleanWrapper selected = new ReadOnlyBooleanWrapper(this, "selected"); + + public Tab(String id) { + setId(id); + } + + public Tab(String id, String text) { + setId(id); + setText(text); + } + + public String getId() { + return id.get(); + } + + public StringProperty idProperty() { + return id; + } + + public void setId(String id) { + this.id.set(id); + } + + public String getText() { + return text.get(); + } + + public StringProperty textProperty() { + return text; + } + + public void setText(String text) { + this.text.set(text); + } + + public boolean isSelected() { + return selected.get(); + } + + public ReadOnlyBooleanProperty selectedProperty() { + return selected.getReadOnlyProperty(); + } + + private void setSelected(boolean selected) { + this.selected.set(selected); + } + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java index b7e83f65f..4a29b19b3 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java @@ -265,17 +265,22 @@ public class DecoratorController { decorator.canRefreshProperty().set(false); } + decorator.canCloseProperty().set(navigator.size() > 2); + if (to instanceof DecoratorPage) { decorator.showCloseAsHomeProperty().set(!((DecoratorPage) to).isPageCloseable()); - decorator.stateProperty().bind(((DecoratorPage) to).stateProperty()); } else { decorator.showCloseAsHomeProperty().set(true); + } + + // state property should be updated at last. + if (to instanceof DecoratorPage) { + decorator.stateProperty().bind(((DecoratorPage) to).stateProperty()); + } else { decorator.stateProperty().unbind(); decorator.stateProperty().set(new DecoratorPage.State("", null, navigator.canGoBack(), false, true)); } - decorator.canCloseProperty().set(navigator.canGoBack()); - if (to instanceof Region) { Region region = (Region) to; // Let root pane fix window size. 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 e4ea4c533..6cfc04909 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 @@ -213,22 +213,23 @@ public class DecoratorSkin extends SkinBase { } navBar.setCenter(center); - HBox navRight = new HBox(); - navRight.setAlignment(Pos.CENTER_RIGHT); - JFXButton refreshNavButton = new JFXButton(); - refreshNavButton.setGraphic(SVG.refresh(Theme.foregroundFillBinding(), -1, -1)); - refreshNavButton.getStyleClass().add("jfx-decorator-button"); - refreshNavButton.ripplerFillProperty().bind(Theme.whiteFillBinding()); - refreshNavButton.onActionProperty().bind(skinnable.onRefreshNavButtonActionProperty()); - refreshNavButton.visibleProperty().set(canRefresh); + if (canRefresh) { + HBox navRight = new HBox(); + navRight.setAlignment(Pos.CENTER_RIGHT); + JFXButton refreshNavButton = new JFXButton(); + refreshNavButton.setGraphic(SVG.refresh(Theme.foregroundFillBinding(), -1, -1)); + refreshNavButton.getStyleClass().add("jfx-decorator-button"); + refreshNavButton.ripplerFillProperty().bind(Theme.whiteFillBinding()); + refreshNavButton.onActionProperty().bind(skinnable.onRefreshNavButtonActionProperty()); - Rectangle separator = new Rectangle(); - separator.visibleProperty().bind(refreshNavButton.visibleProperty()); - separator.heightProperty().bind(navBar.heightProperty()); - separator.setFill(Color.GRAY); + Rectangle separator = new Rectangle(); + separator.visibleProperty().bind(refreshNavButton.visibleProperty()); + separator.heightProperty().bind(navBar.heightProperty()); + separator.setFill(Color.GRAY); - navRight.getChildren().setAll(refreshNavButton, separator); - navBar.setRight(navRight); + navRight.getChildren().setAll(refreshNavButton, separator); + navBar.setRight(navRight); + } } return navBar; } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPage.java index 62f9a1f90..6bea32ef9 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPage.java @@ -17,7 +17,6 @@ */ package org.jackhuang.hmcl.ui.versions; -import com.jfoenix.controls.JFXTabPane; import javafx.application.Platform; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; @@ -33,6 +32,7 @@ import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.ListPageBase; +import org.jackhuang.hmcl.ui.construct.TabHeader; import org.jackhuang.hmcl.util.Logging; import org.jackhuang.hmcl.util.io.FileUtils; @@ -53,11 +53,12 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n; public final class ModListPage extends ListPageBase { private final BooleanProperty modded = new SimpleBooleanProperty(this, "modded", false); - private JFXTabPane parentTab; + private TabHeader.Tab tab; private ModManager modManager; private LibraryAnalyzer libraryAnalyzer; - public ModListPage() { + public ModListPage(TabHeader.Tab tab) { + this.tab = tab; FXUtils.applyDragListener(this, it -> Arrays.asList("jar", "zip", "litemod").contains(FileUtils.getExtension(it)), mods -> { mods.forEach(it -> { @@ -101,10 +102,12 @@ public final class ModListPage extends ListPageBase { loadingProperty().set(false); if (exception == null) - FXUtils.onWeakChangeAndOperate(parentTab.getSelectionModel().selectedItemProperty(), newValue -> { - if (newValue != null && newValue.getUserData() == ModListPage.this) + getProperties().put(ModListPage.class, FXUtils.onWeakChangeAndOperate(tab.selectedProperty(), newValue -> { + if (newValue) itemsProperty().setAll(list.stream().map(ModListPageSkin.ModInfoObject::new).collect(Collectors.toList())); - }); + })); + else + getProperties().remove(ModListPage.class); }, Platform::runLater); } @@ -141,10 +144,6 @@ public final class ModListPage extends ListPageBase selectedItems) { try { modManager.removeMods(selectedItems.stream() 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 7fe49bdd8..8bfbd1696 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 @@ -18,114 +18,100 @@ package org.jackhuang.hmcl.ui.versions; import com.jfoenix.controls.JFXButton; +import com.jfoenix.controls.JFXListView; import com.jfoenix.controls.JFXPopup; -import com.jfoenix.controls.JFXTabPane; import javafx.application.Platform; -import javafx.beans.property.ReadOnlyBooleanProperty; -import javafx.beans.property.ReadOnlyBooleanWrapper; -import javafx.beans.property.ReadOnlyStringProperty; -import javafx.beans.property.ReadOnlyStringWrapper; -import javafx.fxml.FXML; -import javafx.scene.control.Tab; +import javafx.beans.property.*; +import javafx.geometry.Pos; +import javafx.scene.control.Control; +import javafx.scene.control.SelectionMode; +import javafx.scene.control.SkinBase; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; import javafx.scene.layout.StackPane; +import javafx.scene.paint.Color; +import javafx.scene.shape.Rectangle; +import org.jackhuang.hmcl.game.HMCLGameRepository; +import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.setting.Profile; +import org.jackhuang.hmcl.setting.Profiles; 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; -import org.jackhuang.hmcl.ui.construct.Navigator; -import org.jackhuang.hmcl.ui.construct.PageCloseEvent; -import org.jackhuang.hmcl.ui.construct.PopupMenu; +import org.jackhuang.hmcl.ui.animation.ContainerAnimations; +import org.jackhuang.hmcl.ui.animation.TransitionPane; +import org.jackhuang.hmcl.ui.construct.*; +import org.jackhuang.hmcl.ui.decorator.DecoratorPage; import org.jackhuang.hmcl.util.io.FileUtils; +import org.jackhuang.hmcl.util.versioning.VersionNumber; import java.io.File; +import java.util.Comparator; +import java.util.Date; +import java.util.List; import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; +import static org.jackhuang.hmcl.ui.FXUtils.runInFX; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; -public final class VersionPage extends StackPane { - private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper(this, "title", null); - private final ReadOnlyBooleanWrapper loading = new ReadOnlyBooleanWrapper(this, "loading", false); - - @FXML - private VersionSettingsPage versionSettings; - @FXML - private Tab modTab; - @FXML - private ModListPage mod; - @FXML - private InstallerListPage installer; - @FXML - private WorldListPage world; - @FXML - private JFXButton btnBrowseMenu; - @FXML - private JFXButton btnDelete; - @FXML - private JFXButton btnManagementMenu; - @FXML - private JFXButton btnExport; - @FXML - private JFXButton btnTestGame; - @FXML - private StackPane contentPane; - @FXML - private JFXTabPane tabPane; - - private final JFXPopup browsePopup; - private final JFXPopup managementPopup; +public class VersionPage extends Control implements DecoratorPage { + private final ReadOnlyObjectWrapper state = new ReadOnlyObjectWrapper<>(); + private final BooleanProperty loading = new SimpleBooleanProperty(); + private final JFXListView listView = new JFXListView<>(); + private final TabHeader.Tab versionSettingsTab = new TabHeader.Tab("versionSettingsTab"); + private final VersionSettingsPage versionSettingsPage = new VersionSettingsPage(); + private final TabHeader.Tab modListTab = new TabHeader.Tab("modListTab"); + private final ModListPage modListPage = new ModListPage(modListTab); + private final TabHeader.Tab installerListTab = new TabHeader.Tab("installerListTab"); + private final InstallerListPage installerListPage = new InstallerListPage(); + private final TabHeader.Tab worldListTab = new TabHeader.Tab("worldList"); + private final WorldListPage worldListPage = new WorldListPage(); + private final TransitionPane transitionPane = new TransitionPane(); + private final ObjectProperty selectedTab = new SimpleObjectProperty<>(); private Profile profile; private String version; { - FXUtils.loadFXML(this, "/assets/fxml/version/version.fxml"); + Profiles.registerVersionsListener(this::loadVersions); - PopupMenu browseList = new PopupMenu(); - browsePopup = new JFXPopup(browseList); - browseList.getContent().setAll( - new IconedMenuItem(null, i18n("folder.game"), FXUtils.withJFXPopupClosing(() -> onBrowse(""), browsePopup)), - new IconedMenuItem(null, i18n("folder.mod"), FXUtils.withJFXPopupClosing(() -> onBrowse("mods"), browsePopup)), - new IconedMenuItem(null, i18n("folder.config"), FXUtils.withJFXPopupClosing(() -> onBrowse("config"), browsePopup)), - new IconedMenuItem(null, i18n("folder.resourcepacks"), FXUtils.withJFXPopupClosing(() -> onBrowse("resourcepacks"), browsePopup)), - new IconedMenuItem(null, i18n("folder.screenshots"), FXUtils.withJFXPopupClosing(() -> onBrowse("screenshots"), browsePopup)), - new IconedMenuItem(null, i18n("folder.saves"), FXUtils.withJFXPopupClosing(() -> onBrowse("saves"), browsePopup)) - ); - - PopupMenu managementList = new PopupMenu(); - managementPopup = new JFXPopup(managementList); - managementList.getContent().setAll( - new IconedMenuItem(null, i18n("version.manage.redownload_assets_index"), FXUtils.withJFXPopupClosing(() -> Versions.updateGameAssets(profile, version), managementPopup)), - new IconedMenuItem(null, i18n("version.manage.remove_libraries"), FXUtils.withJFXPopupClosing(() -> FileUtils.deleteDirectoryQuietly(new File(profile.getRepository().getBaseDirectory(), "libraries")), managementPopup)), - new IconedMenuItem(null, i18n("version.manage.clean"), FXUtils.withJFXPopupClosing(() -> Versions.cleanVersion(profile, version), managementPopup)).addTooltip(i18n("version.manage.clean.tooltip")) - ); - - FXUtils.installFastTooltip(btnDelete, i18n("version.manage.remove")); - FXUtils.installFastTooltip(btnBrowseMenu, i18n("settings.game.exploration")); - FXUtils.installFastTooltip(btnManagementMenu, i18n("settings.game.management")); - FXUtils.installFastTooltip(btnExport, i18n("modpack.export")); - - btnTestGame.setGraphic(SVG.launch(Theme.whiteFillBinding(), 20, 20)); - FXUtils.installFastTooltip(btnTestGame, i18n("version.launch.test")); + listView.getSelectionModel().selectedItemProperty().addListener((a, b, newValue) -> { + loadVersion(newValue, profile); + }); addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onNavigated); } - public void load(String id, Profile profile) { - this.version = id; + private void loadVersions(Profile profile) { + HMCLGameRepository repository = profile.getRepository(); + List children = repository.getVersions().parallelStream() + .filter(version -> !version.isHidden()) + .sorted(Comparator.comparing((Version version) -> version.getReleaseTime() == null ? new Date(0L) : version.getReleaseTime()) + .thenComparing(a -> VersionNumber.asVersion(a.getId()))) + .map(Version::getId) + .collect(Collectors.toList()); + runInFX(() -> { + if (profile == Profiles.getSelectedProfile()) { + this.profile = profile; + loading.set(false); + listView.getItems().setAll(children); + } + }); + } + + public void loadVersion(String version, Profile profile) { + listView.getSelectionModel().select(version); + this.version = version; this.profile = profile; - title.set(i18n("version.manage.manage") + " - " + id); - - versionSettings.loadVersion(profile, id); - mod.setParentTab(tabPane); - modTab.setUserData(mod); + versionSettingsPage.loadVersion(profile, version); loading.set(true); CompletableFuture.allOf( - mod.loadVersion(profile, id), - installer.loadVersion(profile, id), - world.loadVersion(profile, id)) + modListPage.loadVersion(profile, version), + installerListPage.loadVersion(profile, version), + worldListPage.loadVersion(profile, version)) .whenCompleteAsync((result, exception) -> loading.set(false), Platform::runLater); } @@ -141,45 +127,164 @@ public final class VersionPage extends StackPane { return; } - load(this.version, this.profile); - } - - @FXML - private void onTestGame() { - Versions.testGame(profile, version); - } - - @FXML - private void onBrowseMenu() { - browsePopup.show(btnBrowseMenu, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 0, btnBrowseMenu.getHeight()); - } - - @FXML - private void onManagementMenu() { - managementPopup.show(btnManagementMenu, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 0, btnManagementMenu.getHeight()); + loadVersion(this.version, this.profile); } private void onBrowse(String sub) { FXUtils.openFolder(new File(profile.getRepository().getRunDirectory(version), sub)); } - public String getTitle() { - return title.get(); + private void redownloadAssetIndex() { + Versions.updateGameAssets(profile, version); } - public ReadOnlyStringProperty titleProperty() { - return title.getReadOnlyProperty(); + private void clearLibraries() { + FileUtils.deleteDirectoryQuietly(new File(profile.getRepository().getBaseDirectory(), "libraries")); } - public void setTitle(String title) { - this.title.set(title); + private void clearJunkFiles() { + Versions.cleanVersion(profile, version); } - public boolean isLoading() { - return loading.get(); + private void testGame() { + Versions.testGame(profile, version); } - public ReadOnlyBooleanProperty loadingProperty() { - return loading.getReadOnlyProperty(); + @Override + protected Skin createDefaultSkin() { + return new Skin(this); + } + + @Override + public ReadOnlyObjectProperty stateProperty() { + return state.getReadOnlyProperty(); + } + + public static class Skin extends SkinBase { + + /** + * Constructor for all SkinBase instances. + * + * @param control The control for which this Skin should attach to. + */ + protected Skin(VersionPage control) { + super(control); + + control.listView.getSelectionModel().setSelectionMode(SelectionMode.SINGLE); + + SpinnerPane spinnerPane = new SpinnerPane(); + spinnerPane.getStyleClass().add("large-spinner-pane"); + + // the root page, with the sidebar in left, navigator in center. + BorderPane root = new BorderPane(); + root.getStyleClass().add("gray-background"); + + { + BorderPane leftRootPane = new BorderPane(); + FXUtils.setLimitWidth(leftRootPane, 200); + + StackPane drawerContainer = new StackPane(); + drawerContainer.getChildren().setAll(control.listView); + leftRootPane.setCenter(drawerContainer); + + Rectangle separator = new Rectangle(); + separator.heightProperty().bind(root.heightProperty()); + separator.setWidth(1); + separator.setFill(Color.GRAY); + + leftRootPane.setRight(separator); + + root.setLeft(leftRootPane); + } + + TabHeader tabPane = new TabHeader(); + tabPane.setPickOnBounds(false); + tabPane.getStyleClass().add("jfx-decorator-tab"); + control.versionSettingsTab.setText(i18n("settings")); + control.modListTab.setText(i18n("mods")); + control.installerListTab.setText(i18n("settings.tabs.installers")); + control.worldListTab.setText(i18n("world")); + tabPane.getTabs().setAll( + control.versionSettingsTab, + control.modListTab, + control.installerListTab, + control.worldListTab); + control.selectedTab.bind(tabPane.getSelectionModel().selectedItemProperty()); + FXUtils.onChangeAndOperate(tabPane.getSelectionModel().selectedItemProperty(), newValue -> { + if (control.versionSettingsTab.equals(newValue)) { + control.transitionPane.setContent(control.versionSettingsPage, ContainerAnimations.FADE.getAnimationProducer()); + } else if (control.modListTab.equals(newValue)) { + control.transitionPane.setContent(control.modListPage, ContainerAnimations.FADE.getAnimationProducer()); + } else if (control.installerListTab.equals(newValue)) { + control.transitionPane.setContent(control.installerListPage, ContainerAnimations.FADE.getAnimationProducer()); + } else if (control.worldListTab.equals(newValue)) { + control.transitionPane.setContent(control.worldListPage, ContainerAnimations.FADE.getAnimationProducer()); + } + }); + + HBox toolBar = new HBox(); + toolBar.setAlignment(Pos.TOP_RIGHT); + toolBar.setPickOnBounds(false); + { + PopupMenu browseList = new PopupMenu(); + JFXPopup browsePopup = new JFXPopup(browseList); + browseList.getContent().setAll( + new IconedMenuItem(null, i18n("folder.game"), FXUtils.withJFXPopupClosing(() -> control.onBrowse(""), browsePopup)), + new IconedMenuItem(null, i18n("folder.mod"), FXUtils.withJFXPopupClosing(() -> control.onBrowse("mods"), browsePopup)), + new IconedMenuItem(null, i18n("folder.config"), FXUtils.withJFXPopupClosing(() -> control.onBrowse("config"), browsePopup)), + new IconedMenuItem(null, i18n("folder.resourcepacks"), FXUtils.withJFXPopupClosing(() -> control.onBrowse("resourcepacks"), browsePopup)), + new IconedMenuItem(null, i18n("folder.screenshots"), FXUtils.withJFXPopupClosing(() -> control.onBrowse("screenshots"), browsePopup)), + new IconedMenuItem(null, i18n("folder.saves"), FXUtils.withJFXPopupClosing(() -> control.onBrowse("saves"), browsePopup)) + ); + + PopupMenu managementList = new PopupMenu(); + JFXPopup managementPopup = new JFXPopup(managementList); + managementList.getContent().setAll( + new IconedMenuItem(null, i18n("version.manage.redownload_assets_index"), FXUtils.withJFXPopupClosing(control::redownloadAssetIndex, managementPopup)), + new IconedMenuItem(null, i18n("version.manage.remove_libraries"), FXUtils.withJFXPopupClosing(control::clearLibraries, managementPopup)), + new IconedMenuItem(null, i18n("version.manage.clean"), FXUtils.withJFXPopupClosing(control::clearJunkFiles, managementPopup)).addTooltip(i18n("version.manage.clean.tooltip")) + ); + + JFXButton testGameButton = new JFXButton(); + FXUtils.setLimitWidth(testGameButton, 40); + FXUtils.setLimitHeight(testGameButton, 40); + testGameButton.setGraphic(SVG.launch(Theme.whiteFillBinding(), 20, 20)); + testGameButton.getStyleClass().add("jfx-decorator-button"); + testGameButton.ripplerFillProperty().bind(Theme.whiteFillBinding()); + testGameButton.setOnAction(event -> control.testGame()); + FXUtils.installFastTooltip(testGameButton, i18n("version.launch.test")); + + JFXButton browseMenuButton = new JFXButton(); + FXUtils.setLimitWidth(browseMenuButton, 40); + FXUtils.setLimitHeight(browseMenuButton, 40); + browseMenuButton.setGraphic(SVG.folderOpen(Theme.whiteFillBinding(), 20, 20)); + browseMenuButton.getStyleClass().add("jfx-decorator-button"); + browseMenuButton.ripplerFillProperty().bind(Theme.whiteFillBinding()); + browseMenuButton.setOnAction(event -> browsePopup.show(browseMenuButton, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 0, browseMenuButton.getHeight())); + FXUtils.installFastTooltip(browseMenuButton, i18n("settings.game.exploration")); + + JFXButton managementMenuButton = new JFXButton(); + FXUtils.setLimitWidth(managementMenuButton, 40); + FXUtils.setLimitHeight(managementMenuButton, 40);; + managementMenuButton.setGraphic(SVG.wrench(Theme.whiteFillBinding(), 20, 20)); + managementMenuButton.getStyleClass().add("jfx-decorator-button"); + managementMenuButton.ripplerFillProperty().bind(Theme.whiteFillBinding()); + managementMenuButton.setOnAction(event -> managementPopup.show(managementMenuButton, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 0, managementMenuButton.getHeight())); + FXUtils.installFastTooltip(managementMenuButton, i18n("settings.game.management")); + + toolBar.getChildren().setAll(testGameButton, browseMenuButton, managementMenuButton); + } + + BorderPane titleBar = new BorderPane(); + titleBar.setLeft(tabPane); + titleBar.setRight(toolBar); + control.state.set(new State(i18n("version.manage.manage"), titleBar, true, false, true)); + + root.setCenter(control.transitionPane); + + spinnerPane.loadingProperty().bind(control.loading); + spinnerPane.setContent(root); + getChildren().setAll(spinnerPane); + } } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionRootPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionRootPage.java deleted file mode 100644 index b5c1b946f..000000000 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionRootPage.java +++ /dev/null @@ -1,145 +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.ui.versions; - -import com.jfoenix.controls.JFXListView; -import com.jfoenix.controls.JFXTabPane; -import javafx.beans.property.BooleanProperty; -import javafx.beans.property.ReadOnlyObjectProperty; -import javafx.beans.property.ReadOnlyObjectWrapper; -import javafx.beans.property.SimpleBooleanProperty; -import javafx.scene.control.Control; -import javafx.scene.control.SelectionMode; -import javafx.scene.control.SkinBase; -import javafx.scene.control.Tab; -import javafx.scene.layout.BorderPane; -import javafx.scene.layout.StackPane; -import javafx.scene.paint.Color; -import javafx.scene.shape.Rectangle; -import org.jackhuang.hmcl.game.HMCLGameRepository; -import org.jackhuang.hmcl.game.Version; -import org.jackhuang.hmcl.setting.Profile; -import org.jackhuang.hmcl.setting.Profiles; -import org.jackhuang.hmcl.ui.FXUtils; -import org.jackhuang.hmcl.ui.construct.SpinnerPane; -import org.jackhuang.hmcl.ui.construct.TabHeader; -import org.jackhuang.hmcl.ui.decorator.DecoratorPage; -import org.jackhuang.hmcl.util.versioning.VersionNumber; - -import java.util.Comparator; -import java.util.Date; -import java.util.List; -import java.util.stream.Collectors; - -import static org.jackhuang.hmcl.ui.FXUtils.runInFX; -import static org.jackhuang.hmcl.util.i18n.I18n.i18n; - -public class VersionRootPage extends Control implements DecoratorPage { - private final ReadOnlyObjectWrapper state = new ReadOnlyObjectWrapper<>(); - private final BooleanProperty loading = new SimpleBooleanProperty(); - private final JFXListView listView = new JFXListView<>(); - private final VersionPage versionPage = new VersionPage(); - private Profile profile; - - { - Profiles.registerVersionsListener(this::loadVersions); - - listView.getSelectionModel().selectedItemProperty().addListener((a, b, newValue) -> { - loadVersion(newValue, profile); - }); - } - - private void loadVersions(Profile profile) { - HMCLGameRepository repository = profile.getRepository(); - List children = repository.getVersions().parallelStream() - .filter(version -> !version.isHidden()) - .sorted(Comparator.comparing((Version version) -> version.getReleaseTime() == null ? new Date(0L) : version.getReleaseTime()) - .thenComparing(a -> VersionNumber.asVersion(a.getId()))) - .map(Version::getId) - .collect(Collectors.toList()); - runInFX(() -> { - if (profile == Profiles.getSelectedProfile()) { - this.profile = profile; - loading.set(false); - listView.getItems().setAll(children); - } - }); - } - - public void loadVersion(String version, Profile profile) { - listView.getSelectionModel().select(version); - versionPage.load(version, profile); - } - - @Override - protected Skin createDefaultSkin() { - return new Skin(this); - } - - @Override - public ReadOnlyObjectProperty stateProperty() { - return state.getReadOnlyProperty(); - } - - public static class Skin extends SkinBase { - - /** - * Constructor for all SkinBase instances. - * - * @param control The control for which this Skin should attach to. - */ - protected Skin(VersionRootPage control) { - super(control); - - control.listView.getSelectionModel().setSelectionMode(SelectionMode.SINGLE); - - SpinnerPane spinnerPane = new SpinnerPane(); - spinnerPane.getStyleClass().add("large-spinner-pane"); - - // the root page, with the sidebar in left, navigator in center. - BorderPane root = new BorderPane(); - root.getStyleClass().add("gray-background"); - - { - BorderPane leftRootPane = new BorderPane(); - FXUtils.setLimitWidth(leftRootPane, 200); - - StackPane drawerContainer = new StackPane(); - drawerContainer.getChildren().setAll(control.listView); - leftRootPane.setCenter(drawerContainer); - - Rectangle separator = new Rectangle(); - separator.heightProperty().bind(root.heightProperty()); - separator.setWidth(1); - separator.setFill(Color.GRAY); - - leftRootPane.setRight(separator); - - root.setLeft(leftRootPane); - } - - control.state.set(new State(i18n("version.manage.manage"), null, true, false, true)); - - root.setCenter(control.versionPage); - - spinnerPane.loadingProperty().bind(control.versionPage.loadingProperty()); - spinnerPane.setContent(root); - getChildren().setAll(spinnerPane); - } - } -} diff --git a/HMCL/src/main/resources/assets/css/root.css b/HMCL/src/main/resources/assets/css/root.css index e426f550a..e27fa60a6 100644 --- a/HMCL/src/main/resources/assets/css/root.css +++ b/HMCL/src/main/resources/assets/css/root.css @@ -32,12 +32,6 @@ -fx-arc-height: 5px; } -.list-view, -.scroll-pane, -.scroll-pane > .viewport { - -fx-background-color: transparent; -} - .disabled Label { -fx-text-fill: rgba(0, 0, 0, 0.5); } @@ -1044,6 +1038,11 @@ -fx-font-size: 14; } +.jfx-decorator-tab .tab-label { + -fx-text-fill: -fx-base-text-fill; + -fx-font-size: 14; +} + .resize-border { -fx-border-color: -fx-base-color; -fx-border-width: 0 2 2 2;