张世豪
8 天以前 c57cb0cd9feb4495c89246b2faec6d5e45c23c30
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
package yaokong;
 
import ui.UIConfig;
 
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.List;
 
public class RemoteControlDialog extends JDialog {
    private final Color THEME_COLOR;
    private boolean serialWarningShown;
    private final List<ButtonInteraction> buttonInteractions = new ArrayList<>();
    private ModernJoystickComponent moveJoystick;  // 移动摇杆(控制前进后退)
    private ModernJoystickComponent turnJoystick;  // 转向摇杆(控制左右转向)
    private JLabel speedLabel;  // 首页的速度标签引用
    private JLabel joystickValuesLabel; // 旧的顶部显示(保留以防兼容),但主要使用下面两个标签
    // 分别显示两个摇杆的数值/状态,位于各自摇杆的正上方
    private JLabel moveJoystickValueLabel;
    private JLabel turnJoystickValueLabel;
    private Timer speedUpdateTimer;  // 速度更新定时器
    // 摇杆控制定时器:持续发送控制指令
    private Timer forwardControlTimer;  // 前进/后退控制定时器
    private Timer steeringControlTimer;  // 转向控制定时器
    private int targetForwardSpeed = 0;  // 目标前进/后退速度
    private int targetSteeringSpeed = 0;  // 目标转向速度
    private List<JButton> bladeButtons = new ArrayList<>();  // 存储刀盘控制按钮,用于清理定时器
    private String bladeUpDefaultText = "刀盘升";  // 刀盘升按钮默认文字
    private String bladeDownDefaultText = "刀盘降";  // 刀盘降按钮默认文字
    
    public RemoteControlDialog(Component parent, Color themeColor, JLabel speedLabel) {
        super(parent != null ? (JFrame) SwingUtilities.getWindowAncestor(parent) : null,
              "遥控操作", false);
        this.THEME_COLOR = themeColor;
        this.speedLabel = speedLabel;
        setModalityType(ModalityType.MODELESS);
        setAlwaysOnTop(true);
        Control03.resetSpeeds();
        Control03.sendNeutralCommandIfDebugSerialOpen();
        int dialogWidth = UIConfig.DIALOG_WIDTH;  // 与首页宽度一致
        int dialogHeight = (int) (UIConfig.DIALOG_HEIGHT / 3.0 * 0.7) +150;  // 增加90像素(原40+新增40+再增10)
        initializeDialog(dialogWidth, dialogHeight);
        initializeRemoteContent();
        startSpeedUpdateTimer();  // 启动速度更新定时器
        if (parent == null) {
            setLocationRelativeTo(null); // 居中显示
        }
    }
    
    private void startSpeedUpdateTimer() {
        // 不再在首页显示“行进/转向”内容,隐藏并清空速度标签
        if (speedLabel != null) {
            speedLabel.setVisible(false);
            speedLabel.setText("");
            // 不创建速度更新定时器,避免在首页显示行进/转向信息
            speedUpdateTimer = null;
        }
    }
    
    private void initializeDialog(int width, int height) {
        setSize(width, height);
        setLocationRelativeTo(getOwner());
        setResizable(false);
        Container contentPane = getContentPane();
        if (contentPane instanceof JComponent) {
            JComponent jContentPane = (JComponent) contentPane;
            jContentPane.setBackground(new Color(0, 0, 0, 0)); // 透明背景
            jContentPane.setOpaque(false);
        }
    }
    
    private void initializeRemoteContent() {
        JPanel contentPanel = new JPanel(new BorderLayout());
        contentPanel.setBorder(BorderFactory.createEmptyBorder(16, 16, 16, 16));
        contentPanel.setOpaque(false); // 设置为透明
 
        // 创建顶部面板,包含按钮和数值显示
        JPanel topPanel = new JPanel(new BorderLayout());
        topPanel.setOpaque(false);
        
        // 创建按钮面板(4个按钮,间隔30像素)
        JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 30, 0));
        buttonPanel.setOpaque(false);
        buttonPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 8, 0)); // 底部间距8像素
        
        // 按钮1:off.png <-> starton.png,文字:启动 <-> 熄火
        JLabel label1 = new JLabel("启动", SwingConstants.CENTER);
        label1.setFont(new Font("微软雅黑", Font.PLAIN, 12));
        label1.setForeground(new Color(40, 40, 40));
        JButton button1 = createIconButtonWithLabel("image/off.png", "image/starton.png", 27, 27, 
                                                     label1, "启动", "熄火", "POWER");
        JPanel panel1 = createButtonWithLabel(button1, label1);
        buttonPanel.add(panel1);
        
        // 按钮2:dengoff.png <-> dengon.png,文字:开灯 <-> 关灯
        JLabel label2 = new JLabel("开灯", SwingConstants.CENTER);
        label2.setFont(new Font("微软雅黑", Font.PLAIN, 12));
        label2.setForeground(new Color(40, 40, 40));
        JButton button2 = createIconButtonWithLabel("image/dengoff.png", "image/dengon.png", 30, 30,
                                                     label2, "开灯", "关灯", "LIGHT");
        JPanel panel2 = createButtonWithLabel(button2, label2);
        buttonPanel.add(panel2);
        
        // 按钮3:xia1.png(固定图标),文字:刀盘升
        JButton button3 = createBladeControlButton("image/xia1.png", "image/xia20.png", 30, 30, true);
        JLabel label3 = new JLabel("刀盘升", SwingConstants.CENTER);
        label3.setFont(new Font("微软雅黑", Font.PLAIN, 12));
        label3.setForeground(new Color(40, 40, 40));
        button3.putClientProperty("label", label3);
        button3.putClientProperty("defaultText", bladeUpDefaultText);
        bladeButtons.add(button3);  // 添加到列表以便清理
        JPanel panel3 = createButtonWithLabel(button3, label3);
        buttonPanel.add(panel3);
        
        // 按钮4:xia2.png(固定图标),文字:刀盘降
        JButton button4 = createBladeControlButton("image/xia2.png", "image/xia10.png", 30, 30, false);
        JLabel label4 = new JLabel("刀盘降", SwingConstants.CENTER);
        label4.setFont(new Font("微软雅黑", Font.PLAIN, 12));
        label4.setForeground(new Color(40, 40, 40));
        button4.putClientProperty("label", label4);
        button4.putClientProperty("defaultText", bladeDownDefaultText);
        bladeButtons.add(button4);  // 添加到列表以便清理
        JPanel panel4 = createButtonWithLabel(button4, label4);
        buttonPanel.add(panel4);
        
        topPanel.add(buttonPanel, BorderLayout.NORTH);
 
        // 在每个摇杆上方分别显示其状态/数值(分两列)
        JPanel valuesPanel = new JPanel(new GridLayout(1, 2, 20, 0));
        valuesPanel.setOpaque(false);
 
        moveJoystickValueLabel = new JLabel("前后", SwingConstants.CENTER);
        moveJoystickValueLabel.setFont(new Font("微软雅黑", Font.BOLD, 14));
        moveJoystickValueLabel.setForeground(new Color(40, 40, 40));
        moveJoystickValueLabel.setBorder(BorderFactory.createEmptyBorder(8, 4, 8, 4));
        moveJoystickValueLabel.setOpaque(false);
 
        turnJoystickValueLabel = new JLabel("左右", SwingConstants.CENTER);
        turnJoystickValueLabel.setFont(new Font("微软雅黑", Font.BOLD, 14));
        turnJoystickValueLabel.setForeground(new Color(40, 40, 40));
        turnJoystickValueLabel.setBorder(BorderFactory.createEmptyBorder(8, 4, 8, 4));
        turnJoystickValueLabel.setOpaque(false);
 
        valuesPanel.add(moveJoystickValueLabel);
        valuesPanel.add(turnJoystickValueLabel);
        topPanel.add(valuesPanel, BorderLayout.CENTER);
        
        contentPanel.add(topPanel, BorderLayout.NORTH);
 
        // 创建摇杆面板
        JPanel joystickPanel = new JPanel(new GridLayout(1, 2, 20, 0));
        joystickPanel.setOpaque(false);
 
        // 移动摇杆(绿色主题)
        moveJoystick = new ModernJoystickComponent(" ", 
            new Color(46, 204, 113), true);  // 绿色主题
        moveJoystick.setJoystickListener(new JoystickListener() {
            @Override
            public void onJoystickMoved(double x, double y) {
                // 只使用Y轴控制前进后退,向上(北)为正
                // 计算并四舍五入到整数速度值,正为前进,负为后退
                int forwardVal = (int) Math.round(-y * 100.0);
                // 限制在 [-100, 100]
                forwardVal = Math.max(-100, Math.min(100, forwardVal));
 
                // 更新目标速度
                targetForwardSpeed = forwardVal;
 
                if (Math.abs(y) > 0.1) {
                    // 摇杆不在中心位置,启动持续发送定时器
                    startForwardControlTimer();
                } else {
                    // 摇杆回到中心位置,停止发送
                    stopForwardControlTimer();
                    stopForward();
                }
 
                // 更新顶部显示(移动显示当前前进/后退速度,转向取当前转向速度作为参考)
                int steeringVal = Control03.getCurrentSteeringSpeed();
                updateJoystickValues(forwardVal, steeringVal);
            }
        });
        // 转向摇杆(蓝色主题)
        turnJoystick = new ModernJoystickComponent("", 
            new Color(52, 152, 219), false);  // 蓝色主题
        turnJoystick.setJoystickListener(new JoystickListener() {
            @Override
            public void onJoystickMoved(double x, double y) {
                // 只使用X轴控制左右转向,向右为正
                // 计算并四舍五入到整数转向值,正为右转,负为左转
                int steeringVal = (int) Math.round(x * 100.0);
                steeringVal = Math.max(-100, Math.min(100, steeringVal));
 
                // 更新目标速度
                targetSteeringSpeed = steeringVal;
 
                if (Math.abs(x) > 0.1) {
                    // 摇杆不在中心位置,启动持续发送定时器
                    startSteeringControlTimer();
                } else {
                    // 摇杆回到中心位置,停止发送
                    stopSteeringControlTimer();
                    stopSteering();
                }
 
                // 更新顶部显示(转向显示当前转向速度,移动显示当前前进速度)
                int forwardVal = Control03.getCurrentForwardSpeed();
                updateJoystickValues(forwardVal, steeringVal);
            }
        });
        joystickPanel.add(moveJoystick);
        joystickPanel.add(turnJoystick);
 
    // 仅显示摇杆面板(已移除下方的矩形速度显示)
    contentPanel.add(joystickPanel, BorderLayout.CENTER);
 
        getContentPane().add(contentPanel);
    }
 
    /**
     * 创建带标签的按钮面板(垂直布局:按钮在上,标签在下)
     * @param button 按钮
     * @param label 标签
     * @return 包含按钮和标签的面板
     */
    private JPanel createButtonWithLabel(JButton button, JLabel label) {
        JPanel panel = new JPanel(new BorderLayout());
        panel.setOpaque(false);
        panel.add(button, BorderLayout.CENTER);
        panel.add(label, BorderLayout.SOUTH);
        panel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
        return panel;
    }
 
    /**
     * 创建可切换图标的按钮(带标签文字切换)
     * @param defaultIconPath 默认图标路径
     * @param clickedIconPath 点击后的图标路径
     * @param width 图标宽度
     * @param height 图标高度
     * @param label 标签组件
     * @param defaultText 默认文字
     * @param clickedText 点击后的文字
     * @param controlType 控制指令类型:"POWER"表示启动/熄火,"LIGHT"表示开灯/关灯,null表示无控制指令
     * @return 配置好的按钮
     */
    private JButton createIconButtonWithLabel(String defaultIconPath, String clickedIconPath, 
                                               int width, int height, JLabel label, 
                                               String defaultText, String clickedText, String controlType) {
        JButton button = new JButton();
        button.setPreferredSize(new Dimension(width, height));
        button.setMinimumSize(new Dimension(width, height));
        button.setMaximumSize(new Dimension(width, height));
        button.setContentAreaFilled(false);
        button.setBorderPainted(false);
        button.setFocusPainted(false);
        
        // 加载默认图标和点击图标
        ImageIcon defaultIcon = loadIcon(defaultIconPath, width, height);
        ImageIcon clickedIcon = loadIcon(clickedIconPath, width, height);
        
        if (defaultIcon != null) {
            button.setIcon(defaultIcon);
        }
        
        // 使用 clientProperty 来跟踪按钮状态(false=默认图标,true=点击图标)
        button.putClientProperty("isClicked", false);
        button.putClientProperty("defaultIcon", defaultIcon);
        button.putClientProperty("clickedIcon", clickedIcon);
        button.putClientProperty("label", label);
        button.putClientProperty("defaultText", defaultText);
        button.putClientProperty("clickedText", clickedText);
        button.putClientProperty("controlType", controlType);
        
        // 添加点击事件,切换图标和文字,并发送控制指令
        button.addActionListener(e -> {
            Boolean isClicked = (Boolean) button.getClientProperty("isClicked");
            ImageIcon defaultIconRef = (ImageIcon) button.getClientProperty("defaultIcon");
            ImageIcon clickedIconRef = (ImageIcon) button.getClientProperty("clickedIcon");
            JLabel labelRef = (JLabel) button.getClientProperty("label");
            String defaultTextRef = (String) button.getClientProperty("defaultText");
            String clickedTextRef = (String) button.getClientProperty("clickedText");
            String controlTypeRef = (String) button.getClientProperty("controlType");
            
            if (isClicked == null || !isClicked) {
                // 当前是默认图标,切换到点击图标
                if (clickedIconRef != null) {
                    button.setIcon(clickedIconRef);
                    button.putClientProperty("isClicked", true);
                }
                // 更新标签文字
                if (labelRef != null && clickedTextRef != null) {
                    labelRef.setText(clickedTextRef);
                }
                // 发送控制指令(开启)
                if ("POWER".equals(controlTypeRef)) {
                    boolean success = Control05.sendPowerOnIfDebugSerialOpen();
                    if (!success) {
                        showSerialClosedWarning();
                    }
                } else if ("LIGHT".equals(controlTypeRef)) {
                    boolean success = Control07.sendLightOnIfDebugSerialOpen();
                    if (!success) {
                        showSerialClosedWarning();
                    }
                }
            } else {
                // 当前是点击图标,切换回默认图标
                if (defaultIconRef != null) {
                    button.setIcon(defaultIconRef);
                    button.putClientProperty("isClicked", false);
                }
                // 更新标签文字
                if (labelRef != null && defaultTextRef != null) {
                    labelRef.setText(defaultTextRef);
                }
                // 发送控制指令(关闭)
                if ("POWER".equals(controlTypeRef)) {
                    boolean success = Control05.sendPowerOffIfDebugSerialOpen();
                    if (!success) {
                        showSerialClosedWarning();
                    }
                } else if ("LIGHT".equals(controlTypeRef)) {
                    boolean success = Control07.sendLightOffIfDebugSerialOpen();
                    if (!success) {
                        showSerialClosedWarning();
                    }
                }
            }
        });
        
        return button;
    }
 
    /**
     * 创建可切换图标的按钮
     * @param defaultIconPath 默认图标路径
     * @param clickedIconPath 点击后的图标路径
     * @param width 图标宽度
     * @param height 图标高度
     * @return 配置好的按钮
     */
    private JButton createIconButton(String defaultIconPath, String clickedIconPath, int width, int height) {
        JButton button = new JButton();
        button.setPreferredSize(new Dimension(width, height));
        button.setMinimumSize(new Dimension(width, height));
        button.setMaximumSize(new Dimension(width, height));
        button.setContentAreaFilled(false);
        button.setBorderPainted(false);
        button.setFocusPainted(false);
        
        // 加载默认图标和点击图标
        ImageIcon defaultIcon = loadIcon(defaultIconPath, width, height);
        ImageIcon clickedIcon = loadIcon(clickedIconPath, width, height);
        
        if (defaultIcon != null) {
            button.setIcon(defaultIcon);
        }
        
        // 使用 clientProperty 来跟踪按钮状态(false=默认图标,true=点击图标)
        button.putClientProperty("isClicked", false);
        button.putClientProperty("defaultIcon", defaultIcon);
        button.putClientProperty("clickedIcon", clickedIcon);
        
        // 添加点击事件,切换图标
        button.addActionListener(e -> {
            Boolean isClicked = (Boolean) button.getClientProperty("isClicked");
            ImageIcon defaultIconRef = (ImageIcon) button.getClientProperty("defaultIcon");
            ImageIcon clickedIconRef = (ImageIcon) button.getClientProperty("clickedIcon");
            
            if (isClicked == null || !isClicked) {
                // 当前是默认图标,切换到点击图标
                if (clickedIconRef != null) {
                    button.setIcon(clickedIconRef);
                    button.putClientProperty("isClicked", true);
                }
            } else {
                // 当前是点击图标,切换回默认图标
                if (defaultIconRef != null) {
                    button.setIcon(defaultIconRef);
                    button.putClientProperty("isClicked", false);
                }
            }
        });
        
        return button;
    }
 
    /**
     * 创建固定图标的按钮
     * @param iconPath 图标路径
     * @param width 图标宽度
     * @param height 图标高度
     * @return 配置好的按钮
     */
    private JButton createFixedIconButton(String iconPath, int width, int height) {
        JButton button = new JButton();
        button.setPreferredSize(new Dimension(width, height));
        button.setMinimumSize(new Dimension(width, height));
        button.setMaximumSize(new Dimension(width, height));
        button.setContentAreaFilled(false);
        button.setBorderPainted(false);
        button.setFocusPainted(false);
        
        ImageIcon icon = loadIcon(iconPath, width, height);
        if (icon != null) {
            button.setIcon(icon);
        }
        
        return button;
    }
 
    /**
     * 创建刀盘升降控制按钮(支持点击和长按)
     * @param defaultIconPath 默认图标路径
     * @param clickedIconPath 点击/长按时的图标路径
     * @param width 图标宽度
     * @param height 图标高度
     * @param isUp true表示刀盘升,false表示刀盘降
     * @return 配置好的按钮
     */
    private JButton createBladeControlButton(String defaultIconPath, String clickedIconPath, 
                                             int width, int height, boolean isUp) {
        JButton button = new JButton();
        button.setPreferredSize(new Dimension(width, height));
        button.setMinimumSize(new Dimension(width, height));
        button.setMaximumSize(new Dimension(width, height));
        button.setContentAreaFilled(false);
        button.setBorderPainted(false);
        button.setFocusPainted(false);
        
        // 加载图标
        ImageIcon defaultIcon = loadIcon(defaultIconPath, width, height);
        ImageIcon clickedIcon = loadIcon(clickedIconPath, width, height);
        
        if (defaultIcon != null) {
            button.setIcon(defaultIcon);
        }
        
        button.putClientProperty("defaultIcon", defaultIcon);
        button.putClientProperty("clickedIcon", clickedIcon);
        button.putClientProperty("isUp", isUp);
        button.putClientProperty("isPressed", false);
        
        // 长按定时器
        Timer longPressTimer = new Timer(100, e -> {  // 每100ms执行一次
            if (button.getClientProperty("isPressed") == Boolean.TRUE) {
                boolean isUpButton = (Boolean) button.getClientProperty("isUp");
                int currentHeight = Control06.getCurrentBladeHeight();
                
                // 使用Control06的方法发送指令
                boolean success;
                if (isUpButton) {
                    success = Control06.sendBladeUpIfDebugSerialOpen(1);
                } else {
                    success = Control06.sendBladeDownIfDebugSerialOpen(1);
                }
                
                if (!success) {
                    showSerialClosedWarning();
                } else {
                    // 更新按钮标签显示当前数值
                    updateBladeButtonLabel(button);
                }
            }
        });
        longPressTimer.setInitialDelay(500);  // 长按500ms后开始连续变化
        button.putClientProperty("longPressTimer", longPressTimer);  // 存储定时器引用
        
        // 鼠标按下事件
        button.addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                button.putClientProperty("isPressed", true);
                
                // 立即切换图标
                ImageIcon clickedIconRef = (ImageIcon) button.getClientProperty("clickedIcon");
                if (clickedIconRef != null) {
                    button.setIcon(clickedIconRef);
                }
                
                // 点击时立即增加/减少10,使用Control06的方法发送指令
                boolean isUpButton = (Boolean) button.getClientProperty("isUp");
                boolean success;
                if (isUpButton) {
                    success = Control06.sendBladeUpIfDebugSerialOpen(10);
                } else {
                    success = Control06.sendBladeDownIfDebugSerialOpen(10);
                }
                
                if (!success) {
                    showSerialClosedWarning();
                } else {
                    // 更新按钮标签显示当前数值
                    updateBladeButtonLabel(button);
                }
                
                // 启动长按定时器
                longPressTimer.start();
            }
            
            @Override
            public void mouseReleased(MouseEvent e) {
                button.putClientProperty("isPressed", false);
                
                // 停止长按定时器
                longPressTimer.stop();
                
                // 恢复默认图标
                ImageIcon defaultIconRef = (ImageIcon) button.getClientProperty("defaultIcon");
                if (defaultIconRef != null) {
                    button.setIcon(defaultIconRef);
                }
                
                // 恢复默认文字
                JLabel labelRef = (JLabel) button.getClientProperty("label");
                String defaultTextRef = (String) button.getClientProperty("defaultText");
                if (labelRef != null && defaultTextRef != null) {
                    labelRef.setText(defaultTextRef);
                }
            }
        });
        
        return button;
    }
 
    /**
     * 更新刀盘按钮标签显示当前数值
     * @param button 按钮
     */
    private void updateBladeButtonLabel(JButton button) {
        JLabel labelRef = (JLabel) button.getClientProperty("label");
        if (labelRef != null) {
            String defaultTextRef = (String) button.getClientProperty("defaultText");
            int currentHeight = Control06.getCurrentBladeHeight();
            String displayText = defaultTextRef + " " + currentHeight;
            labelRef.setText(displayText);
        }
    }
 
    /**
     * 加载并缩放图标
     * @param iconPath 图标路径
     * @param width 目标宽度
     * @param height 目标高度
     * @return 缩放后的图标
     */
    private ImageIcon loadIcon(String iconPath, int width, int height) {
        try {
            java.net.URL imgURL = getClass().getClassLoader().getResource(iconPath);
            if (imgURL == null) {
                // 尝试从文件系统加载
                java.io.File imgFile = new java.io.File(iconPath);
                if (imgFile.exists()) {
                    ImageIcon originalIcon = new ImageIcon(imgFile.getAbsolutePath());
                    Image scaledImage = originalIcon.getImage().getScaledInstance(width, height, Image.SCALE_SMOOTH);
                    ImageIcon scaledIcon = new ImageIcon(scaledImage);
                    scaledIcon.setDescription(iconPath);
                    return scaledIcon;
                }
            } else {
                ImageIcon originalIcon = new ImageIcon(imgURL);
                Image scaledImage = originalIcon.getImage().getScaledInstance(width, height, Image.SCALE_SMOOTH);
                ImageIcon scaledIcon = new ImageIcon(scaledImage);
                scaledIcon.setDescription(iconPath);
                return scaledIcon;
            }
        } catch (Exception e) {
            System.err.println("无法加载图标: " + iconPath + " - " + e.getMessage());
        }
        return null;
    }
 
    private JPanel createStatusPanel(String label, String value, Color color) {
        JPanel panel = new JPanel(new BorderLayout(0, 5));
        panel.setOpaque(false);
 
        JLabel titleLabel = new JLabel(label, SwingConstants.CENTER);
        titleLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
        titleLabel.setForeground(new Color(255, 255, 255, 180));
 
        JLabel valueLabel = new JLabel(value, SwingConstants.CENTER);
        valueLabel.setFont(new Font("微软雅黑", Font.BOLD, 24));
        valueLabel.setForeground(color);
        valueLabel.setOpaque(true);
        valueLabel.setBackground(new Color(0, 0, 0, 30));
        valueLabel.setBorder(BorderFactory.createCompoundBorder(
            BorderFactory.createLineBorder(new Color(255, 255, 255, 30), 2),
            BorderFactory.createEmptyBorder(10, 20, 10, 20)
        ));
        valueLabel.setPreferredSize(new Dimension(120, 60));
 
        panel.add(titleLabel, BorderLayout.NORTH);
        panel.add(valueLabel, BorderLayout.CENTER);
 
        return panel;
    }
 
    private void showSerialClosedWarning() {
        if (serialWarningShown) {
            return;
        }
        serialWarningShown = true;
        JOptionPane.showMessageDialog(this, "请先打开系统调试串口", "提示", JOptionPane.WARNING_MESSAGE);
    }
 
    private void applyForwardSpeed(int speed) {
        // 计算目标速度(范围-100到100)
        int targetSpeed = Math.max(-100, Math.min(100, speed));
        
        // 获取当前速度
        int currentSpeed = Control03.getCurrentForwardSpeed();
        
        // 如果差异较大,逐步调整
        if (Math.abs(targetSpeed - currentSpeed) > 5) {
            int delta = targetSpeed > currentSpeed ? 10 : -10;
            boolean success = Control03.adjustForwardSpeed(delta);
            if (!success) {
                showSerialClosedWarning();
                return;
            }
            serialWarningShown = false;
        }
    }
 
    private void stopForward() {
        if (Control03.getCurrentForwardSpeed() != 0) {
            boolean success = Control03.approachForwardSpeedToZero(20);
            if (!success) {
                showSerialClosedWarning();
                return;
            }
            serialWarningShown = false;
        }
    }
 
    private void applySteeringSpeed(int speed) {
        // 计算目标转向速度(范围-100到100)
        int targetSpeed = Math.max(-100, Math.min(100, speed));
        
        // 获取当前转向速度
        int currentSpeed = Control03.getCurrentSteeringSpeed();
        
        // 如果差异较大,逐步调整
        if (Math.abs(targetSpeed - currentSpeed) > 5) {
            int delta = targetSpeed > currentSpeed ? 15 : -15;
            boolean success = Control03.adjustSteeringSpeed(delta);
            if (!success) {
                showSerialClosedWarning();
                return;
            }
            serialWarningShown = false;
        }
    }
 
    private void stopSteering() {
        if (Control03.getCurrentSteeringSpeed() != 0) {
            boolean success = Control03.approachSteeringSpeedToZero(25);
            if (!success) {
                showSerialClosedWarning();
                return;
            }
            serialWarningShown = false;
        }
    }
 
    /**
     * 启动前进/后退控制定时器,持续发送控制指令
     */
    private void startForwardControlTimer() {
        // 如果定时器已经在运行,先停止它
        if (forwardControlTimer != null && forwardControlTimer.isRunning()) {
            forwardControlTimer.stop();
        }
        
        // 创建新的定时器,每100ms发送一次指令
        forwardControlTimer = new Timer(100, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                // 持续发送目标速度的指令
                applyForwardSpeedContinuously(targetForwardSpeed);
            }
        });
        forwardControlTimer.setInitialDelay(0);
        forwardControlTimer.start();
    }
 
    /**
     * 停止前进/后退控制定时器
     */
    private void stopForwardControlTimer() {
        if (forwardControlTimer != null && forwardControlTimer.isRunning()) {
            forwardControlTimer.stop();
        }
    }
 
    /**
     * 启动转向控制定时器,持续发送控制指令
     */
    private void startSteeringControlTimer() {
        // 如果定时器已经在运行,先停止它
        if (steeringControlTimer != null && steeringControlTimer.isRunning()) {
            steeringControlTimer.stop();
        }
        
        // 创建新的定时器,每100ms发送一次指令
        steeringControlTimer = new Timer(100, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                // 持续发送目标速度的指令
                applySteeringSpeedContinuously(targetSteeringSpeed);
            }
        });
        steeringControlTimer.setInitialDelay(0);
        steeringControlTimer.start();
    }
 
    /**
     * 停止转向控制定时器
     */
    private void stopSteeringControlTimer() {
        if (steeringControlTimer != null && steeringControlTimer.isRunning()) {
            steeringControlTimer.stop();
        }
    }
 
    /**
     * 持续发送前进/后退速度指令
     */
    private void applyForwardSpeedContinuously(int targetSpeed) {
        int currentSpeed = Control03.getCurrentForwardSpeed();
        int currentSteeringSpeed = Control03.getCurrentSteeringSpeed();
        
        // 如果已经达到目标速度,直接发送一次以保持状态
        if (currentSpeed == targetSpeed) {
            // 直接发送目标速度指令以保持状态(即使速度相同也要发送)
            Control03.setAndSendSpeeds(currentSteeringSpeed, targetSpeed);
        } else {
            // 逐步调整到目标速度
            int delta = targetSpeed > currentSpeed ? 10 : -10;
            Control03.adjustForwardSpeed(delta);
        }
    }
 
    /**
     * 持续发送转向速度指令
     */
    private void applySteeringSpeedContinuously(int targetSpeed) {
        int currentSpeed = Control03.getCurrentSteeringSpeed();
        int currentForwardSpeed = Control03.getCurrentForwardSpeed();
        
        // 如果已经达到目标速度,直接发送一次以保持状态
        if (currentSpeed == targetSpeed) {
            // 直接发送目标速度指令以保持状态(即使速度相同也要发送)
            Control03.setAndSendSpeeds(targetSpeed, currentForwardSpeed);
        } else {
            // 逐步调整到目标速度
            int delta = targetSpeed > currentSpeed ? 15 : -15;
            Control03.adjustSteeringSpeed(delta);
        }
    }
 
    // 更新顶部显示的摇杆数值(在 EDT 上调用),文字根据数值映射为方向描述
    private void updateJoystickValues(int forwardVal, int steeringVal) {
        // 计算移动/转向描述文本
        // 移动方向:正为前进,负为后退,0 为前后
        String moveText;
        if (forwardVal > 0) {
            moveText = String.format("前进 %d", forwardVal);
        } else if (forwardVal < 0) {
            moveText = String.format("后退 %d", Math.abs(forwardVal));
        } else {
            moveText = "前后";
        }
 
        // 转向方向:正为右转,负为左转,0 为左右
        String steerText;
        if (steeringVal > 0) {
            steerText = String.format("右转 %d", steeringVal);
        } else if (steeringVal < 0) {
            steerText = String.format("左转 %d", Math.abs(steeringVal));
        } else {
            steerText = "左右";
        }
 
    // 分别设置两个摇杆上方的文字(同时兼容旧的顶部合并标签)
    final String moveTextFinal = moveText;
    final String steerTextFinal = steerText;
        // 大部分回调来自 EDT(鼠标事件或 Swing Timer),但为安全使用 invokeLater
        SwingUtilities.invokeLater(() -> {
            if (moveJoystickValueLabel != null) moveJoystickValueLabel.setText(moveTextFinal);
            if (turnJoystickValueLabel != null) turnJoystickValueLabel.setText(steerTextFinal);
            // 兼容:同时更新旧的合并显示(如果存在)
            if (joystickValuesLabel != null) joystickValuesLabel.setText(String.format("%s    %s", moveTextFinal, steerTextFinal));
        });
    }
 
    /**
     * 显示对话框并仅显示移动摇杆(隐藏转向摇杆)。
     * 这个方法用于兼容旧调用点,确保外部可以请求只显示移动控制部分。
     */
    public void showOnlyMoveJoystick() {
        if (moveJoystick != null) {
            moveJoystick.setVisible(true);
        }
        if (turnJoystick != null) {
            turnJoystick.setVisible(false);
        }
        // 重新布局并刷新显示
        revalidate();
        repaint();
        // 不再显示首页速度标签(已移除“行进/转向”内容)
        // 显示对话框
        setVisible(true);
        // 尝试让对话框获得焦点
        toFront();
        requestFocus();
    }
 
    /**
     * 仅显示转向(左右)摇杆,隐藏移动摇杆。
     */
    public void showOnlyTurnJoystick() {
        if (moveJoystick != null) {
            moveJoystick.setVisible(false);
        }
        if (turnJoystick != null) {
            turnJoystick.setVisible(true);
        }
        revalidate();
        repaint();
        // 不再显示首页速度标签(已移除“行进/转向”内容)
        setVisible(true);
        toFront();
        requestFocus();
    }
 
    /**
     * 同时显示移动摇杆与转向摇杆(恢复默认视图)。
     */
    public void showBothJoysticks() {
        if (moveJoystick != null) {
            moveJoystick.setVisible(true);
        }
        if (turnJoystick != null) {
            turnJoystick.setVisible(true);
        }
        revalidate();
        repaint();
        // 不再显示首页速度标签(已移除“行进/转向”内容)
        setVisible(true);
        toFront();
        requestFocus();
    }
 
    @Override
    public void dispose() {
        // 停止并清理控制定时器
        stopForwardControlTimer();
        stopSteeringControlTimer();
        if (forwardControlTimer != null) {
            forwardControlTimer = null;
        }
        if (steeringControlTimer != null) {
            steeringControlTimer = null;
        }
        // 前后速度更新定时器
        if (speedUpdateTimer != null) {
            speedUpdateTimer.stop();
            speedUpdateTimer = null;
        }
        // 隐藏速度标签
        if (speedLabel != null) {
            speedLabel.setVisible(false);
            speedLabel.setText("");
        }
        for (ButtonInteraction interaction : buttonInteractions) {
            interaction.shutdown();
        }
        if (moveJoystick != null) {
            moveJoystick.dispose();
        }
        if (turnJoystick != null) {
            turnJoystick.dispose();
        }
        // 停止并清理刀盘按钮的定时器
        for (JButton bladeButton : bladeButtons) {
            Timer timer = (Timer) bladeButton.getClientProperty("longPressTimer");
            if (timer != null && timer.isRunning()) {
                timer.stop();
            }
        }
        bladeButtons.clear();
        super.dispose();
    }
 
    // 摇杆监听器接口
    private interface JoystickListener {
        void onJoystickMoved(double x, double y);
    }
    // 现代化摇杆组件(参考1.html风格)
    private class ModernJoystickComponent extends JPanel {
        private final String title;
        private final Color themeColor;
        private final boolean isVertical;
        private JoystickListener listener;
        private double joystickX = 0;
        private double joystickY = 0;
    // 视觉坐标:用于回中动画(不影响对外发送的逻辑数值)
    private double visualX = 0;
    private double visualY = 0;
        private boolean isDragging = false;
        // 缩小摇杆尺寸,使其在窗口中完整显示
        private final int joystickSize = 140;  // 从150缩小到100
        private final int handleSize = 60;     // 从60缩小到40
        private final int maxMoveDistance;
        private Timer returnToCenterTimer;
        
        public ModernJoystickComponent(String title, Color themeColor, boolean isVertical) {
            this.title = title;
            this.themeColor = themeColor;
            this.isVertical = isVertical;
            this.maxMoveDistance = (joystickSize - handleSize) / 2 - 10;
            
            setOpaque(false);
            // 调整组件整体大小,使摇杆更紧凑
            setPreferredSize(new Dimension(joystickSize + 40, joystickSize + 80));
            
            // 初始化回中定时器
            returnToCenterTimer = new Timer(20, new ActionListener() {
                private static final double RETURN_SPEED = 0.08;
                
                @Override
                public void actionPerformed(ActionEvent e) {
                    // 视觉回中:仅改变 visualX/visualY,不影响对外的逻辑值 joystickX/joystickY
                    if (!isDragging && (Math.abs(visualX) > 0.001 || Math.abs(visualY) > 0.001)) {
                        // 平滑回中视觉坐标
                        visualX = applyReturnForce(visualX, RETURN_SPEED);
                        visualY = applyReturnForce(visualY, RETURN_SPEED);
 
                        // 不在视觉回中期间向外发送非零数值(逻辑值已在释放时置0)
                        repaint();
 
                        // 如果视觉坐标已经接近中心,停止动画并确保为精确 0
                        if (Math.abs(visualX) < 0.001 && Math.abs(visualY) < 0.001) {
                            visualX = 0;
                            visualY = 0;
                            returnToCenterTimer.stop();
                        }
                    }
                }
            });
            returnToCenterTimer.setInitialDelay(0);
            
            addMouseListener(new MouseAdapter() {
                @Override
                public void mousePressed(MouseEvent e) {
                    isDragging = true;
                    if (returnToCenterTimer.isRunning()) {
                        returnToCenterTimer.stop();
                    }
                    updateJoystickPosition(e.getX(), e.getY());
                    // 视觉坐标随逻辑坐标同步
                    visualX = joystickX;
                    visualY = joystickY;
                    repaint();
                }
                
                @Override
                public void mouseReleased(MouseEvent e) {
                    isDragging = false;
                    // 立即把对外逻辑数值置为 0 并通知监听器,保证数值立刻为 0
                    joystickX = 0;
                    joystickY = 0;
                    if (listener != null) {
                        listener.onJoystickMoved(0, 0);
                    }
 
                    // 启动视觉回中动画(仅影响 visualX/visualY,不再发送监听器事件)
                    if (!returnToCenterTimer.isRunning()) {
                        returnToCenterTimer.start();
                    }
                }
            });
            
            addMouseMotionListener(new MouseMotionAdapter() {
                @Override
                public void mouseDragged(MouseEvent e) {
                    if (isDragging) {
                        updateJoystickPosition(e.getX(), e.getY());
                        // 拖动时视觉坐标与逻辑坐标同步
                        visualX = joystickX;
                        visualY = joystickY;
                        repaint();
                    }
                }
            });
        }
        
        private double applyReturnForce(double value, double speed) {
            if (value > 0) {
                return Math.max(0, value - speed);
            } else if (value < 0) {
                return Math.min(0, value + speed);
            }
            return 0;
        }
        
        private void updateJoystickPosition(int mouseX, int mouseY) {
            int centerX = joystickSize / 2 + 20;
            // 将摇杆绘制中心在组件内向下平移 5 像素(整体上移 5px)
            int centerY = joystickSize / 2 + 25;
            
            int rawDx = mouseX - centerX;
            int rawDy = mouseY - centerY;
 
            // 计算在圆形范围内的距离(使用原始整数位移计算距离)
            double distance = Math.sqrt((double)rawDx * rawDx + (double)rawDy * rawDy);
 
            // 使用 double 进行限制与缩放,避免整型截断导致最大值达不到 1.0 的问题
            double scaledDx = rawDx;
            double scaledDy = rawDy;
            if (distance > maxMoveDistance && distance > 0) {
                double factor = maxMoveDistance / distance;
                scaledDx = rawDx * factor;
                scaledDy = rawDy * factor;
            }
 
            // 转换为-1到1的范围(使用 double,避免精度丢失)
            joystickX = maxMoveDistance > 0 ? scaledDx / (double)maxMoveDistance : 0;
            joystickY = maxMoveDistance > 0 ? scaledDy / (double)maxMoveDistance : 0;
            
            // 根据摇杆方向调整
            if (isVertical) {
                joystickX = 0; // 竖向摇杆不处理X轴
            } else {
                joystickY = 0; // 横向摇杆不处理Y轴
            }
            
            if (listener != null) {
                listener.onJoystickMoved(joystickX, joystickY);
            }
        }
        
        public void setJoystickListener(JoystickListener listener) {
            this.listener = listener;
        }
        
        
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g;
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            
            int centerX = joystickSize / 2 + 20;
            // 与 updateJoystickPosition 保持一致:将绘制中心向下平移 5 像素(整体上移 5px)
            int centerY = joystickSize / 2 + 25;
            
            // 绘制标题
            if (title != null && !title.isEmpty()) {
                g2d.setColor(new Color(255, 255, 255, 180));
                g2d.setFont(new Font("微软雅黑", Font.BOLD, 16));
                FontMetrics fm = g2d.getFontMetrics();
                int titleWidth = fm.stringWidth(title);
                g2d.drawString(title, centerX - titleWidth / 2, 30);
            }
            
            // 绘制摇杆底座(渐变圆形)
            RadialGradientPaint baseGradient = new RadialGradientPaint(
                centerX, centerY, joystickSize / 2,
                new float[]{0.0f, 1.0f},
                new Color[]{
                    new Color(44, 62, 80),  // 深蓝灰
                    new Color(26, 37, 47)   // 更深的蓝灰
                }
            );
            g2d.setPaint(baseGradient);
            g2d.fillOval(centerX - joystickSize / 2, centerY - joystickSize / 2, 
                        joystickSize, joystickSize);
            
            // 绘制底座边框
            g2d.setColor(new Color(255, 255, 255, 20));
            g2d.setStroke(new BasicStroke(6));
            g2d.drawOval(centerX - joystickSize / 2, centerY - joystickSize / 2, 
                        joystickSize, joystickSize);
            
            // 绘制内部阴影
            g2d.setColor(new Color(0, 0, 0, 80));
            for (int i = 1; i <= 15; i++) {
                g2d.drawOval(centerX - joystickSize / 2 + i, centerY - joystickSize / 2 + i, 
                            joystickSize - 2 * i, joystickSize - 2 * i);
            }
            
            // 绘制方向指示线
            g2d.setColor(new Color(255, 255, 255, 80));
            g2d.setStroke(new BasicStroke(2));
            
            // 垂直指示线
            g2d.drawLine(centerX, centerY - joystickSize / 4, 
                        centerX, centerY + joystickSize / 4);
            // 水平指示线
            g2d.drawLine(centerX - joystickSize / 4, centerY, 
                        centerX + joystickSize / 4, centerY);
            
            // 绘制方向箭头
            g2d.setColor(new Color(255, 255, 255, 100));
            g2d.setFont(new Font("微软雅黑", Font.BOLD, 12));
            
            if (isVertical) {
                // 竖向箭头
                g2d.drawString("↑", centerX - 4, centerY - joystickSize / 2 + 20);
                g2d.drawString("↓", centerX - 4, centerY + joystickSize / 2 - 8);
            } else {
                // 横向箭头
                g2d.drawString("←", centerX - joystickSize / 2 + 12, centerY + 4);
                g2d.drawString("→", centerX + joystickSize / 2 - 16, centerY + 4);
            }
            
            // 计算摇杆手柄位置(使用视觉坐标 visualX/visualY,这样回中动画只影响视觉)
            int handleX = centerX + (int)(visualX * maxMoveDistance);
            int handleY = centerY + (int)(visualY * maxMoveDistance);
            
            // 绘制手柄阴影
            g2d.setColor(new Color(0, 0, 0, 60));
            g2d.fillOval(handleX - handleSize / 2 + 3, handleY - handleSize / 2 + 3, 
                        handleSize, handleSize);
            
            // 绘制手柄(渐变圆形)
            RadialGradientPaint handleGradient = new RadialGradientPaint(
                handleX, handleY, handleSize / 2,
                new float[]{0.0f, 0.8f, 1.0f},
                new Color[]{
                    themeColor.brighter(),
                    themeColor,
                    themeColor.darker()
                }
            );
            g2d.setPaint(handleGradient);
            g2d.fillOval(handleX - handleSize / 2, handleY - handleSize / 2, 
                        handleSize, handleSize);
            
            // 绘制手柄高光
            g2d.setColor(new Color(255, 255, 255, 100));
            g2d.fillOval(handleX - handleSize / 4, handleY - handleSize / 4, 
                        handleSize / 2, handleSize / 2);
            
            // 绘制手柄中心点
            g2d.setColor(Color.WHITE);
            g2d.fillOval(handleX - 6, handleY - 6, 12, 12);
            g2d.setColor(themeColor.darker());
            g2d.drawOval(handleX - 6, handleY - 6, 12, 12);
            
            
        }
        
        public void dispose() {
            // 清理资源
            listener = null;
            if (returnToCenterTimer != null) {
                returnToCenterTimer.stop();
                returnToCenterTimer = null;
            }
        }
    }
 
    // 保留原有的ButtonInteraction类,但不再使用
    private final class ButtonInteraction extends MouseAdapter {
        // ... 保持原有代码不变 ...
 
        public void shutdown() {
            // 保留占位实现,便于向后兼容
        }
    }
}