@@ -102,6 +102,8 @@ protected void build()
102102
103103 panelBuilder .registerTypeBuilder ("UNASSIGNED_CHALLENGES" , this ::createFreeChallengesButton );
104104
105+ panelBuilder .registerTypeBuilder ("TOGGLE_UNDEPLOYED" , this ::createToggleUndeployedButton );
106+
105107 panelBuilder .registerTypeBuilder ("NEXT" , this ::createNextButton );
106108 panelBuilder .registerTypeBuilder ("PREVIOUS" , this ::createPreviousButton );
107109
@@ -120,8 +122,9 @@ private void updateFreeChallengeList()
120122 this .manager .isChallengeComplete (this .user , this .world , challenge ) &&
121123 (globalRemoveCompleted || challenge .isRemoveWhenCompleted ()));
122124
123- // Remove all undeployed challenges if VisibilityMode is set to Hidden.
124- if (this .addon .getChallengesSettings ().getVisibilityMode ().equals (SettingsUtils .VisibilityMode .HIDDEN ))
125+ // Remove all undeployed challenges if they should be hidden (HIDDEN mode, or
126+ // TOGGLEABLE mode with the player currently hiding them).
127+ if (this .hideUndeployedChallenges ())
125128 {
126129 this .freeChallengeList .removeIf (challenge -> !challenge .isDeployed ());
127130 }
@@ -134,6 +137,21 @@ private void updateFreeChallengeList()
134137 }
135138
136139
140+ /**
141+ * Whether undeployed challenges should be hidden from the viewing player right now.
142+ * True when the visibility mode is HIDDEN, or when it is TOGGLEABLE and the player has
143+ * chosen to hide them.
144+ *
145+ * @return {@code true} if undeployed challenges must be filtered out of the GUI.
146+ */
147+ private boolean hideUndeployedChallenges ()
148+ {
149+ SettingsUtils .VisibilityMode mode = this .addon .getChallengesSettings ().getVisibilityMode ();
150+ return mode == SettingsUtils .VisibilityMode .HIDDEN ||
151+ (mode == SettingsUtils .VisibilityMode .TOGGLEABLE && !this .showUndeployed );
152+ }
153+
154+
137155 /**
138156 * @return whether the viewing user currently has a team (island membership > 1).
139157 */
@@ -161,8 +179,9 @@ private void updateChallengeList()
161179 this .manager .isChallengeComplete (this .user , this .world , challenge ) &&
162180 (globalRemoveCompleted || challenge .isRemoveWhenCompleted ()));
163181
164- // Remove all undeployed challenges if VisibilityMode is set to Hidden.
165- if (this .addon .getChallengesSettings ().getVisibilityMode ().equals (SettingsUtils .VisibilityMode .HIDDEN ))
182+ // Remove all undeployed challenges if they should be hidden (HIDDEN mode, or
183+ // TOGGLEABLE mode with the player currently hiding them).
184+ if (this .hideUndeployedChallenges ())
166185 {
167186 this .challengeList .removeIf (challenge -> !challenge .isDeployed ());
168187 }
@@ -665,6 +684,78 @@ private PanelItem createFreeChallengesButton(@NonNull ItemTemplateRecord templat
665684 }
666685
667686
687+ /**
688+ * Creates the button that lets a player show or hide undeployed challenges when the
689+ * visibility mode is TOGGLEABLE. In any other visibility mode there is nothing to toggle,
690+ * so no button is shown (the slot falls back to the panel background).
691+ *
692+ * @param template the button template.
693+ * @param slot the slot the button occupies.
694+ * @return the toggle button, or {@code null} when the mode is not TOGGLEABLE.
695+ */
696+ @ Nullable
697+ private PanelItem createToggleUndeployedButton (@ NonNull ItemTemplateRecord template , TemplatedPanel .ItemSlot slot )
698+ {
699+ // Only meaningful in TOGGLEABLE mode. In VISIBLE / HIDDEN there is nothing to toggle.
700+ if (this .addon .getChallengesSettings ().getVisibilityMode () != SettingsUtils .VisibilityMode .TOGGLEABLE )
701+ {
702+ return null ;
703+ }
704+
705+ PanelItemBuilder builder = new PanelItemBuilder ();
706+
707+ if (template .icon () != null )
708+ {
709+ builder .icon (template .icon ().clone ());
710+ }
711+
712+ if (template .title () != null )
713+ {
714+ builder .name (this .user .getTranslation (this .world , template .title ()));
715+ }
716+
717+ if (template .description () != null )
718+ {
719+ builder .description (this .user .getTranslation (this .world , template .description ()));
720+ }
721+
722+ // Show whether undeployed challenges are currently shown or hidden for this player.
723+ builder .description (this .user .getTranslation (this .world ,
724+ this .showUndeployed ?
725+ Constants .BUTTON + "toggle-undeployed.shown" :
726+ Constants .BUTTON + "toggle-undeployed.hidden" ));
727+
728+ // Add ClickHandler: flip the preference, reset paging, and rebuild.
729+ builder .clickHandler ((panel , user , clickType , i ) ->
730+ {
731+ this .showUndeployed = !this .showUndeployed ;
732+ // The visible challenge count changes, so the current page may be out of range.
733+ this .challengeIndex = 0 ;
734+ this .build ();
735+
736+ // Always return true.
737+ return true ;
738+ });
739+
740+ // Collect tooltips.
741+ List <String > tooltips = template .actions ().stream ().
742+ filter (action -> action .tooltip () != null ).
743+ map (action -> this .user .getTranslation (this .world , action .tooltip ())).
744+ filter (text -> !text .isBlank ()).
745+ collect (Collectors .toCollection (() -> new ArrayList <>(template .actions ().size ())));
746+
747+ // Add tooltips.
748+ if (!tooltips .isEmpty ())
749+ {
750+ // Empty line and tooltips.
751+ builder .description ("" );
752+ builder .description (tooltips );
753+ }
754+
755+ return builder .build ();
756+ }
757+
758+
668759 @ Nullable
669760 private PanelItem createNextButton (@ NonNull ItemTemplateRecord template , TemplatedPanel .ItemSlot slot )
670761 {
@@ -901,4 +992,12 @@ else if (Constants.LEVEL_BUILDER_KEY.equals(target))
901992 * This indicates last selected level.
902993 */
903994 private LevelStatus lastSelectedLevel ;
995+
996+ /**
997+ * Per-session preference for the TOGGLEABLE visibility mode: whether this player
998+ * currently wants to see undeployed challenges. Defaults to {@code true} (shown), so
999+ * TOGGLEABLE behaves like VISIBLE until the player hides them with the toggle button.
1000+ * Only consulted when the visibility mode is TOGGLEABLE.
1001+ */
1002+ private boolean showUndeployed = true ;
9041003}
0 commit comments