From e2873858bb6b37eae3f31df1f44d0c75da8976e5 Mon Sep 17 00:00:00 2001 From: Stanislav Mishchenko Date: Wed, 1 May 2024 19:26:39 +0300 Subject: [PATCH] add a scene context menu --- .../solve/rendering/canvas/SceneCanvas.kt | 101 ++++++++++++++++-- .../engine/core/input/MouseButton.kt | 7 ++ src/main/kotlin/solve/scene/view/SceneView.kt | 27 +++-- 3 files changed, 118 insertions(+), 17 deletions(-) create mode 100644 src/main/kotlin/solve/rendering/engine/core/input/MouseButton.kt diff --git a/src/main/kotlin/solve/rendering/canvas/SceneCanvas.kt b/src/main/kotlin/solve/rendering/canvas/SceneCanvas.kt index 4be89f19..102e5a36 100644 --- a/src/main/kotlin/solve/rendering/canvas/SceneCanvas.kt +++ b/src/main/kotlin/solve/rendering/canvas/SceneCanvas.kt @@ -1,8 +1,19 @@ package solve.rendering.canvas +import io.github.palexdev.materialfx.beans.Alignment +import io.github.palexdev.materialfx.controls.MFXContextMenu +import io.github.palexdev.materialfx.controls.MFXContextMenuItem +import javafx.application.Platform +import javafx.beans.property.SimpleBooleanProperty +import javafx.geometry.HPos +import javafx.geometry.VPos +import javafx.scene.Node +import javafx.scene.input.Clipboard +import javafx.scene.input.ClipboardContent import org.joml.Vector2f import org.joml.Vector2i import solve.rendering.engine.core.input.LineLayerClickHandler +import solve.rendering.engine.core.input.MouseButton import solve.rendering.engine.core.input.PointLayerClickHandler import solve.rendering.engine.core.renderers.FramesRenderer import solve.rendering.engine.core.renderers.LinesLayerRenderer @@ -19,8 +30,16 @@ import solve.scene.model.Landmark import solve.scene.model.Layer import solve.scene.model.Scene import solve.scene.model.VisualizationFrame +import solve.scene.view.association.AssociationsManager import solve.utils.ServiceLocator +import solve.utils.action import solve.utils.ceilToInt +import solve.utils.getScreenPosition +import solve.utils.item +import solve.utils.lineSeparator +import tornadofx.add +import tornadofx.enableWhen +import tornadofx.wrapIn import solve.rendering.engine.scene.Scene as EngineScene class SceneCanvas : OpenGLCanvas() { @@ -56,9 +75,13 @@ class SceneCanvas : OpenGLCanvas() { (1 + Renderer.FramesSpacing) ) + private var contextMenuInvokeFrame: VisualizationFrame? = null + private val pointLayerClickHandler = PointLayerClickHandler() private val lineLayerClickHandler = LineLayerClickHandler() + private val contextMenu = buildContextMenu(canvas) + init { initializeCanvasEvents() } @@ -120,21 +143,29 @@ class SceneCanvas : OpenGLCanvas() { isDraggingScene = false } - fun interactWithLandmark(screenPoint: Vector2i) { - val frameCoordinate = shaderToFrameVector(calculateWindowTopLeftCornerShaderPosition()) + - screenToFrameVector(screenPoint) - val frameIndexCoordinates = frameCoordinate.toIntVector() + fun handleClick(screenPoint: Vector2i, mouseButton: MouseButton) { + val frameCoordinates = determineFrameCoordinates(screenPoint) + val frameIndexCoordinates = frameCoordinates.toIntVector() val frameIndex = frameIndexCoordinates.y * columnsNumber + frameIndexCoordinates.x - // Frame local coordinate including spacing area. - var frameLocalCoordinate = Vector2f(frameCoordinate.x % 1, frameCoordinate.y % 1) + var frameLocalCoordinate = Vector2f(frameCoordinates.x % 1, frameCoordinates.y % 1) // Frame local coordinate excluding spacing area. frameLocalCoordinate = frameToShaderVector(frameLocalCoordinate) frameLocalCoordinate.x /= framesRatio val framePixelCoordinate = Vector2f(frameLocalCoordinate.x * framesSize.x, frameLocalCoordinate.y * framesSize.y).toIntVector() - handleLandmarkInteraction(frameIndex, framePixelCoordinate) + if (mouseButton == landmarkInteractionMouseButton) { + tryInteractWithLandmark(frameIndex, framePixelCoordinate) + } else if (mouseButton == contextMenuMouseButton) { + contextMenuInvokeFrame = getContextMenuInvokeFrame(frameIndex) ?: return + val canvasScreenPosition = canvas.getScreenPosition() + contextMenu.show( + canvas, + canvasScreenPosition.x + screenPoint.x.toDouble(), + canvasScreenPosition.y + screenPoint.y.toDouble() + ) + } } fun zoomToPoint(screenPoint: Vector2i, newZoom: Float) { @@ -163,7 +194,11 @@ class SceneCanvas : OpenGLCanvas() { engineScene?.update() } - private fun handleLandmarkInteraction(frameIndex: Int, frameInteractionPixel: Vector2i) { + private fun determineFrameCoordinates(screenPoint: Vector2i): Vector2f { + return shaderToFrameVector(calculateWindowTopLeftCornerShaderPosition()) + screenToFrameVector(screenPoint) + } + + private fun tryInteractWithLandmark(frameIndex: Int, frameInteractionPixel: Vector2i) { if (frameIndex >= lastFramesSelection.count()) { return } @@ -309,7 +344,57 @@ class SceneCanvas : OpenGLCanvas() { rightLowerCornerCameraPosition.y.coerceAtLeast(leftUpperCornerCameraPosition.y) } + private fun buildContextMenu(parent: Node): MFXContextMenu { + val contextMenu = MFXContextMenu(parent) + contextMenu.addCopyTimestampItem() + contextMenu.lineSeparator() + contextMenu.addAssociateKeypointsItem() + contextMenu.addClearAssociationsItem() + + return contextMenu + } + + private fun MFXContextMenu.addCopyTimestampItem() { + item("Copy timestamp").action { + val invokeFrame = contextMenuInvokeFrame ?: return@action + + val timestamp = invokeFrame.timestamp + val clipboardContent = ClipboardContent().also { it.putString(timestamp.toString()) } + Clipboard.getSystemClipboard().setContent(clipboardContent) + } + } + + private fun MFXContextMenu.addAssociateKeypointsItem() { + item("Associate keypoints").action { + val invokeFrame = contextMenuInvokeFrame ?: return@action + + println("assoiation") + invokeFrame.toString() + } + } + + private fun MFXContextMenu.addClearAssociationsItem() { + item("Clear associations").action { + val invokeFrame = contextMenuInvokeFrame ?: return@action + + println("clear associations") + invokeFrame.toString() + } + } + + private fun getContextMenuInvokeFrame(contextMenuInvokeFrameIndex: Int) : VisualizationFrame? { + if (contextMenuInvokeFrameIndex >= lastFramesSelection.count()) { + println("The index of the invoked frame is incorrect!") + return null + } + + return lastFramesSelection[contextMenuInvokeFrameIndex] + } + companion object { const val IdentityFramesSizeScale = 1.605f + + private val landmarkInteractionMouseButton = MouseButton.Left + private val contextMenuMouseButton = MouseButton.Right } } diff --git a/src/main/kotlin/solve/rendering/engine/core/input/MouseButton.kt b/src/main/kotlin/solve/rendering/engine/core/input/MouseButton.kt new file mode 100644 index 00000000..a7dcec01 --- /dev/null +++ b/src/main/kotlin/solve/rendering/engine/core/input/MouseButton.kt @@ -0,0 +1,7 @@ +package solve.rendering.engine.core.input + +enum class MouseButton { + Left, + Middle, + Right +} diff --git a/src/main/kotlin/solve/scene/view/SceneView.kt b/src/main/kotlin/solve/scene/view/SceneView.kt index a8f7097b..661dbb41 100644 --- a/src/main/kotlin/solve/scene/view/SceneView.kt +++ b/src/main/kotlin/solve/scene/view/SceneView.kt @@ -2,10 +2,11 @@ package solve.scene.view import javafx.application.Platform import javafx.beans.InvalidationListener -import javafx.scene.input.MouseButton +import javafx.scene.input.MouseButton as JavaFXMouseButton import javafx.scene.input.MouseEvent import org.joml.Vector2i import solve.rendering.canvas.SceneCanvas +import solve.rendering.engine.core.input.MouseButton import solve.scene.SceneFacade import solve.scene.controller.SceneController import solve.scene.model.Landmark @@ -81,7 +82,7 @@ class SceneView : View() { mouseScreenPoint = extrudeEventMousePosition(event) } root.setOnMouseDragged { event -> - if (event.button != MouseInteractButton) { + if (getMouseButton(event) != MouseInteractionButton) { return@setOnMouseDragged } wasMouseDragging = true @@ -89,23 +90,22 @@ class SceneView : View() { canvas.dragTo(mouseScreenPoint) } root.setOnMousePressed { event -> - if (event.button != MouseInteractButton) { + if (getMouseButton(event) != MouseInteractionButton) { return@setOnMousePressed } canvas.startDragging(mouseScreenPoint) } root.setOnMouseReleased { event -> - if (event.button != MouseInteractButton) { + if (getMouseButton(event) != MouseInteractionButton) { return@setOnMouseReleased } canvas.stopDragging() } root.setOnMouseClicked { event -> - if (event.button != MouseInteractButton) { - return@setOnMouseClicked - } + val mouseButton = getMouseButton(event) ?: return@setOnMouseClicked + if (!wasMouseDragging) { - canvas.interactWithLandmark(mouseScreenPoint) + canvas.handleClick(mouseScreenPoint, mouseButton) } wasMouseDragging = false } @@ -119,8 +119,17 @@ class SceneView : View() { } } + private fun getMouseButton(event: MouseEvent) : MouseButton? { + return when (event.button) { + JavaFXMouseButton.PRIMARY -> MouseButton.Left + JavaFXMouseButton.MIDDLE -> MouseButton.Middle + JavaFXMouseButton.SECONDARY -> MouseButton.Right + else -> null + } + } + companion object { - private val MouseInteractButton = MouseButton.PRIMARY + private val MouseInteractionButton = MouseButton.Left const val framesMargin = 0.0 const val scrollSpeed = 20.0