mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2025-09-17 15:57:18 -04:00
alt: version page look
This commit is contained in:
parent
fa13b1fc0d
commit
11d47bf9b1
@ -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;
|
||||
}
|
||||
|
||||
|
@ -178,4 +178,8 @@ public final class SVG {
|
||||
public static Node arrowRight(ObjectBinding<? extends Paint> 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<? extends Paint> 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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -0,0 +1,504 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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<Tab> tabs = FXCollections.observableArrayList();
|
||||
|
||||
public ObservableList<Tab> getTabs() {
|
||||
return tabs;
|
||||
}
|
||||
|
||||
private final ObjectProperty<SingleSelectionModel<Tab>> selectionModel = new SimpleObjectProperty<>(this, "selectionModel", new TabHeaderSelectionModel(this));
|
||||
|
||||
public SingleSelectionModel<Tab> getSelectionModel() {
|
||||
return selectionModel.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<SingleSelectionModel<Tab>> selectionModelProperty() {
|
||||
return selectionModel;
|
||||
}
|
||||
|
||||
public void setSelectionModel(SingleSelectionModel<Tab> selectionModel) {
|
||||
this.selectionModel.set(selectionModel);
|
||||
}
|
||||
|
||||
static class TabHeaderSelectionModel extends SingleSelectionModel<Tab> {
|
||||
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<Tab> 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<Tab> 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<Tab> 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<TabHeader> {
|
||||
|
||||
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<Node> 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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.
|
||||
|
@ -213,22 +213,23 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
@ -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<ModListPageSkin.ModInfoObject> {
|
||||
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<ModListPageSkin.ModInfoObjec
|
||||
}).whenCompleteAsync((list, exception) -> {
|
||||
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<ModListPageSkin.ModInfoObjec
|
||||
}).start();
|
||||
}
|
||||
|
||||
public void setParentTab(JFXTabPane parentTab) {
|
||||
this.parentTab = parentTab;
|
||||
}
|
||||
|
||||
public void removeSelected(ObservableList<ModListPageSkin.ModInfoObject> selectedItems) {
|
||||
try {
|
||||
modManager.removeMods(selectedItems.stream()
|
||||
|
@ -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> state = new ReadOnlyObjectWrapper<>();
|
||||
private final BooleanProperty loading = new SimpleBooleanProperty();
|
||||
private final JFXListView<String> 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<TabHeader.Tab> 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<String> 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<State> stateProperty() {
|
||||
return state.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
public static class Skin extends SkinBase<VersionPage> {
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,145 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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> state = new ReadOnlyObjectWrapper<>();
|
||||
private final BooleanProperty loading = new SimpleBooleanProperty();
|
||||
private final JFXListView<String> 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<String> 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<State> stateProperty() {
|
||||
return state.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
public static class Skin extends SkinBase<VersionRootPage> {
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user