Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 79 additions & 22 deletions HMCL/src/main/java/com/jfoenix/controls/JFXDialog.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,7 @@
import com.jfoenix.transitions.CachedTransition;
import javafx.animation.*;
import javafx.beans.DefaultProperty;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ObjectPropertyBase;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.*;
import javafx.css.*;
import javafx.event.Event;
import javafx.event.EventHandler;
Expand All @@ -45,11 +42,15 @@
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.util.Duration;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.animation.Motion;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

/// Note: for JFXDialog to work properly, the root node **MUST**
/// be of type [StackPane]
Expand Down Expand Up @@ -119,7 +120,12 @@ public JFXDialog(StackPane dialogContainer, Region content, DialogTransition tra
/// - BOTTOM
/// - LEFT
public JFXDialog(StackPane dialogContainer, Region content, DialogTransition transitionType, boolean overlayClose) {
this(dialogContainer, content, null, transitionType, overlayClose);
}

public JFXDialog(StackPane dialogContainer, Region content, @Nullable StackPane overlayPane, DialogTransition transitionType, boolean overlayClose) {
setOverlayClose(overlayClose);
setOverlayPane(overlayPane);
initialize();
setContent(content);
setDialogContainer(dialogContainer);
Expand All @@ -129,35 +135,55 @@ public JFXDialog(StackPane dialogContainer, Region content, DialogTransition tra
}

private void initChangeListeners() {
overlayCloseProperty().addListener((o, oldVal, newVal) -> {
if (newVal) {
this.addEventHandler(MouseEvent.MOUSE_PRESSED, closeHandler);
FXUtils.onChange(overlayCloseProperty(), b -> {
if (b) {
getOverlayPane().addEventHandler(MouseEvent.MOUSE_PRESSED, closeHandler);
} else {
this.removeEventHandler(MouseEvent.MOUSE_PRESSED, closeHandler);
getOverlayPane().removeEventHandler(MouseEvent.MOUSE_PRESSED, closeHandler);
}
});
this.overlayPaneProperty().addListener((observable, oldValue, newValue) -> {
if (oldValue == null) oldValue = JFXDialog.this;
if (newValue == null) newValue = JFXDialog.this;
if (oldValue == newValue) return;
oldValue.getStyleClass().remove("jfx-dialog-overlay-pane");
oldValue.setBackground(null);
oldValue.removeEventHandler(MouseEvent.MOUSE_PRESSED, closeHandler);
newValue.getStyleClass().add("jfx-dialog-overlay-pane");
newValue.setBackground(new Background(new BackgroundFill(Color.rgb(0, 0, 0, 0.1), null, null)));
// close the dialog if clicked on the overlay pane
if (overlayClose.get()) {
newValue.addEventHandler(MouseEvent.MOUSE_PRESSED, closeHandler);
}
});
}

private void initialize() {
this.setVisible(false);
this.getStyleClass().add(DEFAULT_STYLE_CLASS);
this.transitionType.addListener((o, oldVal, newVal) -> {
animation = getShowAnimation(transitionType.get());
});
this.setVisible(false);

FXUtils.onChange(overlayPane, t -> animation = getShowAnimation(transitionType.get()));
FXUtils.onChange(transitionType, t -> animation = getShowAnimation(transitionType.get()));

contentHolder = new StackPane();
contentHolder.setVisible(false);
JFXDepthManager.setDepth(contentHolder, 4);
contentHolder.setPickOnBounds(false);
// ensure stackpane is never resized beyond it's preferred size
contentHolder.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
this.getChildren().add(contentHolder);
this.getStyleClass().add("jfx-dialog-overlay-pane");
StackPane.setAlignment(contentHolder, Pos.CENTER);
this.setBackground(new Background(new BackgroundFill(Color.rgb(0, 0, 0, 0.1), null, null)));

FXUtils.onChangeAndOperate(overlayPane, t -> setPickOnBounds(t == this || t == null));

getOverlayPane().setVisible(false);
getOverlayPane().getStyleClass().add("jfx-dialog-overlay-pane");
getOverlayPane().setBackground(new Background(new BackgroundFill(Color.rgb(0, 0, 0, 0.1), null, null)));
// close the dialog if clicked on the overlay pane
if (overlayClose.get()) {
this.addEventHandler(MouseEvent.MOUSE_PRESSED, closeHandler);
getOverlayPane().addEventHandler(MouseEvent.MOUSE_PRESSED, closeHandler);
}

// prevent propagating the events to overlay pane
contentHolder.addEventHandler(MouseEvent.ANY, Event::consume);
}
Expand Down Expand Up @@ -214,6 +240,21 @@ public final void setOverlayClose(final boolean overlayClose) {
this.overlayCloseProperty().set(overlayClose);
}

private final ObjectProperty<StackPane> overlayPane = new SimpleObjectProperty<>(this);

public final ObjectProperty<StackPane> overlayPaneProperty() {
return this.overlayPane;
}

@NotNull
public final StackPane getOverlayPane() {
return Objects.requireNonNullElse(this.overlayPaneProperty().get(), this);
}

public final void setOverlayPane(final StackPane overlayPane) {
this.overlayPaneProperty().set(overlayPane == null ? this : overlayPane);
}

/// if sets to true, the content of dialog container will be cached and replaced with an image
/// when displaying the dialog (better performance).
/// this is recommended if the content behind the dialog will not change during the showing
Expand Down Expand Up @@ -274,8 +315,11 @@ private void showDialog() {
if (animation != null) {
animation.play();
} else {
setVisible(true);
setOpacity(1);
this.setVisible(true);
contentHolder.setVisible(true);
contentHolder.setOpacity(1);
getOverlayPane().setVisible(true);
getOverlayPane().setOpacity(1);
Event.fireEvent(JFXDialog.this, new JFXDialogEvent(JFXDialogEvent.OPENED));
}
}
Expand All @@ -291,8 +335,11 @@ public void close() {
closeDialog();
});
} else {
setOpacity(0);
setVisible(false);
this.setVisible(false);
contentHolder.setOpacity(0);
contentHolder.setVisible(false);
getOverlayPane().setOpacity(0);
getOverlayPane().setVisible(false);
closeDialog();
}
}
Expand Down Expand Up @@ -340,6 +387,8 @@ private Transition getShowAnimation(DialogTransition transitionType) {

private void resetProperties() {
this.setVisible(false);
contentHolder.setVisible(false);
getOverlayPane().setVisible(false);
contentHolder.setTranslateX(0);
contentHolder.setTranslateY(0);
contentHolder.setScaleX(1);
Expand All @@ -354,17 +403,25 @@ private final class CenterTransition extends CachedTransition {
new KeyFrame(Duration.ZERO,
new KeyValue(contentHolder.scaleXProperty(), INITIAL_SCALE, INTERPOLATOR),
new KeyValue(contentHolder.scaleYProperty(), INITIAL_SCALE, INTERPOLATOR),
new KeyValue(JFXDialog.this.visibleProperty(), false, Motion.LINEAR)
new KeyValue(JFXDialog.this.visibleProperty(), false, Motion.LINEAR),
new KeyValue(contentHolder.visibleProperty(), false, Motion.LINEAR),
new KeyValue(getOverlayPane().visibleProperty(), false, Motion.LINEAR)
),
new KeyFrame(Duration.millis(10),
new KeyValue(JFXDialog.this.visibleProperty(), true, Motion.LINEAR),
new KeyValue(JFXDialog.this.opacityProperty(), 0, INTERPOLATOR)
new KeyValue(contentHolder.visibleProperty(), true, Motion.LINEAR),
new KeyValue(contentHolder.opacityProperty(), 0, INTERPOLATOR),
new KeyValue(getOverlayPane().visibleProperty(), true, Motion.LINEAR),
new KeyValue(getOverlayPane().opacityProperty(), 0, INTERPOLATOR)
),
new KeyFrame(Motion.EXTRA_LONG4,
new KeyValue(contentHolder.scaleXProperty(), 1, INTERPOLATOR),
new KeyValue(contentHolder.scaleYProperty(), 1, INTERPOLATOR),
new KeyValue(JFXDialog.this.visibleProperty(), true, Motion.LINEAR),
new KeyValue(JFXDialog.this.opacityProperty(), 1, INTERPOLATOR)
new KeyValue(contentHolder.visibleProperty(), true, Motion.LINEAR),
new KeyValue(contentHolder.opacityProperty(), 1, INTERPOLATOR),
new KeyValue(getOverlayPane().visibleProperty(), true, Motion.LINEAR),
new KeyValue(getOverlayPane().opacityProperty(), 1, INTERPOLATOR)
))
);
// reduce the number to increase the shifting , increase number to reduce shifting
Expand Down
31 changes: 17 additions & 14 deletions HMCL/src/main/java/org/jackhuang/hmcl/ui/DialogUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,21 +55,23 @@ private DialogUtils() {
/// @param content the dialog content
public static void show(Decorator decorator, Node content) {
StackPane dialogContainer = decorator.getDialogContainer();
StackPane dialogOverlayPane = decorator.getDialogOverlayPane();
if (decorator.getRoot().getScene() == null) {
Platform.runLater(() -> showInDecorator(decorator, dialogContainer, content));
Platform.runLater(() -> showInDecorator(decorator, dialogContainer, content, dialogOverlayPane));
return;
}

showInDecorator(decorator, dialogContainer, content);
showInDecorator(decorator, dialogContainer, content, dialogOverlayPane);
}

/// Shows content in a decorator's resolved dialog container.
///
/// @param decorator the main-window decorator
/// @param dialogContainer the container resolved before any deferred execution
/// @param content the dialog content
private static void showInDecorator(Decorator decorator, StackPane dialogContainer, Node content) {
show(dialogContainer, content, dialog -> {
/// @param dialogOverlayPane the dialog overlay pane
private static void showInDecorator(Decorator decorator, StackPane dialogContainer, Node content, StackPane dialogOverlayPane) {
show(dialogContainer, content, dialogOverlayPane, dialog -> {
JFXDialogPane pane = (JFXDialogPane) dialog.getContent();
decorator.capableDraggingWindow(dialog);
decorator.forbidDraggingWindow(pane);
Expand All @@ -78,24 +80,24 @@ private static void showInDecorator(Decorator decorator, StackPane dialogContain
}

public static void show(StackPane container, Node content) {
show(container, content, null);
show(container, content, null, null);
}

public static void show(StackPane container, Node content, @Nullable Consumer<JFXDialog> onDialogCreated) {
public static void show(StackPane container, Node content, @Nullable StackPane overlayPane, @Nullable Consumer<JFXDialog> onDialogCreated) {
FXUtils.checkFxUserThread();

JFXDialog dialog = (JFXDialog) container.getProperties().get(PROPERTY_DIALOG_INSTANCE);
JFXDialogPane dialogPane = (JFXDialogPane) container.getProperties().get(PROPERTY_DIALOG_PANE_INSTANCE);

if (dialog == null) {
dialog = new JFXDialog(AnimationUtils.isAnimationEnabled()
? JFXDialog.DialogTransition.CENTER
: JFXDialog.DialogTransition.NONE);
dialogPane = new JFXDialogPane();

dialog.setContent(dialogPane);
dialog.setDialogContainer(container);
dialog.setOverlayClose(false);
dialog = new JFXDialog(
container,
dialogPane,
overlayPane,
AnimationUtils.isAnimationEnabled() ? JFXDialog.DialogTransition.CENTER : JFXDialog.DialogTransition.NONE,
false
);

container.getProperties().put(PROPERTY_DIALOG_INSTANCE, dialog);
container.getProperties().put(PROPERTY_DIALOG_PANE_INSTANCE, dialogPane);
Expand Down Expand Up @@ -145,7 +147,8 @@ public void changed(ObservableValue<? extends Boolean> observable, Boolean oldVa
/// @param content the dialog content
public static void showLater(Decorator decorator, Node content) {
StackPane dialogContainer = decorator.getDialogContainer();
Runnable showDialogAction = () -> showInDecorator(decorator, dialogContainer, content);
StackPane dialogOverlayPane = decorator.getDialogOverlayPane();
Runnable showDialogAction = () -> showInDecorator(decorator, dialogContainer, content, dialogOverlayPane);
if (decorator.getRoot().getScene() == null) {
Platform.runLater(() -> showLater(dialogContainer, showDialogAction));
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,13 @@ public StackPane getDialogContainer() {
return mainWindowPane;
}

/// Returns the pane that behaves as the overlay of dialogs.
///
/// @return the dialog overlay pane
public StackPane getDialogOverlayPane() {
return mainWindowPane.getDialogOverlayPane();
}

/// Returns the navigation stack rendered by the main window.
///
/// @return this decorator's navigator
Expand Down Expand Up @@ -1111,5 +1118,4 @@ private void resetRootTransform() {
root.setScaleY(1);
root.setScaleZ(1);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,7 @@
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.util.Duration;
Expand Down Expand Up @@ -75,6 +67,9 @@ final class MainWindowPane extends StackPane {
/// The transition container used when the title-bar state changes.
private final TransitionPane navBarPane;

/// The pane that behaves as the overlay of dialogs.
private final StackPane dialogOverlayPane;

/// Retains listener delegates that are registered through weak listener wrappers.
@SuppressWarnings("FieldCanBeLocal")
private final WeakListenerHolder holder = new WeakListenerHolder();
Expand Down Expand Up @@ -112,11 +107,12 @@ final class MainWindowPane extends StackPane {
center.getChildren().setAll(decorator.getNavigator());
frame.setCenter(center);

HBox rightButtonsContainer = createWindowButtons();
Rectangle buttonsPlaceholder = new Rectangle();
buttonsPlaceholder.setFill(null);
titleBar = new BorderPane();
titleBar.setPickOnBounds(false);
titleBar.getStyleClass().add("jfx-tool-bar");
titleBar.setRight(rightButtonsContainer);
titleBar.setRight(buttonsPlaceholder);

navBarPane = new TransitionPane();
titleBar.setCenter(navBarPane);
Expand All @@ -133,7 +129,18 @@ final class MainWindowPane extends StackPane {

decorator.capableDraggingWindow(titleBar);

getChildren().setAll(backgroundNode, frame);
dialogOverlayPane = new StackPane();
dialogOverlayPane.setVisible(false);

HBox rightButtonsContainer = createWindowButtons();
AnchorPane buttonsLayer = new AnchorPane(rightButtonsContainer);
buttonsLayer.setPickOnBounds(false);
AnchorPane.setTopAnchor(rightButtonsContainer, 0D);
AnchorPane.setRightAnchor(rightButtonsContainer, 0D);
buttonsPlaceholder.heightProperty().bind(rightButtonsContainer.heightProperty());
buttonsPlaceholder.widthProperty().bind(rightButtonsContainer.widthProperty());

getChildren().setAll(backgroundNode, frame, dialogOverlayPane, buttonsLayer);
}

/// Updates the content-corner shape for an edge-to-edge window state.
Expand Down Expand Up @@ -312,6 +319,10 @@ private Node createNavBar(DecoratorPage.State state) {
return navBar;
}

public StackPane getDialogOverlayPane() {
return dialogOverlayPane;
}

/// Produces directional transitions for page-title changes.
private enum NavBarAnimations implements TransitionPane.AnimationProducer {
/// Moves the next title in from the right.
Expand Down