1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package io.github.tonywasher.joceanus.tethys.javafx.menu;
18
19 import io.github.tonywasher.joceanus.oceanus.event.OceanusEventManager;
20 import io.github.tonywasher.joceanus.oceanus.event.OceanusEventRegistrar;
21 import javafx.animation.KeyFrame;
22 import javafx.animation.Timeline;
23 import javafx.collections.ObservableList;
24 import javafx.event.Event;
25 import javafx.event.EventHandler;
26 import javafx.event.EventType;
27 import javafx.geometry.Dimension2D;
28 import javafx.geometry.Insets;
29 import javafx.geometry.Point2D;
30 import javafx.geometry.Pos;
31 import javafx.geometry.Side;
32 import javafx.scene.Node;
33 import javafx.scene.Scene;
34 import javafx.scene.control.ContentDisplay;
35 import javafx.scene.control.Label;
36 import javafx.scene.input.KeyEvent;
37 import javafx.scene.input.MouseEvent;
38 import javafx.scene.input.ScrollEvent;
39 import javafx.scene.layout.BorderPane;
40 import javafx.scene.layout.VBox;
41 import javafx.scene.shape.Polygon;
42 import javafx.stage.Modality;
43 import javafx.stage.Stage;
44 import javafx.stage.StageStyle;
45 import javafx.util.Duration;
46 import io.github.tonywasher.joceanus.tethys.api.base.TethysUIConstant;
47 import io.github.tonywasher.joceanus.tethys.api.base.TethysUIEvent;
48 import io.github.tonywasher.joceanus.tethys.api.base.TethysUIIcon;
49 import io.github.tonywasher.joceanus.tethys.api.menu.TethysUIScrollIcon;
50 import io.github.tonywasher.joceanus.tethys.api.menu.TethysUIScrollItem;
51 import io.github.tonywasher.joceanus.tethys.api.menu.TethysUIScrollMenu;
52 import io.github.tonywasher.joceanus.tethys.api.menu.TethysUIScrollSubMenu;
53 import io.github.tonywasher.joceanus.tethys.api.menu.TethysUIScrollToggle;
54 import io.github.tonywasher.joceanus.tethys.core.menu.TethysUICoreScrollMenu;
55 import io.github.tonywasher.joceanus.tethys.javafx.base.TethysUIFXArrowIcon;
56 import io.github.tonywasher.joceanus.tethys.javafx.base.TethysUIFXIcon;
57 import io.github.tonywasher.joceanus.tethys.javafx.base.TethysUIFXUtils;
58
59 import java.util.ArrayList;
60 import java.util.Iterator;
61 import java.util.List;
62
63
64
65
66
67
68
69
70 public class TethysUIFXScrollMenu<T>
71 implements TethysUIScrollMenu<T> {
72
73
74
75 private static final String CSS_STYLE_NAME = "jtethys-javafx-contextmenu.css";
76
77
78
79
80 private static final String CSS_STYLE = TethysUIFXScrollMenu.class.getResource(CSS_STYLE_NAME).toExternalForm();
81
82
83
84
85 private static final String STYLE_MENU = TethysUIFXUtils.CSS_STYLE_BASE + "-context";
86
87
88
89
90 static final String STYLE_ITEM = STYLE_MENU + "-item";
91
92
93
94
95 private final OceanusEventManager<TethysUIEvent> theEventManager;
96
97
98
99
100 private final List<TethysUIFXScrollElement> theMenuItems;
101
102
103
104
105 private final ObservableList<Node> theActiveItems;
106
107
108
109
110 private int theFirstIndex;
111
112
113
114
115 private int theMaxDisplayItems;
116
117
118
119
120 private final ScrollControl theUpItem;
121
122
123
124
125 private final ScrollControl theDownItem;
126
127
128
129
130 private final BorderPane theContainer;
131
132
133
134
135 private final TethysUIFXScrollSubMenu<T> theParentMenu;
136
137
138
139
140 private final TethysUIFXScrollMenu<T> theParentContext;
141
142
143
144
145 private Stage theStage;
146
147
148
149
150 private TethysUIFXScrollSubMenu<T> theActiveMenu;
151
152
153
154
155 private TethysUIFXScrollItem<T> theActiveItem;
156
157
158
159
160 private TethysUIFXScrollItem<T> theSelectedItem;
161
162
163
164
165 private boolean closeOnToggle;
166
167
168
169
170 private boolean needReBuild;
171
172
173
174
175 private Dimension2D theMenuSize;
176
177
178
179
180 TethysUIFXScrollMenu() {
181 this(TethysUICoreScrollMenu.DEFAULT_ITEMCOUNT);
182 }
183
184
185
186
187
188
189 TethysUIFXScrollMenu(final int pMaxDisplayItems) {
190 this(null, pMaxDisplayItems);
191 }
192
193
194
195
196
197
198 TethysUIFXScrollMenu(final TethysUIFXScrollSubMenu<T> pParent) {
199 this(pParent, pParent.getContext().getMaxDisplayItems());
200 }
201
202
203
204
205
206
207
208 private TethysUIFXScrollMenu(final TethysUIFXScrollSubMenu<T> pParent,
209 final int pMaxDisplayItems) {
210
211 if (pMaxDisplayItems <= 0) {
212 throw new IllegalArgumentException(TethysUICoreScrollMenu.ERROR_MAXITEMS);
213 }
214
215
216 theEventManager = new OceanusEventManager<>();
217
218
219 theMaxDisplayItems = pMaxDisplayItems;
220 theParentMenu = pParent;
221 theParentContext = theParentMenu == null
222 ? null
223 : theParentMenu.getContext();
224
225
226 needReBuild = true;
227
228
229 closeOnToggle = true;
230
231
232 theUpItem = new ScrollControl(TethysUIFXArrowIcon.UP.getArrow(), -1);
233 theDownItem = new ScrollControl(TethysUIFXArrowIcon.DOWN.getArrow(), 1);
234
235
236 theMenuItems = new ArrayList<>();
237 final VBox myBox = new VBox();
238 myBox.setSpacing(2);
239 myBox.setPadding(new Insets(2, 2, 2, 2));
240 theActiveItems = myBox.getChildren();
241
242
243 theContainer = new BorderPane();
244 theContainer.setCenter(myBox);
245 theContainer.getStyleClass().add(STYLE_MENU);
246 }
247
248 @Override
249 public OceanusEventRegistrar<TethysUIEvent> getEventRegistrar() {
250 return theEventManager.getEventRegistrar();
251 }
252
253
254
255
256 private void ensureStage() {
257 if (theStage == null) {
258 createStage();
259 }
260 }
261
262
263
264
265 private void createStage() {
266
267 theStage = new Stage(StageStyle.UNDECORATED);
268 theStage.initModality(Modality.NONE);
269
270
271 final Scene myScene = new Scene(theContainer);
272 final ObservableList<String> mySheets = myScene.getStylesheets();
273 mySheets.add(CSS_STYLE);
274 theStage.setScene(myScene);
275
276
277 theStage.focusedProperty().addListener((v, o, n) -> handleFocusChange(n));
278
279
280 myScene.setOnKeyPressed(this::handleKeyPress);
281
282
283 theStage.addEventFilter(ScrollEvent.SCROLL, this::handleScroll);
284 }
285
286
287
288
289 private void closeOnFocusLoss() {
290
291 if (theParentContext != null) {
292 theParentContext.closeOnFocusLoss();
293 }
294
295
296 closeMenu();
297 }
298
299 @Override
300 public TethysUIScrollItem<T> getSelectedItem() {
301 return theSelectedItem;
302 }
303
304
305
306
307
308
309 public int getMaxDisplayItems() {
310 return theMaxDisplayItems;
311 }
312
313 @Override
314 public void setCloseOnToggle(final boolean pCloseOnToggle) {
315
316 closeOnToggle = pCloseOnToggle;
317 }
318
319
320
321
322
323
324 public boolean isShowing() {
325 return theStage != null
326 && theStage.isShowing();
327 }
328
329 @Override
330 public void setMaxDisplayItems(final int pMaxDisplayItems) {
331
332 if (pMaxDisplayItems <= 0) {
333 throw new IllegalArgumentException(TethysUICoreScrollMenu.ERROR_MAXITEMS);
334 }
335
336
337 if (isShowing()) {
338 throw new IllegalStateException();
339 }
340
341
342 theMaxDisplayItems = pMaxDisplayItems;
343
344
345 final Iterator<TethysUIFXScrollElement> myIterator = theMenuItems.iterator();
346 while (myIterator.hasNext()) {
347 final TethysUIFXScrollElement myChild = myIterator.next();
348
349
350 if (myChild instanceof TethysUIFXScrollSubMenu) {
351
352 final TethysUIFXScrollSubMenu<?> mySubMenu = (TethysUIFXScrollSubMenu<?>) myChild;
353 mySubMenu.setMaxDisplayItems(pMaxDisplayItems);
354 }
355 }
356
357
358 needReBuild = true;
359 }
360
361
362
363
364
365
366
367 public void showMenuAtPosition(final Node pAnchor,
368 final Side pSide) {
369
370 ensureStage();
371
372
373 determineSize();
374
375
376 if (theMenuSize != null) {
377
378 final Point2D myLocation = TethysUIFXUtils.obtainDisplayPoint(pAnchor, pSide, theMenuSize);
379
380
381 showMenuAtLocation(myLocation);
382 }
383 }
384
385
386
387
388
389
390
391
392 public void showMenuAtPosition(final Node pAnchor,
393 final double pX,
394 final double pY) {
395
396 ensureStage();
397
398
399 determineSize();
400
401
402 if (theMenuSize != null) {
403
404 final Point2D myRequest = new Point2D(pX, pY);
405 final Point2D myLocation = TethysUIFXUtils.obtainDisplayPoint(pAnchor, myRequest, theMenuSize);
406
407
408 showMenuAtLocation(myLocation);
409 }
410 }
411
412
413
414
415
416
417 private void showMenuAtLocation(final Point2D pLocation) {
418
419 theStage.setX(pLocation.getX());
420 theStage.setY(pLocation.getY());
421
422
423 showMenu();
424 }
425
426
427
428
429 private void showMenu() {
430
431 theSelectedItem = null;
432 theActiveMenu = null;
433 theActiveItem = null;
434
435
436 theStage.show();
437 }
438
439
440
441
442 void closeMenu() {
443
444 closeChildren();
445
446
447 theStage.close();
448 }
449
450
451
452
453 void clearActiveItem() {
454 theActiveItem = null;
455 }
456
457
458
459
460
461
462 void setSelectedItem(final TethysUIFXScrollItem<T> pItem) {
463
464 if (theParentContext != null) {
465
466 theParentContext.setSelectedItem(pItem);
467
468
469 } else {
470
471 boolean doCloseMenu = true;
472
473
474 theSelectedItem = pItem;
475 if (theSelectedItem instanceof TethysUIFXScrollToggle) {
476 final TethysUIScrollToggle<?> myItem = (TethysUIScrollToggle<?>) theSelectedItem;
477 myItem.toggleSelected();
478 doCloseMenu = closeOnToggle;
479 }
480
481
482 if (doCloseMenu) {
483
484 closeMenu();
485 }
486
487
488 theEventManager.fireEvent(TethysUIEvent.NEWVALUE, theSelectedItem);
489 }
490 }
491
492
493
494
495 private void handleEscapeKey() {
496
497 if (theParentContext != null) {
498
499 theParentContext.handleEscapeKey();
500
501
502 } else {
503
504 theEventManager.fireEvent(TethysUIEvent.WINDOWCLOSED);
505
506
507 closeMenu();
508 }
509 }
510
511
512
513
514 private void handleEnterKey() {
515
516 if (theActiveItem != null) {
517
518 setSelectedItem(theActiveItem);
519 }
520 }
521
522
523
524
525
526
527 private void handleFocusChange(final Boolean pState) {
528
529 if (Boolean.TRUE.equals(!pState)
530 && theActiveMenu == null) {
531
532 if (theParentMenu == null) {
533 theEventManager.fireEvent(TethysUIEvent.WINDOWCLOSED);
534 }
535
536
537 if (isShowing()) {
538 closeOnFocusLoss();
539 }
540 }
541 }
542
543
544
545
546
547
548 private void handleKeyPress(final KeyEvent pEvent) {
549 switch (pEvent.getCode()) {
550 case ESCAPE:
551 handleEscapeKey();
552 break;
553 case ENTER:
554 handleEnterKey();
555 break;
556 default:
557 break;
558 }
559 }
560
561
562
563
564
565
566 private void handleScroll(final ScrollEvent pEvent) {
567
568 final double myDelta = pEvent.getDeltaY() / pEvent.getMultiplierY();
569 requestScroll((int) -myDelta);
570
571
572 pEvent.consume();
573 }
574
575
576
577
578
579
580 void handleActiveItem(final TethysUIFXScrollItem<T> pItem) {
581
582 closeChildren();
583
584
585 theActiveItem = pItem;
586 }
587
588
589
590
591
592
593 void handleActiveMenu(final TethysUIFXScrollSubMenu<T> pMenu) {
594
595 if (theActiveMenu != null
596 && theActiveMenu.getIndex() != pMenu.getIndex()) {
597 theActiveMenu.hide();
598 }
599
600
601 theActiveItem = null;
602
603
604 theActiveMenu = pMenu;
605 }
606
607 @Override
608 public void removeAllItems() {
609
610 if (isShowing()) {
611 closeMenu();
612 }
613
614
615 theMenuItems.clear();
616 theFirstIndex = 0;
617 needReBuild = true;
618
619
620 theSelectedItem = null;
621 }
622
623 @Override
624 public boolean isEmpty() {
625
626 return theMenuItems.isEmpty();
627 }
628
629
630
631
632
633
634 protected int getItemCount() {
635
636 return theMenuItems.size();
637 }
638
639
640
641
642
643
644 void scrollToIndex(final int pIndex) {
645
646 showIndex(pIndex);
647
648
649 if (theParentMenu != null) {
650 theParentMenu.scrollToMenu();
651 }
652 }
653
654
655
656
657
658
659 private void showIndex(final int pIndex) {
660
661 final int myCount = theMenuItems.size();
662 if (pIndex < 0
663 || pIndex >= myCount) {
664 return;
665 }
666
667
668 if (pIndex < theFirstIndex) {
669
670 requestScroll(pIndex - theFirstIndex);
671 return;
672 }
673
674
675 final int myLastIndex = theFirstIndex
676 + theMaxDisplayItems
677 - 1;
678 if (myLastIndex < pIndex) {
679
680 requestScroll(pIndex - myLastIndex);
681 }
682 }
683
684 @Override
685 public TethysUIScrollItem<T> addItem(final T pValue) {
686
687 return addItem(pValue, pValue.toString(), null);
688 }
689
690 @Override
691 public TethysUIScrollItem<T> addItem(final T pValue,
692 final String pName) {
693
694 return addItem(pValue, pName, null);
695 }
696
697 @Override
698 public TethysUIScrollItem<T> addItem(final T pValue,
699 final TethysUIIcon pGraphic) {
700
701 return addItem(pValue, pValue.toString(), pGraphic);
702 }
703
704 @Override
705 public TethysUIScrollItem<T> addNullItem(final String pName) {
706
707 return addItem(null, pName, null);
708 }
709
710 @Override
711 public TethysUIScrollItem<T> addNullItem(final String pName,
712 final TethysUIIcon pGraphic) {
713
714 return addItem(null, pName, pGraphic);
715 }
716
717 @Override
718 public TethysUIScrollItem<T> addItem(final T pValue,
719 final String pName,
720 final TethysUIIcon pGraphic) {
721
722 if (isShowing()) {
723 throw new IllegalStateException();
724 }
725
726
727 final TethysUIFXScrollItem<T> myItem = new TethysUIFXScrollItem<>(this, pValue, pName, pGraphic);
728
729
730 theMenuItems.add(myItem);
731 needReBuild = true;
732 return myItem;
733 }
734
735 @Override
736 public TethysUIScrollSubMenu<T> addSubMenu(final String pName) {
737
738 return addSubMenu(pName, null);
739 }
740
741 @Override
742 public TethysUIScrollSubMenu<T> addSubMenu(final String pName,
743 final TethysUIIcon pGraphic) {
744
745 if (isShowing()) {
746 throw new IllegalStateException();
747 }
748
749
750 final TethysUIFXScrollSubMenu<T> myMenu = new TethysUIFXScrollSubMenu<>(this, pName, pGraphic);
751
752
753 theMenuItems.add(myMenu);
754 needReBuild = true;
755 return myMenu;
756 }
757
758 @Override
759 public TethysUIScrollToggle<T> addToggleItem(final T pValue) {
760
761 return addToggleItem(pValue, pValue.toString());
762 }
763
764 @Override
765 public TethysUIScrollToggle<T> addToggleItem(final T pValue,
766 final String pName) {
767
768 if (isShowing()) {
769 throw new IllegalStateException();
770 }
771
772
773 final TethysUIFXScrollToggle<T> myItem = new TethysUIFXScrollToggle<>(this, pValue, pName);
774
775
776 theMenuItems.add(myItem);
777 needReBuild = true;
778 return myItem;
779 }
780
781
782
783
784 void closeChildren() {
785
786 if (theActiveMenu != null) {
787 theActiveMenu.hide();
788 }
789 theActiveMenu = null;
790 }
791
792
793
794
795 private void determineSize() {
796
797 if (!needReBuild) {
798 return;
799 }
800
801
802 if (!theMenuItems.isEmpty()) {
803
804 final int myCount = theMenuItems.size();
805 final int myScroll = Math.min(theMaxDisplayItems, myCount);
806
807
808 theActiveItems.clear();
809
810
811 if (myScroll == myCount) {
812
813 theContainer.setTop(null);
814 theContainer.setBottom(null);
815
816
817 for (int i = 0; i < myCount; i++) {
818
819 theActiveItems.add(theMenuItems.get(i).getBorderPane());
820 }
821
822
823 theStage.show();
824 theStage.close();
825
826
827 theMenuSize = new Dimension2D(theStage.getWidth(), theStage.getHeight());
828
829
830 } else {
831
832 theContainer.setTop(theUpItem.getLabel());
833 theContainer.setBottom(theDownItem.getLabel());
834 theUpItem.setVisible(true);
835 theDownItem.setVisible(true);
836
837
838 for (final TethysUIFXScrollElement myItem : theMenuItems) {
839
840 theActiveItems.add(myItem.getBorderPane());
841 }
842
843
844 theStage.show();
845 theStage.close();
846 final double myWidth = theStage.getWidth();
847
848
849 theActiveItems.clear();
850
851
852 if (theFirstIndex < 0) {
853 theFirstIndex = 0;
854 }
855
856
857 int myMaxIndex = theFirstIndex
858 + myScroll;
859 if (myMaxIndex > myCount) {
860
861 theFirstIndex = myCount
862 - myScroll;
863 myMaxIndex = myCount;
864 }
865
866
867 for (int i = theFirstIndex; i < myMaxIndex; i++) {
868
869 theActiveItems.add(theMenuItems.get(i).getBorderPane());
870 }
871
872
873 theStage.show();
874 theStage.close();
875 final double myHeight = theStage.getHeight();
876
877
878 theUpItem.setVisible(theFirstIndex > 0);
879 theDownItem.setVisible(myMaxIndex < myCount);
880
881
882 theMenuSize = new Dimension2D(myWidth, myHeight);
883
884
885 theStage.setMinWidth(myWidth);
886 }
887
888
889 } else {
890 theMenuSize = null;
891 }
892
893
894 needReBuild = false;
895 }
896
897
898
899
900 protected void scrollPlusOne() {
901
902 if (!needReBuild) {
903
904 final int myCount = theMenuItems.size();
905
906
907 closeChildren();
908
909
910 theUpItem.setVisible(true);
911
912
913 theActiveItems.remove(0);
914
915
916 final int myLast = theFirstIndex + theMaxDisplayItems;
917 final TethysUIFXScrollElement myItem = theMenuItems.get(myLast);
918 theActiveItems.add(myItem.getBorderPane());
919
920
921 theDownItem.setVisible(myLast + 1 < myCount);
922 }
923
924
925 theFirstIndex++;
926 }
927
928
929
930
931 protected void scrollMinusOne() {
932
933 if (!needReBuild) {
934
935 closeChildren();
936
937
938 theDownItem.setVisible(true);
939
940
941 theActiveItems.remove(theMaxDisplayItems - 1);
942
943
944 final TethysUIFXScrollElement myItem = theMenuItems.get(theFirstIndex - 1);
945 theActiveItems.add(0, myItem.getBorderPane());
946
947
948 theUpItem.setVisible(theFirstIndex > 1);
949 }
950
951
952 theFirstIndex--;
953 }
954
955
956
957
958
959
960 void requestScroll(final int pDelta) {
961
962 if (pDelta > 0) {
963
964 final int myCount = theMenuItems.size();
965 final int mySpace = myCount - theFirstIndex - theMaxDisplayItems;
966 int myScroll = Math.min(mySpace, pDelta);
967
968
969 while (myScroll-- > 0) {
970
971 scrollPlusOne();
972 }
973
974
975 } else if (theFirstIndex > 0) {
976
977 int myScroll = Math.min(theFirstIndex, -pDelta);
978
979
980 while (myScroll-- > 0) {
981
982 scrollMinusOne();
983 }
984 }
985 }
986
987
988
989
990 public abstract static class TethysUIFXScrollElement {
991
992
993
994 private final BorderPane theBorderPane;
995
996
997
998
999 private final Label theLabel;
1000
1001
1002
1003
1004 private final Label theIcon;
1005
1006
1007
1008
1009
1010
1011
1012 private TethysUIFXScrollElement(final String pName,
1013 final TethysUIIcon pGraphic) {
1014
1015 theBorderPane = new BorderPane();
1016
1017
1018 theLabel = new Label();
1019 theLabel.setText(pName);
1020 theLabel.setMaxWidth(Double.MAX_VALUE);
1021
1022
1023 theIcon = new Label();
1024 theIcon.setGraphic(TethysUIFXIcon.getIcon(pGraphic));
1025 theIcon.setMinWidth(TethysUIConstant.DEFAULT_ICONSIZE);
1026
1027
1028 theBorderPane.setLeft(theIcon);
1029 theBorderPane.setCenter(theLabel);
1030
1031
1032 theBorderPane.getStyleClass().add(STYLE_ITEM);
1033 }
1034
1035
1036
1037
1038
1039
1040 BorderPane getBorderPane() {
1041 return theBorderPane;
1042 }
1043
1044
1045
1046
1047
1048
1049
1050
1051 <T extends Event> void addEventFilter(final EventType<T> pEvent,
1052 final EventHandler<? super T> pFilter) {
1053 theBorderPane.addEventFilter(pEvent, pFilter);
1054 }
1055
1056
1057
1058
1059
1060
1061 public String getText() {
1062 return theLabel.getText();
1063 }
1064
1065
1066
1067
1068
1069
1070 protected void setIcon(final TethysUIFXIcon pGraphic) {
1071 theIcon.setGraphic(TethysUIFXIcon.getIcon(pGraphic));
1072 }
1073
1074
1075
1076
1077 protected void addMenuIcon() {
1078 final Label myLabel = new Label();
1079 myLabel.setGraphic(TethysUIFXArrowIcon.RIGHT.getArrow());
1080 theBorderPane.setRight(myLabel);
1081 }
1082 }
1083
1084
1085
1086
1087
1088
1089 protected static class TethysUIFXScrollItem<T>
1090 extends TethysUIFXScrollElement
1091 implements TethysUIScrollItem<T> {
1092
1093
1094
1095 private final TethysUIFXScrollMenu<T> theContext;
1096
1097
1098
1099
1100 private final int theIndex;
1101
1102
1103
1104
1105 private final T theValue;
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115 protected TethysUIFXScrollItem(final TethysUIFXScrollMenu<T> pContext,
1116 final T pValue,
1117 final String pName,
1118 final TethysUIIcon pGraphic) {
1119
1120 super(pName, pGraphic);
1121
1122
1123 theContext = pContext;
1124 theValue = pValue;
1125
1126
1127 theIndex = theContext.getItemCount();
1128
1129
1130 addEventFilter(MouseEvent.MOUSE_ENTERED, e -> theContext.handleActiveItem(this));
1131
1132
1133 addEventFilter(MouseEvent.MOUSE_CLICKED, e -> theContext.setSelectedItem(this));
1134 }
1135
1136 @Override
1137 public T getValue() {
1138 return theValue;
1139 }
1140
1141 @Override
1142 public void scrollToItem() {
1143 theContext.scrollToIndex(theIndex);
1144 }
1145 }
1146
1147
1148
1149
1150
1151
1152 private static final class TethysUIFXScrollToggle<T>
1153 extends TethysUIFXScrollItem<T>
1154 implements TethysUIScrollToggle<T> {
1155
1156
1157
1158 private boolean isSelected;
1159
1160
1161
1162
1163
1164
1165
1166
1167 TethysUIFXScrollToggle(final TethysUIFXScrollMenu<T> pContext,
1168 final T pValue,
1169 final String pName) {
1170
1171 super(pContext, pValue, pName, null);
1172 }
1173
1174 @Override
1175 public boolean isSelected() {
1176 return isSelected;
1177 }
1178
1179 @Override
1180 public void setSelected(final boolean pSelected) {
1181 isSelected = pSelected;
1182 setIcon(isSelected
1183 ? TethysUIFXUtils.getIconAtSize(TethysUIScrollIcon.CHECKMARK, TethysUIConstant.DEFAULT_ICONSIZE)
1184 : null);
1185 }
1186
1187 @Override
1188 public void toggleSelected() {
1189 setSelected(!isSelected);
1190 }
1191 }
1192
1193
1194
1195
1196
1197
1198 public static final class TethysUIFXScrollSubMenu<T>
1199 extends TethysUIFXScrollElement
1200 implements TethysUIScrollSubMenu<T> {
1201
1202
1203
1204 private final TethysUIFXScrollMenu<T> theContext;
1205
1206
1207
1208
1209 private final int theIndex;
1210
1211
1212
1213
1214 private final TethysUIFXScrollMenu<T> theSubMenu;
1215
1216
1217
1218
1219
1220
1221
1222
1223 TethysUIFXScrollSubMenu(final TethysUIFXScrollMenu<T> pContext,
1224 final String pName,
1225 final TethysUIIcon pGraphic) {
1226
1227 super(pName, pGraphic);
1228
1229
1230 theContext = pContext;
1231
1232
1233 theSubMenu = new TethysUIFXScrollMenu<>(this);
1234
1235
1236 theIndex = theContext.getItemCount();
1237
1238
1239 addMenuIcon();
1240
1241
1242 addEventFilter(MouseEvent.MOUSE_ENTERED, e -> {
1243 theContext.handleActiveMenu(this);
1244 theSubMenu.showMenuAtPosition(getBorderPane(), Side.RIGHT);
1245 });
1246 }
1247
1248
1249
1250
1251
1252
1253 TethysUIFXScrollMenu<T> getContext() {
1254 return theContext;
1255 }
1256
1257 @Override
1258 public TethysUIFXScrollMenu<T> getSubMenu() {
1259 return theSubMenu;
1260 }
1261
1262
1263
1264
1265
1266
1267 int getIndex() {
1268 return theIndex;
1269 }
1270
1271
1272
1273
1274 void hide() {
1275 theSubMenu.closeMenu();
1276 }
1277
1278
1279
1280
1281
1282
1283
1284 private void setMaxDisplayItems(final int pMaxDisplayItems) {
1285
1286 theSubMenu.setMaxDisplayItems(pMaxDisplayItems);
1287 }
1288
1289
1290
1291
1292 void scrollToMenu() {
1293 theContext.scrollToIndex(theIndex);
1294 }
1295 }
1296
1297
1298
1299
1300 private final class ScrollControl {
1301
1302
1303
1304 private final Label theLabel;
1305
1306
1307
1308
1309 private final int theIncrement;
1310
1311
1312
1313
1314 private final Timeline theKickStartTimer;
1315
1316
1317
1318
1319 private final Timeline theRepeatTimer;
1320
1321
1322
1323
1324
1325
1326
1327 ScrollControl(final Polygon pIcon,
1328 final int pIncrement) {
1329
1330 theLabel = new Label();
1331
1332
1333 theLabel.setGraphic(pIcon);
1334 theLabel.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
1335 theLabel.setAlignment(Pos.CENTER);
1336 theLabel.setMaxWidth(Double.MAX_VALUE);
1337 theLabel.getStyleClass().add(STYLE_ITEM);
1338
1339
1340 theIncrement = pIncrement;
1341
1342
1343 theLabel.addEventFilter(MouseEvent.MOUSE_CLICKED, e -> processScroll());
1344
1345
1346 theLabel.addEventHandler(MouseEvent.MOUSE_ENTERED, e -> processMouseEnter());
1347
1348
1349 theLabel.addEventHandler(MouseEvent.MOUSE_EXITED, e -> processMouseExit());
1350
1351
1352 theKickStartTimer = new Timeline(new KeyFrame(Duration.millis(TethysUICoreScrollMenu.INITIAL_SCROLLDELAY),
1353 e -> processScroll()));
1354 theRepeatTimer = new Timeline(new KeyFrame(Duration.millis(TethysUICoreScrollMenu.REPEAT_SCROLLDELAY),
1355 e -> processScroll()));
1356 }
1357
1358
1359
1360
1361
1362
1363 Label getLabel() {
1364 return theLabel;
1365 }
1366
1367
1368
1369
1370
1371
1372 void setVisible(final boolean pVisible) {
1373 theLabel.setVisible(pVisible);
1374 }
1375
1376
1377
1378
1379 private void processScroll() {
1380
1381 requestScroll(theIncrement);
1382 theRepeatTimer.play();
1383 }
1384
1385
1386
1387
1388 private void processMouseEnter() {
1389
1390 closeChildren();
1391
1392
1393 clearActiveItem();
1394
1395
1396 theKickStartTimer.play();
1397 }
1398
1399
1400
1401
1402 private void processMouseExit() {
1403
1404 theKickStartTimer.stop();
1405 theRepeatTimer.stop();
1406 }
1407 }
1408 }