3535import com .techsenger .tabpanepro .core .skin .TabPaneProSkin .TabHeaderArea ;
3636import com .techsenger .toolkit .core .Pair ;
3737import java .util .ArrayList ;
38+ import java .util .EnumSet ;
3839import java .util .HashSet ;
3940import java .util .Iterator ;
4041import java .util .List ;
42+ import java .util .Set ;
4143import java .util .UUID ;
4244import java .util .function .BiConsumer ;
4345import java .util .function .Consumer ;
46+ import java .util .function .Function ;
4447import java .util .stream .Collectors ;
4548import javafx .beans .property .ObjectProperty ;
4649import javafx .beans .property .ReadOnlyObjectProperty ;
@@ -1615,6 +1618,45 @@ private void validate(DropPosition info) {
16151618
16161619 private static final class Transformer {
16171620
1621+ /**
1622+ * Computes the set of donor choices actually available for inserting a TabDock at {@code side} of an anchor,
1623+ * given whether the insertion requires wrapping the anchor into a new SplitPane.
1624+ * <p>
1625+ * When wrapping is required, the wrapped anchor is the sole possible donor — the returned set always has
1626+ * exactly one element, labeled {@link SpaceDonor#PREVIOUS_SIBLING} or {@link SpaceDonor#NEXT_SIBLING} depending
1627+ * on which side of the new TabDock the anchor ends up on after wrapping.
1628+ * <p>
1629+ * When inserting directly as a sibling (no wrap), the set reflects which neighbors actually exist at the
1630+ * insertion point: a single neighbor at the edge of the SplitPane yields a single-element set;
1631+ * {@link SpaceDonor#NEAREST_SIBLINGS} is offered only when both neighbors exist, and
1632+ * {@link SpaceDonor#ALL_SIBLINGS} only when the target SplitPane has more than two items — with exactly two
1633+ * items it would be identical to {@code NEAREST_SIBLINGS}.
1634+ *
1635+ * @param wraps whether this insertion requires wrapping the anchor
1636+ * @param side the side of the anchor the new TabDock will occupy
1637+ * @param newChildIndex the live index the new TabDock will be inserted at within the target SplitPane
1638+ * @param oldItemCount the number of items in the target SplitPane before insertion
1639+ * @return the set of donor choices available for this insertion; never empty
1640+ */
1641+ private static Set <SpaceDonor > resolveDonorOptions (boolean wraps , Side side , int newChildIndex ,
1642+ int oldItemCount ) {
1643+ if (wraps ) {
1644+ boolean anchorIsPrevious = side == RIGHT || side == BOTTOM ;
1645+ return Set .of (anchorIsPrevious ? SpaceDonor .PREVIOUS_SIBLING : SpaceDonor .NEXT_SIBLING );
1646+ }
1647+ boolean hasPrevious = newChildIndex > 0 ;
1648+ boolean hasNext = newChildIndex < oldItemCount ;
1649+ if (hasPrevious && hasNext ) {
1650+ var opts = EnumSet .of (SpaceDonor .PREVIOUS_SIBLING , SpaceDonor .NEXT_SIBLING ,
1651+ SpaceDonor .NEAREST_SIBLINGS );
1652+ if (oldItemCount > 2 ) {
1653+ opts .add (SpaceDonor .ALL_SIBLINGS );
1654+ }
1655+ return opts ;
1656+ }
1657+ return Set .of (hasPrevious ? SpaceDonor .PREVIOUS_SIBLING : SpaceDonor .NEXT_SIBLING );
1658+ }
1659+
16181660 private final DockHostFxView <?> dockHost ;
16191661
16201662 private final DockHostFxView <?>.Composer composer ;
@@ -1871,34 +1913,52 @@ private void addTabDock(Side side, ContainerPosition anchorInfo, ContainerPositi
18711913 * {@code RIGHT} side against a {@code HORIZONTAL} parent), the new TabDock is inserted as a direct sibling next
18721914 * to the container within that same parent. Otherwise, the container is first
18731915 * {@linkplain #wrap(AbstractContainer, int) wrapped} in a new group with the orientation {@code side} implies,
1874- * and the new TabDock is inserted into that new group instead — mirroring the wrap-or-insert decision the
1875- * drag-and-drop cases 1–12 make from mouse geometry, but driven here by an explicit side rather than cursor
1876- * position.
1916+ * and the new TabDock is inserted into that new group instead.
1917+ * <p>
1918+ * The space for the new TabDock (and, when this SplitPane has no main area, its own resize delta) is taken from
1919+ * one or more existing siblings, chosen via {@code donorResolver}. The resolver is invoked only when the
1920+ * insertion point actually offers more than one possible donor — see {@link #resolveDonorOptions}. When
1921+ * wrapping is required, or the insertion point sits at the edge of its SplitPane with a single neighbor, there
1922+ * is no real choice and {@code donorResolver} is never called.
18771923 *
18781924 * @param anchorContainer the container to add the new TabDock next to; must currently be live
18791925 * @param side the side of {@code anchorContainer} the new TabDock should occupy
18801926 * @param dock the TabDock to add
18811927 * @param size the desired size (width for {@code LEFT}/{@code RIGHT}, height for {@code TOP}/{@code BOTTOM}) of
18821928 * the new TabDock
1929+ * @param donorResolver called with the set of available donor choices when more than one exists; must return
1930+ * one of the offered values
1931+ * @throws IllegalArgumentException if {@code donorResolver} returns a value not present in the set it was
1932+ * offered
18831933 */
1884- private void addTabDock (AbstractContainer anchorContainer , Side side , TabDockFxView <?> dock , double size ) {
1934+ private void addTabDock (AbstractContainer anchorContainer , Side side , TabDockFxView <?> dock , double size ,
1935+ Function <Set <SpaceDonor >, SpaceDonor > donorResolver ) {
18851936 dock .getComposer ().setDockHost (dockHost );
18861937 var neededOrientation = side .isVertical () ? Orientation .HORIZONTAL : Orientation .VERTICAL ;
18871938 var parentContainer = anchorContainer .getLogicalParent ();
18881939 SplitPaneContainer targetParent ;
18891940 int index ;
1941+ boolean wraps ;
18901942 if (parentContainer != null && parentContainer .getSplitPane ().getOrientation () == neededOrientation ) {
18911943 targetParent = parentContainer ;
18921944 var anchorIndex = targetParent .getSplitPane ().getItems ().indexOf (anchorContainer );
18931945 index = (side == LEFT || side == TOP ) ? anchorIndex : anchorIndex + 1 ;
1946+ wraps = false ;
18941947 } else {
18951948 var anchorIndex = anchorContainer .resolvePosition ().getIndex ();
18961949 targetParent = wrap (anchorContainer , anchorIndex );
1897- // let the freshly created group pick up the anchor's current size before we measure it below
18981950 refresh ();
18991951 index = (side == LEFT || side == TOP ) ? 0 : 1 ;
1952+ wraps = true ;
19001953 }
19011954 var splitPane = targetParent .getSplitPane ();
1955+ var oldItemCount = splitPane .getItems ().size ();
1956+ var options = resolveDonorOptions (wraps , side , index , oldItemCount );
1957+ var donor = options .size () == 1 ? options .iterator ().next () : donorResolver .apply (options );
1958+ if (!options .contains (donor )) {
1959+ throw new IllegalArgumentException (
1960+ "donorResolver returned " + donor + ", but only " + options + " were offered" );
1961+ }
19021962 var oldSplitPaneSize = splitPane .getOrientation () == Orientation .HORIZONTAL
19031963 ? splitPane .getWidth () : splitPane .getHeight ();
19041964 var dividerSize = splitPane .computeDividerSize ();
@@ -1912,13 +1972,14 @@ private void addTabDock(AbstractContainer anchorContainer, Side side, TabDockFxV
19121972 var mainIndex = indexOfMain (targetParent );
19131973 if (mainIndex >= 0 ) {
19141974 splitPane .updateDividersOnAddWithMain (oldSplitPaneSize , oldPositions , dividerSize , mainIndex , index ,
1915- size );
1975+ size , donor );
19161976 } else {
1917- splitPane .updateDividersOnAddWithoutMain (oldSplitPaneSize , oldPositions , dividerSize , index , size );
1977+ splitPane .updateDividersOnAddWithoutMain (oldSplitPaneSize , oldPositions , dividerSize , index ,
1978+ size , donor );
19181979 }
19191980 if (logger .isDebugEnabled ()) {
1920- logger .debug ("{} Added {} at {} of {}" , dockHost .getDescriptor ().getLogPrefix (),
1921- dock .getDescriptor ().getFullName (), side , anchorContainer .getChildFullName ());
1981+ logger .debug ("{} Added {} at {} of {} with donor {} " , dockHost .getDescriptor ().getLogPrefix (),
1982+ dock .getDescriptor ().getFullName (), side , anchorContainer .getChildFullName (), donor );
19221983 dockHost .printTreeDebugInfo ();
19231984 }
19241985 }
@@ -1960,10 +2021,12 @@ private void addTabDock(SplitPaneContainer parentContainer, TabDockFxView<?> doc
19602021 }
19612022 var mainIndex = indexOfMain (parentContainer );
19622023 if (mainIndex >= 0 ) {
1963- splitPane .updateDividersOnAddWithMain (oldSplitPaneSize , oldPositions , dividerSize ,
1964- mainIndex , index , size );
2024+ // no donor choice exposed on this call path; matches prior behavior
2025+ splitPane .updateDividersOnAddWithMain (oldSplitPaneSize , oldPositions , dividerSize , mainIndex ,
2026+ index , size , SpaceDonor .NEAREST_SIBLINGS );
19652027 } else {
1966- splitPane .updateDividersOnAddWithoutMain (oldSplitPaneSize , oldPositions , dividerSize , index , size );
2028+ splitPane .updateDividersOnAddWithoutMain (oldSplitPaneSize , oldPositions , dividerSize , index , size ,
2029+ SpaceDonor .NEAREST_SIBLINGS );
19672030 }
19682031 if (logger .isDebugEnabled ()) {
19692032 logger .debug ("{} Added {} into {}" , dockHost .getDescriptor ().getLogPrefix (),
@@ -2069,9 +2132,10 @@ private void restoreTabDock(TabDockContainer tabDockContainer) {
20692132 var mainIndex = indexOfMain (parentContainer );
20702133 if (mainIndex >= 0 ) {
20712134 splitPane .updateDividersOnAddWithMain (oldSplitPaneSize , oldPositions , dividerSize , mainIndex , index ,
2072- dockSize );
2135+ dockSize , SpaceDonor . NEAREST_SIBLINGS );
20732136 } else {
2074- splitPane .updateDividersOnAddWithoutMain (oldSplitPaneSize , oldPositions , dividerSize , index , dockSize );
2137+ splitPane .updateDividersOnAddWithoutMain (oldSplitPaneSize , oldPositions , dividerSize , index , dockSize ,
2138+ SpaceDonor .NEAREST_SIBLINGS );
20752139 }
20762140 if (logger .isDebugEnabled ()) {
20772141 logger .debug ("{} Restored {} into {}" , dockHost .getDescriptor ().getLogPrefix (),
@@ -2538,18 +2602,28 @@ public void addTabDock(TabDockFxView<?> dock, Side side, double size) {
25382602 * {@code node} must be a live node — obtained from {@link #getModelNode(AreaFxView)}, or reached from it via
25392603 * {@link ModelNode#getParent()} or {@link GroupNode#getChildren()} — since it must correspond to an actual
25402604 * current position in this docking layout. A node built via {@link ModelNodeBuilder} does not qualify.
2605+ * <p>
2606+ * The space for the new TabDock is taken from one or more of its existing siblings. When the insertion point
2607+ * offers more than one possible donor, {@code donorResolver} is called with the available choices and must
2608+ * return one of them — see {@link SpaceDonor}. When there is only one possible donor (insertion requires
2609+ * wrapping the anchor, or the anchor sits at the edge of its SplitPane), {@code donorResolver} is not called
2610+ * at all.
25412611 *
25422612 * @param tabDock the TabDock to add
25432613 * @param node the live node to add the new TabDock next to
25442614 * @param side the side of {@code node} the new TabDock should occupy
25452615 * @param size the desired size (width for {@code LEFT}/{@code RIGHT}, height for {@code TOP}/{@code BOTTOM}) of
25462616 * the new TabDock
2547- * @throws IllegalArgumentException if {@code node} is not a live node obtained from this docking layout
2617+ * @param donorResolver called with the set of available donor choices when more than one exists; must return
2618+ * one of the offered values
2619+ * @throws IllegalArgumentException if {@code node} is not a live node obtained from this docking layout, or if
2620+ * {@code donorResolver} returns a value it was not offered
25482621 */
2549- public void addTabDock (TabDockFxView <?> tabDock , ModelNode node , Side side , double size ) {
2622+ public void addTabDock (TabDockFxView <?> tabDock , ModelNode node , Side side , double size ,
2623+ Function <Set <SpaceDonor >, SpaceDonor > donorResolver ) {
25502624 getModifiableChildren ().add (tabDock );
25512625 var anchorContainer = ContainerUtils .resolveContainer (node );
2552- view .transformer .addTabDock (anchorContainer , side , tabDock , size );
2626+ view .transformer .addTabDock (anchorContainer , side , tabDock , size , donorResolver );
25532627 }
25542628
25552629 public void removeTabDock (TabDockFxView <?> dock ) {
0 commit comments