@@ -646,6 +646,131 @@ class PopulatorContext<T> {
646646 @ FunctionalInterface
647647 public interface ViewStrategy {
648648 /**
649+ * Parses an array of strings to create a {@link ViewStrategy} for rendering elements on a canvas.
650+ *
651+ * <p>The provided array of strings represents a grid layout, where each character indicates the
652+ * presence or absence of an element at a specific position.</p>
653+ *
654+ * <ul>
655+ * <li>Use any letter (e.g. {@code 'x'}) to indicate the presence of an element.</li>
656+ * <li>Use {@code '-'} to indicate the absence of an element.</li>
657+ * <li>Grouping is supported: multiple identical letters may represent a single element. This is
658+ * useful for creating components larger than one slot (e.g., a button occupying 4 cells).</li>
659+ * </ul>
660+ *
661+ * <p><b>Examples:</b></p>
662+ *
663+ * <p>Example 1: A simple layout with 10 individual slots (no grouping).</p>
664+ * <pre>{@code
665+ * String[] rows = {
666+ * "---------",
667+ * "--xxxxx--",
668+ * "--xxxxx--",
669+ * "---------",
670+ * "---------",
671+ * "---------"
672+ * };
673+ *
674+ * ViewStrategy viewStrategy = ViewStrategy.parseLayout(false, rows);
675+ * canvas.viewStrategy(viewStrategy);
676+ * }</pre>
677+ *
678+ * <p>Example 2: A grouped layout with two buttons, each occupying four slots.</p>
679+ * <pre>{@code
680+ * String[] rows = {
681+ * "---------",
682+ * "--aa-bb--",
683+ * "--aa-bb--",
684+ * "---------",
685+ * "---------",
686+ * "---------"
687+ * };
688+ *
689+ * ViewStrategy viewStrategy = ViewStrategy.parseLayout(true, rows);
690+ * canvas.viewStrategy(viewStrategy);
691+ * }</pre>
692+ *
693+ * @param groupLetters whether identical letters should be treated as grouped elements
694+ * or as individual slots
695+ * @param rows an array of strings representing the grid layout
696+ * @return a {@link ViewStrategy} defining the rendering behavior based on the parsed layout
697+ * @throws IllegalArgumentException if any row does not have a length of exactly 9
698+ */
699+ static ViewStrategy parseLayout (boolean groupLetters , String ... rows ) {
700+ // Precompute the button areas once
701+ final List <Button .DetectableArea > areas = new ArrayList <>();
702+
703+ if (groupLetters ) {
704+ // letter → bounding box
705+ Map <Character , Bounds > groups = new LinkedHashMap <>();
706+
707+ // Iterate through each row in the grid
708+ for (int y = 0 ; y < rows .length ; y ++) {
709+ String row = rows [y ];
710+
711+ // Check if the length of the row is 9, throw an exception if not
712+ if (row .length () != 9 ) {
713+ throw new IllegalArgumentException ("Row length must be 9" );
714+ }
715+
716+ // Iterate through each character in the row
717+ for (int x = 0 ; x < 9 ; x ++) {
718+ char c = row .charAt (x );
719+
720+ // Skip empty slots
721+ if (c == '-' ) {
722+ continue ;
723+ }
724+
725+ // Expand bounding box
726+ int finalX = x ;
727+ int finalY = y ;
728+ groups .computeIfAbsent (c , k -> new Bounds (finalX , finalY ))
729+ .expand (x , y );
730+ }
731+ }
732+
733+ // Convert bounding boxes into detectable areas
734+ for (Bounds b : groups .values ()) {
735+ areas .add (
736+ Button .DetectableArea .of (
737+ new org .bukkit .util .Vector (b .minX , 0 , b .minY ),
738+ new org .bukkit .util .Vector (b .maxX , 0 , b .maxY )
739+ )
740+ );
741+ }
742+
743+ } else {
744+ // Treat each non '-' character as an individual button slot
745+ for (int y = 0 ; y < rows .length ; y ++) {
746+ String row = rows [y ];
747+
748+ if (row .length () != 9 ) {
749+ throw new IllegalArgumentException ("Row length must be 9" );
750+ }
751+
752+ for (int x = 0 ; x < 9 ; x ++) {
753+ if (row .charAt (x ) != '-' ) {
754+ areas .add (Button .DetectableArea .of (x , y ));
755+ }
756+ }
757+ }
758+ }
759+
760+ // Define a ViewStrategy using a lambda expression
761+ return (ctx ) -> {
762+ // Set the button area based on the counter
763+ if (ctx .counter < areas .size ()) {
764+ ctx .button .detectableArea (areas .get (ctx .counter ));
765+ }
766+
767+ // Return the total number of elements per page
768+ return areas .size ();
769+ };
770+ }
771+
772+ /**
773+ * This method is deprecated and will be removed in future versions. Use {@link #parseLayout(boolean, String...)} instead.
649774 * Parses an array of strings to create a {@link ViewStrategy} for rendering elements on a canvas.
650775 * <p>
651776 * The provided array of strings represents a grid, where each character in the strings
@@ -672,6 +797,7 @@ public interface ViewStrategy {
672797 * @return A {@link ViewStrategy} that defines the rendering behavior based on the parsed grid.
673798 * @throws IllegalArgumentException If the length of any row is not equal to 9.
674799 */
800+ @ Deprecated // Use parseLayout instead
675801 static ViewStrategy parse (String [] rows ) {
676802 // Define a ViewStrategy using a lambda expression
677803 return (ctx ) -> {
@@ -724,9 +850,28 @@ static ViewStrategy parse(String[] rows) {
724850 @ Accessors (fluent = true , chain = true )
725851 class ViewStrategyContext {
726852 private final int counter ;
727- private final @ NotNull Canvas canvas ;
853+ private final /* @NotNull*/ Canvas canvas ;
728854 private final @ NotNull Button button ;
729855 }
856+
857+ /**
858+ * A helper class to calculate bounds.
859+ */
860+ final class Bounds {
861+ int minX , minY , maxX , maxY ;
862+
863+ Bounds (int x , int y ) {
864+ this .minX = this .maxX = x ;
865+ this .minY = this .maxY = y ;
866+ }
867+
868+ void expand (int x , int y ) {
869+ minX = Math .min (minX , x );
870+ minY = Math .min (minY , y );
871+ maxX = Math .max (maxX , x );
872+ maxY = Math .max (maxY , y );
873+ }
874+ }
730875 }
731876 }
732877
0 commit comments