feat(container): implement parent-child relationship for containers and update z-index management

This commit is contained in:
extio-de
2025-12-28 14:43:41 +01:00
parent 6c8db742c1
commit 00acf2296f
2 changed files with 138 additions and 14 deletions

View File

@@ -2,7 +2,11 @@ package de.extio.game_engine.renderer.container;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
@@ -60,6 +64,10 @@ public class Container extends AbstractClientModule implements InitializingBean
protected short zIndex; protected short zIndex;
protected Container parent;
protected Set<Container> children = new HashSet<>();
public Container(final ModuleService moduleService, final RenderingBoPool renderingBoPool, final RendererControl rendererControl, final EventService eventService, final RendererWorkingSet rendererWorkingSet) { public Container(final ModuleService moduleService, final RenderingBoPool renderingBoPool, final RendererControl rendererControl, final EventService eventService, final RendererWorkingSet rendererWorkingSet) {
this.moduleService = moduleService; this.moduleService = moduleService;
this.renderingBoPool = renderingBoPool; this.renderingBoPool = renderingBoPool;
@@ -284,22 +292,136 @@ public class Container extends AbstractClientModule implements InitializingBean
} }
synchronized (DISPLAYED_CONTAINERS) { synchronized (DISPLAYED_CONTAINERS) {
for (final var container : DISPLAYED_CONTAINERS) { final List<Container> path = this.getPathToRoot(this);
if (container == this) { final Container root = path.get(0);
continue; final Set<Container> pathSet = new HashSet<>(path);
final Set<Container> displayed = new HashSet<>(DISPLAYED_CONTAINERS);
final List<Container> others = new ArrayList<>();
for (final Container c : DISPLAYED_CONTAINERS) {
if (!isDescendantOrSelf(root, c)) {
others.add(c);
} }
if (container.getzIndex() > this.getzIndex()) { }
final var newZIndex = (short) (container.getzIndex() - 1); others.sort((c1, c2) -> Short.compare(c1.getzIndex(), c2.getzIndex()));
if (container.getzIndex() != newZIndex) {
container.setzIndex(newZIndex); final Map<Container, Short> subtreeMinDisplayedZIndex = new IdentityHashMap<>();
container.draw(); final Map<Container, Boolean> subtreeHasDisplayed = new IdentityHashMap<>();
this.computeSubtreeInfo(root, displayed, subtreeMinDisplayedZIndex, subtreeHasDisplayed, new HashSet<>());
final List<Container> 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++;
} }
} }
} }
final var newZIndex = (short) (DISPLAYED_CONTAINERS.size() - 1);
if (this.getzIndex() != newZIndex) { private List<Container> getPathToRoot(final Container container) {
this.setzIndex(newZIndex); final List<Container> path = new ArrayList<>();
this.draw(); 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<Container> displayedNodes, final Map<Container, Short> subtreeMinDisplayedZIndex, final Map<Container, Boolean> subtreeHasDisplayed, final Set<Container> 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<Container> displayedNodes, final Set<Container> pathSet, final Map<Container, Short> subtreeMinDisplayedZIndex, final Map<Container, Boolean> subtreeHasDisplayed, final List<Container> result, final Set<Container> visited) {
if (!visited.add(node)) {
return;
}
if (!Boolean.TRUE.equals(subtreeHasDisplayed.get(node))) {
return;
}
if (displayedNodes.contains(node)) {
result.add(node);
}
final List<Container> 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);
} }
} }
} }

View File

@@ -69,12 +69,14 @@ public class DemoModule extends AbstractClientModule {
this.secondaryWindow.setNormalizedPosition(centeredPosition(this.secondaryWindow.getNormalizedDimension())); this.secondaryWindow.setNormalizedPosition(centeredPosition(this.secondaryWindow.getNormalizedDimension()));
this.secondaryWindow.setDraggable(true); this.secondaryWindow.setDraggable(true);
this.secondaryWindow.setCloseButton(true); this.secondaryWindow.setCloseButton(true);
this.secondaryWindow.setParent(this.mainWindow);
this.themeSelectionWindow = this.applicationContext.getBean(Window.class); this.themeSelectionWindow = this.applicationContext.getBean(Window.class);
this.themeSelectionWindow.setNormalizedDimension(RendererControl.REFERENCE_RESOLUTION.divide(7).multiply(2)); this.themeSelectionWindow.setNormalizedDimension(RendererControl.REFERENCE_RESOLUTION.divide(7).multiply(2));
this.themeSelectionWindow.setNormalizedPosition(centeredPosition(this.themeSelectionWindow.getNormalizedDimension())); this.themeSelectionWindow.setNormalizedPosition(centeredPosition(this.themeSelectionWindow.getNormalizedDimension()));
this.themeSelectionWindow.setDraggable(true); this.themeSelectionWindow.setDraggable(true);
this.themeSelectionWindow.setCloseButton(true); this.themeSelectionWindow.setCloseButton(true);
this.themeSelectionWindow.setParent(this.mainWindow);
this.getModuleService().changeActiveState(this.getId(), true); this.getModuleService().changeActiveState(this.getId(), true);
this.getModuleService().changeDisplayState(this.getId(), true); this.getModuleService().changeDisplayState(this.getId(), true);