Skip to content
Merged
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
9d04d9d
简化 JFXSpinnerSkin 颜色常量逻辑
Glavo Mar 18, 2026
abd39af
支持禁用动画时的 JFXSpinner 简化动画
Glavo Mar 18, 2026
90a9eda
为 JFXSpinner 动画添加线性插值器
Glavo Mar 18, 2026
a6393a9
优化 JFXSpinner 关键帧数组管理
Glavo Mar 18, 2026
e231371
内联 JFXSpinner 关键帧数组生成逻辑
Glavo Mar 18, 2026
13c0959
调整 JFXSpinner 动画起始角度偏移
Glavo Mar 18, 2026
75d36fe
重构 JFXSpinner 关键帧生成逻辑
Glavo Mar 18, 2026
d6a14ec
修复 JFXSpinner 弧形填充和时间线初始化
Glavo Mar 18, 2026
61de612
移除 VersionsPage 测试用延迟代码
Glavo Mar 18, 2026
e5c25c5
调整 JFXSpinner 弧形长度和动画时长
Glavo Mar 18, 2026
70a870d
调整 JFXSpinner 动画时长为 1.2 秒
Glavo Mar 18, 2026
eac88f6
简化 JFXSpinner 动画控制逻辑
Glavo Mar 18, 2026
a965346
重命名 JFXSpinner 关键帧方法为 addKeyFrames
Glavo Mar 18, 2026
35e671e
移除 JFXSpinner 时间线延迟设置
Glavo Mar 18, 2026
503c589
简化 JFXSpinner 初始化和清理逻辑
Glavo Mar 18, 2026
eaf64d8
重构 JFXSpinner 进度更新和动画逻辑
Glavo Mar 18, 2026
0e2a670
移除 JFXSpinner 多余空行
Glavo Mar 18, 2026
4360a85
简化 JFXSpinner 注释格式
Glavo Mar 18, 2026
f6f75fa
设置 JFXSpinner 弧形起始角度为 90 度
Glavo Mar 18, 2026
fb28f62
优化 JFXSpinner 动画初始化和弧形属性设置
Glavo Mar 18, 2026
9594dcf
优化 JFXSpinner 弧形长度和插值器设置
Glavo Mar 18, 2026
d252212
update createTransition
Glavo Mar 18, 2026
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
190 changes: 67 additions & 123 deletions HMCL/src/main/java/com/jfoenix/skins/JFXSpinnerSkin.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,18 @@
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.scene.Group;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.SkinBase;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Arc;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.StrokeLineCap;
import javafx.util.Duration;
import org.jackhuang.hmcl.ui.animation.AnimationUtils;

import java.util.ArrayList;
import java.util.List;

/// JFXSpinner material design skin
///
Expand All @@ -46,11 +48,6 @@ public class JFXSpinnerSkin extends SkinBase<JFXSpinner> {

private static final double DEFAULT_STROKE_WIDTH = 4;

private static final Color GREEN_COLOR = Color.valueOf("#0F9D58");
private static final Color RED_COLOR = Color.valueOf("#db4437");
private static final Color YELLOW_COLOR = Color.valueOf("#f4b400");
private static final Color BLUE_COLOR = Color.valueOf("#4285f4");

private JFXSpinner control;
private final TreeShowingProperty treeShowingProperty;
private boolean isValid = false;
Expand All @@ -60,7 +57,6 @@ public class JFXSpinnerSkin extends SkinBase<JFXSpinner> {
private Arc track;
private final StackPane arcPane;
private final Rectangle fillRect;
private double arcLength = -1;

private final double startingAngle;

Expand All @@ -73,7 +69,6 @@ public JFXSpinnerSkin(JFXSpinner control) {

arc = new Arc();
arc.setManaged(false);
arc.setStartAngle(0);
arc.setLength(180);
arc.getStyleClass().setAll("arc");
arc.setFill(Color.TRANSPARENT);
Expand All @@ -82,7 +77,6 @@ public JFXSpinnerSkin(JFXSpinner control) {

track = new Arc();
track.setManaged(false);
track.setStartAngle(0);
track.setLength(360);
track.setStrokeWidth(DEFAULT_STROKE_WIDTH);
track.getStyleClass().setAll("track");
Expand All @@ -97,72 +91,28 @@ public JFXSpinnerSkin(JFXSpinner control) {
getChildren().setAll(arcPane);

// register listeners
registerChangeListener(control.indeterminateProperty(), obs -> initialize());
registerChangeListener(control.progressProperty(), obs -> updateProgress());
registerChangeListener(treeShowingProperty, obs -> updateAnimation());
registerChangeListener(control.sceneProperty(), obs -> updateAnimation());
registerChangeListener(treeShowingProperty, obs -> updateProgress());
}

private void initialize() {
if (getSkinnable().isIndeterminate()) {
if (timeline == null) {
createTransition();
if (treeShowingProperty.get()) {
private void updateProgress() {
double progress = Double.min(getSkinnable().getProgress(), 1.0);
if (progress < 0) { // indeterminate
boolean treeShowing = treeShowingProperty.get();
if (treeShowing) {
if (timeline == null) {
timeline = createTransition();
timeline.playFromStart();
} else {
timeline.play();
}
} else if (timeline != null) {
timeline.pause();
}
} else {
} else { // determinate
clearAnimation();
arc.setStartAngle(90);
updateProgress();
}
}

private KeyFrame[] getKeyFrames(double angle, double duration, Paint color) {
KeyFrame[] frames = new KeyFrame[4];
frames[0] = new KeyFrame(Duration.seconds(duration),
new KeyValue(arc.lengthProperty(), 5, Interpolator.LINEAR),
new KeyValue(arc.startAngleProperty(),
angle + 45 + startingAngle,
Interpolator.LINEAR));
frames[1] = new KeyFrame(Duration.seconds(duration + 0.4),
new KeyValue(arc.lengthProperty(), 250, Interpolator.LINEAR),
new KeyValue(arc.startAngleProperty(),
angle + 90 + startingAngle,
Interpolator.LINEAR));
frames[2] = new KeyFrame(Duration.seconds(duration + 0.7),
new KeyValue(arc.lengthProperty(), 250, Interpolator.LINEAR),
new KeyValue(arc.startAngleProperty(),
angle + 135 + startingAngle,
Interpolator.LINEAR));
frames[3] = new KeyFrame(Duration.seconds(duration + 1.1),
new KeyValue(arc.lengthProperty(), 5, Interpolator.LINEAR),
new KeyValue(arc.startAngleProperty(),
angle + 435 + startingAngle,
Interpolator.LINEAR),
new KeyValue(arc.strokeProperty(), color, Interpolator.EASE_BOTH));
return frames;
}

private void pauseTimeline(boolean pause) {
if (getSkinnable().isIndeterminate()) {
if (timeline == null) {
createTransition();
}
if (pause) {
timeline.pause();
} else {
timeline.play();
}
}
}

private void updateAnimation() {
final boolean isTreeShowing = treeShowingProperty.get();
if (timeline != null) {
pauseTimeline(!isTreeShowing);
} else if (isTreeShowing) {
createTransition();
arc.setLength(-360 * progress);
}
}

Expand Down Expand Up @@ -222,13 +172,9 @@ protected void layoutChildren(double contentX, double contentY, double contentWi
fillRect.setHeight(arcSize);

if (!isValid) {
initialize();
updateProgress();
isValid = true;
}

if (!getSkinnable().isIndeterminate()) {
arc.setLength(arcLength);
}
}

private void updateArcLayout(double radius, double arcSize) {
Expand All @@ -244,66 +190,64 @@ private void updateArcLayout(double radius, double arcSize) {
track.setStrokeWidth(arc.getStrokeWidth());
}

boolean wasIndeterminate = false;

protected void updateProgress() {
final ProgressIndicator control = getSkinnable();
final boolean isIndeterminate = control.isIndeterminate();
if (!(isIndeterminate && wasIndeterminate)) {
arcLength = -360 * control.getProgress();
control.requestLayout();
}
wasIndeterminate = isIndeterminate;
}

private void createTransition() {
if (!getSkinnable().isIndeterminate()) return;
final Paint initialColor = arc.getStroke();
if (initialColor == null) {
arc.setStroke(BLUE_COLOR);
}

KeyFrame[] blueFrame = getKeyFrames(0, 0, initialColor == null ? BLUE_COLOR : initialColor);
KeyFrame[] redFrame = getKeyFrames(450, 1.4, initialColor == null ? RED_COLOR : initialColor);
KeyFrame[] yellowFrame = getKeyFrames(900, 2.8, initialColor == null ? YELLOW_COLOR : initialColor);
KeyFrame[] greenFrame = getKeyFrames(1350, 4.2, initialColor == null ? GREEN_COLOR : initialColor);

KeyFrame endingFrame = new KeyFrame(Duration.seconds(5.6),
private void addKeyFrames(List<KeyFrame> frames, double angle, double duration) {
frames.add(new KeyFrame(Duration.seconds(duration),
new KeyValue(arc.lengthProperty(), 5, Interpolator.LINEAR),
new KeyValue(arc.startAngleProperty(),
1845 + startingAngle,
Interpolator.LINEAR));
angle + 45 + startingAngle,
Interpolator.LINEAR)));
frames.add(new KeyFrame(Duration.seconds(duration + 0.4),
new KeyValue(arc.lengthProperty(), 250, Interpolator.LINEAR),
new KeyValue(arc.startAngleProperty(),
angle + 90 + startingAngle,
Interpolator.LINEAR)));
frames.add(new KeyFrame(Duration.seconds(duration + 0.7),
new KeyValue(arc.lengthProperty(), 250, Interpolator.LINEAR),
new KeyValue(arc.startAngleProperty(),
angle + 135 + startingAngle,
Interpolator.LINEAR)));
frames.add(new KeyFrame(Duration.seconds(duration + 1.1),
new KeyValue(arc.lengthProperty(), 5, Interpolator.LINEAR),
new KeyValue(arc.startAngleProperty(),
angle + 435 + startingAngle,
Interpolator.LINEAR)));
}

if (timeline != null) {
timeline.stop();
timeline.getKeyFrames().clear();
private Timeline createTransition() {
Timeline timeline;
if (AnimationUtils.isAnimationEnabled()) {
var keyFrames = new ArrayList<KeyFrame>(17);
addKeyFrames(keyFrames, 0, 0);
addKeyFrames(keyFrames, 450, 1.4);
addKeyFrames(keyFrames, 900, 2.8);
addKeyFrames(keyFrames, 1350, 4.2);
keyFrames.add(new KeyFrame(Duration.seconds(5.6),
new KeyValue(arc.lengthProperty(), 5, Interpolator.LINEAR),
new KeyValue(arc.startAngleProperty(),
1845 + startingAngle,
Interpolator.LINEAR)));

timeline = new Timeline();
timeline.getKeyFrames().setAll(keyFrames);
} else {
final double arcLength = 250;
timeline = new Timeline(
new KeyFrame(Duration.ZERO,
new KeyValue(arc.startAngleProperty(), 45 + startingAngle, Interpolator.LINEAR),
new KeyValue(arc.lengthProperty(), arcLength, Interpolator.DISCRETE)),
new KeyFrame(Duration.seconds(1.2),
new KeyValue(arc.startAngleProperty(), 45 + 360 + startingAngle, Interpolator.LINEAR),
new KeyValue(arc.lengthProperty(), arcLength, Interpolator.DISCRETE))
);
}
timeline = new Timeline(blueFrame[0],
blueFrame[1],
blueFrame[2],
blueFrame[3],
redFrame[0],
redFrame[1],
redFrame[2],
redFrame[3],
yellowFrame[0],
yellowFrame[1],
yellowFrame[2],
yellowFrame[3],
greenFrame[0],
greenFrame[1],
greenFrame[2],
greenFrame[3],
endingFrame);

timeline.setCycleCount(Timeline.INDEFINITE);
timeline.setDelay(Duration.ZERO);
timeline.playFromStart();
return timeline;
}

private void clearAnimation() {
if (timeline != null) {
timeline.stop();
timeline.getKeyFrames().clear();
timeline = null;
}
}
Expand Down
Loading