diff --git a/HMCL/src/main/java/com/jfoenix/controls/JFXButton.java b/HMCL/src/main/java/com/jfoenix/controls/JFXButton.java new file mode 100644 index 000000000..43e30725c --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/controls/JFXButton.java @@ -0,0 +1,129 @@ +// +// Source code recreated from a .class file by IntelliJ IDEA +// (powered by Fernflower decompiler) +// + +package com.jfoenix.controls; + +import com.jfoenix.converters.ButtonTypeConverter; +import com.jfoenix.skins.JFXButtonSkin; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.css.CssMetaData; +import javafx.css.SimpleStyleableObjectProperty; +import javafx.css.Styleable; +import javafx.css.StyleableObjectProperty; +import javafx.css.StyleableProperty; +import javafx.scene.Node; +import javafx.scene.control.Button; +import javafx.scene.control.Control; +import javafx.scene.control.Labeled; +import javafx.scene.control.Skin; +import javafx.scene.paint.Paint; + +public class JFXButton extends Button { + private static final String DEFAULT_STYLE_CLASS = "jfx-button"; + + private List> STYLEABLES; + + public JFXButton() { + this.initialize(); + } + + public JFXButton(String text) { + super(text); + this.initialize(); + } + + public JFXButton(String text, Node graphic) { + super(text, graphic); + this.initialize(); + } + + private void initialize() { + this.getStyleClass().add(DEFAULT_STYLE_CLASS); + } + + protected Skin createDefaultSkin() { + return new JFXButtonSkin(this); + } + + private final ObjectProperty ripplerFill = new SimpleObjectProperty<>(this, "ripplerFill", null); + + public final ObjectProperty ripplerFillProperty() { + return this.ripplerFill; + } + + public final Paint getRipplerFill() { + return this.ripplerFillProperty().get(); + } + + public final void setRipplerFill(Paint ripplerFill) { + this.ripplerFillProperty().set(ripplerFill); + } + + private final StyleableObjectProperty buttonType = new SimpleStyleableObjectProperty<>( + JFXButton.StyleableProperties.BUTTON_TYPE, this, "buttonType", JFXButton.ButtonType.FLAT); + + public ButtonType getButtonType() { + return this.buttonType == null ? JFXButton.ButtonType.FLAT : this.buttonType.get(); + } + + public StyleableObjectProperty buttonTypeProperty() { + return this.buttonType; + } + + public void setButtonType(ButtonType type) { + this.buttonType.set(type); + } + + public List> getControlCssMetaData() { + if (this.STYLEABLES == null) { + List> styleables = new ArrayList<>(Control.getClassCssMetaData()); + styleables.addAll(getClassCssMetaData()); + styleables.addAll(Labeled.getClassCssMetaData()); + this.STYLEABLES = List.copyOf(styleables); + } + + return this.STYLEABLES; + } + + public static List> getClassCssMetaData() { + return JFXButton.StyleableProperties.CHILD_STYLEABLES; + } + + protected void layoutChildren() { + super.layoutChildren(); + this.setNeedsLayout(false); + } + + public enum ButtonType { + FLAT, + RAISED; + } + + private static final class StyleableProperties { + private static final CssMetaData BUTTON_TYPE; + private static final List> CHILD_STYLEABLES; + + static { + BUTTON_TYPE = new CssMetaData<>("-jfx-button-type", ButtonTypeConverter.getInstance(), JFXButton.ButtonType.FLAT) { + public boolean isSettable(JFXButton control) { + return control.buttonType == null || !control.buttonType.isBound(); + } + + public StyleableProperty getStyleableProperty(JFXButton control) { + return control.buttonTypeProperty(); + } + }; + List> styleables = new ArrayList<>(Control.getClassCssMetaData()); + Collections.addAll(styleables, BUTTON_TYPE); + CHILD_STYLEABLES = List.copyOf(styleables); + } + } +} diff --git a/HMCL/src/main/java/com/jfoenix/skins/JFXButtonSkin.java b/HMCL/src/main/java/com/jfoenix/skins/JFXButtonSkin.java new file mode 100644 index 000000000..b8d5dc220 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/skins/JFXButtonSkin.java @@ -0,0 +1,222 @@ +// +// Source code recreated from a .class file by IntelliJ IDEA +// (powered by Fernflower decompiler) +// + +package com.jfoenix.skins; + +import com.jfoenix.adapters.skins.ButtonSkin; +import com.jfoenix.controls.JFXButton; +import com.jfoenix.controls.JFXRippler; +import com.jfoenix.effects.JFXDepthManager; +import com.jfoenix.transitions.CachedTransition; +import javafx.animation.Interpolator; +import javafx.animation.KeyFrame; +import javafx.animation.KeyValue; +import javafx.animation.Timeline; +import javafx.animation.Transition; +import javafx.beans.binding.Bindings; +import javafx.beans.property.ReadOnlyBooleanProperty; +import javafx.geometry.Insets; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.effect.DropShadow; +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.shape.Shape; +import javafx.util.Duration; +import org.jackhuang.hmcl.ui.FXUtils; + +public class JFXButtonSkin extends ButtonSkin { + private final StackPane buttonContainer = new StackPane(); + private final JFXRippler buttonRippler = new JFXRippler(new StackPane()) { + protected Node getMask() { + StackPane mask = new StackPane(); + mask.shapeProperty().bind(JFXButtonSkin.this.buttonContainer.shapeProperty()); + mask.backgroundProperty().bind(Bindings.createObjectBinding(() -> new Background( + new BackgroundFill(Color.WHITE, + JFXButtonSkin.this.buttonContainer.backgroundProperty().get() != null && !JFXButtonSkin.this.buttonContainer.getBackground().getFills().isEmpty() + ? JFXButtonSkin.this.buttonContainer.getBackground().getFills().get(0).getRadii() + : JFXButtonSkin.this.defaultRadii, JFXButtonSkin.this.buttonContainer.backgroundProperty().get() != null && !JFXButtonSkin.this.buttonContainer.getBackground().getFills().isEmpty() ? JFXButtonSkin.this.buttonContainer.getBackground().getFills().get(0).getInsets() : Insets.EMPTY)), + JFXButtonSkin.this.buttonContainer.backgroundProperty())); + mask.resize(JFXButtonSkin.this.buttonContainer.getWidth() - JFXButtonSkin.this.buttonContainer.snappedRightInset() - JFXButtonSkin.this.buttonContainer.snappedLeftInset(), JFXButtonSkin.this.buttonContainer.getHeight() - JFXButtonSkin.this.buttonContainer.snappedBottomInset() - JFXButtonSkin.this.buttonContainer.snappedTopInset()); + return mask; + } + + private void initListeners() { + this.ripplerPane.setOnMousePressed((event) -> { + if (JFXButtonSkin.this.releaseManualRippler != null) { + JFXButtonSkin.this.releaseManualRippler.run(); + } + + JFXButtonSkin.this.releaseManualRippler = null; + this.createRipple(event.getX(), event.getY()); + }); + } + }; + private Transition clickedAnimation; + private final CornerRadii defaultRadii = new CornerRadii(3.0); + private boolean invalid = true; + private Runnable releaseManualRippler = null; + + public JFXButtonSkin(JFXButton button) { + super(button); + this.getSkinnable().armedProperty().addListener((o, oldVal, newVal) -> { + if (newVal) { + this.releaseManualRippler = this.buttonRippler.createManualRipple(); + if (this.clickedAnimation != null) { + this.clickedAnimation.setRate(1.0); + this.clickedAnimation.play(); + } + } else { + if (this.releaseManualRippler != null) { + this.releaseManualRippler.run(); + } + + if (this.clickedAnimation != null) { + this.clickedAnimation.setRate(-1.0); + this.clickedAnimation.play(); + } + } + + }); + this.buttonContainer.getChildren().add(this.buttonRippler); + button.buttonTypeProperty().addListener((o, oldVal, newVal) -> this.updateButtonType(newVal)); + button.setOnMousePressed((e) -> { + if (this.clickedAnimation != null) { + this.clickedAnimation.setRate(1.0F); + this.clickedAnimation.play(); + } + }); + button.setOnMouseReleased((e) -> { + if (this.clickedAnimation != null) { + this.clickedAnimation.setRate(-1.0F); + this.clickedAnimation.play(); + } + }); + + ReadOnlyBooleanProperty focusVisibleProperty = FXUtils.focusVisibleProperty(button); + if (focusVisibleProperty == null) { + focusVisibleProperty = button.focusedProperty(); + } + focusVisibleProperty.addListener((o, oldVal, newVal) -> { + if (newVal) { + if (!this.getSkinnable().isPressed()) { + this.buttonRippler.showOverlay(); + } + } else { + this.buttonRippler.hideOverlay(); + } + }); + button.pressedProperty().addListener((o, oldVal, newVal) -> this.buttonRippler.hideOverlay()); + button.setPickOnBounds(false); + this.buttonContainer.setPickOnBounds(false); + this.buttonContainer.shapeProperty().bind(this.getSkinnable().shapeProperty()); + this.buttonContainer.borderProperty().bind(this.getSkinnable().borderProperty()); + this.buttonContainer.backgroundProperty().bind(Bindings.createObjectBinding(() -> { + if (button.getBackground() == null || this.isJavaDefaultBackground(button.getBackground()) || this.isJavaDefaultClickedBackground(button.getBackground())) { + button.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, this.defaultRadii, null))); + } + + try { + return new Background(new BackgroundFill(this.getSkinnable().getBackground() != null ? this.getSkinnable().getBackground().getFills().get(0).getFill() : Color.TRANSPARENT, this.getSkinnable().getBackground() != null ? this.getSkinnable().getBackground().getFills().get(0).getRadii() : this.defaultRadii, Insets.EMPTY)); + } catch (Exception var3) { + return this.getSkinnable().getBackground(); + } + }, this.getSkinnable().backgroundProperty())); + button.ripplerFillProperty().addListener((o, oldVal, newVal) -> this.buttonRippler.setRipplerFill(newVal)); + if (button.getBackground() == null || this.isJavaDefaultBackground(button.getBackground())) { + button.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, this.defaultRadii, null))); + } + + this.updateButtonType(button.getButtonType()); + this.updateChildren(); + } + + protected void updateChildren() { + super.updateChildren(); + if (this.buttonContainer != null) { + this.getChildren().add(0, this.buttonContainer); + } + + for (int i = 1; i < this.getChildren().size(); ++i) { + this.getChildren().get(i).setMouseTransparent(true); + } + } + + protected void layoutChildren(double x, double y, double w, double h) { + if (this.invalid) { + if (((JFXButton) this.getSkinnable()).getRipplerFill() == null) { + for (int i = this.getChildren().size() - 1; i >= 1; --i) { + if (this.getChildren().get(i) instanceof Shape shape) { + this.buttonRippler.setRipplerFill(shape.getFill()); + shape.fillProperty().addListener((o, oldVal, newVal) -> this.buttonRippler.setRipplerFill(newVal)); + break; + } + + if (this.getChildren().get(i) instanceof Label label) { + this.buttonRippler.setRipplerFill(label.getTextFill()); + label.textFillProperty().addListener((o, oldVal, newVal) -> this.buttonRippler.setRipplerFill(newVal)); + break; + } + } + } else { + this.buttonRippler.setRipplerFill(((JFXButton) this.getSkinnable()).getRipplerFill()); + } + + this.invalid = false; + } + + double shift = 1.0F; + this.buttonContainer.resizeRelocate(this.getSkinnable().getLayoutBounds().getMinX() - shift, this.getSkinnable().getLayoutBounds().getMinY() - shift, this.getSkinnable().getWidth() + (double) 2.0F * shift, this.getSkinnable().getHeight() + (double) 2.0F * shift); + this.layoutLabelInArea(x, y, w, h); + } + + private boolean isJavaDefaultBackground(Background background) { + try { + String firstFill = background.getFills().get(0).getFill().toString(); + return "0xffffffba".equals(firstFill) || "0xffffffbf".equals(firstFill) || "0xffffff12".equals(firstFill) || "0xffffffbd".equals(firstFill); + } catch (Exception var3) { + return false; + } + } + + private boolean isJavaDefaultClickedBackground(Background background) { + try { + return "0x039ed3ff".equals(background.getFills().get(0).getFill().toString()); + } catch (Exception var3) { + return false; + } + } + + private void updateButtonType(JFXButton.ButtonType type) { + switch (type) { + case RAISED -> { + JFXDepthManager.setDepth(this.buttonContainer, 2); + this.clickedAnimation = new ButtonClickTransition(); + } + case FLAT -> this.buttonContainer.setEffect(null); + } + } + + private class ButtonClickTransition extends CachedTransition { + public ButtonClickTransition() { + super(JFXButtonSkin.this.buttonContainer, new Timeline( + new KeyFrame(Duration.ZERO, + new KeyValue(((DropShadow) JFXButtonSkin.this.buttonContainer.getEffect()).radiusProperty(), JFXDepthManager.getShadowAt(2).radiusProperty().get(), Interpolator.EASE_BOTH), + new KeyValue(((DropShadow) JFXButtonSkin.this.buttonContainer.getEffect()).spreadProperty(), JFXDepthManager.getShadowAt(2).spreadProperty().get(), Interpolator.EASE_BOTH), + new KeyValue(((DropShadow) JFXButtonSkin.this.buttonContainer.getEffect()).offsetXProperty(), JFXDepthManager.getShadowAt(2).offsetXProperty().get(), Interpolator.EASE_BOTH), + new KeyValue(((DropShadow) JFXButtonSkin.this.buttonContainer.getEffect()).offsetYProperty(), JFXDepthManager.getShadowAt(2).offsetYProperty().get(), Interpolator.EASE_BOTH)), + new KeyFrame(Duration.millis(1000.0F), + new KeyValue(((DropShadow) JFXButtonSkin.this.buttonContainer.getEffect()).radiusProperty(), JFXDepthManager.getShadowAt(5).radiusProperty().get(), Interpolator.EASE_BOTH), + new KeyValue(((DropShadow) JFXButtonSkin.this.buttonContainer.getEffect()).spreadProperty(), JFXDepthManager.getShadowAt(5).spreadProperty().get(), Interpolator.EASE_BOTH), + new KeyValue(((DropShadow) JFXButtonSkin.this.buttonContainer.getEffect()).offsetXProperty(), JFXDepthManager.getShadowAt(5).offsetXProperty().get(), Interpolator.EASE_BOTH), + new KeyValue(((DropShadow) JFXButtonSkin.this.buttonContainer.getEffect()).offsetYProperty(), JFXDepthManager.getShadowAt(5).offsetYProperty().get(), Interpolator.EASE_BOTH)))); + this.setCycleDuration(Duration.seconds(0.2)); + this.setDelay(Duration.seconds(0.0F)); + } + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java index c5c338204..5f473b320 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java @@ -1433,16 +1433,6 @@ public final class FXUtils { }); } - public static void clearFocus(Node node) { - Scene scene = node.getScene(); - if (scene != null) { - Parent root = scene.getRoot(); - if (root != null) { - root.requestFocus(); - } - } - } - public static void copyOnDoubleClick(Labeled label) { label.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> { if (e.getButton() == MouseButton.PRIMARY && e.getClickCount() == 2) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/ToolbarListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ToolbarListPageSkin.java index 53a61026d..1faf3572b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/ToolbarListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ToolbarListPageSkin.java @@ -89,10 +89,7 @@ public abstract class ToolbarListPageSkin ret.getStyleClass().add("jfx-tool-bar-button"); ret.setGraphic(wrap(svg.createIcon())); ret.setText(text); - ret.setOnAction(e -> { - onClick.run(); - FXUtils.clearFocus(ret); - }); + ret.setOnAction(e -> onClick.run()); return ret; } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java index 0eb6710c2..3cf2f7135 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java @@ -190,6 +190,7 @@ public final class MessageDialogPane extends HBox { public Builder addCancel(String cancelText, @Nullable Runnable cancel) { JFXButton btnCancel = new JFXButton(cancelText); + btnCancel.setButtonType(JFXButton.ButtonType.FLAT); btnCancel.getStyleClass().add("dialog-cancel"); if (cancel != null) { btnCancel.setOnAction(e -> cancel.run()); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java index daa341ee8..0e42b999e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java @@ -175,10 +175,7 @@ public final class VersionsPage extends Control implements WizardPage, Refreshab { if ("game".equals(control.libraryId)) { JFXButton wikiButton = newToggleButton4(SVG.GLOBE_BOOK); - wikiButton.setOnAction(event -> { - onOpenWiki(); - FXUtils.clearFocus(wikiButton); - }); + wikiButton.setOnAction(event -> onOpenWiki()); FXUtils.installFastTooltip(wikiButton, i18n("wiki.tooltip")); actions.getChildren().add(wikiButton); }