Use JFXListView in place of custom components to accelerate rendering

This commit is contained in:
huanghongxun 2019-01-22 16:01:00 +08:00
parent 056a4eead2
commit 0813211c20
4 changed files with 56 additions and 49 deletions

View File

@ -17,14 +17,17 @@
*/ */
package org.jackhuang.hmcl.ui.construct; package org.jackhuang.hmcl.ui.construct;
import com.jfoenix.controls.JFXListView;
import com.jfoenix.controls.JFXProgressBar; import com.jfoenix.controls.JFXProgressBar;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ReadOnlyIntegerProperty; import javafx.beans.property.ReadOnlyIntegerProperty;
import javafx.beans.property.ReadOnlyIntegerWrapper; import javafx.beans.property.ReadOnlyIntegerWrapper;
import javafx.geometry.Insets;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.layout.BorderPane; import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane; import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.download.forge.ForgeInstallTask; import org.jackhuang.hmcl.download.forge.ForgeInstallTask;
import org.jackhuang.hmcl.download.game.GameAssetDownloadTask; import org.jackhuang.hmcl.download.game.GameAssetDownloadTask;
import org.jackhuang.hmcl.download.liteloader.LiteLoaderInstallTask; import org.jackhuang.hmcl.download.liteloader.LiteLoaderInstallTask;
@ -42,15 +45,16 @@ import java.util.Map;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public final class TaskListPane extends StackPane { public final class TaskListPane extends StackPane {
private final AdvancedListBox listBox = new AdvancedListBox(); private final JFXListView<Task> listBox = new JFXListView<>();
private final Map<Task, ProgressListNode> nodes = new HashMap<>(); private final Map<Task, ProgressListNode> nodes = new HashMap<>();
private final ReadOnlyIntegerWrapper finishedTasks = new ReadOnlyIntegerWrapper(); private final ReadOnlyIntegerWrapper finishedTasks = new ReadOnlyIntegerWrapper();
private final ReadOnlyIntegerWrapper totTasks = new ReadOnlyIntegerWrapper(); private final ReadOnlyIntegerWrapper totTasks = new ReadOnlyIntegerWrapper();
public TaskListPane() { public TaskListPane() {
listBox.setSpacing(0);
getChildren().setAll(listBox); getChildren().setAll(listBox);
listBox.setPadding(Insets.EMPTY);
listBox.setCellFactory(listView -> new ProgressListNode());
} }
public ReadOnlyIntegerProperty finishedTasksProperty() { public ReadOnlyIntegerProperty finishedTasksProperty() {
@ -66,7 +70,7 @@ public final class TaskListPane extends StackPane {
@Override @Override
public void onStart() { public void onStart() {
Platform.runLater(() -> { Platform.runLater(() -> {
listBox.clear(); listBox.getItems().clear();
finishedTasks.set(0); finishedTasks.set(0);
totTasks.set(0); totTasks.set(0);
}); });
@ -108,66 +112,66 @@ public final class TaskListPane extends StackPane {
task.setName(i18n("modpack.scan")); task.setName(i18n("modpack.scan"));
} }
ProgressListNode node = new ProgressListNode(task); Platform.runLater(() -> listBox.getItems().add(task));
nodes.put(task, node);
Platform.runLater(() -> listBox.add(node));
} }
@Override @Override
public void onFinished(Task task) { public void onFinished(Task task) {
ProgressListNode node = nodes.remove(task);
if (node == null)
return;
node.unbind();
Platform.runLater(() -> { Platform.runLater(() -> {
listBox.remove(node); if (listBox.getItems().remove(task))
finishedTasks.set(finishedTasks.getValue() + 1); finishedTasks.set(finishedTasks.getValue() + 1);
});
}
@Override
public void onFailed(Task task, Throwable throwable) {
ProgressListNode node = nodes.remove(task);
if (node == null)
return;
Platform.runLater(() -> {
node.setThrowable(throwable);
finishedTasks.set(finishedTasks.getValue() + 1);
}); });
} }
}); });
} }
private static class ProgressListNode extends VBox { private static class ProgressListNode extends ListCell<Task> {
private final BorderPane borderPane = new BorderPane();
private final JFXProgressBar bar = new JFXProgressBar(); private final JFXProgressBar bar = new JFXProgressBar();
private final Label title = new Label(); private final Label title = new Label();
private final Label state = new Label(); private final Label state = new Label();
public ProgressListNode(Task task) { {
bar.progressProperty().bind(task.progressProperty());
title.setText(task.getName());
state.textProperty().bind(task.messageProperty());
BorderPane borderPane = new BorderPane();
borderPane.setLeft(title); borderPane.setLeft(title);
borderPane.setRight(state); borderPane.setRight(state);
getChildren().addAll(borderPane, bar); borderPane.setBottom(bar);
borderPane.setMinWidth(0);
borderPane.setPrefWidth(1);
setPadding(Insets.EMPTY);
bar.minWidthProperty().bind(widthProperty()); bar.minWidthProperty().bind(widthProperty());
bar.prefWidthProperty().bind(widthProperty()); bar.prefWidthProperty().bind(widthProperty());
bar.maxWidthProperty().bind(widthProperty()); bar.maxWidthProperty().bind(widthProperty());
} }
public void unbind() { @Override
bar.progressProperty().unbind(); protected void updateItem(Task item, boolean empty) {
state.textProperty().unbind(); boolean wasEmpty = isEmpty();
} Task oldTask = getItem();
public void setThrowable(Throwable throwable) { if (!wasEmpty && oldTask != null) {
unbind(); bar.progressProperty().unbind();
state.setText(throwable.getLocalizedMessage()); state.textProperty().unbind();
bar.setProgress(0); }
super.updateItem(item, empty);
if (empty || item == null) {
setGraphic(null);
} else {
setGraphic(borderPane);
bar.visibleProperty().bind(Bindings.createBooleanBinding(() -> item.progressProperty().get() != -1, item.progressProperty()));
bar.progressProperty().bind(item.progressProperty());
state.textProperty().bind(Bindings.createObjectBinding(() -> {
if (item.getState() == Task.TaskState.FAILED) {
return item.getLastException().getLocalizedMessage();
} else {
return item.messageProperty().get();
}
}, item.messageProperty(), item.stateProperty()));
title.setText(item.getName());
}
} }
} }
} }

View File

@ -18,10 +18,7 @@
package org.jackhuang.hmcl.task; package org.jackhuang.hmcl.task;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.property.ReadOnlyDoubleProperty; import javafx.beans.property.*;
import javafx.beans.property.ReadOnlyDoubleWrapper;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.ReadOnlyStringWrapper;
import org.jackhuang.hmcl.event.EventManager; import org.jackhuang.hmcl.event.EventManager;
import org.jackhuang.hmcl.util.AutoTypingMap; import org.jackhuang.hmcl.util.AutoTypingMap;
import org.jackhuang.hmcl.util.InvocationDispatcher; import org.jackhuang.hmcl.util.InvocationDispatcher;
@ -58,14 +55,18 @@ public abstract class Task {
this.significance = significance; this.significance = significance;
} }
private TaskState state = TaskState.READY; private ReadOnlyObjectWrapper<TaskState> state = new ReadOnlyObjectWrapper<>(this, "state", TaskState.READY);
public TaskState getState() { public TaskState getState() {
return state; return state.get();
} }
void setState(TaskState state) { void setState(TaskState state) {
this.state = state; this.state.setValue(state);
}
public ReadOnlyObjectProperty<TaskState> stateProperty() {
return state.getReadOnlyProperty();
} }
private Throwable lastException = null; private Throwable lastException = null;

View File

@ -211,6 +211,7 @@ public final class TaskExecutor {
task.onDone().fireEvent(new TaskEvent(this, task, false)); task.onDone().fireEvent(new TaskEvent(this, task, false));
taskListeners.forEach(it -> it.onFinished(task)); taskListeners.forEach(it -> it.onFinished(task));
} catch (InterruptedException e) { } catch (InterruptedException e) {
task.setLastException(e);
if (task.getSignificance().shouldLog()) { if (task.getSignificance().shouldLog()) {
Logging.LOG.log(Level.FINE, "Task aborted: " + task.getName()); Logging.LOG.log(Level.FINE, "Task aborted: " + task.getName());
} }

View File

@ -38,6 +38,7 @@ public abstract class TaskListener implements EventListener {
} }
public void onFailed(Task task, Throwable throwable) { public void onFailed(Task task, Throwable throwable) {
onFinished(task);
} }
public void onStop(boolean success, TaskExecutor executor) { public void onStop(boolean success, TaskExecutor executor) {