diff --git a/src/main/java/de/extio/game_engine/renderer/container/Container.java b/src/main/java/de/extio/game_engine/renderer/container/Container.java index 754b39a..39ed55b 100644 --- a/src/main/java/de/extio/game_engine/renderer/container/Container.java +++ b/src/main/java/de/extio/game_engine/renderer/container/Container.java @@ -2,7 +2,11 @@ package de.extio.game_engine.renderer.container; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; +import java.util.IdentityHashMap; import java.util.List; +import java.util.Map; +import java.util.Set; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.annotation.Scope; @@ -60,6 +64,10 @@ public class Container extends AbstractClientModule implements InitializingBean protected short zIndex; + protected Container parent; + + protected Set children = new HashSet<>(); + public Container(final ModuleService moduleService, final RenderingBoPool renderingBoPool, final RendererControl rendererControl, final EventService eventService, final RendererWorkingSet rendererWorkingSet) { this.moduleService = moduleService; this.renderingBoPool = renderingBoPool; @@ -284,22 +292,136 @@ public class Container extends AbstractClientModule implements InitializingBean } synchronized (DISPLAYED_CONTAINERS) { - for (final var container : DISPLAYED_CONTAINERS) { - if (container == this) { - continue; - } - if (container.getzIndex() > this.getzIndex()) { - final var newZIndex = (short) (container.getzIndex() - 1); - if (container.getzIndex() != newZIndex) { - container.setzIndex(newZIndex); - container.draw(); - } + final List path = this.getPathToRoot(this); + final Container root = path.get(0); + final Set pathSet = new HashSet<>(path); + + final Set displayed = new HashSet<>(DISPLAYED_CONTAINERS); + + final List others = new ArrayList<>(); + for (final Container c : DISPLAYED_CONTAINERS) { + if (!isDescendantOrSelf(root, c)) { + others.add(c); } } - final var newZIndex = (short) (DISPLAYED_CONTAINERS.size() - 1); - if (this.getzIndex() != newZIndex) { - this.setzIndex(newZIndex); - this.draw(); + others.sort((c1, c2) -> Short.compare(c1.getzIndex(), c2.getzIndex())); + + final Map subtreeMinDisplayedZIndex = new IdentityHashMap<>(); + final Map subtreeHasDisplayed = new IdentityHashMap<>(); + this.computeSubtreeInfo(root, displayed, subtreeMinDisplayedZIndex, subtreeHasDisplayed, new HashSet<>()); + + final List hierarchyDisplayed = new ArrayList<>(); + if (Boolean.TRUE.equals(subtreeHasDisplayed.get(root))) { + this.flattenDisplayedSubtree(root, displayed, pathSet, subtreeMinDisplayedZIndex, subtreeHasDisplayed, hierarchyDisplayed, new HashSet<>()); + } + + short z = 0; + for (final Container c : others) { + if (c.getzIndex() != z) { + c.setzIndex(z); + c.draw(); + } + z++; + } + for (final Container c : hierarchyDisplayed) { + if (c.getzIndex() != z) { + c.setzIndex(z); + c.draw(); + } + z++; + } + } + } + + private List getPathToRoot(final Container container) { + final List path = new ArrayList<>(); + Container current = container; + while (current != null) { + path.add(0, current); + current = current.parent; + } + return path; + } + + private boolean isDescendantOrSelf(final Container root, final Container c) { + Container curr = c; + while (curr != null) { + if (curr == root) { + return true; + } + curr = curr.parent; + } + return false; + } + + private void computeSubtreeInfo(final Container node, final Set displayedNodes, final Map subtreeMinDisplayedZIndex, final Map subtreeHasDisplayed, final Set visited) { + if (!visited.add(node)) { + return; + } + + short minZ = displayedNodes.contains(node) ? node.getzIndex() : Short.MAX_VALUE; + boolean hasDisplayed = displayedNodes.contains(node); + + for (final Container child : node.children) { + this.computeSubtreeInfo(child, displayedNodes, subtreeMinDisplayedZIndex, subtreeHasDisplayed, visited); + if (Boolean.TRUE.equals(subtreeHasDisplayed.get(child))) { + hasDisplayed = true; + final Short childMin = subtreeMinDisplayedZIndex.get(child); + if (childMin != null && childMin < minZ) { + minZ = childMin; + } + } + } + + subtreeHasDisplayed.put(node, hasDisplayed); + if (hasDisplayed) { + subtreeMinDisplayedZIndex.put(node, minZ); + } + } + + private void flattenDisplayedSubtree(final Container node, final Set displayedNodes, final Set pathSet, final Map subtreeMinDisplayedZIndex, final Map subtreeHasDisplayed, final List result, final Set visited) { + if (!visited.add(node)) { + return; + } + if (!Boolean.TRUE.equals(subtreeHasDisplayed.get(node))) { + return; + } + + if (displayedNodes.contains(node)) { + result.add(node); + } + + final List childrenSorted = new ArrayList<>(node.children); + childrenSorted.sort((c1, c2) -> { + final boolean c1OnPath = pathSet.contains(c1); + final boolean c2OnPath = pathSet.contains(c2); + if (c1OnPath != c2OnPath) { + return c1OnPath ? 1 : -1; + } + final short c1Key = subtreeMinDisplayedZIndex.getOrDefault(c1, Short.MAX_VALUE); + final short c2Key = subtreeMinDisplayedZIndex.getOrDefault(c2, Short.MAX_VALUE); + final int cmp = Short.compare(c1Key, c2Key); + if (cmp != 0) { + return cmp; + } + return Integer.compare(System.identityHashCode(c1), System.identityHashCode(c2)); + }); + + for (final Container child : childrenSorted) { + if (Boolean.TRUE.equals(subtreeHasDisplayed.get(child))) { + this.flattenDisplayedSubtree(child, displayedNodes, pathSet, subtreeMinDisplayedZIndex, subtreeHasDisplayed, result, visited); + } + } + } + + public void setParent(final Container parent) { + synchronized (DISPLAYED_CONTAINERS) { + if (this.parent != null) { + this.parent.children.remove(this); + } + this.parent = parent; + if (parent != null) { + parent.children.add(this); } } } diff --git a/src/test/java/de/extio/game_engine/demo/DemoModule.java b/src/test/java/de/extio/game_engine/demo/DemoModule.java index debbcc7..85526c4 100644 --- a/src/test/java/de/extio/game_engine/demo/DemoModule.java +++ b/src/test/java/de/extio/game_engine/demo/DemoModule.java @@ -69,12 +69,14 @@ public class DemoModule extends AbstractClientModule { this.secondaryWindow.setNormalizedPosition(centeredPosition(this.secondaryWindow.getNormalizedDimension())); this.secondaryWindow.setDraggable(true); this.secondaryWindow.setCloseButton(true); + this.secondaryWindow.setParent(this.mainWindow); this.themeSelectionWindow = this.applicationContext.getBean(Window.class); this.themeSelectionWindow.setNormalizedDimension(RendererControl.REFERENCE_RESOLUTION.divide(7).multiply(2)); this.themeSelectionWindow.setNormalizedPosition(centeredPosition(this.themeSelectionWindow.getNormalizedDimension())); this.themeSelectionWindow.setDraggable(true); this.themeSelectionWindow.setCloseButton(true); + this.themeSelectionWindow.setParent(this.mainWindow); this.getModuleService().changeActiveState(this.getId(), true); this.getModuleService().changeDisplayState(this.getId(), true);