优化 SpinnerPane (#5533)
This commit is contained in:
@@ -18,8 +18,6 @@
|
|||||||
package org.jackhuang.hmcl.ui.construct;
|
package org.jackhuang.hmcl.ui.construct;
|
||||||
|
|
||||||
import com.jfoenix.controls.JFXSpinner;
|
import com.jfoenix.controls.JFXSpinner;
|
||||||
import javafx.beans.DefaultProperty;
|
|
||||||
import javafx.beans.InvalidationListener;
|
|
||||||
import javafx.beans.property.*;
|
import javafx.beans.property.*;
|
||||||
import javafx.event.Event;
|
import javafx.event.Event;
|
||||||
import javafx.event.EventHandler;
|
import javafx.event.EventHandler;
|
||||||
@@ -33,14 +31,12 @@ import org.jackhuang.hmcl.ui.FXUtils;
|
|||||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||||
|
|
||||||
@DefaultProperty("content")
|
/// A spinner pane that can show spinner, failed reason, or content.
|
||||||
public class SpinnerPane extends Control {
|
public class SpinnerPane extends Control {
|
||||||
private final ObjectProperty<Node> content = new SimpleObjectProperty<>(this, "content");
|
private static final String DEFAULT_STYLE_CLASS = "spinner-pane";
|
||||||
private final BooleanProperty loading = new SimpleBooleanProperty(this, "loading");
|
|
||||||
private final StringProperty failedReason = new SimpleStringProperty(this, "failedReason");
|
|
||||||
|
|
||||||
public SpinnerPane() {
|
public SpinnerPane() {
|
||||||
getStyleClass().add("spinner-pane");
|
getStyleClass().add(DEFAULT_STYLE_CLASS);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void showSpinner() {
|
public void showSpinner() {
|
||||||
@@ -52,60 +48,136 @@ public class SpinnerPane extends Control {
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Node getContent() {
|
private void updateContent() {
|
||||||
return content.get();
|
if (getSkin() instanceof Skin skin) {
|
||||||
|
skin.updateContent();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ObjectProperty<Node> content;
|
||||||
|
|
||||||
public ObjectProperty<Node> contentProperty() {
|
public ObjectProperty<Node> contentProperty() {
|
||||||
|
if (content == null)
|
||||||
|
content = new ObjectPropertyBase<>() {
|
||||||
|
@Override
|
||||||
|
public Object getBean() {
|
||||||
|
return SpinnerPane.this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "content";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void invalidated() {
|
||||||
|
updateContent();
|
||||||
|
}
|
||||||
|
};
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setContent(Node content) {
|
public Node getContent() {
|
||||||
this.content.set(content);
|
return contentProperty().get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isLoading() {
|
public void setContent(Node content) {
|
||||||
return loading.get();
|
contentProperty().set(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private BooleanProperty loading;
|
||||||
|
|
||||||
public BooleanProperty loadingProperty() {
|
public BooleanProperty loadingProperty() {
|
||||||
|
if (loading == null)
|
||||||
|
loading = new BooleanPropertyBase() {
|
||||||
|
@Override
|
||||||
|
public Object getBean() {
|
||||||
|
return SpinnerPane.this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "loading";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void invalidated() {
|
||||||
|
updateContent();
|
||||||
|
}
|
||||||
|
};
|
||||||
return loading;
|
return loading;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setLoading(boolean loading) {
|
public boolean isLoading() {
|
||||||
this.loading.set(loading);
|
return loading != null && loading.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getFailedReason() {
|
public void setLoading(boolean loading) {
|
||||||
return failedReason.get();
|
loadingProperty().set(loading);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private StringProperty failedReason;
|
||||||
|
|
||||||
public StringProperty failedReasonProperty() {
|
public StringProperty failedReasonProperty() {
|
||||||
|
if (failedReason == null)
|
||||||
|
failedReason = new StringPropertyBase() {
|
||||||
|
@Override
|
||||||
|
public Object getBean() {
|
||||||
|
return SpinnerPane.this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "failedReason";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void invalidated() {
|
||||||
|
updateContent();
|
||||||
|
}
|
||||||
|
};
|
||||||
return failedReason;
|
return failedReason;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFailedReason(String failedReason) {
|
public String getFailedReason() {
|
||||||
this.failedReason.set(failedReason);
|
return failedReason != null ? failedReason.get() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setFailedReason(String failedReason) {
|
||||||
|
failedReasonProperty().set(failedReason);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ObjectProperty<EventHandler<Event>> onFailedAction;
|
||||||
|
|
||||||
public final ObjectProperty<EventHandler<Event>> onFailedActionProperty() {
|
public final ObjectProperty<EventHandler<Event>> onFailedActionProperty() {
|
||||||
return onFailedAction;
|
if (onFailedAction == null) {
|
||||||
|
onFailedAction = new ObjectPropertyBase<>() {
|
||||||
|
@Override
|
||||||
|
public Object getBean() {
|
||||||
|
return SpinnerPane.this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final void setOnFailedAction(EventHandler<Event> value) {
|
@Override
|
||||||
onFailedActionProperty().set(value);
|
public String getName() {
|
||||||
|
return "onFailedAction";
|
||||||
}
|
}
|
||||||
|
|
||||||
public final EventHandler<Event> getOnFailedAction() {
|
|
||||||
return onFailedActionProperty().get();
|
|
||||||
}
|
|
||||||
|
|
||||||
private final ObjectProperty<EventHandler<Event>> onFailedAction = new SimpleObjectProperty<EventHandler<Event>>(this, "onFailedAction") {
|
|
||||||
@Override
|
@Override
|
||||||
protected void invalidated() {
|
protected void invalidated() {
|
||||||
setEventHandler(FAILED_ACTION, get());
|
setEventHandler(FAILED_ACTION, get());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
return onFailedAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final EventHandler<Event> getOnFailedAction() {
|
||||||
|
return onFailedAction != null ? onFailedAction.get() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void setOnFailedAction(EventHandler<Event> value) {
|
||||||
|
onFailedActionProperty().set(value);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected SkinBase<SpinnerPane> createDefaultSkin() {
|
protected SkinBase<SpinnerPane> createDefaultSkin() {
|
||||||
@@ -113,47 +185,58 @@ public class SpinnerPane extends Control {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static final class Skin extends SkinBase<SpinnerPane> {
|
private static final class Skin extends SkinBase<SpinnerPane> {
|
||||||
private final JFXSpinner spinner = new JFXSpinner();
|
|
||||||
private final StackPane contentPane = new StackPane();
|
|
||||||
private final StackPane topPane = new StackPane();
|
|
||||||
private final TransitionPane root = new TransitionPane();
|
private final TransitionPane root = new TransitionPane();
|
||||||
private final StackPane failedPane = new StackPane();
|
|
||||||
private final Label failedReasonLabel = new Label();
|
|
||||||
@SuppressWarnings("FieldCanBeLocal") // prevent from gc.
|
|
||||||
private final InvalidationListener observer;
|
|
||||||
|
|
||||||
Skin(SpinnerPane control) {
|
Skin(SpinnerPane control) {
|
||||||
super(control);
|
super(control);
|
||||||
|
|
||||||
topPane.getChildren().setAll(spinner);
|
updateContent();
|
||||||
topPane.getStyleClass().add("notice-pane");
|
this.getChildren().setAll(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
private StackPane contentPane;
|
||||||
|
private StackPane spinnerPane;
|
||||||
|
private StackPane failedPane;
|
||||||
|
private Label failedReasonLabel;
|
||||||
|
|
||||||
|
void updateContent() {
|
||||||
|
SpinnerPane control = getSkinnable();
|
||||||
|
|
||||||
|
Node nextContent;
|
||||||
|
if (control.isLoading()) {
|
||||||
|
if (spinnerPane == null) {
|
||||||
|
spinnerPane = new StackPane(new JFXSpinner());
|
||||||
|
spinnerPane.getStyleClass().add("notice-pane");
|
||||||
|
}
|
||||||
|
nextContent = spinnerPane;
|
||||||
|
} else if (control.getFailedReason() != null) {
|
||||||
|
if (failedPane == null) {
|
||||||
|
failedReasonLabel = new Label();
|
||||||
|
failedPane = new StackPane(failedReasonLabel);
|
||||||
failedPane.getStyleClass().add("notice-pane");
|
failedPane.getStyleClass().add("notice-pane");
|
||||||
failedPane.getChildren().setAll(failedReasonLabel);
|
FXUtils.onClicked(failedPane, () -> control.fireEvent(new Event(SpinnerPane.FAILED_ACTION)));
|
||||||
FXUtils.onClicked(failedPane, () -> {
|
}
|
||||||
EventHandler<Event> action = control.getOnFailedAction();
|
failedReasonLabel.setText(control.getFailedReason());
|
||||||
if (action != null)
|
nextContent = failedPane;
|
||||||
action.handle(new Event(FAILED_ACTION));
|
} else {
|
||||||
});
|
if (contentPane == null) {
|
||||||
|
contentPane = new StackPane();
|
||||||
|
}
|
||||||
|
|
||||||
FXUtils.onChangeAndOperate(getSkinnable().content, newValue -> {
|
Node content = control.getContent();
|
||||||
if (newValue == null) {
|
if (content != null)
|
||||||
|
contentPane.getChildren().setAll(content);
|
||||||
|
else
|
||||||
contentPane.getChildren().clear();
|
contentPane.getChildren().clear();
|
||||||
} else {
|
|
||||||
contentPane.getChildren().setAll(newValue);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
getChildren().setAll(root);
|
|
||||||
|
|
||||||
observer = FXUtils.observeWeak(() -> {
|
nextContent = contentPane;
|
||||||
if (getSkinnable().getFailedReason() != null) {
|
|
||||||
root.setContent(failedPane, ContainerAnimations.FADE);
|
|
||||||
failedReasonLabel.setText(getSkinnable().getFailedReason());
|
|
||||||
} else if (getSkinnable().isLoading()) {
|
|
||||||
root.setContent(topPane, ContainerAnimations.FADE);
|
|
||||||
} else {
|
|
||||||
root.setContent(contentPane, ContainerAnimations.FADE);
|
|
||||||
}
|
}
|
||||||
}, getSkinnable().loadingProperty(), getSkinnable().failedReasonProperty());
|
|
||||||
|
if (nextContent != failedPane && failedReasonLabel != null) {
|
||||||
|
failedReasonLabel.setText(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
root.setContent(nextContent, ContainerAnimations.FADE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user