513 lines
18 KiB
Java
513 lines
18 KiB
Java
import imgui.ImGuiViewport;
|
|
import imgui.flag.ImGuiColorEditFlags;
|
|
import imgui.flag.ImGuiCond;
|
|
import imgui.flag.ImGuiDir;
|
|
import imgui.flag.ImGuiInputTextFlags;
|
|
import imgui.flag.ImGuiStyleVar;
|
|
import imgui.flag.ImGuiWindowFlags;
|
|
import imgui.extension.imnodes.ImNodes;
|
|
import imgui.extension.imnodes.ImNodesContext;
|
|
import imgui.extension.imnodes.flag.ImNodesPinShape;
|
|
import imgui.extension.nodeditor.NodeEditor;
|
|
import imgui.internal.ImGui;
|
|
import imgui.internal.flag.ImGuiDockNodeFlags;
|
|
import imgui.extension.nodeditor.NodeEditorConfig;
|
|
import imgui.extension.nodeditor.NodeEditorContext;
|
|
import imgui.extension.nodeditor.flag.NodeEditorPinKind;
|
|
import imgui.type.ImBoolean;
|
|
import imgui.ImColor;
|
|
import imgui.type.ImInt;
|
|
import imgui.type.ImLong;
|
|
import imgui.type.ImString;
|
|
import org.lwjgl.BufferUtils;
|
|
|
|
import javax.imageio.ImageIO;
|
|
import java.awt.image.BufferedImage;
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.nio.ByteBuffer;
|
|
import java.util.HashMap;
|
|
import java.util.Map;
|
|
|
|
import static org.lwjgl.opengl.GL32.GL_CLAMP_TO_EDGE;
|
|
import static org.lwjgl.opengl.GL32.GL_LINEAR;
|
|
import static org.lwjgl.opengl.GL32.GL_RGBA;
|
|
import static org.lwjgl.opengl.GL32.GL_RGBA8;
|
|
import static org.lwjgl.opengl.GL32.GL_TEXTURE_2D;
|
|
import static org.lwjgl.opengl.GL32.GL_TEXTURE_MAG_FILTER;
|
|
import static org.lwjgl.opengl.GL32.GL_TEXTURE_MIN_FILTER;
|
|
import static org.lwjgl.opengl.GL32.GL_TEXTURE_WRAP_S;
|
|
import static org.lwjgl.opengl.GL32.GL_TEXTURE_WRAP_T;
|
|
import static org.lwjgl.opengl.GL32.GL_UNSIGNED_BYTE;
|
|
import static org.lwjgl.opengl.GL32.glBindTexture;
|
|
import static org.lwjgl.opengl.GL32.glGenTextures;
|
|
import static org.lwjgl.opengl.GL32.glTexImage2D;
|
|
import static org.lwjgl.opengl.GL32.glTexParameteri;
|
|
|
|
@SuppressWarnings({"MagicNumber", "VisibilityModifier"})
|
|
final class ExampleUi {
|
|
private static final String IMGUI_DEMO_LINK = "https://raw.githubusercontent.com/ocornut/imgui/05bc204/imgui_demo.cpp";
|
|
|
|
private static final int COLOR_DODGERBLUE = ImColor.rgbToColor("#1E90FF");
|
|
private static final int COLOR_CORAL = ImColor.rgbToColor("#FF7F50");
|
|
private static final int COLOR_LIMEGREEN = ImColor.rgbToColor("#32CD32");
|
|
|
|
// Test data for payload
|
|
private String dropTargetText = "Drop Here";
|
|
|
|
// Resizable input example
|
|
private final ImString resizableStr = new ImString(5);
|
|
|
|
// Toggles
|
|
private final ImBoolean showBottomDockedWindow = new ImBoolean(true);
|
|
private final ImBoolean showDemoWindow = new ImBoolean();
|
|
private final ImBoolean showImNodesWindow = new ImBoolean(false);
|
|
private final ImBoolean showImNodeEditorWindow = new ImBoolean(false);
|
|
|
|
// Attach image example
|
|
private int dukeTexture = 0;
|
|
|
|
private boolean isBottomDockedWindowInit = false;
|
|
|
|
// To modify background color dynamically
|
|
final float[] backgroundColor = new float[]{0.5f, 0, 0};
|
|
|
|
// Context for imnodes example
|
|
private ImNodesContext imNodesContext;
|
|
|
|
// Context for imgui-node-editor example
|
|
private NodeEditorContext nodeEditorContext;
|
|
|
|
// Graph used both for imnodes and imgui-node-editor demo
|
|
private final Graph graph = new Graph();
|
|
|
|
void render() throws Exception {
|
|
if (imNodesContext == null) {
|
|
imNodesContext = new ImNodesContext();
|
|
}
|
|
if (nodeEditorContext == null) {
|
|
final NodeEditorConfig config = new NodeEditorConfig();
|
|
config.setSettingsFile(null);
|
|
nodeEditorContext = new NodeEditorContext(config);
|
|
}
|
|
|
|
final int dockspaceId = ImGui.getID("MyDockSpace");
|
|
showDockSpace(dockspaceId);
|
|
setupBottomDockedWindow(dockspaceId);
|
|
showBottomDockedWindow();
|
|
|
|
final ImGuiViewport mainViewport = ImGui.getMainViewport();
|
|
ImGui.setNextWindowSize(600, 300, ImGuiCond.Once);
|
|
ImGui.setNextWindowPos(mainViewport.getWorkPosX() + 10, mainViewport.getWorkPosY() + 10, ImGuiCond.Once);
|
|
|
|
ImGui.begin("Custom window"); // Start Custom window
|
|
showWindowImage();
|
|
showToggles();
|
|
|
|
ImGui.separator();
|
|
|
|
showDragNDrop();
|
|
showBackgroundPicker();
|
|
|
|
ImGui.separator();
|
|
|
|
showResizableInput();
|
|
|
|
ImGui.separator();
|
|
ImGui.newLine();
|
|
|
|
showDemoLink();
|
|
|
|
ImGui.end(); // End Custom window
|
|
|
|
if (showDemoWindow.get()) {
|
|
ImGui.showDemoWindow(showDemoWindow);
|
|
}
|
|
|
|
if (showImNodesWindow.get()) {
|
|
showImNodesWindow();
|
|
}
|
|
|
|
if (showImNodeEditorWindow.get()) {
|
|
showImNodeEditorWindow();
|
|
}
|
|
}
|
|
|
|
private void showDockSpace(final int dockspaceId) {
|
|
final ImGuiViewport mainViewport = ImGui.getMainViewport();
|
|
final int windowFlags = ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoResize
|
|
| ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoBringToFrontOnFocus | ImGuiWindowFlags.NoNavFocus | ImGuiWindowFlags.NoBackground;
|
|
|
|
ImGui.setNextWindowPos(mainViewport.getWorkPosX(), mainViewport.getWorkPosY());
|
|
ImGui.setNextWindowSize(mainViewport.getWorkSizeX(), mainViewport.getWorkSizeY());
|
|
ImGui.setNextWindowViewport(mainViewport.getID());
|
|
ImGui.pushStyleVar(ImGuiStyleVar.WindowRounding, 0);
|
|
ImGui.pushStyleVar(ImGuiStyleVar.WindowBorderSize, 0);
|
|
|
|
ImGui.pushStyleVar(ImGuiStyleVar.WindowPadding, 0, 0);
|
|
ImGui.begin("Dockspace Demo", windowFlags);
|
|
ImGui.popStyleVar(3);
|
|
|
|
ImGui.dockSpace(dockspaceId, 0, 0, ImGuiDockNodeFlags.PassthruCentralNode);
|
|
ImGui.end();
|
|
}
|
|
|
|
private void setupBottomDockedWindow(final int dockspaceId) {
|
|
if (!isBottomDockedWindowInit) {
|
|
ImGui.dockBuilderRemoveNode(dockspaceId);
|
|
ImGui.dockBuilderAddNode(dockspaceId, ImGuiDockNodeFlags.DockSpace);
|
|
|
|
final int dockIdBottom = ImGui.dockBuilderSplitNode(dockspaceId, ImGuiDir.Down, .25f, null, null);
|
|
|
|
ImGui.dockBuilderDockWindow("Bottom Docked Window", dockIdBottom);
|
|
ImGui.dockBuilderSetNodeSize(dockIdBottom, 150f, 150f);
|
|
ImGui.dockBuilderFinish(dockspaceId);
|
|
|
|
isBottomDockedWindowInit = true;
|
|
}
|
|
}
|
|
|
|
private void showBottomDockedWindow() {
|
|
if (showBottomDockedWindow.get()) {
|
|
ImGui.begin("Bottom Docked Window", showBottomDockedWindow, ImGuiWindowFlags.AlwaysAutoResize);
|
|
ImGui.text("An example of how to create docked windows.");
|
|
ImGui.end();
|
|
}
|
|
}
|
|
|
|
private void showWindowImage() throws IOException {
|
|
if (dukeTexture == 0) {
|
|
dukeTexture = loadTexture(ImageIO.read(new File("src/test/resources/Duke_waving.png")));
|
|
}
|
|
|
|
// Draw an image in the bottom-right corner of the window
|
|
final float xPoint = ImGui.getWindowPosX() + ImGui.getWindowSizeX() - 100;
|
|
final float yPoint = ImGui.getWindowPosY() + ImGui.getWindowSizeY();
|
|
ImGui.getWindowDrawList().addImage(dukeTexture, xPoint, yPoint - 180, xPoint + 100, yPoint);
|
|
}
|
|
|
|
private void showToggles() {
|
|
ImGui.checkbox("Show Demo Window", showDemoWindow);
|
|
ImGui.checkbox("Show Bottom Docked Window", showBottomDockedWindow);
|
|
ImGui.checkbox("Show demo for imnodes", showImNodesWindow);
|
|
ImGui.checkbox("Show demo for imgui-node-editor", showImNodeEditorWindow);
|
|
if (ImGui.button("Reset Bottom Dock Window")) {
|
|
isBottomDockedWindowInit = false;
|
|
}
|
|
}
|
|
|
|
private void showDragNDrop() {
|
|
ImGui.button("Drag me");
|
|
if (ImGui.beginDragDropSource()) {
|
|
ImGui.setDragDropPayloadObject("payload_type", "Test Payload");
|
|
ImGui.text("Drag started");
|
|
ImGui.endDragDropSource();
|
|
}
|
|
ImGui.sameLine();
|
|
ImGui.text(dropTargetText);
|
|
if (ImGui.beginDragDropTarget()) {
|
|
final Object payload = ImGui.acceptDragDropPayloadObject("payload_type");
|
|
if (payload != null) {
|
|
dropTargetText = (String) payload;
|
|
}
|
|
ImGui.endDragDropTarget();
|
|
}
|
|
}
|
|
|
|
private void showBackgroundPicker() {
|
|
ImGui.alignTextToFramePadding();
|
|
ImGui.text("Background color:");
|
|
ImGui.sameLine();
|
|
ImGui.colorEdit3("##click_counter_col", backgroundColor, ImGuiColorEditFlags.NoInputs | ImGuiColorEditFlags.NoDragDrop);
|
|
}
|
|
|
|
private void showResizableInput() {
|
|
ImGui.text("You can use text inputs with auto-resizable strings!");
|
|
ImGui.inputText("Resizable input", resizableStr, ImGuiInputTextFlags.CallbackResize);
|
|
ImGui.text("text len:");
|
|
ImGui.sameLine();
|
|
ImGui.textColored(COLOR_DODGERBLUE, Integer.toString(resizableStr.getLength()));
|
|
ImGui.sameLine();
|
|
ImGui.text("| buffer size:");
|
|
ImGui.sameLine();
|
|
ImGui.textColored(COLOR_CORAL, Integer.toString(resizableStr.getBufferSize()));
|
|
}
|
|
|
|
private void showDemoLink() {
|
|
ImGui.text("Consider to look the original ImGui demo: ");
|
|
ImGui.setNextItemWidth(500);
|
|
ImGui.textColored(COLOR_LIMEGREEN, IMGUI_DEMO_LINK);
|
|
ImGui.sameLine();
|
|
if (ImGui.button("Copy")) {
|
|
ImGui.setClipboardText(IMGUI_DEMO_LINK);
|
|
}
|
|
}
|
|
|
|
private void showImNodesWindow() {
|
|
ImGui.setNextWindowSize(400F, 400F, ImGuiCond.Appearing);
|
|
ImGui.begin("Demo imnodes", showImNodesWindow);
|
|
|
|
ImGui.text("This a demo graph editor for ImNodes");
|
|
|
|
ImNodes.editorContextSet(imNodesContext);
|
|
ImNodes.beginNodeEditor();
|
|
|
|
for (Graph.GraphNode node : graph.nodes.values()) {
|
|
ImNodes.beginNode(node.nodeId);
|
|
|
|
ImNodes.beginNodeTitleBar();
|
|
ImGui.text(node.getName());
|
|
ImNodes.endNodeTitleBar();
|
|
|
|
|
|
ImNodes.beginInputAttribute(node.getInputPinId(), ImNodesPinShape.CircleFilled);
|
|
ImGui.text("In");
|
|
ImNodes.endInputAttribute();
|
|
|
|
ImGui.sameLine();
|
|
|
|
ImNodes.beginOutputAttribute(node.getOutputPinId());
|
|
ImGui.text("Out");
|
|
ImNodes.endOutputAttribute();
|
|
|
|
ImNodes.endNode();
|
|
}
|
|
|
|
int uniqueLinkId = 1;
|
|
for (Graph.GraphNode node : graph.nodes.values()) {
|
|
if (graph.nodes.containsKey(node.outputNodeId)) {
|
|
ImNodes.link(uniqueLinkId++, node.getOutputPinId(), graph.nodes.get(node.outputNodeId).getInputPinId());
|
|
}
|
|
}
|
|
|
|
final boolean isEditorHovered = ImNodes.isEditorHovered();
|
|
|
|
ImNodes.endNodeEditor();
|
|
final ImInt a = new ImInt();
|
|
final ImInt b = new ImInt();
|
|
if (ImNodes.isLinkCreated(a, b)) {
|
|
final Graph.GraphNode source = graph.findByOutput(a.get());
|
|
final Graph.GraphNode target = graph.findByInput(b.get());
|
|
if (source != null && target != null && source.outputNodeId != target.nodeId) {
|
|
source.outputNodeId = target.nodeId;
|
|
}
|
|
}
|
|
|
|
if (ImGui.isMouseClicked(1)) {
|
|
final int hoveredNode = ImNodes.getHoveredNode();
|
|
if (hoveredNode != -1) {
|
|
ImGui.openPopup("node-context");
|
|
ImGui.getStateStorage().setInt(ImGui.getID("delete-node-id"), hoveredNode);
|
|
} else if (isEditorHovered) {
|
|
ImGui.openPopup("node-editor-context");
|
|
}
|
|
}
|
|
|
|
if (ImGui.isPopupOpen("node-context")) {
|
|
final int targetNode = ImGui.getStateStorage().getInt(ImGui.getID("delete-node-id"));
|
|
if (ImGui.beginPopup("node-context")) {
|
|
if (ImGui.button("Delete " + graph.nodes.get(targetNode).getName())) {
|
|
graph.nodes.remove(targetNode);
|
|
ImGui.closeCurrentPopup();
|
|
}
|
|
ImGui.endPopup();
|
|
}
|
|
}
|
|
|
|
if (ImGui.beginPopup("node-editor-context")) {
|
|
if (ImGui.button("Create new node")) {
|
|
final Graph.GraphNode node = graph.createGraphNode();
|
|
ImNodes.setNodeScreenSpacePos(node.nodeId, ImGui.getMousePosX(), ImGui.getMousePosY());
|
|
ImGui.closeCurrentPopup();
|
|
}
|
|
ImGui.endPopup();
|
|
}
|
|
|
|
ImGui.end();
|
|
}
|
|
|
|
private void showImNodeEditorWindow() {
|
|
ImGui.setNextWindowSize(400F, 400F, ImGuiCond.Appearing);
|
|
ImGui.begin("Demo imgui-node-editor", showImNodeEditorWindow);
|
|
|
|
ImGui.text("This a demo graph editor for imgui-node-editor");
|
|
|
|
if (ImGui.button("Navigate to content")) {
|
|
NodeEditor.navigateToContent(1F);
|
|
}
|
|
|
|
NodeEditor.setCurrentEditor(nodeEditorContext);
|
|
NodeEditor.begin("Node Editor");
|
|
|
|
for (Graph.GraphNode node : graph.nodes.values()) {
|
|
NodeEditor.beginNode(node.nodeId);
|
|
|
|
ImGui.text(node.getName());
|
|
|
|
NodeEditor.beginPin(node.getInputPinId(), NodeEditorPinKind.Input);
|
|
ImGui.text("-> In");
|
|
NodeEditor.endPin();
|
|
|
|
ImGui.sameLine();
|
|
|
|
NodeEditor.beginPin(node.getOutputPinId(), NodeEditorPinKind.Output);
|
|
ImGui.text("Out ->");
|
|
NodeEditor.endPin();
|
|
|
|
NodeEditor.endNode();
|
|
}
|
|
|
|
if (NodeEditor.beginCreate()) {
|
|
final ImLong a = new ImLong();
|
|
final ImLong b = new ImLong();
|
|
if (NodeEditor.queryNewLink(a, b)) {
|
|
final Graph.GraphNode source = graph.findByOutput(a.get());
|
|
final Graph.GraphNode target = graph.findByInput(b.get());
|
|
if (source != null && target != null && source.outputNodeId != target.nodeId && NodeEditor.acceptNewItem()) {
|
|
source.outputNodeId = target.nodeId;
|
|
}
|
|
}
|
|
}
|
|
NodeEditor.endCreate();
|
|
|
|
int uniqueLinkId = 1;
|
|
for (Graph.GraphNode node : graph.nodes.values()) {
|
|
if (graph.nodes.containsKey(node.outputNodeId)) {
|
|
NodeEditor.link(uniqueLinkId++, node.getOutputPinId(), graph.nodes.get(node.outputNodeId).getInputPinId());
|
|
}
|
|
}
|
|
|
|
NodeEditor.suspend();
|
|
final long nodeWithContextMenu = NodeEditor.getNodeWithContextMenu();
|
|
if (nodeWithContextMenu != -1) {
|
|
ImGui.openPopup("node-context");
|
|
ImGui.getStateStorage().setInt(ImGui.getID("delete-node-id"), (int) nodeWithContextMenu);
|
|
}
|
|
|
|
if (ImGui.isPopupOpen("node-context")) {
|
|
final int targetNode = ImGui.getStateStorage().getInt(ImGui.getID("delete-node-id"));
|
|
if (ImGui.beginPopup("node-context")) {
|
|
if (ImGui.button("Delete " + graph.nodes.get(targetNode).getName())) {
|
|
graph.nodes.remove(targetNode);
|
|
ImGui.closeCurrentPopup();
|
|
}
|
|
ImGui.endPopup();
|
|
}
|
|
}
|
|
|
|
if (NodeEditor.showBackgroundContextMenu()) {
|
|
ImGui.openPopup("node-editor-context");
|
|
}
|
|
if (ImGui.beginPopup("node-editor-context")) {
|
|
if (ImGui.button("Create new node")) {
|
|
final Graph.GraphNode node = graph.createGraphNode();
|
|
final float canvasX = NodeEditor.toCanvasX(ImGui.getMousePosX());
|
|
final float canvasY = NodeEditor.toCanvasY(ImGui.getMousePosY());
|
|
NodeEditor.setNodePosition(node.nodeId, canvasX, canvasY);
|
|
ImGui.closeCurrentPopup();
|
|
}
|
|
ImGui.endPopup();
|
|
}
|
|
NodeEditor.resume();
|
|
|
|
NodeEditor.end();
|
|
ImGui.end();
|
|
}
|
|
|
|
private int loadTexture(final BufferedImage image) {
|
|
final int[] pixels = new int[image.getWidth() * image.getHeight()];
|
|
image.getRGB(0, 0, image.getWidth(), image.getHeight(), pixels, 0, image.getWidth());
|
|
|
|
final ByteBuffer buffer = BufferUtils.createByteBuffer(image.getWidth() * image.getHeight() * 4); // 4 for RGBA, 3 for RGB
|
|
for (int y = 0; y < image.getHeight(); y++) {
|
|
for (int x = 0; x < image.getWidth(); x++) {
|
|
final int pixel = pixels[y * image.getWidth() + x];
|
|
buffer.put((byte) ((pixel >> 16) & 0xFF));
|
|
buffer.put((byte) ((pixel >> 8) & 0xFF));
|
|
buffer.put((byte) (pixel & 0xFF));
|
|
buffer.put((byte) ((pixel >> 24) & 0xFF));
|
|
}
|
|
}
|
|
buffer.flip();
|
|
|
|
final int textureID = glGenTextures();
|
|
glBindTexture(GL_TEXTURE_2D, textureID);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, image.getWidth(), image.getHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
|
|
|
|
return textureID;
|
|
}
|
|
|
|
|
|
// Simple graph struct for both of node editors demos
|
|
private static final class Graph {
|
|
|
|
private int nextNodeId = 1;
|
|
private int nextPinId = 100;
|
|
|
|
final Map<Integer, GraphNode> nodes = new HashMap<>();
|
|
|
|
private Graph() {
|
|
final GraphNode first = createGraphNode();
|
|
final GraphNode second = createGraphNode();
|
|
first.outputNodeId = second.nodeId;
|
|
}
|
|
|
|
public GraphNode createGraphNode() {
|
|
final GraphNode node = new GraphNode(nextNodeId++, nextPinId++, nextPinId++);
|
|
this.nodes.put(node.nodeId, node);
|
|
return node;
|
|
}
|
|
|
|
public GraphNode findByInput(final long inputPinId) {
|
|
for (GraphNode node : nodes.values()) {
|
|
if (node.getInputPinId() == inputPinId) {
|
|
return node;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public GraphNode findByOutput(final long outputPinId) {
|
|
for (GraphNode node : nodes.values()) {
|
|
if (node.getOutputPinId() == outputPinId) {
|
|
return node;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private static final class GraphNode {
|
|
private final int nodeId;
|
|
private final int inputPinId;
|
|
private final int outputPinId;
|
|
|
|
public int outputNodeId = -1;
|
|
|
|
private GraphNode(final int nodeId, final int inputPinId, final int outputPintId) {
|
|
this.nodeId = nodeId;
|
|
this.inputPinId = inputPinId;
|
|
this.outputPinId = outputPintId;
|
|
}
|
|
|
|
public int getInputPinId() {
|
|
return inputPinId;
|
|
}
|
|
|
|
public int getOutputPinId() {
|
|
return outputPinId;
|
|
}
|
|
|
|
public String getName() {
|
|
return "Node " + (char) (64 + nodeId);
|
|
}
|
|
}
|
|
}
|
|
}
|