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
59 changes: 58 additions & 1 deletion HMCL/src/main/java/com/jfoenix/controls/JFXRippler.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import javafx.css.converter.BooleanConverter;
import javafx.css.converter.PaintConverter;
import javafx.css.converter.SizeConverter;
import javafx.event.EventHandler;
import javafx.geometry.Bounds;
import javafx.scene.CacheHint;
import javafx.scene.Group;
Expand All @@ -45,6 +46,8 @@
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Shape;
import javafx.util.Duration;
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
import org.jackhuang.hmcl.ui.animation.Motion;

import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
Expand Down Expand Up @@ -76,6 +79,8 @@ public enum RipplerMask {
protected RippleGenerator rippler;
protected Pane ripplerPane;
protected Node control;
private Animation coverAnimation;
private Rectangle hoverOverlay;

protected static final double RIPPLE_MAX_RADIUS = 300;
private static final Interpolator RIPPLE_INTERPOLATOR = Interpolator.SPLINE(0.0825,
Expand Down Expand Up @@ -128,14 +133,55 @@ public JFXRippler(Node control, RipplerMask mask, RipplerPos pos) {
setCache(true);
setCacheHint(CacheHint.SPEED);
setCacheShape(true);

EventHandler<MouseEvent> mouseEventHandler;
if (AnimationUtils.isAnimationEnabled()) {
mouseEventHandler = event -> {
if (coverAnimation != null) {
coverAnimation.stop();
}

boolean isEntered = event.getEventType() == MouseEvent.MOUSE_ENTERED;
coverAnimation = new Timeline(new KeyFrame(Motion.SHORT4,
new KeyValue(hoverOverlay.opacityProperty(), isEntered ? 1 : 0, isEntered ? Motion.EASE_IN : Motion.EASE_OUT)));
Comment thread
CiiLu marked this conversation as resolved.
coverAnimation.play();
};
Comment thread
CiiLu marked this conversation as resolved.
Comment on lines +139 to +148

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

在启用动画的分支中,如果 hoverOverlaynull,调用 hoverOverlay.opacityProperty() 将会抛出 NullPointerException。虽然 hoverOverlaycreateRippleUI() 中进行了初始化,但为了保持与其他方法(如 interpolateBackgroundresetRippler)中对 hoverOverlay 进行空检查的一致性,建议在此处也添加空检查。

Suggested change
mouseEventHandler = event -> {
if (coverAnimation != null) {
coverAnimation.stop();
}
boolean isEntered = event.getEventType() == MouseEvent.MOUSE_ENTERED;
coverAnimation = new Timeline(new KeyFrame(Motion.SHORT4,
new KeyValue(hoverOverlay.opacityProperty(), isEntered ? 1 : 0, isEntered ? Motion.EASE_IN : Motion.EASE_OUT)));
coverAnimation.play();
};
mouseEventHandler = event -> {
if (hoverOverlay == null) {
return;
}
if (coverAnimation != null) {
coverAnimation.stop();
}
boolean isEntered = event.getEventType() == MouseEvent.MOUSE_ENTERED;
coverAnimation = new Timeline(new KeyFrame(Motion.SHORT4,
new KeyValue(hoverOverlay.opacityProperty(), isEntered ? 1 : 0, isEntered ? Motion.EASE_IN : Motion.EASE_OUT)));
coverAnimation.play();
};

} else {
mouseEventHandler = event ->
interpolateBackground(event.getEventType() == MouseEvent.MOUSE_ENTERED ? 1 : 0);
}

addEventHandler(MouseEvent.MOUSE_ENTERED, mouseEventHandler);
addEventHandler(MouseEvent.MOUSE_EXITED, mouseEventHandler);
}

private void interpolateBackground(double frac) {
if (hoverOverlay == null) return;
hoverOverlay.setOpacity(frac);
}
Comment thread
CiiLu marked this conversation as resolved.

protected final void createRippleUI() {
// create rippler panels
rippler = new RippleGenerator();
ripplerPane = new StackPane();
ripplerPane.setMouseTransparent(true);
ripplerPane.getChildren().add(rippler);

hoverOverlay = new Rectangle();
hoverOverlay.setManaged(false);
hoverOverlay.setCache(true);
hoverOverlay.setCacheHint(CacheHint.SPEED);
hoverOverlay.setOpacity(0);

hoverOverlay.fillProperty().bind(Bindings.createObjectBinding(() -> {
Paint currentFill = getRipplerFill();
if (currentFill instanceof Color fill) {
return Color.color(fill.getRed(), fill.getGreen(), fill.getBlue(), 0.15);
} else {
return currentFill;
}
}, ripplerFillProperty()));

ripplerPane.getChildren().addAll(hoverOverlay, rippler);
getChildren().add(ripplerPane);
}

Expand Down Expand Up @@ -561,6 +607,17 @@ private void resetClip() {
protected void resetRippler() {
resetOverLay();
resetClip();

if (hoverOverlay != null && control != null) {
Bounds bounds = control.getBoundsInParent();
double diffMinX = Math.abs(control.getBoundsInLocal().getMinX() - control.getLayoutBounds().getMinX());
double diffMinY = Math.abs(control.getBoundsInLocal().getMinY() - control.getLayoutBounds().getMinY());
hoverOverlay.setX(bounds.getMinX() + diffMinX - snappedLeftInset());
hoverOverlay.setY(bounds.getMinY() + diffMinY - snappedTopInset());
hoverOverlay.setWidth(control.getLayoutBounds().getWidth());
hoverOverlay.setHeight(control.getLayoutBounds().getHeight());
hoverOverlay.setClip(getMask());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

resetRippler is called frequently during layout changes. Calling getMask() here on every pulse is inefficient because it creates a new Node object each time. This can lead to excessive object allocation and GC pressure during UI transitions (e.g., when the control is resizing). Consider caching the mask node or only updating it when the mask type or significant bounds change.

}
Comment on lines +611 to +620

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

getMaskType() == RipplerMask.RECT 时,hoverOverlay 本身已经是一个大小完全相同的矩形,因此不需要再设置裁剪遮罩(Clip)。在频繁触发的 resetRippler() 中,每次都创建并设置新的 Rectangle 遮罩会带来不必要的性能开销和 GC 压力。建议在遮罩类型为 RECT 时将 Clip 设为 null 以进行优化。

        if (hoverOverlay != null && control != null) {
            Bounds bounds = control.getBoundsInParent();
            double diffMinX = Math.abs(control.getBoundsInLocal().getMinX() - control.getLayoutBounds().getMinX());
            double diffMinY = Math.abs(control.getBoundsInLocal().getMinY() - control.getLayoutBounds().getMinY());
            hoverOverlay.setX(bounds.getMinX() + diffMinX - snappedLeftInset());
            hoverOverlay.setY(bounds.getMinY() + diffMinY - snappedTopInset());
            hoverOverlay.setWidth(control.getLayoutBounds().getWidth());
            hoverOverlay.setHeight(control.getLayoutBounds().getHeight());
            if (getMaskType() == RipplerMask.RECT) {
                hoverOverlay.setClip(null);
            } else {
                hoverOverlay.setClip(getMask());
            }
        }

}

/***************************************************************************
Expand Down
2 changes: 0 additions & 2 deletions HMCL/src/main/java/com/jfoenix/skins/JFXTabPaneSkin.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
import javafx.geometry.Insets;
import javafx.geometry.Side;
import javafx.geometry.VPos;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.control.skin.TabPaneSkin;
Expand Down Expand Up @@ -1126,7 +1125,6 @@ public HeaderControl(ArrowPosition pos) {
StackPane container = new StackPane(arrowButton);
container.getStyleClass().add("container");
container.setPadding(new Insets(7));
container.setCursor(Cursor.HAND);

container.setOnMousePressed(press -> {
offsetProperty.set(header.scrollOffset);
Expand Down
2 changes: 0 additions & 2 deletions HMCL/src/main/java/com/jfoenix/skins/JFXToggleButtonSkin.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
import com.jfoenix.transitions.JFXKeyValue;
import javafx.animation.Interpolator;
import javafx.geometry.Insets;
import javafx.scene.Cursor;
import javafx.scene.control.skin.ToggleButtonSkin;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Circle;
Expand Down Expand Up @@ -86,7 +85,6 @@ public JFXToggleButtonSkin(JFXToggleButton toggleButton) {

final StackPane main = new StackPane();
main.getChildren().setAll(line, rippler);
main.setCursor(Cursor.HAND);

// show focus traversal effect
getSkinnable().armedProperty().addListener((o, oldVal, newVal) -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,6 @@ private static final class InstallerItemSkin extends SkinBase<InstallerItem> {
event.consume();
}
});
pane.setCursor(Cursor.HAND);
} else {
container.setOnMouseClicked(null);
pane.setCursor(Cursor.DEFAULT);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import com.jfoenix.effects.JFXDepthManager;
import javafx.beans.binding.Bindings;
import javafx.geometry.Pos;
import javafx.scene.Cursor;
import javafx.scene.canvas.Canvas;
import javafx.scene.control.Label;
import javafx.scene.control.SkinBase;
Expand Down Expand Up @@ -52,7 +51,6 @@ public AccountListItemSkin(AccountListItem skinnable) {
super(skinnable);

BorderPane root = new BorderPane();
root.setCursor(Cursor.HAND);
FXUtils.onClicked(root, skinnable::fire);

JFXRadioButton chkSelected = new JFXRadioButton();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,20 @@
package org.jackhuang.hmcl.ui.construct;

import com.jfoenix.controls.JFXRippler;
import javafx.animation.Transition;
import javafx.css.*;
import javafx.css.CssMetaData;
import javafx.css.Styleable;
import javafx.css.StyleableObjectProperty;
import javafx.css.StyleableProperty;
import javafx.css.converter.PaintConverter;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Rectangle;
import org.jackhuang.hmcl.theme.Themes;
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
import org.jackhuang.hmcl.ui.animation.Motion;

import java.util.ArrayList;
import java.util.List;
Expand Down Expand Up @@ -63,8 +60,6 @@ protected Node getMask() {
}
};

private Transition coverAnimation;

public RipplerContainer(Node container) {
this.container = container;

Expand All @@ -90,61 +85,6 @@ public RipplerContainer(Node container) {
shape.widthProperty().bind(widthProperty());
shape.heightProperty().bind(heightProperty());
setShape(shape);

EventHandler<MouseEvent> mouseEventHandler;
if (AnimationUtils.isAnimationEnabled()) {
mouseEventHandler = event -> {
if (coverAnimation != null) {
coverAnimation.stop();
coverAnimation = null;
}

if (event.getEventType() == MouseEvent.MOUSE_ENTERED) {
coverAnimation = new Transition() {
{
setCycleDuration(Motion.SHORT4);
setInterpolator(Motion.EASE_IN);
}

@Override
protected void interpolate(double frac) {
interpolateBackground(frac);
}
};
} else {
coverAnimation = new Transition() {
{
setCycleDuration(Motion.SHORT4);
setInterpolator(Motion.EASE_OUT);
}

@Override
protected void interpolate(double frac) {
interpolateBackground(1 - frac);
}
};
}

coverAnimation.play();
};
} else {
mouseEventHandler = event ->
interpolateBackground(event.getEventType() == MouseEvent.MOUSE_ENTERED ? 1 : 0);
}

addEventHandler(MouseEvent.MOUSE_ENTERED, mouseEventHandler);
addEventHandler(MouseEvent.MOUSE_EXITED, mouseEventHandler);
}

private void interpolateBackground(double frac) {
if (frac < 0.01) {
setBackground(null);
} else {
Color onSurface = Themes.getColorScheme().getOnSurface();
setBackground(new Background(new BackgroundFill(
Color.color(onSurface.getRed(), onSurface.getGreen(), onSurface.getBlue(), frac * 0.04),
CornerRadii.EMPTY, Insets.EMPTY)));
}
}

protected void updateChildren() {
Expand Down
2 changes: 0 additions & 2 deletions HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Cursor;
import javafx.scene.control.Label;
import javafx.scene.control.Tooltip;
import javafx.scene.image.ImageView;
Expand Down Expand Up @@ -174,7 +173,6 @@ public final class MainPage extends StackPane implements DecoratorPage {
FXUtils.setLimitHeight(updatePane, 55);
StackPane.setAlignment(updatePane, Pos.TOP_RIGHT);
FXUtils.onClicked(updatePane, this::onUpgrade);
updatePane.setCursor(Cursor.HAND);
FXUtils.onChange(showUpdateProperty(), this::doAnimation);
FXUtils.onChange(showUpdateDialogProperty(), this::showUpdateDialog);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.image.Image;
Expand Down Expand Up @@ -546,7 +545,6 @@ protected ModDownloadListPageSkin(DownloadListPage control) {

HBox container = new HBox(8);
container.setPadding(new Insets(8));
container.setCursor(Cursor.HAND);
container.setAlignment(Pos.CENTER_LEFT);

imageContainer.setMouseTransparent(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@
*/
package org.jackhuang.hmcl.ui.versions;

import com.jfoenix.controls.*;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXPopup;
import com.jfoenix.controls.JFXRadioButton;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Cursor;
import javafx.scene.control.ListCell;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.BorderPane;
Expand Down Expand Up @@ -141,7 +142,6 @@ public void fire() {
right.getChildren().add(btnManage);
}

root.setCursor(Cursor.HAND);
container.setOnMouseClicked(e -> {
GameListItem item = getItem();
if (item == null)
Expand Down
19 changes: 0 additions & 19 deletions HMCL/src/main/resources/assets/css/root.css
Original file line number Diff line number Diff line change
Expand Up @@ -392,10 +392,6 @@
-fx-fill: -monet-inverse-on-surface;
}

.sponsor-pane {
-fx-cursor: hand;
}

.installer-item-wrapper {
-fx-background-color: -monet-surface;
-fx-background-radius: 4;
Expand Down Expand Up @@ -444,7 +440,6 @@
-fx-min-height: 55px;
-fx-background-color: -monet-primary-container;
-fx-opacity: 1; /* Override the opacity of disabled button */
-fx-cursor: hand;
}

.launch-pane > .jfx-button.launch-button {
Expand Down Expand Up @@ -658,7 +653,6 @@
.icon {
-fx-fill: #FE774D;
-fx-padding: 10.0;
-fx-cursor: hand;
}

/*******************************************************************************
Expand Down Expand Up @@ -695,7 +689,6 @@
-fx-min-height: 40px;
-fx-max-height: 40px;
-fx-pref-height: 40px;
-fx-cursor: hand;
}

.jfx-tool-bar .jfx-decorator-button .svg {
Expand Down Expand Up @@ -827,16 +820,6 @@
-fx-background-insets: 0;
}

/*******************************************************************************
* *
* JFX Rippler *
* *
*******************************************************************************/

.jfx-rippler:hover {
-fx-cursor: hand;
}

/*******************************************************************************
* *
* JFX Button *
Expand All @@ -845,7 +828,6 @@

.jfx-button {
-jfx-disable-visual-focus: true;
-fx-cursor: hand;
}

.jfx-button .jfx-rippler {
Expand Down Expand Up @@ -1541,7 +1523,6 @@
.combo-box-popup .list-view .jfx-list-cell {
-fx-background-color: -monet-surface-container;
-fx-text-fill: -monet-on-surface;
-fx-cursor: hand;
-fx-background-insets: 0.0;
}

Expand Down