From 78ece9712631215cd7b8a0e76aa9059be4678333 Mon Sep 17 00:00:00 2001 From: Sebastian Erives Date: Thu, 19 Sep 2024 05:36:45 -0600 Subject: [PATCH] Project system & auto recovery --- EOCVSimPlugin/build.gradle | 7 +- .../plugin/project/PaperVisionProject.java | 29 ++ .../project/recovery/RecoveredProject.java | 37 +++ .../recovery/RecoveryDaemonClient.java | 75 +++++ .../plugin/project/recovery/RecoveryData.java | 26 ++ .../papervision/plugin/PaperVisionDaemon.kt | 47 ++- .../plugin/PaperVisionEOCVSimPlugin.kt | 116 ++++++- .../eocvsim/EOCVSimEngineImageStreamer.kt | 68 +++-- .../eocvsim/PaperVisionDefaultPipeline.kt | 61 ++++ .../plugin/gui/CloseConfirmWindow.kt | 60 ++++ .../plugin/gui/EasyVisionIntroWindow.kt | 37 +++ .../gui/eocvsim/PaperVisionTabButtonsPanel.kt | 139 +++++++++ .../plugin/gui/eocvsim/PaperVisionTabPanel.kt | 110 +++++++ .../gui/eocvsim/ProjectTreeCellRenderer.kt | 28 ++ .../gui/eocvsim/dialog/NewProjectPanel.kt | 136 +++++++++ .../dialog/PaperVisionDialogFactory.kt | 33 ++ .../eocvsim/dialog/ProjectRecoveryPanel.kt | 88 ++++++ .../project/PaperVisionProjectManager.kt | 288 ++++++++++++++++++ .../plugin/project/PaperVisionProjectTree.kt | 101 ++++++ .../recovery/RecoveryDaemonProcessManager.kt | 72 +++++ .../platform/lwjgl/PaperVisionApp.kt | 12 +- .../github/deltacv/papervision/PaperVision.kt | 28 +- .../papervision/attribute/Attribute.kt | 25 +- .../attribute/math/IntAttribute.kt | 2 +- .../attribute/misc/ListAttribute.kt | 5 +- .../attribute/misc/StringAttribute.kt | 2 +- .../attribute/vision/MatAttribute.kt | 6 +- .../vision/structs/ScalarAttribute.kt | 2 +- .../vision/structs/ScalarRangeAttribute.kt | 2 +- .../papervision/codegen/CodeGenManager.kt | 3 +- .../papervision/codegen/dsl/CodeGenContext.kt | 8 +- .../engine/previz/ClientPrevizManager.kt | 4 +- .../deltacv/papervision/gui/NodeEditor.kt | 22 +- .../deltacv/papervision/gui/NodeList.kt | 1 - .../deltacv/papervision/gui/util/Popup.kt | 10 +- .../deltacv/papervision/gui/util/Window.kt | 45 ++- .../deltacv/papervision/id/IdElement.kt | 6 +- .../papervision/id/IdElementContainer.kt | 11 +- .../deltacv/papervision/node/DrawNode.kt | 29 +- .../github/deltacv/papervision/node/Link.kt | 10 +- .../github/deltacv/papervision/node/Node.kt | 46 +-- .../{NodesFromMetadata.kt => NodeRegistry.kt} | 0 .../papervision/node/code/CodeSnippetNode.kt | 4 +- .../papervision/node/math/SumIntegerNode.kt | 2 +- .../papervision/node/vision/MatNodes.kt | 40 ++- .../GroupContoursByShapeNode.kt | 4 +- .../GroupRectsInsideAreaNode.kt | 4 +- .../filter/FilterBiggestRectangleNode.kt | 2 +- .../node/vision/imageproc/ErodeDilateNode.kt | 4 +- .../node/vision/imageproc/ThresholdNode.kt | 1 + .../node/vision/overlay/DrawContoursNode.kt | 4 +- .../node/vision/overlay/DrawRectanglesNode.kt | 12 +- .../{ev/Data.kt => PaperVisionData.kt} | 2 +- .../{ev => }/PaperVisionSerializer.kt | 60 ++-- .../serialization/data/DataSerializable.kt | 3 + .../serialization/data/DataSerializer.kt | 5 +- .../data/adapter/DataDeserialization.kt | 5 +- .../data/adapter/DataSerialization.kt | 10 +- .../SerializeIgnoreExclusionStrategy.kt | 13 +- .../src/main/resources/ico/ico_ezv.jpg | Bin 0 -> 4697 bytes 60 files changed, 1832 insertions(+), 180 deletions(-) create mode 100644 EOCVSimPlugin/src/main/java/io/github/deltacv/papervision/plugin/project/PaperVisionProject.java create mode 100644 EOCVSimPlugin/src/main/java/io/github/deltacv/papervision/plugin/project/recovery/RecoveredProject.java create mode 100644 EOCVSimPlugin/src/main/java/io/github/deltacv/papervision/plugin/project/recovery/RecoveryDaemonClient.java create mode 100644 EOCVSimPlugin/src/main/java/io/github/deltacv/papervision/plugin/project/recovery/RecoveryData.java create mode 100644 EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/eocvsim/PaperVisionDefaultPipeline.kt create mode 100644 EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/gui/CloseConfirmWindow.kt create mode 100644 EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/gui/EasyVisionIntroWindow.kt create mode 100644 EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/gui/eocvsim/PaperVisionTabButtonsPanel.kt create mode 100644 EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/gui/eocvsim/PaperVisionTabPanel.kt create mode 100644 EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/gui/eocvsim/ProjectTreeCellRenderer.kt create mode 100644 EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/gui/eocvsim/dialog/NewProjectPanel.kt create mode 100644 EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/gui/eocvsim/dialog/PaperVisionDialogFactory.kt create mode 100644 EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/gui/eocvsim/dialog/ProjectRecoveryPanel.kt create mode 100644 EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/project/PaperVisionProjectManager.kt create mode 100644 EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/project/PaperVisionProjectTree.kt create mode 100644 EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/project/recovery/RecoveryDaemonProcessManager.kt rename PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/{NodesFromMetadata.kt => NodeRegistry.kt} (100%) rename PaperVision/src/main/kotlin/io/github/deltacv/papervision/serialization/{ev/Data.kt => PaperVisionData.kt} (92%) rename PaperVision/src/main/kotlin/io/github/deltacv/papervision/serialization/{ev => }/PaperVisionSerializer.kt (57%) create mode 100644 PaperVision/src/main/resources/ico/ico_ezv.jpg diff --git a/EOCVSimPlugin/build.gradle b/EOCVSimPlugin/build.gradle index 3a3c988..06e3f29 100644 --- a/EOCVSimPlugin/build.gradle +++ b/EOCVSimPlugin/build.gradle @@ -23,11 +23,16 @@ dependencies { implementation project(":LwjglPlatform") // compileOnly 'com.github.deltacv.EOCV-Sim:EOCV-Sim:3.6.0' - compileOnly 'com.github.deltacv:EOCV-Sim:4.0.0-dev-240912-0046' + compileOnly 'com.github.deltacv:EOCV-Sim:4.0.0-dev-240917-1612' compileOnly "io.github.spair:imgui-java-app:$imgui_version" implementation 'org.codehaus.janino:janino:3.1.12' implementation "org.slf4j:slf4j-api:$slf4j_version" + + implementation "io.javalin:javalin:6.3.0" + implementation 'com.google.code.gson:gson:2.8.9' + + implementation 'org.java-websocket:Java-WebSocket:1.5.7' } \ No newline at end of file diff --git a/EOCVSimPlugin/src/main/java/io/github/deltacv/papervision/plugin/project/PaperVisionProject.java b/EOCVSimPlugin/src/main/java/io/github/deltacv/papervision/plugin/project/PaperVisionProject.java new file mode 100644 index 0000000..bf661e1 --- /dev/null +++ b/EOCVSimPlugin/src/main/java/io/github/deltacv/papervision/plugin/project/PaperVisionProject.java @@ -0,0 +1,29 @@ +package io.github.deltacv.papervision.plugin.project; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; + +public class PaperVisionProject { + + public long timestamp; + public String path; + public String name; + public JsonElement json; + + private static final Gson gson = new Gson(); + + public PaperVisionProject(long timestamp, String path, String name, JsonElement json) { + this.timestamp = timestamp; + this.path = path; + this.name = name; + this.json = json; + } + + public static PaperVisionProject fromJson(String jsonString) { + return gson.fromJson(jsonString, PaperVisionProject.class); + } + + public String toJson() { + return gson.toJson(this); + } +} \ No newline at end of file diff --git a/EOCVSimPlugin/src/main/java/io/github/deltacv/papervision/plugin/project/recovery/RecoveredProject.java b/EOCVSimPlugin/src/main/java/io/github/deltacv/papervision/plugin/project/recovery/RecoveredProject.java new file mode 100644 index 0000000..ec1f0c3 --- /dev/null +++ b/EOCVSimPlugin/src/main/java/io/github/deltacv/papervision/plugin/project/recovery/RecoveredProject.java @@ -0,0 +1,37 @@ +package io.github.deltacv.papervision.plugin.project.recovery; + +import com.google.gson.Gson; +import io.github.deltacv.papervision.plugin.project.PaperVisionProject; + +public class RecoveredProject { + public String originalProjectPath; + public long date; + public String hash; + public PaperVisionProject project; + + private static final Gson gson = new Gson(); + + public RecoveredProject(String originalProjectPath, long date, String hash, PaperVisionProject project) { + this.originalProjectPath = originalProjectPath; + this.date = date; + this.hash = hash; + this.project = project; + } + + public String toJson() { + return gson.toJson(this); + } + + @Override + public String toString() { + return "RecoveredProject{" + + "path='" + originalProjectPath + '\'' + + ", date=" + date + + ", hash='" + hash + '\'' + + '}'; + } + + public static RecoveredProject fromJson(String json) { + return gson.fromJson(json, RecoveredProject.class); + } +} diff --git a/EOCVSimPlugin/src/main/java/io/github/deltacv/papervision/plugin/project/recovery/RecoveryDaemonClient.java b/EOCVSimPlugin/src/main/java/io/github/deltacv/papervision/plugin/project/recovery/RecoveryDaemonClient.java new file mode 100644 index 0000000..1ecf669 --- /dev/null +++ b/EOCVSimPlugin/src/main/java/io/github/deltacv/papervision/plugin/project/recovery/RecoveryDaemonClient.java @@ -0,0 +1,75 @@ +package io.github.deltacv.papervision.plugin.project.recovery; + +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.handshake.ServerHandshake; + +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class RecoveryDaemonClient extends WebSocketClient { + + public static final int MAX_CONNECTION_ATTEMPTS_BEFORE_EXITING = 3; + + public RecoveryDaemonClient(int port) throws URISyntaxException { + super(new URI("ws://localhost:" + port)); + } + + @Override + public void onOpen(ServerHandshake handshakedata) { + System.out.println("Connected to recovery daemon server at port " + uri.getPort()); + } + + @Override + public void onMessage(String message) { + try { + RecoveryData recoveryData = RecoveryData.deserialize(message); + Path recoveryPath = Paths.get(recoveryData.recoveryFolderPath); + + if (!Files.exists(recoveryPath)) { + Files.createDirectories(recoveryPath); + } + + Path recoveryFilePath = recoveryPath.resolve(recoveryData.recoveryFileName); + Files.write(recoveryFilePath, recoveryData.projectData.toJson().getBytes()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void onClose(int code, String reason, boolean remote) { + } + + @Override + public void onError(Exception ex) { + } + + public static void main(String[] args) { + int port = args.length > 0 ? Integer.parseInt(args[0]) : 17112; + + RecoveryDaemonClient client = null; + int connectionAttempts = 0; + + try { + while(!Thread.interrupted()) { + if(connectionAttempts >= MAX_CONNECTION_ATTEMPTS_BEFORE_EXITING) { + System.out.println("Failed to connect to recovery daemon after " + MAX_CONNECTION_ATTEMPTS_BEFORE_EXITING + " attempts. Exiting..."); + System.exit(0); + } + + if(client == null || client.isClosed()) { + client = new RecoveryDaemonClient(port); + client.connect(); + connectionAttempts += 1; + } + + Thread.sleep(100); + } + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } catch(InterruptedException ignored) { } + } +} \ No newline at end of file diff --git a/EOCVSimPlugin/src/main/java/io/github/deltacv/papervision/plugin/project/recovery/RecoveryData.java b/EOCVSimPlugin/src/main/java/io/github/deltacv/papervision/plugin/project/recovery/RecoveryData.java new file mode 100644 index 0000000..cbfdc02 --- /dev/null +++ b/EOCVSimPlugin/src/main/java/io/github/deltacv/papervision/plugin/project/recovery/RecoveryData.java @@ -0,0 +1,26 @@ +package io.github.deltacv.papervision.plugin.project.recovery; + +import com.google.gson.Gson; + +public class RecoveryData { + + public String recoveryFolderPath; + public String recoveryFileName; + public RecoveredProject projectData; + + private static final Gson gson = new Gson(); + + public RecoveryData(String recoveryFolderPath, String recoveryFileName, RecoveredProject projectData) { + this.recoveryFolderPath = recoveryFolderPath; + this.recoveryFileName = recoveryFileName; + this.projectData = projectData; + } + + public static String serialize(RecoveryData data) { + return gson.toJson(data); + } + + public static RecoveryData deserialize(String json) { + return gson.fromJson(json, RecoveryData.class); + } +} \ No newline at end of file diff --git a/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/PaperVisionDaemon.kt b/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/PaperVisionDaemon.kt index ab54816..9a07639 100644 --- a/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/PaperVisionDaemon.kt +++ b/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/PaperVisionDaemon.kt @@ -1,7 +1,9 @@ package io.github.deltacv.papervision.plugin -import com.github.serivesmejia.eocvsim.util.exception.handling.EOCVSimUncaughtExceptionHandler +import com.google.gson.JsonElement import io.github.deltacv.papervision.platform.lwjgl.PaperVisionApp +import io.github.deltacv.papervision.serialization.PaperVisionSerializer +import io.github.deltacv.papervision.util.event.EventHandler import io.github.deltacv.papervision.util.event.EventListener import java.util.concurrent.Executors import java.util.concurrent.Future @@ -22,34 +24,65 @@ object PaperVisionDaemon { private lateinit var app: PaperVisionApp val paperVision get() = app.paperVision + val onAppInstantiate = EventHandler("PaperVisionDaemon-onAppInstantiate") + lateinit var paperVisionFuture: Future<*> private set + var paperVisionWatchdogGrowled = false + private set + fun launchDaemonPaperVision(instantiator: () -> PaperVisionApp) { paperVisionFuture = executorService.submit { app = instantiator() + onAppInstantiate.run() app.start() } + } - executorService.submit { - while(!paperVisionFuture.isDone) { - Thread.sleep(100) + fun openProject(json: JsonElement) { + paperVision.onUpdate.doOnce { + PaperVisionSerializer.deserializeAndApply(json, paperVision) + + if(!app.glfwWindow.visible) { + app.glfwWindow.visible = true } } } + fun currentProjectJson() = PaperVisionSerializer.serialize( + paperVision.nodes.inmutable, paperVision.links.inmutable + ) + + + fun currentProjectJsonTree() = PaperVisionSerializer.serializeToTree( + paperVision.nodes.inmutable, paperVision.links.inmutable + ) + + fun showPaperVision() { + app.glfwWindow.visible = true + } + + fun hidePaperVision() { + app.glfwWindow.visible = false + } + fun invokeOnMainLoop(runnable: Runnable) = paperVision.onUpdate.doOnce(runnable) fun attachToMainLoop(listener: EventListener) = paperVision.onUpdate(listener) + fun attachToEditorChange(listener: EventListener) = + paperVision.nodeEditor.onEditorChange(listener) + fun watchdog() { - if(paperVisionFuture.isDone) paperVisionFuture.get() + if(paperVisionFuture.isDone && !paperVisionWatchdogGrowled) { + paperVisionWatchdogGrowled = true + paperVisionFuture.get() + } } - fun invokeLater(runnable: Runnable) = executorService.submit(runnable) - /** * Stops the PaperVision process by shutting down the executor service. */ diff --git a/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/PaperVisionEOCVSimPlugin.kt b/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/PaperVisionEOCVSimPlugin.kt index 69369a0..0a5f353 100644 --- a/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/PaperVisionEOCVSimPlugin.kt +++ b/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/PaperVisionEOCVSimPlugin.kt @@ -1,17 +1,23 @@ package io.github.deltacv.papervision.plugin +import com.github.serivesmejia.eocvsim.pipeline.PipelineSource import io.github.deltacv.eocvsim.plugin.EOCVSimPlugin import io.github.deltacv.papervision.engine.LocalPaperVisionEngine import io.github.deltacv.papervision.engine.bridge.LocalPaperVisionEngineBridge import io.github.deltacv.papervision.engine.client.message.* import io.github.deltacv.papervision.engine.client.response.BooleanResponse -import io.github.deltacv.papervision.engine.client.response.ErrorResponse import io.github.deltacv.papervision.engine.client.response.OkResponse import io.github.deltacv.papervision.platform.lwjgl.PaperVisionApp +import io.github.deltacv.papervision.plugin.eocvsim.PaperVisionDefaultPipeline import io.github.deltacv.papervision.plugin.eocvsim.PrevizSession +import io.github.deltacv.papervision.plugin.gui.CloseConfirmWindow +import io.github.deltacv.papervision.plugin.gui.eocvsim.PaperVisionTabPanel +import io.github.deltacv.papervision.plugin.gui.eocvsim.dialog.PaperVisionDialogFactory +import io.github.deltacv.papervision.plugin.project.PaperVisionProjectManager import org.opencv.core.Size -import javax.swing.JButton -import javax.swing.JPanel +import java.awt.print.Paper +import javax.swing.JOptionPane +import javax.swing.SwingUtilities class PaperVisionEOCVSimPlugin : EOCVSimPlugin() { @@ -19,27 +25,85 @@ class PaperVisionEOCVSimPlugin : EOCVSimPlugin() { var currentPrevizSession: PrevizSession? = null + val paperVisionProjectManager = PaperVisionProjectManager( + context.loader.pluginFile, fileSystem + ) + private val sessionStreamResolutions = mutableMapOf() override fun onLoad() { PaperVisionDaemon.launchDaemonPaperVision { - PaperVisionApp(true, LocalPaperVisionEngineBridge(engine)) + PaperVisionApp(true, LocalPaperVisionEngineBridge(engine), windowCloseListener = ::paperVisionUserCloseListener) } - eocvSim.visualizer.onPluginGuiAttachment.doOnce { - val panel = JPanel() - panel.add(JButton("Start PaperVision").apply { - addActionListener { - PaperVisionDaemon.invokeOnMainLoop { - PaperVisionDaemon.paperVision.window.visible = true + PaperVisionDaemon.onAppInstantiate { + PaperVisionDaemon.invokeOnMainLoop { + paperVisionProjectManager.init() + + if(paperVisionProjectManager.recoveredProjects.isNotEmpty()) { + SwingUtilities.invokeLater { + PaperVisionDialogFactory.displayProjectRecoveryDialog( + eocvSim.visualizer.frame, paperVisionProjectManager.recoveredProjects + ) { + for (recoveredProject in it) { + paperVisionProjectManager.recoverProject(recoveredProject) + } + + if(it.isNotEmpty()) { + JOptionPane.showMessageDialog( + eocvSim.visualizer.frame, + "Successfully recovered ${it.size} unsaved project(s)", + "PaperVision Project Recovery", + JOptionPane.INFORMATION_MESSAGE + ) + } + + paperVisionProjectManager.deleteAllRecoveredProjects() + } } } - }) + } + } + + eocvSim.pipelineManager.requestAddPipelineClass(PaperVisionDefaultPipeline::class.java, PipelineSource.CLASSPATH) + + eocvSim.visualizer.onPluginGuiAttachment.doOnce { + val switchablePanel = eocvSim.visualizer.pipelineOpModeSwitchablePanel - eocvSim.visualizer.pipelineOpModeSwitchablePanel.add("PaperVision", panel) + switchablePanel.addTab("PaperVision", PaperVisionTabPanel(paperVisionProjectManager)) + + switchablePanel.addChangeListener { + changeToPaperVisionPipelineIfNecessary() + } + } + + eocvSim.pipelineManager.onPipelineChange { + changeToPaperVisionPipelineIfNecessary() } } + private fun paperVisionUserCloseListener(): Boolean { + if(paperVisionProjectManager.currentProject != null) { + PaperVisionDaemon.paperVision.onUpdate.doOnce { + CloseConfirmWindow { + when (it) { + CloseConfirmWindow.Action.YES -> { + paperVisionProjectManager.saveCurrentProject() + PaperVisionDaemon.hidePaperVision() + } + CloseConfirmWindow.Action.NO -> { + PaperVisionDaemon.hidePaperVision() + paperVisionProjectManager.discardCurrentRecovery() + } + else -> { /* NO-OP */ } + } + }.enable() + } + + return false + } else return true + } + override fun onEnable() { engine.setMessageHandlerOf { eocvSim.tunerManager.getTunableFieldWithLabel(message.label)?.setFieldValue(message.index, message.value) @@ -62,6 +126,11 @@ class PaperVisionEOCVSimPlugin : EOCVSimPlugin() { engine.setMessageHandlerOf { sessionStreamResolutions[message.previzName] = Size(message.width.toDouble(), message.height.toDouble()) + + if(currentPrevizSession?.sessionName == message.previzName) { + currentPrevizSession?.streamer?.resolution = Size(message.width.toDouble(), message.height.toDouble()) + } + respond(OkResponse()) } @@ -78,6 +147,8 @@ class PaperVisionEOCVSimPlugin : EOCVSimPlugin() { currentPrevizSession!!.refreshPreviz(message.sourceCode) respond(OkResponse()) + + println(message.sourceCode) } eocvSim.onMainUpdate { @@ -89,4 +160,25 @@ class PaperVisionEOCVSimPlugin : EOCVSimPlugin() { override fun onDisable() { } + private fun changeToPaperVisionPipelineIfNecessary() { + val switchablePanel = eocvSim.visualizer.pipelineOpModeSwitchablePanel + + if(switchablePanel.selectedIndex == switchablePanel.indexOfTab("PaperVision")) { + if(currentPrevizSession?.previzRunning != true) { + eocvSim.pipelineManager.onUpdate.doOnce { + eocvSim.pipelineManager.changePipeline( + eocvSim.pipelineManager.getIndexOf( + PaperVisionDefaultPipeline::class.java, + PipelineSource.CLASSPATH + )!! + ) + + eocvSim.visualizer.viewport.renderer.setFpsMeterEnabled(false) + } + } + } else { + eocvSim.visualizer.viewport.renderer.setFpsMeterEnabled(true) + } + } + } \ No newline at end of file diff --git a/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/eocvsim/EOCVSimEngineImageStreamer.kt b/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/eocvsim/EOCVSimEngineImageStreamer.kt index 6035ea3..ae4a544 100644 --- a/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/eocvsim/EOCVSimEngineImageStreamer.kt +++ b/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/eocvsim/EOCVSimEngineImageStreamer.kt @@ -6,10 +6,7 @@ import io.github.deltacv.papervision.engine.PaperVisionEngine import io.github.deltacv.papervision.engine.message.ByteMessageTag import io.github.deltacv.vision.external.util.extension.aspectRatio import io.github.deltacv.vision.external.util.extension.clipTo -import org.opencv.core.Mat -import org.opencv.core.Point -import org.opencv.core.Rect -import org.opencv.core.Size +import org.opencv.core.* import org.opencv.imgproc.Imgproc import org.openftc.easyopencv.MatRecycler @@ -19,9 +16,12 @@ class EOCVSimEngineImageStreamer( resolution: Size ) : ImageStreamer { - val matRecycler = MatRecycler(2) + val matRecycler = MatRecycler(3) private var bytes = ByteArray(resolution.width.toInt() * resolution.height.toInt() * 3) + private val latestMat = Mat() + private val maskMatMap = mutableMapOf() + var resolution = resolution set(value) { field = value @@ -69,19 +69,21 @@ class EOCVSimEngineImageStreamer( val resizedImg = matRecycler.takeMatOrNull() - Imgproc.resize(image, resizedImg, newSize, 0.0, 0.0, Imgproc.INTER_AREA) - - //get submat of the exact required size and offset position from the "videoMat", - //which has the user-defined size of the current video. - val submat = scaledImg.submat(Rect(Point(xOffset, yOffset), newSize)) - - //then we copy our adjusted mat into the gotten submat. since a submat is just - //a reference to the parent mat, when we copy here our data will be actually - //copied to the actual mat, and so our new mat will be of the correct size and - //centered with the required offset - resizedImg.copyTo(submat) - - resizedImg.returnMat() + try { + Imgproc.resize(image, resizedImg, newSize, 0.0, 0.0, Imgproc.INTER_AREA) + + //get submat of the exact required size and offset position from the "videoMat", + //which has the user-defined size of the current video. + val submat = scaledImg.submat(Rect(Point(xOffset, yOffset), newSize)) + + //then we copy our adjusted mat into the gotten submat. since a submat is just + //a reference to the parent mat, when we copy here our data will be actually + //copied to the actual mat, and so our new mat will be of the correct size and + //centered with the required offset + resizedImg.copyTo(submat) + } finally { + resizedImg.returnMat() + } } } @@ -94,11 +96,33 @@ class EOCVSimEngineImageStreamer( return } - synchronized(bytes) { - scaledImg.get(0, 0, bytes) - scaledImg.returnMat() + fun sendBytes() { + synchronized(bytes) { + scaledImg.get(0, 0, bytes) + engine.sendBytes(byteTag, id, bytes) + } + } - engine.sendBytes(byteTag, id, bytes) + try { + val latestToCurrentMaskMat = maskMatMap.getOrPut(id) { Mat() } + + if (!latestMat.empty() && latestMat.size() == scaledImg.size()) { + latestToCurrentMaskMat.release() + + Core.bitwise_xor(latestMat, scaledImg, latestToCurrentMaskMat) + Core.extractChannel(latestToCurrentMaskMat, latestToCurrentMaskMat, 0) + + if (Core.countNonZero(latestToCurrentMaskMat) == 0) { + return + } else { + sendBytes() + } + } else { + sendBytes() + } + } finally { + scaledImg.copyTo(latestMat) // update latest mat + scaledImg.returnMat() } } diff --git a/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/eocvsim/PaperVisionDefaultPipeline.kt b/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/eocvsim/PaperVisionDefaultPipeline.kt new file mode 100644 index 0000000..6bfe8ba --- /dev/null +++ b/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/eocvsim/PaperVisionDefaultPipeline.kt @@ -0,0 +1,61 @@ +package io.github.deltacv.papervision.plugin.eocvsim + +import com.github.serivesmejia.eocvsim.util.loggerForThis +import com.qualcomm.robotcore.eventloop.opmode.Disabled +import org.firstinspires.ftc.robotcore.external.Telemetry +import org.opencv.core.Mat +import org.opencv.core.MatOfByte +import org.opencv.core.Scalar +import org.opencv.core.Size +import org.opencv.imgcodecs.Imgcodecs +import org.opencv.imgproc.Imgproc +import org.openftc.easyopencv.OpenCvPipeline + +@Disabled +class PaperVisionDefaultPipeline( + val telemetry: Telemetry +) : OpenCvPipeline() { + + val logger by loggerForThis() + + lateinit var drawMat: Mat + + override fun init(mat: Mat) { + drawMat = Mat(mat.size(), mat.type()) + drawMat.setTo(Scalar(0.0, 0.0, 0.0, 0.0)) + + try { + val bytes = PaperVisionDefaultPipeline::class.java.getResourceAsStream("/ico/ico_ezv.png")!!.use { + it.readBytes() + } + + println(bytes.size) + + val logoBytes = MatOfByte(*bytes) + val logoMat = Imgcodecs.imdecode(logoBytes, Imgcodecs.IMREAD_UNCHANGED) + logoBytes.release() + + Imgproc.cvtColor(logoMat, logoMat, Imgproc.COLOR_BGR2RGBA) + + Imgproc.resize(logoMat, logoMat, Size(logoMat.size().width / 2, logoMat.size().width / 2)) + + // Draw the logo centered + val x = (drawMat.width() - logoMat.width()) / 2 + val y = (drawMat.height() - logoMat.height()) / 2 + + val roi = drawMat.submat(y, y + logoMat.height(), x, x + logoMat.width()) + logoMat.copyTo(roi) + + logoMat.release() + } catch(e: Exception) { + logger.warn("Failed to load logo", e) + } + } + + override fun processFrame(input: Mat): Mat { + telemetry.addLine("Making computer vision accessible to everyone") + telemetry.update() + return drawMat + } + +} \ No newline at end of file diff --git a/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/gui/CloseConfirmWindow.kt b/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/gui/CloseConfirmWindow.kt new file mode 100644 index 0000000..c507154 --- /dev/null +++ b/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/gui/CloseConfirmWindow.kt @@ -0,0 +1,60 @@ +package io.github.deltacv.papervision.plugin.gui + +import imgui.ImGui +import imgui.flag.ImGuiWindowFlags +import io.github.deltacv.papervision.gui.util.Window +import io.github.deltacv.papervision.util.flags + +class CloseConfirmWindow( + val callback: (Action) -> Unit +) : Window() { + enum class Action { + YES, + NO, + CANCEL + } + + override var title: String = "Confirm" + override val windowFlags = flags( + ImGuiWindowFlags.NoResize, + ImGuiWindowFlags.NoMove, + ImGuiWindowFlags.NoCollapse + ) + + override val isModal = true + + private var isFirstDraw = true + + override fun onEnable() { + // centerWindow() + focus = true + isFirstDraw = true + } + + override fun drawContents() { + // Recenter the window on the second draw + if (!isFirstDraw) { + // centerWindow() + } else { + isFirstDraw = false + } + + ImGui.text("Do you wanna save before exiting?") + ImGui.separator() + + if(ImGui.button("Yes")) { + callback(Action.YES) + ImGui.closeCurrentPopup() + } + ImGui.sameLine() + if(ImGui.button("No")) { + callback(Action.NO) + ImGui.closeCurrentPopup() + } + ImGui.sameLine() + if(ImGui.button("Cancel")) { + callback(Action.CANCEL) + ImGui.closeCurrentPopup() + } + } +} \ No newline at end of file diff --git a/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/gui/EasyVisionIntroWindow.kt b/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/gui/EasyVisionIntroWindow.kt new file mode 100644 index 0000000..324debe --- /dev/null +++ b/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/gui/EasyVisionIntroWindow.kt @@ -0,0 +1,37 @@ +package io.github.deltacv.papervision.plugin.gui + +import imgui.ImGui +import imgui.flag.ImGuiWindowFlags +import io.github.deltacv.papervision.gui.util.Window +import io.github.deltacv.papervision.util.flags + +class EasyVisionIntroWindow : Window() { + override var title = "Welcome!" + + override val windowFlags = flags( + ImGuiWindowFlags.NoResize, + ImGuiWindowFlags.NoMove, + ImGuiWindowFlags.NoCollapse + ) + + private var isFirstDraw = true + + override fun onEnable() { + centerWindow() + focus = true + isFirstDraw = true + } + + override fun drawContents() { + // Recenter the window on the second draw + if (!isFirstDraw) { + centerWindow() + } else { + isFirstDraw = false + } + + ImGui.text("Welcome to EasyVision!") + ImGui.separator() + } + +} \ No newline at end of file diff --git a/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/gui/eocvsim/PaperVisionTabButtonsPanel.kt b/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/gui/eocvsim/PaperVisionTabButtonsPanel.kt new file mode 100644 index 0000000..4364e57 --- /dev/null +++ b/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/gui/eocvsim/PaperVisionTabButtonsPanel.kt @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2024 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package io.github.deltacv.papervision.plugin.gui.eocvsim + +import io.github.deltacv.papervision.plugin.PaperVisionDaemon +import io.github.deltacv.papervision.plugin.gui.eocvsim.dialog.PaperVisionDialogFactory +import io.github.deltacv.papervision.plugin.project.PaperVisionProjectManager +import io.github.deltacv.papervision.plugin.project.PaperVisionProjectTree +import java.awt.GridBagConstraints +import java.awt.GridBagLayout +import java.awt.Insets +import javax.swing.JButton +import javax.swing.JFrame +import javax.swing.JOptionPane +import javax.swing.JPanel +import javax.swing.JTree +import javax.swing.SwingUtilities +import javax.swing.tree.DefaultMutableTreeNode + +class PaperVisionTabButtonsPanel( + projectsJTree: JTree, + projectManager: PaperVisionProjectManager +) : JPanel(GridBagLayout()) { + + val newProjectBtt = JButton("New Project") + val deleteProjectBtt = JButton("Delete Selection") + + val openSelectionBtt = JButton("Open Selected Project") + + init { + add(newProjectBtt, GridBagConstraints().apply { + insets = Insets(0, 0, 0, 5) + }) + + newProjectBtt.addActionListener { + PaperVisionDialogFactory.displayNewProjectDialog( + SwingUtilities.getWindowAncestor(this) as JFrame, + projectManager.projectTree.projects, + projectManager.projectTree.folders, + ) { projectGroup, projectName -> + projectManager.newProject(projectGroup ?: "", projectName) + + JOptionPane.showConfirmDialog( + SwingUtilities.getWindowAncestor(this), + "Do you wish to open the project that was just created?", + "Project Created", + JOptionPane.YES_NO_OPTION, + JOptionPane.WARNING_MESSAGE + ).takeIf { it == JOptionPane.YES_OPTION }?.run { + projectManager.openProject(projectManager.findProject(projectGroup ?: "", "$projectName.paperproj")!!) + } + } + } + + add(deleteProjectBtt, GridBagConstraints().apply { gridx = 1 }) + + openSelectionBtt.addActionListener { + val selectedProject = projectsJTree.lastSelectedPathComponent + if(selectedProject !is DefaultMutableTreeNode || selectedProject.userObject !is PaperVisionProjectTree.ProjectTreeNode.Project) + return@addActionListener + + projectManager.openProject(selectedProject.userObject as PaperVisionProjectTree.ProjectTreeNode.Project) + } + + deleteProjectBtt.addActionListener { + if(projectsJTree.selectionPaths == null) return@addActionListener + + JOptionPane.showConfirmDialog( + SwingUtilities.getWindowAncestor(this), + "Are you sure you want to delete the selected project(s)?", + "Delete Project", + JOptionPane.YES_NO_OPTION, + JOptionPane.WARNING_MESSAGE + ).takeIf { it == JOptionPane.YES_OPTION }?.let { + val toDelete = mutableListOf() + + for(selection in projectsJTree.selectionPaths!!) { + val selectedProject = selection.lastPathComponent + if(selectedProject !is DefaultMutableTreeNode || selectedProject.userObject !is PaperVisionProjectTree.ProjectTreeNode.Project) + continue + + if(toDelete.contains(selectedProject.userObject)) continue + toDelete.add(selectedProject.userObject as PaperVisionProjectTree.ProjectTreeNode.Project) + } + + PaperVisionDaemon.invokeOnMainLoop { + projectManager.bulkDeleteProjects(*toDelete.toTypedArray()) + } + } + } + + projectsJTree.addTreeSelectionListener { + val selectedProject = projectsJTree.lastSelectedPathComponent + + val state = selectedProject is DefaultMutableTreeNode && + selectedProject.userObject is PaperVisionProjectTree.ProjectTreeNode.Project + + deleteProjectBtt.isEnabled = state + openSelectionBtt.isEnabled = state + } + + add(openSelectionBtt, GridBagConstraints().apply { + gridwidth = 2 + gridy = 1 + + insets = Insets(5, 0, 0, 0) + weightx = 1.0 + + fill = GridBagConstraints.HORIZONTAL + anchor = GridBagConstraints.CENTER + }) + + + deleteProjectBtt.isEnabled = false + openSelectionBtt.isEnabled = false + } + +} \ No newline at end of file diff --git a/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/gui/eocvsim/PaperVisionTabPanel.kt b/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/gui/eocvsim/PaperVisionTabPanel.kt new file mode 100644 index 0000000..fcb4fcf --- /dev/null +++ b/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/gui/eocvsim/PaperVisionTabPanel.kt @@ -0,0 +1,110 @@ +package io.github.deltacv.papervision.plugin.gui.eocvsim + +import io.github.deltacv.papervision.plugin.project.PaperVisionProjectManager +import io.github.deltacv.papervision.plugin.project.PaperVisionProjectTree +import java.awt.GridBagConstraints +import java.awt.GridBagLayout +import java.awt.event.MouseAdapter +import java.awt.event.MouseEvent +import javax.swing.JPanel +import javax.swing.JScrollPane +import javax.swing.JTree +import javax.swing.SwingUtilities +import javax.swing.tree.DefaultMutableTreeNode +import javax.swing.tree.DefaultTreeModel + +class PaperVisionTabPanel( + val projectManager: PaperVisionProjectManager +) : JPanel() { + + val root = DefaultMutableTreeNode("Projects") + + val projectList = JTree(root) + + init { + layout = GridBagLayout() + + projectList.apply { + addMouseListener(object: MouseAdapter() { + override fun mousePressed(e: MouseEvent) { + if(e.clickCount >= 2) { + val node = projectList.lastSelectedPathComponent ?: return + if(node !is DefaultMutableTreeNode) return + + val nodeObject = node.userObject + if(nodeObject !is PaperVisionProjectTree.ProjectTreeNode.Project) return + + projectManager.openProject(nodeObject) + } + } + }) + + cellRenderer = ProjectTreeCellRenderer() + } + + val projectListScroll = JScrollPane() + + projectListScroll.setViewportView(projectList) + + projectManager.onRefresh { + refreshProjectTree() + } + + add(projectListScroll, GridBagConstraints().apply { + gridy = 0 + + weightx = 0.5 + weighty = 1.0 + fill = GridBagConstraints.BOTH + + ipadx = 120 + ipady = 20 + }) + + add(PaperVisionTabButtonsPanel(projectList, projectManager), GridBagConstraints().apply { + gridy = 1 + ipady = 20 + }) + + refreshProjectTree() + } + + fun refreshProjectTree() { + val rootTree = projectManager.projectTree.rootTree.nodes + + SwingUtilities.invokeLater { + root.removeAllChildren() + + if(rootTree.isNotEmpty()) { + fun buildTree(folder: PaperVisionProjectTree.ProjectTreeNode.Folder): DefaultMutableTreeNode { + val folderNode = DefaultMutableTreeNode(folder) + + for (node in folder.nodes) { + when (node) { + is PaperVisionProjectTree.ProjectTreeNode.Project -> { + folderNode.add(DefaultMutableTreeNode(node)) + } + + is PaperVisionProjectTree.ProjectTreeNode.Folder -> { + folderNode.add(buildTree(node)) + } + } + } + + return folderNode + } + + for (node in rootTree) { // skip root "/" from showing up + if (node is PaperVisionProjectTree.ProjectTreeNode.Folder) { + root.add(buildTree(node)) + } else if (node is PaperVisionProjectTree.ProjectTreeNode.Project) { + root.add(DefaultMutableTreeNode(node)) + } + } + } + + (projectList.model as DefaultTreeModel).reload() + projectList.revalidate() + } + } +} \ No newline at end of file diff --git a/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/gui/eocvsim/ProjectTreeCellRenderer.kt b/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/gui/eocvsim/ProjectTreeCellRenderer.kt new file mode 100644 index 0000000..88f0cec --- /dev/null +++ b/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/gui/eocvsim/ProjectTreeCellRenderer.kt @@ -0,0 +1,28 @@ +package io.github.deltacv.papervision.plugin.gui.eocvsim + +import io.github.deltacv.papervision.plugin.project.PaperVisionProjectTree +import java.awt.Component +import javax.swing.JTree +import javax.swing.UIManager +import javax.swing.tree.DefaultMutableTreeNode +import javax.swing.tree.DefaultTreeCellRenderer + +class ProjectTreeCellRenderer: DefaultTreeCellRenderer() { + + override fun getTreeCellRendererComponent(tree: JTree, value: Any, selected: Boolean, expanded: Boolean, leaf: Boolean, row: Int, hasFocus: Boolean): Component { + val component = super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus) + + if(value is DefaultMutableTreeNode) { + val node = value.userObject + + if(node is PaperVisionProjectTree.ProjectTreeNode.Project) { + icon = UIManager.getIcon("FileView.fileIcon") + } else if(node is PaperVisionProjectTree.ProjectTreeNode.Folder) { + icon = UIManager.getIcon("FileView.directoryIcon") + } + } + + return component + } + +} \ No newline at end of file diff --git a/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/gui/eocvsim/dialog/NewProjectPanel.kt b/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/gui/eocvsim/dialog/NewProjectPanel.kt new file mode 100644 index 0000000..6346d8c --- /dev/null +++ b/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/gui/eocvsim/dialog/NewProjectPanel.kt @@ -0,0 +1,136 @@ +package io.github.deltacv.papervision.plugin.gui.eocvsim.dialog + +import java.awt.Dimension +import java.awt.GridBagConstraints +import java.awt.GridBagLayout +import java.awt.Insets +import java.awt.event.KeyEvent +import java.nio.file.FileAlreadyExistsException +import javax.swing.* +import javax.swing.border.EmptyBorder + +class NewProjectPanel( + projects: List, + groups: List, + newProjectCallback: (group: String?, name: String) -> Unit +) : JPanel() { + + val projectNameField = JTextField() + val groupComboBox = JComboBox() + + init { + preferredSize = Dimension(400, 100) + layout = GridBagLayout() + + border = EmptyBorder(5, 15, 15, 15) + + add(JLabel("Project Name:").apply + { + horizontalAlignment = JLabel.RIGHT + border = EmptyBorder(0,0,0,10) + }, GridBagConstraints().apply { + gridx = 0 + gridy = 0 + insets = Insets(10, 0, 0, 0) + }) + + add(projectNameField, GridBagConstraints().apply { + gridx = 1 + gridy = 0 + weightx = 1.0 + fill = GridBagConstraints.HORIZONTAL + insets = Insets(10, 0, 0, 0) + }) + + add(JLabel("Group:").apply { + horizontalAlignment = JLabel.RIGHT + border = EmptyBorder(0,0,0,10) + }, GridBagConstraints().apply { + gridx = 0 + gridy = 1 + + fill = GridBagConstraints.HORIZONTAL + insets = Insets(10, 0, 0, 0) + }) + + groupComboBox.addItem("None") + + for(group in groups) { + if(group.isBlank() || group == "/") continue + groupComboBox.addItem(group) + } + + groupComboBox.selectedIndex = 0 + + add(groupComboBox, GridBagConstraints().apply { + gridx = 1 + gridy = 1 + weightx = 1.0 + fill = GridBagConstraints.HORIZONTAL + insets = Insets(10, 0, 0, 0) + }) + + add(JButton("New Group").apply { + addActionListener { + val newGroup = JOptionPane.showInputDialog(this@NewProjectPanel, "Enter new group name:") ?: return@addActionListener + (groupComboBox.model as DefaultComboBoxModel).addElement(newGroup) + groupComboBox.selectedItem = newGroup + } + }, GridBagConstraints().apply { + gridx = 2 + gridy = 1 + insets = Insets(10, 5, 0, 0) + }) + + val buttonsPanel = JPanel().apply { + border = EmptyBorder(5, 0, 0, 0) + + layout = BoxLayout(this, BoxLayout.X_AXIS) + add(Box.createHorizontalGlue()) + + add(JButton("Create").apply { + addActionListener { + if(!projects.contains(projectNameField.text)) { + if(projectNameField.text.trim().isNotBlank()) { + val projectName = groupComboBox.selectedItem!!.toString() + + try { + newProjectCallback( + if (projectName == "None") null else projectName, + projectNameField.text + ) + } catch(e: FileAlreadyExistsException) { + JOptionPane.showMessageDialog(this, "Project already exists") + return@addActionListener + } + + SwingUtilities.getWindowAncestor(this).isVisible = false + } else { + JOptionPane.showMessageDialog(this, "Please enter a valid name") + } + } else { + JOptionPane.showMessageDialog(this, "Project already exists") + } + } + }) + + add(Box.createRigidArea(Dimension(10, 0))) + + add(JButton("Cancel").apply { + addActionListener { SwingUtilities.getWindowAncestor(this).isVisible = false } + }) + + add(Box.createHorizontalGlue()) + } + + add(buttonsPanel, GridBagConstraints().apply { + gridx = 0 + gridy = 2 + gridwidth = 3 + + fill = GridBagConstraints.HORIZONTAL + anchor = GridBagConstraints.CENTER + }) + } + +} \ No newline at end of file diff --git a/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/gui/eocvsim/dialog/PaperVisionDialogFactory.kt b/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/gui/eocvsim/dialog/PaperVisionDialogFactory.kt new file mode 100644 index 0000000..d43230e --- /dev/null +++ b/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/gui/eocvsim/dialog/PaperVisionDialogFactory.kt @@ -0,0 +1,33 @@ +package io.github.deltacv.papervision.plugin.gui.eocvsim.dialog + +import io.github.deltacv.papervision.plugin.project.recovery.RecoveredProject +import javax.swing.JDialog +import javax.swing.JFrame +import javax.swing.JPanel +import javax.swing.SwingUtilities + +object PaperVisionDialogFactory { + + fun displayNewProjectDialog(parent: JFrame, projects: List, groups: List, callback: (String?, String) -> Unit) { + val panel = NewProjectPanel(projects, groups, callback) + displayDialog("New Project", panel, parent) + } + + fun displayProjectRecoveryDialog(parent: JFrame, recoveredProjects: List, callback: (List) -> Unit) { + val panel = ProjectRecoveryPanel(recoveredProjects, callback) + displayDialog("Recover PaperVision Projects", panel, parent) + } + + private fun displayDialog(title: String, panel: JPanel, parent: JFrame) { + SwingUtilities.invokeLater { + val dialog = JDialog(parent, title, true) + + dialog.add(panel) + dialog.pack() + dialog.setLocationRelativeTo(null) + + dialog.isVisible = true + } + } + +} \ No newline at end of file diff --git a/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/gui/eocvsim/dialog/ProjectRecoveryPanel.kt b/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/gui/eocvsim/dialog/ProjectRecoveryPanel.kt new file mode 100644 index 0000000..e025448 --- /dev/null +++ b/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/gui/eocvsim/dialog/ProjectRecoveryPanel.kt @@ -0,0 +1,88 @@ +package io.github.deltacv.papervision.plugin.gui.eocvsim.dialog + +import io.github.deltacv.papervision.plugin.project.recovery.RecoveredProject +import java.awt.BorderLayout +import java.awt.Dimension +import java.time.Instant +import java.util.* +import javax.swing.* +import javax.swing.table.DefaultTableModel + +class ProjectRecoveryPanel( + recoveredProjects: List, // List of (Project Name, Date) + callback: (List) -> Unit +) : JPanel() { + + init { + layout = BorderLayout(10, 10) + preferredSize = Dimension(400, 250) + + // Add a descriptive label at the top + val label = JLabel("

PaperVision detected unsaved projects from a previous session.
You can choose which projects to recover below.

") + label.horizontalAlignment = SwingConstants.CENTER // Center align the text + + add(label, BorderLayout.NORTH) + + // Data for the table, including the checkbox column initialized to true + val data = recoveredProjects.map { arrayOf(it.originalProjectPath, Date.from(Instant.ofEpochMilli(it.date)), true) }.toTypedArray() + val model = DefaultTableModel(data, arrayOf("Project Name", "Date", "Recover")) + + // Create the table with the custom model + val table = object: JTable(model) { + override fun getColumnClass(column: Int) = when (column) { + 2 -> java.lang.Boolean::class.java + else -> String::class.java + } + + override fun isCellEditable(row: Int, column: Int) = column == 2 + } + val scrollPane = JScrollPane(table) + scrollPane.border = BorderFactory.createEmptyBorder(-5, 10, 0, 10) + add(scrollPane, BorderLayout.CENTER) + + val buttonsPanel = JPanel().apply { + layout = BoxLayout(this, BoxLayout.X_AXIS) + border = BorderFactory.createEmptyBorder(0, 0, 10, 0) + + add(Box.createHorizontalGlue()) + + add(JButton("Recover Selected").apply { + addActionListener { + val selectedProjects = mutableListOf() + + for (i in 0 until model.rowCount) { + val recover = model.getValueAt(i, 2) as Boolean + if (recover) { + selectedProjects.add(recoveredProjects[i]) + } + } + + callback(selectedProjects.toList()) + SwingUtilities.getWindowAncestor(this@ProjectRecoveryPanel).isVisible = false + } + }) + + add(Box.createRigidArea(Dimension(10, 0))) + + add(JButton("Discard All").apply { + addActionListener { + JOptionPane.showConfirmDialog( + this@ProjectRecoveryPanel, + "Are you sure you want to discard all recovered projects?", + "Discard All Projects", + JOptionPane.YES_NO_OPTION, + JOptionPane.WARNING_MESSAGE + ).takeIf { it == JOptionPane.YES_OPTION }?.run { + callback(emptyList()) + SwingUtilities.getWindowAncestor(this@ProjectRecoveryPanel).isVisible = false + } + } + }) + + add(Box.createHorizontalGlue()) + } + + add(buttonsPanel, BorderLayout.SOUTH) + } + +} \ No newline at end of file diff --git a/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/project/PaperVisionProjectManager.kt b/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/project/PaperVisionProjectManager.kt new file mode 100644 index 0000000..69c7fcf --- /dev/null +++ b/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/project/PaperVisionProjectManager.kt @@ -0,0 +1,288 @@ +package io.github.deltacv.papervision.plugin.project + +import com.github.serivesmejia.eocvsim.util.extension.plus +import com.github.serivesmejia.eocvsim.util.io.EOCVSimFolder +import com.github.serivesmejia.eocvsim.util.loggerForThis +import com.google.gson.JsonElement +import com.google.gson.JsonObject +import io.github.deltacv.eocvsim.sandbox.nio.SandboxFileSystem +import io.github.deltacv.papervision.plugin.PaperVisionDaemon +import io.github.deltacv.papervision.plugin.project.recovery.RecoveredProject +import io.github.deltacv.papervision.plugin.project.recovery.RecoveryDaemonProcessManager +import io.github.deltacv.papervision.plugin.project.recovery.RecoveryData +import io.github.deltacv.papervision.util.event.EventHandler +import io.github.deltacv.papervision.util.hexString +import java.io.File +import java.io.FileNotFoundException +import java.nio.charset.StandardCharsets +import java.nio.file.Path +import java.time.Instant +import kotlin.io.path.exists +import kotlin.io.path.pathString + +class PaperVisionProjectManager( + pluginJarFile: File, + val fileSystem: SandboxFileSystem +) { + + companion object { + val recoveryFolder = EOCVSimFolder + File.separator + "recovery" + } + + val root = fileSystem.getPath("") + + var projectTree = PaperVisionProjectTree(root) + private set + + var projects = listOf() + private set + + var currentProject: PaperVisionProjectTree.ProjectTreeNode.Project? = null + private set + + fun paperVisionProjectFrom( + project: PaperVisionProjectTree.ProjectTreeNode.Project, + tree: JsonElement = PaperVisionDaemon.currentProjectJsonTree() + ) = PaperVisionProject( + Instant.now().toEpochMilli(), + findProjectFolderPath(project)!!.pathString, + project.name, + tree + ) + + val recoveryDaemonProcessManager = RecoveryDaemonProcessManager(pluginJarFile).apply { + start() + } + + val logger by loggerForThis() + + val recoveredProjects = mutableListOf().apply { + if(recoveryFolder.exists()) { + for(file in recoveryFolder.listFiles() ?: arrayOf()) { + if(file.extension == "recoverypaperproj") { + val recoveredProject = RecoveredProject.fromJson(file.readText()) + val projectPath = fileSystem.getPath(recoveredProject.originalProjectPath) + + if(projectPath.exists()){ + val project = PaperVisionProject.fromJson(String(fileSystem.readAllBytes(projectPath), StandardCharsets.UTF_8)) + + logger.info("Found recovered project ${recoveredProject.originalProjectPath} from ${recoveredProject.date} compared to ${project.timestamp}") + + if(recoveredProject.date > project.timestamp) { + add(recoveredProject) + logger.info("Asking for recovered project ${recoveredProject.originalProjectPath} from ${recoveredProject.date}") + } + } else { + file.delete() + logger.info("Discarded phased recovery file for ${recoveredProject.originalProjectPath}") + } + } + } + } + }.toList() + + val onRefresh = EventHandler("PaperVisionProjectManager-onRefresh") + + fun init() { + PaperVisionDaemon.attachToEditorChange { + if(currentProject != null) { + PaperVisionDaemon.invokeOnMainLoop { + sendRecoveryProject(currentProject!!) + } + } + } + } + + fun refresh() { + projectTree = PaperVisionProjectTree(root) + projects = recursiveSearchProjects(projectTree.rootTree) + + onRefresh.run() + } + + private fun recursiveSearchProjects(root: PaperVisionProjectTree.ProjectTreeNode.Folder): List { + val list = mutableListOf() + + for(node in root.nodes) { + when(node) { + is PaperVisionProjectTree.ProjectTreeNode.Folder -> { + list.addAll(recursiveSearchProjects(node)) + } + is PaperVisionProjectTree.ProjectTreeNode.Project -> { + list.add(node) + } + } + } + + return list + } + + fun newProject(path: String, name: String, jsonElement: JsonElement? = null, appendExtension: Boolean = true) { + val projectPath = fileSystem.getPath("/").resolve(path) + fileSystem.createDirectories(projectPath) + + val projectFile = projectPath.resolve(if(appendExtension) "$name.paperproj" else name) + fileSystem.createFile(projectFile) + fileSystem.write(projectFile, PaperVisionProject( + Instant.now().toEpochMilli(), + path, name, + jsonElement ?: JsonObject() + ).toJson().toByteArray(StandardCharsets.UTF_8)) + + refresh() + } + + fun deleteProject(project: PaperVisionProjectTree.ProjectTreeNode.Project) { + bulkDeleteProjects(project) + } + + fun bulkDeleteProjects(vararg projects: PaperVisionProjectTree.ProjectTreeNode.Project) { + for(project in projects) { + val path = findProjectPath(project) ?: throw FileNotFoundException("Project $project not found in tree") + fileSystem.delete(path) + } + + refresh() + } + + fun openProject(project: PaperVisionProjectTree.ProjectTreeNode.Project) { + val path = findProjectPath(project) ?: throw FileNotFoundException("Project $project not found in tree") + + logger.info("Opening ${path.pathString}") + + PaperVisionDaemon.openProject( + PaperVisionProject.fromJson( + String(fileSystem.readAllBytes(path), StandardCharsets.UTF_8) + ).json + ) + + currentProject = project + } + + fun saveCurrentProject() { + val project = currentProject ?: return + val path = findProjectPath(project) ?: return + + logger.info("Saving ${path.pathString}") + + fileSystem.write(path, paperVisionProjectFrom(currentProject!!).toJson().toByteArray(StandardCharsets.UTF_8)) + } + + fun findProject(path: String, name: String): PaperVisionProjectTree.ProjectTreeNode.Project? { + val paths = path.split("/").filter { it.isNotBlank() } + + // Start at the root of the project tree + var currentNode: PaperVisionProjectTree.ProjectTreeNode = projectTree.rootTree + + if(path.isNotBlank() || path != "/") { + // Traverse the path segments + for (segment in paths) { + if (currentNode is PaperVisionProjectTree.ProjectTreeNode.Folder) { + // Find the subfolder with the matching segment name + val nextNode = currentNode.nodes.find { it.name == segment } + if (nextNode != null) { + currentNode = nextNode + } else { + // If the segment is not found, return null + return null + } + } else { + // If we encounter a non-folder node before the path is fully traversed, return null + return null + } + } + } + + // At the end of the path, check for a project with the given name + if (currentNode is PaperVisionProjectTree.ProjectTreeNode.Folder) { + return currentNode.nodes.find { + it is PaperVisionProjectTree.ProjectTreeNode.Project && it.name == name + } as? PaperVisionProjectTree.ProjectTreeNode.Project + } + + // If the final node is not a folder or doesn't contain the project, return null + return null + } + + fun findProjectFolderPath(project: PaperVisionProjectTree.ProjectTreeNode.Project) = + findProjectPath(project)?.toAbsolutePath()?.parent + + fun findProjectPath(project: PaperVisionProjectTree.ProjectTreeNode.Project) = findProjectPath(project, projectTree.rootTree, root) + + private fun findProjectPath( + targetProject: PaperVisionProjectTree.ProjectTreeNode.Project, + currentNode: PaperVisionProjectTree.ProjectTreeNode, + currentPath: Path + ): Path? { + return when (currentNode) { + is PaperVisionProjectTree.ProjectTreeNode.Folder -> { + for (node in currentNode.nodes) { + val path = findProjectPath(targetProject, node, currentPath.resolve(currentNode.name)) + if (path != null) { + return path + } + } + null + } + is PaperVisionProjectTree.ProjectTreeNode.Project -> { + if (currentNode == targetProject) { + return currentPath.resolve(currentNode.name) + } + null + } + } + } + + fun sendRecoveryProject(projectNode: PaperVisionProjectTree.ProjectTreeNode.Project) { + val projectPath = findProjectPath(projectNode)?.pathString ?: return + val hex = projectPath.hexString + + logger.info("Sending recovery for $projectPath") + + recoveryDaemonProcessManager.sendRecoveryData( + RecoveryData( + recoveryFolder.path, + "${hex}.recoverypaperproj", + RecoveredProject(projectPath, System.currentTimeMillis(), hex, paperVisionProjectFrom(projectNode)) + ) + ) + } + + fun discardCurrentRecovery() { + if(currentProject == null) return + val path = findProjectPath(currentProject!!)?.pathString ?: return + + logger.info("Discarding recovery for $path") + + val recoveryFile = recoveryFolder.resolve("${path.hexString}.recoverypaperproj") + recoveryFile.delete() + } + + fun recoverProject(recoveredProject: RecoveredProject) { + val projectPath = fileSystem.getPath(recoveredProject.originalProjectPath) + + if(projectPath.exists()) { + fileSystem.delete(projectPath) + } + + newProject( + recoveredProject.project.path, + recoveredProject.project.name, + jsonElement = recoveredProject.project.json, + appendExtension = false + ) + + logger.info("Recovered project ${recoveredProject.originalProjectPath} from ${recoveredProject.date}") + + refresh() + } + + fun deleteAllRecoveredProjects() { + for(file in recoveryFolder.listFiles() ?: arrayOf()) { + if(file.extension == "recoverypaperproj") { + file.delete() + } + } + } + +} \ No newline at end of file diff --git a/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/project/PaperVisionProjectTree.kt b/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/project/PaperVisionProjectTree.kt new file mode 100644 index 0000000..29d63fe --- /dev/null +++ b/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/project/PaperVisionProjectTree.kt @@ -0,0 +1,101 @@ +package io.github.deltacv.papervision.plugin.project + +import java.nio.file.Path +import kotlin.io.path.isDirectory +import kotlin.io.path.name + +class PaperVisionProjectTree(val rootPath: Path) { + + val rootTree = scanDeep(rootPath) + + // returns a list of all projects in the tree by their path string + val projects by lazy { getAllProjects(rootTree) } + + val folders by lazy { getAllFolders(rootTree) } + + private fun getAllProjects(node: ProjectTreeNode.Folder): List { + val projectList = mutableListOf() + + for (child in node.nodes) { + when (child) { + is ProjectTreeNode.Project -> projectList.add(child.name.trim()) + is ProjectTreeNode.Folder -> projectList.addAll(getAllProjects(child)) + } + } + + return projectList + } + + private fun getAllFolders(node: ProjectTreeNode.Folder): List { + val folderList = mutableListOf() + + // Check if the current folder contains any projects + if (node.nodes.any { it is ProjectTreeNode.Project }) { + if(node.name.isNotBlank()) + folderList.add(node.name) + } + + // Recursively check subfolders + for (child in node.nodes) { + if (child is ProjectTreeNode.Folder) { + folderList.addAll(getAllFolders(child)) + } + } + + return folderList + } + + fun print() { + printTree(rootTree, 0) + } + + fun get(vararg path: String) = get(rootTree, path.toList()) + + private fun get(currentNode: ProjectTreeNode.Folder, path: List): ProjectTreeNode? { + if (path.isEmpty()) return currentNode + + val nextNode = currentNode.nodes.find { it.name == path[0] } + return when { + nextNode is ProjectTreeNode.Folder && path.size > 1 -> get(nextNode, path.drop(1)) + nextNode != null && path.size == 1 -> nextNode + else -> null + } + } + + private fun printTree(tree: ProjectTreeNode.Folder, depth: Int) { + for (node in tree.nodes) { + when (node) { + is ProjectTreeNode.Project -> { + println(" ".repeat(depth) + node.name) + } + is ProjectTreeNode.Folder -> { + println(" ".repeat(depth) + node.name) + printTree(node, depth + 1) + } + } + } + } + + private fun scanDeep(path: Path): ProjectTreeNode.Folder { + val tree = mutableListOf() + + rootPath.fileSystem.provider().newDirectoryStream(path) { true }.use { stream -> + for (entry in stream) { + if (entry.isDirectory()) { + tree.add(scanDeep(entry)) + } else { + tree.add(ProjectTreeNode.Project(entry.name)) + } + } + } + + return ProjectTreeNode.Folder(path.name, tree) + } + + sealed class ProjectTreeNode(val name: String) { + class Project(name: String) : ProjectTreeNode(name) + class Folder(name: String, val nodes: List) : ProjectTreeNode(name) + + override fun toString() = name + } +} \ No newline at end of file diff --git a/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/project/recovery/RecoveryDaemonProcessManager.kt b/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/project/recovery/RecoveryDaemonProcessManager.kt new file mode 100644 index 0000000..555a94b --- /dev/null +++ b/EOCVSimPlugin/src/main/kotlin/io/github/deltacv/papervision/plugin/project/recovery/RecoveryDaemonProcessManager.kt @@ -0,0 +1,72 @@ +package io.github.deltacv.papervision.plugin.project.recovery + +import com.github.serivesmejia.eocvsim.util.JavaProcess +import com.github.serivesmejia.eocvsim.util.loggerForThis +import org.java_websocket.WebSocket +import org.java_websocket.handshake.ClientHandshake +import org.java_websocket.server.WebSocketServer +import java.io.File +import java.lang.Exception +import java.net.InetSocketAddress +import java.util.concurrent.Executors + +class RecoveryDaemonProcessManager( + val pluginJarFile: File +) { + + val executor = Executors.newFixedThreadPool(2) + + private val server = WsServer(this) + + val logger by loggerForThis() + + fun start() { + server.start() + } + + private fun submitProcess(port: Int) { + executor.submit { + logger.info("Starting project recovery daemon process") + + val exit = JavaProcess.execClasspath( + RecoveryDaemonClient::class.java, + pluginJarFile.path, + port.toString() + ) + + logger.warn("Project recovery daemon process exited with code $exit") + } + } + + fun sendRecoveryData(recoveryData: RecoveryData) { + for(conn in server.connections) { + conn.send(RecoveryData.serialize(recoveryData)) + } + } + + private class WsServer( + val manager: RecoveryDaemonProcessManager + ) : WebSocketServer(InetSocketAddress(0)) { + val logger by loggerForThis() + + override fun onOpen(ws: WebSocket, p1: ClientHandshake?) { + logger.info("Client connected ${ws.localSocketAddress}") + } + + override fun onClose(ws: WebSocket, p1: Int, p2: String?, p3: Boolean) { + logger.info("Client disconnected ${ws.localSocketAddress}") + } + + override fun onMessage(p0: WebSocket?, p1: String?) { } + + override fun onError(p0: WebSocket?, p1: Exception?) { + logger.error("Error in recovery daemon server", p1) + } + + override fun onStart() { + logger.info("Recovery daemon server started in port $port") + manager.submitProcess(port) + } + } + +} \ No newline at end of file diff --git a/LwjglPlatform/src/main/kotlin/io/github/deltacv/papervision/platform/lwjgl/PaperVisionApp.kt b/LwjglPlatform/src/main/kotlin/io/github/deltacv/papervision/platform/lwjgl/PaperVisionApp.kt index ea1b5d2..ea10579 100644 --- a/LwjglPlatform/src/main/kotlin/io/github/deltacv/papervision/platform/lwjgl/PaperVisionApp.kt +++ b/LwjglPlatform/src/main/kotlin/io/github/deltacv/papervision/platform/lwjgl/PaperVisionApp.kt @@ -13,7 +13,8 @@ import org.lwjgl.glfw.GLFWKeyCallback class PaperVisionApp( val daemon: Boolean, - val bridge: PaperVisionEngineBridge? = null + val bridge: PaperVisionEngineBridge? = null, + val windowCloseListener: (() -> Boolean)? = null ) : Window() { val setup = platformSetup("LWJGL") { @@ -30,6 +31,7 @@ class PaperVisionApp( fun start() { val config = Configuration().apply { title = "" + isFullScreen = true } init(config, daemon) @@ -67,12 +69,16 @@ class PaperVisionApp( } override fun windowCloseAction(): Boolean { + val shouldHide = windowCloseListener?.invoke() ?: true + if(daemon) { - glfwWindow.visible = false + if(shouldHide) { + glfwWindow.visible = false + } glfwSetWindowShouldClose(handle, false) return false } else { - return true + return shouldHide } } diff --git a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/PaperVision.kt b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/PaperVision.kt index ad10687..debf57b 100644 --- a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/PaperVision.kt +++ b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/PaperVision.kt @@ -34,11 +34,10 @@ import io.github.deltacv.papervision.id.IdElementContainer import io.github.deltacv.papervision.id.IdElementContainerStack import io.github.deltacv.papervision.id.NoneIdElement import io.github.deltacv.papervision.io.KeyManager -import io.github.deltacv.papervision.io.resourceToString import io.github.deltacv.papervision.node.Link import io.github.deltacv.papervision.node.Node import io.github.deltacv.papervision.platform.* -import io.github.deltacv.papervision.serialization.ev.EasyVisionSerializer +import io.github.deltacv.papervision.serialization.PaperVisionSerializer import io.github.deltacv.papervision.util.event.EventHandler import io.github.deltacv.papervision.util.loggerForThis import io.github.deltacv.mai18n.Language @@ -46,9 +45,9 @@ import io.github.deltacv.papervision.engine.bridge.NoOpPaperVisionEngineBridge import io.github.deltacv.papervision.engine.client.PaperVisionEngineClient import io.github.deltacv.papervision.engine.previz.ClientPrevizManager import io.github.deltacv.papervision.gui.style.CurrentStyles +import io.github.deltacv.papervision.gui.util.Popup.Companion.WARN import io.github.deltacv.papervision.io.TextureProcessorQueue import io.github.deltacv.papervision.node.NodeRegistry -import org.w3c.dom.Text class PaperVision( private val setupCall: PlatformSetupCallback @@ -97,6 +96,11 @@ class PaperVision( val nodes = IdElementContainer>() val attributes = IdElementContainer() val links = IdElementContainer() + val windows = IdElementContainer() + + val popups = IdElementContainer().apply { + reserveId(WARN) + } lateinit var engineClient: PaperVisionEngineClient @@ -110,6 +114,8 @@ class PaperVision( IdElementContainerStack.threadStack.push(nodes) IdElementContainerStack.threadStack.push(attributes) IdElementContainerStack.threadStack.push(links) + IdElementContainerStack.threadStack.push(windows) + IdElementContainerStack.threadStack.push(popups) logger.info("Starting PaperVision...") @@ -124,7 +130,7 @@ class PaperVision( textureProcessorQueue.subscribeTo(onUpdate) engineClient = PaperVisionEngineClient(setup.engineBridge ?: NoOpPaperVisionEngineBridge) - previzManager = ClientPrevizManager(320, 240, codeGenManager, textureProcessorQueue, engineClient) + previzManager = ClientPrevizManager(160, 120, codeGenManager, textureProcessorQueue, engineClient) engineClient.connect() @@ -132,8 +138,6 @@ class PaperVision( ImGui.getIO().iniFilename = null ImGui.getIO().logFilename = null - EasyVisionSerializer.deserializeAndApply(resourceToString("/testproj.json"), this) - // initializing fonts right after the imgui context is created // we can't create fonts mid-frame so that's kind of a problem defaultFont = fontManager.makeFont("/fonts/Calcutta-Regular.otf", 13f) @@ -147,6 +151,8 @@ class PaperVision( IdElementContainerStack.threadStack.pop>() IdElementContainerStack.threadStack.pop() IdElementContainerStack.threadStack.pop() + IdElementContainerStack.threadStack.pop() + IdElementContainerStack.threadStack.pop() } fun firstProcess() { @@ -162,6 +168,8 @@ class PaperVision( IdElementContainerStack.threadStack.push(nodes) IdElementContainerStack.threadStack.push(attributes) IdElementContainerStack.threadStack.push(links) + IdElementContainerStack.threadStack.push(windows) + IdElementContainerStack.threadStack.push(popups) onUpdate.run() @@ -174,17 +182,17 @@ class PaperVision( ImGui.pushFont(defaultFont.imfont) - for(window in Window.windows.inmutable) { + for(window in windows.inmutable) { window.draw() } - for(tooltip in Popup.popups.inmutable) { + for(tooltip in popups.inmutable) { tooltip.draw() } ImGui.popFont() if(keyManager.pressed(keyManager.keys.ArrowUp)) { - println(EasyVisionSerializer.serialize(nodes.elements, links.elements)) + println(PaperVisionSerializer.serialize(nodes.elements, links.elements)) } keyManager.update() @@ -194,6 +202,8 @@ class PaperVision( IdElementContainerStack.threadStack.pop>() IdElementContainerStack.threadStack.pop() IdElementContainerStack.threadStack.pop() + IdElementContainerStack.threadStack.pop() + IdElementContainerStack.threadStack.pop() } fun destroy() { diff --git a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/attribute/Attribute.kt b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/attribute/Attribute.kt index b169b65..0094e20 100644 --- a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/attribute/Attribute.kt +++ b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/attribute/Attribute.kt @@ -10,10 +10,11 @@ import io.github.deltacv.papervision.id.DrawableIdElementBase import io.github.deltacv.papervision.id.IdElementContainerStack import io.github.deltacv.papervision.node.Link import io.github.deltacv.papervision.node.Node -import io.github.deltacv.papervision.serialization.ev.AttributeSerializationData +import io.github.deltacv.papervision.serialization.AttributeSerializationData import io.github.deltacv.papervision.serialization.data.DataSerializable -import io.github.deltacv.papervision.serialization.ev.BasicAttribData +import io.github.deltacv.papervision.serialization.BasicAttribData import io.github.deltacv.papervision.util.event.EventHandler +import java.util.concurrent.ArrayBlockingQueue import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind import kotlin.contracts.contract @@ -51,11 +52,17 @@ abstract class Attribute : DrawableIdElementBase(), DataSerializable< var wasLastDrawCancelled = false private set - val onChange = EventHandler("OnChange-${this::class.simpleName}") + val onChange = EventHandler("OnChange-${this::class.simpleName}").apply { + doPersistent { + changeQueue.add(true) + } + } val onDelete = EventHandler("OnDelete-${this::class.simpleName}") val position = ImVec2() + internal val changeQueue = ArrayBlockingQueue(50) + abstract fun drawAttribute() fun drawHere() { @@ -70,6 +77,10 @@ abstract class Attribute : DrawableIdElementBase(), DataSerializable< return } + if(changeQueue.remainingCapacity() <= 1) { + changeQueue.poll() + } + if(wasLastDrawCancelled) { wasLastDrawCancelled = false } @@ -77,6 +88,7 @@ abstract class Attribute : DrawableIdElementBase(), DataSerializable< if(isFirstDraw) { enable() isFirstDraw = false + onChange.run() } if(parentNode.showAttributesCircles && showAttributesCircles) { @@ -186,7 +198,10 @@ abstract class Attribute : DrawableIdElementBase(), DataSerializable< fun rebuildPreviz() { if(!isOnEditor) return - parentNode.editor.paperVision.previzManager.refreshPreviz() + // schedule for a frame later + editor.paperVision.onUpdate.doOnce { + parentNode.editor.paperVision.previzManager.refreshPreviz() + } } protected fun getOutputValue(current: CodeGen.Current) = parentNode.getOutputValueOf(current, this) @@ -210,6 +225,8 @@ abstract class Attribute : DrawableIdElementBase(), DataSerializable< return data } + override fun pollChange() = changeQueue.poll() ?: false + override fun toString() = "Attribute(type=${this::class.java.typeName}, id=$id)" } diff --git a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/attribute/math/IntAttribute.kt b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/attribute/math/IntAttribute.kt index 379bcc7..9c9b357 100644 --- a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/attribute/math/IntAttribute.kt +++ b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/attribute/math/IntAttribute.kt @@ -9,7 +9,7 @@ import io.github.deltacv.papervision.attribute.Type import io.github.deltacv.papervision.attribute.TypedAttribute import io.github.deltacv.papervision.codegen.CodeGen import io.github.deltacv.papervision.codegen.GenValue -import io.github.deltacv.papervision.serialization.ev.AttributeSerializationData +import io.github.deltacv.papervision.serialization.AttributeSerializationData import io.github.deltacv.papervision.util.Range2i class IntAttribute( diff --git a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/attribute/misc/ListAttribute.kt b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/attribute/misc/ListAttribute.kt index 09af4d5..bb63841 100644 --- a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/attribute/misc/ListAttribute.kt +++ b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/attribute/misc/ListAttribute.kt @@ -12,15 +12,14 @@ import io.github.deltacv.papervision.gui.style.rgbaColor import io.github.deltacv.papervision.serialization.data.DataSerializable import io.github.deltacv.papervision.serialization.data.adapter.dataSerializableToJsonObject import io.github.deltacv.papervision.serialization.data.adapter.jsonObjectToDataSerializable -import io.github.deltacv.papervision.serialization.ev.AttributeSerializationData +import io.github.deltacv.papervision.serialization.AttributeSerializationData open class ListAttribute( override val mode: AttributeMode, val elementType: Type, override var variableName: String? = null, length: Int? = null, - val allowAddOrDelete: Boolean = true, - var sameLine: Boolean = false + val allowAddOrDelete: Boolean = true ) : TypedAttribute(Companion) { companion object : Type { diff --git a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/attribute/misc/StringAttribute.kt b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/attribute/misc/StringAttribute.kt index 616e523..d2eb7ef 100644 --- a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/attribute/misc/StringAttribute.kt +++ b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/attribute/misc/StringAttribute.kt @@ -8,7 +8,7 @@ import io.github.deltacv.papervision.attribute.Type import io.github.deltacv.papervision.attribute.TypedAttribute import io.github.deltacv.papervision.codegen.CodeGen import io.github.deltacv.papervision.codegen.GenValue -import io.github.deltacv.papervision.serialization.ev.AttributeSerializationData +import io.github.deltacv.papervision.serialization.AttributeSerializationData class StringAttribute( override val mode: AttributeMode, diff --git a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/attribute/vision/MatAttribute.kt b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/attribute/vision/MatAttribute.kt index 42dc7b0..2c9a736 100644 --- a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/attribute/vision/MatAttribute.kt +++ b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/attribute/vision/MatAttribute.kt @@ -10,6 +10,7 @@ import io.github.deltacv.papervision.gui.eocvsim.ImageDisplayNode import io.github.deltacv.papervision.gui.style.rgbaColor import io.github.deltacv.papervision.gui.util.ExtraWidgets import io.github.deltacv.papervision.serialization.data.SerializeData +import io.github.deltacv.papervision.serialization.data.SerializeIgnore class MatAttribute( override val mode: AttributeMode, @@ -26,14 +27,17 @@ class MatAttribute( override fun new(mode: AttributeMode, variableName: String) = MatAttribute(mode, variableName) } - @SerializeData + @SerializeIgnore var isPrevizEnabled = false private set + private var prevIsPrevizEnabled = false + @SerializeIgnore var wasPrevizJustEnabled = false private set + @SerializeIgnore var displayWindow: ImageDisplayNode? = null private set diff --git a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/attribute/vision/structs/ScalarAttribute.kt b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/attribute/vision/structs/ScalarAttribute.kt index ab535f3..47b304d 100644 --- a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/attribute/vision/structs/ScalarAttribute.kt +++ b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/attribute/vision/structs/ScalarAttribute.kt @@ -16,7 +16,7 @@ class ScalarAttribute( mode: AttributeMode, color: ColorSpace, variableName: String? = null -) : ListAttribute(mode, IntAttribute, variableName, color.channels, sameLine = true) { +) : ListAttribute(mode, IntAttribute, variableName, color.channels) { var color = color set(value) { diff --git a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/attribute/vision/structs/ScalarRangeAttribute.kt b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/attribute/vision/structs/ScalarRangeAttribute.kt index 214ddf1..2d5d9f5 100644 --- a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/attribute/vision/structs/ScalarRangeAttribute.kt +++ b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/attribute/vision/structs/ScalarRangeAttribute.kt @@ -16,7 +16,7 @@ class ScalarRangeAttribute( mode: AttributeMode, color: ColorSpace, variableName: String? = null -) : ListAttribute(mode, RangeAttribute, variableName, color.channels, sameLine = true) { +) : ListAttribute(mode, RangeAttribute, variableName, color.channels) { var color = color set(value) { diff --git a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/codegen/CodeGenManager.kt b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/codegen/CodeGenManager.kt index 30031dd..9607e66 100644 --- a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/codegen/CodeGenManager.kt +++ b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/codegen/CodeGenManager.kt @@ -5,6 +5,7 @@ import io.github.deltacv.papervision.codegen.language.Language import io.github.deltacv.papervision.codegen.language.jvm.JavaLanguage import io.github.deltacv.papervision.exception.AttributeGenException import io.github.deltacv.papervision.gui.util.Popup +import io.github.deltacv.papervision.id.IdElementContainerStack import io.github.deltacv.papervision.util.loggerForThis class CodeGenManager(val paperVision: PaperVision) { @@ -16,7 +17,7 @@ class CodeGenManager(val paperVision: PaperVision) { language: Language = JavaLanguage, isForPreviz: Boolean = false ): String { - for(popup in Popup.popups.inmutable) { + for(popup in IdElementContainerStack.threadStack.peekNonNull().inmutable) { if(popup.label == "Gen-Error") { popup.delete() } diff --git a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/codegen/dsl/CodeGenContext.kt b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/codegen/dsl/CodeGenContext.kt index ed59e03..34411c3 100644 --- a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/codegen/dsl/CodeGenContext.kt +++ b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/codegen/dsl/CodeGenContext.kt @@ -26,11 +26,11 @@ class CodeGenContext(val codeGen: CodeGen) : LanguageContext(codeGen.language) { fun public(variable: Variable, label: String? = null) = codeGen.classStartScope.instanceVariable(Visibility.PUBLIC, variable, label) - fun private(variable: Variable, label: String? = null) = - codeGen.classStartScope.instanceVariable(Visibility.PRIVATE, variable, label) + fun private(variable: Variable) = + codeGen.classStartScope.instanceVariable(Visibility.PRIVATE, variable, null) - fun protected(variable: Variable, label: String? = null) = - codeGen.classStartScope.instanceVariable(Visibility.PROTECTED, variable, label) + fun protected(variable: Variable) = + codeGen.classStartScope.instanceVariable(Visibility.PROTECTED, variable, null) private var isFirstGroup = true diff --git a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/engine/previz/ClientPrevizManager.kt b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/engine/previz/ClientPrevizManager.kt index 8350d17..f57cf27 100644 --- a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/engine/previz/ClientPrevizManager.kt +++ b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/engine/previz/ClientPrevizManager.kt @@ -82,7 +82,9 @@ class ClientPrevizManager( }) } - fun refreshPreviz() = refreshPreviz(codeGenManager.build(previzName!!, JavaLanguage, true)) + fun refreshPreviz() = previzName?.let{ + refreshPreviz(codeGenManager.build(it, JavaLanguage, true)) + } fun refreshPreviz(sourceCode: String) { if(previzRunning) diff --git a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/gui/NodeEditor.kt b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/gui/NodeEditor.kt index 922ac2d..2f1e069 100644 --- a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/gui/NodeEditor.kt +++ b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/gui/NodeEditor.kt @@ -35,7 +35,8 @@ class NodeEditor(val paperVision: PaperVision, val keyManager: KeyManager) : Win val PAN_CONSTANT = 25f } - val context = ImNodesContext() + var context = ImNodesContext() + private set var isNodeFocused = false private set @@ -73,6 +74,7 @@ class NodeEditor(val paperVision: PaperVision, val keyManager: KeyManager) : Win private val scrollTimer = ElapsedTime() val onDraw = EventHandler("NodeEditor-OnDraw") + val onEditorChange = EventHandler("NodeEditor-OnChange") override var title = "editor" override val windowFlags = flags( @@ -94,9 +96,9 @@ class NodeEditor(val paperVision: PaperVision, val keyManager: KeyManager) : Win ImNodes.createContext() + originNode.enable() inputNode.enable() outputNode.enable() - originNode.enable() playButton = EOCVSimPlayButtonWindow( { paperVision.nodeList.floatingButton }, @@ -107,9 +109,10 @@ class NodeEditor(val paperVision: PaperVision, val keyManager: KeyManager) : Win } override fun drawContents() { + ImNodes.editorContextSet(context) + onDraw.run() - ImNodes.editorContextSet(context) ImNodes.beginNodeEditor() ImNodes.setNodeGridSpacePos(originNode.id, 0f, 0f) @@ -119,9 +122,15 @@ class NodeEditor(val paperVision: PaperVision, val keyManager: KeyManager) : Win for (node in nodes.inmutable) { node.editor = this node.draw() + if(node.pollChange()) { + onEditorChange.run() + } } for (link in links) { link.draw() + if(link.pollChange()) { + onEditorChange.run() + } } ImNodes.endNodeEditor() @@ -214,6 +223,7 @@ class NodeEditor(val paperVision: PaperVision, val keyManager: KeyManager) : Win fun startImageDisplayFor(attribute: Attribute): ImageDisplayNode { val window = ImageDisplayNode(paperVision.previzManager.stream) paperVision.previzManager.onPrevizStart { + // automagically update the stream when the previz starts window.stream = paperVision.previzManager.stream } @@ -224,7 +234,7 @@ class NodeEditor(val paperVision: PaperVision, val keyManager: KeyManager) : Win window.delete() } - val link = Link(attribute.id, window.inputId, false) + val link = Link(attribute.id, window.inputId, false, shouldSerialize = false) link.enable() window.onDelete.doOnce { link.delete() } @@ -329,6 +339,10 @@ class NodeEditor(val paperVision: PaperVision, val keyManager: KeyManager) : Win ImNodes.destroyContext() } + fun recreateContext() { + context = ImNodesContext() + } + class EOCVSimPlayButtonWindow( val floatingButtonSupplier: () -> FrameWidthWindow, val previzManager: ClientPrevizManager, diff --git a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/gui/NodeList.kt b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/gui/NodeList.kt index ce6dc56..08b8b32 100644 --- a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/gui/NodeList.kt +++ b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/gui/NodeList.kt @@ -258,7 +258,6 @@ class NodeList( headers.delete() } - override fun restore() { super.restore() headers.restore() diff --git a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/gui/util/Popup.kt b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/gui/util/Popup.kt index c8bd5b3..73ab458 100644 --- a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/gui/util/Popup.kt +++ b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/gui/util/Popup.kt @@ -6,18 +6,20 @@ import io.github.deltacv.papervision.id.DrawableIdElementBase import io.github.deltacv.papervision.id.IdElementContainer import io.github.deltacv.papervision.util.ElapsedTime import io.github.deltacv.mai18n.tr +import io.github.deltacv.papervision.id.IdElementContainerStack open class Popup( val text: String, val position: ImVec2, val timeSecs: Double, val label: String? = null, - override val requestedId: Int? = null, - override val idElementContainer: IdElementContainer = popups + override val requestedId: Int? = null ) : DrawableIdElementBase() { private val timer = ElapsedTime() + override val idElementContainer = IdElementContainerStack.threadStack.peekNonNull() + override fun onEnable() { timer.reset() } @@ -44,10 +46,6 @@ open class Popup( fun warning(text: String, secsPerCharacter: Double = 0.16) { Popup(text, ImGui.getMousePos(), text.length * secsPerCharacter, requestedId = WARN).enable() } - - val popups = IdElementContainer().apply { - reserveId(WARN) - } } } \ No newline at end of file diff --git a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/gui/util/Window.kt b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/gui/util/Window.kt index 112811d..6604a07 100644 --- a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/gui/util/Window.kt +++ b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/gui/util/Window.kt @@ -2,18 +2,23 @@ package io.github.deltacv.papervision.gui.util import imgui.ImGui import imgui.ImVec2 +import imgui.type.ImBoolean import io.github.deltacv.papervision.id.DrawableIdElementBase import io.github.deltacv.papervision.id.IdElementContainer import io.github.deltacv.mai18n.tr +import io.github.deltacv.papervision.id.IdElementContainerStack abstract class Window( override val requestedId: Int? = null, - override val idElementContainer: IdElementContainer = windows ) : DrawableIdElementBase() { + override val idElementContainer = IdElementContainerStack.threadStack.peekNonNull() + abstract var title: String abstract val windowFlags: Int + open val isModal: Boolean = false + private var nextPosition: ImVec2? = null private var internalPosition = ImVec2() @@ -37,12 +42,21 @@ abstract class Window( private var realFocus = false private var userFocus = false + private val modalPOpen = ImBoolean(true) + var focus: Boolean set(value) { userFocus = value } get() = realFocus + override fun enable() { + super.enable() + + if(isModal) + ImGui.openPopup("${tr(title)}###$id") + } + override fun draw() { preDrawContents() @@ -57,21 +71,34 @@ abstract class Window( ImGui.setNextWindowFocus() } - ImGui.begin("${tr(title)}###$id", windowFlags) - drawContents() + if(isModal) { + if(ImGui.beginPopupModal("${tr(title)}###$id", modalPOpen, windowFlags)) { + contents() + ImGui.endPopup() + } + } else { + if(ImGui.begin("${tr(title)}###$id", windowFlags)) { + contents() + ImGui.end() + } + } + } + + private fun contents() { + drawContents() - ImGui.getWindowPos(internalPosition) - ImGui.getWindowSize(internalSize) - realFocus = ImGui.isWindowFocused() - ImGui.end() + ImGui.getWindowPos(internalPosition) + ImGui.getWindowSize(internalSize) + realFocus = ImGui.isWindowFocused() } open fun preDrawContents() { } abstract fun drawContents() - companion object { - val windows = IdElementContainer() + fun centerWindow() { + val displaySize = ImGui.getIO().displaySize + position = ImVec2((displaySize.x - size.x) / 2, (displaySize.y - size.y) / 2) } } diff --git a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/id/IdElement.kt b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/id/IdElement.kt index fb8cf2b..35ace81 100644 --- a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/id/IdElement.kt +++ b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/id/IdElement.kt @@ -20,6 +20,8 @@ interface DrawableIdElement : IdElement { fun enable() + fun pollChange(): Boolean + } @Suppress("UNCHECKED_CAST") @@ -40,7 +42,7 @@ abstract class DrawableIdElementBase> : DrawableIdE } override fun enable() { - if(internalId == null) { + if(internalId == null || !idElementContainer.has(id, this as T)) { internalId = provideId() onEnable() @@ -60,4 +62,6 @@ abstract class DrawableIdElementBase> : DrawableIdE idElementContainer[id] = this as T } + override fun pollChange() = false + } \ No newline at end of file diff --git a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/id/IdElementContainer.kt b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/id/IdElementContainer.kt index 623088d..b193225 100644 --- a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/id/IdElementContainer.kt +++ b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/id/IdElementContainer.kt @@ -28,12 +28,12 @@ class IdElementContainer : Iterable { } } - e.set(id, element) + e[id] = element elements.add(element) reallocateArray() - e.lastIndexOf(element) + id } fun reserveId(id: Int): Int { @@ -47,9 +47,15 @@ class IdElementContainer : Iterable { e.add(id, null) + reallocateArray() + return id } + fun has(id: Int, elem: T) = try { + get(id) == elem + } catch(_: Exception) { false } + fun nextId(element: () -> T) = lazy { nextId(element()).value } @@ -65,6 +71,7 @@ class IdElementContainer : Iterable { fun nextId() = lazy { e.add(null) + reallocateArray() e.lastIndexOf(null) } diff --git a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/DrawNode.kt b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/DrawNode.kt index 8d9b3a8..1671877 100644 --- a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/DrawNode.kt +++ b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/DrawNode.kt @@ -9,6 +9,7 @@ import io.github.deltacv.papervision.attribute.Attribute import io.github.deltacv.papervision.codegen.CodeGenSession import io.github.deltacv.mai18n.tr import java.lang.IllegalArgumentException +import java.util.concurrent.ArrayBlockingQueue abstract class DrawNode( allowDelete: Boolean = true @@ -22,6 +23,8 @@ abstract class DrawNode( private var isFirstDraw = true + private var changeQueue = ArrayBlockingQueue(50) + val annotationData by lazy { val annotation = this.javaClass.getAnnotation(PaperNode::class.java) ?: throw IllegalArgumentException("Node ${javaClass.typeName} needs to have a @RegisterNode annotation") @@ -29,14 +32,30 @@ abstract class DrawNode( AnnotationData(annotation.name, annotation.description, annotation.category, annotation.showInList) } + init { + onChange { + changeQueue.add(true) + } + } + var titleColor = annotationData.category.color var titleHoverColor = annotationData.category.colorSelected + override fun onEnable() { + for(attribute in nodeAttributes) { + attribute.onChange { onChange.run() } + } + } + open fun init() {} override fun draw() { val title = annotationData.name + if(changeQueue.remainingCapacity() <= 1) { + changeQueue.poll() + } + ImNodes.pushColorStyle(ImNodesColorStyle.TitleBar, titleColor) ImNodes.pushColorStyle(ImNodesColorStyle.TitleBarHovered, titleHoverColor) ImNodes.pushColorStyle(ImNodesColorStyle.TitleBarSelected, titleHoverColor) @@ -50,15 +69,15 @@ abstract class DrawNode( drawAttributes() ImNodes.endNode() - ImNodes.popColorStyle() - ImNodes.popColorStyle() - ImNodes.popColorStyle() - if(isFirstDraw) { init() isFirstDraw = false } + ImNodes.popColorStyle() + ImNodes.popColorStyle() + ImNodes.popColorStyle() + nextNodePosition?.let { ImNodes.setNodeScreenSpacePos(id, it.x, it.y) nextNodePosition = null @@ -98,6 +117,8 @@ abstract class DrawNode( open fun drawNode() { } + override fun pollChange() = changeQueue.poll() ?: false + data class AnnotationData(val name: String, val description: String, val category: Category, diff --git a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/Link.kt b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/Link.kt index 4e36c56..7169447 100644 --- a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/Link.kt +++ b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/Link.kt @@ -7,19 +7,20 @@ import io.github.deltacv.papervision.attribute.TypedAttribute import io.github.deltacv.papervision.id.DrawableIdElementBase import io.github.deltacv.papervision.id.IdElementContainerStack import io.github.deltacv.papervision.serialization.data.DataSerializable -import io.github.deltacv.papervision.serialization.ev.LinkSerializationData +import io.github.deltacv.papervision.serialization.LinkSerializationData class Link( val a: Int, val b: Int, val isDestroyableByUser: Boolean = true, + override val shouldSerialize: Boolean = true ) : DrawableIdElementBase(), DataSerializable { val attribIdElementContainer = IdElementContainerStack.threadStack.peekNonNull() override val idElementContainer = IdElementContainerStack.threadStack.peekNonNull() - val aAttrib by lazy { attribIdElementContainer[a] } - val bAttrib by lazy { attribIdElementContainer[b] } + val aAttrib get() = attribIdElementContainer[a] + val bAttrib get() = attribIdElementContainer[b] constructor(data: LinkSerializationData) : this(data.from, data.to) @@ -51,6 +52,9 @@ class Link( } } + override fun onEnable() { + } + override fun delete() { aAttrib?.links?.remove(this) bAttrib?.links?.remove(this) diff --git a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/Node.kt b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/Node.kt index ff202b3..1779853 100644 --- a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/Node.kt +++ b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/Node.kt @@ -12,8 +12,8 @@ import io.github.deltacv.papervision.id.DrawableIdElementBase import io.github.deltacv.papervision.id.IdElementContainerStack import io.github.deltacv.papervision.node.vision.OutputMatNode import io.github.deltacv.papervision.serialization.data.DataSerializable -import io.github.deltacv.papervision.serialization.ev.BasicNodeData -import io.github.deltacv.papervision.serialization.ev.NodeSerializationData +import io.github.deltacv.papervision.serialization.BasicNodeData +import io.github.deltacv.papervision.serialization.NodeSerializationData import io.github.deltacv.papervision.util.event.EventHandler import io.github.deltacv.papervision.util.event.EventListener @@ -37,7 +37,7 @@ abstract class Node( lateinit var editor: NodeEditor internal set - val isOnEditor get() = ::editor.isInitialized + val isOnEditor get() = ::editor.isInitialized && idElementContainer.contains(this) override val genOptions = CodeGenOptions() @@ -67,33 +67,41 @@ abstract class Node( override fun delete() { if(allowDelete) { - for (attribute in nodeAttributes.toTypedArray()) { - for(link in attribute.links.toTypedArray()) { - link.delete() - } + forceDelete() + } + } - attribute.delete() - attribs.remove(attribute) + fun forceDelete() { + for (attribute in nodeAttributes.toTypedArray()) { + for(link in attribute.links.toTypedArray()) { + link.delete() } - idElementContainer.removeId(id) - onDelete.run() + attribute.delete() + attribs.remove(attribute) } + + idElementContainer.removeId(id) + onDelete.run() } override fun restore() { if(allowDelete) { - for (attribute in nodeAttributes.toTypedArray()) { - for(link in attribute.links.toTypedArray()) { - link.restore() - } + forceRestore() + } + } - attribute.restore() - attribs.add(attribute) + fun forceRestore() { + for (attribute in nodeAttributes.toTypedArray()) { + for(link in attribute.links.toTypedArray()) { + link.restore() } - idElementContainer[id] = this + attribute.restore() + attribs.add(attribute) } + + idElementContainer[id] = this } fun addAttribute(attribute: Attribute) { @@ -114,7 +122,7 @@ abstract class Node( operator fun Attribute.unaryPlus() = addAttribute(this) - open override fun getOutputValueOf(current: CodeGen.Current, attrib: Attribute): GenValue { + override fun getOutputValueOf(current: CodeGen.Current, attrib: Attribute): GenValue { raise("Node doesn't have output attributes") } diff --git a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/NodesFromMetadata.kt b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/NodeRegistry.kt similarity index 100% rename from PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/NodesFromMetadata.kt rename to PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/NodeRegistry.kt diff --git a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/code/CodeSnippetNode.kt b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/code/CodeSnippetNode.kt index 55ff002..fadeddf 100644 --- a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/code/CodeSnippetNode.kt +++ b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/code/CodeSnippetNode.kt @@ -6,11 +6,11 @@ import io.github.deltacv.papervision.node.Category import io.github.deltacv.papervision.node.DrawNode import io.github.deltacv.papervision.node.PaperNode -@PaperNode( +/* @PaperNode( name = "nod_codesnippet", category = Category.CODE, description = "A user-written snippet of Java code that will be inlined in the final pipeline, with configurable parameters and outputs." -) +) */ class CodeSnippetNode : DrawNode() { override fun drawNode() { diff --git a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/math/SumIntegerNode.kt b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/math/SumIntegerNode.kt index 1183148..f25617c 100644 --- a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/math/SumIntegerNode.kt +++ b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/math/SumIntegerNode.kt @@ -17,7 +17,7 @@ import io.github.deltacv.papervision.node.Category ) class SumIntegerNode : DrawNode() { - val numbers = ListAttribute(INPUT, IntAttribute, "$[att_numbers]", sameLine = true) + val numbers = ListAttribute(INPUT, IntAttribute, "$[att_numbers]") val result = IntAttribute(OUTPUT, "$[att_result]") override fun onEnable() { diff --git a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/vision/MatNodes.kt b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/vision/MatNodes.kt index 5361b24..a24d39b 100644 --- a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/vision/MatNodes.kt +++ b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/vision/MatNodes.kt @@ -13,6 +13,7 @@ import io.github.deltacv.papervision.codegen.build.Variable import io.github.deltacv.papervision.codegen.build.type.OpenCvTypes import io.github.deltacv.papervision.node.Category import io.github.deltacv.papervision.node.PaperNode +import java.lang.IndexOutOfBoundsException @PaperNode( name = "nod_pipelineinput", @@ -24,12 +25,15 @@ class InputMatNode @JvmOverloads constructor( ) : DrawNode(allowDelete = false) { override fun init() { - windowSizeSupplier?.let { - val nodeSize = ImVec2() - ImNodes.getNodeDimensions(id, nodeSize) - - val windowSize = it() - ImNodes.setNodeScreenSpacePos(id, nodeSize.x * 0.5f, windowSize.y / 2f - nodeSize.y / 2) + editor.onDraw.doOnce { + if(serializedId != null) return@doOnce + windowSizeSupplier?.let { + val nodeSize = ImVec2() + ImNodes.getNodeDimensions(id, nodeSize) + + val windowSize = it() + ImNodes.setNodeScreenSpacePos(id, nodeSize.x * 0.5f, windowSize.y / 2f - nodeSize.y / 2) + } } } @@ -39,6 +43,11 @@ class InputMatNode @JvmOverloads constructor( + output.rebuildOnChange() } + fun ensureAttributeExists() { // prevent weird oopsies due to the special way these persistent buddies are handled + enable() + output.enable() + } + override fun genCode(current: CodeGen.Current) = NoSession fun startGen(current: CodeGen.Current) { @@ -67,12 +76,16 @@ class OutputMatNode @JvmOverloads constructor( } override fun init() { - windowSizeSupplier?.let { - val nodeSize = ImVec2() - ImNodes.getNodeDimensions(id, nodeSize) + editor.onDraw.doOnce { + if(serializedId != null) return@doOnce - val windowSize = it() - ImNodes.setNodeScreenSpacePos(id, windowSize.x - nodeSize.x * 1.5f , windowSize.y / 2f - nodeSize.y / 2) + windowSizeSupplier?.let { + val nodeSize = ImVec2() + ImNodes.getNodeDimensions(id, nodeSize) + + val windowSize = it() + ImNodes.setNodeScreenSpacePos(id, windowSize.x - nodeSize.x * 1.5f , windowSize.y / 2f - nodeSize.y / 2) + } } } @@ -82,6 +95,11 @@ class OutputMatNode @JvmOverloads constructor( + input.rebuildOnChange() } + fun ensureAttributeExists() { // prevent weird oopsies due to the special way these persistent buddies are handled + enable() + input.enable() + } + override fun genCode(current: CodeGen.Current) = current { current.scope { returnMethod(input.value(current).value) diff --git a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/vision/classification/GroupContoursByShapeNode.kt b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/vision/classification/GroupContoursByShapeNode.kt index 2968ecb..012673f 100644 --- a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/vision/classification/GroupContoursByShapeNode.kt +++ b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/vision/classification/GroupContoursByShapeNode.kt @@ -29,14 +29,14 @@ enum class Shape(val sides: Int?) { ) class GroupContoursByShapeNode : DrawNode() { - val input = ListAttribute(INPUT, PointsAttribute,"$[att_contours]") + val input = ListAttribute(INPUT, PointsAttribute, "$[att_contours]") val shape = EnumAttribute(INPUT, Shape.values(), "$[att_shape]") val sides = IntAttribute(INPUT, "$[att_sides]") val accuracy = IntAttribute(INPUT, "$[att_accuracy]") - val output = ListAttribute(OUTPUT, PointsAttribute,"$[att_filteredcontours]") + val output = ListAttribute(OUTPUT, PointsAttribute, "$[att_filteredcontours]") private var previousSides = 0 diff --git a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/vision/classification/GroupRectsInsideAreaNode.kt b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/vision/classification/GroupRectsInsideAreaNode.kt index e88f3aa..c3979c9 100644 --- a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/vision/classification/GroupRectsInsideAreaNode.kt +++ b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/vision/classification/GroupRectsInsideAreaNode.kt @@ -28,7 +28,7 @@ enum class Orientation { Vertical, Horizontal } class GroupRectsInsideAreaNode : DrawNode() { val source = MatAttribute(INPUT,"$[att_source]") - val input = ListAttribute(INPUT, RectAttribute,"$[att_rects]") + val input = ListAttribute(INPUT, RectAttribute, "$[att_rects]") val areaOrientation = EnumAttribute(INPUT, Orientation.values(), "$[att_areaorientation]") @@ -36,7 +36,7 @@ class GroupRectsInsideAreaNode : DrawNode() { val areaEndPositionPercentage = IntAttribute(INPUT, "$[att_areaend_positionpercentage]") val areaRect = RectAttribute(OUTPUT, "$[att_arearect]") - val output = ListAttribute(OUTPUT, RectAttribute,"$[att_rectsinside]") + val output = ListAttribute(OUTPUT, RectAttribute, "$[att_rectsinside]") override fun onEnable() { + source.rebuildOnChange() diff --git a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/vision/featuredet/filter/FilterBiggestRectangleNode.kt b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/vision/featuredet/filter/FilterBiggestRectangleNode.kt index 9dd2b67..fa158d8 100644 --- a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/vision/featuredet/filter/FilterBiggestRectangleNode.kt +++ b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/vision/featuredet/filter/FilterBiggestRectangleNode.kt @@ -19,7 +19,7 @@ import io.github.deltacv.papervision.node.PaperNode ) class FilterBiggestRectangleNode : DrawNode() { - val input = ListAttribute(INPUT, RectAttribute,"$[att_rects]") + val input = ListAttribute(INPUT, RectAttribute, "$[att_rects]") val output = RectAttribute(OUTPUT, "$[att_biggestrect]") override fun onEnable() { diff --git a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/vision/imageproc/ErodeDilateNode.kt b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/vision/imageproc/ErodeDilateNode.kt index c460a61..cfab519 100644 --- a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/vision/imageproc/ErodeDilateNode.kt +++ b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/vision/imageproc/ErodeDilateNode.kt @@ -57,8 +57,8 @@ class ErodeDilateNode : DrawNode() { val output = uniqueVariable("${input.value.value!!}ErodedDilated", OpenCvTypes.Mat.new()) group { - private(erodeValVariable, erodeValue.label()) - private(dilateValVariable, dilateValue.label()) + public(erodeValVariable, erodeValue.label()) + public(dilateValVariable, dilateValue.label()) private(element) private(output) } diff --git a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/vision/imageproc/ThresholdNode.kt b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/vision/imageproc/ThresholdNode.kt index d442f04..97c782c 100644 --- a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/vision/imageproc/ThresholdNode.kt +++ b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/vision/imageproc/ThresholdNode.kt @@ -57,6 +57,7 @@ class ThresholdNode : DrawNode() { if(color != lastColor) { scalar.color = color + scalar.rebuildPreviz() } lastColor = color diff --git a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/vision/overlay/DrawContoursNode.kt b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/vision/overlay/DrawContoursNode.kt index 0f2cacc..54de3a9 100644 --- a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/vision/overlay/DrawContoursNode.kt +++ b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/vision/overlay/DrawContoursNode.kt @@ -46,7 +46,7 @@ open class DrawContoursNode lineThickness.value.set(1) // initial value if(!isDrawOnInput) { - + outputMat.enablePrevizButton() + + outputMat.enablePrevizButton().rebuildOnChange() } else { inputMat.variableName = "$[att_drawon_image]" } @@ -104,7 +104,7 @@ open class DrawContoursNode ) if(!isDrawOnInput) { - outputMat.streamIfEnabled(output, input.color) + outputMat.streamIfEnabled(drawMat, input.color) } } diff --git a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/vision/overlay/DrawRectanglesNode.kt b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/vision/overlay/DrawRectanglesNode.kt index cfd3d6c..e0ca69d 100644 --- a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/vision/overlay/DrawRectanglesNode.kt +++ b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/node/vision/overlay/DrawRectanglesNode.kt @@ -36,16 +36,16 @@ open class DrawRectanglesNode val outputMat = MatAttribute(OUTPUT, "$[att_output]") override fun onEnable() { - +inputMat.rebuildOnChange() - +rectangles.rebuildOnChange() + + inputMat.rebuildOnChange() + + rectangles.rebuildOnChange() - +lineColor - +lineThickness + + lineColor + + lineThickness lineThickness.value.set(1) // initial value if (!isDrawOnInput) { - +outputMat.enablePrevizButton() + + outputMat.enablePrevizButton().rebuildOnChange() } else { inputMat.variableName = "$[att_drawon_image]" } @@ -66,7 +66,7 @@ open class DrawRectanglesNode ) val input = inputMat.value(current) - var rectanglesList = rectangles.value(current) + val rectanglesList = rectangles.value(current) val thickness = lineThickness.value(current).value val thicknessVariable = uniqueVariable("rectsThickness", thickness.v) diff --git a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/serialization/ev/Data.kt b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/serialization/PaperVisionData.kt similarity index 92% rename from PaperVision/src/main/kotlin/io/github/deltacv/papervision/serialization/ev/Data.kt rename to PaperVision/src/main/kotlin/io/github/deltacv/papervision/serialization/PaperVisionData.kt index ebb314d..312ed0b 100644 --- a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/serialization/ev/Data.kt +++ b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/serialization/PaperVisionData.kt @@ -1,4 +1,4 @@ -package io.github.deltacv.papervision.serialization.ev +package io.github.deltacv.papervision.serialization import imgui.ImVec2 diff --git a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/serialization/ev/PaperVisionSerializer.kt b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/serialization/PaperVisionSerializer.kt similarity index 57% rename from PaperVision/src/main/kotlin/io/github/deltacv/papervision/serialization/ev/PaperVisionSerializer.kt rename to PaperVision/src/main/kotlin/io/github/deltacv/papervision/serialization/PaperVisionSerializer.kt index d8ee89d..f1a7188 100644 --- a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/serialization/ev/PaperVisionSerializer.kt +++ b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/serialization/PaperVisionSerializer.kt @@ -1,4 +1,4 @@ -package io.github.deltacv.papervision.serialization.ev +package io.github.deltacv.papervision.serialization import com.google.gson.JsonElement import io.github.deltacv.papervision.PaperVision @@ -9,25 +9,25 @@ import io.github.deltacv.papervision.node.vision.OutputMatNode import io.github.deltacv.papervision.serialization.data.DataSerializable import io.github.deltacv.papervision.serialization.data.DataSerializer -object EasyVisionSerializer { +object PaperVisionSerializer { fun serialize(nodes: List>, links: List): String { val serializables = mutableMapOf>>() - serializables["nodes"] = nodes - serializables["links"] = links + serializables["nodes"] = nodes.filter { it.shouldSerialize } + serializables["links"] = links.filter { it.shouldSerialize } return DataSerializer.serialize(serializables) } fun serializeToTree(nodes: List>, links: List): JsonElement { val serializables = mutableMapOf>>() - serializables["nodes"] = nodes - serializables["links"] = links + serializables["nodes"] = nodes.filter { it.shouldSerialize } + serializables["links"] = links.filter { it.shouldSerialize } return DataSerializer.serializeToTree(serializables) } - private fun deserialize(obj: JsonElement?, json: String?, paperVision: PaperVision?): EasyVisionData { + private fun deserialize(obj: JsonElement?, json: String?, paperVision: PaperVision?): PaperVisionData { val data = if(obj != null) { DataSerializer.deserialize(obj) } else { @@ -37,12 +37,20 @@ object EasyVisionSerializer { val nodes = mutableListOf>() val links = mutableListOf() - paperVision?.apply { - nodes.clear() - attributes.clear() - links.clear() + paperVision?.let { + for(node in it.nodes.inmutable) { + if(node != it.nodeEditor.originNode) // everything freaking breaks if we delete this thing + node.forceDelete() + } + + for(link in it.links.inmutable) { + link.delete() + } } + var createdOutputNode = false + var createdInputNode = false + val nodesData = data["nodes"] if(nodesData != null) { for(node in nodesData) { @@ -50,19 +58,35 @@ object EasyVisionSerializer { // applying the inputmatnode and outputmatnode positions in case they passed a node editor if(paperVision != null) { when (node) { - is InputMatNode -> paperVision.nodeEditor.inputNode = node - is OutputMatNode -> paperVision.nodeEditor.outputNode = node + is InputMatNode -> { + paperVision.nodeEditor.inputNode = node + createdInputNode = true + } + is OutputMatNode -> { + paperVision.nodeEditor.outputNode = node + createdOutputNode = true + } } - } - - if(paperVision != null) { node.enable() } + nodes.add(node) } } } + paperVision?.let { + if(!createdInputNode) { + it.nodeEditor.inputNode = InputMatNode().apply { enable() } + } + if(!createdOutputNode) { + it.nodeEditor.outputNode = OutputMatNode().apply { enable() } + } + + it.nodeEditor.inputNode.ensureAttributeExists() + it.nodeEditor.outputNode.ensureAttributeExists() + } + val linksData = data["links"] if(linksData != null) { for(link in linksData) { @@ -75,7 +99,7 @@ object EasyVisionSerializer { } } - return EasyVisionData(nodes, links) + return PaperVisionData(nodes, links) } fun deserialize(json: String) = deserialize(null, json, null) @@ -86,4 +110,4 @@ object EasyVisionSerializer { } -data class EasyVisionData(@JvmField val nodes: List>, @JvmField val links: List) \ No newline at end of file +data class PaperVisionData(@JvmField val nodes: List>, @JvmField val links: List) \ No newline at end of file diff --git a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/serialization/data/DataSerializable.kt b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/serialization/data/DataSerializable.kt index 46afc88..198dc48 100644 --- a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/serialization/data/DataSerializable.kt +++ b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/serialization/data/DataSerializable.kt @@ -1,6 +1,9 @@ package io.github.deltacv.papervision.serialization.data interface DataSerializable { + val shouldSerialize: Boolean + get() = true + fun serialize(): D fun deserialize(data: D) } \ No newline at end of file diff --git a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/serialization/data/DataSerializer.kt b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/serialization/data/DataSerializer.kt index 78e286d..c43a53c 100644 --- a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/serialization/data/DataSerializer.kt +++ b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/serialization/data/DataSerializer.kt @@ -35,8 +35,5 @@ object DataSerializer { @Target(AnnotationTarget.FIELD) annotation class SerializeData -@Target(AnnotationTarget.CLASS) +@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY) annotation class SerializeIgnore - -@Target(AnnotationTarget.CLASS) -annotation class SerializationName(val name: String) \ No newline at end of file diff --git a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/serialization/data/adapter/DataDeserialization.kt b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/serialization/data/adapter/DataDeserialization.kt index f7506c3..18408dc 100644 --- a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/serialization/data/adapter/DataDeserialization.kt +++ b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/serialization/data/adapter/DataDeserialization.kt @@ -13,13 +13,14 @@ fun jsonObjectToDataSerializable( context: JsonDeserializationContext? = null, inst: DataSerializable? = null ): DataSerializable<*> { + val classLoader = DataSerializable::class.java.classLoader val jsonObject = json.asJsonObject - val dataClass = Class.forName(jsonObject.get("dataClass").asString) + val dataClass = classLoader.loadClass(jsonObject.get("dataClass").asString) val dataObj = jsonObject.get("data") ?: JsonObject() val dataInstance = context?.deserialize(dataObj, dataClass) ?: gson.fromJson(dataObj, dataClass) - val objectClass = Class.forName(jsonObject.get("objectClass").asString) + val objectClass = classLoader.loadClass(jsonObject.get("objectClass").asString) val objectInstance = inst ?: try { diff --git a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/serialization/data/adapter/DataSerialization.kt b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/serialization/data/adapter/DataSerialization.kt index 0ca455e..87c1514 100644 --- a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/serialization/data/adapter/DataSerialization.kt +++ b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/serialization/data/adapter/DataSerialization.kt @@ -6,6 +6,7 @@ import com.google.gson.JsonSerializationContext import io.github.deltacv.papervision.node.hasSuperclass import io.github.deltacv.papervision.serialization.data.DataSerializable import io.github.deltacv.papervision.serialization.data.SerializeData +import io.github.deltacv.papervision.serialization.data.SerializeIgnore fun DataSerializable<*>.toJsonObject(): JsonObject { val obj = JsonObject() @@ -14,8 +15,15 @@ fun DataSerializable<*>.toJsonObject(): JsonObject { field.isAccessible = true val value = field.get(this) ?: continue + if(field.isAnnotationPresent(SerializeIgnore::class.java) || field.type.isAnnotationPresent(SerializeIgnore::class.java)) { + continue + } + if(hasSuperclass(field.type, DataSerializable::class.java)) { - obj.add(field.name, dataSerializableToJsonObject(value as DataSerializable<*>)) + if(!(value as DataSerializable<*>).shouldSerialize) { + continue + } + obj.add(field.name, dataSerializableToJsonObject(value)) } else if(field.isAnnotationPresent(SerializeData::class.java)) { obj.add(field.name, gson.toJsonTree(value)) } diff --git a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/serialization/data/adapter/SerializeIgnoreExclusionStrategy.kt b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/serialization/data/adapter/SerializeIgnoreExclusionStrategy.kt index fa8021e..f88d95c 100644 --- a/PaperVision/src/main/kotlin/io/github/deltacv/papervision/serialization/data/adapter/SerializeIgnoreExclusionStrategy.kt +++ b/PaperVision/src/main/kotlin/io/github/deltacv/papervision/serialization/data/adapter/SerializeIgnoreExclusionStrategy.kt @@ -5,9 +5,14 @@ import com.google.gson.FieldAttributes import io.github.deltacv.papervision.serialization.data.SerializeIgnore object SerializeIgnoreExclusionStrategy : ExclusionStrategy { - override fun shouldSkipField(f: FieldAttributes?) = - f?.declaredClass?.isAnnotationPresent(SerializeIgnore::class.java) ?: false + override fun shouldSkipField(f: FieldAttributes): Boolean { + // Check if the field or its class has the SerializeIgnore annotation + return f.declaredClass.isAnnotationPresent(SerializeIgnore::class.java) || + f.annotations.any { it.annotationClass.java.isAnnotationPresent(SerializeIgnore::class.java) } + } - override fun shouldSkipClass(clazz: Class<*>?) = - clazz?.isAnnotationPresent(SerializeIgnore::class.java) ?: false + override fun shouldSkipClass(clazz: Class<*>): Boolean { + // Check if the class has the SerializeIgnore annotation + return clazz.isAnnotationPresent(SerializeIgnore::class.java) + } } \ No newline at end of file diff --git a/PaperVision/src/main/resources/ico/ico_ezv.jpg b/PaperVision/src/main/resources/ico/ico_ezv.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d7a6d16ea92c9e7b0e3e2095a324b363a1932593 GIT binary patch literal 4697 zcmbVQXH?V8(*K95fP}7erAik9N>O?Zgeommfq)e05keJtKtVxjC;@_kA|e7J(hOKY zIzc)ppi-nsAhaZuy!hOEp7*_9?uU1FKkS*^nX|u{bM`ko)Jf`hfDLMBVhDgh0024} z0QDFU)(g579Eu1FLwKF{K%MssM)=5uB0T@Cq<#hd0_gq`1_pWthEt3ua*E~jA7SBS zVP!d)oa|g|U^Xsxp|jlFXN5!r`2|I!#l@wSm6Y}Mok9QD)BXP(P(K3fr+{vtn-(Mj z(6EDO*+J9}fbRqWE$9!He*{EBOGkf-It`rqm->M8&lX^X$1jf)0UD`NkwSo0@UdAW zE`Z=S3Fvn?oVt$ztPhKWYtEL^dwkaw8w{tB5MvE2+8J!=xBH#~>cbJo#fulX-@@-+ zTE73fIUpkiuWf```Dkkw$g%_a^i46~Ww(9;`wfRCmYqIJpQxE79509yU5bZJ0wS_B z?C!L39@^R&EDb4uop%*o7&44-o<1N+`PnrU2Y{ur(9{3%dJ;zvJ;2Vv$#tHMf&0QG9uZM( zJ26EaOZR^V7sLTjfyK)tn)i2pV`WtiXi)M@=;Q&FD25rD)!ul^>h6p zRs8nH5J70~ATqbx`uTCpCxv|9fUa~}Yh;ISsB5leaw69dr<2yCpef_SClY}}+gnm4 zhkN!mXt_tjQ(U>{!#PSFRth#^VcH+9HcI^Pcy*42g*gURN6k~LD&{u#U~&wsyvc;U z%(ldDI$0bH*CqjGVKxB)LhIDv>CrABq15+}LNML@Y!D{vBDryNxBdi1zI@m_n?Cd^ zn6|str6;)d`UfzJ(vc`dJ3259{ezT&B@jM@QMoZO^Byg zGCJ^k;I7%H-^eVI{84_E9RIWFw;VGrs@1n;C`(VT-nDWLk~2gvV+|IYCz&D9zlbRb zf{!Am3@iN*!bm7v1$S5JrK@B4l_knDc%JvOi+^#MUekOSb&v&@chnE}!`+TLw%#|u z9Ep8VX9epW_$d#}I2W#J-Bs-{gnmSx2TJK*(gBQg`Y(zuo{l+akBRTOvLUA3r>WFs zlW$z2-yL3v=0~)gzJsbQ*JH?p{-@OkT=pVmSg}XM)52uDN$Z`5JSZG&eniGL72^LRwTR@M&@Y+aAAl1uJb1i@>F; z`1*alYh+9dEpGei-SXGjcuV>y$*%-ExqVZ1kA4m{r+d$ZdaqNCKHiLev$XQcY{h?e z2yRE`-JQAG<+?y}bW6GN%uduHWW^f+uPbdqJN_&Z&$-GaNCmhRmy7So&hGyTpzmcZ zQnGI;MBYduw)K8Kt;`a}IMsslOZs#Nj>>nBH8JYTf*-M3G@qZdpQg1j&8g;5T#J3l z-dl$i=yYAbvF9pm&vmdVcFVjt$L^(hLr<|>^fWW{{lh8QubQJH7shc5BXb`g-h^n` zeU~b{(15zz`&Y%}Zt^mCI!VJfIph)M8P0ntJ z+dUVIXFG#JKolp=tG|t$vta8L{yw!X(SP-RZJF2$l~Gv_>xE;vE)ErlrA-~?j(lb6 z{36;h!Q^sA4@Zs!pFhMC(k!HSEn@)F$~*}R4jSL8{qaV1+rh!u+uVh2z(UkE+A0g9 zyvh|@-yT4q0&!Kdb5*LanPbTPMJ=8W+fqN4_ee2(y;wG%3vZivWv5sfk*!<&e2d)a z!r&lcZAzEb7gxw_k8gBmszSt^R0|Xt)41E&!--=Mp22CZ-bFQYLFG5+RK5AzU0H`0 zNUp+JDyh9q6=iw^jconp$1I$tb`te5FR1{>7B|iQP#YxE3On3*#3a{9H8;GiE&qSIgHv*a-KfLJs;W48)Le@V$D_yN7DepkKO;&U}BG$;)u}IB@#h7p+Pv^X)!VK$nC7bNJ=&zc9yI_x~DLj z?)i&R-3W@9Yl*v)D_i7m2~ysibjy=UUl5gZ{`?oMW9tM@!HC8yhj-9DMA`w-%qtY} zSX)e7F_wQwZngR7T@RSWCvT6}9VE20ConUcm1);>$Rn}jf6jb=LxKJ46}Vs@MWU-} zwh^5_Z=4f-xb!P-#(4TZ-(`yj9+NsrNB%viM~U4MiCziMI>E+ZQ3TxJ_<8avDq!IM zxV~1bVu%XN>tx#yD;O!xY4RjfzoU}&T^409v$FF!Up<(o$;*P&<2=Ci)R*A10`5OT z)k8nx)u!L6wVu1G^`ZbG5cBW@R@u@N->VgjVG*VRc8_;x@>3&*ImAEGz_%?5H7@H326klWfB3s|7YI-s9q6QT}2t?fs$U*Tf z7adfOXqNJhbt_y>oB;>H3Vjs8p**(?(Upmii^NY~41;NW#pk^oMi8Za*&c zy{dTKI`0*|xN5(i-4=jBhoQ9BbsfjR&M8+Kc}vyiLRPv+r|Uyp`Ud=rgRPrMj=p5+^U2Qc)t_cdy2ZV_Z|}RGrUHpnz$Ctt zl>QWLEP47yE@V*fkedqNPAFNC?S#o`XlMZX6LO{f!>%V)3OmOIK#`7(Q$*+dC94}; zq7dy6?%`1-T}$`Ge-kXH)(OF`rkf~@207oKoth%_H!1w65=vUMDoUNNcVTYqpA*hw zg|6W`zSj~CwvQ_@RN!nsufMY3Zq@V8xaei#rdJW25EU5kN}~c!bD0JP2K>@C-h^7(iJ)c_tx;WsBH$zPBod9ranbplu#{s2Q=Ks zZXz<`<(SC+Gd4#IhXbu|Fsu-zgu&Z66JN&{w{zx;%q8PJ9~PRzzW8b4P+P)h9?7vu z8xQR^;x|>}e{Zv7a!P)e)JARPdu(hTdzc9udbg(qQ#w)~-+Cg7zhlYF^*g7b3vTm8)r@HCaK``-iA7 z2IuTYQA-~rTU-F9wLO7c)vfugkY|wVI*Qly@3bM3r5zokVlnFGSf9HC2KXf#lR6`E zt`Js{%OL)&IOQV2DSZ8F&2;Ad;&&zro&&!RV*POeWY1nhC-}s0*Q;MQ_T7vng$~4q z7_}KQls_0i?7Lzlnl9KrhM^vy4MTrVBVGwqz}N1O^EAyWB)1r#Pb4YDLIz58@%*XM zQHPc*S`GyB#)^ZHU+M{dj`L=O&|`hr$~ayMz4fi-T|}qu&*YnPCI{yddSYN(nK~6; ze(~~GbEuTgmW#RWpjcuZ3r3qI0*oFQ)m_FAH(oVJq>af~?Cp^T*vv+ATj*pqR$b?` zuH59WATHCAfXzN7cV2nS9wF6F&#xr?kQCjXG-lqo^X@g+&$q=*|pw{8aB)PGL4pu5Cj_O#Q= z8y1#Fsz!yI_4mB3jAhJ;-QsMoNEurIKT|h}Y-qVg`jq9#o{>`x`TUw+G(IzW0^28M_SeOo6k7ZhaQvZjC~Gd1sL+uD|=Fv8E`_{j?$17T^M z&P0Owj{4=>0Wk&W`FN>%>kZrNhQ;5RiriOOS<)o0)i z#iYZtN6$5_*~9)A#{(3egoe#mANT%Vv| z=CVtwRNP$buADO=yvAb5S^2mwsczHD>B~bUtrOT}&*+|SrY+YsD!rs*$;|IsILe&2 zdPgg-&WigI&s9e4D-W!OLi=>uWNJiGnYzqIcgZ=!ah11C(ac{CI1RYD0`zA)wl`hP z+QHUZ(|VLpldxl|_kzw=8QHE;_A8Kn{ zUeksZSrzQ$inzGu<5RvuY^bLo>vuf7{a1dSx_4x$2?OFsNz6)sILezkjJN0iQd5hGSdBQH?o?UjA14b?wW82DwjiKLVt zIaMhIg=qo~Df#1#6S2qd((Bs6TC2p!k-foHWcARkiJt3SSt=|?s5nuMfpQ-wibaf`6t+Ma`wi8aAizEQqG zI~^yw)f5y@J7|}w5ccd zmAQLQP;YZ9khZpOU*sVz{J$RL@3=9UN6rvJqQ*>*B;CoI+qQSJv$XPrmTnW5t)X$Y zqLI_8FEtm9%-@;0L0zzs8Ey*XYSiW&b3C{vdJ!-0>O}S+Id!3<4^UpAQ*d>p#GK~k zkh-wzb~QX|50RbOse^d5Yy^6abe}K&`_*~blN#Qe4E{PxnaC9D?=O_l8;U!8e<)kl zE7f@w+3B+7ld*E>=kP-S`6M}YCa4X3v@?%N#ug3B(vVSaSDk;2y^Von956MiJu z74VhVs1g)THSDZ5DBw`+yJ4f_khD(MGC>&OSpW=$E^2Oa3G-a_@@pb&;o{$r9wm37 zd$@-tTL}0~hd;b}ZfkI+rC{h*JelyPWx)P_J1BoX|G|HkAT%J579jOs4xIE}{v4S4 EA8k3BL;wH) literal 0 HcmV?d00001