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 io.github.tonywasher.joceanus.tethys.api.base.TethysUIConstant;
22 import io.github.tonywasher.joceanus.tethys.api.base.TethysUIEvent;
23 import io.github.tonywasher.joceanus.tethys.api.base.TethysUIIcon;
24 import io.github.tonywasher.joceanus.tethys.api.base.TethysUIIconId;
25 import io.github.tonywasher.joceanus.tethys.api.menu.TethysUIScrollIcon;
26 import io.github.tonywasher.joceanus.tethys.api.menu.TethysUIScrollItem;
27 import io.github.tonywasher.joceanus.tethys.api.menu.TethysUIScrollMenu;
28 import io.github.tonywasher.joceanus.tethys.api.menu.TethysUIScrollToggle;
29 import io.github.tonywasher.joceanus.tethys.core.menu.TethysUICoreScrollMenu;
30 import io.github.tonywasher.joceanus.tethys.javafx.base.TethysUIFXArrowIcon;
31 import io.github.tonywasher.joceanus.tethys.javafx.base.TethysUIFXIcon;
32 import io.github.tonywasher.joceanus.tethys.javafx.base.TethysUIFXUtils;
33 import javafx.animation.KeyFrame;
34 import javafx.animation.Timeline;
35 import javafx.collections.ObservableList;
36 import javafx.event.Event;
37 import javafx.event.EventHandler;
38 import javafx.event.EventType;
39 import javafx.geometry.Dimension2D;
40 import javafx.geometry.Insets;
41 import javafx.geometry.Point2D;
42 import javafx.geometry.Pos;
43 import javafx.geometry.Side;
44 import javafx.scene.Node;
45 import javafx.scene.Scene;
46 import javafx.scene.control.ContentDisplay;
47 import javafx.scene.control.Label;
48 import javafx.scene.input.KeyEvent;
49 import javafx.scene.input.MouseEvent;
50 import javafx.scene.input.ScrollEvent;
51 import javafx.scene.layout.BorderPane;
52 import javafx.scene.layout.VBox;
53 import javafx.scene.shape.Polygon;
54 import javafx.stage.Modality;
55 import javafx.stage.Stage;
56 import javafx.stage.StageStyle;
57 import javafx.util.Duration;
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 (!pState && theActiveMenu == null) {
530
531 if (theParentMenu == null) {
532 theEventManager.fireEvent(TethysUIEvent.WINDOWCLOSED);
533 }
534
535
536 if (isShowing()) {
537 closeOnFocusLoss();
538 }
539 }
540 }
541
542
543
544
545
546
547 private void handleKeyPress(final KeyEvent pEvent) {
548 switch (pEvent.getCode()) {
549 case ESCAPE:
550 handleEscapeKey();
551 break;
552 case ENTER:
553 handleEnterKey();
554 break;
555 default:
556 break;
557 }
558 }
559
560
561
562
563
564
565 private void handleScroll(final ScrollEvent pEvent) {
566
567 final double myDelta = pEvent.getDeltaY() / pEvent.getMultiplierY();
568 requestScroll((int) -myDelta);
569
570
571 pEvent.consume();
572 }
573
574
575
576
577
578
579 void handleActiveItem(final TethysUIFXScrollItem<T> pItem) {
580
581 closeChildren();
582
583
584 theActiveItem = pItem;
585 }
586
587
588
589
590
591
592 void handleActiveMenu(final TethysUIFXScrollSubMenu<T> pMenu) {
593
594 if (theActiveMenu != null
595 && theActiveMenu.getIndex() != pMenu.getIndex()) {
596 theActiveMenu.hide();
597 }
598
599
600 theActiveItem = null;
601
602
603 theActiveMenu = pMenu;
604 }
605
606 @Override
607 public void removeAllItems() {
608
609 if (isShowing()) {
610 closeMenu();
611 }
612
613
614 theMenuItems.clear();
615 theFirstIndex = 0;
616 needReBuild = true;
617
618
619 theSelectedItem = null;
620 }
621
622 @Override
623 public boolean isEmpty() {
624
625 return theMenuItems.isEmpty();
626 }
627
628
629
630
631
632
633 protected int getItemCount() {
634
635 return theMenuItems.size();
636 }
637
638
639
640
641
642
643 void scrollToIndex(final int pIndex) {
644
645 showIndex(pIndex);
646
647
648 if (theParentMenu != null) {
649 theParentMenu.scrollToMenu();
650 }
651 }
652
653
654
655
656
657
658 private void showIndex(final int pIndex) {
659
660 final int myCount = theMenuItems.size();
661 if (pIndex < 0
662 || pIndex >= myCount) {
663 return;
664 }
665
666
667 if (pIndex < theFirstIndex) {
668
669 requestScroll(pIndex - theFirstIndex);
670 return;
671 }
672
673
674 final int myLastIndex = theFirstIndex
675 + theMaxDisplayItems
676 - 1;
677 if (myLastIndex < pIndex) {
678
679 requestScroll(pIndex - myLastIndex);
680 }
681 }
682
683 @Override
684 public TethysUIScrollItem<T> addItem(final T pValue) {
685
686 return addItem(pValue, pValue.toString(), null);
687 }
688
689 @Override
690 public TethysUIScrollItem<T> addItem(final T pValue,
691 final String pName) {
692
693 return addItem(pValue, pName, null);
694 }
695
696 @Override
697 public TethysUIScrollItem<T> addItem(final T pValue,
698 final TethysUIIconId pId) {
699
700 return addItem(pValue, pValue.toString(), TethysUIFXUtils.getIconAtSize(pId, TethysUICoreScrollMenu.ICON_SIZE));
701 }
702
703 @Override
704 public TethysUIScrollItem<T> addItem(final T pValue,
705 final TethysUIIcon pGraphic) {
706
707 return addItem(pValue, pValue.toString(), pGraphic);
708 }
709
710 @Override
711 public TethysUIScrollItem<T> addNullItem(final String pName) {
712
713 return addItem(null, pName, null);
714 }
715
716 @Override
717 public TethysUIScrollItem<T> addNullItem(final String pName,
718 final TethysUIIcon pGraphic) {
719
720 return addItem(null, pName, pGraphic);
721 }
722
723 @Override
724 public TethysUIScrollItem<T> addItem(final T pValue,
725 final String pName,
726 final TethysUIIcon pGraphic) {
727
728 if (isShowing()) {
729 throw new IllegalStateException();
730 }
731
732
733 final TethysUIFXScrollItem<T> myItem = new TethysUIFXScrollItem<>(this, pValue, pName, pGraphic);
734
735
736 theMenuItems.add(myItem);
737 needReBuild = true;
738 return myItem;
739 }
740
741 @Override
742 public TethysUIScrollSubMenu<T> addSubMenu(final String pName) {
743
744 return addSubMenu(pName, null);
745 }
746
747 @Override
748 public TethysUIScrollSubMenu<T> addSubMenu(final String pName,
749 final TethysUIIcon pGraphic) {
750
751 if (isShowing()) {
752 throw new IllegalStateException();
753 }
754
755
756 final TethysUIFXScrollSubMenu<T> myMenu = new TethysUIFXScrollSubMenu<>(this, pName, pGraphic);
757
758
759 theMenuItems.add(myMenu);
760 needReBuild = true;
761 return myMenu;
762 }
763
764 @Override
765 public TethysUIScrollToggle<T> addToggleItem(final T pValue) {
766
767 return addToggleItem(pValue, pValue.toString());
768 }
769
770 @Override
771 public TethysUIScrollToggle<T> addToggleItem(final T pValue,
772 final String pName) {
773
774 if (isShowing()) {
775 throw new IllegalStateException();
776 }
777
778
779 final TethysUIFXScrollToggle<T> myItem = new TethysUIFXScrollToggle<>(this, pValue, pName);
780
781
782 theMenuItems.add(myItem);
783 needReBuild = true;
784 return myItem;
785 }
786
787
788
789
790 void closeChildren() {
791
792 if (theActiveMenu != null) {
793 theActiveMenu.hide();
794 }
795 theActiveMenu = null;
796 }
797
798
799
800
801 private void determineSize() {
802
803 if (!needReBuild) {
804 return;
805 }
806
807
808 if (!theMenuItems.isEmpty()) {
809
810 final int myCount = theMenuItems.size();
811 final int myScroll = Math.min(theMaxDisplayItems, myCount);
812
813
814 theActiveItems.clear();
815
816
817 if (myScroll == myCount) {
818
819 theContainer.setTop(null);
820 theContainer.setBottom(null);
821
822
823 for (int i = 0; i < myCount; i++) {
824
825 theActiveItems.add(theMenuItems.get(i).getBorderPane());
826 }
827
828
829 theStage.show();
830 theStage.close();
831
832
833 theMenuSize = new Dimension2D(theStage.getWidth(), theStage.getHeight());
834
835
836 } else {
837
838 theContainer.setTop(theUpItem.getLabel());
839 theContainer.setBottom(theDownItem.getLabel());
840 theUpItem.setVisible(true);
841 theDownItem.setVisible(true);
842
843
844 for (final TethysUIFXScrollElement myItem : theMenuItems) {
845
846 theActiveItems.add(myItem.getBorderPane());
847 }
848
849
850 theStage.show();
851 theStage.close();
852 final double myWidth = theStage.getWidth();
853
854
855 theActiveItems.clear();
856
857
858 if (theFirstIndex < 0) {
859 theFirstIndex = 0;
860 }
861
862
863 int myMaxIndex = theFirstIndex
864 + myScroll;
865 if (myMaxIndex > myCount) {
866
867 theFirstIndex = myCount
868 - myScroll;
869 myMaxIndex = myCount;
870 }
871
872
873 for (int i = theFirstIndex; i < myMaxIndex; i++) {
874
875 theActiveItems.add(theMenuItems.get(i).getBorderPane());
876 }
877
878
879 theStage.show();
880 theStage.close();
881 final double myHeight = theStage.getHeight();
882
883
884 theUpItem.setVisible(theFirstIndex > 0);
885 theDownItem.setVisible(myMaxIndex < myCount);
886
887
888 theMenuSize = new Dimension2D(myWidth, myHeight);
889
890
891 theStage.setMinWidth(myWidth);
892 }
893
894
895 } else {
896 theMenuSize = null;
897 }
898
899
900 needReBuild = false;
901 }
902
903
904
905
906 protected void scrollPlusOne() {
907
908 if (!needReBuild) {
909
910 final int myCount = theMenuItems.size();
911
912
913 closeChildren();
914
915
916 theUpItem.setVisible(true);
917
918
919 theActiveItems.remove(0);
920
921
922 final int myLast = theFirstIndex + theMaxDisplayItems;
923 final TethysUIFXScrollElement myItem = theMenuItems.get(myLast);
924 theActiveItems.add(myItem.getBorderPane());
925
926
927 theDownItem.setVisible(myLast + 1 < myCount);
928 }
929
930
931 theFirstIndex++;
932 }
933
934
935
936
937 protected void scrollMinusOne() {
938
939 if (!needReBuild) {
940
941 closeChildren();
942
943
944 theDownItem.setVisible(true);
945
946
947 theActiveItems.remove(theMaxDisplayItems - 1);
948
949
950 final TethysUIFXScrollElement myItem = theMenuItems.get(theFirstIndex - 1);
951 theActiveItems.add(0, myItem.getBorderPane());
952
953
954 theUpItem.setVisible(theFirstIndex > 1);
955 }
956
957
958 theFirstIndex--;
959 }
960
961
962
963
964
965
966 void requestScroll(final int pDelta) {
967
968 if (pDelta > 0) {
969
970 final int myCount = theMenuItems.size();
971 final int mySpace = myCount - theFirstIndex - theMaxDisplayItems;
972 int myScroll = Math.min(mySpace, pDelta);
973
974
975 while (myScroll-- > 0) {
976
977 scrollPlusOne();
978 }
979
980
981 } else if (theFirstIndex > 0) {
982
983 int myScroll = Math.min(theFirstIndex, -pDelta);
984
985
986 while (myScroll-- > 0) {
987
988 scrollMinusOne();
989 }
990 }
991 }
992
993
994
995
996 public abstract static class TethysUIFXScrollElement {
997
998
999
1000 private final BorderPane theBorderPane;
1001
1002
1003
1004
1005 private final Label theLabel;
1006
1007
1008
1009
1010 private final Label theIcon;
1011
1012
1013
1014
1015
1016
1017
1018 private TethysUIFXScrollElement(final String pName,
1019 final TethysUIIcon pGraphic) {
1020
1021 theBorderPane = new BorderPane();
1022
1023
1024 theLabel = new Label();
1025 theLabel.setText(pName);
1026 theLabel.setMaxWidth(Double.MAX_VALUE);
1027
1028
1029 theIcon = new Label();
1030 theIcon.setGraphic(TethysUIFXIcon.getIcon(pGraphic));
1031 theIcon.setMinWidth(TethysUIConstant.DEFAULT_ICONSIZE);
1032
1033
1034 theBorderPane.setLeft(theIcon);
1035 theBorderPane.setCenter(theLabel);
1036
1037
1038 theBorderPane.getStyleClass().add(STYLE_ITEM);
1039 }
1040
1041
1042
1043
1044
1045
1046 BorderPane getBorderPane() {
1047 return theBorderPane;
1048 }
1049
1050
1051
1052
1053
1054
1055
1056
1057 <T extends Event> void addEventFilter(final EventType<T> pEvent,
1058 final EventHandler<? super T> pFilter) {
1059 theBorderPane.addEventFilter(pEvent, pFilter);
1060 }
1061
1062
1063
1064
1065
1066
1067 public String getText() {
1068 return theLabel.getText();
1069 }
1070
1071
1072
1073
1074
1075
1076 protected void setIcon(final TethysUIFXIcon pGraphic) {
1077 theIcon.setGraphic(TethysUIFXIcon.getIcon(pGraphic));
1078 }
1079
1080
1081
1082
1083 protected void addMenuIcon() {
1084 final Label myLabel = new Label();
1085 myLabel.setGraphic(TethysUIFXArrowIcon.RIGHT.getArrow());
1086 theBorderPane.setRight(myLabel);
1087 }
1088 }
1089
1090
1091
1092
1093
1094
1095 protected static class TethysUIFXScrollItem<T>
1096 extends TethysUIFXScrollElement
1097 implements TethysUIScrollItem<T> {
1098
1099
1100
1101 private final TethysUIFXScrollMenu<T> theContext;
1102
1103
1104
1105
1106 private final int theIndex;
1107
1108
1109
1110
1111 private final T theValue;
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121 protected TethysUIFXScrollItem(final TethysUIFXScrollMenu<T> pContext,
1122 final T pValue,
1123 final String pName,
1124 final TethysUIIcon pGraphic) {
1125
1126 super(pName, pGraphic);
1127
1128
1129 theContext = pContext;
1130 theValue = pValue;
1131
1132
1133 theIndex = theContext.getItemCount();
1134
1135
1136 addEventFilter(MouseEvent.MOUSE_ENTERED, e -> theContext.handleActiveItem(this));
1137
1138
1139 addEventFilter(MouseEvent.MOUSE_CLICKED, e -> theContext.setSelectedItem(this));
1140 }
1141
1142 @Override
1143 public T getValue() {
1144 return theValue;
1145 }
1146
1147 @Override
1148 public void scrollToItem() {
1149 theContext.scrollToIndex(theIndex);
1150 }
1151 }
1152
1153
1154
1155
1156
1157
1158 private static final class TethysUIFXScrollToggle<T>
1159 extends TethysUIFXScrollItem<T>
1160 implements TethysUIScrollToggle<T> {
1161
1162
1163
1164 private boolean isSelected;
1165
1166
1167
1168
1169
1170
1171
1172
1173 TethysUIFXScrollToggle(final TethysUIFXScrollMenu<T> pContext,
1174 final T pValue,
1175 final String pName) {
1176
1177 super(pContext, pValue, pName, null);
1178 }
1179
1180 @Override
1181 public boolean isSelected() {
1182 return isSelected;
1183 }
1184
1185 @Override
1186 public void setSelected(final boolean pSelected) {
1187 isSelected = pSelected;
1188 setIcon(isSelected
1189 ? TethysUIFXUtils.getIconAtSize(TethysUIScrollIcon.CHECKMARK, TethysUIConstant.DEFAULT_ICONSIZE)
1190 : null);
1191 }
1192
1193 @Override
1194 public void toggleSelected() {
1195 setSelected(!isSelected);
1196 }
1197 }
1198
1199
1200
1201
1202
1203
1204 public static final class TethysUIFXScrollSubMenu<T>
1205 extends TethysUIFXScrollElement
1206 implements TethysUIScrollSubMenu<T> {
1207
1208
1209
1210 private final TethysUIFXScrollMenu<T> theContext;
1211
1212
1213
1214
1215 private final int theIndex;
1216
1217
1218
1219
1220 private final TethysUIFXScrollMenu<T> theSubMenu;
1221
1222
1223
1224
1225
1226
1227
1228
1229 TethysUIFXScrollSubMenu(final TethysUIFXScrollMenu<T> pContext,
1230 final String pName,
1231 final TethysUIIcon pGraphic) {
1232
1233 super(pName, pGraphic);
1234
1235
1236 theContext = pContext;
1237
1238
1239 theSubMenu = new TethysUIFXScrollMenu<>(this);
1240
1241
1242 theIndex = theContext.getItemCount();
1243
1244
1245 addMenuIcon();
1246
1247
1248 addEventFilter(MouseEvent.MOUSE_ENTERED, e -> {
1249 theContext.handleActiveMenu(this);
1250 theSubMenu.showMenuAtPosition(getBorderPane(), Side.RIGHT);
1251 });
1252 }
1253
1254
1255
1256
1257
1258
1259 TethysUIFXScrollMenu<T> getContext() {
1260 return theContext;
1261 }
1262
1263 @Override
1264 public TethysUIFXScrollMenu<T> getSubMenu() {
1265 return theSubMenu;
1266 }
1267
1268
1269
1270
1271
1272
1273 int getIndex() {
1274 return theIndex;
1275 }
1276
1277
1278
1279
1280 void hide() {
1281 theSubMenu.closeMenu();
1282 }
1283
1284
1285
1286
1287
1288
1289
1290 private void setMaxDisplayItems(final int pMaxDisplayItems) {
1291
1292 theSubMenu.setMaxDisplayItems(pMaxDisplayItems);
1293 }
1294
1295
1296
1297
1298 void scrollToMenu() {
1299 theContext.scrollToIndex(theIndex);
1300 }
1301 }
1302
1303
1304
1305
1306 private final class ScrollControl {
1307
1308
1309
1310 private final Label theLabel;
1311
1312
1313
1314
1315 private final int theIncrement;
1316
1317
1318
1319
1320 private final Timeline theKickStartTimer;
1321
1322
1323
1324
1325 private final Timeline theRepeatTimer;
1326
1327
1328
1329
1330
1331
1332
1333 ScrollControl(final Polygon pIcon,
1334 final int pIncrement) {
1335
1336 theLabel = new Label();
1337
1338
1339 theLabel.setGraphic(pIcon);
1340 theLabel.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
1341 theLabel.setAlignment(Pos.CENTER);
1342 theLabel.setMaxWidth(Double.MAX_VALUE);
1343 theLabel.getStyleClass().add(STYLE_ITEM);
1344
1345
1346 theIncrement = pIncrement;
1347
1348
1349 theLabel.addEventFilter(MouseEvent.MOUSE_CLICKED, e -> processScroll());
1350
1351
1352 theLabel.addEventHandler(MouseEvent.MOUSE_ENTERED, e -> processMouseEnter());
1353
1354
1355 theLabel.addEventHandler(MouseEvent.MOUSE_EXITED, e -> processMouseExit());
1356
1357
1358 theKickStartTimer = new Timeline(new KeyFrame(Duration.millis(TethysUICoreScrollMenu.INITIAL_SCROLLDELAY),
1359 e -> processScroll()));
1360 theRepeatTimer = new Timeline(new KeyFrame(Duration.millis(TethysUICoreScrollMenu.REPEAT_SCROLLDELAY),
1361 e -> processScroll()));
1362 }
1363
1364
1365
1366
1367
1368
1369 Label getLabel() {
1370 return theLabel;
1371 }
1372
1373
1374
1375
1376
1377
1378 void setVisible(final boolean pVisible) {
1379 theLabel.setVisible(pVisible);
1380 }
1381
1382
1383
1384
1385 private void processScroll() {
1386
1387 requestScroll(theIncrement);
1388 theRepeatTimer.play();
1389 }
1390
1391
1392
1393
1394 private void processMouseEnter() {
1395
1396 closeChildren();
1397
1398
1399 clearActiveItem();
1400
1401
1402 theKickStartTimer.play();
1403 }
1404
1405
1406
1407
1408 private void processMouseExit() {
1409
1410 theKickStartTimer.stop();
1411 theRepeatTimer.stop();
1412 }
1413 }
1414 }