feat(renderer): implement z-index management for layered rendering and update component order
This commit is contained in:
169
README.md
169
README.md
@@ -263,12 +263,181 @@ The `RendererWorkingSet` supports two rendering paradigms:
|
|||||||
|
|
||||||
Both approaches can be mixed within the same application, allowing each module to choose the most appropriate strategy for its needs.
|
Both approaches can be mixed within the same application, allowing each module to choose the most appropriate strategy for its needs.
|
||||||
|
|
||||||
|
#### UI Controls
|
||||||
|
|
||||||
|
The renderer provides a comprehensive set of interactive UI controls that can be used to build game interfaces. Controls are created as `ControlRenderingBo` objects and support common UI elements with automatic event handling.
|
||||||
|
|
||||||
|
**Available Control Types:**
|
||||||
|
- **`ButtonControl`**: Standard clickable button
|
||||||
|
- **`ToggleButtonControl`**: Button with on/off state
|
||||||
|
- **`SwitchControl`**: Toggle switch with visual state
|
||||||
|
- **`LabelControl`**: Non-interactive text display with optional background
|
||||||
|
- **`TextfieldControl`**: Single-line or multi-line text input
|
||||||
|
- **`SliderControl`**: Value slider with horizontal/vertical orientation
|
||||||
|
- **`TableControl`**: Data table with scrolling support
|
||||||
|
- **`WindowCloseButtonControl`**: Specialized close button for windows
|
||||||
|
- **`TooltipControl`**: Hover tooltip display
|
||||||
|
|
||||||
|
**Creating and Using Controls:**
|
||||||
|
|
||||||
|
```java
|
||||||
|
// Create a button control
|
||||||
|
var button = renderingBoPool.acquire("myButton", ControlRenderingBo.class)
|
||||||
|
.setType(ButtonControl.class)
|
||||||
|
.setCaption("Click Me")
|
||||||
|
.setFontSize(16)
|
||||||
|
.setVisible(true)
|
||||||
|
.setEnabled(true)
|
||||||
|
.withPositionAbsoluteAnchorTopLeft(100, 50)
|
||||||
|
.withDimensionAbsolute(120, 40);
|
||||||
|
|
||||||
|
// Add to working set
|
||||||
|
rendererWorkingSet.put(moduleId, button);
|
||||||
|
|
||||||
|
// Create a toggle button with custom state
|
||||||
|
var toggle = renderingBoPool.acquire("myToggle", ControlRenderingBo.class)
|
||||||
|
.setType(ToggleButtonControl.class)
|
||||||
|
.setCaption("Toggle")
|
||||||
|
.setCustomData(Boolean.TRUE) // Initial toggled state
|
||||||
|
.withPositionAbsoluteAnchorTopLeft(250, 50)
|
||||||
|
.withDimensionAbsolute(100, 40);
|
||||||
|
|
||||||
|
// Create a label with custom colors
|
||||||
|
var label = renderingBoPool.acquire("myLabel", ControlRenderingBo.class)
|
||||||
|
.setType(LabelControl.class)
|
||||||
|
.setCaption("Status: Ready")
|
||||||
|
.setColor(RgbaColor.WHITE) // Foreground color
|
||||||
|
.setCustomData(RgbaColor.DARK_GRAY) // Background color
|
||||||
|
.setCustomData2(HorizontalAlignment.CENTER) // Text alignment
|
||||||
|
.withPositionAbsoluteAnchorTopLeft(100, 100)
|
||||||
|
.withDimensionAbsolute(200, 30);
|
||||||
|
|
||||||
|
// Create a text field (multi-line)
|
||||||
|
var textField = renderingBoPool.acquire("myTextField", ControlRenderingBo.class)
|
||||||
|
.setType(TextfieldControl.class)
|
||||||
|
.setCaption("Enter text here...")
|
||||||
|
.setCustomData(Boolean.TRUE) // Multi-line mode
|
||||||
|
.setCustomData2(Boolean.FALSE) // Not read-only
|
||||||
|
.withPositionAbsoluteAnchorTopLeft(100, 150)
|
||||||
|
.withDimensionAbsolute(300, 100);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Handling Control Events:**
|
||||||
|
|
||||||
|
Controls fire `UiControlEvent` instances when interacted with. Register event handlers to respond to user actions:
|
||||||
|
|
||||||
|
```java
|
||||||
|
// In your module's onActivate() method
|
||||||
|
eventService.registerConsumer(
|
||||||
|
moduleId,
|
||||||
|
UiControlEvent.class,
|
||||||
|
event -> {
|
||||||
|
var controlId = event.getControlId();
|
||||||
|
var eventData = event.getData();
|
||||||
|
|
||||||
|
switch (controlId) {
|
||||||
|
case "myButton" -> handleButtonClick();
|
||||||
|
case "myToggle" -> handleToggleChange((Boolean) eventData);
|
||||||
|
case "myTextField" -> handleTextChange((String) eventData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Control Groups:**
|
||||||
|
|
||||||
|
Controls can be organized into groups for coordinated behavior (e.g., radio button groups where only one can be selected):
|
||||||
|
|
||||||
|
```java
|
||||||
|
var option1 = renderingBoPool.acquire("option1", ControlRenderingBo.class)
|
||||||
|
.setType(ToggleButtonControl.class)
|
||||||
|
.setControlGroup("radioGroup")
|
||||||
|
.setCaption("Option 1");
|
||||||
|
|
||||||
|
var option2 = renderingBoPool.acquire("option2", ControlRenderingBo.class)
|
||||||
|
.setType(ToggleButtonControl.class)
|
||||||
|
.setControlGroup("radioGroup")
|
||||||
|
.setCaption("Option 2");
|
||||||
|
```
|
||||||
|
|
||||||
|
When toggle buttons share a control group, only one can be active at a time - selecting one automatically deselects the others.
|
||||||
|
|
||||||
|
**Custom Data Fields:**
|
||||||
|
|
||||||
|
Controls support up to four custom data fields (`setCustomData()` through `setCustomData4()`) for control-specific configuration:
|
||||||
|
- Toggle buttons: `customData` = initial state (Boolean)
|
||||||
|
- Labels: `customData` = background color, `customData2` = text alignment
|
||||||
|
- Text fields: `customData` = multi-line mode (Boolean), `customData2` = read-only flag
|
||||||
|
- Sliders: `customData` = horizontal orientation, `customData2` = current value, `customData3` = secondary value
|
||||||
|
- Tables: `customData` = data list, `customData2` = row count, `customData3` = version number
|
||||||
|
|
||||||
#### Integration with Module System
|
#### Integration with Module System
|
||||||
|
|
||||||
The Renderer System integrates with the Module System to provide automatic cleanup of rendering objects. When a client module (`AbstractClientModule`) is deactivated or unloaded, its entire renderer working set is automatically cleared by the Module Service. This ensures that rendering objects associated with inactive or unloaded modules are not rendered and don't consume memory.
|
The Renderer System integrates with the Module System to provide automatic cleanup of rendering objects. When a client module (`AbstractClientModule`) is deactivated or unloaded, its entire renderer working set is automatically cleared by the Module Service. This ensures that rendering objects associated with inactive or unloaded modules are not rendered and don't consume memory.
|
||||||
|
|
||||||
Modules don't need to manually clear their rendering objects in their `onDeactivate()` or `onUnload()` lifecycle methods - the Module Service handles this cleanup automatically using `rendererWorkingSet.clear(moduleId)`.
|
Modules don't need to manually clear their rendering objects in their `onDeactivate()` or `onUnload()` lifecycle methods - the Module Service handles this cleanup automatically using `rendererWorkingSet.clear(moduleId)`.
|
||||||
|
|
||||||
|
#### Rendering Layers and Z-Index
|
||||||
|
|
||||||
|
The rendering system uses a two-level ordering system to control the draw order of rendering objects and UI components:
|
||||||
|
|
||||||
|
**Rendering Layers**
|
||||||
|
|
||||||
|
All `RenderingBo` objects have a `layer` property that determines their primary rendering order. Objects are rendered from lowest layer value to highest layer value, with higher layers painted on top. The `RenderingBoLayer` class defines standard layer constants:
|
||||||
|
|
||||||
|
- **Background Layers**: `BACKGROUND0` (100), `BACKGROUND1` (110), `BACKGROUND2` (120)
|
||||||
|
- **Foreground Layers**: `FOREGROUND0` (1000) through `FOREGROUND6` (1600)
|
||||||
|
- **UI Layers**: `UI_BGR` (2000), `UI0` (2100), `UI1` (2200), `UI2` (2300), `UI_TOP` (2499)
|
||||||
|
- **Top Layer**: `TOP` (32000) - always rendered last
|
||||||
|
|
||||||
|
**Z-Index for Fine-Grained Ordering**
|
||||||
|
|
||||||
|
Within the UI layers (`UI_BGR` through `UI_TOP`), rendering objects can additionally specify a `zIndex` property for fine-grained ordering. The z-index allows multiple objects on the same layer to be ordered relative to each other:
|
||||||
|
|
||||||
|
```java
|
||||||
|
// Both labels are on UI0 layer, but label2 renders on top due to higher zIndex
|
||||||
|
var label1 = pool.acquire("label1", LabelBo.class)
|
||||||
|
.setLayer(RenderingBoLayer.UI0)
|
||||||
|
.setZIndex((short) 0)
|
||||||
|
.setText("Behind");
|
||||||
|
|
||||||
|
var label2 = pool.acquire("label2", LabelBo.class)
|
||||||
|
.setLayer(RenderingBoLayer.UI0)
|
||||||
|
.setZIndex((short) 10)
|
||||||
|
.setText("In Front");
|
||||||
|
```
|
||||||
|
|
||||||
|
**Effective Layer Calculation**
|
||||||
|
|
||||||
|
For objects in UI layers, the effective rendering order is calculated as:
|
||||||
|
```
|
||||||
|
effectiveLayer = layer + (zIndex * ZINDEX_STEPS)
|
||||||
|
where ZINDEX_STEPS = UI_TOP - UI_BGR + 1 = 500
|
||||||
|
```
|
||||||
|
|
||||||
|
This formula ensures that:
|
||||||
|
- Objects with the same layer and zIndex render in the order they were added
|
||||||
|
- Higher zIndex values always render on top of lower values within the same layer
|
||||||
|
- **The zIndex must be less than 48** to prevent overlapping with different layer ranges
|
||||||
|
|
||||||
|
**AWT Component Z-Order**
|
||||||
|
|
||||||
|
For UI controls implemented as AWT/Swing components (buttons, labels, text fields, etc.), the z-index is synchronized with AWT's component z-order. When a control's zIndex or layer changes, the `G2DMainFrame.recalculateAllComponentZOrder()` method:
|
||||||
|
|
||||||
|
1. Collects all components and their effective layers
|
||||||
|
2. Sorts components by effective layer (ascending order)
|
||||||
|
3. Applies the sorted z-order to all components using `Container.setComponentZOrder()`
|
||||||
|
|
||||||
|
This ensures that AWT components respect the same layering rules as rendered objects, maintaining consistent visual stacking throughout the UI.
|
||||||
|
|
||||||
|
**Best Practices**
|
||||||
|
|
||||||
|
- Use layers for major UI groupings (backgrounds, game content, UI overlays)
|
||||||
|
- Use zIndex for ordering within a specific layer (window stacking, overlapping controls)
|
||||||
|
- Reserve `TOP` layer for critical always-on-top elements (tooltips, modal dialogs)
|
||||||
|
- **Keep zIndex values below 48** - higher values will cause incorrect rendering order
|
||||||
|
- For most use cases, zIndex values in the 0-20 range are sufficient
|
||||||
|
|
||||||
#### Setup / Autoconfiguration
|
#### Setup / Autoconfiguration
|
||||||
**Configuration Class**: `RendererAutoConfiguration`
|
**Configuration Class**: `RendererAutoConfiguration`
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ public class Container extends AbstractClientModule implements InitializingBean
|
|||||||
|
|
||||||
protected final Area2 area = new Area2(MutableCoordI2.create(), MutableCoordI2.create());
|
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 final Area2 normalizedArea = new Area2(MutableCoordI2.create(), MutableCoordI2.create());
|
||||||
|
|
||||||
protected boolean draggable;
|
protected boolean draggable;
|
||||||
@@ -56,6 +56,8 @@ public class Container extends AbstractClientModule implements InitializingBean
|
|||||||
protected HorizontalAlignment horizontalAlignment = HorizontalAlignment.CENTER;
|
protected HorizontalAlignment horizontalAlignment = HorizontalAlignment.CENTER;
|
||||||
|
|
||||||
protected VerticalAlignment verticalAlignment = VerticalAlignment.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) {
|
public Container(final ModuleService moduleService, final RenderingBoPool renderingBoPool, final RendererControl rendererControl, final EventService eventService, final RendererWorkingSet rendererWorkingSet) {
|
||||||
this.moduleService = moduleService;
|
this.moduleService = moduleService;
|
||||||
@@ -81,6 +83,7 @@ public class Container extends AbstractClientModule implements InitializingBean
|
|||||||
@Override
|
@Override
|
||||||
public void onShow() {
|
public void onShow() {
|
||||||
DISPLAYED_CONTAINERS.add(this);
|
DISPLAYED_CONTAINERS.add(this);
|
||||||
|
this.zIndexToTop();
|
||||||
this.draw();
|
this.draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,14 +164,17 @@ public class Container extends AbstractClientModule implements InitializingBean
|
|||||||
if (this.draggable && event.isDrag()) {
|
if (this.draggable && event.isDrag()) {
|
||||||
Container foregroundContainer = null;
|
Container foregroundContainer = null;
|
||||||
synchronized (DISPLAYED_CONTAINERS) {
|
synchronized (DISPLAYED_CONTAINERS) {
|
||||||
|
short zIndexHighest = -1;
|
||||||
for (final var container : DISPLAYED_CONTAINERS) {
|
for (final var container : DISPLAYED_CONTAINERS) {
|
||||||
if (container.dragging) {
|
if (container.dragging) {
|
||||||
foregroundContainer = container;
|
foregroundContainer = container;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (SpatialUtils2.intersects(container.area.getPosition(), container.area.getDimension(), event.getScaledCoord(), ImmutableCoordI2.one())) {
|
if (SpatialUtils2.intersects(container.area.getPosition(), container.area.getDimension(), event.getScaledCoord(), ImmutableCoordI2.one())) {
|
||||||
// TODO z-index handling
|
if (container.zIndex > zIndexHighest) {
|
||||||
foregroundContainer = container;
|
zIndexHighest = container.zIndex;
|
||||||
|
foregroundContainer = container;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -206,7 +212,9 @@ public class Container extends AbstractClientModule implements InitializingBean
|
|||||||
public void draw() {
|
public void draw() {
|
||||||
final var uncommitted = this.rendererWorkingSet.getUncommittedWork(this.id);
|
final var uncommitted = this.rendererWorkingSet.getUncommittedWork(this.id);
|
||||||
for (final RenderingBo bo : uncommitted.values()) {
|
for (final RenderingBo bo : uncommitted.values()) {
|
||||||
bo.withPositionAbsoluteAnchorTopLeft(this.area.getPosition()
|
bo
|
||||||
|
.setZIndex(this.zIndex)
|
||||||
|
.withPositionAbsoluteAnchorTopLeft(this.area.getPosition()
|
||||||
.toMutableCoordD2()
|
.toMutableCoordD2()
|
||||||
.add(bo.getLocalX(), bo.getLocalY())
|
.add(bo.getLocalX(), bo.getLocalY())
|
||||||
.toImmutableCoordI2());
|
.toImmutableCoordI2());
|
||||||
@@ -243,6 +251,9 @@ public class Container extends AbstractClientModule implements InitializingBean
|
|||||||
this.draw();
|
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() {
|
public CoordI2 getNormalizedPosition() {
|
||||||
return this.normalizedArea.getPosition().toImmutableCoordI2();
|
return this.normalizedArea.getPosition().toImmutableCoordI2();
|
||||||
}
|
}
|
||||||
@@ -252,7 +263,28 @@ public class Container extends AbstractClientModule implements InitializingBean
|
|||||||
this.area.getDimension().setXY(normalizedDimension);
|
this.area.getDimension().setXY(normalizedDimension);
|
||||||
this.draw();
|
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() {
|
public CoordI2 getNormalizedDimension() {
|
||||||
return this.normalizedArea.getDimension().toImmutableCoordI2();
|
return this.normalizedArea.getDimension().toImmutableCoordI2();
|
||||||
}
|
}
|
||||||
@@ -280,4 +312,13 @@ public class Container extends AbstractClientModule implements InitializingBean
|
|||||||
public void setVerticalAlignment(final VerticalAlignment verticalAlignment) {
|
public void setVerticalAlignment(final VerticalAlignment verticalAlignment) {
|
||||||
this.verticalAlignment = verticalAlignment;
|
this.verticalAlignment = verticalAlignment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public short getzIndex() {
|
||||||
|
return zIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setzIndex(final short zIndex) {
|
||||||
|
this.zIndex = zIndex;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ public class Window extends Container {
|
|||||||
.setThickBorder(true)
|
.setThickBorder(true)
|
||||||
.withDimensionAbsolute(this.area.getDimension())
|
.withDimensionAbsolute(this.area.getDimension())
|
||||||
.withPositionAbsoluteAnchorTopLeft(this.area.getPosition())
|
.withPositionAbsoluteAnchorTopLeft(this.area.getPosition())
|
||||||
|
.setZIndex(this.zIndex)
|
||||||
.setLayer(RenderingBoLayer.UI_BGR);
|
.setLayer(RenderingBoLayer.UI_BGR);
|
||||||
this.rendererWorkingSet.put(this.id, windowFrame);
|
this.rendererWorkingSet.put(this.id, windowFrame);
|
||||||
|
|
||||||
@@ -65,6 +66,7 @@ public class Window extends Container {
|
|||||||
.setVisible(true)
|
.setVisible(true)
|
||||||
.withDimensionAbsolute(this.area.getDimension())
|
.withDimensionAbsolute(this.area.getDimension())
|
||||||
.withPositionAbsoluteAnchorTopLeft(this.area.getPosition())
|
.withPositionAbsoluteAnchorTopLeft(this.area.getPosition())
|
||||||
|
.setZIndex(this.zIndex)
|
||||||
.setLayer(RenderingBoLayer.UI1);
|
.setLayer(RenderingBoLayer.UI1);
|
||||||
this.rendererWorkingSet.put(this.id, closeButtonBo);
|
this.rendererWorkingSet.put(this.id, closeButtonBo);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package de.extio.game_engine.renderer.g2d;
|
|||||||
|
|
||||||
import java.awt.AWTEvent;
|
import java.awt.AWTEvent;
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
|
import java.awt.Component;
|
||||||
import java.awt.EventQueue;
|
import java.awt.EventQueue;
|
||||||
import java.awt.Frame;
|
import java.awt.Frame;
|
||||||
import java.awt.Graphics;
|
import java.awt.Graphics;
|
||||||
@@ -22,6 +23,7 @@ import java.awt.event.MouseWheelListener;
|
|||||||
import java.awt.event.WindowEvent;
|
import java.awt.event.WindowEvent;
|
||||||
import java.awt.event.WindowListener;
|
import java.awt.event.WindowListener;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
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.RendererControl;
|
||||||
import de.extio.game_engine.renderer.RendererData;
|
import de.extio.game_engine.renderer.RendererData;
|
||||||
import de.extio.game_engine.renderer.g2d.control.G2DControlHasExclusiveKeyEvent;
|
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.KeyStrokeEvent;
|
||||||
import de.extio.game_engine.renderer.model.event.MouseClickEvent;
|
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.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.MouseExitEvent;
|
|
||||||
import de.extio.game_engine.renderer.model.event.MouseMoveEvent;
|
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;
|
||||||
import de.extio.game_engine.renderer.model.options.VideoOptions.VideoOptionsVideoMode;
|
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();
|
final var scaleFactor = ((G2DRendererControl) this.rendererData.getRendererControl()).getScaleFactor();
|
||||||
return ImmutableCoordI2.create((int) (rawCoord.getX() / scaleFactor), (int) (rawCoord.getY() / scaleFactor));
|
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) {
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ public class G2DDrawControl extends G2DAbstractRenderingBo implements ControlRen
|
|||||||
|
|
||||||
private final static Logger LOGGER = LoggerFactory.getLogger(G2DDrawControl.class);
|
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<>();
|
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.setEnabled(this.enabled);
|
||||||
control.setCaption(this.caption);
|
control.setCaption(this.caption);
|
||||||
control.setTooltip(this.tooltip);
|
control.setTooltip(this.tooltip);
|
||||||
|
control.setZIndex(this.zIndex);
|
||||||
|
control.setLayer(this.layer);
|
||||||
|
|
||||||
if (this.clazz == LabelControl.class) {
|
if (this.clazz == LabelControl.class) {
|
||||||
((LabelControl) control).setForegroundColor(this.color);
|
((LabelControl) control).setForegroundColor(this.color);
|
||||||
|
|||||||
@@ -11,8 +11,10 @@ import org.slf4j.LoggerFactory;
|
|||||||
import de.extio.game_engine.renderer.RendererData;
|
import de.extio.game_engine.renderer.RendererData;
|
||||||
import de.extio.game_engine.renderer.g2d.G2DRenderer;
|
import de.extio.game_engine.renderer.g2d.G2DRenderer;
|
||||||
import de.extio.game_engine.renderer.g2d.G2DRenderingHintFactory;
|
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;
|
||||||
import de.extio.game_engine.renderer.g2d.control.G2DDrawControlTooltip.TooltipRecord;
|
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.renderer.model.bo.ControlRenderingBo.BaseControl;
|
||||||
import de.extio.game_engine.spatial2.SpatialUtils2;
|
import de.extio.game_engine.spatial2.SpatialUtils2;
|
||||||
import de.extio.game_engine.spatial2.model.CoordI2;
|
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);
|
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 x;
|
||||||
|
|
||||||
protected int y;
|
protected int y;
|
||||||
@@ -60,6 +64,10 @@ public abstract class G2DBaseControlImpl implements BaseControl {
|
|||||||
|
|
||||||
protected CoordI2 tooltipMousePosition;
|
protected CoordI2 tooltipMousePosition;
|
||||||
|
|
||||||
|
protected short zIndex;
|
||||||
|
|
||||||
|
protected short layer;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BaseControl setInUse(final boolean inUse) {
|
public BaseControl setInUse(final boolean inUse) {
|
||||||
this.inUse = inUse;
|
this.inUse = inUse;
|
||||||
@@ -167,6 +175,21 @@ public abstract class G2DBaseControlImpl implements BaseControl {
|
|||||||
return this;
|
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
|
@Override
|
||||||
public void render() {
|
public void render() {
|
||||||
if (this.visible && this.bufferedImage != null) {
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,9 @@ public class G2DButtonControlImpl extends G2DBaseControlImpl implements ButtonCo
|
|||||||
|
|
||||||
this.createControl();
|
this.createControl();
|
||||||
this.initControl();
|
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
|
@Override
|
||||||
@@ -44,6 +46,7 @@ public class G2DButtonControlImpl extends G2DBaseControlImpl implements ButtonCo
|
|||||||
this.rebuildBufferedImage();
|
this.rebuildBufferedImage();
|
||||||
this.control.paint(this.bufferedImageGraphics);
|
this.control.paint(this.bufferedImageGraphics);
|
||||||
this.control.setDirty(false);
|
this.control.setDirty(false);
|
||||||
|
this.updateAllComponentZOrder();
|
||||||
}
|
}
|
||||||
else if (this.positionModified) {
|
else if (this.positionModified) {
|
||||||
this.control.setLocation(this.x, this.y);
|
this.control.setLocation(this.x, this.y);
|
||||||
|
|||||||
@@ -56,7 +56,9 @@ public class G2DLabelControlImpl extends G2DBaseControlImpl implements LabelCont
|
|||||||
|
|
||||||
this.createControl();
|
this.createControl();
|
||||||
this.initControl();
|
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
|
@Override
|
||||||
@@ -74,6 +76,7 @@ public class G2DLabelControlImpl extends G2DBaseControlImpl implements LabelCont
|
|||||||
this.rebuildBufferedImage();
|
this.rebuildBufferedImage();
|
||||||
this.control.paint(this.bufferedImageGraphics);
|
this.control.paint(this.bufferedImageGraphics);
|
||||||
this.control.setDirty(false);
|
this.control.setDirty(false);
|
||||||
|
this.updateAllComponentZOrder();
|
||||||
}
|
}
|
||||||
if (this.tooltip != null) {
|
if (this.tooltip != null) {
|
||||||
this.tooltipMousePosition = this.control.getLastMousePosition();
|
this.tooltipMousePosition = this.control.getLastMousePosition();
|
||||||
|
|||||||
@@ -80,7 +80,9 @@ public class G2DSliderControlImpl extends G2DBaseControlImpl implements SliderCo
|
|||||||
|
|
||||||
this.createControl();
|
this.createControl();
|
||||||
this.initControl();
|
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
|
@Override
|
||||||
@@ -104,6 +106,7 @@ public class G2DSliderControlImpl extends G2DBaseControlImpl implements SliderCo
|
|||||||
this.rebuildBufferedImage();
|
this.rebuildBufferedImage();
|
||||||
this.control.paint(this.bufferedImageGraphics);
|
this.control.paint(this.bufferedImageGraphics);
|
||||||
this.control.setDirty(false);
|
this.control.setDirty(false);
|
||||||
|
this.updateAllComponentZOrder();
|
||||||
}
|
}
|
||||||
if (this.tooltip != null) {
|
if (this.tooltip != null) {
|
||||||
this.tooltipMousePosition = this.control.getLastMousePosition();
|
this.tooltipMousePosition = this.control.getLastMousePosition();
|
||||||
|
|||||||
@@ -58,7 +58,9 @@ public class G2DTableControlImpl extends G2DBaseControlImpl implements TableCont
|
|||||||
|
|
||||||
this.createControl();
|
this.createControl();
|
||||||
this.initControl();
|
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.setColor(RgbaColor.LIGHT_GRAY);
|
||||||
this.scrollbar.setHorizontal(false);
|
this.scrollbar.setHorizontal(false);
|
||||||
@@ -78,7 +80,7 @@ public class G2DTableControlImpl extends G2DBaseControlImpl implements TableCont
|
|||||||
this.rebuildBufferedImage();
|
this.rebuildBufferedImage();
|
||||||
this.initControl();
|
this.initControl();
|
||||||
this.control.setDirty(false);
|
this.control.setDirty(false);
|
||||||
|
this.updateAllComponentZOrder();
|
||||||
}
|
}
|
||||||
this.control.paint(this.bufferedImageGraphics);
|
this.control.paint(this.bufferedImageGraphics);
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
@Override
|
||||||
@@ -98,6 +100,7 @@ public class G2DTextfieldControlImpl extends G2DBaseControlImpl implements Textf
|
|||||||
else {
|
else {
|
||||||
((CustomTextField) this.textComponent).setDirty(false);
|
((CustomTextField) this.textComponent).setDirty(false);
|
||||||
}
|
}
|
||||||
|
this.updateAllComponentZOrder();
|
||||||
}
|
}
|
||||||
|
|
||||||
super.render();
|
super.render();
|
||||||
|
|||||||
@@ -136,9 +136,11 @@ public class G2DTextfieldControlImpl2 extends G2DBaseControlImpl implements Text
|
|||||||
this.textComponent.setName(this.id);
|
this.textComponent.setName(this.id);
|
||||||
this.initControl();
|
this.initControl();
|
||||||
|
|
||||||
|
final var mainFrame = ((G2DRenderer) this.rendererData.getRenderer()).getMainFrame();
|
||||||
if (this.multiLine) {
|
if (this.multiLine) {
|
||||||
// ((G2DRenderer) this.RendererData.getRenderer()).getMainFrame().add(this.scrollPane);
|
// mainFrame.add(this.scrollPane);
|
||||||
((G2DRenderer) this.rendererData.getRenderer()).getMainFrame().add(this.textComponent);
|
mainFrame.add(this.textComponent);
|
||||||
|
this.updateAllComponentZOrder();
|
||||||
|
|
||||||
this.scrollbar.setColor(RgbaColor.LIGHT_GRAY);
|
this.scrollbar.setColor(RgbaColor.LIGHT_GRAY);
|
||||||
this.scrollbar.setHorizontal(false);
|
this.scrollbar.setHorizontal(false);
|
||||||
@@ -147,7 +149,8 @@ public class G2DTextfieldControlImpl2 extends G2DBaseControlImpl implements Text
|
|||||||
this.scrollbar.build();
|
this.scrollbar.build();
|
||||||
}
|
}
|
||||||
else {
|
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 {
|
else {
|
||||||
((CustomJTextField) this.textComponent).setDirty(false);
|
((CustomJTextField) this.textComponent).setDirty(false);
|
||||||
}
|
}
|
||||||
|
this.updateAllComponentZOrder();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.multiLine) {
|
if (this.multiLine) {
|
||||||
|
|||||||
Reference in New Issue
Block a user