Skip to content

Commit 51141ab

Browse files
committed
Add SpaceDonor for docking layout
1 parent fe403f0 commit 51141ab

3 files changed

Lines changed: 293 additions & 201 deletions

File tree

shellfx-layout/src/main/java/com/techsenger/shellfx/layout/dockhost/DockHostFxView.java

Lines changed: 91 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,15 @@
3535
import com.techsenger.tabpanepro.core.skin.TabPaneProSkin.TabHeaderArea;
3636
import com.techsenger.toolkit.core.Pair;
3737
import java.util.ArrayList;
38+
import java.util.EnumSet;
3839
import java.util.HashSet;
3940
import java.util.Iterator;
4041
import java.util.List;
42+
import java.util.Set;
4143
import java.util.UUID;
4244
import java.util.function.BiConsumer;
4345
import java.util.function.Consumer;
46+
import java.util.function.Function;
4447
import java.util.stream.Collectors;
4548
import javafx.beans.property.ObjectProperty;
4649
import 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

Comments
 (0)