feat(renderer): implement z-index management for layered rendering and update component order

This commit is contained in:
extio-de
2025-12-27 16:16:05 +01:00
parent f36492197f
commit 7bc529a473
12 changed files with 354 additions and 15 deletions

View File

@@ -44,7 +44,7 @@ public class Container extends AbstractClientModule implements InitializingBean
protected final Area2 area = new Area2(MutableCoordI2.create(), MutableCoordI2.create());
// Normalized area relative to reference resolution, the actual area is aligned based on alignment settings. Scale factors are not considered/handled by the renderer control.
// Normalized area of this container relative to reference resolution, the actual position is automatically aligned based on alignment settings (e.g. if the user uses ultra-wide monitors). Global scale factors are not considered/handled by the renderer control.
protected final Area2 normalizedArea = new Area2(MutableCoordI2.create(), MutableCoordI2.create());
protected boolean draggable;
@@ -56,6 +56,8 @@ public class Container extends AbstractClientModule implements InitializingBean
protected HorizontalAlignment horizontalAlignment = HorizontalAlignment.CENTER;
protected VerticalAlignment verticalAlignment = VerticalAlignment.CENTER;
protected short zIndex;
public Container(final ModuleService moduleService, final RenderingBoPool renderingBoPool, final RendererControl rendererControl, final EventService eventService, final RendererWorkingSet rendererWorkingSet) {
this.moduleService = moduleService;
@@ -81,6 +83,7 @@ public class Container extends AbstractClientModule implements InitializingBean
@Override
public void onShow() {
DISPLAYED_CONTAINERS.add(this);
this.zIndexToTop();
this.draw();
}
@@ -161,14 +164,17 @@ public class Container extends AbstractClientModule implements InitializingBean
if (this.draggable && event.isDrag()) {
Container foregroundContainer = null;
synchronized (DISPLAYED_CONTAINERS) {
short zIndexHighest = -1;
for (final var container : DISPLAYED_CONTAINERS) {
if (container.dragging) {
foregroundContainer = container;
break;
}
if (SpatialUtils2.intersects(container.area.getPosition(), container.area.getDimension(), event.getScaledCoord(), ImmutableCoordI2.one())) {
// TODO z-index handling
foregroundContainer = container;
if (container.zIndex > zIndexHighest) {
zIndexHighest = container.zIndex;
foregroundContainer = container;
}
}
}
}
@@ -206,7 +212,9 @@ public class Container extends AbstractClientModule implements InitializingBean
public void draw() {
final var uncommitted = this.rendererWorkingSet.getUncommittedWork(this.id);
for (final RenderingBo bo : uncommitted.values()) {
bo.withPositionAbsoluteAnchorTopLeft(this.area.getPosition()
bo
.setZIndex(this.zIndex)
.withPositionAbsoluteAnchorTopLeft(this.area.getPosition()
.toMutableCoordD2()
.add(bo.getLocalX(), bo.getLocalY())
.toImmutableCoordI2());
@@ -243,6 +251,9 @@ public class Container extends AbstractClientModule implements InitializingBean
this.draw();
}
/**
* Normalized position of this container relative to reference resolution, the actual position is automatically aligned based on alignment settings (e.g. if the user uses ultra-wide monitors). Global scale factors are not considered/handled by the renderer control.
*/
public CoordI2 getNormalizedPosition() {
return this.normalizedArea.getPosition().toImmutableCoordI2();
}
@@ -252,7 +263,28 @@ public class Container extends AbstractClientModule implements InitializingBean
this.area.getDimension().setXY(normalizedDimension);
this.draw();
}
private void zIndexToTop() {
if (! this.draggable) {
return;
}
synchronized (DISPLAYED_CONTAINERS) {
for (final var container : DISPLAYED_CONTAINERS) {
if (container == this) {
continue;
}
if (container.getzIndex() > this.getzIndex()) {
container.setzIndex((short) (container.getzIndex() - 1));
}
}
this.setzIndex((short) (DISPLAYED_CONTAINERS.size() - 1));
}
}
/**
* Normalized position of this container relative to reference resolution. Global scale factors are not considered/handled by the renderer control.
*/
public CoordI2 getNormalizedDimension() {
return this.normalizedArea.getDimension().toImmutableCoordI2();
}
@@ -280,4 +312,13 @@ public class Container extends AbstractClientModule implements InitializingBean
public void setVerticalAlignment(final VerticalAlignment verticalAlignment) {
this.verticalAlignment = verticalAlignment;
}
public short getzIndex() {
return zIndex;
}
public void setzIndex(final short zIndex) {
this.zIndex = zIndex;
}
}

View File

@@ -53,6 +53,7 @@ public class Window extends Container {
.setThickBorder(true)
.withDimensionAbsolute(this.area.getDimension())
.withPositionAbsoluteAnchorTopLeft(this.area.getPosition())
.setZIndex(this.zIndex)
.setLayer(RenderingBoLayer.UI_BGR);
this.rendererWorkingSet.put(this.id, windowFrame);
@@ -65,6 +66,7 @@ public class Window extends Container {
.setVisible(true)
.withDimensionAbsolute(this.area.getDimension())
.withPositionAbsoluteAnchorTopLeft(this.area.getPosition())
.setZIndex(this.zIndex)
.setLayer(RenderingBoLayer.UI1);
this.rendererWorkingSet.put(this.id, closeButtonBo);
}

View File

@@ -2,6 +2,7 @@ package de.extio.game_engine.renderer.g2d;
import java.awt.AWTEvent;
import java.awt.Color;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.Frame;
import java.awt.Graphics;
@@ -22,6 +23,7 @@ import java.awt.event.MouseWheelListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -35,11 +37,12 @@ import de.extio.game_engine.keyboard.KeyModifiers;
import de.extio.game_engine.renderer.RendererControl;
import de.extio.game_engine.renderer.RendererData;
import de.extio.game_engine.renderer.g2d.control.G2DControlHasExclusiveKeyEvent;
import de.extio.game_engine.renderer.g2d.control.G2DDrawControl;
import de.extio.game_engine.renderer.g2d.control.impl.G2DBaseControlImpl;
import de.extio.game_engine.renderer.model.event.KeyStrokeEvent;
import de.extio.game_engine.renderer.model.event.MouseClickEvent;
import de.extio.game_engine.renderer.model.event.MouseEnterEvent;
import de.extio.game_engine.renderer.model.event.MouseExitEvent;
import de.extio.game_engine.renderer.model.event.MouseExitEvent;
import de.extio.game_engine.renderer.model.event.MouseMoveEvent;
import de.extio.game_engine.renderer.model.options.VideoOptions;
import de.extio.game_engine.renderer.model.options.VideoOptions.VideoOptionsVideoMode;
@@ -474,4 +477,44 @@ public class G2DMainFrame extends Frame {
final var scaleFactor = ((G2DRendererControl) this.rendererData.getRendererControl()).getScaleFactor();
return ImmutableCoordI2.create((int) (rawCoord.getX() / scaleFactor), (int) (rawCoord.getY() / scaleFactor));
}
private static final List<ComponentZOrderPair> COMPONENT_Z_ORDER_PAIRS = new ArrayList<>();
public void recalculateAllComponentZOrder() {
synchronized (getTreeLock()) {
try {
final var componentCount = this.getComponentCount();
if (componentCount == 0) {
return;
}
for (int i = 0; i < componentCount; i++) {
final var component = this.getComponent(i);
final var componentName = component.getName();
if (componentName == null) {
continue;
}
final var control = G2DDrawControl.CACHED_CONTROLS.get(componentName);
if (control != null && control instanceof G2DBaseControlImpl) {
final var baseControl = (G2DBaseControlImpl) control;
COMPONENT_Z_ORDER_PAIRS.add(new ComponentZOrderPair(component, baseControl.getEffectiveLayer()));
}
}
COMPONENT_Z_ORDER_PAIRS.sort((a, b) -> Integer.compare(b.effectiveLayer, a.effectiveLayer));
for (int i = 0; i < COMPONENT_Z_ORDER_PAIRS.size(); i++) {
this.setComponentZOrder(COMPONENT_Z_ORDER_PAIRS.get(i).component, i);
}
}
finally {
COMPONENT_Z_ORDER_PAIRS.clear();
}
}
}
private static record ComponentZOrderPair (Component component, int effectiveLayer) {
}
}

View File

@@ -37,7 +37,7 @@ public class G2DDrawControl extends G2DAbstractRenderingBo implements ControlRen
private final static Logger LOGGER = LoggerFactory.getLogger(G2DDrawControl.class);
private static Map<String, BaseControl> CACHED_CONTROLS = new HashMap<>();
public static Map<String, BaseControl> CACHED_CONTROLS = new HashMap<>();
public static Map<String, List<BaseControl>> CONTROL_GROUPS = new HashMap<>();
@@ -218,6 +218,8 @@ public class G2DDrawControl extends G2DAbstractRenderingBo implements ControlRen
control.setEnabled(this.enabled);
control.setCaption(this.caption);
control.setTooltip(this.tooltip);
control.setZIndex(this.zIndex);
control.setLayer(this.layer);
if (this.clazz == LabelControl.class) {
((LabelControl) control).setForegroundColor(this.color);

View File

@@ -11,8 +11,10 @@ import org.slf4j.LoggerFactory;
import de.extio.game_engine.renderer.RendererData;
import de.extio.game_engine.renderer.g2d.G2DRenderer;
import de.extio.game_engine.renderer.g2d.G2DRenderingHintFactory;
import de.extio.game_engine.renderer.g2d.control.G2DDrawControl;
import de.extio.game_engine.renderer.g2d.control.G2DDrawControlTooltip;
import de.extio.game_engine.renderer.g2d.control.G2DDrawControlTooltip.TooltipRecord;
import de.extio.game_engine.renderer.model.RenderingBoLayer;
import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.BaseControl;
import de.extio.game_engine.spatial2.SpatialUtils2;
import de.extio.game_engine.spatial2.model.CoordI2;
@@ -22,6 +24,8 @@ public abstract class G2DBaseControlImpl implements BaseControl {
protected final static Logger LOGGER = LoggerFactory.getLogger(G2DBaseControlImpl.class);
private static final short ZINDEX_STEPS = RenderingBoLayer.UI_TOP - RenderingBoLayer.UI_BGR + 1;
protected int x;
protected int y;
@@ -60,6 +64,10 @@ public abstract class G2DBaseControlImpl implements BaseControl {
protected CoordI2 tooltipMousePosition;
protected short zIndex;
protected short layer;
@Override
public BaseControl setInUse(final boolean inUse) {
this.inUse = inUse;
@@ -167,6 +175,21 @@ public abstract class G2DBaseControlImpl implements BaseControl {
return this;
}
public G2DBaseControlImpl setZIndex(final short zIndex) {
this.modified |= this.zIndex != zIndex;
this.zIndex = zIndex;
return this;
}
public short getZIndex() {
return this.zIndex;
}
public G2DBaseControlImpl setLayer(final short layer) {
this.layer = layer;
return this;
}
@Override
public void render() {
if (this.visible && this.bufferedImage != null) {
@@ -224,4 +247,45 @@ public abstract class G2DBaseControlImpl implements BaseControl {
}
}
protected int calculateComponentZOrder() {
final var mainFrame = ((G2DRenderer) this.rendererData.getRenderer()).getMainFrame();
final var componentCount = mainFrame.getComponentCount();
if (componentCount == 0) {
return 0;
}
final var myEffectiveLayer = this.getEffectiveLayer();
int zOrder = 0;
for (int i = 0; i < componentCount; i++) {
final var component = mainFrame.getComponent(i);
final var componentName = component.getName();
if (componentName == null) {
continue;
}
final var otherControl = G2DDrawControl.CACHED_CONTROLS.get(componentName);
if (otherControl != null && otherControl instanceof final G2DBaseControlImpl baseControl) {
final var otherEffectiveLayer = baseControl.getEffectiveLayer();
if (otherEffectiveLayer < myEffectiveLayer) {
zOrder++;
}
}
}
return Math.min(zOrder, componentCount - 1);
}
public int getEffectiveLayer() {
if (this.layer >= RenderingBoLayer.UI_BGR && this.layer <= RenderingBoLayer.UI_TOP) {
return this.layer + (this.zIndex * ZINDEX_STEPS);
}
return this.layer;
}
protected void updateAllComponentZOrder() {
((G2DRenderer) this.rendererData.getRenderer()).getMainFrame().recalculateAllComponentZOrder();
}
}

View File

@@ -27,7 +27,9 @@ public class G2DButtonControlImpl extends G2DBaseControlImpl implements ButtonCo
this.createControl();
this.initControl();
((G2DRenderer) this.rendererData.getRenderer()).getMainFrame().add(this.control);
final var mainFrame = ((G2DRenderer) this.rendererData.getRenderer()).getMainFrame();
mainFrame.add(this.control);
this.updateAllComponentZOrder();
}
@Override
@@ -44,6 +46,7 @@ public class G2DButtonControlImpl extends G2DBaseControlImpl implements ButtonCo
this.rebuildBufferedImage();
this.control.paint(this.bufferedImageGraphics);
this.control.setDirty(false);
this.updateAllComponentZOrder();
}
else if (this.positionModified) {
this.control.setLocation(this.x, this.y);

View File

@@ -56,7 +56,9 @@ public class G2DLabelControlImpl extends G2DBaseControlImpl implements LabelCont
this.createControl();
this.initControl();
((G2DRenderer) this.rendererData.getRenderer()).getMainFrame().add(this.control);
final var mainFrame = ((G2DRenderer) this.rendererData.getRenderer()).getMainFrame();
mainFrame.add(this.control);
this.updateAllComponentZOrder();
}
@Override
@@ -74,6 +76,7 @@ public class G2DLabelControlImpl extends G2DBaseControlImpl implements LabelCont
this.rebuildBufferedImage();
this.control.paint(this.bufferedImageGraphics);
this.control.setDirty(false);
this.updateAllComponentZOrder();
}
if (this.tooltip != null) {
this.tooltipMousePosition = this.control.getLastMousePosition();

View File

@@ -80,7 +80,9 @@ public class G2DSliderControlImpl extends G2DBaseControlImpl implements SliderCo
this.createControl();
this.initControl();
((G2DRenderer) this.rendererData.getRenderer()).getMainFrame().add(this.control);
final var mainFrame = ((G2DRenderer) this.rendererData.getRenderer()).getMainFrame();
mainFrame.add(this.control);
this.updateAllComponentZOrder();
}
@Override
@@ -104,6 +106,7 @@ public class G2DSliderControlImpl extends G2DBaseControlImpl implements SliderCo
this.rebuildBufferedImage();
this.control.paint(this.bufferedImageGraphics);
this.control.setDirty(false);
this.updateAllComponentZOrder();
}
if (this.tooltip != null) {
this.tooltipMousePosition = this.control.getLastMousePosition();

View File

@@ -58,7 +58,9 @@ public class G2DTableControlImpl extends G2DBaseControlImpl implements TableCont
this.createControl();
this.initControl();
((G2DRenderer) this.rendererData.getRenderer()).getMainFrame().add(this.control);
final var mainFrame = ((G2DRenderer) this.rendererData.getRenderer()).getMainFrame();
mainFrame.add(this.control);
this.updateAllComponentZOrder();
this.scrollbar.setColor(RgbaColor.LIGHT_GRAY);
this.scrollbar.setHorizontal(false);
@@ -78,7 +80,7 @@ public class G2DTableControlImpl extends G2DBaseControlImpl implements TableCont
this.rebuildBufferedImage();
this.initControl();
this.control.setDirty(false);
this.updateAllComponentZOrder();
}
this.control.paint(this.bufferedImageGraphics);

View File

@@ -64,7 +64,9 @@ public class G2DTextfieldControlImpl extends G2DBaseControlImpl implements Textf
});
}
((G2DRenderer) this.rendererData.getRenderer()).getMainFrame().add(this.textComponent);
final var mainFrame = ((G2DRenderer) this.rendererData.getRenderer()).getMainFrame();
mainFrame.add(this.textComponent);
this.updateAllComponentZOrder();
}
@Override
@@ -98,6 +100,7 @@ public class G2DTextfieldControlImpl extends G2DBaseControlImpl implements Textf
else {
((CustomTextField) this.textComponent).setDirty(false);
}
this.updateAllComponentZOrder();
}
super.render();

View File

@@ -136,9 +136,11 @@ public class G2DTextfieldControlImpl2 extends G2DBaseControlImpl implements Text
this.textComponent.setName(this.id);
this.initControl();
final var mainFrame = ((G2DRenderer) this.rendererData.getRenderer()).getMainFrame();
if (this.multiLine) {
// ((G2DRenderer) this.RendererData.getRenderer()).getMainFrame().add(this.scrollPane);
((G2DRenderer) this.rendererData.getRenderer()).getMainFrame().add(this.textComponent);
// mainFrame.add(this.scrollPane);
mainFrame.add(this.textComponent);
this.updateAllComponentZOrder();
this.scrollbar.setColor(RgbaColor.LIGHT_GRAY);
this.scrollbar.setHorizontal(false);
@@ -147,7 +149,8 @@ public class G2DTextfieldControlImpl2 extends G2DBaseControlImpl implements Text
this.scrollbar.build();
}
else {
((G2DRenderer) this.rendererData.getRenderer()).getMainFrame().add(this.textComponent);
mainFrame.add(this.textComponent);
this.updateAllComponentZOrder();
}
}
@@ -188,6 +191,7 @@ public class G2DTextfieldControlImpl2 extends G2DBaseControlImpl implements Text
else {
((CustomJTextField) this.textComponent).setDirty(false);
}
this.updateAllComponentZOrder();
}
if (this.multiLine) {