From 8b912a58458d0500fb1ed6b9f5b1c8cf780932a5 Mon Sep 17 00:00:00 2001 From: extio-de Date: Fri, 26 Dec 2025 17:13:06 +0100 Subject: [PATCH] feat(container): Dragging and adjustIfOutsideViewport --- .../renderer/container/Container.java | 139 +++++++++++++++++- .../renderer/container/legacy/Window.java | 6 +- 2 files changed, 138 insertions(+), 7 deletions(-) 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 c503640..0c63d11 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 @@ -1,5 +1,9 @@ package de.extio.game_engine.renderer.container; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + import org.springframework.beans.factory.InitializingBean; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @@ -11,17 +15,23 @@ import de.extio.game_engine.renderer.RendererControl; import de.extio.game_engine.renderer.model.RenderingBo; import de.extio.game_engine.renderer.model.bo.HorizontalAlignment; import de.extio.game_engine.renderer.model.bo.VerticalAlignment; +import de.extio.game_engine.renderer.model.event.MouseClickEvent; +import de.extio.game_engine.renderer.model.event.MouseMoveEvent; import de.extio.game_engine.renderer.model.event.ViewportResizeEvent; import de.extio.game_engine.renderer.work.RendererWorkingSet; import de.extio.game_engine.renderer.work.RenderingBoPool; +import de.extio.game_engine.spatial2.SpatialUtils2; import de.extio.game_engine.spatial2.model.Area2; import de.extio.game_engine.spatial2.model.CoordI2; +import de.extio.game_engine.spatial2.model.ImmutableCoordI2; import de.extio.game_engine.spatial2.model.MutableCoordI2; @Component @Scope("prototype") public class Container extends AbstractClientModule implements InitializingBean { + protected final static List DISPLAYED_CONTAINERS = Collections.synchronizedList(new ArrayList<>()); + protected final ModuleService moduleService; protected final RenderingBoPool renderingBoPool; @@ -39,6 +49,8 @@ public class Container extends AbstractClientModule implements InitializingBean protected boolean draggable; protected boolean dragging; + + protected CoordI2 dragPrevPosition; protected HorizontalAlignment horizontalAlignment = HorizontalAlignment.CENTER; @@ -60,24 +72,144 @@ public class Container extends AbstractClientModule implements InitializingBean @Override public void onActivate() { this.eventService.register(ViewportResizeEvent.class, this.id, this::onViewportResize); + this.eventService.register(MouseMoveEvent.class, this.id, this::onMouseMoveEvent); + this.eventService.register(MouseClickEvent.class, this.id, this::onMouseClickEvent); this.onViewportResize(null); } @Override public void onShow() { + DISPLAYED_CONTAINERS.add(this); this.draw(); } - + @Override - public void onDeactivate() { - + public void onHide() { + DISPLAYED_CONTAINERS.remove(this); } protected void onViewportResize(final ViewportResizeEvent event) { this.setNormalizedPosition(this.normalizedArea.getPosition()); this.setNormalizedDimension(this.normalizedArea.getDimension()); + this.adjustIfOutsideViewport(); this.draw(); } + + private boolean adjustIfOutsideViewport() { + final var viewportDimension = this.rendererControl.getEffectiveViewportDimension(); + final var position = this.area.getPosition(); + final var dimension = this.area.getDimension(); + + final var containerArea = this.area.getDimension().getX() * this.area.getDimension().getY(); + final var intersectionArea = this.calculateIntersectionArea(position, dimension, viewportDimension); + final var intersectionPercentage = containerArea > 0 ? (double) intersectionArea / containerArea : 0.0; + + if (intersectionPercentage >= 0.1) { + return false; + } + + int adjustedX = position.getX(); + int adjustedY = position.getY(); + + if (position.getX() >= viewportDimension.getX()) { + adjustedX = viewportDimension.getX() - dimension.getX(); + } else if (position.getX() + dimension.getX() <= 0) { + adjustedX = 0; + } else if (position.getX() < 0) { + adjustedX = 0; + } else if (position.getX() + dimension.getX() > viewportDimension.getX()) { + adjustedX = viewportDimension.getX() - dimension.getX(); + } + + if (position.getY() >= viewportDimension.getY()) { + adjustedY = viewportDimension.getY() - dimension.getY(); + } else if (position.getY() + dimension.getY() <= 0) { + adjustedY = 0; + } else if (position.getY() < 0) { + adjustedY = 0; + } else if (position.getY() + dimension.getY() > viewportDimension.getY()) { + adjustedY = viewportDimension.getY() - dimension.getY(); + } + + final var normalizedX = this.calculateNormalizedX(adjustedX); + final var normalizedY = this.calculateNormalizedY(adjustedY); + this.normalizedArea.getPosition().setXY(normalizedX, normalizedY); + this.area.getPosition().setXY(adjustedX, adjustedY); + + this.LOGGER.debug("Adjusted container {} position to {},{} to remain visible in viewport", this.id, adjustedX, adjustedY); + return true; + } + + private int calculateIntersectionArea(final CoordI2 position, final CoordI2 dimension, final CoordI2 viewportDimension) { + final int x1 = Math.max(0, position.getX()); + final int y1 = Math.max(0, position.getY()); + final int x2 = Math.min(viewportDimension.getX(), position.getX() + dimension.getX()); + final int y2 = Math.min(viewportDimension.getY(), position.getY() + dimension.getY()); + + final int width = Math.max(0, x2 - x1); + final int height = Math.max(0, y2 - y1); + + return width * height; + } + + protected int calculateNormalizedX(final int actualX) { + return switch (this.horizontalAlignment) { + case LEFT -> actualX; + case CENTER -> actualX - (this.rendererControl.getEffectiveViewportDimension().getX() - RendererControl.REFERENCE_RESOLUTION.getX()) / 2; + case RIGHT -> actualX - (this.rendererControl.getEffectiveViewportDimension().getX() - RendererControl.REFERENCE_RESOLUTION.getX()); + }; + } + + protected int calculateNormalizedY(final int actualY) { + return switch (this.verticalAlignment) { + case TOP -> actualY; + case CENTER -> actualY - (this.rendererControl.getEffectiveViewportDimension().getY() - RendererControl.REFERENCE_RESOLUTION.getY()) / 2; + case BOTTOM -> actualY - (this.rendererControl.getEffectiveViewportDimension().getY() - RendererControl.REFERENCE_RESOLUTION.getY()); + }; + } + + protected void onMouseMoveEvent(final MouseMoveEvent event) { + if (this.draggable && event.isDrag()) { + Container foregroundContainer = null; + synchronized (DISPLAYED_CONTAINERS) { + for (final var container : DISPLAYED_CONTAINERS) { + if (container.dragging) { + foregroundContainer = container; + break; + } + if (SpatialUtils2.intersects(container.area.getPosition(), container.area.getDimension(), event.getCoord(), ImmutableCoordI2.one())) { + // TODO z-index handling + foregroundContainer = container; + } + } + } + if (foregroundContainer == this) { + if (!this.dragging) { + this.dragging = true; + this.dragPrevPosition = null; + this.LOGGER.debug("Started dragging container {}", this.id); + } + if (this.dragPrevPosition != null) { + final var delta = event.getCoord().substract(this.dragPrevPosition); + if (! ImmutableCoordI2.zero().equals(delta)) { + this.setNormalizedPosition(this.getNormalizedPosition().add(delta)); + } + } + this.dragPrevPosition = event.getCoord(); + } + } + } + + protected void onMouseClickEvent(final MouseClickEvent event) { + if (! event.isPressed() && this.dragging) { + this.dragging = false; + this.dragPrevPosition = null; + if (this.adjustIfOutsideViewport()) { + this.draw(); + } + this.LOGGER.debug("Stopped dragging container {}", this.id); + } + } /** * Override in subclasses, but make sure to call super.draw() at the end if overridden @@ -120,7 +252,6 @@ public class Container extends AbstractClientModule implements InitializingBean case BOTTOM -> this.area.getPosition().setY(normalizedPosition.getY() + this.rendererControl.getEffectiveViewportDimension().getY() - RendererControl.REFERENCE_RESOLUTION.getY()); } this.draw(); - this.LOGGER.debug("Effective viewport dimension: {}", this.rendererControl.getEffectiveViewportDimension()); } public CoordI2 getNormalizedPosition() { diff --git a/src/main/java/de/extio/game_engine/renderer/container/legacy/Window.java b/src/main/java/de/extio/game_engine/renderer/container/legacy/Window.java index 28ef14d..2a74cb9 100644 --- a/src/main/java/de/extio/game_engine/renderer/container/legacy/Window.java +++ b/src/main/java/de/extio/game_engine/renderer/container/legacy/Window.java @@ -16,7 +16,7 @@ import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.WindowCloseButt import de.extio.game_engine.renderer.model.color.RgbaColor; import de.extio.game_engine.renderer.work.RenderingBoPool; import de.extio.game_engine.renderer.model.bo.DrawWindowRenderingBo; -import de.extio.game_engine.spatial2.WorldUtils2; +import de.extio.game_engine.spatial2.SpatialUtils2; import de.extio.game_engine.spatial2.model.Area2; import de.extio.game_engine.spatial2.model.CoordD2; import de.extio.game_engine.spatial2.model.CoordI2; @@ -82,7 +82,7 @@ public class Window implements HasPositionAndDimension2, AutoCloseable { // return false; synchronized (WINDOWS) { for (final Window window : WINDOWS) { - if (WorldUtils2.intersects(window, area)) { + if (SpatialUtils2.intersects(window, area)) { return true; } } @@ -163,7 +163,7 @@ public class Window implements HasPositionAndDimension2, AutoCloseable { } public boolean intersects(final CoordI2 coord) { - return WorldUtils2.intersects(this.area.getPosition(), this.area.getDimension(), coord, ImmutableCoordI2.one()); + return SpatialUtils2.intersects(this.area.getPosition(), this.area.getDimension(), coord, ImmutableCoordI2.one()); } public void render(final List renderingBo, final BiConsumer, RenderingBo> consumer) {