feat: display mod description and website url. Closes #945.

This commit is contained in:
huanghongxun
2021-08-07 16:18:23 +08:00
parent 1ddcc23b89
commit b2594122a8
18 changed files with 330 additions and 66 deletions

View File

@@ -258,4 +258,8 @@ public final class SVG {
public static Node releaseCircleOutline(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M9,7H13A2,2 0 0,1 15,9V11C15,11.84 14.5,12.55 13.76,12.85L15,17H13L11.8,13H11V17H9V7M11,9V11H13V9H11M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12C4,16.41 7.58,20 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4Z", fill, width, height);
}
public static Node informationOutline(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M11,9H13V7H11M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M11,17H13V11H11V17Z", fill, width, height);
}
}

View File

@@ -17,11 +17,13 @@
*/
package org.jackhuang.hmcl.ui.construct;
import com.jfoenix.controls.JFXListView;
import com.jfoenix.effects.JFXDepthManager;
import javafx.css.PseudoClass;
import javafx.geometry.Insets;
import javafx.scene.Cursor;
import javafx.scene.control.ListCell;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.ui.FXUtils;
@@ -30,7 +32,7 @@ public abstract class FloatListCell<T> extends ListCell<T> {
protected final StackPane pane = new StackPane();
{
public FloatListCell(JFXListView<T> listView) {
setText(null);
setGraphic(null);
@@ -42,6 +44,14 @@ public abstract class FloatListCell<T> extends ListCell<T> {
FXUtils.onChangeAndOperate(selectedProperty(), selected -> {
pane.pseudoClassStateChanged(SELECTED, selected);
});
Region clippedContainer = (Region) listView.lookup(".clipped-container");
setPrefWidth(0);
if (clippedContainer != null) {
maxWidthProperty().bind(clippedContainer.widthProperty());
prefWidthProperty().bind(clippedContainer.widthProperty());
minWidthProperty().bind(clippedContainer.widthProperty());
}
}
@Override

View File

@@ -119,7 +119,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
chkSnapshot.selectedProperty().addListener(listener);
chkOld.selectedProperty().addListener(listener);
list.setCellFactory(listView -> new FloatListCell<RemoteVersion>() {
list.setCellFactory(listView -> new FloatListCell<RemoteVersion>(list) {
ImageView imageView = new ImageView();
TwoLineListItem content = new TwoLineListItem();

View File

@@ -75,7 +75,7 @@ class DatapackListPageSkin extends SkinBase<DatapackListPage> {
center.getStyleClass().add("large-spinner-pane");
center.loadingProperty().bind(skinnable.loadingProperty());
listView.setCellFactory(x -> new FloatListCell<DatapackInfoObject>() {
listView.setCellFactory(x -> new FloatListCell<DatapackInfoObject>(listView) {
JFXCheckBox checkBox = new JFXCheckBox();
TwoLineListItem content = new TwoLineListItem();
BooleanProperty booleanProperty;

View File

@@ -250,7 +250,7 @@ public class ModDownloadListPage extends Control implements DecoratorPage, Versi
CurseAddon selectedItem = listView.getSelectionModel().getSelectedItem();
Controllers.navigate(new ModDownloadPage(selectedItem, getSkinnable().version.get(), getSkinnable().callback));
});
listView.setCellFactory(x -> new FloatListCell<CurseAddon>() {
listView.setCellFactory(x -> new FloatListCell<CurseAddon>(listView) {
TwoLineListItem content = new TwoLineListItem();
ImageView imageView = new ImageView();

View File

@@ -191,22 +191,14 @@ public class ModDownloadPage extends Control implements DecoratorPage {
JFXListView<CurseAddon.LatestFile> listView = new JFXListView<>();
spinnerPane.setContent(listView);
Bindings.bindContent(listView.getItems(), getSkinnable().items);
listView.setCellFactory(x -> new FloatListCell<CurseAddon.LatestFile>() {
listView.setCellFactory(x -> new FloatListCell<CurseAddon.LatestFile>(listView) {
TwoLineListItem content = new TwoLineListItem();
StackPane graphicPane = new StackPane();
{
Region clippedContainer = (Region)listView.lookup(".clipped-container");
setPrefWidth(0);
HBox container = new HBox(8);
container.setAlignment(Pos.CENTER_LEFT);
pane.getChildren().add(container);
if (clippedContainer != null) {
maxWidthProperty().bind(clippedContainer.widthProperty());
prefWidthProperty().bind(clippedContainer.widthProperty());
minWidthProperty().bind(clippedContainer.widthProperty());
}
container.getChildren().setAll(graphicPane, content);
}

View File

@@ -17,7 +17,9 @@
*/
package org.jackhuang.hmcl.ui.versions;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXCheckBox;
import com.jfoenix.controls.JFXDialogLayout;
import com.jfoenix.controls.JFXListView;
import com.jfoenix.controls.datamodels.treetable.RecursiveTreeObject;
import com.jfoenix.effects.JFXDepthManager;
@@ -27,20 +29,38 @@ import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.SkinBase;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.mod.ModInfo;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
import org.jackhuang.hmcl.ui.construct.FloatListCell;
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.io.CompressingUtils;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import org.jetbrains.annotations.NotNull;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton;
import static org.jackhuang.hmcl.util.Lang.mapOf;
import static org.jackhuang.hmcl.util.Pair.pair;
import static org.jackhuang.hmcl.util.StringUtils.isNotBlank;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
@@ -80,37 +100,7 @@ class ModListPageSkin extends SkinBase<ModListPage> {
center.getStyleClass().add("large-spinner-pane");
center.loadingProperty().bind(skinnable.loadingProperty());
listView.setCellFactory(x -> new FloatListCell<ModInfoObject>() {
JFXCheckBox checkBox = new JFXCheckBox();
TwoLineListItem content = new TwoLineListItem();
BooleanProperty booleanProperty;
{
Region clippedContainer = (Region)listView.lookup(".clipped-container");
setPrefWidth(0);
HBox container = new HBox(8);
container.setAlignment(Pos.CENTER_LEFT);
pane.getChildren().add(container);
if (clippedContainer != null) {
maxWidthProperty().bind(clippedContainer.widthProperty());
prefWidthProperty().bind(clippedContainer.widthProperty());
minWidthProperty().bind(clippedContainer.widthProperty());
}
container.getChildren().setAll(checkBox, content);
}
@Override
protected void updateControl(ModInfoObject dataItem, boolean empty) {
if (empty) return;
content.setTitle(dataItem.getTitle());
content.setSubtitle(dataItem.getSubtitle());
if (booleanProperty != null) {
checkBox.selectedProperty().unbindBidirectional(booleanProperty);
}
checkBox.selectedProperty().bindBidirectional(booleanProperty = dataItem.active);
}
});
listView.setCellFactory(x -> new ModInfoListCell(listView));
listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
Bindings.bindContent(listView.getItems(), skinnable.getItems());
@@ -164,4 +154,111 @@ class ModListPageSkin extends SkinBase<ModListPage> {
return modInfo.getFileName().toLowerCase().compareTo(o.modInfo.getFileName().toLowerCase());
}
}
static class ModInfoDialog extends JFXDialogLayout {
ModInfoDialog(ModInfoObject modInfo) {
HBox titleContainer = new HBox();
titleContainer.setSpacing(8);
ImageView imageView = new ImageView();
if (StringUtils.isNotBlank(modInfo.getModInfo().getLogoPath())) {
Task.supplyAsync(() -> {
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(modInfo.getModInfo().getFile())) {
Path iconPath = fs.getPath(modInfo.getModInfo().getLogoPath());
if (Files.exists(iconPath)) {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
Files.copy(iconPath, stream);
return new ByteArrayInputStream(stream.toByteArray());
}
}
return null;
}).whenComplete(Schedulers.javafx(), (stream, exception) -> {
if (stream != null) {
imageView.setImage(new Image(stream, 40, 40, true, true));
} else {
imageView.setImage(new Image("/assets/img/command.png", 40, 40, true, true));
}
}).start();
}
TwoLineListItem title = new TwoLineListItem();
title.setTitle(modInfo.getModInfo().getName());
if (StringUtils.isNotBlank(modInfo.getModInfo().getVersion())) {
title.getTags().setAll(modInfo.getModInfo().getVersion());
}
title.setSubtitle(FileUtils.getName(modInfo.getModInfo().getFile()));
titleContainer.getChildren().setAll(FXUtils.limitingSize(imageView, 40, 40), title);
setHeading(titleContainer);
Label description = new Label(modInfo.getModInfo().getDescription().toString());
setBody(description);
JFXButton okButton = new JFXButton();
okButton.getStyleClass().add("dialog-accept");
okButton.setText(i18n("button.ok"));
okButton.setOnAction(e -> fireEvent(new DialogCloseEvent()));
JFXButton searchButton = new JFXButton();
searchButton.getStyleClass().add("dialog-cancel");
searchButton.setText(i18n("mods.mcmod.search"));
searchButton.setOnAction(e -> {
fireEvent(new DialogCloseEvent());
FXUtils.openLink(NetworkUtils.withQuery("https://search.mcmod.cn/s", mapOf(
pair("key", modInfo.getModInfo().getName()),
pair("site", "all"),
pair("filter", "0")
)));
});
if (StringUtils.isNotBlank(modInfo.getModInfo().getUrl())) {
JFXButton officialPageButton = new JFXButton();
officialPageButton.getStyleClass().add("dialog-cancel");
officialPageButton.setText(i18n("mods.url"));
officialPageButton.setOnAction(e -> {
fireEvent(new DialogCloseEvent());
FXUtils.openLink(modInfo.getModInfo().getUrl());
});
setActions(okButton, officialPageButton, searchButton);
} else {
setActions(okButton, searchButton);
}
}
}
static class ModInfoListCell extends FloatListCell<ModInfoObject> {
JFXCheckBox checkBox = new JFXCheckBox();
TwoLineListItem content = new TwoLineListItem();
JFXButton infoButton = new JFXButton();
BooleanProperty booleanProperty;
ModInfoListCell(JFXListView<ModInfoObject> listView) {
super(listView);
HBox container = new HBox(8);
container.setAlignment(Pos.CENTER_LEFT);
pane.getChildren().add(container);
HBox.setHgrow(content, Priority.ALWAYS);
infoButton.getStyleClass().add("toggle-icon4");
infoButton.setGraphic(FXUtils.limitingSize(SVG.informationOutline(Theme.blackFillBinding(), 24, 24), 24, 24));
container.getChildren().setAll(checkBox, content, infoButton);
}
@Override
protected void updateControl(ModInfoObject dataItem, boolean empty) {
if (empty) return;
content.setTitle(dataItem.getTitle());
content.setSubtitle(dataItem.getSubtitle());
if (booleanProperty != null) {
checkBox.selectedProperty().unbindBidirectional(booleanProperty);
}
checkBox.selectedProperty().bindBidirectional(booleanProperty = dataItem.active);
infoButton.setOnMouseClicked(e -> {
Controllers.dialog(new ModInfoDialog(dataItem));
});
}
}
}

View File

@@ -188,12 +188,16 @@
.two-line-list-item > HBox > .subtitle {
-fx-text-fill: rgba(0, 0, 0, 0.5);
-fx-font-weight: normal;
-fx-font-size: 12px;
}
.two-line-list-item > HBox > HBox > .tag {
-fx-text-fill: -fx-base-color;
-fx-background-color: -fx-base-rippler-color;
-fx-padding: 2;
-fx-font-weight: normal;
-fx-font-size: 12px;
}
.bubble {

View File

@@ -377,11 +377,15 @@ mods.add=Install mods
mods.add.failed=Failed to install mods %s.
mods.add.success=Successfully installed mods %s.
mods.choose_mod=Choose your mods
mods.download=Mod Downloads
mods.enable=Enable
mods.disable=Disable
mods.download=Mod Downloads
mods.download.title=Mod Downloads - %1s
mods.enable=Enable
mods.mcmod.page=MCWiki
mods.mcmod.search=Search in MCWiki
mods.name=Name
mods.not_modded=You should install a modloader first (Fabric, Forge or LiteLoader)
mods.url=Official Page
datapack=Datapacks
datapack.add=Install datapack

View File

@@ -376,14 +376,17 @@ mods=模组管理
mods.add=添加模组
mods.add.failed=添加模组 %s 失败。
mods.add.success=成功添加模组 %s。
mods.category=类别
mods.choose_mod=选择模组
mods.disable=禁用
mods.download=模组下载
mods.download.title=模组下载 - %1s
mods.enable=启用
mods.disable=禁用
mods.mcmod.page=MC 百科页面
mods.mcmod.search=MC 百科搜索
mods.name=名称
mods.category=类别
mods.not_modded=你需要先在自动安装页面安装 Fabric、Forge 或 LiteLoader 才能进行模组管理。
mods.url=官方页面
datapack=数据包
datapack.add=添加数据包