统一异步操作视觉状态
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package com.example.workutils.system.ui.async;
|
||||
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ProgressIndicator;
|
||||
@@ -9,6 +10,9 @@ import java.util.Objects;
|
||||
|
||||
public final class LoadingButtonBinder {
|
||||
|
||||
private static final double SPINNER_SIZE = 14;
|
||||
private static final PseudoClass RUNNING = PseudoClass.getPseudoClass("running");
|
||||
|
||||
private LoadingButtonBinder() {
|
||||
}
|
||||
|
||||
@@ -17,13 +21,17 @@ public final class LoadingButtonBinder {
|
||||
Objects.requireNonNull(button, "button");
|
||||
Objects.requireNonNull(controller, "controller");
|
||||
Node idleGraphic = button.getGraphic();
|
||||
String idleAccessibleText = button.getAccessibleText();
|
||||
preserveIdleWidth(button);
|
||||
ProgressIndicator spinner = createSpinner();
|
||||
|
||||
ChangeListener<OperationSnapshot> listener =
|
||||
(observable, oldValue, snapshot) ->
|
||||
apply(button, idleGraphic, spinner, snapshot, idleText);
|
||||
apply(button, idleGraphic, idleAccessibleText, spinner,
|
||||
snapshot, idleText);
|
||||
controller.snapshotProperty().addListener(listener);
|
||||
apply(button, idleGraphic, spinner, controller.snapshot(), idleText);
|
||||
apply(button, idleGraphic, idleAccessibleText, spinner,
|
||||
controller.snapshot(), idleText);
|
||||
}
|
||||
|
||||
public static String textFor(OperationSnapshot snapshot, String idleText) {
|
||||
@@ -37,13 +45,29 @@ public final class LoadingButtonBinder {
|
||||
};
|
||||
}
|
||||
|
||||
private static void apply(Button button, Node idleGraphic, ProgressIndicator spinner,
|
||||
OperationSnapshot snapshot, String idleText) {
|
||||
private static void apply(Button button, Node idleGraphic, String idleAccessibleText,
|
||||
ProgressIndicator spinner, OperationSnapshot snapshot,
|
||||
String idleText) {
|
||||
boolean busy = snapshot.state() == OperationState.RUNNING
|
||||
|| snapshot.state() == OperationState.CANCELLING;
|
||||
button.setText(textFor(snapshot, idleText));
|
||||
String visibleText = textFor(snapshot, idleText);
|
||||
button.setText(visibleText);
|
||||
button.setAccessibleText(busy ? visibleText : idleAccessibleText);
|
||||
button.setDisable(busy);
|
||||
button.setGraphic(busy ? spinner : idleGraphic);
|
||||
button.pseudoClassStateChanged(RUNNING, busy);
|
||||
}
|
||||
|
||||
private static void preserveIdleWidth(Button button) {
|
||||
button.applyCss();
|
||||
double idleWidth = button.prefWidth(-1);
|
||||
double configuredMinWidth = button.getMinWidth();
|
||||
if (configuredMinWidth >= 0) {
|
||||
idleWidth = Math.max(idleWidth, configuredMinWidth);
|
||||
}
|
||||
if (Double.isFinite(idleWidth) && idleWidth > 0) {
|
||||
button.setMinWidth(idleWidth);
|
||||
}
|
||||
}
|
||||
|
||||
private static String trimEllipsis(String message) {
|
||||
@@ -56,9 +80,10 @@ public final class LoadingButtonBinder {
|
||||
|
||||
private static ProgressIndicator createSpinner() {
|
||||
ProgressIndicator spinner = new ProgressIndicator();
|
||||
spinner.setPrefSize(14, 14);
|
||||
spinner.setMinSize(14, 14);
|
||||
spinner.setMaxSize(14, 14);
|
||||
spinner.getStyleClass().add("loading-spinner");
|
||||
spinner.setPrefSize(SPINNER_SIZE, SPINNER_SIZE);
|
||||
spinner.setMinSize(SPINNER_SIZE, SPINNER_SIZE);
|
||||
spinner.setMaxSize(SPINNER_SIZE, SPINNER_SIZE);
|
||||
return spinner;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,39 +8,53 @@ import javafx.scene.control.ProgressBar;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public final class OperationStatusPane extends HBox {
|
||||
|
||||
private final ProgressBar progressBar = new ProgressBar(0);
|
||||
private final Label messageLabel = new Label("就绪");
|
||||
private final Label percentageLabel = new Label();
|
||||
private final Button cancelButton;
|
||||
private final boolean cancellable;
|
||||
|
||||
/** @deprecated Use {@link #OperationStatusPane(AsyncOperationController, boolean)}. */
|
||||
@Deprecated
|
||||
public OperationStatusPane(AsyncOperationController<?> controller, int fontSize) {
|
||||
this(controller, fontSize, true);
|
||||
this(controller, true);
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link #OperationStatusPane(AsyncOperationController, boolean)}. */
|
||||
@Deprecated
|
||||
public OperationStatusPane(AsyncOperationController<?> controller, int fontSize,
|
||||
boolean cancellable) {
|
||||
this(controller, cancellable);
|
||||
}
|
||||
|
||||
public OperationStatusPane(AsyncOperationController<?> controller, boolean cancellable) {
|
||||
super(Theme.S2);
|
||||
this.cancellable = cancellable;
|
||||
Objects.requireNonNull(controller, "controller");
|
||||
setAlignment(Pos.CENTER_LEFT);
|
||||
getStyleClass().add("operation-status");
|
||||
|
||||
progressBar.setPrefWidth(240);
|
||||
progressBar.setAccessibleText("任务进度");
|
||||
messageLabel.setFont(Theme.base(fontSize));
|
||||
messageLabel.setTextFill(Theme.TEXT_SECONDARY);
|
||||
messageLabel.getStyleClass().add(Theme.CSS_SECONDARY_TEXT);
|
||||
messageLabel.setAccessibleText("任务状态");
|
||||
percentageLabel.getStyleClass().add(Theme.CSS_SECONDARY_TEXT);
|
||||
percentageLabel.setAccessibleText("任务进度百分比");
|
||||
percentageLabel.setFocusTraversable(false);
|
||||
HBox.setHgrow(messageLabel, Priority.ALWAYS);
|
||||
|
||||
cancelButton = com.example.workutils.system.ui.StyledButton.danger(
|
||||
"停止", Theme.base(fontSize));
|
||||
cancelButton.setAccessibleText("停止当前任务");
|
||||
cancelButton.setOnAction(event -> controller.cancel());
|
||||
|
||||
getChildren().addAll(progressBar, messageLabel);
|
||||
getChildren().addAll(progressBar, messageLabel, percentageLabel);
|
||||
if (cancellable) {
|
||||
cancelButton = new Button("停止");
|
||||
cancelButton.getStyleClass().add(Theme.CSS_BTN_DANGER);
|
||||
cancelButton.setAccessibleText("停止当前任务");
|
||||
cancelButton.setFocusTraversable(true);
|
||||
cancelButton.setOnAction(event -> controller.cancel());
|
||||
getChildren().add(cancelButton);
|
||||
} else {
|
||||
cancelButton = null;
|
||||
}
|
||||
controller.snapshotProperty().addListener(
|
||||
(observable, oldValue, value) -> render(value));
|
||||
@@ -49,16 +63,30 @@ public final class OperationStatusPane extends HBox {
|
||||
|
||||
private void render(OperationSnapshot snapshot) {
|
||||
progressBar.setProgress(snapshot.progress());
|
||||
boolean active = snapshot.state() == OperationState.RUNNING
|
||||
|| snapshot.state() == OperationState.CANCELLING;
|
||||
boolean determinate = active && Double.isFinite(snapshot.progress())
|
||||
&& snapshot.progress() >= 0;
|
||||
percentageLabel.setText(determinate
|
||||
? Math.round(snapshot.progress() * 100) + "%"
|
||||
: "");
|
||||
percentageLabel.setVisible(determinate);
|
||||
percentageLabel.setManaged(determinate);
|
||||
messageLabel.setText(snapshot.message().isBlank() ? defaultMessage(snapshot.state())
|
||||
: snapshot.message());
|
||||
String stateDescription = "任务状态:" + messageLabel.getText();
|
||||
String stateDescription = "任务状态:" + messageLabel.getText()
|
||||
+ (determinate ? ",进度 " + percentageLabel.getText() : "");
|
||||
setAccessibleText(stateDescription);
|
||||
messageLabel.setAccessibleText(stateDescription);
|
||||
progressBar.setAccessibleText("任务进度:" + stateDescription);
|
||||
cancelButton.setVisible(cancellable
|
||||
&& snapshot.state() == OperationState.RUNNING);
|
||||
cancelButton.setManaged(cancelButton.isVisible());
|
||||
cancelButton.setDisable(snapshot.state() != OperationState.RUNNING);
|
||||
percentageLabel.setAccessibleText(determinate
|
||||
? "任务进度:" + percentageLabel.getText()
|
||||
: "任务进度未知");
|
||||
if (cancelButton != null) {
|
||||
cancelButton.setVisible(snapshot.state() == OperationState.RUNNING);
|
||||
cancelButton.setManaged(cancelButton.isVisible());
|
||||
cancelButton.setDisable(snapshot.state() != OperationState.RUNNING);
|
||||
}
|
||||
|
||||
getStyleClass().removeAll(
|
||||
"operation-status-success", "operation-status-error");
|
||||
|
||||
Reference in New Issue
Block a user