diff --git a/example/build.gradle b/example/build.gradle
new file mode 100644
index 0000000..39da07f
--- /dev/null
+++ b/example/build.gradle
@@ -0,0 +1,12 @@
+plugins {
+ id 'application'
+ id 'checkstyle'
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ implementation project(':imgui-app')
+}
diff --git a/example/src/main/java/Main.java b/example/src/main/java/Main.java
new file mode 100644
index 0000000..60b941a
--- /dev/null
+++ b/example/src/main/java/Main.java
@@ -0,0 +1,33 @@
+import imgui.ImGui;
+import imgui.ImGuiIO;
+import imgui.app.Application;
+import imgui.app.Configuration;
+import imgui.flag.ImGuiConfigFlags;
+
+public final class Main extends Application {
+ @Override
+ protected void configure(final Configuration config) {
+ config.setTitle("Example Application");
+ }
+
+ @Override
+ protected void initImGui(final Configuration config) {
+ super.initImGui(config);
+
+ final ImGuiIO io = ImGui.getIO();
+ io.setIniFilename(null); // We don't want to save .ini file
+ io.addConfigFlags(ImGuiConfigFlags.NavEnableKeyboard); // Enable Keyboard Controls
+ io.addConfigFlags(ImGuiConfigFlags.DockingEnable); // Enable Docking
+ io.addConfigFlags(ImGuiConfigFlags.ViewportsEnable); // Enable Multi-Viewport / Platform Windows
+ io.setConfigViewportsNoTaskBarIcon(true);
+ }
+
+ @Override
+ public void process() {
+ ImGui.text("Hello, World");
+ }
+
+ public static void main(final String[] args) {
+ launch(new Main());
+ }
+}
diff --git a/imgui-app/build.gradle b/imgui-app/build.gradle
new file mode 100644
index 0000000..df72155
--- /dev/null
+++ b/imgui-app/build.gradle
@@ -0,0 +1,66 @@
+plugins {
+ id 'java-library'
+ id 'io.freefair.lombok' version '5.3.0'
+ id 'checkstyle'
+ id 'com.jfrog.bintray' version '1.8.4'
+ id 'maven-publish'
+}
+
+sourceCompatibility = 1.8
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ api platform('org.lwjgl:lwjgl-bom:3.2.3')
+
+ api 'org.lwjgl:lwjgl'
+ api 'org.lwjgl:lwjgl-glfw'
+ api 'org.lwjgl:lwjgl-opengl'
+
+ ['natives-linux', 'natives-windows', 'natives-macos'].each {
+ runtime "org.lwjgl:lwjgl::$it"
+ runtime "org.lwjgl:lwjgl-glfw::$it"
+ runtime "org.lwjgl:lwjgl-opengl::$it"
+ }
+
+ api project(':imgui-binding')
+ api project(':imgui-lwjgl3')
+}
+
+jar {
+ from('../bin') {
+ include 'imgui-java64.dll'
+ include 'libimgui-java64.so'
+ include 'libimgui-java64.dylib'
+ into 'io/imgui/java/native-bin/'
+ }
+}
+
+task sourcesJar(type: Jar, dependsOn: classes) {
+ archiveClassifier.set('sources')
+ from sourceSets.main.allSource
+}
+
+publishing {
+ publications {
+ MyPublication(MavenPublication) {
+ from components.java
+ groupId 'io.imgui.java'
+ artifactId 'imgui-java-app'
+ artifact sourcesJar
+ version property('version')
+ }
+ }
+}
+
+bintray {
+ user = System.getenv('BINTRAY_USER')
+ key = System.getenv('BINTRAY_API_KEY')
+ publications = ['MyPublication']
+ pkg {
+ repo = 'io.imgui.java'
+ name = 'imgui-java-app'
+ }
+}
diff --git a/imgui-app/lombok.config b/imgui-app/lombok.config
new file mode 100644
index 0000000..6aa51d7
--- /dev/null
+++ b/imgui-app/lombok.config
@@ -0,0 +1,2 @@
+# This file is generated by the 'io.freefair.lombok' Gradle plugin
+config.stopBubbling = true
diff --git a/imgui-app/src/main/java/imgui/app/Application.java b/imgui-app/src/main/java/imgui/app/Application.java
new file mode 100644
index 0000000..ed7a6dd
--- /dev/null
+++ b/imgui-app/src/main/java/imgui/app/Application.java
@@ -0,0 +1,81 @@
+package imgui.app;
+
+/**
+ * Application class from which ImGui applications extend.
+ * Serves as an abstraction layer to hide all low-level details about window creation and rendering routine.
+ *
+ *
Life-cycle
+ * The entry point for ImGui applications is the Application class and {@link #launch(Application)} method.
+ * It initializes application instance and starts the main application loop.
+ *
+ *
+ * - {@link #configure(Configuration)} It's called before window creation, so only basic application setups are expected.
+ * - {@link #initWindow(Configuration)} Method creates application window.
+ * - {@link #initImGui(Configuration)} Method initializes Dear ImGui context. Could be used to do Dear ImGui setup as well.
+ * - {@link #process()} Method is meant to be overridden with user application logic.
+ * - {@link #disposeImGui()} Destroys Dear ImGui context.
+ * - {@link #disposeWindow()} Destroys application window.
+ *
+ *
+ * As it could be seen, ImGui application differs from the classic one in the way of its life-cycle flow.
+ * Instead of creating widgets and adding listeners to them we have an application loop where everything is handled right away.
+ * Read more about Immediate GUI mode to understand that paradigm better.
+ *
+ *
Example
+ * The simplest application example could be done in the next way:
+ *
+ * {@code
+ * import imgui.ImGui;
+ * import imgui.app.Application;
+ * import imgui.app.Configuration;
+ *
+ * public class Main extends Application {
+ * @Override
+ * protected void configure(final Configuration config) {
+ * config.setTitle("Example Application");
+ * }
+ *
+ * @Override
+ * public void process() {
+ * ImGui.text("Hello, World");
+ * }
+ *
+ * public static void main(final String[] args) {
+ * launch(new Main());
+ * }
+ * }
+ * }
+ *
+ * As its said, {@link #process()} method is meant to be overridden. All your application logic should go there.
+ *
+ *
Threading
+ * Unlike other Java applications, ImGui is about "one thread for everything". You still can use multi-threading, but be careful.
+ * For example, large list of computations could be separated between application ticks. {@link #process()} method is called constantly.
+ * Use that wisely and remember that all GUI should be in the main thread.
+ */
+public abstract class Application extends Window {
+ /**
+ * Method is called before window creation. Could be used to provide basic window information, like title name etc.
+ *
+ * @param config configuration object with basic window information
+ */
+ protected void configure(final Configuration config) {
+ }
+
+ /**
+ * Entry point of any ImGui application. Use it to start the application loop.
+ *
+ * @param app application instance to run
+ */
+ public static void launch(final Application app) {
+ initialize(app);
+ app.run();
+ app.dispose();
+ }
+
+ private static void initialize(final Application app) {
+ final Configuration config = new Configuration();
+ app.configure(config);
+ app.init(config);
+ }
+}
diff --git a/imgui-app/src/main/java/imgui/app/Configuration.java b/imgui-app/src/main/java/imgui/app/Configuration.java
new file mode 100644
index 0000000..92f6d55
--- /dev/null
+++ b/imgui-app/src/main/java/imgui/app/Configuration.java
@@ -0,0 +1,26 @@
+package imgui.app;
+
+import lombok.Data;
+
+/**
+ * Data class to provide basic information about the window. Like, the title name etc.
+ */
+@Data
+public final class Configuration {
+ /**
+ * Application title.
+ */
+ private String title = "ImGui Java Application";
+ /**
+ * Application window width.
+ */
+ private int width = 1280;
+ /**
+ * Application window height.
+ */
+ private int height = 768;
+ /**
+ * When true, application will be maximized by default.
+ */
+ private boolean fullScreen = false;
+}
diff --git a/imgui-app/src/main/java/imgui/app/Window.java b/imgui-app/src/main/java/imgui/app/Window.java
new file mode 100644
index 0000000..c5b36d8
--- /dev/null
+++ b/imgui-app/src/main/java/imgui/app/Window.java
@@ -0,0 +1,186 @@
+package imgui.app;
+
+import imgui.ImGui;
+import imgui.flag.ImGuiConfigFlags;
+import imgui.gl3.ImGuiImplGl3;
+import imgui.glfw.ImGuiImplGlfw;
+import org.lwjgl.glfw.Callbacks;
+import org.lwjgl.glfw.GLFW;
+import org.lwjgl.glfw.GLFWErrorCallback;
+import org.lwjgl.glfw.GLFWVidMode;
+import org.lwjgl.opengl.GL;
+import org.lwjgl.opengl.GL32;
+import org.lwjgl.system.MemoryStack;
+import org.lwjgl.system.MemoryUtil;
+
+import java.awt.Color;
+import java.nio.IntBuffer;
+import java.util.Objects;
+
+/**
+ * Low-level abstraction, which creates application window and starts the main loop.
+ * It's recommended to use {@link Application}, but this class could be extended directly as well.
+ * Take a note, that in this case you need to call all application loop methods ({@link #init(Configuration)}, {@link #run()}, {@link #dispose()}) manually.
+ */
+public abstract class Window {
+ private final ImGuiImplGlfw imGuiGlfw = new ImGuiImplGlfw();
+ private final ImGuiImplGl3 imGuiGl3 = new ImGuiImplGl3();
+
+ private String glslVersion = null;
+
+ /**
+ * Pointer to native GLFW window.
+ */
+ protected long handle;
+ /**
+ * Background color of the window.
+ */
+ protected Color colorBg = Color.GRAY;
+
+ /**
+ * Method to initialize application.
+ *
+ * @param config configuration object with basic window information
+ */
+ protected final void init(final Configuration config) {
+ initWindow(config);
+ initImGui(config);
+ imGuiGlfw.init(handle, true);
+ imGuiGl3.init(glslVersion);
+ }
+
+ /**
+ * Method to dispose all used application resources and destroy its window.
+ */
+ protected final void dispose() {
+ imGuiGl3.dispose();
+ imGuiGlfw.dispose();
+ disposeImGui();
+ disposeWindow();
+ }
+
+ /**
+ * Method to create and initialize GLFW window.
+ *
+ * @param config configuration object with basic window information
+ */
+ protected void initWindow(final Configuration config) {
+ GLFWErrorCallback.createPrint(System.err).set();
+
+ if (!GLFW.glfwInit()) {
+ throw new IllegalStateException("Unable to initialize GLFW");
+ }
+
+ decideGlGlslVersions();
+
+ GLFW.glfwWindowHint(GLFW.GLFW_VISIBLE, GLFW.GLFW_FALSE);
+ handle = GLFW.glfwCreateWindow(config.getWidth(), config.getHeight(), config.getTitle(), MemoryUtil.NULL, MemoryUtil.NULL);
+
+ if (handle == MemoryUtil.NULL) {
+ throw new RuntimeException("Failed to create the GLFW window");
+ }
+
+ try (MemoryStack stack = MemoryStack.stackPush()) {
+ final IntBuffer pWidth = stack.mallocInt(1); // int*
+ final IntBuffer pHeight = stack.mallocInt(1); // int*
+
+ GLFW.glfwGetWindowSize(handle, pWidth, pHeight);
+
+ final GLFWVidMode vidmode = Objects.requireNonNull(GLFW.glfwGetVideoMode(GLFW.glfwGetPrimaryMonitor()));
+
+ GLFW.glfwSetWindowPos(handle, (vidmode.width() - pWidth.get(0)) / 2, (vidmode.height() - pHeight.get(0)) / 2);
+ }
+
+ GLFW.glfwMakeContextCurrent(handle);
+ GLFW.glfwSwapInterval(GLFW.GLFW_TRUE);
+ GLFW.glfwShowWindow(handle);
+
+ if (config.isFullScreen()) {
+ GLFW.glfwMaximizeWindow(handle);
+ }
+
+ GL.createCapabilities();
+ }
+
+ private void decideGlGlslVersions() {
+ final boolean isMac = System.getProperty("os.name").toLowerCase().contains("mac");
+ if (isMac) {
+ glslVersion = "#version 150";
+ GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MAJOR, 3);
+ GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MINOR, 2);
+ GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_PROFILE, GLFW.GLFW_OPENGL_CORE_PROFILE); // 3.2+ only
+ GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_FORWARD_COMPAT, GLFW.GLFW_TRUE); // Required on Mac
+ } else {
+ glslVersion = "#version 130";
+ GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MAJOR, 3);
+ GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MINOR, 0);
+ }
+ }
+
+ /**
+ * Method to initialize Dear ImGui context. Could be overridden to do custom Dear ImGui setup before application start.
+ *
+ * @param config configuration object with basic window information
+ */
+ protected void initImGui(final Configuration config) {
+ ImGui.createContext();
+ }
+
+ /**
+ * Main application loop.
+ */
+ protected final void run() {
+ while (!GLFW.glfwWindowShouldClose(handle)) {
+ startFrame();
+ process();
+ endFrame();
+ }
+ }
+
+ /**
+ * Method to be overridden by user to provide main application logic.
+ */
+ public abstract void process();
+
+ private void startFrame() {
+ if (colorBg != null) {
+ GL32.glClearColor(colorBg.getRed() / 255f, colorBg.getGreen() / 255f, colorBg.getBlue() / 255f, colorBg.getAlpha() / 255f);
+ }
+
+ GL32.glClear(GL32.GL_COLOR_BUFFER_BIT | GL32.GL_DEPTH_BUFFER_BIT);
+ imGuiGlfw.newFrame();
+ ImGui.newFrame();
+ }
+
+ private void endFrame() {
+ ImGui.render();
+ imGuiGl3.renderDrawData(ImGui.getDrawData());
+
+ if (ImGui.getIO().hasConfigFlags(ImGuiConfigFlags.ViewportsEnable)) {
+ final long backupWindowPtr = GLFW.glfwGetCurrentContext();
+ ImGui.updatePlatformWindows();
+ ImGui.renderPlatformWindowsDefault();
+ GLFW.glfwMakeContextCurrent(backupWindowPtr);
+ }
+
+ GLFW.glfwSwapBuffers(handle);
+ GLFW.glfwPollEvents();
+ }
+
+ /**
+ * Method to destroy Dear ImGui context.
+ */
+ protected void disposeImGui() {
+ ImGui.destroyContext();
+ }
+
+ /**
+ * Method to destroy GLFW window.
+ */
+ protected void disposeWindow() {
+ Callbacks.glfwFreeCallbacks(handle);
+ GLFW.glfwDestroyWindow(handle);
+ GLFW.glfwTerminate();
+ Objects.requireNonNull(GLFW.glfwSetErrorCallback(null)).free();
+ }
+}
diff --git a/settings.gradle b/settings.gradle
index 3ee5cd0..f07d364 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -2,4 +2,6 @@ rootProject.name = 'imgui-java'
include 'imgui-binding'
include 'imgui-lwjgl3'
include 'imgui-binding-natives'
+include 'imgui-app'
+include 'example'