Compare commits

...
13 Commits
67 changed files with 624 additions and 433 deletions

No files matched your search

@@ -17,19 +17,14 @@
*/
package org.jackhuang.hmcl.setting;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.value.ObservableObjectValue;
import org.jackhuang.hmcl.download.AdaptedDownloadProvider;
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider;
import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.download.MojangDownloadProvider;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.ui.FXUtils;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -40,6 +35,8 @@ import static org.jackhuang.hmcl.util.Pair.pair;
public final class DownloadProviders {
private DownloadProviders() {}
private static final AdaptedDownloadProvider DOWNLOAD_PROVIDER = new AdaptedDownloadProvider();
public static final Map<String, DownloadProvider> providersById = mapOf(
pair("mojang", new MojangDownloadProvider()),
pair("bmclapi", new BMCLAPIDownloadProvider("https://bmclapi2.bangbang93.com")),
@@ -47,32 +44,44 @@ public final class DownloadProviders {
public static final String DEFAULT_PROVIDER_ID = "mcbbs";
private static ObjectBinding<DownloadProvider> downloadProviderProperty;
static void init() {
downloadProviderProperty = Bindings.createObjectBinding(
() -> Optional.ofNullable(providersById.get(config().getDownloadType()))
.orElse(providersById.get(DEFAULT_PROVIDER_ID)),
config().downloadTypeProperty());
FXUtils.onChangeAndOperate(config().downloadTypeProperty(), downloadType -> {
DownloadProvider primary = Optional.ofNullable(providersById.get(config().getDownloadType()))
.orElse(providersById.get(DEFAULT_PROVIDER_ID));
DOWNLOAD_PROVIDER.setDownloadProviderCandidates(
Stream.concat(
Stream.of(primary),
providersById.values().stream().filter(x -> x != primary)
).collect(Collectors.toList())
);
});
}
public static String getPrimaryDownloadProviderId() {
String downloadType = config().getDownloadType();
if (providersById.containsKey(downloadType))
return downloadType;
else
return DEFAULT_PROVIDER_ID;
}
public static AdaptedDownloadProvider getDownloadProviderByPrimaryId(String primaryId) {
AdaptedDownloadProvider adaptedDownloadProvider = new AdaptedDownloadProvider();
DownloadProvider primary = Optional.ofNullable(providersById.get(primaryId))
.orElse(providersById.get(DEFAULT_PROVIDER_ID));
adaptedDownloadProvider.setDownloadProviderCandidates(
Stream.concat(
Stream.of(primary),
providersById.values().stream().filter(x -> x != primary)
).collect(Collectors.toList())
);
return adaptedDownloadProvider;
}
/**
* Get current primary preferred download provider
*/
public static DownloadProvider getDownloadProvider() {
return downloadProviderProperty.get();
}
/**
* Preferred download providers have the primary one first, the official one next.
* @return the preferred download providers
*/
public static List<DownloadProvider> getPreferredDownloadProviders() {
DownloadProvider provider = getDownloadProvider();
return Stream.concat(Stream.of(provider), providersById.values().stream().filter(x -> x != provider)).collect(Collectors.toList());
}
public static ObservableObjectValue<DownloadProvider> downloadProviderProperty() {
return downloadProviderProperty;
public static AdaptedDownloadProvider getDownloadProvider() {
return DOWNLOAD_PROVIDER;
}
}
@@ -24,6 +24,7 @@ import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.*;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.event.EventBus;
import org.jackhuang.hmcl.event.EventPriority;
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
@@ -162,7 +163,11 @@ public final class Profile implements Observable {
}
public DefaultDependencyManager getDependency() {
return new DefaultDependencyManager(repository, DownloadProviders.getDownloadProvider(), DownloadProviders.getPreferredDownloadProviders(), HMCLCacheRepository.REPOSITORY);
return getDependency(DownloadProviders.getDownloadProvider());
}
public DefaultDependencyManager getDependency(DownloadProvider downloadProvider) {
return new DefaultDependencyManager(repository, downloadProvider, HMCLCacheRepository.REPOSITORY);
}
public VersionSetting getVersionSetting(String id) {
@@ -110,7 +110,9 @@ public final class Controllers {
Task.runAsync(JavaVersion::initialize).start();
scene = new Scene(decorator.getDecorator(), 800, 480);
scene = new Scene(decorator.getDecorator(), 802, 482);
stage.setMinHeight(482);
stage.setMinWidth(802);
decorator.getDecorator().prefWidthProperty().bind(scene.widthProperty());
decorator.getDecorator().prefHeightProperty().bind(scene.heightProperty());
scene.getStylesheets().setAll(config().getTheme().getStylesheets());
@@ -18,7 +18,10 @@
package org.jackhuang.hmcl.ui;
import com.jfoenix.controls.*;
import javafx.animation.*;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.property.Property;
@@ -26,7 +29,6 @@ import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.beans.value.WeakChangeListener;
import javafx.beans.value.WritableValue;
import javafx.event.EventHandler;
import javafx.fxml.FXMLLoader;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
@@ -34,8 +36,6 @@ import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
@@ -43,7 +43,6 @@ import javafx.scene.shape.Rectangle;
import javafx.util.Callback;
import javafx.util.Duration;
import javafx.util.StringConverter;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.ResourceNotFoundError;
import org.jackhuang.hmcl.util.i18n.I18n;
@@ -61,12 +60,9 @@ import java.lang.reflect.Method;
import java.net.URI;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.stream.Collectors;
@@ -443,72 +439,6 @@ public final class FXUtils {
comboBox.getSelectionModel().selectedIndexProperty().removeListener(listener);
}
public static void smoothScrolling(ListView<?> listView) {
listView.skinProperty().addListener(o -> {
ScrollBar bar = (ScrollBar) listView.lookup(".scroll-bar");
Node virtualFlow = listView.lookup(".virtual-flow");
double[] frictions = new double[]{0.99, 0.1, 0.05, 0.04, 0.03, 0.02, 0.01, 0.04, 0.01, 0.008, 0.008, 0.008, 0.008, 0.0006, 0.0005, 0.00003, 0.00001};
double[] pushes = new double[]{1};
double[] derivatives = new double[frictions.length];
Timeline timeline = new Timeline();
bar.addEventHandler(MouseEvent.DRAG_DETECTED, e -> timeline.stop());
EventHandler<ScrollEvent> scrollEventHandler = event -> {
if (event.getEventType() == ScrollEvent.SCROLL) {
int direction = event.getDeltaY() > 0 ? -1 : 1;
for (int i = 0; i < pushes.length; ++i)
derivatives[i] += direction * pushes[i];
if (timeline.getStatus() == Animation.Status.STOPPED)
timeline.play();
event.consume();
}
};
bar.addEventHandler(ScrollEvent.ANY, scrollEventHandler);
virtualFlow.setOnScroll(scrollEventHandler);
timeline.getKeyFrames().add(new KeyFrame(Duration.millis(3), event -> {
for (int i = 0; i < derivatives.length; ++i)
derivatives[i] *= frictions[i];
for (int i = 1; i < derivatives.length; ++i)
derivatives[i] += derivatives[i - 1];
double dy = derivatives[derivatives.length - 1];
double height = listView.getLayoutBounds().getHeight();
bar.setValue(Lang.clamp(0, bar.getValue() + dy / height, 1));
if (Math.abs(dy) < 0.001)
timeline.stop();
listView.requestLayout();
}));
timeline.setCycleCount(Animation.INDEFINITE);
});
}
public static <T> T runInUIThread(Supplier<T> supplier) {
if (javafx.application.Platform.isFxApplicationThread()) {
return supplier.get();
} else {
CountDownLatch doneLatch = new CountDownLatch(1);
AtomicReference<T> reference = new AtomicReference<>();
Platform.runLater(() -> {
try {
reference.set(supplier.get());
} finally {
doneLatch.countDown();
}
});
try {
doneLatch.await();
} catch (InterruptedException var3) {
Thread.currentThread().interrupt();
}
return reference.get();
}
}
/**
* Suppress IllegalArgumentException since the url is supposed to be correct definitely.
* @param url the url of image. The image resource should be a file within the jar.
@@ -41,8 +41,7 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class InstallerItem extends BorderPane {
public InstallerItem(String libraryId, String libraryVersion, @Nullable Runnable upgrade, @Nullable Consumer<InstallerItem> deleteCallback) {
getStyleClass().add("two-line-list-item");
setStyle("-fx-background-radius: 2; -fx-background-color: white; -fx-padding: 8;");
getStyleClass().addAll("two-line-list-item", "card");
JFXDepthManager.setDepth(this, 1);
String[] urls = new String[]{"/assets/img/grass.png", "/assets/img/fabric.png", "/assets/img/forge.png", "/assets/img/chicken.png", "/assets/img/command.png"};
@@ -58,7 +57,7 @@ public class InstallerItem extends BorderPane {
if (libraryVersion == null) {
label.setText(i18n("install.installer.not_installed", i18n("install.installer." + libraryId)));
} else {
label.setText(i18n("install.installer.version", i18n("install.installer." + libraryId)) + ": " + libraryVersion);
label.setText(i18n("install.installer.version", i18n("install.installer." + libraryId), libraryVersion));
}
setCenter(label);
regularLibrary = true;
@@ -40,7 +40,6 @@ public class ListPageSkin extends SkinBase<ListPage<?>> {
spinnerPane.getStyleClass().add("large-spinner-pane");
Pane placeholder = new Pane();
VBox list = new VBox();
spinnerPane.getStyleClass().add("content-background");
StackPane contentPane = new StackPane();
{
@@ -37,7 +37,7 @@ public final class SVG {
SVGPath path = new SVGPath();
path.getStyleClass().add("svg");
path.setContent(d);
path.fillProperty().bind(fill);
if (fill != null) path.fillProperty().bind(fill);
if (width < 0 || height < 0) {
StackPane pane = new StackPane(path);
@@ -102,7 +102,7 @@ public class AccountListItemSkin extends SkinBase<AccountListItem> {
root.setRight(right);
root.getStyleClass().add("card");
root.setPadding(new Insets(8, 8, 8, 0));
root.setStyle("-fx-padding: 8 8 8 0;");
JFXDepthManager.setDepth(root, 1);
getChildren().setAll(root);
@@ -36,8 +36,7 @@ public abstract class FloatListCell<T> extends ListCell<T> {
pane.getStyleClass().add("card");
pane.setCursor(Cursor.HAND);
pane.setPadding(new Insets(8));
setPadding(new Insets(5));
setPadding(new Insets(9, 9, 0, 9));
JFXDepthManager.setDepth(pane, 1);
FXUtils.onChangeAndOperate(selectedProperty(), selected -> {
@@ -80,7 +80,6 @@ public class DecoratorController {
decorator.setOnCloseButtonAction(Launcher::stopApplication);
navigator = new Navigator();
navigator.setOnNavigating(this::onNavigating);
navigator.setOnNavigated(this::onNavigated);
navigator.init(mainPage);
@@ -30,6 +30,7 @@ import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.SkinBase;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
@@ -49,7 +50,7 @@ public class DecoratorSkin extends SkinBase<Decorator> {
private static final SVGGlyph minus = Lang.apply(new SVGGlyph(0, "MINUS", "M804.571 420.571v109.714q0 22.857-16 38.857t-38.857 16h-694.857q-22.857 0-38.857-16t-16-38.857v-109.714q0-22.857 16-38.857t38.857-16h694.857q22.857 0 38.857 16t16 38.857z", Color.WHITE),
glyph -> { glyph.setSize(12, 2); glyph.setTranslateY(4); });
private final BorderPane root;
private final StackPane parent;
private final StackPane titleContainer;
private final Stage primaryStage;
private final TransitionPane navBarPane;
@@ -72,14 +73,15 @@ public class DecoratorSkin extends SkinBase<Decorator> {
minus.fillProperty().bind(Theme.foregroundFillBinding());
Decorator skinnable = getSkinnable();
StackPane parent = new StackPane();
parent = new StackPane();
parent.getStyleClass().add("window");
parent.backgroundProperty().bind(skinnable.backgroundProperty());
parent.setPickOnBounds(false);
parent.prefHeightProperty().bind(control.prefHeightProperty());
parent.prefWidthProperty().bind(control.prefWidthProperty());
root = new BorderPane();
root.getStyleClass().addAll("jfx-decorator", "resize-border");
parent.setOnMouseReleased(this::onMouseReleased);
parent.setOnMouseDragged(this::onMouseDragged);
parent.setOnMouseMoved(this::onMouseMoved);
// animation layer at bottom
{
@@ -89,12 +91,17 @@ public class DecoratorSkin extends SkinBase<Decorator> {
leftPane.getStyleClass().add("jfx-decorator-drawer");
layer.getChildren().setAll(leftPane);
parent.getChildren().add(layer);
parent.getChildren().add(root);
}
StackPane wrapper = new StackPane();
BorderPane root = new BorderPane();
root.getStyleClass().addAll("jfx-decorator");
wrapper.getChildren().setAll(root);
skinnable.setDrawerWrapper(wrapper);
parent.getChildren().add(wrapper);
// center node with a animation layer at bottom, a container layer at middle and a "welcome" layer at top.
StackPane container = new StackPane();
skinnable.setDrawerWrapper(container);
FXUtils.setOverflowHidden(container);
// content layer at middle
@@ -128,11 +135,7 @@ public class DecoratorSkin extends SkinBase<Decorator> {
root.setCenter(container);
titleContainer = new StackPane();
root.setOnMouseReleased(this::onMouseReleased);
root.setOnMouseDragged(this::onMouseDragged);
root.setOnMouseMoved(this::onMouseMoved);
titleContainer.setPickOnBounds(false);
titleContainer.setMinHeight(40);
titleContainer.getStyleClass().addAll("jfx-tool-bar");
titleContainer.addEventHandler(MouseEvent.MOUSE_ENTERED, e -> allowMove = true);
titleContainer.addEventHandler(MouseEvent.MOUSE_EXITED, e -> {
@@ -151,6 +154,8 @@ public class DecoratorSkin extends SkinBase<Decorator> {
rectangle.widthProperty().bind(titleContainer.widthProperty());
rectangle.heightProperty().bind(titleContainer.heightProperty().add(100));
titleContainer.setClip(rectangle);
Rectangle buttonsContainerPlaceHolder = new Rectangle();
{
navBarPane = new TransitionPane();
FXUtils.onChangeAndOperate(skinnable.stateProperty(), s -> {
@@ -170,11 +175,14 @@ public class DecoratorSkin extends SkinBase<Decorator> {
s.isAnimate() ? Duration.millis(160) : null, leftPane.prefWidthProperty(), null, s.getLeftPaneWidth(), FXUtils.SINE);
});
titleBar.setCenter(navBarPane);
titleBar.setRight(buttonsContainerPlaceHolder);
}
root.setTop(titleContainer);
{
HBox buttonsContainer = new HBox();
buttonsContainer.setStyle("-fx-background-color: transparent;");
buttonsContainer.setAlignment(Pos.CENTER_RIGHT);
buttonsContainer.setPadding(new Insets(4));
buttonsContainer.setAlignment(Pos.TOP_RIGHT);
buttonsContainer.setMaxHeight(40);
{
JFXButton btnMin = new JFXButton();
StackPane pane = new StackPane(minus);
@@ -190,9 +198,14 @@ public class DecoratorSkin extends SkinBase<Decorator> {
buttonsContainer.getChildren().setAll(btnMin, btnClose);
}
titleBar.setRight(buttonsContainer);
AnchorPane layer = new AnchorPane();
layer.setPickOnBounds(false);
layer.getChildren().add(buttonsContainer);
AnchorPane.setTopAnchor(buttonsContainer, 0.0);
AnchorPane.setRightAnchor(buttonsContainer, 0.0);
buttonsContainerPlaceHolder.widthProperty().bind(buttonsContainer.widthProperty());
parent.getChildren().add(layer);
}
root.setTop(titleContainer);
getChildren().add(parent);
}
@@ -277,19 +290,19 @@ public class DecoratorSkin extends SkinBase<Decorator> {
}
private boolean isRightEdge(double x, double y, Bounds boundsInParent) {
return x < root.getWidth() && x > root.getWidth() - root.snappedLeftInset();
return x < parent.getWidth() && x >= parent.getWidth() - parent.snappedLeftInset();
}
private boolean isTopEdge(double x, double y, Bounds boundsInParent) {
return y >= 0 && y < root.snappedLeftInset();
return y >= 0 && y <= parent.snappedTopInset();
}
private boolean isBottomEdge(double x, double y, Bounds boundsInParent) {
return y < root.getHeight() && y > root.getHeight() - root.snappedLeftInset();
return y < parent.getHeight() && y >= parent.getHeight() - parent.snappedLeftInset();
}
private boolean isLeftEdge(double x, double y, Bounds boundsInParent) {
return x >= 0 && x < root.snappedLeftInset();
return x >= 0 && x <= parent.snappedLeftInset();
}
private boolean setStageWidth(double width) {
@@ -325,48 +338,48 @@ public class DecoratorSkin extends SkinBase<Decorator> {
updateInitMouseValues(mouseEvent);
if (primaryStage.isResizable()) {
double x = mouseEvent.getX(), y = mouseEvent.getY();
Bounds boundsInParent = root.getBoundsInParent();
if (root.getBorder() != null && root.getBorder().getStrokes().size() > 0) {
double diagonalSize = root.snappedLeftInset() + 10;
Bounds boundsInParent = parent.getBoundsInParent();
if (parent.getBorder() != null && parent.getBorder().getStrokes().size() > 0) {
double diagonalSize = parent.snappedLeftInset() + 10;
if (this.isRightEdge(x, y, boundsInParent)) {
if (y < diagonalSize) {
root.setCursor(Cursor.NE_RESIZE);
} else if (y > root.getHeight() - diagonalSize) {
root.setCursor(Cursor.SE_RESIZE);
parent.setCursor(Cursor.NE_RESIZE);
} else if (y > parent.getHeight() - diagonalSize) {
parent.setCursor(Cursor.SE_RESIZE);
} else {
root.setCursor(Cursor.E_RESIZE);
parent.setCursor(Cursor.E_RESIZE);
}
} else if (this.isLeftEdge(x, y, boundsInParent)) {
if (y < diagonalSize) {
root.setCursor(Cursor.NW_RESIZE);
} else if (y > root.getHeight() - diagonalSize) {
root.setCursor(Cursor.SW_RESIZE);
parent.setCursor(Cursor.NW_RESIZE);
} else if (y > parent.getHeight() - diagonalSize) {
parent.setCursor(Cursor.SW_RESIZE);
} else {
root.setCursor(Cursor.W_RESIZE);
parent.setCursor(Cursor.W_RESIZE);
}
} else if (this.isTopEdge(x, y, boundsInParent)) {
if (x < diagonalSize) {
root.setCursor(Cursor.NW_RESIZE);
} else if (x > root.getWidth() - diagonalSize) {
root.setCursor(Cursor.NE_RESIZE);
parent.setCursor(Cursor.NW_RESIZE);
} else if (x > parent.getWidth() - diagonalSize) {
parent.setCursor(Cursor.NE_RESIZE);
} else {
root.setCursor(Cursor.N_RESIZE);
parent.setCursor(Cursor.N_RESIZE);
}
} else if (this.isBottomEdge(x, y, boundsInParent)) {
if (x < diagonalSize) {
root.setCursor(Cursor.SW_RESIZE);
} else if (x > root.getWidth() - diagonalSize) {
root.setCursor(Cursor.SE_RESIZE);
parent.setCursor(Cursor.SW_RESIZE);
} else if (x > parent.getWidth() - diagonalSize) {
parent.setCursor(Cursor.SE_RESIZE);
} else {
root.setCursor(Cursor.S_RESIZE);
parent.setCursor(Cursor.S_RESIZE);
}
} else {
root.setCursor(Cursor.DEFAULT);
parent.setCursor(Cursor.DEFAULT);
}
}
}
} else {
root.setCursor(Cursor.DEFAULT);
parent.setCursor(Cursor.DEFAULT);
}
}
@@ -382,7 +395,7 @@ public class DecoratorSkin extends SkinBase<Decorator> {
this.newY = mouseEvent.getScreenY();
double deltaX = this.newX - this.initX;
double deltaY = this.newY - this.initY;
Cursor cursor = root.getCursor();
Cursor cursor = parent.getCursor();
if (Cursor.E_RESIZE == cursor) {
this.setStageWidth(this.primaryStage.getWidth() + deltaX);
mouseEvent.consume();
@@ -20,7 +20,6 @@ package org.jackhuang.hmcl.ui.download;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.download.LibraryAnalyzer;
import org.jackhuang.hmcl.download.RemoteVersion;
import org.jackhuang.hmcl.game.GameRepository;
@@ -40,7 +39,7 @@ class AdditionalInstallersPage extends InstallersPage {
protected final String gameVersion;
protected final Version version;
public AdditionalInstallersPage(String gameVersion, Version version, WizardController controller, GameRepository repository, DownloadProvider downloadProvider) {
public AdditionalInstallersPage(String gameVersion, Version version, WizardController controller, GameRepository repository, InstallerWizardDownloadProvider downloadProvider) {
super(controller, repository, gameVersion, downloadProvider);
this.gameVersion = gameVersion;
this.version = version;
@@ -108,7 +107,7 @@ class AdditionalInstallersPage extends InstallersPage {
libraries[i].removable.set(true);
compatible = false;
} else if (alreadyInstalled || getVersion(libraryId) != null) {
libraries[i].label.set(i18n("install.installer.version", i18n("install.installer." + libraryId)) + ": " + libraryVersion);
libraries[i].label.set(i18n("install.installer.version", i18n("install.installer." + libraryId), libraryVersion));
libraries[i].removable.set(true);
} else {
libraries[i].label.set(i18n("install.installer.not_installed", i18n("install.installer." + libraryId)));
@@ -0,0 +1,72 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2020 huangyuhui <[email protected]> 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.download;
import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.download.VersionList;
import java.net.URL;
import java.util.List;
public class InstallerWizardDownloadProvider implements DownloadProvider {
private DownloadProvider fallback;
public InstallerWizardDownloadProvider(DownloadProvider fallback) {
this.fallback = fallback;
}
public void setDownloadProvider(DownloadProvider downloadProvider) {
fallback = downloadProvider;
}
@Override
public String getVersionListURL() {
return fallback.getVersionListURL();
}
@Override
public String getAssetBaseURL() {
return fallback.getAssetBaseURL();
}
@Override
public List<URL> getAssetObjectCandidates(String assetObjectLocation) {
return fallback.injectURLWithCandidates(assetObjectLocation);
}
@Override
public String injectURL(String baseURL) {
return fallback.injectURL(baseURL);
}
@Override
public List<URL> injectURLWithCandidates(String baseURL) {
return fallback.injectURLWithCandidates(baseURL);
}
@Override
public VersionList<?> getVersionListById(String id) {
return fallback.getVersionListById(id);
}
@Override
public int getConcurrency() {
return fallback.getConcurrency();
}
}
@@ -37,7 +37,6 @@ import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.download.RemoteVersion;
import org.jackhuang.hmcl.game.GameRepository;
import org.jackhuang.hmcl.setting.Theme;
@@ -64,7 +63,7 @@ public class InstallersPage extends Control implements WizardPage {
protected JFXTextField txtName = new JFXTextField();
protected BooleanProperty installable = new SimpleBooleanProperty();
public InstallersPage(WizardController controller, GameRepository repository, String gameVersion, DownloadProvider downloadProvider) {
public InstallersPage(WizardController controller, GameRepository repository, String gameVersion, InstallerWizardDownloadProvider downloadProvider) {
this.controller = controller;
Validator hasVersion = new Validator(s -> !repository.hasVersion(s) && StringUtils.isNotBlank(s));
@@ -105,7 +104,7 @@ public class InstallersPage extends Control implements WizardPage {
for (InstallerPageItem library : libraries) {
String libraryId = library.id;
if (controller.getSettings().containsKey(libraryId)) {
library.label.set(i18n("install.installer.version", i18n("install.installer." + libraryId)) + ": " + getVersion(libraryId));
library.label.set(i18n("install.installer.version", i18n("install.installer." + libraryId), getVersion(libraryId)));
library.removable.set(true);
} else {
library.label.setValue(i18n("install.installer.not_installed", i18n("install.installer." + libraryId)));
@@ -153,7 +152,6 @@ public class InstallersPage extends Control implements WizardPage {
final Label label;
InstallersPageItemSkin(String imageUrl, InstallerPageItem item, boolean clickable) {
setPadding(new Insets(8));
getStyleClass().add("card");
setLeft(FXUtils.limitingSize(imageView = new ImageView(new Image(imageUrl, 32, 32, true, true)), 32, 32));
@@ -200,7 +198,7 @@ public class InstallersPage extends Control implements WizardPage {
HBox versionNamePane = new HBox(8);
versionNamePane.setAlignment(Pos.CENTER_LEFT);
versionNamePane.getStyleClass().add("card");
versionNamePane.setPadding(new Insets(20, 8, 20, 16));
versionNamePane.setStyle("-fx-padding: 20 8 20 16");
versionNamePane.getChildren().add(new Label(i18n("archive.name")));
@@ -18,12 +18,16 @@
package org.jackhuang.hmcl.ui.download;
import javafx.scene.Node;
import org.jackhuang.hmcl.download.*;
import org.jackhuang.hmcl.download.ArtifactMalformedException;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.download.RemoteVersion;
import org.jackhuang.hmcl.download.VersionMismatchException;
import org.jackhuang.hmcl.download.fabric.FabricInstallTask;
import org.jackhuang.hmcl.download.game.GameAssetIndexDownloadTask;
import org.jackhuang.hmcl.download.game.LibraryDownloadException;
import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.setting.DownloadProviders;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.task.DownloadException;
import org.jackhuang.hmcl.task.Task;
@@ -53,14 +57,16 @@ public final class UpdateInstallerWizardProvider implements WizardProvider {
private final Version version;
private final String libraryId;
private final String oldLibraryVersion;
private final InstallerWizardDownloadProvider downloadProvider;
public UpdateInstallerWizardProvider(@NotNull Profile profile, @NotNull String gameVersion, @NotNull Version version, @NotNull String libraryId, @Nullable String oldLibraryVersion) {
this.profile = profile;
this.dependencyManager = profile.getDependency();
this.gameVersion = gameVersion;
this.version = version;
this.libraryId = libraryId;
this.oldLibraryVersion = oldLibraryVersion;
this.downloadProvider = new InstallerWizardDownloadProvider(DownloadProviders.getDownloadProvider());
this.dependencyManager = profile.getDependency(downloadProvider);
}
@Override
@@ -95,15 +101,14 @@ public final class UpdateInstallerWizardProvider implements WizardProvider {
@Override
public Node createPage(WizardController controller, int step, Map<String, Object> settings) {
DownloadProvider provider = profile.getDependency().getPrimaryDownloadProvider();
switch (step) {
case 0:
return new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer." + libraryId)), gameVersion, provider, libraryId, () -> {
return new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer." + libraryId)), gameVersion, downloadProvider, libraryId, () -> {
if (oldLibraryVersion == null) {
controller.onFinish();
} else if ("game".equals(libraryId)) {
String newGameVersion = ((RemoteVersion) settings.get(libraryId)).getSelfVersion();
controller.onNext(new AdditionalInstallersPage(newGameVersion, version, controller, profile.getRepository(), provider));
controller.onNext(new AdditionalInstallersPage(newGameVersion, version, controller, profile.getRepository(), downloadProvider));
} else {
Controllers.confirm(i18n("install.change_version.confirm", i18n("install.installer." + libraryId), oldLibraryVersion, ((RemoteVersion) settings.get(libraryId)).getSelfVersion()),
i18n("install.change_version"), controller::onFinish, controller::onCancel);
@@ -18,9 +18,10 @@
package org.jackhuang.hmcl.ui.download;
import javafx.scene.Node;
import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.download.GameBuilder;
import org.jackhuang.hmcl.download.RemoteVersion;
import org.jackhuang.hmcl.setting.DownloadProviders;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
@@ -33,9 +34,13 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public final class VanillaInstallWizardProvider implements WizardProvider {
private final Profile profile;
private final DefaultDependencyManager dependencyManager;
private final InstallerWizardDownloadProvider downloadProvider;
public VanillaInstallWizardProvider(Profile profile) {
this.profile = profile;
this.downloadProvider = new InstallerWizardDownloadProvider(DownloadProviders.getDownloadProvider());
this.dependencyManager = profile.getDependency(downloadProvider);
}
@Override
@@ -44,7 +49,7 @@ public final class VanillaInstallWizardProvider implements WizardProvider {
}
private Task<Void> finishVersionDownloadingAsync(Map<String, Object> settings) {
GameBuilder builder = profile.getDependency().gameBuilder();
GameBuilder builder = dependencyManager.gameBuilder();
String name = (String) settings.get("name");
builder.name(name);
@@ -69,11 +74,10 @@ public final class VanillaInstallWizardProvider implements WizardProvider {
@Override
public Node createPage(WizardController controller, int step, Map<String, Object> settings) {
DownloadProvider provider = profile.getDependency().getPrimaryDownloadProvider();
switch (step) {
case 0:
return new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer.game")), "", provider, "game",
() -> controller.onNext(new InstallersPage(controller, profile.getRepository(), ((RemoteVersion) controller.getSettings().get("game")).getGameVersion(), provider)));
return new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer.game")), "", downloadProvider, "game",
() -> controller.onNext(new InstallersPage(controller, profile.getRepository(), ((RemoteVersion) controller.getSettings().get("game")).getGameVersion(), downloadProvider)));
default:
throw new IllegalStateException("error step " + step + ", settings: " + settings + ", pages: " + controller.getPages());
}
@@ -18,6 +18,7 @@
package org.jackhuang.hmcl.ui.download;
import com.jfoenix.controls.JFXCheckBox;
import com.jfoenix.controls.JFXComboBox;
import com.jfoenix.controls.JFXListView;
import com.jfoenix.controls.JFXSpinner;
import javafx.application.Platform;
@@ -31,7 +32,6 @@ import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.download.RemoteVersion;
import org.jackhuang.hmcl.download.VersionList;
import org.jackhuang.hmcl.download.fabric.FabricRemoteVersion;
@@ -39,6 +39,7 @@ import org.jackhuang.hmcl.download.forge.ForgeRemoteVersion;
import org.jackhuang.hmcl.download.game.GameRemoteVersion;
import org.jackhuang.hmcl.download.liteloader.LiteLoaderRemoteVersion;
import org.jackhuang.hmcl.download.optifine.OptiFineRemoteVersion;
import org.jackhuang.hmcl.setting.DownloadProviders;
import org.jackhuang.hmcl.task.TaskExecutor;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
@@ -54,6 +55,7 @@ import java.util.Map;
import java.util.logging.Level;
import java.util.stream.Collectors;
import static org.jackhuang.hmcl.ui.FXUtils.stringConverter;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
@@ -83,23 +85,34 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
private HBox checkPane;
@FXML
private VBox centrePane;
@FXML
private JFXComboBox<String> downloadSourceComboBox;
private final VersionList<?> versionList;
private VersionList<?> versionList;
private TaskExecutor executor;
public VersionsPage(WizardController controller, String title, String gameVersion, DownloadProvider downloadProvider, String libraryId, Runnable callback) {
public VersionsPage(WizardController controller, String title, String gameVersion, InstallerWizardDownloadProvider downloadProvider, String libraryId, Runnable callback) {
this.title = title;
this.gameVersion = gameVersion;
this.libraryId = libraryId;
this.controller = controller;
this.versionList = downloadProvider.getVersionListById(libraryId);
FXUtils.loadFXML(this, "/assets/fxml/download/versions.fxml");
if (versionList.hasType()) {
centrePane.getChildren().setAll(checkPane, list);
} else
centrePane.getChildren().setAll(list);
downloadSourceComboBox.getItems().setAll(DownloadProviders.providersById.keySet());
downloadSourceComboBox.setConverter(stringConverter(key -> i18n("download.provider." + key)));
downloadSourceComboBox.getSelectionModel().selectedItemProperty().addListener((a, b, newValue) -> {
controller.getSettings().put("downloadProvider", newValue);
downloadProvider.setDownloadProvider(DownloadProviders.getDownloadProviderByPrimaryId(newValue));
versionList = downloadProvider.getVersionListById(libraryId);
if (versionList.hasType()) {
centrePane.getChildren().setAll(checkPane, list);
} else {
centrePane.getChildren().setAll(list);
}
refresh();
});
downloadSourceComboBox.getSelectionModel().select((String)controller.getSettings().getOrDefault("downloadProvider", DownloadProviders.getPrimaryDownloadProviderId()));
InvalidationListener listener = o -> list.getItems().setAll(loadVersions());
chkRelease.selectedProperty().addListener(listener);
@@ -184,13 +197,15 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
@Override
public void refresh() {
VersionList<?> currentVersionList = versionList;
root.setContent(spinner, ContainerAnimations.FADE.getAnimationProducer());
executor = versionList.refreshAsync(gameVersion).whenComplete(exception -> {
executor = currentVersionList.refreshAsync(gameVersion).whenComplete(exception -> {
if (exception == null) {
List<RemoteVersion> items = loadVersions();
Platform.runLater(() -> {
if (versionList.getVersions(gameVersion).isEmpty()) {
if (versionList != currentVersionList) return;
if (currentVersionList.getVersions(gameVersion).isEmpty()) {
root.setContent(emptyPane, ContainerAnimations.FADE.getAnimationProducer());
} else {
if (items.isEmpty()) {
@@ -206,6 +221,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
} else {
LOG.log(Level.WARNING, "Failed to fetch versions list", exception);
Platform.runLater(() -> {
if (versionList != currentVersionList) return;
root.setContent(failedPane, ContainerAnimations.FADE.getAnimationProducer());
});
}
@@ -115,6 +115,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
}
StackPane launchPane = new StackPane();
launchPane.getStyleClass().add("launch-pane");
launchPane.setMaxWidth(230);
launchPane.setMaxHeight(55);
launchPane.setOnScroll(event -> {
@@ -134,8 +135,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
JFXButton launchButton = new JFXButton();
launchButton.setPrefWidth(230);
launchButton.setPrefHeight(55);
launchButton.setButtonType(JFXButton.ButtonType.RAISED);
launchButton.getStyleClass().add("jfx-button-raised");
//launchButton.setButtonType(JFXButton.ButtonType.RAISED);
launchButton.setOnAction(e -> launch());
launchButton.setDefaultButton(true);
launchButton.setClip(new Rectangle(-100, -100, 310, 200));
@@ -161,7 +161,6 @@ public final class MainPage extends StackPane implements DecoratorPage {
}
Rectangle separator = new Rectangle();
separator.getStyleClass().add("darker-fill");
separator.setWidth(1);
separator.setHeight(57);
separator.setTranslateX(95);
@@ -170,13 +169,12 @@ public final class MainPage extends StackPane implements DecoratorPage {
menuButton = new JFXButton();
menuButton.setPrefHeight(55);
menuButton.setPrefWidth(230);
menuButton.setButtonType(JFXButton.ButtonType.RAISED);
menuButton.getStyleClass().add("jfx-button-raised");
//menuButton.setButtonType(JFXButton.ButtonType.RAISED);
menuButton.setStyle("-fx-font-size: 15px;");
menuButton.setOnMouseClicked(e -> onMenu());
menuButton.setClip(new Rectangle(211, -100, 100, 200));
StackPane graphic = new StackPane();
Node svg = SVG.triangle(Theme.whiteFillBinding(), 10, 10);
Node svg = SVG.triangle(Theme.foregroundFillBinding(), 10, 10);
StackPane.setAlignment(svg, Pos.CENTER_RIGHT);
graphic.getChildren().setAll(svg);
graphic.setTranslateX(12);
@@ -18,11 +18,9 @@
package org.jackhuang.hmcl.ui.main;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.control.SkinBase;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.event.EventBus;
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
import org.jackhuang.hmcl.game.HMCLGameRepository;
@@ -182,6 +180,26 @@ public class RootPage extends DecoratorTabPage {
return profileListPage;
}
public Tab getMainTab() {
return mainTab;
}
public Tab getSettingsTab() {
return settingsTab;
}
public Tab getGameTab() {
return gameTab;
}
public Tab getAccountTab() {
return accountTab;
}
public Tab getProfileTab() {
return profileTab;
}
private static class Skin extends SkinBase<RootPage> {
protected Skin(RootPage control) {
@@ -247,9 +265,7 @@ public class RootPage extends DecoratorTabPage {
control.transitionPane.getStyleClass().add("jfx-decorator-content-container");
control.transitionPane.getChildren().setAll(getSkinnable().getMainPage());
FXUtils.setOverflowHidden(control.transitionPane, 8);
StackPane wrapper = new StackPane(control.transitionPane);
wrapper.setPadding(new Insets(4));
root.setCenter(wrapper);
root.setCenter(control.transitionPane);
}
getChildren().setAll(root);
@@ -34,7 +34,6 @@ import org.jackhuang.hmcl.setting.*;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
import org.jackhuang.hmcl.ui.construct.Navigator;
import org.jackhuang.hmcl.ui.construct.Validator;
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
import org.jackhuang.hmcl.upgrade.RemoteVersion;
@@ -63,7 +62,7 @@ import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.util.Lang.thread;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.reservedSelectedPropertyFor;
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.reversedSelectedPropertyFor;
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.selectedItemPropertyFor;
public final class SettingsPage extends SettingsView implements DecoratorPage {
@@ -73,7 +72,6 @@ public final class SettingsPage extends SettingsView implements DecoratorPage {
public SettingsPage() {
FXUtils.smoothScrolling(scroll);
addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onDecoratorPageNavigating);
// ==== Download sources ====
cboDownloadSource.getItems().setAll(DownloadProviders.providersById.keySet());
@@ -112,7 +110,7 @@ public final class SettingsPage extends SettingsView implements DecoratorPage {
proxyPane.disableProperty().bind(chkDisableProxy.selectedProperty());
authPane.disableProperty().bind(chkProxyAuthentication.selectedProperty().not());
reservedSelectedPropertyFor(chkDisableProxy).bindBidirectional(config().hasProxyProperty());
reversedSelectedPropertyFor(chkDisableProxy).bindBidirectional(config().hasProxyProperty());
chkProxyAuthentication.selectedProperty().bindBidirectional(config().hasProxyAuthProperty());
ToggleGroup proxyConfigurationGroup = new ToggleGroup();
@@ -75,7 +75,7 @@ public class ProfileListItemSkin extends SkinBase<ProfileListItem> {
root.setRight(right);
root.getStyleClass().add("card");
root.setPadding(new Insets(8, 8, 8, 0));
root.setStyle("-fx-padding: 8 8 8 0");
JFXDepthManager.setDepth(root, 1);
item.titleProperty().bind(skinnable.titleProperty());
item.subtitleProperty().bind(skinnable.subtitleProperty());
@@ -57,7 +57,6 @@ public class GameList extends ListPageBase<GameListItem> implements DecoratorPag
});
Profiles.registerVersionsListener(this::loadVersions);
addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onDecoratorPageNavigating);
}
private void loadVersions(Profile profile) {
@@ -99,7 +99,7 @@ public class GameListItemSkin extends SkinBase<GameListItem> {
root.setRight(right);
root.getStyleClass().add("card");
root.setPadding(new Insets(8, 8, 8, 0));
root.setStyle("-fx-padding: 8 8 8 0");
JFXDepthManager.setDepth(root, 1);
getChildren().setAll(root);
@@ -87,13 +87,12 @@ class ModListPageSkin extends SkinBase<ModListPage> {
BooleanProperty booleanProperty;
{
Region clippedContainer = (Region)listView.lookup(".clipped-container");
setPrefWidth(0);
HBox container = new HBox(8);
container.setPadding(new Insets(0, 0, 0, 6));
container.setAlignment(Pos.CENTER_LEFT);
pane.getChildren().add(container);
pane.setPadding(new Insets(8, 8, 8, 0));
if (clippedContainer != null) {
maxWidthProperty().bind(clippedContainer.widthProperty());
prefWidthProperty().bind(clippedContainer.widthProperty());
@@ -17,16 +17,17 @@
*/
package org.jackhuang.hmcl.ui.versions;
import com.jfoenix.controls.*;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXListCell;
import com.jfoenix.controls.JFXListView;
import com.jfoenix.controls.JFXPopup;
import javafx.application.Platform;
import javafx.beans.property.*;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.*;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.util.Callback;
import org.jackhuang.hmcl.game.HMCLGameRepository;
import org.jackhuang.hmcl.game.Version;
@@ -244,7 +245,6 @@ public class VersionPage extends Control implements DecoratorPage {
}, listViewItemPopup))
);
FXUtils.smoothScrolling(control.listView);
control.listView.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
control.listView.setCellFactory(new Callback<ListView<String>, ListCell<String>>() {
@Override
@@ -266,18 +266,8 @@ public class VersionPage extends Control implements DecoratorPage {
// the root page, with the sidebar in left, navigator in center.
BorderPane root = new BorderPane();
{
StackPane drawerContainer = new StackPane();
FXUtils.setLimitWidth(drawerContainer, 200);
drawerContainer.getStyleClass().add("gray-background");
drawerContainer.getChildren().setAll(control.listView);
FXUtils.setOverflowHidden(drawerContainer, 8);
StackPane wrapper = new StackPane(drawerContainer);
wrapper.setPadding(new Insets(4, 0, 4, 4));
root.setLeft(wrapper);
}
FXUtils.setLimitWidth(control.listView, 200);
root.setLeft(control.listView);
TabHeader tabPane = new TabHeader();
tabPane.setPickOnBounds(false);
@@ -328,9 +318,7 @@ public class VersionPage extends Control implements DecoratorPage {
);
JFXButton upgradeButton = new JFXButton();
FXUtils.setLimitWidth(upgradeButton, 40);
FXUtils.setLimitHeight(upgradeButton, 40);
upgradeButton.setGraphic(SVG.update(Theme.whiteFillBinding(), 20, 20));
upgradeButton.setGraphic(SVG.update(null, 20, 20));
upgradeButton.getStyleClass().add("jfx-decorator-button");
upgradeButton.ripplerFillProperty().bind(Theme.whiteFillBinding());
upgradeButton.setOnAction(event -> control.updateGame());
@@ -338,18 +326,14 @@ public class VersionPage extends Control implements DecoratorPage {
FXUtils.installFastTooltip(upgradeButton, i18n("version.update"));
JFXButton testGameButton = new JFXButton();
FXUtils.setLimitWidth(testGameButton, 40);
FXUtils.setLimitHeight(testGameButton, 40);
testGameButton.setGraphic(SVG.launch(Theme.whiteFillBinding(), 20, 20));
testGameButton.setGraphic(SVG.launch(null, 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.setGraphic(SVG.folderOpen(null, 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()));
@@ -357,8 +341,8 @@ public class VersionPage extends Control implements DecoratorPage {
JFXButton managementMenuButton = new JFXButton();
FXUtils.setLimitWidth(managementMenuButton, 40);
FXUtils.setLimitHeight(managementMenuButton, 40);;
managementMenuButton.setGraphic(SVG.wrench(Theme.whiteFillBinding(), 20, 20));
FXUtils.setLimitHeight(managementMenuButton, 40);
managementMenuButton.setGraphic(SVG.wrench(null, 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()));
@@ -370,13 +354,11 @@ public class VersionPage extends Control implements DecoratorPage {
BorderPane titleBar = new BorderPane();
titleBar.setLeft(tabPane);
titleBar.setRight(toolBar);
control.state.set(new State(i18n("version.manage.manage"), titleBar, true, false, true));
control.state.set(new State(i18n("version.manage.manage"), titleBar, true, false, true, false, 200));
control.transitionPane.getStyleClass().add("gray-background");
FXUtils.setOverflowHidden(control.transitionPane, 8);
StackPane wrapper = new StackPane(control.transitionPane);
wrapper.setPadding(new Insets(4));
root.setCenter(wrapper);
//control.transitionPane.getStyleClass().add("gray-background");
//FXUtils.setOverflowHidden(control.transitionPane, 8);
root.setCenter(control.transitionPane);
spinnerPane.loadingProperty().bind(control.loading);
spinnerPane.setContent(root);
@@ -19,16 +19,17 @@ package org.jackhuang.hmcl.ui.versions;
import javafx.stage.FileChooser;
import org.jackhuang.hmcl.download.game.GameAssetDownloadTask;
import org.jackhuang.hmcl.game.GameDirectoryType;
import org.jackhuang.hmcl.game.GameRepository;
import org.jackhuang.hmcl.game.LauncherHelper;
import org.jackhuang.hmcl.setting.Accounts;
import org.jackhuang.hmcl.game.GameDirectoryType;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskExecutor;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
import org.jackhuang.hmcl.ui.construct.PromptDialogPane;
import org.jackhuang.hmcl.ui.construct.Validator;
import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider;
@@ -150,7 +151,9 @@ public class Versions {
if (Accounts.getSelectedAccount() == null)
Controllers.getRootPage().checkAccount();
else if (id == null || !profile.getRepository().isLoaded() || !profile.getRepository().hasVersion(id))
Controllers.dialog(i18n("version.empty.launch"));
Controllers.dialog(i18n("version.empty.launch"), i18n("launch.failed"), MessageDialogPane.MessageType.ERROR, () -> {
Controllers.getRootPage().getSelectionModel().select(Controllers.getRootPage().getGameTab());
});
else
return true;
return false;
@@ -85,7 +85,7 @@ public class WorldListItemSkin extends SkinBase<WorldListItem> {
root.setRight(right);
root.getStyleClass().add("card");
root.setPadding(new Insets(8, 8, 8, 0));
root.setStyle("-fx-padding: 8 8 8 0");
JFXDepthManager.setDepth(root, 1);
getChildren().setAll(root);
+62 -43
View File
@@ -18,22 +18,6 @@
.root {
}
.jfx-list-view .scroll-bar {
-fx-skin: "org.jackhuang.hmcl.ui.construct.FloatScrollBarSkin";
}
.jfx-list-view .scroll-bar .track {
-fx-fill: transparent;
}
.scroll-pane .scroll-bar {
-fx-skin: "org.jackhuang.hmcl.ui.construct.FloatScrollBarSkin";
}
.scroll-pane .scroll-bar .track {
-fx-fill: transparent;
}
.scroll-bar .thumb {
-fx-fill: rgba(255, 255, 255, 0.5);
-fx-arc-width: 5px;
@@ -44,13 +28,6 @@
-fx-text-fill: rgba(0, 0, 0, 0.5);
}
.side-menu {
-fx-padding: 20.0, 10.0;
-fx-font-size: 15.0;
-fx-font-weight: NORMAL;
-fx-text-fill: #444;
}
.title-label {
-fx-font-size: 16.0px;
-fx-padding: 14.0px;
@@ -153,10 +130,6 @@
-fx-text-fill: white;
}
.darker-fill {
-fx-fill: -fx-base-darker-color;
}
.sponsor-pane {
-fx-padding: 16;
-fx-cursor: hand;
@@ -166,6 +139,32 @@
-fx-wrap-text: true;
}
/*******************************************************************************
* *
* Main Page *
* *
******************************************************************************/
.launch-pane .jfx-button {
-fx-background-color: -fx-base-color;
-fx-cursor: hand;
}
.launch-pane .jfx-button .jfx-rippler {
-jfx-rippler-fill: white;
-jfx-mask-type: CIRCLE;
-fx-padding: 0.0;
}
.launch-pane .jfx-button, .jfx-button * {
-fx-text-fill: -fx-base-text-fill;
-fx-font-size: 14px;
}
.launch-pane Rectangle {
-fx-fill: -fx-base-darker-color;
}
/*******************************************************************************
* *
* JFX Tab Pane *
@@ -370,7 +369,9 @@
-fx-font-size: 13.0;
-fx-font-weight: BOLD;
-fx-pref-width: 100.0%;
-fx-pref-height: 32.0px;
-fx-min-height: 40px;
-fx-max-height: 40px;
-fx-pref-height: 40px;
}
.jfx-tool-bar .background {
@@ -383,9 +384,19 @@
}
.jfx-tool-bar .jfx-decorator-button {
-fx-min-width: 40px;
-fx-max-width: 40px;
-fx-pref-width: 40px;
-fx-min-height: 40px;
-fx-max-height: 40px;
-fx-pref-height: 40px;
-fx-cursor: hand;
}
.jfx-tool-bar .jfx-decorator-button .svg {
-fx-fill: -fx-base-text-fill;
}
.jfx-tool-bar Label {
-fx-text-fill: WHITE;
}
@@ -436,9 +447,9 @@
}
.jfx-decorator-button {
-fx-max-width: 35px;
-fx-max-width: 40px;
-fx-background-radius: 5px;
-fx-max-height: 35px;
-fx-max-height: 40px;
-fx-background-color: transparent;
-jfx-toggle-color: rgba(128, 128, 255, 0.2);
-jfx-untoggle-color: transparent;
@@ -671,8 +682,15 @@
-fx-background-color: transparent;
}
.jfx-list-view .scroll-bar {
-fx-skin: "org.jackhuang.hmcl.ui.construct.FloatScrollBarSkin";
}
.jfx-list-view .scroll-bar .track {
-fx-fill: transparent;
}
.jfx-list-view-float {
-fx-padding: 5;
-fx-background-insets: 0.0;
-jfx-cell-horizontal-margin: 0.0;
-jfx-cell-vertical-margin: 5.0;
@@ -689,6 +707,7 @@
.card {
-fx-background-color: WHITE;
-fx-background-radius: 4;
-fx-padding: 8px;
}
.card:selected {
@@ -1092,12 +1111,8 @@
-fx-decorator-color: -fx-base-color;
}
.jfx-decorator .jfx-decorator-buttons-container {
-fx-background-color: -fx-decorator-color;
}
.jfx-decorator-drawer {
-fx-background-color: rgba(244, 244, 244, 0.55);
-fx-background-color: rgba(244, 244, 244, 0.5);
}
.jfx-decorator-title {
@@ -1118,9 +1133,9 @@
-fx-text-fill: -fx-base-text-fill;
}
.resize-border {
-fx-border-color: -fx-base-color;
-fx-border-width: 2;
.window {
-fx-border-color: black;
-fx-border-width: 1;
}
.debug-border {
@@ -1132,10 +1147,6 @@
-fx-background-color: rgba(244, 244, 244, 0.5);
}
.drawer-background {
-fx-background-color: #FAFAFA;
}
.gray-background {
-fx-background-color: rgba(244, 244, 244, 0.5);
}
@@ -1325,6 +1336,14 @@
-fx-background-color: null;
}
.scroll-pane .scroll-bar {
-fx-skin: "org.jackhuang.hmcl.ui.construct.FloatScrollBarSkin";
}
.scroll-pane .scroll-bar .track {
-fx-fill: transparent;
}
/*******************************************************************************
* *
* Error Facade *
@@ -13,7 +13,7 @@
</fx:define>
<VBox fx:id="list" styleClass="jfx-list-view" maxHeight="150" spacing="8" maxWidth="400">
<Label padding="${insets}" text="%install.modpack" />
<JFXButton fx:id="btnLocal" styleClass="card" style="-fx-padding: 8; -fx-cursor: HAND" prefWidth="${list.width}" onMouseClicked="#onChooseLocalFile">
<JFXButton fx:id="btnLocal" styleClass="card" style="-fx-cursor: HAND" prefWidth="${list.width}" onMouseClicked="#onChooseLocalFile">
<graphic>
<BorderPane mouseTransparent="true">
<left>
@@ -25,7 +25,7 @@
</BorderPane>
</graphic>
</JFXButton>
<JFXButton fx:id="btnRemote" styleClass="card" style="-fx-padding: 8; -fx-cursor: HAND" prefWidth="${list.width}" onMouseClicked="#onChooseRemoteFile">
<JFXButton fx:id="btnRemote" styleClass="card" style="-fx-cursor: HAND" prefWidth="${list.width}" onMouseClicked="#onChooseRemoteFile">
<graphic>
<BorderPane mouseTransparent="true">
<left>
@@ -9,9 +9,20 @@
type="BorderPane"
prefHeight="400.0" prefWidth="600.0">
<top>
<StackPane styleClass="sponsor-pane" onMouseClicked="#onSponsor">
<Label text="%sponsor.bmclapi" />
</StackPane>
<VBox>
<StackPane styleClass="sponsor-pane" onMouseClicked="#onSponsor">
<Label text="%sponsor.bmclapi" />
</StackPane>
<BorderPane style="-fx-padding: 0 16 16 16;">
<left>
<Label text="%settings.launcher.download_source" BorderPane.alignment="CENTER_LEFT" />
</left>
<right>
<JFXComboBox fx:id="downloadSourceComboBox">
</JFXComboBox>
</right>
</BorderPane>
</VBox>
</top>
<center>
<TransitionPane fx:id="root">
@@ -151,9 +151,9 @@ install.installer.install_offline.tooltip=Supports import of Forge/OptiFine inst
install.installer.install_online=Install Online
install.installer.install_online.tooltip=Support installation of Fabric, Forge, OptiFine, LiteLoader.
install.installer.liteloader=LiteLoader
install.installer.not_installed=%s not Installed
install.installer.not_installed=%s Version: not installed
install.installer.optifine=OptiFine
install.installer.version=%s Version
install.installer.version=%s Version: %s
install.modpack=Install a modpack
install.new_game=Install a New Game
install.new_game.already_exists=This version already exists.
@@ -149,7 +149,7 @@ install.installer.install_online.tooltip=Soporta instalación de Fabric, Forge,
install.installer.liteloader=LiteLoader
install.installer.not_installed=%s no está instalado
install.installer.optifine=OptiFine
install.installer.version=Versión %s
install.installer.version=Versión %s: %s
install.modpack=Instalar un modpack
install.new_game=Instalar un juego nuevo
install.new_game.already_exists=Esta versión ya existe.
@@ -153,7 +153,7 @@ install.installer.install_online.tooltip=Поддерживается устан
install.installer.liteloader=LiteLoader
install.installer.not_installed=%s не установлен
install.installer.optifine=OptiFine
install.installer.version=%s - версия
install.installer.version=%s - версия: %s
install.modpack=Установить модпак
install.new_game=Установите новую игру
install.new_game.already_exists=Эта версия уже существует.
@@ -150,9 +150,9 @@ install.installer.install_offline.tooltip=支援匯入已經下載好的 Forge/O
install.installer.install_online=線上安裝
install.installer.install_online.tooltip=支援安裝 Fabric、Forge、OptiFine、LiteLoader
install.installer.liteloader=LiteLoader
install.installer.not_installed=暫時不安裝 %s,可以點選此處安裝
install.installer.not_installed=%s 版本: 不安裝
install.installer.optifine=OptiFine
install.installer.version=%s 版本
install.installer.version=%s 版本: %s
install.modpack=安裝整合包
install.new_game=安裝新遊戲版本
install.new_game.already_exists=此版本已經存在,請重新命名
@@ -326,7 +326,7 @@ world.time=yyyy年MM月dd日 HH:mm:ss
profile=遊戲目錄
profile.already_exists=該名稱已存在
profile.default=目前目錄
profile.home=官方啟動器
profile.home=官方啟動器目錄
profile.instance_directory=遊戲路徑
profile.instance_directory.choose=選擇遊戲路徑
profile.manage=遊戲目錄列表
@@ -150,9 +150,9 @@ install.installer.install_offline.tooltip=支持导入已经下载好的 Forge/O
install.installer.install_online=在线安装
install.installer.install_online.tooltip=支持安装 Fabric、Forge、OptiFine、LiteLoader
install.installer.liteloader=LiteLoader
install.installer.not_installed=暂不安装 %s,可以点击此处安装
install.installer.not_installed=%s 版本: 不安装
install.installer.optifine=OptiFine
install.installer.version=%s 版本
install.installer.version=%s 版本: %s
install.modpack=安装整合包
install.new_game=安装新游戏版本
install.new_game.already_exists=此版本已经存在,请换一个名字
@@ -326,7 +326,7 @@ world.time=yyyy 年 MM 月 dd 日 HH:mm:ss
profile=游戏目录
profile.already_exists=该名称已存在
profile.default=当前目录
profile.home=官方启动器
profile.home=官方启动器目录
profile.instance_directory=游戏路径
profile.instance_directory.choose=选择游戏路径
profile.manage=游戏目录列表
@@ -17,23 +17,19 @@
*/
package org.jackhuang.hmcl.download;
import java.util.List;
/**
*
* @author huangyuhui
*/
public abstract class AbstractDependencyManager implements DependencyManager {
public abstract DownloadProvider getPrimaryDownloadProvider();
public abstract List<DownloadProvider> getPreferredDownloadProviders();
public abstract DownloadProvider getDownloadProvider();
@Override
public abstract DefaultCacheRepository getCacheRepository();
@Override
public VersionList<?> getVersionList(String id) {
return getPrimaryDownloadProvider().getVersionListById(id);
return getDownloadProvider().getVersionListById(id);
}
}
@@ -0,0 +1,81 @@
/*
* 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.download;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
/**
* The download provider that changes the real download source in need.
*
* @author huangyuhui
*/
public class AdaptedDownloadProvider implements DownloadProvider {
private List<DownloadProvider> downloadProviderCandidates;
public void setDownloadProviderCandidates(List<DownloadProvider> downloadProviderCandidates) {
this.downloadProviderCandidates = new ArrayList<>(downloadProviderCandidates);
}
public DownloadProvider getPreferredDownloadProvider() {
List<DownloadProvider> d = downloadProviderCandidates;
if (d == null || d.isEmpty()) {
throw new IllegalStateException("No download provider candidate");
}
return d.get(0);
}
@Override
public String getVersionListURL() {
return getPreferredDownloadProvider().getVersionListURL();
}
@Override
public String getAssetBaseURL() {
return getPreferredDownloadProvider().getAssetBaseURL();
}
@Override
public String injectURL(String baseURL) {
return getPreferredDownloadProvider().injectURL(baseURL);
}
@Override
public List<URL> injectURLWithCandidates(String baseURL) {
List<DownloadProvider> d = downloadProviderCandidates;
List<URL> results = new ArrayList<>(d.size());
for (int i = 0; i < d.size(); i++) {
results.add(NetworkUtils.toURL(d.get(i).injectURL(baseURL)));
}
return results;
}
@Override
public VersionList<?> getVersionListById(String id) {
return getPreferredDownloadProvider().getVersionListById(id);
}
@Override
public int getConcurrency() {
return getPreferredDownloadProvider().getConcurrency();
}
}
@@ -28,7 +28,6 @@ import org.jackhuang.hmcl.task.Task;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
/**
* Note: This class has no state.
@@ -39,13 +38,11 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
private final DefaultGameRepository repository;
private final DownloadProvider downloadProvider;
private final List<DownloadProvider> preferredDownloadProviders;
private final DefaultCacheRepository cacheRepository;
public DefaultDependencyManager(DefaultGameRepository repository, DownloadProvider downloadProvider, List<DownloadProvider> preferredDownloadProviders, DefaultCacheRepository cacheRepository) {
public DefaultDependencyManager(DefaultGameRepository repository, DownloadProvider downloadProvider, DefaultCacheRepository cacheRepository) {
this.repository = repository;
this.downloadProvider = downloadProvider;
this.preferredDownloadProviders = preferredDownloadProviders;
this.cacheRepository = cacheRepository;
}
@@ -55,15 +52,10 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
}
@Override
public DownloadProvider getPrimaryDownloadProvider() {
public DownloadProvider getDownloadProvider() {
return downloadProvider;
}
@Override
public List<DownloadProvider> getPreferredDownloadProviders() {
return preferredDownloadProviders;
}
@Override
public DefaultCacheRepository getCacheRepository() {
return cacheRepository;
@@ -32,21 +32,15 @@ import java.util.Map;
public class DefaultGameBuilder extends GameBuilder {
private final DefaultDependencyManager dependencyManager;
private final DownloadProvider downloadProvider;
public DefaultGameBuilder(DefaultDependencyManager dependencyManager) {
this.dependencyManager = dependencyManager;
this.downloadProvider = dependencyManager.getPrimaryDownloadProvider();
}
public DefaultDependencyManager getDependencyManager() {
return dependencyManager;
}
public DownloadProvider getDownloadProvider() {
return downloadProvider;
}
@Override
public Task<?> buildAsync() {
List<String> stages = new ArrayList<>();
@@ -17,6 +17,13 @@
*/
package org.jackhuang.hmcl.download;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import java.net.URL;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
/**
* The service provider that provides Minecraft online file downloads.
*
@@ -28,6 +35,10 @@ public interface DownloadProvider {
String getAssetBaseURL();
default List<URL> getAssetObjectCandidates(String assetObjectLocation) {
return Collections.singletonList(NetworkUtils.toURL(getAssetBaseURL() + assetObjectLocation));
}
/**
* Inject into original URL provided by Mojang and Forge.
*
@@ -40,9 +51,26 @@ public interface DownloadProvider {
String injectURL(String baseURL);
/**
* the specific version list that this download provider provides. i.e. "forge", "liteloader", "game", "optifine"
* Inject into original URL provided by Mojang and Forge.
*
* @param id the id of specific version list that this download provider provides. i.e. "forge", "liteloader", "game", "optifine"
* Since there are many provided URLs that are written in JSONs and are unmodifiable,
* this method provides a way to change them.
*
* @param baseURL original URL provided by Mojang and Forge.
* @return the URL that is equivalent to [baseURL], but belongs to your own service provider.
*/
default List<URL> injectURLWithCandidates(String baseURL) {
return Collections.singletonList(NetworkUtils.toURL(baseURL));
}
default List<URL> injectURLsWithCandidates(List<String> urls) {
return urls.stream().flatMap(url -> injectURLWithCandidates(url).stream()).collect(Collectors.toList());
}
/**
* the specific version list that this download provider provides. i.e. "fabric", "forge", "liteloader", "game", "optifine"
*
* @param id the id of specific version list that this download provider provides. i.e. "fabric", "forge", "liteloader", "game", "optifine"
* @return the version list
* @throws IllegalArgumentException if the version list does not exist
*/
@@ -35,7 +35,7 @@ public class RemoteVersion implements Comparable<RemoteVersion> {
private final String libraryId;
private final String gameVersion;
private final String selfVersion;
private final String[] url;
private final List<String> urls;
private final Type type;
/**
@@ -43,10 +43,10 @@ public class RemoteVersion implements Comparable<RemoteVersion> {
*
* @param gameVersion the Minecraft version that this remote version suits.
* @param selfVersion the version string of the remote version.
* @param url the installer or universal jar URL.
* @param urls the installer or universal jar original URL.
*/
public RemoteVersion(String libraryId, String gameVersion, String selfVersion, String... url) {
this(libraryId, gameVersion, selfVersion, Type.UNCATEGORIZED, url);
public RemoteVersion(String libraryId, String gameVersion, String selfVersion, List<String> urls) {
this(libraryId, gameVersion, selfVersion, Type.UNCATEGORIZED, urls);
}
/**
@@ -54,13 +54,13 @@ public class RemoteVersion implements Comparable<RemoteVersion> {
*
* @param gameVersion the Minecraft version that this remote version suits.
* @param selfVersion the version string of the remote version.
* @param url the installer or universal jar URL.
* @param urls the installer or universal jar URL.
*/
public RemoteVersion(String libraryId, String gameVersion, String selfVersion, Type type, String... url) {
public RemoteVersion(String libraryId, String gameVersion, String selfVersion, Type type, List<String> urls) {
this.libraryId = Objects.requireNonNull(libraryId);
this.gameVersion = Objects.requireNonNull(gameVersion);
this.selfVersion = Objects.requireNonNull(selfVersion);
this.url = Objects.requireNonNull(url);
this.urls = Objects.requireNonNull(urls);
this.type = Objects.requireNonNull(type);
}
@@ -76,8 +76,8 @@ public class RemoteVersion implements Comparable<RemoteVersion> {
return selfVersion;
}
public String[] getUrl() {
return url;
public List<String> getUrls() {
return urls;
}
public Type getVersionType() {
@@ -28,10 +28,8 @@ import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.GetTask;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import java.util.*;
import java.util.stream.Collectors;
/**
* <b>Note</b>: Fabric should be installed first.
@@ -51,9 +49,7 @@ public final class FabricInstallTask extends Task<Version> {
this.version = version;
this.remote = remoteVersion;
launchMetaTask = new GetTask(dependencyManager.getPreferredDownloadProviders().stream()
.map(downloadProvider -> downloadProvider.injectURL(getLaunchMetaUrl(remote.getGameVersion(), remote.getSelfVersion())))
.map(NetworkUtils::toURL).collect(Collectors.toList()))
launchMetaTask = new GetTask(dependencyManager.getDownloadProvider().injectURLsWithCandidates(remoteVersion.getUrls()))
.setCacheRepository(dependencyManager.getCacheRepository());
}
@@ -90,10 +86,6 @@ public final class FabricInstallTask extends Task<Version> {
dependencies.add(dependencyManager.checkLibraryCompletionAsync(getResult(), true));
}
private static String getLaunchMetaUrl(String gameVersion, String loaderVersion) {
return String.format("https://meta.fabricmc.net/v2/versions/loader/%s/%s", gameVersion, loaderVersion);
}
private Version getPatch(FabricInfo fabricInfo, String gameVersion, String loaderVersion) {
JsonObject launcherMeta = fabricInfo.launcherMeta;
Arguments arguments = new Arguments();
@@ -23,16 +23,18 @@ import org.jackhuang.hmcl.download.RemoteVersion;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.Task;
import java.util.List;
public class FabricRemoteVersion extends RemoteVersion {
/**
* Constructor.
*
* @param gameVersion the Minecraft version that this remote version suits.
* @param selfVersion the version string of the remote version.
* @param url the installer or universal jar URL.
* @param urls the installer or universal jar original URL.
*/
FabricRemoteVersion(String gameVersion, String selfVersion, String url) {
super(LibraryAnalyzer.LibraryType.FABRIC.getPatchId(), gameVersion, selfVersion, url);
FabricRemoteVersion(String gameVersion, String selfVersion, List<String> urls) {
super(LibraryAnalyzer.LibraryType.FABRIC.getPatchId(), gameVersion, selfVersion, urls);
}
@Override
@@ -28,6 +28,7 @@ import org.jetbrains.annotations.Nullable;
import javax.xml.stream.XMLStreamException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@@ -47,7 +48,7 @@ public final class FabricVersionList extends VersionList<FabricRemoteVersion> {
public Task<?> refreshAsync() {
return new Task<Void>() {
@Override
public void execute() throws IOException, XMLStreamException {
public void execute() throws IOException {
List<String> gameVersions = getGameVersions(GAME_META_URL);
List<String> loaderVersions = getGameVersions(LOADER_META_URL);
@@ -56,7 +57,8 @@ public final class FabricVersionList extends VersionList<FabricRemoteVersion> {
try {
for (String gameVersion : gameVersions)
for (String loaderVersion : loaderVersions)
versions.put(gameVersion, new FabricRemoteVersion(gameVersion, loaderVersion, ""));
versions.put(gameVersion, new FabricRemoteVersion(gameVersion, loaderVersion,
Collections.singletonList(getLaunchMetaUrl(gameVersion, loaderVersion))));
} finally {
lock.writeLock().unlock();
}
@@ -73,6 +75,10 @@ public final class FabricVersionList extends VersionList<FabricRemoteVersion> {
}.getType()).stream().map(GameVersion::getVersion).collect(Collectors.toList());
}
private static String getLaunchMetaUrl(String gameVersion, String loaderVersion) {
return String.format("https://meta.fabricmc.net/v2/versions/loader/%s/%s", gameVersion, loaderVersion);
}
private static class GameVersion {
private final String version;
private final String maven;
@@ -102,8 +102,7 @@ public final class ForgeBMCLVersionList extends VersionList<ForgeRemoteVersion>
if (urls.isEmpty())
continue;
versions.put(gameVersion, new ForgeRemoteVersion(
version.getGameVersion(), version.getVersion(), urls.toArray(new String[0])
));
version.getGameVersion(), version.getVersion(), urls));
}
} finally {
lock.writeLock().unlock();
@@ -18,6 +18,7 @@
package org.jackhuang.hmcl.download.forge;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.download.DependencyManager;
import org.jackhuang.hmcl.download.VersionMismatchException;
import org.jackhuang.hmcl.game.GameVersion;
import org.jackhuang.hmcl.game.Version;
@@ -26,15 +27,15 @@ import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.CompressingUtils;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import org.jackhuang.hmcl.util.versioning.VersionNumber;
import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.stream.Collectors;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import static org.jackhuang.hmcl.util.StringUtils.removePrefix;
import static org.jackhuang.hmcl.util.StringUtils.removeSuffix;
@@ -68,9 +69,7 @@ public final class ForgeInstallTask extends Task<Version> {
installer = Files.createTempFile("forge-installer", ".jar");
dependent = new FileDownloadTask(
dependencyManager.getPreferredDownloadProviders().stream()
.flatMap(downloadProvider -> Arrays.stream(remote.getUrl()).map(downloadProvider::injectURL))
.map(NetworkUtils::toURL).collect(Collectors.toList()),
dependencyManager.getDownloadProvider().injectURLsWithCandidates(remote.getUrls()),
installer.toFile(), null)
.setCacheRepository(dependencyManager.getCacheRepository())
.setCaching(true);
@@ -98,13 +97,45 @@ public final class ForgeInstallTask extends Task<Version> {
}
@Override
public void execute() {
if (VersionNumber.VERSION_COMPARATOR.compare("1.13", remote.getGameVersion()) <= 0)
public void execute() throws IOException, VersionMismatchException {
if (detectForgeInstallerType(dependencyManager, version, installer))
dependency = new ForgeNewInstallTask(dependencyManager, version, remote.getSelfVersion(), installer);
else
dependency = new ForgeOldInstallTask(dependencyManager, version, remote.getSelfVersion(), installer);
}
/**
* Detect Forge installer type.
*
* @param dependencyManager game repository
* @param version version.json
* @param installer the Forge installer, either the new or old one.
* @return true for new, false for old
* @throws IOException if unable to read compressed content of installer file, or installer file is corrupted, or the installer is not the one we want.
* @throws VersionMismatchException if required game version of installer does not match the actual one.
*/
public static boolean detectForgeInstallerType(DependencyManager dependencyManager, Version version, Path installer) throws IOException, VersionMismatchException {
Optional<String> gameVersion = GameVersion.minecraftVersion(dependencyManager.getGameRepository().getVersionJar(version));
if (!gameVersion.isPresent()) throw new IOException();
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) {
String installProfileText = FileUtils.readText(fs.getPath("install_profile.json"));
Map installProfile = JsonUtils.fromNonNullJson(installProfileText, Map.class);
if (installProfile.containsKey("spec")) {
ForgeNewInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeNewInstallProfile.class);
if (!gameVersion.get().equals(profile.getMinecraft()))
throw new VersionMismatchException(profile.getMinecraft(), gameVersion.get());
return true;
} else if (installProfile.containsKey("install") && installProfile.containsKey("versionInfo")) {
ForgeInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeInstallProfile.class);
if (!gameVersion.get().equals(profile.getInstall().getMinecraft()))
throw new VersionMismatchException(profile.getInstall().getMinecraft(), gameVersion.get());
return false;
} else {
throw new IOException();
}
}
}
/**
* Install Forge library from existing local file.
* This method will try to identify this installer whether it is in old or new format.
@@ -23,15 +23,17 @@ import org.jackhuang.hmcl.download.RemoteVersion;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.Task;
import java.util.List;
public class ForgeRemoteVersion extends RemoteVersion {
/**
* Constructor.
*
* @param gameVersion the Minecraft version that this remote version suits.
* @param selfVersion the version string of the remote version.
* @param url the installer or universal jar URL.
* @param url the installer or universal jar original URL.
*/
public ForgeRemoteVersion(String gameVersion, String selfVersion, String... url) {
public ForgeRemoteVersion(String gameVersion, String selfVersion, List<String> url) {
super(LibraryAnalyzer.LibraryType.FORGE.getPatchId(), gameVersion, selfVersion, url);
}
@@ -26,7 +26,9 @@ import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import org.jackhuang.hmcl.util.versioning.VersionNumber;
import java.util.*;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
/**
*
@@ -76,13 +78,13 @@ public final class ForgeVersionList extends VersionList<ForgeRemoteVersion> {
String classifier = version.getGameVersion() + "-" + version.getVersion()
+ (StringUtils.isNotBlank(version.getBranch()) ? "-" + version.getBranch() : "");
String fileName = root.getArtifact() + "-" + classifier + "-" + file[1] + "." + file[0];
jar = downloadProvider.injectURL(root.getWebPath() + classifier + "/" + fileName);
jar = root.getWebPath() + classifier + "/" + fileName;
}
if (jar == null)
continue;
versions.put(gameVersion, new ForgeRemoteVersion(
version.getGameVersion(), version.getVersion(), jar
version.getGameVersion(), version.getVersion(), Collections.singletonList(jar)
));
}
}
@@ -18,7 +18,6 @@
package org.jackhuang.hmcl.download.game;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSyntaxException;
import org.jackhuang.hmcl.download.AbstractDependencyManager;
import org.jackhuang.hmcl.game.AssetIndex;
import org.jackhuang.hmcl.game.AssetIndexInfo;
@@ -30,7 +29,6 @@ import org.jackhuang.hmcl.util.CacheRepository;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import java.io.File;
import java.io.IOException;
@@ -39,7 +37,6 @@ import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.stream.Collectors;
/**
*
@@ -112,10 +109,7 @@ public final class GameAssetDownloadTask extends Task<Void> {
Logging.LOG.log(Level.WARNING, "Unable to calc hash value of file " + file.toPath(), e);
}
if (download) {
List<URL> urls = dependencyManager.getPreferredDownloadProviders().stream()
.map(downloadProvider -> downloadProvider.getAssetBaseURL() + assetObject.getLocation())
.map(NetworkUtils::toURL)
.collect(Collectors.toList());
List<URL> urls = dependencyManager.getDownloadProvider().getAssetObjectCandidates(assetObject.getLocation());
FileDownloadTask task = new FileDownloadTask(urls, file, new FileDownloadTask.IntegrityCheck("SHA-1", assetObject.getHash()));
task.setName(assetObject.getHash());
@@ -22,7 +22,6 @@ import org.jackhuang.hmcl.game.AssetIndexInfo;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.FileDownloadTask;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import java.io.File;
import java.io.IOException;
@@ -65,7 +64,7 @@ public final class GameAssetIndexDownloadTask extends Task<Void> {
// We should not check the hash code of asset index file since this file is not consistent
// And Mojang will modify this file anytime. So assetIndex.hash might be outdated.
dependencies.add(new FileDownloadTask(
NetworkUtils.toURL(dependencyManager.getPrimaryDownloadProvider().injectURL(assetIndexInfo.getUrl())),
dependencyManager.getDownloadProvider().injectURLWithCandidates(assetIndexInfo.getUrl()),
assetIndexFile
).setCacheRepository(dependencyManager.getCacheRepository()));
}
@@ -23,13 +23,11 @@ import org.jackhuang.hmcl.task.FileDownloadTask;
import org.jackhuang.hmcl.task.FileDownloadTask.IntegrityCheck;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.CacheRepository;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import java.io.File;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
/**
* Task to download Minecraft jar
@@ -59,9 +57,7 @@ public final class GameDownloadTask extends Task<Void> {
File jar = dependencyManager.getGameRepository().getVersionJar(version);
FileDownloadTask task = new FileDownloadTask(
dependencyManager.getPreferredDownloadProviders().stream()
.map(downloadProvider -> downloadProvider.injectURL(version.getDownloadInfo().getUrl()))
.map(NetworkUtils::toURL).collect(Collectors.toList()),
dependencyManager.getDownloadProvider().injectURLWithCandidates(version.getDownloadInfo().getUrl()),
jar,
IntegrityCheck.of(CacheRepository.SHA1, version.getDownloadInfo().getSha1()))
.setCaching(true)
@@ -26,6 +26,7 @@ import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.Immutable;
import java.util.Date;
import java.util.List;
/**
*
@@ -37,7 +38,7 @@ public final class GameRemoteVersion extends RemoteVersion {
private final ReleaseType type;
private final Date time;
public GameRemoteVersion(String gameVersion, String selfVersion, String url, ReleaseType type, Date time) {
public GameRemoteVersion(String gameVersion, String selfVersion, List<String> url, ReleaseType type, Date time) {
super(LibraryAnalyzer.LibraryType.MINECRAFT.getPatchId(), gameVersion, selfVersion, getReleaseType(type), url);
this.type = type;
this.time = time;
@@ -74,7 +74,7 @@ public final class GameVersionList extends VersionList<GameRemoteVersion> {
versions.put(remoteVersion.getGameVersion(), new GameRemoteVersion(
remoteVersion.getGameVersion(),
remoteVersion.getGameVersion(),
remoteVersion.getUrl(),
Collections.singletonList(remoteVersion.getUrl()),
remoteVersion.getType(), remoteVersion.getReleaseTime())
);
}
@@ -33,7 +33,7 @@ import org.tukaani.xz.XZInputStream;
import java.io.*;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
@@ -42,7 +42,6 @@ import java.util.jar.JarInputStream;
import java.util.jar.JarOutputStream;
import java.util.jar.Pack200;
import java.util.logging.Level;
import java.util.stream.Collectors;
import static org.jackhuang.hmcl.util.DigestUtils.digest;
import static org.jackhuang.hmcl.util.Hex.encodeHex;
@@ -130,21 +129,15 @@ public class LibraryDownloadTask extends Task<Void> {
}
try {
URL packXz = NetworkUtils.toURL(dependencyManager.getPrimaryDownloadProvider().injectURL(url) + ".pack.xz");
URL packXz = NetworkUtils.toURL(dependencyManager.getDownloadProvider().injectURL(url) + ".pack.xz");
if (NetworkUtils.urlExists(packXz)) {
List<URL> urls = dependencyManager.getPreferredDownloadProviders().stream()
.map(downloadProvider -> downloadProvider.injectURL(url) + ".pack.xz")
.map(NetworkUtils::toURL)
.collect(Collectors.toList());
List<URL> urls = dependencyManager.getDownloadProvider().injectURLWithCandidates(url + ".pack.xz");
task = new FileDownloadTask(urls, xzFile, null)
.setCacheRepository(cacheRepository)
.setCaching(true);
xz = true;
} else {
List<URL> urls = dependencyManager.getPreferredDownloadProviders().stream()
.map(downloadProvider -> downloadProvider.injectURL(url))
.map(NetworkUtils::toURL)
.collect(Collectors.toList());
List<URL> urls = dependencyManager.getDownloadProvider().injectURLWithCandidates(url);
task = new FileDownloadTask(urls, jar,
library.getDownload().getSha1() != null ? new IntegrityCheck("SHA-1", library.getDownload().getSha1()) : null)
.setCacheRepository(cacheRepository)
@@ -192,7 +185,7 @@ public class LibraryDownloadTask extends Task<Void> {
while (entry != null) {
byte[] eData = IOUtils.readFullyWithoutClosing(jar);
if (entry.getName().equals("checksums.sha1")) {
hashes = new String(eData, Charset.forName("UTF-8")).split("\n");
hashes = new String(eData, StandardCharsets.UTF_8).split("\n");
}
if (!entry.isDirectory()) {
files.put(entry.getName(), encodeHex(digest("SHA-1", eData)));
@@ -22,14 +22,11 @@ import org.jackhuang.hmcl.download.RemoteVersion;
import org.jackhuang.hmcl.download.VersionList;
import org.jackhuang.hmcl.task.GetTask;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
/**
*
@@ -46,9 +43,8 @@ public final class VersionJsonDownloadTask extends Task<String> {
this.gameVersion = gameVersion;
this.dependencyManager = dependencyManager;
this.gameVersionList = dependencyManager.getVersionList("game");
if (!gameVersionList.isLoaded())
dependents.add(gameVersionList.refreshAsync());
dependents.add(gameVersionList.loadAsync());
setSignificance(TaskSignificance.MODERATE);
}
@@ -67,10 +63,6 @@ public final class VersionJsonDownloadTask extends Task<String> {
public void execute() throws IOException {
RemoteVersion remoteVersion = gameVersionList.getVersion(gameVersion, gameVersion)
.orElseThrow(() -> new IOException("Cannot find specific version " + gameVersion + " in remote repository"));
dependencies.add(new GetTask(
dependencyManager.getPreferredDownloadProviders().stream()
.flatMap(downloadProvider -> Arrays.stream(remoteVersion.getUrl()).map(downloadProvider::injectURL))
.map(NetworkUtils::toURL).collect(Collectors.toList())
).storeTo(this::setResult));
dependencies.add(new GetTask(dependencyManager.getDownloadProvider().injectURLsWithCandidates(remoteVersion.getUrls())).storeTo(this::setResult));
}
}
@@ -101,7 +101,7 @@ public final class LiteLoaderBMCLVersionList extends VersionList<LiteLoaderRemot
}
versions.put(key, new LiteLoaderRemoteVersion(gameVersion,
version, url,
version, Collections.singletonList(url),
v.getTweakClass(), v.getLibraries()
));
}
@@ -62,7 +62,7 @@ public final class LiteLoaderInstallTask extends Task<Version> {
Library library = new Library(
new Artifact("com.mumfrey", "liteloader", remote.getSelfVersion()),
"http://dl.liteloader.com/versions/",
new LibrariesDownloadInfo(new LibraryDownloadInfo(null, remote.getUrl()[0]))
new LibrariesDownloadInfo(new LibraryDownloadInfo(null, remote.getUrls().get(0)))
);
setResult(new Version(LibraryAnalyzer.LibraryType.LITELOADER.getPatchId(),
@@ -25,6 +25,7 @@ import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.Task;
import java.util.Collection;
import java.util.List;
public class LiteLoaderRemoteVersion extends RemoteVersion {
private final String tweakClass;
@@ -34,10 +35,10 @@ public class LiteLoaderRemoteVersion extends RemoteVersion {
*
* @param gameVersion the Minecraft version that this remote version suits.
* @param selfVersion the version string of the remote version.
* @param url the installer or universal jar URL.
* @param urls the installer or universal jar original URL.
*/
LiteLoaderRemoteVersion(String gameVersion, String selfVersion, String url, String tweakClass, Collection<Library> libraries) {
super(LibraryAnalyzer.LibraryType.LITELOADER.getPatchId(), gameVersion, selfVersion, url);
LiteLoaderRemoteVersion(String gameVersion, String selfVersion, List<String> urls, String tweakClass, Collection<Library> libraries) {
super(LibraryAnalyzer.LibraryType.LITELOADER.getPatchId(), gameVersion, selfVersion, urls);
this.tweakClass = tweakClass;
this.libraries = libraries;
@@ -102,7 +102,7 @@ public final class LiteLoaderVersionList extends VersionList<LiteLoaderRemoteVer
}
versions.put(key, new LiteLoaderRemoteVersion(gameVersion,
version, downloadProvider.injectURL(url),
version, Collections.singletonList(url),
v.getTweakClass(), v.getLibraries()
));
}
@@ -77,7 +77,7 @@ public final class OptiFineBMCLVersionList extends VersionList<OptiFineRemoteVer
continue;
String gameVersion = VersionNumber.normalize(element.getGameVersion());
versions.put(gameVersion, new OptiFineRemoteVersion(gameVersion, version, mirror, isPre));
versions.put(gameVersion, new OptiFineRemoteVersion(gameVersion, version, Collections.singletonList(mirror), isPre));
}
} finally {
lock.writeLock().unlock();
@@ -25,7 +25,6 @@ import org.jackhuang.hmcl.task.FileDownloadTask;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.io.CompressingUtils;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import org.jackhuang.hmcl.util.platform.JavaVersion;
import org.jackhuang.hmcl.util.platform.SystemUtils;
import org.jenkinsci.constant_pool_scanner.ConstantPool;
@@ -81,7 +80,7 @@ public final class OptiFineInstallTask extends Task<Version> {
new Artifact("optifine", "OptiFine", mavenVersion, "installer"), null,
new LibrariesDownloadInfo(new LibraryDownloadInfo(
"optifine/OptiFine/" + mavenVersion + "/OptiFine-" + mavenVersion + "-installer.jar",
remote.getUrl()[0]))
remote.getUrls().get(0).toString()))
);
}
@@ -96,9 +95,7 @@ public final class OptiFineInstallTask extends Task<Version> {
if (installer == null) {
dependents.add(new FileDownloadTask(
dependencyManager.getPreferredDownloadProviders().stream()
.flatMap(downloadProvider -> Arrays.stream(remote.getUrl()).map(downloadProvider::injectURL))
.map(NetworkUtils::toURL).collect(Collectors.toList()),
dependencyManager.getDownloadProvider().injectURLsWithCandidates(remote.getUrls()),
dest.toFile(), null)
.setCacheRepository(dependencyManager.getCacheRepository())
.setCaching(true));
@@ -231,7 +228,7 @@ public final class OptiFineInstallTask extends Task<Version> {
throw new VersionMismatchException(mcVersion, gameVersion.get());
return new OptiFineInstallTask(dependencyManager, version,
new OptiFineRemoteVersion(mcVersion, ofEdition + "_" + ofRelease, "", false), installer);
new OptiFineRemoteVersion(mcVersion, ofEdition + "_" + ofRelease, Collections.singletonList(""), false), installer);
}
}
}
@@ -23,12 +23,12 @@ import org.jackhuang.hmcl.download.RemoteVersion;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.Task;
import java.util.function.Supplier;
import java.util.List;
public class OptiFineRemoteVersion extends RemoteVersion {
public OptiFineRemoteVersion(String gameVersion, String selfVersion, String url, boolean snapshot) {
super(LibraryAnalyzer.LibraryType.OPTIFINE.getPatchId(), gameVersion, selfVersion, snapshot ? Type.SNAPSHOT : Type.RELEASE, url);
public OptiFineRemoteVersion(String gameVersion, String selfVersion, List<String> urls, boolean snapshot) {
super(LibraryAnalyzer.LibraryType.OPTIFINE.getPatchId(), gameVersion, selfVersion, snapshot ? Type.SNAPSHOT : Type.RELEASE, urls);
}
@Override
@@ -283,9 +283,18 @@ public class DefaultLauncher extends Launcher {
File runDirectory = repository.getRunDirectory(version.getId());
if (StringUtils.isNotBlank(options.getPreLaunchCommand()))
new ProcessBuilder(options.getPreLaunchCommand())
if (StringUtils.isNotBlank(options.getPreLaunchCommand())) {
String versionName = Optional.ofNullable(options.getVersionName()).orElse(version.getId());
String preLaunchCommand = options.getPreLaunchCommand()
.replace("$INST_NAME", versionName)
.replace("$INST_ID", versionName)
.replace("$INST_DIR", repository.getVersionRoot(version.getId()).getAbsolutePath())
.replace("$INST_MC_DIR", repository.getRunDirectory(version.getId()).getAbsolutePath())
.replace("$INST_JAVA", options.getJava().getBinary().toString());
new ProcessBuilder(preLaunchCommand)
.directory(runDirectory).start().waitFor();
}
Process process;
try {
@@ -119,6 +119,15 @@ public class FileDownloadTask extends Task<Void> {
this(Collections.singletonList(url), file, integrityCheck, retry);
}
/**
* Constructor.
* @param urls urls of remote file, will be attempted in order.
* @param file the location that download to.
*/
public FileDownloadTask(List<URL> urls, File file) {
this(urls, file, null);
}
/**
* Constructor.
* @param urls urls of remote file, will be attempted in order.
@@ -214,11 +214,11 @@ public final class NetworkUtils {
}
public static boolean urlExists(URL url) throws IOException {
try (InputStream stream = url.openStream()) {
return true;
} catch (FileNotFoundException e) {
return false;
}
HttpURLConnection con = createConnection(url);
con = resolveConnection(con);
int responseCode = con.getResponseCode();
con.disconnect();
return responseCode / 100 == 2;
}
// ==== Shortcut methods for encoding/decoding URLs in UTF-8 ====
@@ -17,7 +17,12 @@
*/
package org.jackhuang.hmcl.util.javafx;
import static org.jackhuang.hmcl.util.Pair.pair;
import javafx.beans.InvalidationListener;
import javafx.beans.WeakInvalidationListener;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.Property;
import javafx.collections.ObservableList;
import javafx.scene.control.*;
import java.util.Objects;
import java.util.Optional;
@@ -25,16 +30,7 @@ import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Function;
import javafx.beans.InvalidationListener;
import javafx.beans.WeakInvalidationListener;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.Property;
import javafx.collections.ObservableList;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.SelectionModel;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;
import static org.jackhuang.hmcl.util.Pair.pair;
/**
* @author yushijinhun
@@ -126,7 +122,7 @@ public final class ExtendedProperties {
// ==== CheckBox ====
@SuppressWarnings("unchecked")
public static ObjectProperty<Boolean> reservedSelectedPropertyFor(CheckBox checkbox) {
public static ObjectProperty<Boolean> reversedSelectedPropertyFor(CheckBox checkbox) {
return (ObjectProperty<Boolean>) checkbox.getProperties().computeIfAbsent(
PROP_PREFIX + ".checkbox.reservedSelected",
any -> new MappedProperty<Boolean, Boolean>(checkbox, "ext.reservedSelected",
+5
View File
@@ -6,6 +6,11 @@ HMCL is a Minecraft launcher which supports Mod management, game customizing, au
No plugin API is provided.
## Download
Download the latest version [from the official website](https://hmcl.huangyuhui.net/download)
Note: Github releases are outdated.
## License
The software is distributed under [GPL v3](https://www.gnu.org/licenses/gpl-3.0.html) with additional terms.