├── .gitignore ├── DroidSans.ttf ├── dub.json ├── examples ├── colors │ ├── colors.d │ ├── dub.json │ ├── dub.selections.json │ └── window.d ├── demo │ ├── demo.d │ ├── dub.json │ ├── dub.selections.json │ └── window.d └── memory │ ├── SciTEDirectory.properties │ ├── dub.json │ ├── dub.selections.json │ ├── memory.d │ └── window.d ├── lib ├── x86-64 │ └── glfw3dll.lib └── x86 │ └── glfw3dll.lib ├── license.txt ├── readme.md ├── screenshot └── imgui.png └── src ├── deimos └── glfw │ ├── glfw2.d │ └── glfw3.d ├── glad └── gl │ ├── all.d │ ├── enums.d │ ├── ext.d │ ├── funcs.d │ ├── gl.d │ ├── loader.d │ └── types.d ├── glwtf ├── exception.d ├── glfw.d ├── input.d ├── signals.d └── window.d └── imgui ├── api.d ├── engine.d ├── gl3_renderer.d ├── package.d ├── stdb_truetype.d └── util.d /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /bin/* 3 | /examples/bin/* 4 | /data 5 | /todo/* 6 | *.a 7 | *.c 8 | *.dat 9 | *.h 10 | *.o 11 | *.7z 12 | *.di 13 | *.cpp 14 | *.dat 15 | *.log 16 | *.lib 17 | !implib/*.lib 18 | *.dll 19 | *.exe 20 | *.map 21 | *.obj 22 | *.rar 23 | *.xml 24 | *.deps 25 | *.modTime 26 | .sandbox 27 | .dub 28 | -------------------------------------------------------------------------------- /DroidSans.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d-gamedev-team/dimgui/c58f56407b21f4ec702883acd1a5f0c611e2a25e/DroidSans.ttf -------------------------------------------------------------------------------- /dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dimgui", 3 | 4 | "description": "dimgui is a D port of the imgui OpenGL GUI library", 5 | 6 | "authors": [ 7 | "Mikko Mononen", 8 | "Adrien Herubel", 9 | "Andrej Mitrovic" 10 | ], 11 | 12 | "homepage": "https://github.com/d-gamedev-team/dimgui", 13 | 14 | "copyright": "Copyright (c) 2009-2010 Mikko Mononen memon@inside.org", 15 | 16 | "license": "zlib", 17 | 18 | "targetName": "imgui", 19 | 20 | "targetType": "staticLibrary", 21 | 22 | "targetPath" : "bin", 23 | 24 | "sourcePaths": [ 25 | "src" 26 | ], 27 | 28 | "libs-posix": [ 29 | "dl", 30 | "glfw" 31 | ], 32 | 33 | "libs-linux": [ 34 | "GL", 35 | "Xrandr", 36 | "Xext", 37 | "Xxf86vm", 38 | "Xi", 39 | "Xcursor", 40 | "Xinerama", 41 | "X11" 42 | ], 43 | 44 | "libs-windows": ["glfw3dll"], 45 | 46 | "lflags-windows-x86": ["/LIBPATH:$PACKAGE_DIR\\lib\\x86"], 47 | "lflags-windows-x86_64": ["/LIBPATH:$PACKAGE_DIR\\lib\\x86-64"], 48 | 49 | "subPackages": [ 50 | "examples/colors", 51 | "examples/demo", 52 | "examples/memory" 53 | ] 54 | } 55 | -------------------------------------------------------------------------------- /examples/colors/colors.d: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Andrej Mitrovic 2014. 3 | * Distributed under the Boost Software License, Version 1.0. 4 | * (See accompanying file LICENSE_1_0.txt or copy at 5 | * http://www.boost.org/LICENSE_1_0.txt) 6 | */ 7 | module colors; 8 | 9 | import std.exception; 10 | import std.file; 11 | import std.path; 12 | import std.range; 13 | import std.stdio; 14 | import std.string; 15 | 16 | import deimos.glfw.glfw3; 17 | 18 | import glad.gl.enums; 19 | import glad.gl.ext; 20 | import glad.gl.funcs; 21 | import glad.gl.loader; 22 | import glad.gl.types; 23 | 24 | import glwtf.input; 25 | import glwtf.window; 26 | 27 | import imgui; 28 | 29 | import window; 30 | 31 | version (OSX) 32 | version = MaybeHighResolutionDisplay; 33 | version (iOS) 34 | version = MaybeHighResolutionDisplay; 35 | 36 | struct RGBAF 37 | { 38 | float r = 0.0, g = 0.0, b = 0.0, a = 0.0; 39 | 40 | RGBAF opBinary(string op)(RGBAF rgba) 41 | { 42 | RGBAF res = this; 43 | 44 | mixin("res.r = res.r " ~ op ~ " rgba.r;"); 45 | mixin("res.g = res.g " ~ op ~ " rgba.g;"); 46 | mixin("res.b = res.b " ~ op ~ " rgba.b;"); 47 | mixin("res.a = res.a " ~ op ~ " rgba.a;"); 48 | 49 | return res; 50 | } 51 | } 52 | 53 | auto clamp(T1, T2, T3)(T1 value, T2 min, T3 max) 54 | { 55 | return (((value) >(max)) ? (max) : (((value) <(min)) ? (min) : (value))); 56 | } 57 | 58 | RGBA toRGBA(RGBAF c) 59 | { 60 | return RGBA(cast(ubyte)(255.0f * clamp(c.r, 0.0, 1.0)), 61 | cast(ubyte)(255.0f * clamp(c.g, 0.0, 1.0)), 62 | cast(ubyte)(255.0f * clamp(c.b, 0.0, 1.0)), 63 | cast(ubyte)(255.0f * clamp(c.a, 0.0, 1.0))); 64 | } 65 | 66 | RGBAF toRGBAF(RGBA c) 67 | { 68 | return RGBAF(clamp((cast(float)c.r) / 255.0, 0.0, 1.0), 69 | clamp((cast(float)c.g) / 255.0, 0.0, 1.0), 70 | clamp((cast(float)c.b) / 255.0, 0.0, 1.0), 71 | clamp((cast(float)c.a) / 255.0, 0.0, 1.0)); 72 | } 73 | 74 | struct GUI 75 | { 76 | this(Window window) 77 | { 78 | this.window = window; 79 | 80 | window.on_scroll.strongConnect(&onScroll); 81 | 82 | int width; 83 | int height; 84 | glfwGetFramebufferSize(window.window, &width, &height); 85 | 86 | // trigger initial viewport transform. 87 | onWindowResize(width, height); 88 | 89 | window.on_resize.strongConnect(&onWindowResize); 90 | 91 | oldColorScheme = defaultColorScheme; 92 | updateColorScheme(); 93 | } 94 | 95 | ColorScheme oldColorScheme; 96 | 97 | void updateColorScheme() 98 | { 99 | auto rgbaBright = RGBAF(brightness, brightness, brightness, 0); 100 | 101 | foreach (ref outColor, oldColor; zip(defaultColorScheme.walkColors, oldColorScheme.walkColors)) 102 | { 103 | auto oldRGBAF = toRGBAF(*oldColor); 104 | auto res = oldRGBAF + color + rgbaBright; 105 | *outColor = res.toRGBA(); 106 | } 107 | } 108 | 109 | void render() 110 | { 111 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 112 | 113 | // Mouse states 114 | ubyte mousebutton = 0; 115 | double mouseX; 116 | double mouseY; 117 | glfwGetCursorPos(window.window, &mouseX, &mouseY); 118 | 119 | version (MaybeHighResolutionDisplay) 120 | { 121 | // Scale the cursor position for high-resolution displays. 122 | if (mouseXToWindowFactor == 0) // need to initialize 123 | { 124 | int virtualWindowWidth, virtualWindowHeight; 125 | glfwGetWindowSize(window.window, &virtualWindowWidth, &virtualWindowHeight); 126 | if (virtualWindowWidth != 0 && virtualWindowHeight != 0) 127 | { 128 | int frameBufferWidth, frameBufferHeight; 129 | glfwGetFramebufferSize(window.window, &frameBufferWidth, &frameBufferHeight); 130 | mouseXToWindowFactor = double(frameBufferWidth) / virtualWindowWidth; 131 | mouseYToWindowFactor = double(frameBufferHeight) / virtualWindowHeight; 132 | } 133 | } 134 | mouseX *= mouseXToWindowFactor; 135 | mouseY *= mouseYToWindowFactor; 136 | } 137 | 138 | const scrollAreaWidth = windowWidth / 4; 139 | const scrollAreaHeight = windowHeight - 20; 140 | 141 | int mousex = cast(int)mouseX; 142 | int mousey = cast(int)mouseY; 143 | 144 | mousey = windowHeight - mousey; 145 | int leftButton = glfwGetMouseButton(window.window, GLFW_MOUSE_BUTTON_LEFT); 146 | int rightButton = glfwGetMouseButton(window.window, GLFW_MOUSE_BUTTON_RIGHT); 147 | int middleButton = glfwGetMouseButton(window.window, GLFW_MOUSE_BUTTON_MIDDLE); 148 | 149 | if (leftButton == GLFW_PRESS) 150 | mousebutton |= MouseButton.left; 151 | 152 | imguiBeginFrame(mousex, mousey, mousebutton, mouseScroll); 153 | 154 | if (mouseScroll != 0) 155 | mouseScroll = 0; 156 | 157 | imguiBeginScrollArea("Scroll area 1", 10, 10, scrollAreaWidth, scrollAreaHeight, &scrollArea1); 158 | 159 | imguiSeparatorLine(); 160 | imguiSeparator(); 161 | 162 | if (imguiSlider("Transparency Alpha", &color.a, 0.0, 1.0, 0.01f)) 163 | updateColorScheme(); 164 | 165 | if (imguiSlider("Brightness", &brightness, -1.0, 1.0, 0.01f)) 166 | updateColorScheme(); 167 | 168 | if (imguiSlider("Red Channel", &color.r, 0.0, 1.0, 0.01f)) 169 | updateColorScheme(); 170 | 171 | if (imguiSlider("Green Channel", &color.g, 0.0, 1.0, 0.01f)) 172 | updateColorScheme(); 173 | 174 | if (imguiSlider("Blue Channel", &color.b, 0.0, 1.0, 0.01f)) 175 | updateColorScheme(); 176 | 177 | // should not be clickable 178 | enforce(!imguiSlider("Disabled slider", &disabledSliderValue, 0.0, 100.0, 1.0f, Enabled.no)); 179 | 180 | imguiIndent(); 181 | imguiLabel("Indented"); 182 | imguiUnindent(); 183 | imguiLabel("Unindented"); 184 | 185 | imguiEndScrollArea(); 186 | 187 | imguiBeginScrollArea("Scroll area 2", 20 + (1 * scrollAreaWidth), 10, scrollAreaWidth, scrollAreaHeight, &scrollArea2); 188 | imguiSeparatorLine(); 189 | imguiSeparator(); 190 | 191 | foreach (i; 0 .. 100) 192 | imguiLabel("A wall of text"); 193 | 194 | imguiEndScrollArea(); 195 | 196 | imguiBeginScrollArea("Scroll area 3", 30 + (2 * scrollAreaWidth), 10, scrollAreaWidth, scrollAreaHeight, &scrollArea3); 197 | imguiLabel(lastInfo); 198 | imguiEndScrollArea(); 199 | 200 | imguiEndFrame(); 201 | 202 | const graphicsXPos = 40 + (3 * scrollAreaWidth); 203 | 204 | imguiDrawText(graphicsXPos, scrollAreaHeight, TextAlign.left, "Free text", RGBA(32, 192, 32, 192)); 205 | imguiDrawText(graphicsXPos + 100, windowHeight - 40, TextAlign.right, "Free text", RGBA(32, 32, 192, 192)); 206 | imguiDrawText(graphicsXPos + 50, windowHeight - 60, TextAlign.center, "Free text", RGBA(192, 32, 32, 192)); 207 | 208 | imguiDrawLine(graphicsXPos, windowHeight - 80, graphicsXPos + 100, windowHeight - 60, 1.0f, RGBA(32, 192, 32, 192)); 209 | imguiDrawLine(graphicsXPos, windowHeight - 100, graphicsXPos + 100, windowHeight - 80, 2.0, RGBA(32, 32, 192, 192)); 210 | imguiDrawLine(graphicsXPos, windowHeight - 120, graphicsXPos + 100, windowHeight - 100, 3.0, RGBA(192, 32, 32, 192)); 211 | 212 | imguiDrawRoundedRect(graphicsXPos, windowHeight - 240, 100, 100, 5.0, RGBA(32, 192, 32, 192)); 213 | imguiDrawRoundedRect(graphicsXPos, windowHeight - 350, 100, 100, 10.0, RGBA(32, 32, 192, 192)); 214 | imguiDrawRoundedRect(graphicsXPos, windowHeight - 470, 100, 100, 20.0, RGBA(192, 32, 32, 192)); 215 | 216 | imguiDrawRect(graphicsXPos, windowHeight - 590, 100, 100, RGBA(32, 192, 32, 192)); 217 | imguiDrawRect(graphicsXPos, windowHeight - 710, 100, 100, RGBA(32, 32, 192, 192)); 218 | imguiDrawRect(graphicsXPos, windowHeight - 830, 100, 100, RGBA(192, 32, 32, 192)); 219 | 220 | imguiRender(windowWidth, windowHeight); 221 | } 222 | 223 | /** 224 | This tells OpenGL what area of the available area we are 225 | rendering to. In this case, we change it to match the 226 | full available area. Without this function call resizing 227 | the window would have no effect on the rendering. 228 | */ 229 | void onWindowResize(int width, int height) 230 | { 231 | // bottom-left position. 232 | enum int x = 0; 233 | enum int y = 0; 234 | 235 | /** 236 | This function defines the current viewport transform. 237 | It defines as a region of the window, specified by the 238 | bottom-left position and a width/height. 239 | 240 | Note about the viewport transform: 241 | It is the process of transforming vertex data from normalized 242 | device coordinate space to window space. It specifies the 243 | viewable region of a window. 244 | */ 245 | glfwGetFramebufferSize(window.window, &width, &height); 246 | glViewport(x, y, width, height); 247 | 248 | windowWidth = width; 249 | windowHeight = height; 250 | version (MaybeHighResolutionDisplay) 251 | { 252 | mouseXToWindowFactor = 0; 253 | mouseYToWindowFactor = 0; 254 | } 255 | } 256 | 257 | void onScroll(double hOffset, double vOffset) 258 | { 259 | mouseScroll = -cast(int)vOffset; 260 | } 261 | 262 | private: 263 | Window window; 264 | int windowWidth; 265 | int windowHeight; 266 | version (MaybeHighResolutionDisplay) 267 | { 268 | double mouseXToWindowFactor = 0; 269 | double mouseYToWindowFactor = 0; 270 | } 271 | 272 | bool checkState1 = false; 273 | bool checkState2 = false; 274 | bool checkState3 = true; 275 | bool collapseState1 = true; 276 | bool collapseState2 = false; 277 | 278 | RGBAF color; 279 | float brightness = 0; 280 | 281 | float disabledSliderValue = 30.0; 282 | int scrollArea1 = 0; 283 | int scrollArea2 = 0; 284 | int scrollArea3 = 0; 285 | int mouseScroll = 0; 286 | 287 | char[] lastInfo; // last clicked element information 288 | } 289 | 290 | int main(string[] args) 291 | { 292 | int width = 1024, height = 768; 293 | 294 | auto window = createWindow("imgui", WindowMode.windowed, width, height); 295 | scope (exit) destroy(window); 296 | 297 | GUI gui = GUI(window); 298 | 299 | glfwSwapInterval(1); 300 | 301 | string fontPath = thisExePath().dirName().buildPath("../").buildPath("DroidSans.ttf"); 302 | 303 | enforce(imguiInit(fontPath)); 304 | 305 | glClearColor(0.8f, 0.8f, 0.8f, 1.0f); 306 | glEnable(GL_BLEND); 307 | glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 308 | glDisable(GL_DEPTH_TEST); 309 | 310 | while (!glfwWindowShouldClose(window.window)) 311 | { 312 | gui.render(); 313 | 314 | /* Swap front and back buffers. */ 315 | window.swap_buffers(); 316 | 317 | /* Poll for and process events. */ 318 | glfwPollEvents(); 319 | 320 | if (window.is_key_down(GLFW_KEY_ESCAPE)) 321 | glfwSetWindowShouldClose(window.window, true); 322 | } 323 | 324 | // Clean UI 325 | imguiDestroy(); 326 | 327 | return 0; 328 | } 329 | -------------------------------------------------------------------------------- /examples/colors/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "colors", 3 | 4 | "description": "colors", 5 | 6 | "authors": [ 7 | "Adrien Herubel", 8 | "Andrej Mitrovic" 9 | ], 10 | 11 | "homepage": "https://github.com/d-gamedev-team/dimgui", 12 | 13 | "copyright": "Copyright (C) 2012 - 2013 Adrien Herubel", 14 | 15 | "license": "zlib", 16 | 17 | "targetName" : "colors", 18 | 19 | "targetPath" : "../../bin", 20 | 21 | "targetType": "executable", 22 | 23 | "sourcePaths": ["."], 24 | 25 | "mainSourceFile": "colors.d", 26 | 27 | "dependencies": { 28 | "dimgui": {"path": "../../", "version": "~master"} 29 | }, 30 | } 31 | -------------------------------------------------------------------------------- /examples/colors/dub.selections.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileVersion": 1, 3 | "versions": {} 4 | } -------------------------------------------------------------------------------- /examples/colors/window.d: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Andrej Mitrovic 2014. 3 | * Distributed under the Boost Software License, Version 1.0. 4 | * (See accompanying file LICENSE_1_0.txt or copy at 5 | * http://www.boost.org/LICENSE_1_0.txt) 6 | */ 7 | module window; 8 | 9 | /** 10 | Contains various helpers, common code, and initialization routines. 11 | */ 12 | 13 | import std.algorithm : min; 14 | import std.exception : enforce; 15 | import std.functional : toDelegate; 16 | import std.stdio : stderr; 17 | import std.string : format; 18 | 19 | import deimos.glfw.glfw3; 20 | 21 | import glad.gl.enums; 22 | import glad.gl.ext; 23 | import glad.gl.funcs; 24 | import glad.gl.loader; 25 | import glad.gl.types; 26 | 27 | import glwtf.input; 28 | import glwtf.window; 29 | 30 | /// init 31 | shared static this() 32 | { 33 | enforce(glfwInit()); 34 | } 35 | 36 | /// uninit 37 | shared static ~this() 38 | { 39 | glfwTerminate(); 40 | } 41 | 42 | /// 43 | enum WindowMode 44 | { 45 | fullscreen, 46 | windowed, 47 | } 48 | 49 | /** 50 | Create a window, an OpenGL 3.x context, and set up some other 51 | common routines for error handling, window resizing, etc. 52 | */ 53 | Window createWindow(string windowName, WindowMode windowMode = WindowMode.windowed, int width = 1024, int height = 768) 54 | { 55 | auto vidMode = glfwGetVideoMode(glfwGetPrimaryMonitor()); 56 | 57 | // constrain the window size so it isn't larger than the desktop size. 58 | width = min(width, vidMode.width); 59 | height = min(height, vidMode.height); 60 | 61 | // set the window to be initially inivisible since we're repositioning it. 62 | glfwWindowHint(GLFW_VISIBLE, 0); 63 | 64 | // enable debugging 65 | glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, 1); 66 | 67 | Window window = createWindowContext(windowName, WindowMode.windowed, width, height); 68 | 69 | // center the window on the screen 70 | glfwSetWindowPos(window.window, (vidMode.width - width) / 2, (vidMode.height - height) / 2); 71 | 72 | // glfw-specific error routine (not a generic GL error handler) 73 | register_glfw_error_callback(&glfwErrorCallback); 74 | 75 | // anti-aliasing number of samples. 76 | window.samples = 4; 77 | 78 | // activate an opengl context. 79 | window.make_context_current(); 80 | 81 | // load all OpenGL function pointers via glad. 82 | enforce(gladLoadGL()); 83 | 84 | enforce(glGenBuffers !is null); 85 | 86 | // only interested in GL 3.x 87 | enforce(GLVersion.major >= 3); 88 | 89 | // turn v-sync off. 90 | glfwSwapInterval(0); 91 | 92 | version (OSX) 93 | { 94 | // GL_ARM_debug_output and GL_KHR_debug are not supported under OS X 10.9.3 95 | } 96 | else 97 | { 98 | // ensure the debug output extension is supported 99 | enforce(GL_ARB_debug_output || GL_KHR_debug); 100 | 101 | // cast: workaround for 'nothrow' propagation bug (haven't been able to reduce it) 102 | auto hookDebugCallback = GL_ARB_debug_output ? glDebugMessageCallbackARB 103 | : cast(typeof(glDebugMessageCallbackARB))glDebugMessageCallback; 104 | 105 | 106 | // hook the debug callback 107 | // cast: when using derelict it assumes its nothrow 108 | hookDebugCallback(cast(GLDEBUGPROCARB)&glErrorCallback, null); 109 | 110 | // enable proper stack tracing support (otherwise we'd get random failures at runtime) 111 | glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB); 112 | } 113 | 114 | // finally show the window 115 | glfwShowWindow(window.window); 116 | 117 | return window; 118 | } 119 | 120 | /** Create a window and an OpenGL context. */ 121 | Window createWindowContext(string windowName, WindowMode windowMode, int width, int height) 122 | { 123 | auto window = new Window(); 124 | auto monitor = windowMode == WindowMode.fullscreen ? glfwGetPrimaryMonitor() : null; 125 | auto context = window.create_highest_available_context(width, height, windowName, monitor, null, GLFW_OPENGL_CORE_PROFILE); 126 | 127 | // ensure we've loaded a proper context 128 | enforce(context.major >= 3); 129 | 130 | return window; 131 | } 132 | 133 | /** Just emit errors to stderr on GLFW errors. */ 134 | void glfwErrorCallback(int code, string msg) 135 | { 136 | stderr.writefln("Error (%s): %s", code, msg); 137 | } 138 | 139 | /// 140 | class GLException : Exception 141 | { 142 | @safe pure nothrow this(string msg = "", string file = __FILE__, size_t line = __LINE__, Throwable next = null) 143 | { 144 | super(msg, file, line, next); 145 | } 146 | 147 | @safe pure nothrow this(string msg, Throwable next, string file = __FILE__, size_t line = __LINE__) 148 | { 149 | super(msg, file, line, next); 150 | } 151 | } 152 | 153 | /** 154 | GL_ARB_debug_output or GL_KHR_debug callback. 155 | 156 | Throwing exceptions across language boundaries is ok as 157 | long as $(B GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB) is enabled. 158 | */ 159 | extern (System) 160 | private void glErrorCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, in GLchar* message, GLvoid* userParam) 161 | { 162 | //string msg = format("glErrorCallback: source: %s, type: %s, id: %s, severity: %s, length: %s, message: %s, userParam: %s", 163 | // source, type, id, severity, length, message.to!string, userParam); 164 | 165 | //stderr.writeln(msg); 166 | } 167 | -------------------------------------------------------------------------------- /examples/demo/demo.d: -------------------------------------------------------------------------------- 1 | module demo; 2 | 3 | import std.exception; 4 | import std.file; 5 | import std.path; 6 | import std.stdio; 7 | import std.string; 8 | 9 | import deimos.glfw.glfw3; 10 | 11 | import glad.gl.enums; 12 | import glad.gl.ext; 13 | import glad.gl.funcs; 14 | import glad.gl.loader; 15 | import glad.gl.types; 16 | 17 | import glwtf.input; 18 | import glwtf.window; 19 | 20 | import imgui; 21 | 22 | import window; 23 | 24 | version (OSX) 25 | version = MaybeHighResolutionDisplay; 26 | version (iOS) 27 | version = MaybeHighResolutionDisplay; 28 | 29 | struct GUI 30 | { 31 | this(Window window) 32 | { 33 | this.window = window; 34 | 35 | window.on_scroll.strongConnect(&onScroll); 36 | 37 | int width; 38 | int height; 39 | glfwGetFramebufferSize(window.window, &width, &height); 40 | 41 | // trigger initial viewport transform. 42 | onWindowResize(width, height); 43 | 44 | window.on_resize.strongConnect(&onWindowResize); 45 | 46 | // Not really needed, but makes it obvious what we're doing 47 | textEntered = textInputBuffer[0 .. 0]; 48 | 49 | glfwSetCharCallback(window.window, &getUnicode); 50 | glfwSetKeyCallback(window.window, &getKey); 51 | } 52 | 53 | void render() 54 | { 55 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 56 | 57 | // Mouse states 58 | ubyte mousebutton = 0; 59 | double mouseX; 60 | double mouseY; 61 | glfwGetCursorPos(window.window, &mouseX, &mouseY); 62 | 63 | version (MaybeHighResolutionDisplay) 64 | { 65 | // Scale the cursor position for high-resolution displays. 66 | if (mouseXToWindowFactor == 0) // need to initialize 67 | { 68 | int virtualWindowWidth, virtualWindowHeight; 69 | glfwGetWindowSize(window.window, &virtualWindowWidth, &virtualWindowHeight); 70 | if (virtualWindowWidth != 0 && virtualWindowHeight != 0) 71 | { 72 | int frameBufferWidth, frameBufferHeight; 73 | glfwGetFramebufferSize(window.window, &frameBufferWidth, &frameBufferHeight); 74 | mouseXToWindowFactor = double(frameBufferWidth) / virtualWindowWidth; 75 | mouseYToWindowFactor = double(frameBufferHeight) / virtualWindowHeight; 76 | } 77 | } 78 | mouseX *= mouseXToWindowFactor; 79 | mouseY *= mouseYToWindowFactor; 80 | } 81 | 82 | const scrollAreaWidth = windowWidth / 4; 83 | const scrollAreaHeight = windowHeight - 20; 84 | 85 | int mousex = cast(int)mouseX; 86 | int mousey = cast(int)mouseY; 87 | 88 | mousey = windowHeight - mousey; 89 | int leftButton = glfwGetMouseButton(window.window, GLFW_MOUSE_BUTTON_LEFT); 90 | int rightButton = glfwGetMouseButton(window.window, GLFW_MOUSE_BUTTON_RIGHT); 91 | int middleButton = glfwGetMouseButton(window.window, GLFW_MOUSE_BUTTON_MIDDLE); 92 | 93 | if (leftButton == GLFW_PRESS) 94 | mousebutton |= MouseButton.left; 95 | 96 | imguiBeginFrame(mousex, mousey, mousebutton, mouseScroll, staticUnicode); 97 | staticUnicode = 0; 98 | 99 | if (mouseScroll != 0) 100 | mouseScroll = 0; 101 | 102 | imguiBeginScrollArea("Scroll area 1", 10, 10, scrollAreaWidth, scrollAreaHeight, &scrollArea1); 103 | 104 | imguiSeparatorLine(); 105 | imguiSeparator(); 106 | 107 | imguiButton("Button"); 108 | 109 | imguiButton("Disabled button", Enabled.no); 110 | imguiItem("Item"); 111 | imguiItem("Disabled item", Enabled.no); 112 | 113 | static char[1024] buff1; 114 | if (imguiCheck("Checkbox", &checkState1)) 115 | lastInfo = sformat(buff1, "Toggled the checkbox to: '%s'", checkState1 ? "On" : "Off"); 116 | 117 | // should not be clickable 118 | enforce(!imguiCheck("Inactive disabled checkbox", &checkState2, Enabled.no)); 119 | 120 | enforce(!imguiCheck("Inactive enabled checkbox", &checkState3, Enabled.no)); 121 | 122 | if(imguiTextInput("Text input:", textInputBuffer, textEntered)) 123 | { 124 | lastTextEntered = textEntered.idup; 125 | textEntered = textInputBuffer[0 .. 0]; 126 | } 127 | imguiLabel("Entered text: " ~ lastTextEntered); 128 | 129 | static char[1024] buff2; 130 | if (imguiCollapse("Collapse", "subtext", &collapseState1)) 131 | lastInfo = sformat(buff2, "subtext changed to: '%s'", collapseState1 ? "Maximized" : "Minimized"); 132 | 133 | if (collapseState1) 134 | { 135 | imguiIndent(); 136 | imguiLabel("Collapsable element"); 137 | imguiUnindent(); 138 | } 139 | 140 | // should not be clickable 141 | enforce(!imguiCollapse("Disabled collapse", "subtext", &collapseState2, Enabled.no)); 142 | 143 | imguiLabel("Label"); 144 | imguiValue("Value"); 145 | 146 | imguiLabel("Unicode characters"); 147 | imguiValue("한글 é ý ú í ó á š ž"); 148 | 149 | static char[1024] buff3; 150 | if (imguiSlider("Slider", &sliderValue1, 0.0, 100.0, 1.0f)) 151 | lastInfo = sformat(buff3, "Slider clicked, current value is: '%s'", sliderValue1); 152 | 153 | // should not be clickable 154 | enforce(!imguiSlider("Disabled slider", &sliderValue2, 0.0, 100.0, 1.0f, Enabled.no)); 155 | 156 | imguiIndent(); 157 | imguiLabel("Indented"); 158 | imguiUnindent(); 159 | imguiLabel("Unindented"); 160 | 161 | imguiEndScrollArea(); 162 | 163 | imguiBeginScrollArea("Scroll area 2", 20 + (1 * scrollAreaWidth), 10, scrollAreaWidth, scrollAreaHeight, &scrollArea2); 164 | imguiSeparatorLine(); 165 | imguiSeparator(); 166 | 167 | foreach (i; 0 .. 100) 168 | imguiLabel("A wall of text"); 169 | 170 | imguiEndScrollArea(); 171 | 172 | imguiBeginScrollArea("Scroll area 3", 30 + (2 * scrollAreaWidth), 10, scrollAreaWidth, scrollAreaHeight, &scrollArea3); 173 | imguiLabel(lastInfo); 174 | imguiEndScrollArea(); 175 | 176 | imguiEndFrame(); 177 | 178 | const graphicsXPos = 40 + (3 * scrollAreaWidth); 179 | 180 | imguiDrawText(graphicsXPos, scrollAreaHeight, TextAlign.left, "Free text", RGBA(32, 192, 32, 192)); 181 | imguiDrawText(graphicsXPos + 100, windowHeight - 40, TextAlign.right, "Free text", RGBA(32, 32, 192, 192)); 182 | imguiDrawText(graphicsXPos + 50, windowHeight - 60, TextAlign.center, "Free text", RGBA(192, 32, 32, 192)); 183 | 184 | imguiDrawLine(graphicsXPos, windowHeight - 80, graphicsXPos + 100, windowHeight - 60, 1.0f, RGBA(32, 192, 32, 192)); 185 | imguiDrawLine(graphicsXPos, windowHeight - 100, graphicsXPos + 100, windowHeight - 80, 2.0, RGBA(32, 32, 192, 192)); 186 | imguiDrawLine(graphicsXPos, windowHeight - 120, graphicsXPos + 100, windowHeight - 100, 3.0, RGBA(192, 32, 32, 192)); 187 | 188 | imguiDrawRoundedRect(graphicsXPos, windowHeight - 240, 100, 100, 5.0, RGBA(32, 192, 32, 192)); 189 | imguiDrawRoundedRect(graphicsXPos, windowHeight - 350, 100, 100, 10.0, RGBA(32, 32, 192, 192)); 190 | imguiDrawRoundedRect(graphicsXPos, windowHeight - 470, 100, 100, 20.0, RGBA(192, 32, 32, 192)); 191 | 192 | imguiDrawRect(graphicsXPos, windowHeight - 590, 100, 100, RGBA(32, 192, 32, 192)); 193 | imguiDrawRect(graphicsXPos, windowHeight - 710, 100, 100, RGBA(32, 32, 192, 192)); 194 | imguiDrawRect(graphicsXPos, windowHeight - 830, 100, 100, RGBA(192, 32, 32, 192)); 195 | 196 | imguiRender(windowWidth, windowHeight); 197 | } 198 | 199 | /** 200 | This tells OpenGL what area of the available area we are 201 | rendering to. In this case, we change it to match the 202 | full available area. Without this function call resizing 203 | the window would have no effect on the rendering. 204 | */ 205 | void onWindowResize(int width, int height) 206 | { 207 | // bottom-left position. 208 | enum int x = 0; 209 | enum int y = 0; 210 | 211 | /** 212 | This function defines the current viewport transform. 213 | It defines as a region of the window, specified by the 214 | bottom-left position and a width/height. 215 | 216 | Note about the viewport transform: 217 | It is the process of transforming vertex data from normalized 218 | device coordinate space to window space. It specifies the 219 | viewable region of a window. 220 | */ 221 | glfwGetFramebufferSize(window.window, &width, &height); 222 | glViewport(x, y, width, height); 223 | 224 | windowWidth = width; 225 | windowHeight = height; 226 | version (MaybeHighResolutionDisplay) 227 | { 228 | mouseXToWindowFactor = 0; 229 | mouseYToWindowFactor = 0; 230 | } 231 | } 232 | 233 | void onScroll(double hOffset, double vOffset) 234 | { 235 | mouseScroll = -cast(int)vOffset; 236 | } 237 | 238 | extern(C) static void getUnicode(GLFWwindow* w, uint unicode) 239 | { 240 | staticUnicode = unicode; 241 | } 242 | 243 | extern(C) static void getKey(GLFWwindow* w, int key, int scancode, int action, int mods) 244 | { 245 | if(action != GLFW_PRESS) { return; } 246 | if(key == GLFW_KEY_ENTER) { staticUnicode = 0x0D; } 247 | else if(key == GLFW_KEY_BACKSPACE) { staticUnicode = 0x08; } 248 | } 249 | 250 | private: 251 | Window window; 252 | int windowWidth; 253 | int windowHeight; 254 | version (MaybeHighResolutionDisplay) 255 | { 256 | double mouseXToWindowFactor = 0; 257 | double mouseYToWindowFactor = 0; 258 | } 259 | bool checkState1 = false; 260 | bool checkState2 = false; 261 | bool checkState3 = true; 262 | bool collapseState1 = true; 263 | bool collapseState2 = false; 264 | float sliderValue1 = 50.0; 265 | float sliderValue2 = 30.0; 266 | int scrollArea1 = 0; 267 | int scrollArea2 = 0; 268 | int scrollArea3 = 0; 269 | int mouseScroll = 0; 270 | 271 | char[] lastInfo; // last clicked element information 272 | 273 | static dchar staticUnicode; 274 | // Buffer to store text input 275 | char[128] textInputBuffer; 276 | // Slice of textInputBuffer 277 | char[] textEntered; 278 | // Text entered last time the user the text input field. 279 | string lastTextEntered; 280 | } 281 | 282 | int main(string[] args) 283 | { 284 | int width = 1024, height = 768; 285 | 286 | auto window = createWindow("imgui", WindowMode.windowed, width, height); 287 | scope (exit) destroy(window); 288 | 289 | GUI gui = GUI(window); 290 | 291 | glfwSwapInterval(1); 292 | 293 | string fontPath = thisExePath().dirName().buildPath("../").buildPath("DroidSans.ttf"); 294 | 295 | enforce(imguiInit(fontPath)); 296 | 297 | glClearColor(0.8f, 0.8f, 0.8f, 1.0f); 298 | glEnable(GL_BLEND); 299 | glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 300 | glDisable(GL_DEPTH_TEST); 301 | 302 | while (!glfwWindowShouldClose(window.window)) 303 | { 304 | gui.render(); 305 | 306 | /* Swap front and back buffers. */ 307 | window.swap_buffers(); 308 | 309 | /* Poll for and process events. */ 310 | glfwPollEvents(); 311 | 312 | if (window.is_key_down(GLFW_KEY_ESCAPE)) 313 | glfwSetWindowShouldClose(window.window, true); 314 | } 315 | 316 | // Clean UI 317 | imguiDestroy(); 318 | 319 | return 0; 320 | } 321 | -------------------------------------------------------------------------------- /examples/demo/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | 4 | "description": "demo", 5 | 6 | "authors": [ 7 | "Adrien Herubel", 8 | "Andrej Mitrovic" 9 | ], 10 | 11 | "homepage": "https://github.com/d-gamedev-team/dimgui", 12 | 13 | "copyright": "Copyright (C) 2012 - 2013 Adrien Herubel", 14 | 15 | "license": "zlib", 16 | 17 | "targetName" : "demo", 18 | 19 | "targetPath" : "../../bin", 20 | 21 | "targetType": "executable", 22 | 23 | "sourceFiles": ["demo.d", "window.d"], 24 | 25 | "mainSourceFile": "demo.d", 26 | 27 | "dependencies": { 28 | "dimgui": {"path": "../../", "version": "~master"} 29 | }, 30 | } 31 | -------------------------------------------------------------------------------- /examples/demo/dub.selections.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileVersion": 1, 3 | "versions": {} 4 | } -------------------------------------------------------------------------------- /examples/demo/window.d: -------------------------------------------------------------------------------- 1 | module window; 2 | 3 | /** 4 | Contains various helpers, common code, and initialization routines. 5 | */ 6 | 7 | import std.algorithm : min; 8 | import std.exception : enforce; 9 | import std.functional : toDelegate; 10 | import std.stdio : stderr; 11 | import std.string : format; 12 | 13 | import deimos.glfw.glfw3; 14 | 15 | import glad.gl.enums; 16 | import glad.gl.ext; 17 | import glad.gl.funcs; 18 | import glad.gl.loader; 19 | import glad.gl.types; 20 | 21 | import glwtf.input; 22 | import glwtf.window; 23 | 24 | /// init 25 | shared static this() 26 | { 27 | enforce(glfwInit()); 28 | } 29 | 30 | /// uninit 31 | shared static ~this() 32 | { 33 | glfwTerminate(); 34 | } 35 | 36 | /// 37 | enum WindowMode 38 | { 39 | fullscreen, 40 | windowed, 41 | } 42 | 43 | /** 44 | Create a window, an OpenGL 3.x context, and set up some other 45 | common routines for error handling, window resizing, etc. 46 | */ 47 | Window createWindow(string windowName, WindowMode windowMode = WindowMode.windowed, int width = 1024, int height = 768) 48 | { 49 | auto vidMode = glfwGetVideoMode(glfwGetPrimaryMonitor()); 50 | 51 | // constrain the window size so it isn't larger than the desktop size. 52 | width = min(width, vidMode.width); 53 | height = min(height, vidMode.height); 54 | 55 | // set the window to be initially inivisible since we're repositioning it. 56 | glfwWindowHint(GLFW_VISIBLE, 0); 57 | 58 | // enable debugging 59 | glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, 1); 60 | 61 | Window window = createWindowContext(windowName, WindowMode.windowed, width, height); 62 | 63 | // center the window on the screen 64 | glfwSetWindowPos(window.window, (vidMode.width - width) / 2, (vidMode.height - height) / 2); 65 | 66 | // glfw-specific error routine (not a generic GL error handler) 67 | register_glfw_error_callback(&glfwErrorCallback); 68 | 69 | // anti-aliasing number of samples. 70 | window.samples = 4; 71 | 72 | // activate an opengl context. 73 | window.make_context_current(); 74 | 75 | // load all OpenGL function pointers via glad. 76 | enforce(gladLoadGL()); 77 | 78 | enforce(glGenBuffers !is null); 79 | 80 | // only interested in GL 3.x 81 | enforce(GLVersion.major >= 3); 82 | 83 | // turn v-sync off. 84 | glfwSwapInterval(0); 85 | 86 | version (OSX) 87 | { 88 | // GL_ARM_debug_output and GL_KHR_debug are not supported under OS X 10.9.3 89 | } 90 | else 91 | { 92 | // ensure the debug output extension is supported 93 | enforce(GL_ARB_debug_output || GL_KHR_debug); 94 | 95 | // cast: workaround for 'nothrow' propagation bug (haven't been able to reduce it) 96 | auto hookDebugCallback = GL_ARB_debug_output ? glDebugMessageCallbackARB 97 | : cast(typeof(glDebugMessageCallbackARB))glDebugMessageCallback; 98 | 99 | 100 | // hook the debug callback 101 | // cast: when using derelict it assumes its nothrow 102 | hookDebugCallback(cast(GLDEBUGPROCARB)&glErrorCallback, null); 103 | 104 | // enable proper stack tracing support (otherwise we'd get random failures at runtime) 105 | glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB); 106 | } 107 | 108 | // finally show the window 109 | glfwShowWindow(window.window); 110 | 111 | return window; 112 | } 113 | 114 | /** Create a window and an OpenGL context. */ 115 | Window createWindowContext(string windowName, WindowMode windowMode, int width, int height) 116 | { 117 | auto window = new Window(); 118 | auto monitor = windowMode == WindowMode.fullscreen ? glfwGetPrimaryMonitor() : null; 119 | auto context = window.create_highest_available_context(width, height, windowName, monitor, null, GLFW_OPENGL_CORE_PROFILE); 120 | 121 | // ensure we've loaded a proper context 122 | enforce(context.major >= 3); 123 | 124 | return window; 125 | } 126 | 127 | /** Just emit errors to stderr on GLFW errors. */ 128 | void glfwErrorCallback(int code, string msg) 129 | { 130 | stderr.writefln("Error (%s): %s", code, msg); 131 | } 132 | 133 | /// 134 | class GLException : Exception 135 | { 136 | @safe pure nothrow this(string msg = "", string file = __FILE__, size_t line = __LINE__, Throwable next = null) 137 | { 138 | super(msg, file, line, next); 139 | } 140 | 141 | @safe pure nothrow this(string msg, Throwable next, string file = __FILE__, size_t line = __LINE__) 142 | { 143 | super(msg, file, line, next); 144 | } 145 | } 146 | 147 | /** 148 | GL_ARB_debug_output or GL_KHR_debug callback. 149 | 150 | Throwing exceptions across language boundaries is ok as 151 | long as $(B GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB) is enabled. 152 | */ 153 | extern (System) 154 | private void glErrorCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, in GLchar* message, GLvoid* userParam) 155 | { 156 | //string msg = format("glErrorCallback: source: %s, type: %s, id: %s, severity: %s, length: %s, message: %s, userParam: %s", 157 | // source, type, id, severity, length, message.to!string, userParam); 158 | 159 | //stderr.writeln(msg); 160 | } 161 | -------------------------------------------------------------------------------- /examples/memory/SciTEDirectory.properties: -------------------------------------------------------------------------------- 1 | # This is a SciTE[1] configuration file. You can ignore this file if you're not using SciTE. 2 | # [1] : http://www.scintilla.org/SciTE.html 3 | command.go.subsystem.*.d=0 4 | command.go.*.d=dub -q --root=$(SciteDirectoryHome) 5 | -------------------------------------------------------------------------------- /examples/memory/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "memory", 3 | 4 | "description": "memory", 5 | 6 | "authors": [ 7 | "Adrien Herubel", 8 | "Andrej Mitrovic" 9 | ], 10 | 11 | "homepage": "https://github.com/d-gamedev-team/dimgui", 12 | 13 | "copyright": "Copyright (C) 2012 - 2013 Adrien Herubel", 14 | 15 | "license": "zlib", 16 | 17 | "targetName" : "memory", 18 | 19 | "targetPath" : "../../bin", 20 | 21 | "targetType": "executable", 22 | 23 | "sourceFiles": ["memory.d", "window.d"], 24 | 25 | "mainSourceFile": "memory.d", 26 | 27 | "dependencies": { 28 | "dimgui": {"path": "../../", "version": "~master"} 29 | }, 30 | } 31 | -------------------------------------------------------------------------------- /examples/memory/dub.selections.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileVersion": 1, 3 | "versions": {} 4 | } 5 | -------------------------------------------------------------------------------- /examples/memory/memory.d: -------------------------------------------------------------------------------- 1 | module memory; 2 | 3 | /** 4 | This example demonstrates how to properly handle memory management 5 | for displaying things such as text. 6 | */ 7 | 8 | import std.exception; 9 | import std.file; 10 | import std.path; 11 | import std.stdio; 12 | import std.string; 13 | 14 | import deimos.glfw.glfw3; 15 | 16 | import glad.gl.enums; 17 | import glad.gl.ext; 18 | import glad.gl.funcs; 19 | import glad.gl.loader; 20 | import glad.gl.types; 21 | 22 | import glwtf.input; 23 | import glwtf.window; 24 | 25 | import imgui; 26 | 27 | import window; 28 | 29 | version (OSX) 30 | version = MaybeHighResolutionDisplay; 31 | version (iOS) 32 | version = MaybeHighResolutionDisplay; 33 | 34 | struct GUI 35 | { 36 | this(Window window) 37 | { 38 | this.window = window; 39 | 40 | window.on_scroll.strongConnect(&onScroll); 41 | 42 | int width; 43 | int height; 44 | glfwGetFramebufferSize(window.window, &width, &height); 45 | 46 | // trigger initial viewport transform. 47 | onWindowResize(width, height); 48 | 49 | window.on_resize.strongConnect(&onWindowResize); 50 | } 51 | 52 | void render() 53 | { 54 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 55 | 56 | // Mouse states 57 | ubyte mousebutton = 0; 58 | double mouseX; 59 | double mouseY; 60 | glfwGetCursorPos(window.window, &mouseX, &mouseY); 61 | 62 | version (MaybeHighResolutionDisplay) 63 | { 64 | // Scale the cursor position for high-resolution displays. 65 | if (mouseXToWindowFactor == 0) // need to initialize 66 | { 67 | int virtualWindowWidth, virtualWindowHeight; 68 | glfwGetWindowSize(window.window, &virtualWindowWidth, &virtualWindowHeight); 69 | if (virtualWindowWidth != 0 && virtualWindowHeight != 0) 70 | { 71 | int frameBufferWidth, frameBufferHeight; 72 | glfwGetFramebufferSize(window.window, &frameBufferWidth, &frameBufferHeight); 73 | mouseXToWindowFactor = double(frameBufferWidth) / virtualWindowWidth; 74 | mouseYToWindowFactor = double(frameBufferHeight) / virtualWindowHeight; 75 | } 76 | } 77 | mouseX *= mouseXToWindowFactor; 78 | mouseY *= mouseYToWindowFactor; 79 | } 80 | 81 | const scrollAreaWidth = (windowWidth / 4) - 10; // -10 to allow room for the scrollbar 82 | const scrollAreaHeight = windowHeight - 20; 83 | 84 | int mousex = cast(int)mouseX; 85 | int mousey = cast(int)mouseY; 86 | 87 | mousey = windowHeight - mousey; 88 | int leftButton = glfwGetMouseButton(window.window, GLFW_MOUSE_BUTTON_LEFT); 89 | int rightButton = glfwGetMouseButton(window.window, GLFW_MOUSE_BUTTON_RIGHT); 90 | int middleButton = glfwGetMouseButton(window.window, GLFW_MOUSE_BUTTON_MIDDLE); 91 | 92 | if (leftButton == GLFW_PRESS) 93 | mousebutton |= MouseButton.left; 94 | 95 | imguiBeginFrame(mousex, mousey, mousebutton, mouseScroll); 96 | 97 | if (mouseScroll != 0) 98 | mouseScroll = 0; 99 | 100 | /// Improper memory management. 101 | displayArea1(scrollAreaWidth, scrollAreaHeight); 102 | 103 | /// Attempted workaround, but still improper memory management. 104 | char[128] buffer; 105 | displayArea2(scrollAreaWidth, scrollAreaHeight, buffer); 106 | 107 | /// Proper memory management. 108 | char[128][100] buffers; 109 | displayArea3(scrollAreaWidth, scrollAreaHeight, buffers); 110 | 111 | /// Alternatively you may use 'string', which is guaranteed to be immutable 112 | /// and will outlive any stack scope since the garbage collector will keep 113 | /// a reference to it. 114 | displayArea4(scrollAreaWidth, scrollAreaHeight); 115 | 116 | imguiEndFrame(); 117 | 118 | imguiRender(windowWidth, windowHeight); 119 | } 120 | 121 | void displayArea1(int scrollAreaWidth, int scrollAreaHeight) 122 | { 123 | imguiBeginScrollArea("Improper memory management 1", 10, 10, scrollAreaWidth, scrollAreaHeight, &scrollArea1); 124 | 125 | imguiSeparatorLine(); 126 | imguiSeparator(); 127 | 128 | /// Note: improper memory management: 'buffer' is scoped to this function, 129 | /// but imguiLabel will keep a reference to the 'buffer' until 'imguiRender' 130 | /// is called. 'imguiRender' is only called after 'displayArea1' returns, 131 | /// after which 'buffer' will not be usable (it's memory allocated on the stack!). 132 | /// Result: Random text being displayed or even crashes are possible. 133 | char[128] buffer; 134 | auto text = buffer.sformat("This is my text: %s", "more text"); 135 | imguiLabel(text); 136 | 137 | imguiEndScrollArea(); 138 | } 139 | 140 | void displayArea2(int scrollAreaWidth, int scrollAreaHeight, ref char[128] buffer) 141 | { 142 | imguiBeginScrollArea("Improper memory management 2", 20 + (1 * scrollAreaWidth), 10, scrollAreaWidth, scrollAreaHeight, &scrollArea2); 143 | 144 | imguiSeparatorLine(); 145 | imguiSeparator(); 146 | 147 | foreach (idx; 0 .. 100) 148 | { 149 | /// Note: improper memory management: 'buffer' will be re-used in each 150 | /// iteration of this loop, but imguiLabel will just keep a reference 151 | /// to the same memory location on each call. 152 | /// Result: Typically the same bit of text is displayed 100 times. 153 | auto text = buffer.sformat("Item number %s", idx); 154 | imguiLabel(text); 155 | } 156 | 157 | imguiEndScrollArea(); 158 | } 159 | 160 | void displayArea3(int scrollAreaWidth, int scrollAreaHeight, ref char[128][100] buffers) 161 | { 162 | imguiBeginScrollArea("Proper memory management 1", 30 + (2 * scrollAreaWidth), 10, scrollAreaWidth, scrollAreaHeight, &scrollArea3); 163 | 164 | imguiSeparatorLine(); 165 | imguiSeparator(); 166 | 167 | foreach (idx, ref buffer; buffers) 168 | { 169 | /// Note: Proper memory management: 'buffer' is unique for all the items, 170 | /// and imguiLabel can safely store a reference to each string since each 171 | /// buffer will be valid until the exit of the scope where the 'imguiRender' 172 | /// call is emitted. 173 | auto text = buffer.sformat("Item number %s", idx); 174 | imguiLabel(text); 175 | } 176 | 177 | imguiEndScrollArea(); 178 | } 179 | 180 | void displayArea4(int scrollAreaWidth, int scrollAreaHeight) 181 | { 182 | imguiBeginScrollArea("Proper memory management 2", 40 + (3 * scrollAreaWidth), 10, scrollAreaWidth, scrollAreaHeight, &scrollArea4); 183 | 184 | imguiSeparatorLine(); 185 | imguiSeparator(); 186 | 187 | foreach (idx; 0 .. 100) 188 | { 189 | /// Note: Proper memory management: the string will not be prematurely 190 | /// garbage-collected since the GC will know that 'imguiLabel' will store 191 | /// a refererence to this string for use in a later 'imguiRender call. 192 | string str = "This is just some text"; 193 | imguiLabel(str); 194 | } 195 | 196 | imguiEndScrollArea(); 197 | } 198 | 199 | /** 200 | This tells OpenGL what area of the available area we are 201 | rendering to. In this case, we change it to match the 202 | full available area. Without this function call resizing 203 | the window would have no effect on the rendering. 204 | */ 205 | void onWindowResize(int width, int height) 206 | { 207 | // bottom-left position. 208 | enum int x = 0; 209 | enum int y = 0; 210 | 211 | /** 212 | This function defines the current viewport transform. 213 | It defines as a region of the window, specified by the 214 | bottom-left position and a width/height. 215 | 216 | Note about the viewport transform: 217 | It is the process of transforming vertex data from normalized 218 | device coordinate space to window space. It specifies the 219 | viewable region of a window. 220 | */ 221 | glfwGetFramebufferSize(window.window, &width, &height); 222 | glViewport(x, y, width, height); 223 | 224 | windowWidth = width; 225 | windowHeight = height; 226 | version (MaybeHighResolutionDisplay) 227 | { 228 | mouseXToWindowFactor = 0; 229 | mouseYToWindowFactor = 0; 230 | } 231 | } 232 | 233 | void onScroll(double hOffset, double vOffset) 234 | { 235 | mouseScroll = -cast(int)vOffset; 236 | } 237 | 238 | private: 239 | Window window; 240 | int windowWidth; 241 | int windowHeight; 242 | version (MaybeHighResolutionDisplay) 243 | { 244 | double mouseXToWindowFactor = 0; 245 | double mouseYToWindowFactor = 0; 246 | } 247 | 248 | int scrollArea1 = 0; 249 | int scrollArea2 = 0; 250 | int scrollArea3 = 0; 251 | int scrollArea4 = 0; 252 | int mouseScroll = 0; 253 | } 254 | 255 | int main(string[] args) 256 | { 257 | int width = 1024, height = 768; 258 | 259 | auto window = createWindow("imgui", WindowMode.windowed, width, height); 260 | scope (exit) destroy(window); 261 | 262 | GUI gui = GUI(window); 263 | 264 | glfwSwapInterval(1); 265 | 266 | string fontPath = thisExePath().dirName().buildPath("../").buildPath("DroidSans.ttf"); 267 | 268 | enforce(imguiInit(fontPath)); 269 | 270 | glClearColor(0.8f, 0.8f, 0.8f, 1.0f); 271 | glEnable(GL_BLEND); 272 | glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 273 | glDisable(GL_DEPTH_TEST); 274 | 275 | while (!glfwWindowShouldClose(window.window)) 276 | { 277 | gui.render(); 278 | 279 | /* Swap front and back buffers. */ 280 | window.swap_buffers(); 281 | 282 | /* Poll for and process events. */ 283 | glfwPollEvents(); 284 | 285 | if (window.is_key_down(GLFW_KEY_ESCAPE)) 286 | glfwSetWindowShouldClose(window.window, true); 287 | } 288 | 289 | // Clean UI 290 | imguiDestroy(); 291 | 292 | return 0; 293 | } 294 | -------------------------------------------------------------------------------- /examples/memory/window.d: -------------------------------------------------------------------------------- 1 | module window; 2 | 3 | /** 4 | Contains various helpers, common code, and initialization routines. 5 | */ 6 | 7 | import std.algorithm : min; 8 | import std.exception : enforce; 9 | import std.functional : toDelegate; 10 | import std.stdio : stderr; 11 | import std.string : format; 12 | 13 | import deimos.glfw.glfw3; 14 | 15 | import glad.gl.enums; 16 | import glad.gl.ext; 17 | import glad.gl.funcs; 18 | import glad.gl.loader; 19 | import glad.gl.types; 20 | 21 | import glwtf.input; 22 | import glwtf.window; 23 | 24 | /// init 25 | shared static this() 26 | { 27 | enforce(glfwInit()); 28 | } 29 | 30 | /// uninit 31 | shared static ~this() 32 | { 33 | glfwTerminate(); 34 | } 35 | 36 | /// 37 | enum WindowMode 38 | { 39 | fullscreen, 40 | windowed, 41 | } 42 | 43 | /** 44 | Create a window, an OpenGL 3.x context, and set up some other 45 | common routines for error handling, window resizing, etc. 46 | */ 47 | Window createWindow(string windowName, WindowMode windowMode = WindowMode.windowed, int width = 1024, int height = 768) 48 | { 49 | auto vidMode = glfwGetVideoMode(glfwGetPrimaryMonitor()); 50 | 51 | // constrain the window size so it isn't larger than the desktop size. 52 | width = min(width, vidMode.width); 53 | height = min(height, vidMode.height); 54 | 55 | // set the window to be initially inivisible since we're repositioning it. 56 | glfwWindowHint(GLFW_VISIBLE, 0); 57 | 58 | // enable debugging 59 | glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, 1); 60 | 61 | Window window = createWindowContext(windowName, WindowMode.windowed, width, height); 62 | 63 | // center the window on the screen 64 | glfwSetWindowPos(window.window, (vidMode.width - width) / 2, (vidMode.height - height) / 2); 65 | 66 | // glfw-specific error routine (not a generic GL error handler) 67 | register_glfw_error_callback(&glfwErrorCallback); 68 | 69 | // anti-aliasing number of samples. 70 | window.samples = 4; 71 | 72 | // activate an opengl context. 73 | window.make_context_current(); 74 | 75 | // load all OpenGL function pointers via glad. 76 | enforce(gladLoadGL()); 77 | 78 | enforce(glGenBuffers !is null); 79 | 80 | // only interested in GL 3.x 81 | enforce(GLVersion.major >= 3); 82 | 83 | // turn v-sync off. 84 | glfwSwapInterval(0); 85 | 86 | // ensure the debug output extension is supported 87 | enforce(GL_ARB_debug_output || GL_KHR_debug); 88 | 89 | // cast: workaround for 'nothrow' propagation bug (haven't been able to reduce it) 90 | auto hookDebugCallback = GL_ARB_debug_output ? glDebugMessageCallbackARB 91 | : cast(typeof(glDebugMessageCallbackARB))glDebugMessageCallback; 92 | 93 | 94 | // hook the debug callback 95 | // cast: when using derelict it assumes its nothrow 96 | hookDebugCallback(cast(GLDEBUGPROCARB)&glErrorCallback, null); 97 | 98 | // enable proper stack tracing support (otherwise we'd get random failures at runtime) 99 | glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB); 100 | 101 | // finally show the window 102 | glfwShowWindow(window.window); 103 | 104 | return window; 105 | } 106 | 107 | /** Create a window and an OpenGL context. */ 108 | Window createWindowContext(string windowName, WindowMode windowMode, int width, int height) 109 | { 110 | auto window = new Window(); 111 | auto monitor = windowMode == WindowMode.fullscreen ? glfwGetPrimaryMonitor() : null; 112 | auto context = window.create_highest_available_context(width, height, windowName, monitor, null, GLFW_OPENGL_CORE_PROFILE); 113 | 114 | // ensure we've loaded a proper context 115 | enforce(context.major >= 3); 116 | 117 | return window; 118 | } 119 | 120 | /** Just emit errors to stderr on GLFW errors. */ 121 | void glfwErrorCallback(int code, string msg) 122 | { 123 | stderr.writefln("Error (%s): %s", code, msg); 124 | } 125 | 126 | /// 127 | class GLException : Exception 128 | { 129 | @safe pure nothrow this(string msg = "", string file = __FILE__, size_t line = __LINE__, Throwable next = null) 130 | { 131 | super(msg, file, line, next); 132 | } 133 | 134 | @safe pure nothrow this(string msg, Throwable next, string file = __FILE__, size_t line = __LINE__) 135 | { 136 | super(msg, file, line, next); 137 | } 138 | } 139 | 140 | /** 141 | GL_ARB_debug_output or GL_KHR_debug callback. 142 | 143 | Throwing exceptions across language boundaries is ok as 144 | long as $(B GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB) is enabled. 145 | */ 146 | extern (System) 147 | private void glErrorCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, in GLchar* message, GLvoid* userParam) 148 | { 149 | //string msg = format("glErrorCallback: source: %s, type: %s, id: %s, severity: %s, length: %s, message: %s, userParam: %s", 150 | // source, type, id, severity, length, message.to!string, userParam); 151 | 152 | //stderr.writeln(msg); 153 | } 154 | -------------------------------------------------------------------------------- /lib/x86-64/glfw3dll.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d-gamedev-team/dimgui/c58f56407b21f4ec702883acd1a5f0c611e2a25e/lib/x86-64/glfw3dll.lib -------------------------------------------------------------------------------- /lib/x86/glfw3dll.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d-gamedev-team/dimgui/c58f56407b21f4ec702883acd1a5f0c611e2a25e/lib/x86/glfw3dll.lib -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009-2010 Mikko Mononen memon@inside.org 2 | 3 | This software is provided 'as-is', without any express or implied 4 | warranty. In no event will the authors be held liable for any damages 5 | arising from the use of this software. 6 | Permission is granted to anyone to use this software for any purpose, 7 | including commercial applications, and to alter it and redistribute it 8 | freely, subject to the following restrictions: 9 | 1. The origin of this software must not be misrepresented; you must not 10 | claim that you wrote the original software. If you use this software 11 | in a product, an acknowledgment in the product documentation would be 12 | appreciated but is not required. 13 | 2. Altered source versions must be plainly marked as such, and must not be 14 | misrepresented as being the original software. 15 | 3. This notice may not be removed or altered from any source distribution. 16 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # dimgui 2 | 3 | ![dimgui](https://raw.github.com/d-gamedev-team/dimgui/master/screenshot/imgui.png) 4 | 5 | This is a D port of the [imgui] OpenGL GUI library. 6 | 7 | **dimgui** is an [immediate-mode] GUI library. 8 | 9 | Homepage: https://github.com/d-gamedev-team/dimgui 10 | 11 | ## Supported compilers 12 | 13 | Currently requires DMD v2.071.0, not tested with other compilers, 14 | and not frequently tested on Windows. 15 | 16 | ## Examples 17 | 18 | Use [dub] to build and run the example project: 19 | 20 | ``` 21 | # Shows a nice demo of the various UI elements. 22 | $ dub run dimgui:demo 23 | 24 | # Shows how to properly handle memory management. 25 | $ dub run dimgui:memory 26 | ``` 27 | 28 | Note: You will need to install the [glfw] shared library in order to run the example. 29 | 30 | ## Real-world examples 31 | 32 | **dimgui** is used in the following projects: 33 | 34 | - [dbox] - The 2D physics library uses **dimgui** for its interactive test-suite. 35 | 36 | ## Documentation 37 | 38 | The public API is available in the [imgui.api] module. 39 | 40 | ## Memory Management 41 | 42 | For efficiency reasons [imgui] will batch all commands and will render the current frame 43 | once **imguiRender** is called. Calls to UI-defining functions such as **imguiLabel** will 44 | store a reference to the passed-in string and will not draw the string immediately. 45 | 46 | This means you should not pass in memory allocated on the stack unless you can guarantee that: 47 | 48 | - The memory on the stack will live up to the point **imguiRender** is called. 49 | - The memory passed to the UI-defining functions is unique for each call. 50 | 51 | An example of both improper and proper memory management is shown in the [memory] example. 52 | 53 | ## Building dimgui as a static library 54 | 55 | Run [dub] alone in the root project directory to build **dimgui** as a static library: 56 | 57 | ``` 58 | $ dub 59 | ``` 60 | 61 | ## Links 62 | 63 | - The original [imgui] github repository. 64 | 65 | ## License 66 | 67 | Distributed under the [zlib] license. 68 | 69 | See the accompanying file [license.txt][zlib]. 70 | 71 | [dub]: http://code.dlang.org/ 72 | [immediate-mode]: http://sol.gfxile.net/imgui/ 73 | [imgui]: https://github.com/AdrienHerubel/imgui 74 | [imgui.api]: https://github.com/d-gamedev-team/dimgui/blob/master/src/imgui/api.d 75 | [zlib]: https://raw.github.com/d-gamedev-team/dimgui/master/license.txt 76 | [glfw]: http://www.glfw.org/ 77 | [memory]: https://github.com/d-gamedev-team/dimgui/blob/master/examples/memory/memory.d 78 | [dbox]: https://github.com/d-gamedev-team/dbox 79 | -------------------------------------------------------------------------------- /screenshot/imgui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d-gamedev-team/dimgui/c58f56407b21f4ec702883acd1a5f0c611e2a25e/screenshot/imgui.png -------------------------------------------------------------------------------- /src/deimos/glfw/glfw2.d: -------------------------------------------------------------------------------- 1 | module deimos.glfw.glfw2; 2 | /************************************************************************ 3 | * GLFW - An OpenGL framework 4 | * API version: 2.7 5 | * WWW: http://www.glfw.org/ 6 | *------------------------------------------------------------------------ 7 | * Copyright (c) 2002-2006 Marcus Geelnard 8 | * Copyright (c) 2006-2010 Camilla Berglund 9 | * 10 | * This software is provided 'as-is', without any express or implied 11 | * warranty. In no event will the authors be held liable for any damages 12 | * arising from the use of this software. 13 | * 14 | * Permission is granted to anyone to use this software for any purpose, 15 | * including commercial applications, and to alter it and redistribute it 16 | * freely, subject to the following restrictions: 17 | * 18 | * 1. The origin of this software must not be misrepresented; you must not 19 | * claim that you wrote the original software. If you use this software 20 | * in a product, an acknowledgment in the product documentation would 21 | * be appreciated but is not required. 22 | * 23 | * 2. Altered source versions must be plainly marked as such, and must not 24 | * be misrepresented as being the original software. 25 | * 26 | * 3. This notice may not be removed or altered from any source 27 | * distribution. 28 | * 29 | *************************************************************************/ 30 | extern (C) { 31 | 32 | enum { 33 | /************************************************************************* 34 | * GLFW version 35 | *************************************************************************/ 36 | GLFW_VERSION_MAJOR = 2, 37 | GLFW_VERSION_MINOR = 7, 38 | GLFW_VERSION_REVISION = 2, 39 | 40 | /************************************************************************* 41 | * Input handling definitions 42 | *************************************************************************/ 43 | 44 | /* Key and button state/action definitions */ 45 | GLFW_RELEASE = 0, 46 | GLFW_PRESS = 1, 47 | 48 | /* Keyboard key definitions: 8-bit ISO-8859-1 (Latin 1) encoding is used 49 | * for printable keys (such as A-Z, 0-9 etc), and values above 256 50 | * represent special (non-printable) keys (e.g. F1, Page Up etc). 51 | */ 52 | GLFW_KEY_UNKNOWN = -1, 53 | GLFW_KEY_SPACE = 32, 54 | GLFW_KEY_SPECIAL = 256, 55 | GLFW_KEY_ESC = (GLFW_KEY_SPECIAL+1), 56 | GLFW_KEY_F1 = (GLFW_KEY_SPECIAL+2), 57 | GLFW_KEY_F2 = (GLFW_KEY_SPECIAL+3), 58 | GLFW_KEY_F3 = (GLFW_KEY_SPECIAL+4), 59 | GLFW_KEY_F4 = (GLFW_KEY_SPECIAL+5), 60 | GLFW_KEY_F5 = (GLFW_KEY_SPECIAL+6), 61 | GLFW_KEY_F6 = (GLFW_KEY_SPECIAL+7), 62 | GLFW_KEY_F7 = (GLFW_KEY_SPECIAL+8), 63 | GLFW_KEY_F8 = (GLFW_KEY_SPECIAL+9), 64 | GLFW_KEY_F9 = (GLFW_KEY_SPECIAL+10), 65 | GLFW_KEY_F10 = (GLFW_KEY_SPECIAL+11), 66 | GLFW_KEY_F11 = (GLFW_KEY_SPECIAL+12), 67 | GLFW_KEY_F12 = (GLFW_KEY_SPECIAL+13), 68 | GLFW_KEY_F13 = (GLFW_KEY_SPECIAL+14), 69 | GLFW_KEY_F14 = (GLFW_KEY_SPECIAL+15), 70 | GLFW_KEY_F15 = (GLFW_KEY_SPECIAL+16), 71 | GLFW_KEY_F16 = (GLFW_KEY_SPECIAL+17), 72 | GLFW_KEY_F17 = (GLFW_KEY_SPECIAL+18), 73 | GLFW_KEY_F18 = (GLFW_KEY_SPECIAL+19), 74 | GLFW_KEY_F19 = (GLFW_KEY_SPECIAL+20), 75 | GLFW_KEY_F20 = (GLFW_KEY_SPECIAL+21), 76 | GLFW_KEY_F21 = (GLFW_KEY_SPECIAL+22), 77 | GLFW_KEY_F22 = (GLFW_KEY_SPECIAL+23), 78 | GLFW_KEY_F23 = (GLFW_KEY_SPECIAL+24), 79 | GLFW_KEY_F24 = (GLFW_KEY_SPECIAL+25), 80 | GLFW_KEY_F25 = (GLFW_KEY_SPECIAL+26), 81 | GLFW_KEY_UP = (GLFW_KEY_SPECIAL+27), 82 | GLFW_KEY_DOWN = (GLFW_KEY_SPECIAL+28), 83 | GLFW_KEY_LEFT = (GLFW_KEY_SPECIAL+29), 84 | GLFW_KEY_RIGHT = (GLFW_KEY_SPECIAL+30), 85 | GLFW_KEY_LSHIFT = (GLFW_KEY_SPECIAL+31), 86 | GLFW_KEY_RSHIFT = (GLFW_KEY_SPECIAL+32), 87 | GLFW_KEY_LCTRL = (GLFW_KEY_SPECIAL+33), 88 | GLFW_KEY_RCTRL = (GLFW_KEY_SPECIAL+34), 89 | GLFW_KEY_LALT = (GLFW_KEY_SPECIAL+35), 90 | GLFW_KEY_RALT = (GLFW_KEY_SPECIAL+36), 91 | GLFW_KEY_TAB = (GLFW_KEY_SPECIAL+37), 92 | GLFW_KEY_ENTER = (GLFW_KEY_SPECIAL+38), 93 | GLFW_KEY_BACKSPACE = (GLFW_KEY_SPECIAL+39), 94 | GLFW_KEY_INSERT = (GLFW_KEY_SPECIAL+40), 95 | GLFW_KEY_DEL = (GLFW_KEY_SPECIAL+41), 96 | GLFW_KEY_PAGEUP = (GLFW_KEY_SPECIAL+42), 97 | GLFW_KEY_PAGEDOWN = (GLFW_KEY_SPECIAL+43), 98 | GLFW_KEY_HOME = (GLFW_KEY_SPECIAL+44), 99 | GLFW_KEY_END = (GLFW_KEY_SPECIAL+45), 100 | GLFW_KEY_KP_0 = (GLFW_KEY_SPECIAL+46), 101 | GLFW_KEY_KP_1 = (GLFW_KEY_SPECIAL+47), 102 | GLFW_KEY_KP_2 = (GLFW_KEY_SPECIAL+48), 103 | GLFW_KEY_KP_3 = (GLFW_KEY_SPECIAL+49), 104 | GLFW_KEY_KP_4 = (GLFW_KEY_SPECIAL+50), 105 | GLFW_KEY_KP_5 = (GLFW_KEY_SPECIAL+51), 106 | GLFW_KEY_KP_6 = (GLFW_KEY_SPECIAL+52), 107 | GLFW_KEY_KP_7 = (GLFW_KEY_SPECIAL+53), 108 | GLFW_KEY_KP_8 = (GLFW_KEY_SPECIAL+54), 109 | GLFW_KEY_KP_9 = (GLFW_KEY_SPECIAL+55), 110 | GLFW_KEY_KP_DIVIDE = (GLFW_KEY_SPECIAL+56), 111 | GLFW_KEY_KP_MULTIPLY = (GLFW_KEY_SPECIAL+57), 112 | GLFW_KEY_KP_SUBTRACT = (GLFW_KEY_SPECIAL+58), 113 | GLFW_KEY_KP_ADD = (GLFW_KEY_SPECIAL+59), 114 | GLFW_KEY_KP_DECIMAL = (GLFW_KEY_SPECIAL+60), 115 | GLFW_KEY_KP_EQUAL = (GLFW_KEY_SPECIAL+61), 116 | GLFW_KEY_KP_ENTER = (GLFW_KEY_SPECIAL+62), 117 | GLFW_KEY_KP_NUM_LOCK = (GLFW_KEY_SPECIAL+63), 118 | GLFW_KEY_CAPS_LOCK = (GLFW_KEY_SPECIAL+64), 119 | GLFW_KEY_SCROLL_LOCK = (GLFW_KEY_SPECIAL+65), 120 | GLFW_KEY_PAUSE = (GLFW_KEY_SPECIAL+66), 121 | GLFW_KEY_LSUPER = (GLFW_KEY_SPECIAL+67), 122 | GLFW_KEY_RSUPER = (GLFW_KEY_SPECIAL+68), 123 | GLFW_KEY_MENU = (GLFW_KEY_SPECIAL+69), 124 | GLFW_KEY_LAST = GLFW_KEY_MENU, 125 | /* Mouse button definitions */ 126 | GLFW_MOUSE_BUTTON_1 = 0, 127 | GLFW_MOUSE_BUTTON_2 = 1, 128 | GLFW_MOUSE_BUTTON_3 = 2, 129 | GLFW_MOUSE_BUTTON_4 = 3, 130 | GLFW_MOUSE_BUTTON_5 = 4, 131 | GLFW_MOUSE_BUTTON_6 = 5, 132 | GLFW_MOUSE_BUTTON_7 = 6, 133 | GLFW_MOUSE_BUTTON_8 = 7, 134 | GLFW_MOUSE_BUTTON_LAST = GLFW_MOUSE_BUTTON_8, 135 | /* Mouse button aliases */ 136 | GLFW_MOUSE_BUTTON_LEFT = GLFW_MOUSE_BUTTON_1, 137 | GLFW_MOUSE_BUTTON_RIGHT = GLFW_MOUSE_BUTTON_2, 138 | GLFW_MOUSE_BUTTON_MIDDLE = GLFW_MOUSE_BUTTON_3, 139 | /* Joystick identifiers */ 140 | GLFW_JOYSTICK_1 = 0, 141 | GLFW_JOYSTICK_2 = 1, 142 | GLFW_JOYSTICK_3 = 2, 143 | GLFW_JOYSTICK_4 = 3, 144 | GLFW_JOYSTICK_5 = 4, 145 | GLFW_JOYSTICK_6 = 5, 146 | GLFW_JOYSTICK_7 = 6, 147 | GLFW_JOYSTICK_8 = 7, 148 | GLFW_JOYSTICK_9 = 8, 149 | GLFW_JOYSTICK_10 = 9, 150 | GLFW_JOYSTICK_11 = 10, 151 | GLFW_JOYSTICK_12 = 11, 152 | GLFW_JOYSTICK_13 = 12, 153 | GLFW_JOYSTICK_14 = 13, 154 | GLFW_JOYSTICK_15 = 14, 155 | GLFW_JOYSTICK_16 = 15, 156 | GLFW_JOYSTICK_LAST = GLFW_JOYSTICK_16, 157 | /************************************************************************* 158 | * Other definitions; 159 | *************************************************************************/ 160 | /* glfwOpenWindow modes */ 161 | GLFW_WINDOW = 0x00010001, 162 | GLFW_FULLSCREEN = 0x00010002, 163 | /* glfwGetWindowParam tokens */ 164 | GLFW_OPENED = 0x00020001, 165 | GLFW_ACTIVE = 0x00020002, 166 | GLFW_ICONIFIED = 0x00020003, 167 | GLFW_ACCELERATED = 0x00020004, 168 | GLFW_RED_BITS = 0x00020005, 169 | GLFW_GREEN_BITS = 0x00020006, 170 | GLFW_BLUE_BITS = 0x00020007, 171 | GLFW_ALPHA_BITS = 0x00020008, 172 | GLFW_DEPTH_BITS = 0x00020009, 173 | GLFW_STENCIL_BITS = 0x0002000A, 174 | /* The following constants are used for both glfwGetWindowParam 175 | * and glfwOpenWindowHint; 176 | */ 177 | GLFW_REFRESH_RATE = 0x0002000B, 178 | GLFW_ACCUM_RED_BITS = 0x0002000C, 179 | GLFW_ACCUM_GREEN_BITS = 0x0002000D, 180 | GLFW_ACCUM_BLUE_BITS = 0x0002000E, 181 | GLFW_ACCUM_ALPHA_BITS = 0x0002000F, 182 | GLFW_AUX_BUFFERS = 0x00020010, 183 | GLFW_STEREO = 0x00020011, 184 | GLFW_WINDOW_NO_RESIZE = 0x00020012, 185 | GLFW_FSAA_SAMPLES = 0x00020013, 186 | GLFW_OPENGL_VERSION_MAJOR = 0x00020014, 187 | GLFW_OPENGL_VERSION_MINOR = 0x00020015, 188 | GLFW_OPENGL_FORWARD_COMPAT = 0x00020016, 189 | GLFW_OPENGL_DEBUG_CONTEXT = 0x00020017, 190 | GLFW_OPENGL_PROFILE = 0x00020018, 191 | 192 | /* GLFW_OPENGL_PROFILE tokens */ 193 | GLFW_OPENGL_CORE_PROFILE = 0x00050001, 194 | GLFW_OPENGL_COMPAT_PROFILE = 0x00050002, 195 | 196 | /* glfwEnable/glfwDisable tokens */ 197 | GLFW_MOUSE_CURSOR = 0x00030001, 198 | GLFW_STICKY_KEYS = 0x00030002, 199 | GLFW_STICKY_MOUSE_BUTTONS = 0x00030003, 200 | GLFW_SYSTEM_KEYS = 0x00030004, 201 | GLFW_KEY_REPEAT = 0x00030005, 202 | GLFW_AUTO_POLL_EVENTS = 0x00030006, 203 | 204 | /* glfwWaitThread wait modes */ 205 | GLFW_WAIT = 0x00040001, 206 | GLFW_NOWAIT = 0x00040002, 207 | 208 | /* glfwGetJoystickParam tokens */ 209 | GLFW_PRESENT = 0x00050001, 210 | GLFW_AXES = 0x00050002, 211 | GLFW_BUTTONS = 0x00050003, 212 | 213 | /* glfwReadImage/glfwLoadTexture2D flags */ 214 | GLFW_NO_RESCALE_BIT = 0x00000001,/* Only for glfwReadImage */ 215 | GLFW_ORIGIN_UL_BIT = 0x00000002, 216 | GLFW_BUILD_MIPMAPS_BIT = 0x00000004,/* Only for glfwLoadTexture2D */ 217 | GLFW_ALPHA_MAP_BIT = 0x00000008, 218 | 219 | /* Time spans longer than this (seconds) are considered to be infinity */ 220 | GLFW_INFINITY = 100000.0 221 | } 222 | 223 | /************************************************************************* 224 | * Typedefs 225 | *************************************************************************/ 226 | 227 | /* The video mode structure used by glfwGetVideoModes() */ 228 | struct GLFWvidmode { 229 | int Width, Height; 230 | int RedBits, BlueBits, GreenBits; 231 | } 232 | 233 | /* Image/texture information */ 234 | struct GLFWimage { 235 | int Width, Height; 236 | int Format; 237 | int BytesPerPixel; 238 | ubyte* Data; 239 | } 240 | 241 | /* Thread ID */ 242 | alias int GLFWthread; 243 | 244 | /* Mutex object */ 245 | alias void* GLFWmutex; 246 | 247 | /* Condition variable object */ 248 | alias void* GLFWcond; 249 | 250 | /* Function pointer types */ 251 | alias void function(int,int) GLFWwindowsizefun; 252 | alias int function() GLFWwindowclosefun; 253 | alias void function() GLFWwindowrefreshfun; 254 | alias void function(int,int) GLFWmousebuttonfun; 255 | alias void function(int,int) GLFWmouseposfun; 256 | alias void function(int) GLFWmousewheelfun; 257 | alias void function(int,int) GLFWkeyfun; 258 | alias void function(int,int) GLFWcharfun; 259 | alias void function(void*) GLFWthreadfun; 260 | 261 | 262 | /************************************************************************* 263 | * Prototypes 264 | *************************************************************************/ 265 | 266 | /* GLFW initialization, termination and version querying */ 267 | 268 | int glfwInit(); 269 | void glfwTerminate(); 270 | void glfwGetVersion( int *major, int *minor, int *rev ); 271 | 272 | /* Window handling */ 273 | int glfwOpenWindow( int width, int height, int redbits, int greenbits, int bluebits, int alphabits, int depthbits, int stencilbits, int mode ); 274 | void glfwOpenWindowHint( int target, int hint ); 275 | void glfwCloseWindow(); 276 | void glfwSetWindowTitle( const(char)* title ); 277 | void glfwGetWindowSize( int *width, int *height ); 278 | void glfwSetWindowSize( int width, int height ); 279 | void glfwSetWindowPos( int x, int y ); 280 | void glfwIconifyWindow(); 281 | void glfwRestoreWindow(); 282 | void glfwSwapBuffers(); 283 | void glfwSwapInterval( int interval ); 284 | int glfwGetWindowParam( int param ); 285 | void glfwSetWindowSizeCallback( GLFWwindowsizefun cbfun ); 286 | void glfwSetWindowCloseCallback( GLFWwindowclosefun cbfun ); 287 | void glfwSetWindowRefreshCallback( GLFWwindowrefreshfun cbfun ); 288 | 289 | /* Video mode functions */ 290 | int glfwGetVideoModes( GLFWvidmode *list, int maxcount ); 291 | void glfwGetDesktopMode( GLFWvidmode *mode ); 292 | 293 | /* Input handling */ 294 | void glfwPollEvents(); 295 | void glfwWaitEvents(); 296 | int glfwGetKey( int key ); 297 | int glfwGetMouseButton( int button ); 298 | void glfwGetMousePos( int *xpos, int *ypos ); 299 | void glfwSetMousePos( int xpos, int ypos ); 300 | int glfwGetMouseWheel(); 301 | void glfwSetMouseWheel( int pos ); 302 | void glfwSetKeyCallback( GLFWkeyfun cbfun ); 303 | void glfwSetCharCallback( GLFWcharfun cbfun ); 304 | void glfwSetMouseButtonCallback( GLFWmousebuttonfun cbfun ); 305 | void glfwSetMousePosCallback( GLFWmouseposfun cbfun ); 306 | void glfwSetMouseWheelCallback( GLFWmousewheelfun cbfun ); 307 | 308 | /* Joystick input */ 309 | int glfwGetJoystickParam( int joy, int param ); 310 | int glfwGetJoystickPos( int joy, float *pos, int numaxes ); 311 | int glfwGetJoystickButtons( int joy, ubyte *buttons, int numbuttons ); 312 | 313 | /* Time */ 314 | double glfwGetTime(); 315 | void glfwSetTime( double time ); 316 | void glfwSleep( double time ); 317 | 318 | /* Extension support */ 319 | int glfwExtensionSupported( const(char)* extension ); 320 | void* glfwGetProcAddress( const(char)* procname ); 321 | void glfwGetGLVersion( int *major, int *minor, int *rev ); 322 | 323 | /* Threading support */ 324 | GLFWthread glfwCreateThread( GLFWthreadfun fun, void *arg ); 325 | void glfwDestroyThread( GLFWthread ID ); 326 | int glfwWaitThread( GLFWthread ID, int waitmode ); 327 | GLFWthread glfwGetThreadID(); 328 | GLFWmutex glfwCreateMutex(); 329 | void glfwDestroyMutex( GLFWmutex mutex ); 330 | void glfwLockMutex( GLFWmutex mutex ); 331 | void glfwUnlockMutex( GLFWmutex mutex ); 332 | GLFWcond glfwCreateCond(); 333 | void glfwDestroyCond( GLFWcond cond ); 334 | void glfwWaitCond( GLFWcond cond, GLFWmutex mutex, double timeout ); 335 | void glfwSignalCond( GLFWcond cond ); 336 | void glfwBroadcastCond( GLFWcond cond ); 337 | int glfwGetNumberOfProcessors(); 338 | 339 | /* Enable/disable functions */ 340 | void glfwEnable( int token ); 341 | void glfwDisable( int token ); 342 | 343 | /* Image/texture I/O support */ 344 | int glfwReadImage( const(char)* name, GLFWimage *img, int flags ); 345 | int glfwReadMemoryImage( const void *data, long size, GLFWimage *img, int flags ); 346 | void glfwFreeImage( GLFWimage *img ); 347 | int glfwLoadTexture2D( const(char)* name, int flags ); 348 | int glfwLoadMemoryTexture2D( const void *data, long size, int flags ); 349 | int glfwLoadTextureImage2D( GLFWimage *img, int flags ); 350 | 351 | } 352 | 353 | -------------------------------------------------------------------------------- /src/glad/gl/all.d: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | OpenGL loader generated by glad 0.1.28 on 11/05/18 20:34:08. 4 | 5 | Language/Generator: D 6 | Specification: gl 7 | APIs: gl=4.6 8 | Profile: compatibility 9 | Extensions: 10 | GL_ARB_debug_output, 11 | GL_KHR_debug 12 | Loader: True 13 | Local files: False 14 | Omit khrplatform: False 15 | Reproducible: False 16 | 17 | Commandline: 18 | --profile="compatibility" --api="gl=4.6" --generator="d" --spec="gl" --extensions="GL_ARB_debug_output,GL_KHR_debug" 19 | Online: 20 | http://glad.dav1d.de/#profile=compatibility&language=d&specification=gl&loader=on&api=gl%3D4.6&extensions=GL_ARB_debug_output&extensions=GL_KHR_debug 21 | */ 22 | 23 | module glad.gl.all; 24 | 25 | 26 | public import glad.gl.funcs; 27 | public import glad.gl.ext; 28 | public import glad.gl.enums; 29 | public import glad.gl.types; 30 | -------------------------------------------------------------------------------- /src/glad/gl/ext.d: -------------------------------------------------------------------------------- 1 | module glad.gl.ext; 2 | 3 | 4 | private import glad.gl.types; 5 | private import glad.gl.enums; 6 | private import glad.gl.funcs; 7 | bool GL_ARB_debug_output; 8 | bool GL_KHR_debug; 9 | nothrow @nogc extern(System) { 10 | alias fp_glDebugMessageControlARB = void function(GLenum, GLenum, GLenum, GLsizei, const(GLuint)*, GLboolean); 11 | alias fp_glDebugMessageInsertARB = void function(GLenum, GLenum, GLuint, GLenum, GLsizei, const(GLchar)*); 12 | alias fp_glDebugMessageCallbackARB = void function(GLDEBUGPROCARB, const(void)*); 13 | alias fp_glGetDebugMessageLogARB = GLuint function(GLuint, GLsizei, GLenum*, GLenum*, GLuint*, GLenum*, GLsizei*, GLchar*); 14 | alias fp_glDebugMessageControlKHR = void function(GLenum, GLenum, GLenum, GLsizei, const(GLuint)*, GLboolean); 15 | alias fp_glDebugMessageInsertKHR = void function(GLenum, GLenum, GLuint, GLenum, GLsizei, const(GLchar)*); 16 | alias fp_glDebugMessageCallbackKHR = void function(GLDEBUGPROCKHR, const(void)*); 17 | alias fp_glGetDebugMessageLogKHR = GLuint function(GLuint, GLsizei, GLenum*, GLenum*, GLuint*, GLenum*, GLsizei*, GLchar*); 18 | alias fp_glPushDebugGroupKHR = void function(GLenum, GLuint, GLsizei, const(GLchar)*); 19 | alias fp_glPopDebugGroupKHR = void function(); 20 | alias fp_glObjectLabelKHR = void function(GLenum, GLuint, GLsizei, const(GLchar)*); 21 | alias fp_glGetObjectLabelKHR = void function(GLenum, GLuint, GLsizei, GLsizei*, GLchar*); 22 | alias fp_glObjectPtrLabelKHR = void function(const(void)*, GLsizei, const(GLchar)*); 23 | alias fp_glGetObjectPtrLabelKHR = void function(const(void)*, GLsizei, GLsizei*, GLchar*); 24 | alias fp_glGetPointervKHR = void function(GLenum, void**); 25 | } 26 | __gshared { 27 | fp_glDebugMessageCallbackARB glDebugMessageCallbackARB; 28 | fp_glGetPointervKHR glGetPointervKHR; 29 | fp_glObjectPtrLabelKHR glObjectPtrLabelKHR; 30 | fp_glDebugMessageCallbackKHR glDebugMessageCallbackKHR; 31 | fp_glDebugMessageControlARB glDebugMessageControlARB; 32 | fp_glGetDebugMessageLogARB glGetDebugMessageLogARB; 33 | fp_glGetObjectPtrLabelKHR glGetObjectPtrLabelKHR; 34 | fp_glDebugMessageInsertARB glDebugMessageInsertARB; 35 | fp_glDebugMessageControlKHR glDebugMessageControlKHR; 36 | fp_glObjectLabelKHR glObjectLabelKHR; 37 | fp_glGetDebugMessageLogKHR glGetDebugMessageLogKHR; 38 | fp_glDebugMessageInsertKHR glDebugMessageInsertKHR; 39 | fp_glPopDebugGroupKHR glPopDebugGroupKHR; 40 | fp_glGetObjectLabelKHR glGetObjectLabelKHR; 41 | fp_glPushDebugGroupKHR glPushDebugGroupKHR; 42 | } 43 | -------------------------------------------------------------------------------- /src/glad/gl/types.d: -------------------------------------------------------------------------------- 1 | module glad.gl.types; 2 | 3 | 4 | alias GLvoid = void; 5 | alias GLintptr = ptrdiff_t; 6 | alias GLsizei = int; 7 | alias GLchar = char; 8 | alias GLcharARB = byte; 9 | alias GLushort = ushort; 10 | alias GLint64EXT = long; 11 | alias GLshort = short; 12 | alias GLuint64 = ulong; 13 | alias GLhalfARB = ushort; 14 | alias GLubyte = ubyte; 15 | alias GLdouble = double; 16 | alias GLhandleARB = uint; 17 | alias GLint64 = long; 18 | alias GLenum = uint; 19 | alias GLeglImageOES = void*; 20 | alias GLintptrARB = ptrdiff_t; 21 | alias GLsizeiptr = ptrdiff_t; 22 | alias GLint = int; 23 | alias GLboolean = ubyte; 24 | alias GLbitfield = uint; 25 | alias GLsizeiptrARB = ptrdiff_t; 26 | alias GLfloat = float; 27 | alias GLuint64EXT = ulong; 28 | alias GLclampf = float; 29 | alias GLbyte = byte; 30 | alias GLclampd = double; 31 | alias GLuint = uint; 32 | alias GLvdpauSurfaceNV = ptrdiff_t; 33 | alias GLfixed = int; 34 | alias GLhalf = ushort; 35 | alias GLclampx = int; 36 | alias GLhalfNV = ushort; 37 | struct ___GLsync; alias __GLsync = ___GLsync*; 38 | alias GLsync = __GLsync*; 39 | struct __cl_context; alias _cl_context = __cl_context*; 40 | struct __cl_event; alias _cl_event = __cl_event*; 41 | extern(System) { 42 | alias GLDEBUGPROC = void function(GLenum, GLenum, GLuint, GLenum, GLsizei, in GLchar*, GLvoid*); 43 | alias GLDEBUGPROCARB = GLDEBUGPROC; 44 | alias GLDEBUGPROCKHR = GLDEBUGPROC; 45 | alias GLDEBUGPROCAMD = void function(GLuint, GLenum, GLenum, GLsizei, in GLchar*, GLvoid*); 46 | } 47 | -------------------------------------------------------------------------------- /src/glwtf/exception.d: -------------------------------------------------------------------------------- 1 | module glwtf.exception; 2 | 3 | 4 | class GLFWException : Exception { 5 | this(string s, string f=__FILE__, size_t l=__LINE__) { 6 | super(s, f, l); 7 | } 8 | } 9 | 10 | class WindowException : GLFWException { 11 | this(string s, string f=__FILE__, size_t l=__LINE__) { 12 | super(s, f, l); 13 | } 14 | } -------------------------------------------------------------------------------- /src/glwtf/glfw.d: -------------------------------------------------------------------------------- 1 | module glwtf.glfw; 2 | 3 | version(DynamicGLFW) { 4 | public import derelict.glfw3.glfw3; 5 | } else { 6 | public import deimos.glfw.glfw3; 7 | } -------------------------------------------------------------------------------- /src/glwtf/input.d: -------------------------------------------------------------------------------- 1 | module glwtf.input; 2 | 3 | 4 | private { 5 | import glwtf.glfw; 6 | import glwtf.signals; 7 | 8 | import std.conv : to; 9 | } 10 | 11 | AEventHandler cast_userptr(GLFWwindow* window) 12 | out (result) { assert(result !is null, "glfwGetWindowUserPointer returned null"); } 13 | body { 14 | void* user_ptr = glfwGetWindowUserPointer(window); 15 | return cast(AEventHandler)user_ptr; 16 | } 17 | 18 | 19 | private void function(int, string) glfw_error_callback; 20 | 21 | void register_glfw_error_callback(void function(int, string) cb) { 22 | glfw_error_callback = cb; 23 | 24 | glfwSetErrorCallback(&error_callback); 25 | } 26 | 27 | extern(C) { 28 | // window events // 29 | void window_resize_callback(GLFWwindow* window, int width, int height) { 30 | AEventHandler ae = cast_userptr(window); 31 | 32 | ae.on_resize.emit(width, height); 33 | } 34 | 35 | void window_close_callback(GLFWwindow* window) { 36 | AEventHandler ae = cast_userptr(window); 37 | 38 | bool close = cast(int)ae._on_close(); 39 | if(close) { 40 | ae.on_closing.emit(); 41 | } else { 42 | glfwSetWindowShouldClose(window, 0); 43 | } 44 | } 45 | 46 | void window_refresh_callback(GLFWwindow* window) { 47 | AEventHandler ae = cast_userptr(window); 48 | 49 | ae.on_refresh.emit(); 50 | } 51 | 52 | void window_focus_callback(GLFWwindow* window, int focused) { 53 | AEventHandler ae = cast_userptr(window); 54 | 55 | ae.on_focus.emit(focused == 1); 56 | } 57 | 58 | void window_iconify_callback(GLFWwindow* window, int iconified) { 59 | AEventHandler ae = cast_userptr(window); 60 | 61 | ae.on_iconify.emit(iconified == 1); 62 | } 63 | 64 | // user input // 65 | void key_callback(GLFWwindow* window, int key, int scancode, int state, int modifier) { 66 | AEventHandler ae = cast_userptr(window); 67 | 68 | if(state == GLFW_PRESS) { 69 | ae.on_key_down.emit(key, scancode, modifier); 70 | } else if(state == GLFW_REPEAT) { 71 | ae.on_key_repeat.emit(key, scancode, modifier); 72 | } else { 73 | ae.on_key_up.emit(key, scancode, modifier); 74 | } 75 | } 76 | 77 | void char_callback(GLFWwindow* window, uint c) { 78 | AEventHandler ae = cast_userptr(window); 79 | 80 | ae.on_char.emit(cast(dchar)c); 81 | } 82 | 83 | void mouse_button_callback(GLFWwindow* window, int button, int state, int modifier) { 84 | AEventHandler ae = cast_userptr(window); 85 | 86 | if(state == GLFW_PRESS) { 87 | ae.on_mouse_button_down.emit(button, modifier); 88 | } else { 89 | ae.on_mouse_button_up.emit(button, modifier); 90 | } 91 | } 92 | 93 | void cursor_pos_callback(GLFWwindow* window, double x, double y) { 94 | AEventHandler ae = cast_userptr(window); 95 | 96 | ae.on_mouse_pos.emit(x, y); 97 | } 98 | 99 | void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) { 100 | AEventHandler ae = cast_userptr(window); 101 | 102 | ae.on_scroll.emit(xoffset, yoffset); 103 | } 104 | 105 | // misc // 106 | void error_callback(int errno, const(char)* error) { 107 | glfw_error_callback(errno, to!string(error)); 108 | } 109 | } 110 | 111 | abstract class AEventHandler { 112 | // window 113 | Signal!(int, int) on_resize; 114 | Signal!() on_closing; 115 | Signal!() on_refresh; 116 | Signal!(bool) on_focus; 117 | Signal!(bool) on_iconify; 118 | 119 | bool _on_close() { return true; } 120 | 121 | // input 122 | Signal!(int, int, int) on_key_down; 123 | Signal!(int, int, int) on_key_repeat; 124 | Signal!(int, int, int) on_key_up; 125 | Signal!(dchar) on_char; 126 | Signal!(int, int) on_mouse_button_down; 127 | Signal!(int, int) on_mouse_button_up; 128 | Signal!(double, double) on_mouse_pos; 129 | Signal!(double, double) on_scroll; 130 | } 131 | 132 | 133 | class BaseGLFWEventHandler : AEventHandler { 134 | Signal!()[GLFW_KEY_LAST] single_key_down; 135 | Signal!()[GLFW_KEY_LAST] single_key_up; 136 | 137 | protected bool[GLFW_KEY_LAST] keymap; 138 | protected bool[GLFW_MOUSE_BUTTON_LAST] mousemap; 139 | 140 | this() { 141 | on_key_down.connect!"_on_key_down"(this); 142 | on_key_up.connect!"_on_key_up"(this); 143 | on_mouse_button_down.connect!"_on_mouse_button_down"(this); 144 | on_mouse_button_up.connect!"_on_mouse_button_up"(this); 145 | } 146 | 147 | package void register_callbacks(GLFWwindow* window) { 148 | glfwSetWindowUserPointer(window, cast(void *)this); 149 | 150 | glfwSetWindowSizeCallback(window, &window_resize_callback); 151 | glfwSetWindowCloseCallback(window, &window_close_callback); 152 | glfwSetWindowRefreshCallback(window, &window_refresh_callback); 153 | glfwSetWindowFocusCallback(window, &window_focus_callback); 154 | glfwSetWindowIconifyCallback(window, &window_iconify_callback); 155 | 156 | glfwSetKeyCallback(window, &key_callback); 157 | glfwSetCharCallback(window, &char_callback); 158 | glfwSetMouseButtonCallback(window, &mouse_button_callback); 159 | glfwSetCursorPosCallback(window, &cursor_pos_callback); 160 | glfwSetScrollCallback(window, &scroll_callback); 161 | } 162 | 163 | public void _on_key_down(int key, int scancode, int modifier) { 164 | keymap[key] = true; 165 | single_key_down[key].emit(); 166 | } 167 | 168 | public void _on_key_up(int key, int scancode, int modifier) { 169 | keymap[key] = false; 170 | single_key_up[key].emit(); 171 | } 172 | 173 | public void _on_mouse_button_down(int button, int modifier) { 174 | mousemap[button] = true; 175 | } 176 | public void _on_mouse_button_up(int button, int modifier) { 177 | mousemap[button] = false; 178 | } 179 | 180 | bool is_key_down(int key) { 181 | return keymap[key]; 182 | } 183 | 184 | bool is_mouse_down(int button) { 185 | return mousemap[button]; 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/glwtf/signals.d: -------------------------------------------------------------------------------- 1 | // Written in the D programming language. 2 | 3 | /** 4 | * Signals and Slots are an implementation of the Observer Pattern. 5 | * Essentially, when a Signal is emitted, a list of connected Observers 6 | * (called slots) are called. 7 | * 8 | * Copyright: Copyright Robert Klotzner 2012 - 2013. 9 | * License: Boost License 1.0. 10 | * Authors: Robert Klotzner 11 | */ 12 | /* Copyright Robert Klotzner 2012 - 2013. 13 | * Distributed under the Boost Software License, Version 1.0. 14 | * (See accompanying file LICENSE_1_0.txt or copy at 15 | * http://www.boost.org/LICENSE_1_0.txt) 16 | * 17 | * Based on the original implementation written by Walter Bright. (std.signals) 18 | * I shamelessly stole some ideas of: http://forum.dlang.org/thread/jjote0$1cql$1@digitalmars.com 19 | * written by Alex Rønne Petersen. 20 | */ 21 | module glwtf.signals; 22 | 23 | import core.atomic; 24 | import core.memory; 25 | 26 | 27 | // Hook into the GC to get informed about object deletions. 28 | private alias void delegate(Object) DisposeEvt; 29 | private extern (C) void rt_attachDisposeEvent( Object obj, DisposeEvt evt ); 30 | private extern (C) void rt_detachDisposeEvent( Object obj, DisposeEvt evt ); 31 | //debug=signal; 32 | // http://d.puremagic.com/issues/show_bug.cgi?id=10645 33 | version=bug10645; 34 | 35 | 36 | /** 37 | * string mixin for creating a signal. 38 | * 39 | * It creates a Signal instance named "_name", where name is given 40 | * as first parameter with given protection and an accessor method 41 | * with the current context protection named "name" returning either a 42 | * ref RestrictedSignal or ref Signal depending on the given 43 | * protection. 44 | * 45 | * Bugs: 46 | * This mixin generator does not work with templated types right now because of: 47 | * $(LINK2 http://d.puremagic.com/issues/show_bug.cgi?id=10502, 10502)$(BR) 48 | * You might wanna use the Signal struct directly in this 49 | * case. Ideally you write the code, the mixin would generate, manually 50 | * to ensure an easy upgrade path when the above bug gets fixed: 51 | --- 52 | * ref RestrictedSignal!(SomeTemplate!int) mysig() { return _mysig;} 53 | * private Signal!(SomeTemplate!int) _mysig; 54 | --- 55 | * 56 | * Params: 57 | * name = How the signal should be named. The ref returning function 58 | * will be named like this, the actual struct instance will have an 59 | * underscore prefixed. 60 | * 61 | * protection = Can be any valid protection specifier like 62 | * "private", "protected", "package" or in addition "none". Default 63 | * is "private". It specifies the protection of the Signal instance, 64 | * if none is given, private is used and the ref returning function 65 | * will return a Signal instead of a RestrictedSignal. The 66 | * protection of the accessor method is specified by the surrounding 67 | * protection scope. 68 | * 69 | * Example: 70 | --- 71 | import std.stdio; 72 | class MyObject 73 | { 74 | mixin(signal!(string, int)("valueChanged")); 75 | 76 | int value() @property { return _value; } 77 | int value(int v) @property 78 | { 79 | if (v != _value) 80 | { 81 | _value = v; 82 | // call all the connected slots with the two parameters 83 | _valueChanged.emit("setting new value", v); 84 | } 85 | return v; 86 | } 87 | private: 88 | int _value; 89 | } 90 | 91 | class Observer 92 | { // our slot 93 | void watch(string msg, int i) 94 | { 95 | writefln("Observed msg '%s' and value %s", msg, i); 96 | } 97 | } 98 | void watch(string msg, int i) 99 | { 100 | writefln("Globally observed msg '%s' and value %s", msg, i); 101 | } 102 | void main() 103 | { 104 | auto a = new MyObject; 105 | Observer o = new Observer; 106 | 107 | a.value = 3; // should not call o.watch() 108 | a.valueChanged.connect!"watch"(o); // o.watch is the slot 109 | a.value = 4; // should call o.watch() 110 | a.valueChanged.disconnect!"watch"(o); // o.watch is no longer a slot 111 | a.value = 5; // so should not call o.watch() 112 | a.valueChanged.connect!"watch"(o); // connect again 113 | // Do some fancy stuff: 114 | a.valueChanged.connect!Observer(o, (obj, msg, i) => obj.watch("Some other text I made up", i+1)); 115 | a.valueChanged.strongConnect(&watch); 116 | a.value = 6; // should call o.watch() 117 | destroy(o); // destroying o should automatically disconnect it 118 | a.value = 7; // should not call o.watch() 119 | 120 | } 121 | --- 122 | * which should print: 123 | *
 124 |  * Observed msg 'setting new value' and value 4
 125 |  * Observed msg 'setting new value' and value 6
 126 |  * Observed msg 'Some other text I made up' and value 7
 127 |  * Globally observed msg 'setting new value' and value 6
 128 |  * Globally observed msg 'setting new value' and value 7
 129 |  * 
130 | */ 131 | string signal(Args...)(string name, string protection="private") @safe { 132 | assert(protection == "public" || protection == "private" || protection == "package" || protection == "protected" || protection == "none", "Invalid protection specified, must be either: public, private, package, protected or none"); 133 | 134 | string argList="("; 135 | import std.traits : fullyQualifiedName; 136 | foreach (arg; Args) 137 | { 138 | argList~=fullyQualifiedName!(arg)~", "; 139 | } 140 | if (argList.length>"(".length) 141 | argList = argList[0 .. $-2]; 142 | argList ~= ")"; 143 | 144 | string output = (protection == "none" ? "private" : protection) ~ " Signal!" ~ argList ~ " _" ~ name ~ ";\n"; 145 | string rType= protection == "none" ? "Signal!" : "RestrictedSignal!"; 146 | output ~= "ref " ~ rType ~ argList ~ " " ~ name ~ "() { return _" ~ name ~ ";}\n"; 147 | return output; 148 | } 149 | 150 | /** 151 | * Full signal implementation. 152 | * 153 | * It implements the emit function for all other functionality it has 154 | * this aliased to RestrictedSignal. 155 | * 156 | * A signal is a way to couple components together in a very loose 157 | * way. The receiver does not need to know anything about the sender 158 | * and the sender does not need to know anything about the 159 | * receivers. The sender will just call emit when something happens, 160 | * the signal takes care of notifing all interested parties. By using 161 | * wrapper delegates/functions, not even the function signature of 162 | * sender/receiver need to match. Another consequence of this very 163 | * loose coupling is, that a connected object will be freed by the GC 164 | * if all references to it are dropped, even if it is still connected 165 | * to a signal, the connection will simply be dropped. If this wasn't 166 | * the case you'd either end up managing connections by hand, soon 167 | * asking yourself why you are using a language with a GC and then 168 | * still have to handle the life time of your objects manually or you 169 | * don't care which results in memory leaks. If in your application 170 | * the connections made by a signal are not that loose you can use 171 | * strongConnect(), in this case the GC won't free your object until 172 | * it was disconnected from the signal or the signal got itself destroyed. 173 | * 174 | * This struct is not thread-safe. 175 | * 176 | * Bugs: The code probably won't compile with -profile because of bug: 177 | * $(LINK2 http://d.puremagic.com/issues/show_bug.cgi?id=10260, 10260) 178 | */ 179 | struct Signal(Args...) 180 | { 181 | alias restricted this; 182 | 183 | /** 184 | * Emit the signal. 185 | * 186 | * All connected slots which are still alive will be called. If 187 | * any of the slots throws an exception, the other slots will 188 | * still be called. You'll receive a chained exception with all 189 | * exceptions that were thrown. Thus slots won't influence each 190 | * others execution. 191 | * 192 | * The slots are called in the same sequence as they were registered. 193 | * 194 | * emit also takes care of actually removing dead connections. For 195 | * concurrency reasons they are set just to an invalid state by the GC. 196 | * 197 | * If you remove a slot during emit() it won't be called in the 198 | * current run if it wasn't already. 199 | * 200 | * If you add a slot during emit() it will be called in the 201 | * current emit() run. Note however Signal is not thread-safe, "called 202 | * during emit" basically means called from within a slot. 203 | */ 204 | void emit( Args args ) @trusted 205 | { 206 | restricted_._impl.emit(args); 207 | } 208 | 209 | /** 210 | * Get access to the rest of the signals functionality. 211 | */ 212 | ref RestrictedSignal!(Args) restricted() @property @trusted 213 | { 214 | return restricted_; 215 | } 216 | 217 | private: 218 | RestrictedSignal!(Args) restricted_; 219 | } 220 | 221 | /** 222 | * The signal implementation, not providing an emit method. 223 | * 224 | * The idea is to instantiate a Signal privately and provide a 225 | * public accessor method for accessing the contained 226 | * RestrictedSignal. You can use the signal string mixin, which does 227 | * exactly that. 228 | */ 229 | struct RestrictedSignal(Args...) 230 | { 231 | /** 232 | * Direct connection to an object. 233 | * 234 | * Use this method if you want to connect directly to an objects 235 | * method matching the signature of this signal. The connection 236 | * will have weak reference semantics, meaning if you drop all 237 | * references to the object the garbage collector will collect it 238 | * and this connection will be removed. 239 | * 240 | * Preconditions: obj must not be null. mixin("&obj."~method) 241 | * must be valid and compatible. 242 | * Params: 243 | * obj = Some object of a class implementing a method 244 | * compatible with this signal. 245 | */ 246 | void connect(string method, ClassType)(ClassType obj) @trusted 247 | if (is(ClassType == class) && __traits(compiles, {void delegate(Args) dg = mixin("&obj."~method);})) 248 | in 249 | { 250 | assert(obj); 251 | } 252 | body 253 | { 254 | _impl.addSlot(obj, cast(void delegate())mixin("&obj."~method)); 255 | } 256 | /** 257 | * Indirect connection to an object. 258 | * 259 | * Use this overload if you want to connect to an object's method 260 | * which does not match the signal's signature. You can provide 261 | * any delegate to do the parameter adaption, but make sure your 262 | * delegates' context does not contain a reference to the target 263 | * object, instead use the provided obj parameter, where the 264 | * object passed to connect will be passed to your delegate. 265 | * This is to make weak ref semantics possible, if your delegate 266 | * contains a ref to obj, the object won't be freed as long as 267 | * the connection remains. 268 | * 269 | * Preconditions: obj and dg must not be null (dg's context 270 | * may). dg's context must not be equal to obj. 271 | * 272 | * Params: 273 | * obj = The object to connect to. It will be passed to the 274 | * delegate when the signal is emitted. 275 | * 276 | * dg = A wrapper delegate which takes care of calling some 277 | * method of obj. It can do any kind of parameter adjustments 278 | * necessary. 279 | */ 280 | void connect(ClassType)(ClassType obj, void delegate(ClassType obj, Args) dg) @trusted 281 | if (is(ClassType == class)) 282 | in 283 | { 284 | assert(obj); 285 | assert(dg); 286 | assert(cast(void*)obj !is dg.ptr); 287 | } 288 | body 289 | { 290 | _impl.addSlot(obj, cast(void delegate()) dg); 291 | } 292 | 293 | /** 294 | * Connect with strong ref semantics. 295 | * 296 | * Use this overload if you either really, really want strong ref 297 | * semantics for some reason or because you want to connect some 298 | * non-class method delegate. Whatever the delegates' context 299 | * references, will stay in memory as long as the signals 300 | * connection is not removed and the signal gets not destroyed 301 | * itself. 302 | * 303 | * Preconditions: dg must not be null. (Its context may.) 304 | * 305 | * Params: 306 | * dg = The delegate to be connected. 307 | */ 308 | void strongConnect(void delegate(Args) dg) @trusted 309 | in 310 | { 311 | assert(dg); 312 | } 313 | body 314 | { 315 | _impl.addSlot(null, cast(void delegate()) dg); 316 | } 317 | 318 | 319 | /** 320 | * Disconnect a direct connection. 321 | * 322 | * After issuing this call, methods of obj won't be triggered any 323 | * longer when emit is called. 324 | * Preconditions: Same as for direct connect. 325 | */ 326 | void disconnect(string method, ClassType)(ClassType obj) @trusted 327 | if (is(ClassType == class) && __traits(compiles, {void delegate(Args) dg = mixin("&obj."~method);})) 328 | in 329 | { 330 | assert(obj); 331 | } 332 | body 333 | { 334 | void delegate(Args) dg = mixin("&obj."~method); 335 | _impl.removeSlot(obj, cast(void delegate()) dg); 336 | } 337 | 338 | /** 339 | * Disconnect an indirect connection. 340 | * 341 | * For this to work properly, dg has to be exactly the same as 342 | * the one passed to connect. So if you used a lamda you have to 343 | * keep a reference to it somewhere if you want to disconnect 344 | * the connection later on. If you want to remove all 345 | * connections to a particular object use the overload which only 346 | * takes an object paramter. 347 | */ 348 | void disconnect(ClassType)(ClassType obj, void delegate(ClassType, T1) dg) @trusted 349 | if (is(ClassType == class)) 350 | in 351 | { 352 | assert(obj); 353 | assert(dg); 354 | } 355 | body 356 | { 357 | _impl.removeSlot(obj, cast(void delegate())dg); 358 | } 359 | 360 | /** 361 | * Disconnect all connections to obj. 362 | * 363 | * All connections to obj made with calls to connect are removed. 364 | */ 365 | void disconnect(ClassType)(ClassType obj) @trusted if (is(ClassType == class)) 366 | in 367 | { 368 | assert(obj); 369 | } 370 | body 371 | { 372 | _impl.removeSlot(obj); 373 | } 374 | 375 | /** 376 | * Disconnect a connection made with strongConnect. 377 | * 378 | * Disconnects all connections to dg. 379 | */ 380 | void strongDisconnect(void delegate(Args) dg) @trusted 381 | in 382 | { 383 | assert(dg); 384 | } 385 | body 386 | { 387 | _impl.removeSlot(null, cast(void delegate()) dg); 388 | } 389 | private: 390 | SignalImpl _impl; 391 | } 392 | 393 | private struct SignalImpl 394 | { 395 | /** 396 | * Forbid copying. 397 | * Unlike the old implementations, it now is theoretically 398 | * possible to copy a signal. Even different semantics are 399 | * possible. But none of the possible semantics are what the user 400 | * intended in all cases, so I believe it is still the safer 401 | * choice to simply disallow copying. 402 | */ 403 | @disable this(this); 404 | /// Forbit copying 405 | @disable void opAssign(SignalImpl other); 406 | 407 | void emit(Args...)( Args args ) 408 | { 409 | int emptyCount = 0; 410 | if (!_slots.emitInProgress) 411 | { 412 | _slots.emitInProgress = true; 413 | scope (exit) _slots.emitInProgress = false; 414 | } 415 | else 416 | emptyCount = -1; 417 | doEmit(0, emptyCount, args); 418 | if (emptyCount > 0) 419 | { 420 | _slots.slots = _slots.slots[0 .. $-emptyCount]; 421 | _slots.slots.assumeSafeAppend(); 422 | } 423 | } 424 | 425 | void addSlot(Object obj, void delegate() dg) 426 | { 427 | auto oldSlots = _slots.slots; 428 | if (oldSlots.capacity <= oldSlots.length) 429 | { 430 | auto buf = new SlotImpl[oldSlots.length+1]; // TODO: This growing strategy might be inefficient. 431 | foreach (i, ref slot ; oldSlots) 432 | buf[i].moveFrom(slot); 433 | oldSlots = buf; 434 | } 435 | else 436 | oldSlots.length = oldSlots.length + 1; 437 | 438 | oldSlots[$-1].construct(obj, dg); 439 | _slots.slots = oldSlots; 440 | } 441 | void removeSlot(Object obj, void delegate() dg) 442 | { 443 | removeSlot((const ref SlotImpl item) => item.wasConstructedFrom(obj, dg)); 444 | } 445 | void removeSlot(Object obj) 446 | { 447 | removeSlot((const ref SlotImpl item) => item.obj is obj); 448 | } 449 | 450 | ~this() 451 | { 452 | foreach (ref slot; _slots.slots) 453 | { 454 | debug (signal) { import std.stdio; stderr.writefln("Destruction, removing some slot(%s, weakref: %s), signal: ", &slot, &slot._obj, &this); } 455 | slot.reset(); // This is needed because ATM the GC won't trigger struct 456 | // destructors to be run when within a GC managed array. 457 | } 458 | } 459 | /// Little helper functions: 460 | 461 | /** 462 | * Find and make invalid any slot for which isRemoved returns true. 463 | */ 464 | void removeSlot(bool delegate(const ref SlotImpl) isRemoved) 465 | { 466 | if(_slots.emitInProgress) 467 | { 468 | foreach (ref slot; _slots.slots) 469 | if (isRemoved(slot)) 470 | slot.reset(); 471 | } 472 | else // It is save to do immediate cleanup: 473 | { 474 | int emptyCount = 0; 475 | auto mslots = _slots.slots; 476 | foreach (i, ref slot; mslots) 477 | // We are retrieving obj twice which is quite expensive because of GC lock: 478 | if(!slot.isValid || isRemoved(slot)) 479 | { 480 | emptyCount++; 481 | slot.reset(); 482 | } 483 | else if(emptyCount) 484 | mslots[i-emptyCount].moveFrom(slot); 485 | 486 | if (emptyCount > 0) 487 | { 488 | mslots = mslots[0..$-emptyCount]; 489 | mslots.assumeSafeAppend(); 490 | _slots.slots = mslots; 491 | } 492 | } 493 | } 494 | 495 | /** 496 | * Helper method to allow all slots being called even in case of an exception. 497 | * All exceptions that occur will be chained. 498 | * Any invalid slots (GC collected or removed) will be dropped. 499 | */ 500 | void doEmit(Args...)(int offset, ref int emptyCount, Args args ) 501 | { 502 | int i=offset; 503 | auto myslots = _slots.slots; 504 | scope (exit) if (i+10) 519 | { 520 | myslots[i-emptyCount].reset(); 521 | myslots[i-emptyCount].moveFrom(myslots[i]); 522 | } 523 | } 524 | } 525 | 526 | SlotArray _slots; 527 | } 528 | 529 | 530 | // Simple convenience struct for signal implementation. 531 | // Its is inherently unsafe. It is not a template so SignalImpl does 532 | // not need to be one. 533 | private struct SlotImpl 534 | { 535 | @disable this(this); 536 | @disable void opAssign(SlotImpl other); 537 | 538 | /// Pass null for o if you have a strong ref delegate. 539 | /// dg.funcptr must not point to heap memory. 540 | void construct(Object o, void delegate() dg) 541 | in { assert(this is SlotImpl.init); } 542 | body 543 | { 544 | _obj.construct(o); 545 | _dataPtr = dg.ptr; 546 | _funcPtr = dg.funcptr; 547 | assert(GC.addrOf(_funcPtr) is null, "Your function is implemented on the heap? Such dirty tricks are not supported with std.signal!"); 548 | if (o) 549 | { 550 | if (_dataPtr is cast(void*) o) 551 | _dataPtr = directPtrFlag; 552 | hasObject = true; 553 | } 554 | } 555 | 556 | /** 557 | * Check whether this slot was constructed from object o and delegate dg. 558 | */ 559 | bool wasConstructedFrom(Object o, void delegate() dg) const 560 | { 561 | if ( o && dg.ptr is cast(void*) o) 562 | return obj is o && _dataPtr is directPtrFlag && funcPtr is dg.funcptr; 563 | else 564 | return obj is o && _dataPtr is dg.ptr && funcPtr is dg.funcptr; 565 | } 566 | /** 567 | * Implement proper explict move. 568 | */ 569 | void moveFrom(ref SlotImpl other) 570 | in { assert(this is SlotImpl.init); } 571 | body 572 | { 573 | auto o = other.obj; 574 | _obj.construct(o); 575 | _dataPtr = other._dataPtr; 576 | _funcPtr = other._funcPtr; 577 | other.reset(); // Destroy original! 578 | 579 | } 580 | @property Object obj() const 581 | { 582 | return _obj.obj; 583 | } 584 | 585 | /** 586 | * Whether or not _obj should contain a valid object. (We have a weak connection) 587 | */ 588 | bool hasObject() @property const 589 | { 590 | return cast(ptrdiff_t) _funcPtr & 1; 591 | } 592 | 593 | /** 594 | * Check whether this is a valid slot. 595 | * 596 | * Meaning opCall will call something and return true; 597 | */ 598 | bool isValid() @property const 599 | { 600 | return funcPtr && (!hasObject || obj !is null); 601 | } 602 | /** 603 | * Call the slot. 604 | * 605 | * Returns: True if the call was successful (the slot was valid). 606 | */ 607 | bool opCall(Args...)(Args args) 608 | { 609 | auto o = obj; 610 | void* o_addr = cast(void*)(o); 611 | 612 | if (!funcPtr || (hasObject && !o_addr)) 613 | return false; 614 | if (_dataPtr is directPtrFlag || !hasObject) 615 | { 616 | void delegate(Args) mdg; 617 | mdg.funcptr=cast(void function(Args)) funcPtr; 618 | debug (signal) { import std.stdio; writefln("hasObject: %s, o_addr: %s, dataPtr: %s", hasObject, o_addr, _dataPtr);} 619 | assert((hasObject && _dataPtr is directPtrFlag) || (!hasObject && _dataPtr !is directPtrFlag)); 620 | if (hasObject) 621 | mdg.ptr = o_addr; 622 | else 623 | mdg.ptr = _dataPtr; 624 | mdg(args); 625 | } 626 | else 627 | { 628 | void delegate(Object, Args) mdg; 629 | mdg.ptr = _dataPtr; 630 | mdg.funcptr = cast(void function(Object, Args)) funcPtr; 631 | mdg(o, args); 632 | } 633 | return true; 634 | } 635 | /** 636 | * Reset this instance to its intial value. 637 | */ 638 | void reset() { 639 | _funcPtr = SlotImpl.init._funcPtr; 640 | _dataPtr = SlotImpl.init._dataPtr; 641 | _obj.reset(); 642 | } 643 | private: 644 | void* funcPtr() @property const 645 | { 646 | return cast(void*)( cast(ptrdiff_t)_funcPtr & ~cast(ptrdiff_t)1); 647 | } 648 | void hasObject(bool yes) @property 649 | { 650 | if (yes) 651 | _funcPtr = cast(void*)(cast(ptrdiff_t) _funcPtr | 1); 652 | else 653 | _funcPtr = cast(void*)(cast(ptrdiff_t) _funcPtr & ~cast(ptrdiff_t)1); 654 | } 655 | void* _funcPtr; 656 | void* _dataPtr; 657 | WeakRef _obj; 658 | 659 | 660 | enum directPtrFlag = cast(void*)(~0); 661 | } 662 | 663 | 664 | // Provides a way of holding a reference to an object, without the GC seeing it. 665 | private struct WeakRef 666 | { 667 | /** 668 | * As struct must be relocatable, it is not even possible to 669 | * provide proper copy support for WeakRef. rt_attachDisposeEvent 670 | * is used for registering unhook. D's move semantics assume 671 | * relocatable objects, which results in this(this) being called 672 | * for one instance and the destructor for another, thus the wrong 673 | * handlers are deregistered. D's assumption of relocatable 674 | * objects is not matched, so move() for example will still simply 675 | * swap contents of two structs, resulting in the wrong unhook 676 | * delegates being unregistered. 677 | 678 | * Unfortunately the runtime still blindly copies WeakRefs if they 679 | * are in a dynamic array and reallocation is needed. This case 680 | * has to be handled separately. 681 | */ 682 | @disable this(this); 683 | @disable void opAssign(WeakRef other); 684 | void construct(Object o) 685 | in { assert(this is WeakRef.init); } 686 | body 687 | { 688 | debug (signal) createdThis=&this; 689 | debug (signal) { import std.stdio; writefln("WeakRef.construct for %s and object: %s", &this, o); } 690 | if (!o) 691 | return; 692 | _obj = InvisibleAddress.construct(cast(void*)o); 693 | rt_attachDisposeEvent(o, &unhook); 694 | } 695 | Object obj() @property const 696 | { 697 | void* o = null; 698 | // Two iterations are necessary, because after the atomic load 699 | // we still have an invisible address, thus the GC can reset 700 | // _obj after we already retrieved the data. Also the call to 701 | // GC.addrOf needs to be done twice, otherwise still segfaults 702 | // still happen. With two iterations I wasn't able to trigger 703 | // any segfault with test/testheisenbug.d. The assertion in 704 | // Observer.watch never triggered anyways with this 705 | // implementation. 706 | foreach ( i ; 0..2) 707 | { 708 | auto tmp = atomicLoad(_obj); // Does not work with constructor 709 | debug (signal) { import std.stdio; writefln("Loaded %s, should be: %s", tmp, cast(InvisibleAddress)_obj); } 710 | o = tmp.address; 711 | if ( o is null) 712 | return null; // Nothing to do then. 713 | o = GC.addrOf(tmp.address); 714 | } 715 | if (o) 716 | { 717 | assert(GC.addrOf(o), "Not possible!"); 718 | return cast(Object)o; 719 | } 720 | return null; 721 | } 722 | /** 723 | * Reset this instance to its intial value. 724 | */ 725 | void reset() { 726 | auto o = obj; 727 | debug (signal) { import std.stdio; writefln("WeakRef.reset for %s and object: %s", &this, o); } 728 | if (o) 729 | { 730 | rt_detachDisposeEvent(o, &unhook); 731 | unhook(o); 732 | } 733 | debug (signal) createdThis = null; 734 | } 735 | 736 | ~this() 737 | { 738 | reset(); 739 | } 740 | private: 741 | debug (signal) 742 | { 743 | invariant() 744 | { 745 | import std.conv : text; 746 | assert(createdThis is null || &this is createdThis, text("We changed address! This should really not happen! Orig address: ", cast(void*)createdThis, " new address: ", cast(void*)&this)); 747 | } 748 | WeakRef* createdThis; 749 | } 750 | void unhook(Object o) 751 | { 752 | version (all) 753 | atomicStore(_obj, InvisibleAddress.construct(null)); 754 | else 755 | _obj = InvisibleAddress(null); 756 | } 757 | shared(InvisibleAddress) _obj; 758 | } 759 | 760 | version(D_LP64) 761 | { 762 | struct InvisibleAddress 763 | { 764 | version(bug10645) 765 | { 766 | static InvisibleAddress construct(void* o) 767 | { 768 | return InvisibleAddress(~cast(ptrdiff_t)o); 769 | } 770 | } 771 | else 772 | { 773 | this(void* o) 774 | { 775 | _addr = ~cast(ptrdiff_t)(o); 776 | debug (signal) debug (3) { import std.stdio; writeln("Constructor _addr: ", _addr);} 777 | debug (signal) debug (3) { import std.stdio; writeln("Constructor ~_addr: ", ~_addr);} 778 | } 779 | } 780 | void* address() @property const 781 | { 782 | debug (signal) debug (3) { import std.stdio; writeln("_addr: ", _addr);} 783 | debug (signal) debug (3) { import std.stdio; writeln("~_addr: ", ~_addr);} 784 | return cast(void*) ~ _addr; 785 | } 786 | debug(signal) string toString() 787 | { 788 | import std.conv : text; 789 | return text(address); 790 | } 791 | private: 792 | ptrdiff_t _addr = ~ cast(ptrdiff_t) 0; 793 | } 794 | } 795 | else 796 | { 797 | struct InvisibleAddress 798 | { 799 | version(bug10645) 800 | { 801 | static InvisibleAddress construct(void* o) 802 | { 803 | auto tmp = cast(ptrdiff_t) cast(void*) o; 804 | auto addrHigh = (tmp>>16)&0x0000ffff | 0xffff0000; // Address relies in kernel space 805 | auto addrLow = tmp&0x0000ffff | 0xffff0000; 806 | return InvisibleAddress(addrHigh, addrLow); 807 | } 808 | } 809 | else 810 | { 811 | this(void* o) 812 | { 813 | auto tmp = cast(ptrdiff_t) cast(void*) o; 814 | _addrHigh = (tmp>>16)&0x0000ffff | 0xffff0000; // Address relies in kernel space 815 | _addrLow = tmp&0x0000ffff | 0xffff0000; 816 | } 817 | } 818 | void* address() @property const 819 | { 820 | return cast(void*) (_addrHigh<<16 | (_addrLow & 0x0000ffff)); 821 | } 822 | debug(signal) string toString() 823 | { 824 | import std.conv : text; 825 | return text(address); 826 | } 827 | private: 828 | ptrdiff_t _addrHigh = 0xffff0000; 829 | ptrdiff_t _addrLow = 0xffff0000; 830 | } 831 | } 832 | 833 | /** 834 | * Provides a way of storing flags in unused parts of a typical D array. 835 | * 836 | * By unused I mean the highest bits of the length (We don't need to support 4 billion slots per signal with int or 10^19 if length gets changed to 64 bits.) 837 | */ 838 | private struct SlotArray { 839 | // Choose int for now, this saves 4 bytes on 64 bits. 840 | alias int lengthType; 841 | import std.bitmanip : bitfields; 842 | enum reservedBitsCount = 3; 843 | enum maxSlotCount = lengthType.max >> reservedBitsCount; 844 | SlotImpl[] slots() @property 845 | { 846 | return _ptr[0 .. length]; 847 | } 848 | void slots(SlotImpl[] newSlots) @property 849 | { 850 | _ptr = newSlots.ptr; 851 | version(assert) 852 | { 853 | import std.conv : text; 854 | assert(newSlots.length <= maxSlotCount, text("Maximum slots per signal exceeded: ", newSlots.length, "/", maxSlotCount)); 855 | } 856 | _blength.length &= ~maxSlotCount; 857 | _blength.length |= newSlots.length; 858 | } 859 | size_t length() @property 860 | { 861 | return _blength.length & maxSlotCount; 862 | } 863 | 864 | bool emitInProgress() @property 865 | { 866 | return _blength.emitInProgress; 867 | } 868 | void emitInProgress(bool val) @property 869 | { 870 | _blength.emitInProgress = val; 871 | } 872 | private: 873 | SlotImpl* _ptr; 874 | union BitsLength { 875 | mixin(bitfields!( 876 | bool, "", lengthType.sizeof*8-1, 877 | bool, "emitInProgress", 1 878 | )); 879 | lengthType length; 880 | } 881 | BitsLength _blength; 882 | } 883 | unittest { 884 | SlotArray arr; 885 | auto tmp = new SlotImpl[10]; 886 | arr.slots = tmp; 887 | assert(arr.length == 10); 888 | assert(!arr.emitInProgress); 889 | arr.emitInProgress = true; 890 | assert(arr.emitInProgress); 891 | assert(arr.length == 10); 892 | assert(arr.slots is tmp); 893 | arr.slots = tmp; 894 | assert(arr.emitInProgress); 895 | assert(arr.length == 10); 896 | assert(arr.slots is tmp); 897 | debug (signal){ import std.stdio; 898 | writeln("Slot array tests passed!"); 899 | } 900 | } 901 | unittest 902 | { // Check that above example really works ... 903 | debug (signal) import std.stdio; 904 | class MyObject 905 | { 906 | mixin(signal!(string, int)("valueChanged")); 907 | 908 | int value() @property { return _value; } 909 | int value(int v) @property 910 | { 911 | if (v != _value) 912 | { 913 | _value = v; 914 | // call all the connected slots with the two parameters 915 | _valueChanged.emit("setting new value", v); 916 | } 917 | return v; 918 | } 919 | private: 920 | int _value; 921 | } 922 | 923 | class Observer 924 | { // our slot 925 | void watch(string msg, int i) 926 | { 927 | debug (signal) writefln("Observed msg '%s' and value %s", msg, i); 928 | } 929 | } 930 | 931 | void watch(string msg, int i) 932 | { 933 | debug (signal) writefln("Globally observed msg '%s' and value %s", msg, i); 934 | } 935 | auto a = new MyObject; 936 | Observer o = new Observer; 937 | 938 | a.value = 3; // should not call o.watch() 939 | a.valueChanged.connect!"watch"(o); // o.watch is the slot 940 | a.value = 4; // should call o.watch() 941 | a.valueChanged.disconnect!"watch"(o); // o.watch is no longer a slot 942 | a.value = 5; // so should not call o.watch() 943 | a.valueChanged.connect!"watch"(o); // connect again 944 | // Do some fancy stuff: 945 | a.valueChanged.connect!Observer(o, (obj, msg, i) => obj.watch("Some other text I made up", i+1)); 946 | a.valueChanged.strongConnect(&watch); 947 | a.value = 6; // should call o.watch() 948 | destroy(o); // destroying o should automatically disconnect it 949 | a.value = 7; // should not call o.watch() 950 | 951 | } 952 | 953 | unittest 954 | { 955 | debug (signal) import std.stdio; 956 | class Observer 957 | { 958 | void watch(string msg, int i) 959 | { 960 | //writefln("Observed msg '%s' and value %s", msg, i); 961 | captured_value = i; 962 | captured_msg = msg; 963 | } 964 | 965 | 966 | int captured_value; 967 | string captured_msg; 968 | } 969 | 970 | class SimpleObserver 971 | { 972 | void watchOnlyInt(int i) { 973 | captured_value = i; 974 | } 975 | int captured_value; 976 | } 977 | 978 | class Foo 979 | { 980 | @property int value() { return _value; } 981 | 982 | @property int value(int v) 983 | { 984 | if (v != _value) 985 | { _value = v; 986 | _extendedSig.emit("setting new value", v); 987 | //_simpleSig.emit(v); 988 | } 989 | return v; 990 | } 991 | 992 | mixin(signal!(string, int)("extendedSig")); 993 | //Signal!(int) simpleSig; 994 | 995 | private: 996 | int _value; 997 | } 998 | 999 | Foo a = new Foo; 1000 | Observer o = new Observer; 1001 | SimpleObserver so = new SimpleObserver; 1002 | // check initial condition 1003 | assert(o.captured_value == 0); 1004 | assert(o.captured_msg == ""); 1005 | 1006 | // set a value while no observation is in place 1007 | a.value = 3; 1008 | assert(o.captured_value == 0); 1009 | assert(o.captured_msg == ""); 1010 | 1011 | // connect the watcher and trigger it 1012 | a.extendedSig.connect!"watch"(o); 1013 | a.value = 4; 1014 | assert(o.captured_value == 4); 1015 | assert(o.captured_msg == "setting new value"); 1016 | 1017 | // disconnect the watcher and make sure it doesn't trigger 1018 | a.extendedSig.disconnect!"watch"(o); 1019 | a.value = 5; 1020 | assert(o.captured_value == 4); 1021 | assert(o.captured_msg == "setting new value"); 1022 | //a.extendedSig.connect!Observer(o, (obj, msg, i) { obj.watch("Hahah", i); }); 1023 | a.extendedSig.connect!Observer(o, (obj, msg, i) => obj.watch("Hahah", i) ); 1024 | 1025 | a.value = 7; 1026 | debug (signal) stderr.writeln("After asignment!"); 1027 | assert(o.captured_value == 7); 1028 | assert(o.captured_msg == "Hahah"); 1029 | a.extendedSig.disconnect(o); // Simply disconnect o, otherwise we would have to store the lamda somewhere if we want to disconnect later on. 1030 | // reconnect the watcher and make sure it triggers 1031 | a.extendedSig.connect!"watch"(o); 1032 | a.value = 6; 1033 | assert(o.captured_value == 6); 1034 | assert(o.captured_msg == "setting new value"); 1035 | 1036 | // destroy the underlying object and make sure it doesn't cause 1037 | // a crash or other problems 1038 | debug (signal) stderr.writefln("Disposing"); 1039 | destroy(o); 1040 | debug (signal) stderr.writefln("Disposed"); 1041 | a.value = 7; 1042 | } 1043 | 1044 | unittest { 1045 | class Observer 1046 | { 1047 | int i; 1048 | long l; 1049 | string str; 1050 | 1051 | void watchInt(string str, int i) 1052 | { 1053 | this.str = str; 1054 | this.i = i; 1055 | } 1056 | 1057 | void watchLong(string str, long l) 1058 | { 1059 | this.str = str; 1060 | this.l = l; 1061 | } 1062 | } 1063 | 1064 | class Bar 1065 | { 1066 | @property void value1(int v) { _s1.emit("str1", v); } 1067 | @property void value2(int v) { _s2.emit("str2", v); } 1068 | @property void value3(long v) { _s3.emit("str3", v); } 1069 | 1070 | mixin(signal!(string, int) ("s1")); 1071 | mixin(signal!(string, int) ("s2")); 1072 | mixin(signal!(string, long)("s3")); 1073 | } 1074 | 1075 | void test(T)(T a) 1076 | { 1077 | auto o1 = new Observer; 1078 | auto o2 = new Observer; 1079 | auto o3 = new Observer; 1080 | 1081 | // connect the watcher and trigger it 1082 | a.s1.connect!"watchInt"(o1); 1083 | a.s2.connect!"watchInt"(o2); 1084 | a.s3.connect!"watchLong"(o3); 1085 | 1086 | assert(!o1.i && !o1.l && !o1.str); 1087 | assert(!o2.i && !o2.l && !o2.str); 1088 | assert(!o3.i && !o3.l && !o3.str); 1089 | 1090 | a.value1 = 11; 1091 | assert(o1.i == 11 && !o1.l && o1.str == "str1"); 1092 | assert(!o2.i && !o2.l && !o2.str); 1093 | assert(!o3.i && !o3.l && !o3.str); 1094 | o1.i = -11; o1.str = "x1"; 1095 | 1096 | a.value2 = 12; 1097 | assert(o1.i == -11 && !o1.l && o1.str == "x1"); 1098 | assert(o2.i == 12 && !o2.l && o2.str == "str2"); 1099 | assert(!o3.i && !o3.l && !o3.str); 1100 | o2.i = -12; o2.str = "x2"; 1101 | 1102 | a.value3 = 13; 1103 | assert(o1.i == -11 && !o1.l && o1.str == "x1"); 1104 | assert(o2.i == -12 && !o1.l && o2.str == "x2"); 1105 | assert(!o3.i && o3.l == 13 && o3.str == "str3"); 1106 | o3.l = -13; o3.str = "x3"; 1107 | 1108 | // disconnect the watchers and make sure it doesn't trigger 1109 | a.s1.disconnect!"watchInt"(o1); 1110 | a.s2.disconnect!"watchInt"(o2); 1111 | a.s3.disconnect!"watchLong"(o3); 1112 | 1113 | a.value1 = 21; 1114 | a.value2 = 22; 1115 | a.value3 = 23; 1116 | assert(o1.i == -11 && !o1.l && o1.str == "x1"); 1117 | assert(o2.i == -12 && !o1.l && o2.str == "x2"); 1118 | assert(!o3.i && o3.l == -13 && o3.str == "x3"); 1119 | 1120 | // reconnect the watcher and make sure it triggers 1121 | a.s1.connect!"watchInt"(o1); 1122 | a.s2.connect!"watchInt"(o2); 1123 | a.s3.connect!"watchLong"(o3); 1124 | 1125 | a.value1 = 31; 1126 | a.value2 = 32; 1127 | a.value3 = 33; 1128 | assert(o1.i == 31 && !o1.l && o1.str == "str1"); 1129 | assert(o2.i == 32 && !o1.l && o2.str == "str2"); 1130 | assert(!o3.i && o3.l == 33 && o3.str == "str3"); 1131 | 1132 | // destroy observers 1133 | destroy(o1); 1134 | destroy(o2); 1135 | destroy(o3); 1136 | a.value1 = 41; 1137 | a.value2 = 42; 1138 | a.value3 = 43; 1139 | } 1140 | 1141 | test(new Bar); 1142 | 1143 | class BarDerived: Bar 1144 | { 1145 | @property void value4(int v) { _s4.emit("str4", v); } 1146 | @property void value5(int v) { _s5.emit("str5", v); } 1147 | @property void value6(long v) { _s6.emit("str6", v); } 1148 | 1149 | mixin(signal!(string, int) ("s4")); 1150 | mixin(signal!(string, int) ("s5")); 1151 | mixin(signal!(string, long)("s6")); 1152 | } 1153 | 1154 | auto a = new BarDerived; 1155 | 1156 | test!Bar(a); 1157 | test!BarDerived(a); 1158 | 1159 | auto o4 = new Observer; 1160 | auto o5 = new Observer; 1161 | auto o6 = new Observer; 1162 | 1163 | // connect the watcher and trigger it 1164 | a.s4.connect!"watchInt"(o4); 1165 | a.s5.connect!"watchInt"(o5); 1166 | a.s6.connect!"watchLong"(o6); 1167 | 1168 | assert(!o4.i && !o4.l && !o4.str); 1169 | assert(!o5.i && !o5.l && !o5.str); 1170 | assert(!o6.i && !o6.l && !o6.str); 1171 | 1172 | a.value4 = 44; 1173 | assert(o4.i == 44 && !o4.l && o4.str == "str4"); 1174 | assert(!o5.i && !o5.l && !o5.str); 1175 | assert(!o6.i && !o6.l && !o6.str); 1176 | o4.i = -44; o4.str = "x4"; 1177 | 1178 | a.value5 = 45; 1179 | assert(o4.i == -44 && !o4.l && o4.str == "x4"); 1180 | assert(o5.i == 45 && !o5.l && o5.str == "str5"); 1181 | assert(!o6.i && !o6.l && !o6.str); 1182 | o5.i = -45; o5.str = "x5"; 1183 | 1184 | a.value6 = 46; 1185 | assert(o4.i == -44 && !o4.l && o4.str == "x4"); 1186 | assert(o5.i == -45 && !o4.l && o5.str == "x5"); 1187 | assert(!o6.i && o6.l == 46 && o6.str == "str6"); 1188 | o6.l = -46; o6.str = "x6"; 1189 | 1190 | // disconnect the watchers and make sure it doesn't trigger 1191 | a.s4.disconnect!"watchInt"(o4); 1192 | a.s5.disconnect!"watchInt"(o5); 1193 | a.s6.disconnect!"watchLong"(o6); 1194 | 1195 | a.value4 = 54; 1196 | a.value5 = 55; 1197 | a.value6 = 56; 1198 | assert(o4.i == -44 && !o4.l && o4.str == "x4"); 1199 | assert(o5.i == -45 && !o4.l && o5.str == "x5"); 1200 | assert(!o6.i && o6.l == -46 && o6.str == "x6"); 1201 | 1202 | // reconnect the watcher and make sure it triggers 1203 | a.s4.connect!"watchInt"(o4); 1204 | a.s5.connect!"watchInt"(o5); 1205 | a.s6.connect!"watchLong"(o6); 1206 | 1207 | a.value4 = 64; 1208 | a.value5 = 65; 1209 | a.value6 = 66; 1210 | assert(o4.i == 64 && !o4.l && o4.str == "str4"); 1211 | assert(o5.i == 65 && !o4.l && o5.str == "str5"); 1212 | assert(!o6.i && o6.l == 66 && o6.str == "str6"); 1213 | 1214 | // destroy observers 1215 | destroy(o4); 1216 | destroy(o5); 1217 | destroy(o6); 1218 | a.value4 = 44; 1219 | a.value5 = 45; 1220 | a.value6 = 46; 1221 | } 1222 | 1223 | unittest 1224 | { 1225 | import std.stdio; 1226 | 1227 | struct Property 1228 | { 1229 | alias value this; 1230 | mixin(signal!(int)("signal")); 1231 | @property int value() 1232 | { 1233 | return value_; 1234 | } 1235 | ref Property opAssign(int val) 1236 | { 1237 | debug (signal) writeln("Assigning int to property with signal: ", &this); 1238 | value_ = val; 1239 | _signal.emit(val); 1240 | return this; 1241 | } 1242 | private: 1243 | int value_; 1244 | } 1245 | 1246 | void observe(int val) 1247 | { 1248 | debug (signal) writefln("observe: Wow! The value changed: %s", val); 1249 | } 1250 | 1251 | class Observer 1252 | { 1253 | void observe(int val) 1254 | { 1255 | debug (signal) writefln("Observer: Wow! The value changed: %s", val); 1256 | debug (signal) writefln("Really! I must know I am an observer (old value was: %s)!", observed); 1257 | observed = val; 1258 | count++; 1259 | } 1260 | int observed; 1261 | int count; 1262 | } 1263 | Property prop; 1264 | void delegate(int) dg = (val) => observe(val); 1265 | prop.signal.strongConnect(dg); 1266 | assert(prop.signal._impl._slots.length==1); 1267 | Observer o=new Observer; 1268 | prop.signal.connect!"observe"(o); 1269 | assert(prop.signal._impl._slots.length==2); 1270 | debug (signal) writeln("Triggering on original property with value 8 ..."); 1271 | prop=8; 1272 | assert(o.count==1); 1273 | assert(o.observed==prop); 1274 | } 1275 | 1276 | unittest 1277 | { 1278 | debug (signal) import std.stdio; 1279 | import std.conv; 1280 | Signal!() s1; 1281 | void testfunc(int id) 1282 | { 1283 | throw new Exception(to!string(id)); 1284 | } 1285 | s1.strongConnect(() => testfunc(0)); 1286 | s1.strongConnect(() => testfunc(1)); 1287 | s1.strongConnect(() => testfunc(2)); 1288 | try s1.emit(); 1289 | catch(Exception e) 1290 | { 1291 | Throwable t=e; 1292 | int i=0; 1293 | while (t) 1294 | { 1295 | debug (signal) stderr.writefln("Caught exception (this is fine)"); 1296 | assert(to!int(t.msg)==i); 1297 | t=t.next; 1298 | i++; 1299 | } 1300 | assert(i==3); 1301 | } 1302 | } 1303 | unittest 1304 | { 1305 | class A 1306 | { 1307 | mixin(signal!(string, int)("s1")); 1308 | } 1309 | 1310 | class B : A 1311 | { 1312 | mixin(signal!(string, int)("s2")); 1313 | } 1314 | } 1315 | 1316 | unittest 1317 | { 1318 | struct Test 1319 | { 1320 | mixin(signal!int("a", "public")); 1321 | mixin(signal!int("ap", "private")); 1322 | mixin(signal!int("app", "protected")); 1323 | mixin(signal!int("an", "none")); 1324 | } 1325 | debug (signal) 1326 | { 1327 | pragma(msg, signal!int("a", "public")); 1328 | pragma(msg, signal!(int, string, int[int])("a", "private")); 1329 | pragma(msg, signal!(int, string, int[int], float, double)("a", "protected")); 1330 | pragma(msg, signal!(int, string, int[int], float, double, long)("a", "none")); 1331 | } 1332 | } 1333 | 1334 | unittest // Test nested emit/removal/addition ... 1335 | { 1336 | Signal!() sig; 1337 | bool doEmit = true; 1338 | int counter = 0; 1339 | int slot3called = 0; 1340 | int slot3shouldcalled = 0; 1341 | void slot1() 1342 | { 1343 | doEmit = !doEmit; 1344 | if(!doEmit) 1345 | sig.emit(); 1346 | } 1347 | void slot3() 1348 | { 1349 | slot3called++; 1350 | } 1351 | void slot2() 1352 | { 1353 | debug (signal) { import std.stdio; writefln("\nCALLED: %s, should called: %s", slot3called, slot3shouldcalled);} 1354 | assert (slot3called == slot3shouldcalled); 1355 | if ( ++counter < 100) 1356 | slot3shouldcalled += counter; 1357 | if ( counter < 100 ) 1358 | sig.strongConnect(&slot3); 1359 | } 1360 | void slot4() 1361 | { 1362 | if ( counter == 100 ) 1363 | sig.strongDisconnect(&slot3); // All connections dropped 1364 | } 1365 | sig.strongConnect(&slot1); 1366 | sig.strongConnect(&slot2); 1367 | sig.strongConnect(&slot4); 1368 | for(int i=0; i<1000; i++) 1369 | sig.emit(); 1370 | debug (signal) 1371 | { 1372 | import std.stdio; 1373 | writeln("slot3called: ", slot3called); 1374 | } 1375 | } 1376 | /* vim: set ts=4 sw=4 expandtab : */ 1377 | -------------------------------------------------------------------------------- /src/glwtf/window.d: -------------------------------------------------------------------------------- 1 | module glwtf.window; 2 | 3 | 4 | private { 5 | import glwtf.glfw; 6 | import glwtf.input : BaseGLFWEventHandler; 7 | import glwtf.exception : WindowException; 8 | 9 | import std.string : toStringz; 10 | import std.exception : enforce; 11 | import std.typecons : Tuple; 12 | } 13 | 14 | 15 | struct Rect { 16 | int x; 17 | int y; 18 | } 19 | 20 | private string set_hint_property(string target, string name, bool getter=false) { 21 | string ret = `@property void ` ~ name ~ `(int hint) { 22 | set_hint(` ~ target ~ `, hint); 23 | }`; 24 | 25 | if(getter) { 26 | ret ~= `@property int ` ~ name ~ `() { 27 | return get_attrib(` ~ target ~ `); 28 | }`; 29 | } 30 | 31 | return ret; 32 | } 33 | 34 | 35 | alias Tuple!(int, "major", int, "minor") OGLVT; 36 | immutable OGLVT[] OGLVTS = [OGLVT(4, 3), OGLVT(4, 2), OGLVT(4, 1), OGLVT(4, 0), 37 | OGLVT(3, 3), OGLVT(3, 2), OGLVT(3, 1), OGLVT(3, 0)]; 38 | 39 | class Window : BaseGLFWEventHandler { 40 | debug { 41 | private GLFWwindow* _window; 42 | 43 | @property GLFWwindow* window() { 44 | assert(_window !is null, "no window created yet!"); 45 | return _window; 46 | } 47 | @property void window(GLFWwindow* window) { 48 | _window = window; 49 | } 50 | } else { 51 | GLFWwindow* window; 52 | } 53 | 54 | this() { 55 | super(); 56 | } 57 | 58 | this(GLFWwindow* window) { 59 | super(); 60 | 61 | this.window = window; 62 | register_callbacks(window); 63 | } 64 | 65 | void set_hint(int target, int hint) { 66 | glfwWindowHint(target, hint); 67 | } 68 | 69 | mixin(set_hint_property("GLFW_RED_BITS", "red_bits")); 70 | mixin(set_hint_property("GLFW_GREEN_BITS", "green_bits")); 71 | mixin(set_hint_property("GLFW_BLUE_BITS", "blue_bits")); 72 | mixin(set_hint_property("GLFW_ALPHA_BITS", "alpha_bits")); 73 | mixin(set_hint_property("GLFW_DEPTH_BITS", "depth_bits")); 74 | mixin(set_hint_property("GLFW_STENCIL_BITS", "stencil_bits")); 75 | mixin(set_hint_property("GLFW_ACCUM_RED_BITS", "accum_red_bits")); 76 | mixin(set_hint_property("GLFW_ACCUM_GREEN_BITS", "accum_green_bits")); 77 | mixin(set_hint_property("GLFW_ACCUM_BLUE_BITS", "accum_blue_bits")); 78 | mixin(set_hint_property("GLFW_ACCUM_ALPHA_BITS", "accum_alpha_bits")); 79 | mixin(set_hint_property("GLFW_AUX_BUFFERS", "aux_buffers")); 80 | mixin(set_hint_property("GLFW_STEREO", "stereo")); 81 | mixin(set_hint_property("GLFW_SAMPLES", "samples")); 82 | mixin(set_hint_property("GLFW_SRGB_CAPABLE", "srgb_capable")); 83 | mixin(set_hint_property("GLFW_CLIENT_API", "client_api", true)); 84 | mixin(set_hint_property("GLFW_OPENGL_API", "opengl_api")); 85 | mixin(set_hint_property("GLFW_CONTEXT_VERSION_MAJOR", "context_version_major", true)); 86 | mixin(set_hint_property("GLFW_CONTEXT_VERSION_MINOR", "context_version_minor", true)); 87 | mixin(set_hint_property("GLFW_OPENGL_FORWARD_COMPAT", "opengl_forward_compat", true)); 88 | mixin(set_hint_property("GLFW_OPENGL_DEBUG_CONTEXT", "opengl_debug_context", true)); 89 | mixin(set_hint_property("GLFW_OPENGL_PROFILE", "opengl_profile", true)); 90 | mixin(set_hint_property("GLFW_CONTEXT_ROBUSTNESS", "context_robustness", true)); 91 | mixin(set_hint_property("GLFW_RESIZABLE", "resizable", true)); 92 | mixin(set_hint_property("GLFW_VISIBLE", "visible", true)); 93 | 94 | void create(int width, int height, string title, GLFWmonitor* monitor = null, GLFWwindow* share = null) { 95 | window = glfwCreateWindow(width, height, title.toStringz(), monitor, share); 96 | enforce!WindowException(window !is null, "Failed to create GLFW Window"); 97 | register_callbacks(window); 98 | } 99 | 100 | auto create_highest_available_context(int width, int height, string title, GLFWmonitor* monitor = null, GLFWwindow* share = null, 101 | int opengl_profile = GLFW_OPENGL_CORE_PROFILE, bool forward_compat = true) { 102 | GLFWwindow* win = null; 103 | 104 | foreach(oglvt; OGLVTS) { 105 | this.context_version_major = oglvt.major; 106 | this.context_version_minor = oglvt.minor; 107 | this.opengl_profile = opengl_profile; 108 | this.opengl_forward_compat = forward_compat; 109 | 110 | win = glfwCreateWindow(width, height, title.toStringz(), monitor, share); 111 | 112 | if(win !is null) { 113 | window = win; 114 | register_callbacks(window); 115 | return oglvt; 116 | } 117 | } 118 | 119 | throw new WindowException("Unable to initialize OpenGL forward compatible context (Version >= 3.0)."); 120 | } 121 | 122 | void destroy() { 123 | glfwDestroyWindow(window); 124 | } 125 | 126 | @property void title(string title) { 127 | glfwSetWindowTitle(window, title.toStringz()); 128 | } 129 | 130 | @property void size(Rect rect) { 131 | glfwSetWindowSize(window, rect.x, rect.y); 132 | } 133 | 134 | @property Rect size() { 135 | Rect rect; 136 | glfwGetWindowSize(window, &rect.x, &rect.y); 137 | return rect; 138 | } 139 | 140 | @property int width() { 141 | return size.x; 142 | } 143 | 144 | @property int height() { 145 | return size.y; 146 | } 147 | 148 | void iconify() { 149 | glfwIconifyWindow(window); 150 | } 151 | 152 | void restore() { 153 | glfwRestoreWindow(window); 154 | } 155 | 156 | // void show() { 157 | // glfwShowWindow(window); 158 | // } 159 | // 160 | // void hide() { 161 | // glfwHideWindow(window); 162 | // } 163 | 164 | int get_attrib(int attrib) { 165 | return glfwGetWindowAttrib(window, attrib); 166 | } 167 | 168 | void set_input_mode(int mode, int value) { 169 | glfwSetInputMode(window, mode, value); 170 | } 171 | 172 | int get_input_mode(int mode) { 173 | return glfwGetInputMode(window, mode); 174 | } 175 | 176 | void make_context_current() { 177 | glfwMakeContextCurrent(window); 178 | } 179 | 180 | void swap_buffers() { 181 | glfwSwapBuffers(window); 182 | } 183 | 184 | // callbacks ------ 185 | // window 186 | bool delegate() on_close; 187 | 188 | override bool _on_close() { 189 | if(on_close !is null) { 190 | return on_close(); 191 | } 192 | 193 | return true; 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /src/imgui/api.d: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009-2010 Mikko Mononen memon@inside.org 3 | * 4 | * This software is provided 'as-is', without any express or implied 5 | * warranty. In no event will the authors be held liable for any damages 6 | * arising from the use of this software. 7 | * Permission is granted to anyone to use this software for any purpose, 8 | * including commercial applications, and to alter it and redistribute it 9 | * freely, subject to the following restrictions: 10 | * 1. The origin of this software must not be misrepresented; you must not 11 | * claim that you wrote the original software. If you use this software 12 | * in a product, an acknowledgment in the product documentation would be 13 | * appreciated but is not required. 14 | * 2. Altered source versions must be plainly marked as such, and must not be 15 | * misrepresented as being the original software. 16 | * 3. This notice may not be removed or altered from any source distribution. 17 | */ 18 | module imgui.api; 19 | 20 | /** 21 | imgui is an immediate mode GUI. See also: 22 | http://sol.gfxile.net/imgui/ 23 | 24 | This module contains the API of the library. 25 | */ 26 | 27 | import std.algorithm; 28 | import std.math; 29 | import std.stdio; 30 | import std.string; 31 | import std.range; 32 | 33 | import imgui.engine; 34 | import imgui.gl3_renderer; 35 | /* import imgui.util; */ 36 | 37 | // todo: opApply to allow changing brightness on all colors. 38 | // todo: check CairoD samples for brightness settingsroutines. 39 | 40 | /** A color scheme contains all the configurable GUI element colors. */ 41 | struct ColorScheme 42 | { 43 | /** 44 | Return a range of all colors. This gives you ref access, 45 | which means you can modify the values. 46 | */ 47 | auto walkColors() 48 | { 49 | return chain( 50 | (&generic.text).only, 51 | (&generic.line).only, 52 | (&generic.rect).only, 53 | (&generic.roundRect).only, 54 | (&scroll.area.back).only, 55 | (&scroll.area.text).only, 56 | (&scroll.bar.back).only, 57 | (&scroll.bar.thumb).only, 58 | (&scroll.bar.thumbHover).only, 59 | (&scroll.bar.thumbPress).only, 60 | (&button.text).only, 61 | (&button.textHover).only, 62 | (&button.textDisabled).only, 63 | (&button.back).only, 64 | (&button.backPress).only, 65 | (&checkbox.back).only, 66 | (&checkbox.press).only, 67 | (&checkbox.checked).only, 68 | (&checkbox.doUncheck).only, 69 | (&checkbox.disabledChecked).only, 70 | (&checkbox.text).only, 71 | (&checkbox.textHover).only, 72 | (&checkbox.textDisabled).only, 73 | (&item.hover).only, 74 | (&item.press).only, 75 | (&item.text).only, 76 | (&item.textDisabled).only, 77 | (&collapse.shown).only, 78 | (&collapse.hidden).only, 79 | (&collapse.doShow).only, 80 | (&collapse.doHide).only, 81 | (&collapse.textHover).only, 82 | (&collapse.text).only, 83 | (&collapse.textDisabled).only, 84 | (&collapse.subtext).only, 85 | (&label.text).only, 86 | (&value.text).only, 87 | (&slider.back).only, 88 | (&slider.thumb).only, 89 | (&slider.thumbHover).only, 90 | (&slider.thumbPress).only, 91 | (&slider.text).only, 92 | (&slider.textHover).only, 93 | (&slider.textDisabled).only, 94 | (&slider.value).only, 95 | (&slider.valueHover).only, 96 | (&slider.valueDisabled).only, 97 | (&separator).only); 98 | } 99 | 100 | /// 101 | static struct Generic 102 | { 103 | RGBA text; /// Used by imguiDrawText. 104 | RGBA line; /// Used by imguiDrawLine. 105 | RGBA rect; /// Used by imguiDrawRect. 106 | RGBA roundRect; /// Used by imguiDrawRoundedRect. 107 | } 108 | 109 | /// 110 | static struct Scroll 111 | { 112 | /// 113 | static struct Area 114 | { 115 | RGBA back = RGBA(0, 0, 0, 192); 116 | RGBA text = RGBA(255, 255, 255, 128); 117 | } 118 | 119 | /// 120 | static struct Bar 121 | { 122 | RGBA back = RGBA(0, 0, 0, 196); 123 | RGBA thumb = RGBA(255, 255, 255, 64); 124 | RGBA thumbHover = RGBA(255, 196, 0, 96); 125 | RGBA thumbPress = RGBA(255, 196, 0, 196); 126 | } 127 | 128 | Area area; /// 129 | Bar bar; /// 130 | } 131 | 132 | /// 133 | static struct Button 134 | { 135 | RGBA text = RGBA(255, 255, 255, 200); 136 | RGBA textHover = RGBA(255, 196, 0, 255); 137 | RGBA textDisabled = RGBA(128, 128, 128, 200); 138 | RGBA back = RGBA(128, 128, 128, 96); 139 | RGBA backPress = RGBA(128, 128, 128, 196); 140 | } 141 | 142 | /// 143 | static struct TextInput 144 | { 145 | RGBA label = RGBA(255, 255, 255, 255); 146 | RGBA text = RGBA(0, 0, 0, 255); 147 | RGBA textDisabled = RGBA(255, 255, 255, 255); 148 | RGBA back = RGBA(255, 196, 0, 255); 149 | RGBA backDisabled = RGBA(128, 128, 128, 96); 150 | } 151 | 152 | /// 153 | static struct Checkbox 154 | { 155 | /// Checkbox background. 156 | RGBA back = RGBA(128, 128, 128, 96); 157 | 158 | /// Checkbox background when it's pressed. 159 | RGBA press = RGBA(128, 128, 128, 196); 160 | 161 | /// An enabled and checked checkbox. 162 | RGBA checked = RGBA(255, 255, 255, 255); 163 | 164 | /// An enabled and checked checkbox which was just pressed to be disabled. 165 | RGBA doUncheck = RGBA(255, 255, 255, 200); 166 | 167 | /// A disabled but checked checkbox. 168 | RGBA disabledChecked = RGBA(128, 128, 128, 200); 169 | 170 | /// Label color of the checkbox. 171 | RGBA text = RGBA(255, 255, 255, 200); 172 | 173 | /// Label color of a hovered checkbox. 174 | RGBA textHover = RGBA(255, 196, 0, 255); 175 | 176 | /// Label color of an disabled checkbox. 177 | RGBA textDisabled = RGBA(128, 128, 128, 200); 178 | } 179 | 180 | /// 181 | static struct Item 182 | { 183 | RGBA hover = RGBA(255, 196, 0, 96); 184 | RGBA press = RGBA(255, 196, 0, 196); 185 | RGBA text = RGBA(255, 255, 255, 200); 186 | RGBA textDisabled = RGBA(128, 128, 128, 200); 187 | } 188 | 189 | /// 190 | static struct Collapse 191 | { 192 | RGBA shown = RGBA(255, 255, 255, 200); 193 | RGBA hidden = RGBA(255, 255, 255, 200); 194 | 195 | RGBA doShow = RGBA(255, 255, 255, 255); 196 | RGBA doHide = RGBA(255, 255, 255, 255); 197 | 198 | RGBA text = RGBA(255, 255, 255, 200); 199 | RGBA textHover = RGBA(255, 196, 0, 255); 200 | RGBA textDisabled = RGBA(128, 128, 128, 200); 201 | 202 | RGBA subtext = RGBA(255, 255, 255, 128); 203 | } 204 | 205 | /// 206 | static struct Label 207 | { 208 | RGBA text = RGBA(255, 255, 255, 255); 209 | } 210 | 211 | /// 212 | static struct Value 213 | { 214 | RGBA text = RGBA(255, 255, 255, 200); 215 | } 216 | 217 | /// 218 | static struct Slider 219 | { 220 | RGBA back = RGBA(0, 0, 0, 128); 221 | RGBA thumb = RGBA(255, 255, 255, 64); 222 | RGBA thumbHover = RGBA(255, 196, 0, 128); 223 | RGBA thumbPress = RGBA(255, 255, 255, 255); 224 | 225 | RGBA text = RGBA(255, 255, 255, 200); 226 | RGBA textHover = RGBA(255, 196, 0, 255); 227 | RGBA textDisabled = RGBA(128, 128, 128, 200); 228 | 229 | RGBA value = RGBA(255, 255, 255, 200); 230 | RGBA valueHover = RGBA(255, 196, 0, 255); 231 | RGBA valueDisabled = RGBA(128, 128, 128, 200); 232 | } 233 | 234 | /// Colors for the generic imguiDraw* functions. 235 | Generic generic; 236 | 237 | /// Colors for the scrollable area. 238 | Scroll scroll; 239 | 240 | /// Colors for button elements. 241 | Button button; 242 | 243 | /// Colors for text input elements. 244 | TextInput textInput; 245 | 246 | /// Colors for checkbox elements. 247 | Checkbox checkbox; 248 | 249 | /// Colors for item elements. 250 | Item item; 251 | 252 | /// Colors for collapse elements. 253 | Collapse collapse; 254 | 255 | /// Colors for label elements. 256 | Label label; 257 | 258 | /// Colors for value elements. 259 | Value value; 260 | 261 | /// Colors for slider elements. 262 | Slider slider; 263 | 264 | /// Color for the separator line. 265 | RGBA separator = RGBA(255, 255, 255, 32); 266 | } 267 | 268 | /** 269 | The current default color scheme. 270 | 271 | You can configure this scheme, it will be used by 272 | default by GUI element creation functions unless 273 | you explicitly pass a custom color scheme. 274 | */ 275 | __gshared ColorScheme defaultColorScheme; 276 | 277 | /// 278 | struct RGBA 279 | { 280 | ubyte r; 281 | ubyte g; 282 | ubyte b; 283 | ubyte a = 255; 284 | 285 | RGBA opBinary(string op)(RGBA rgba) 286 | { 287 | RGBA res = this; 288 | 289 | mixin("res.r = cast(ubyte)res.r " ~ op ~ " rgba.r;"); 290 | mixin("res.g = cast(ubyte)res.g " ~ op ~ " rgba.g;"); 291 | mixin("res.b = cast(ubyte)res.b " ~ op ~ " rgba.b;"); 292 | mixin("res.a = cast(ubyte)res.a " ~ op ~ " rgba.a;"); 293 | 294 | return res; 295 | } 296 | } 297 | 298 | /// 299 | enum TextAlign 300 | { 301 | left, 302 | center, 303 | right, 304 | } 305 | 306 | /** The possible mouse buttons. These can be used as bitflags. */ 307 | enum MouseButton : ubyte 308 | { 309 | left = 0x01, 310 | right = 0x02, 311 | } 312 | 313 | /// 314 | enum Enabled : bool 315 | { 316 | no, 317 | yes, 318 | } 319 | 320 | /** Initialize the imgui library. 321 | 322 | Params: 323 | 324 | fontPath = Path to a TrueType font file to use to draw text. 325 | fontTextureSize = Size of the texture to store font glyphs in. The actual texture 326 | size is a square of this value. 327 | 328 | A bigger texture allows to draw more Unicode characters (if the 329 | font supports them). 256 (62.5kiB) should be enough for ASCII, 330 | 1024 (1MB) should be enough for most European scripts. 331 | 332 | Returns: True on success, false on failure. 333 | */ 334 | bool imguiInit(const(char)[] fontPath, uint fontTextureSize = 1024) 335 | { 336 | return imguiRenderGLInit(fontPath, fontTextureSize); 337 | } 338 | 339 | /** Destroy the imgui library. */ 340 | void imguiDestroy() 341 | { 342 | imguiRenderGLDestroy(); 343 | } 344 | 345 | /** 346 | Begin a new frame. All batched commands after the call to 347 | $(D imguiBeginFrame) will be rendered as a single frame once 348 | $(D imguiRender) is called. 349 | 350 | Note: You should call $(D imguiEndFrame) after batching all 351 | commands to reset the input handling for the next frame. 352 | 353 | Example: 354 | ----- 355 | int cursorX, cursorY; 356 | ubyte mouseButtons; 357 | int mouseScroll; 358 | 359 | /// start a new batch of commands for this frame (the batched commands) 360 | imguiBeginFrame(cursorX, cursorY, mouseButtons, mouseScroll); 361 | 362 | /// define your UI elements here 363 | imguiLabel("some text here"); 364 | 365 | /// end the frame (this just resets the input control state, e.g. mouse button states) 366 | imguiEndFrame(); 367 | 368 | /// now render the batched commands 369 | imguiRender(); 370 | ----- 371 | 372 | Params: 373 | 374 | cursorX = The cursor's last X position. 375 | cursorY = The cursor's last Y position. 376 | mouseButtons = The last mouse buttons pressed (a value or a combination of values of a $(D MouseButton)). 377 | mouseScroll = The last scroll value emitted by the mouse. 378 | unicodeChar = Unicode text input from the keyboard (usually the unicode result of last keypress). 379 | '0' means 'no text input'. Note that for text input to work, even Enter 380 | and backspace must be passed (encoded as 0x0D and 0x08, respectively), 381 | which may not be automatically handled by your input library's text 382 | input functionality (e.g. GLFW's getUnicode() does not do this). 383 | */ 384 | void imguiBeginFrame(int cursorX, int cursorY, ubyte mouseButtons, int mouseScroll, 385 | dchar unicodeChar = 0) 386 | { 387 | updateInput(cursorX, cursorY, mouseButtons, mouseScroll, unicodeChar); 388 | 389 | g_state.hot = g_state.hotToBe; 390 | g_state.hotToBe = 0; 391 | 392 | g_state.wentActive = false; 393 | g_state.isActive = false; 394 | g_state.isHot = false; 395 | 396 | g_state.widgetX = 0; 397 | g_state.widgetY = 0; 398 | g_state.widgetW = 0; 399 | 400 | g_state.areaId = 1; 401 | g_state.widgetId = 1; 402 | 403 | resetGfxCmdQueue(); 404 | } 405 | 406 | /** End the list of batched commands for the current frame. */ 407 | void imguiEndFrame() 408 | { 409 | clearInput(); 410 | } 411 | 412 | /** Render all of the batched commands for the current frame. */ 413 | void imguiRender(int width, int height) 414 | { 415 | imguiRenderGLDraw(width, height); 416 | } 417 | 418 | /** 419 | Begin the definition of a new scrollable area. 420 | 421 | Once elements within the scrollable area are defined 422 | you must call $(D imguiEndScrollArea) to end the definition. 423 | 424 | Params: 425 | 426 | title = The title that will be displayed for this scroll area. 427 | xPos = The X position of the scroll area. 428 | yPos = The Y position of the scroll area. 429 | width = The width of the scroll area. 430 | height = The height of the scroll area. 431 | scroll = A pointer to a variable which will hold the current scroll value of the widget. 432 | colorScheme = Optionally override the current default color scheme when creating this element. 433 | 434 | Returns: 435 | 436 | $(D true) if the mouse was located inside the scrollable area. 437 | */ 438 | bool imguiBeginScrollArea(const(char)[] title, int xPos, int yPos, int width, int height, int* scroll, const ref ColorScheme colorScheme = defaultColorScheme) 439 | { 440 | g_state.areaId++; 441 | g_state.widgetId = 0; 442 | g_scrollId = (g_state.areaId << 16) | g_state.widgetId; 443 | 444 | g_state.widgetX = xPos + SCROLL_AREA_PADDING; 445 | g_state.widgetY = yPos + height - AREA_HEADER + (*scroll); 446 | g_state.widgetW = width - SCROLL_AREA_PADDING * 4; 447 | g_scrollTop = yPos - AREA_HEADER + height; 448 | g_scrollBottom = yPos + SCROLL_AREA_PADDING; 449 | g_scrollRight = xPos + width - SCROLL_AREA_PADDING * 3; 450 | g_scrollVal = scroll; 451 | 452 | g_scrollAreaTop = g_state.widgetY; 453 | 454 | g_focusTop = yPos - AREA_HEADER; 455 | g_focusBottom = yPos - AREA_HEADER + height; 456 | 457 | g_insideScrollArea = inRect(xPos, yPos, width, height, false); 458 | g_state.insideCurrentScroll = g_insideScrollArea; 459 | 460 | addGfxCmdRoundedRect(cast(float)xPos, cast(float)yPos, cast(float)width, cast(float)height, 6, colorScheme.scroll.area.back); 461 | 462 | addGfxCmdText(xPos + AREA_HEADER / 2, yPos + height - AREA_HEADER / 2 - TEXT_HEIGHT / 2, TextAlign.left, title, colorScheme.scroll.area.text); 463 | 464 | // The max() ensures we never have zero- or negative-sized scissor rectangle when the window is very small, 465 | // avoiding a segfault. 466 | addGfxCmdScissor(xPos + SCROLL_AREA_PADDING, 467 | yPos + SCROLL_AREA_PADDING, 468 | max(1, width - SCROLL_AREA_PADDING * 4), 469 | max(1, height - AREA_HEADER - SCROLL_AREA_PADDING)); 470 | 471 | return g_insideScrollArea; 472 | } 473 | 474 | /** 475 | End the definition of the last scrollable element. 476 | 477 | Params: 478 | 479 | colorScheme = Optionally override the current default color scheme when creating this element. 480 | */ 481 | void imguiEndScrollArea(const ref ColorScheme colorScheme = defaultColorScheme) 482 | { 483 | // Disable scissoring. 484 | addGfxCmdScissor(-1, -1, -1, -1); 485 | 486 | // Draw scroll bar 487 | int x = g_scrollRight + SCROLL_AREA_PADDING / 2; 488 | int y = g_scrollBottom; 489 | int w = SCROLL_AREA_PADDING * 2; 490 | int h = g_scrollTop - g_scrollBottom; 491 | 492 | int stop = g_scrollAreaTop; 493 | int sbot = g_state.widgetY; 494 | int sh = stop - sbot; // The scrollable area height. 495 | 496 | float barHeight = cast(float)h / cast(float)sh; 497 | 498 | if (barHeight < 1) 499 | { 500 | float barY = cast(float)(y - sbot) / cast(float)sh; 501 | 502 | if (barY < 0) 503 | barY = 0; 504 | 505 | if (barY > 1) 506 | barY = 1; 507 | 508 | // Handle scroll bar logic. 509 | uint hid = g_scrollId; 510 | int hx = x; 511 | int hy = y + cast(int)(barY * h); 512 | int hw = w; 513 | int hh = cast(int)(barHeight * h); 514 | 515 | const int range = h - (hh - 1); 516 | bool over = inRect(hx, hy, hw, hh); 517 | buttonLogic(hid, over); 518 | 519 | if (isActive(hid)) 520 | { 521 | float u = cast(float)(hy - y) / cast(float)range; 522 | 523 | if (g_state.wentActive) 524 | { 525 | g_state.dragY = g_state.my; 526 | g_state.dragOrig = u; 527 | } 528 | 529 | if (g_state.dragY != g_state.my) 530 | { 531 | u = g_state.dragOrig + (g_state.my - g_state.dragY) / cast(float)range; 532 | 533 | if (u < 0) 534 | u = 0; 535 | 536 | if (u > 1) 537 | u = 1; 538 | *g_scrollVal = cast(int)((1 - u) * (sh - h)); 539 | } 540 | } 541 | 542 | // BG 543 | addGfxCmdRoundedRect(cast(float)x, cast(float)y, cast(float)w, cast(float)h, cast(float)w / 2 - 1, colorScheme.scroll.bar.back); 544 | 545 | // Bar 546 | if (isActive(hid)) 547 | addGfxCmdRoundedRect(cast(float)hx, cast(float)hy, cast(float)hw, cast(float)hh, cast(float)w / 2 - 1, colorScheme.scroll.bar.thumbPress); 548 | else 549 | addGfxCmdRoundedRect(cast(float)hx, cast(float)hy, cast(float)hw, cast(float)hh, cast(float)w / 2 - 1, isHot(hid) ? colorScheme.scroll.bar.thumbHover : colorScheme.scroll.bar.thumb); 550 | 551 | // Handle mouse scrolling. 552 | if (g_insideScrollArea) // && !anyActive()) 553 | { 554 | if (g_state.scroll) 555 | { 556 | *g_scrollVal += 20 * g_state.scroll; 557 | 558 | if (*g_scrollVal < 0) 559 | *g_scrollVal = 0; 560 | 561 | if (*g_scrollVal > (sh - h)) 562 | *g_scrollVal = (sh - h); 563 | } 564 | } 565 | } 566 | g_state.insideCurrentScroll = false; 567 | } 568 | 569 | /** 570 | Define a new button. 571 | 572 | Params: 573 | 574 | label = The text that will be displayed on the button. 575 | enabled = Set whether the button can be pressed. 576 | colorScheme = Optionally override the current default color scheme when creating this element. 577 | 578 | Returns: 579 | 580 | $(D true) if the button is enabled and was pressed. 581 | Note that pressing a button implies pressing and releasing the 582 | left mouse button while over the gui button. 583 | 584 | Example: 585 | ----- 586 | void onPress() { } 587 | if (imguiButton("Push me")) // button was pushed 588 | onPress(); 589 | ----- 590 | */ 591 | bool imguiButton(const(char)[] label, Enabled enabled = Enabled.yes, const ref ColorScheme colorScheme = defaultColorScheme) 592 | { 593 | g_state.widgetId++; 594 | uint id = (g_state.areaId << 16) | g_state.widgetId; 595 | 596 | int x = g_state.widgetX; 597 | int y = g_state.widgetY - BUTTON_HEIGHT; 598 | int w = g_state.widgetW; 599 | int h = BUTTON_HEIGHT; 600 | g_state.widgetY -= BUTTON_HEIGHT + DEFAULT_SPACING; 601 | 602 | bool over = enabled && inRect(x, y, w, h); 603 | bool res = buttonLogic(id, over); 604 | 605 | addGfxCmdRoundedRect(cast(float)x, cast(float)y, cast(float)w, cast(float)h, cast(float)BUTTON_HEIGHT / 2 - 1, 606 | isActive(id) ? colorScheme.button.backPress : colorScheme.button.back); 607 | 608 | if (enabled) 609 | { 610 | addGfxCmdText(x + BUTTON_HEIGHT / 2, y + BUTTON_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.left, label, 611 | isHot(id) ? colorScheme.button.textHover : colorScheme.button.text); 612 | } 613 | else 614 | { 615 | addGfxCmdText(x + BUTTON_HEIGHT / 2, y + BUTTON_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.left, label, 616 | colorScheme.button.textDisabled); 617 | } 618 | 619 | return res; 620 | } 621 | 622 | /** 623 | Define a new checkbox. 624 | 625 | Params: 626 | 627 | label = The text that will be displayed on the button. 628 | checkState = A pointer to a variable which holds the current state of the checkbox. 629 | enabled = Set whether the checkbox can be pressed. 630 | colorScheme = Optionally override the current default color scheme when creating this element. 631 | 632 | Returns: 633 | 634 | $(D true) if the checkbox was toggled on or off. 635 | Note that toggling implies pressing and releasing the 636 | left mouse button while over the checkbox. 637 | 638 | Example: 639 | ----- 640 | bool checkState = false; // initially un-checked 641 | if (imguiCheck("checkbox", &checkState)) // checkbox was toggled 642 | writeln(checkState); // check the current state 643 | ----- 644 | */ 645 | bool imguiCheck(const(char)[] label, bool* checkState, Enabled enabled = Enabled.yes, const ref ColorScheme colorScheme = defaultColorScheme) 646 | { 647 | g_state.widgetId++; 648 | uint id = (g_state.areaId << 16) | g_state.widgetId; 649 | 650 | int x = g_state.widgetX; 651 | int y = g_state.widgetY - BUTTON_HEIGHT; 652 | int w = g_state.widgetW; 653 | int h = BUTTON_HEIGHT; 654 | g_state.widgetY -= BUTTON_HEIGHT + DEFAULT_SPACING; 655 | 656 | bool over = enabled && inRect(x, y, w, h); 657 | bool res = buttonLogic(id, over); 658 | 659 | if (res) // toggle the state 660 | *checkState ^= 1; 661 | 662 | const int cx = x + BUTTON_HEIGHT / 2 - CHECK_SIZE / 2; 663 | const int cy = y + BUTTON_HEIGHT / 2 - CHECK_SIZE / 2; 664 | 665 | addGfxCmdRoundedRect(cast(float)cx - 3, cast(float)cy - 3, cast(float)CHECK_SIZE + 6, cast(float)CHECK_SIZE + 6, 4, 666 | isActive(id) ? colorScheme.checkbox.press : colorScheme.checkbox.back); 667 | 668 | if (*checkState) 669 | { 670 | if (enabled) 671 | addGfxCmdRoundedRect(cast(float)cx, cast(float)cy, cast(float)CHECK_SIZE, cast(float)CHECK_SIZE, cast(float)CHECK_SIZE / 2 - 1, isActive(id) ? colorScheme.checkbox.checked : colorScheme.checkbox.doUncheck); 672 | else 673 | addGfxCmdRoundedRect(cast(float)cx, cast(float)cy, cast(float)CHECK_SIZE, cast(float)CHECK_SIZE, cast(float)CHECK_SIZE / 2 - 1, colorScheme.checkbox.disabledChecked); 674 | } 675 | 676 | if (enabled) 677 | addGfxCmdText(x + BUTTON_HEIGHT, y + BUTTON_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.left, label, isHot(id) ? colorScheme.checkbox.textHover : colorScheme.checkbox.text); 678 | else 679 | addGfxCmdText(x + BUTTON_HEIGHT, y + BUTTON_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.left, label, colorScheme.checkbox.textDisabled); 680 | 681 | return res; 682 | } 683 | 684 | /** 685 | Define a new item. 686 | 687 | Params: 688 | 689 | label = The text that will be displayed as the item. 690 | enabled = Set whether the item can be pressed. 691 | colorScheme = Optionally override the current default color scheme when creating this element. 692 | 693 | Returns: 694 | 695 | $(D true) if the item is enabled and was pressed. 696 | Note that pressing an item implies pressing and releasing the 697 | left mouse button while over the item. 698 | */ 699 | bool imguiItem(const(char)[] label, Enabled enabled = Enabled.yes, const ref ColorScheme colorScheme = defaultColorScheme) 700 | { 701 | g_state.widgetId++; 702 | uint id = (g_state.areaId << 16) | g_state.widgetId; 703 | 704 | int x = g_state.widgetX; 705 | int y = g_state.widgetY - BUTTON_HEIGHT; 706 | int w = g_state.widgetW; 707 | int h = BUTTON_HEIGHT; 708 | g_state.widgetY -= BUTTON_HEIGHT + DEFAULT_SPACING; 709 | 710 | bool over = enabled && inRect(x, y, w, h); 711 | bool res = buttonLogic(id, over); 712 | 713 | if (isHot(id)) 714 | addGfxCmdRoundedRect(cast(float)x, cast(float)y, cast(float)w, cast(float)h, 2.0f, isActive(id) ? colorScheme.item.press : colorScheme.item.hover); 715 | 716 | if (enabled) 717 | addGfxCmdText(x + BUTTON_HEIGHT / 2, y + BUTTON_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.left, label, colorScheme.item.text); 718 | else 719 | addGfxCmdText(x + BUTTON_HEIGHT / 2, y + BUTTON_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.left, label, colorScheme.item.textDisabled); 720 | 721 | return res; 722 | } 723 | 724 | /** 725 | Define a new collapsable element. 726 | 727 | Params: 728 | 729 | label = The text that will be displayed as the item. 730 | subtext = Additional text displayed on the right of the label. 731 | checkState = A pointer to a variable which holds the current state of the collapsable element. 732 | enabled = Set whether the element can be pressed. 733 | colorScheme = Optionally override the current default color scheme when creating this element. 734 | 735 | Returns: 736 | 737 | $(D true) if the collapsable element is enabled and was pressed. 738 | Note that pressing a collapsable element implies pressing and releasing the 739 | left mouse button while over the collapsable element. 740 | */ 741 | bool imguiCollapse(const(char)[] label, const(char)[] subtext, bool* checkState, Enabled enabled = Enabled.yes, const ref ColorScheme colorScheme = defaultColorScheme) 742 | { 743 | g_state.widgetId++; 744 | uint id = (g_state.areaId << 16) | g_state.widgetId; 745 | 746 | int x = g_state.widgetX; 747 | int y = g_state.widgetY - BUTTON_HEIGHT; 748 | int w = g_state.widgetW; 749 | int h = BUTTON_HEIGHT; 750 | g_state.widgetY -= BUTTON_HEIGHT; // + DEFAULT_SPACING; 751 | 752 | const int cx = x + BUTTON_HEIGHT / 2 - CHECK_SIZE / 2; 753 | const int cy = y + BUTTON_HEIGHT / 2 - CHECK_SIZE / 2; 754 | 755 | bool over = enabled && inRect(x, y, w, h); 756 | bool res = buttonLogic(id, over); 757 | 758 | if (res) // toggle the state 759 | *checkState ^= 1; 760 | 761 | if (*checkState) 762 | addGfxCmdTriangle(cx, cy, CHECK_SIZE, CHECK_SIZE, 2, isActive(id) ? colorScheme.collapse.doHide : colorScheme.collapse.shown); 763 | else 764 | addGfxCmdTriangle(cx, cy, CHECK_SIZE, CHECK_SIZE, 1, isActive(id) ? colorScheme.collapse.doShow : colorScheme.collapse.hidden); 765 | 766 | if (enabled) 767 | addGfxCmdText(x + BUTTON_HEIGHT, y + BUTTON_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.left, label, isHot(id) ? colorScheme.collapse.textHover : colorScheme.collapse.text); 768 | else 769 | addGfxCmdText(x + BUTTON_HEIGHT, y + BUTTON_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.left, label, colorScheme.collapse.textDisabled); 770 | 771 | if (subtext) 772 | addGfxCmdText(x + w - BUTTON_HEIGHT / 2, y + BUTTON_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.right, subtext, colorScheme.collapse.subtext); 773 | 774 | return res; 775 | } 776 | 777 | /** 778 | Define a new label. 779 | 780 | Params: 781 | 782 | label = The text that will be displayed as the label. 783 | colorScheme = Optionally override the current default color scheme when creating this element. 784 | */ 785 | void imguiLabel(const(char)[] label, const ref ColorScheme colorScheme = defaultColorScheme) 786 | { 787 | int x = g_state.widgetX; 788 | int y = g_state.widgetY - BUTTON_HEIGHT; 789 | g_state.widgetY -= BUTTON_HEIGHT; 790 | addGfxCmdText(x, y + BUTTON_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.left, label, colorScheme.label.text); 791 | } 792 | 793 | 794 | /** 795 | Define a new value. 796 | 797 | Params: 798 | 799 | label = The text that will be displayed as the value. 800 | colorScheme = Optionally override the current default color scheme when creating this element. 801 | */ 802 | void imguiValue(const(char)[] label, const ref ColorScheme colorScheme = defaultColorScheme) 803 | { 804 | const int x = g_state.widgetX; 805 | const int y = g_state.widgetY - BUTTON_HEIGHT; 806 | const int w = g_state.widgetW; 807 | g_state.widgetY -= BUTTON_HEIGHT; 808 | 809 | addGfxCmdText(x + w - BUTTON_HEIGHT / 2, y + BUTTON_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.right, label, colorScheme.value.text); 810 | } 811 | 812 | /** 813 | Define a new slider. 814 | 815 | Params: 816 | 817 | label = The text that will be displayed above the slider. 818 | sliderState = A pointer to a variable which holds the current slider value. 819 | minValue = The minimum value that the slider can hold. 820 | maxValue = The maximum value that the slider can hold. 821 | stepValue = The step at which the value of the slider will increase or decrease. 822 | enabled = Set whether the slider's value can can be changed with the mouse. 823 | colorScheme = Optionally override the current default color scheme when creating this element. 824 | 825 | Returns: 826 | 827 | $(D true) if the slider is enabled and was pressed. 828 | Note that pressing a slider implies pressing and releasing the 829 | left mouse button while over the slider. 830 | */ 831 | bool imguiSlider(const(char)[] label, float* sliderState, float minValue, float maxValue, float stepValue, Enabled enabled = Enabled.yes, const ref ColorScheme colorScheme = defaultColorScheme) 832 | { 833 | g_state.widgetId++; 834 | uint id = (g_state.areaId << 16) | g_state.widgetId; 835 | 836 | int x = g_state.widgetX; 837 | int y = g_state.widgetY - BUTTON_HEIGHT; 838 | int w = g_state.widgetW; 839 | int h = SLIDER_HEIGHT; 840 | g_state.widgetY -= SLIDER_HEIGHT + DEFAULT_SPACING; 841 | 842 | addGfxCmdRoundedRect(cast(float)x, cast(float)y, cast(float)w, cast(float)h, 4.0f, colorScheme.slider.back); 843 | 844 | const int range = w - SLIDER_MARKER_WIDTH; 845 | 846 | float u = (*sliderState - minValue) / (maxValue - minValue); 847 | 848 | if (u < 0) 849 | u = 0; 850 | 851 | if (u > 1) 852 | u = 1; 853 | int m = cast(int)(u * range); 854 | 855 | bool over = enabled && inRect(x + m, y, SLIDER_MARKER_WIDTH, SLIDER_HEIGHT); 856 | bool res = buttonLogic(id, over); 857 | bool valChanged = false; 858 | 859 | if (isActive(id)) 860 | { 861 | if (g_state.wentActive) 862 | { 863 | g_state.dragX = g_state.mx; 864 | g_state.dragOrig = u; 865 | } 866 | 867 | if (g_state.dragX != g_state.mx) 868 | { 869 | u = g_state.dragOrig + cast(float)(g_state.mx - g_state.dragX) / cast(float)range; 870 | 871 | if (u < 0) 872 | u = 0; 873 | 874 | if (u > 1) 875 | u = 1; 876 | *sliderState = minValue + u * (maxValue - minValue); 877 | *sliderState = floor(*sliderState / stepValue + 0.5f) * stepValue; // Snap to stepValue 878 | m = cast(int)(u * range); 879 | valChanged = true; 880 | } 881 | } 882 | 883 | if (isActive(id)) 884 | addGfxCmdRoundedRect(cast(float)(x + m), cast(float)y, cast(float)SLIDER_MARKER_WIDTH, cast(float)SLIDER_HEIGHT, 4.0f, colorScheme.slider.thumbPress); 885 | else 886 | addGfxCmdRoundedRect(cast(float)(x + m), cast(float)y, cast(float)SLIDER_MARKER_WIDTH, cast(float)SLIDER_HEIGHT, 4.0f, isHot(id) ? colorScheme.slider.thumbHover : colorScheme.slider.thumb); 887 | 888 | // TODO: fix this, take a look at 'nicenum'. 889 | // todo: this should display sub 0.1 if the step is low enough. 890 | int digits = cast(int)(ceil(log10(stepValue))); 891 | char[16] fmtBuf; 892 | auto fmt = sformat(fmtBuf, "%%.%df", digits >= 0 ? 0 : -digits); 893 | char[32] msgBuf; 894 | string msg = sformat(msgBuf, fmt, *sliderState).idup; 895 | 896 | if (enabled) 897 | { 898 | addGfxCmdText(x + SLIDER_HEIGHT / 2, y + SLIDER_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.left, label, isHot(id) ? colorScheme.slider.textHover : colorScheme.slider.text); 899 | addGfxCmdText(x + w - SLIDER_HEIGHT / 2, y + SLIDER_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.right, msg, isHot(id) ? colorScheme.slider.valueHover : colorScheme.slider.value); 900 | } 901 | else 902 | { 903 | addGfxCmdText(x + SLIDER_HEIGHT / 2, y + SLIDER_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.left, label, colorScheme.slider.textDisabled); 904 | addGfxCmdText(x + w - SLIDER_HEIGHT / 2, y + SLIDER_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.right, msg, colorScheme.slider.valueDisabled); 905 | } 906 | 907 | return res || valChanged; 908 | } 909 | 910 | /** Define a text input field. 911 | * 912 | * Params: 913 | * 914 | * text = Label that will be placed beside the text input field. 915 | * buffer = Buffer to store entered text. 916 | * usedSlice = Slice of buffer that stores text entered so far. 917 | * forceInputable = Force the text input field to be inputable regardless of whether it 918 | * has been selected by the user? Useful to e.g. make a text field 919 | * inputable immediately after it appears in a newly opened dialog. 920 | * colorScheme = Optionally override the current default color scheme for this element. 921 | * 922 | * Returns: true if the user has entered and confirmed the text (by pressing Enter), false 923 | * otherwise. 924 | * 925 | * Example (using GLFW): 926 | * -------------------- 927 | * static dchar staticUnicode; 928 | * // Buffer to store text input 929 | * char[128] textInputBuffer; 930 | * // Slice of textInputBuffer 931 | * char[] textEntered; 932 | * 933 | * extern(C) static void getUnicode(GLFWwindow* w, uint unicode) 934 | * { 935 | * staticUnicode = unicode; 936 | * } 937 | * 938 | * extern(C) static void getKey(GLFWwindow* w, int key, int scancode, int action, int mods) 939 | * { 940 | * if(action != GLFW_PRESS) { return; } 941 | * if(key == GLFW_KEY_ENTER) { staticUnicode = 0x0D; } 942 | * else if(key == GLFW_KEY_BACKSPACE) { staticUnicode = 0x08; } 943 | * } 944 | * 945 | * void init() 946 | * { 947 | * GLFWwindow* window; 948 | * 949 | * // ... init the window here ... 950 | * 951 | * // Not really needed, but makes it obvious what we're doing 952 | * textEntered = textInputBuffer[0 .. 0]; 953 | * glfwSetCharCallback(window, &getUnicode); 954 | * glfwSetKeyCallback(window, &getKey); 955 | * } 956 | * 957 | * void frame() 958 | * { 959 | * // These should be defined somewhere 960 | * int mouseX, mouseY, mouseScroll; 961 | * ubyte mousebutton; 962 | * 963 | * // .. code here .. 964 | * 965 | * // Pass text input to imgui 966 | * imguiBeginFrame(cast(int)mouseX, cast(int)mouseY, mousebutton, mouseScroll, staticUnicode); 967 | * // reset staticUnicode for the next frame 968 | * 969 | * staticUnicode = 0; 970 | * 971 | * if(imguiTextInput("Text input:", textInputBuffer, textEntered)) 972 | * { 973 | * import std.stdio; 974 | * writeln("Entered text is: ", textEntered); 975 | * // Reset entered text for next input (use e.g. textEntered.dup if you need a copy). 976 | * textEntered = textInputBuffer[0 .. 0]; 977 | * } 978 | * 979 | * // .. more code here .. 980 | * } 981 | * -------------------- 982 | */ 983 | bool imguiTextInput(const(char)[] label, char[] buffer, ref char[] usedSlice, 984 | bool forceInputable = false, const ref ColorScheme colorScheme = defaultColorScheme) 985 | { 986 | assert(buffer.ptr == usedSlice.ptr && buffer.length >= usedSlice.length, 987 | "The usedSlice parameter on imguiTextInput must be a slice to the buffer " ~ 988 | "parameter"); 989 | 990 | // Label 991 | g_state.widgetId++; 992 | uint id = (g_state.areaId << 16) | g_state.widgetId; 993 | int x = g_state.widgetX; 994 | int y = g_state.widgetY - BUTTON_HEIGHT; 995 | addGfxCmdText(x, y + BUTTON_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.left, label, 996 | colorScheme.textInput.label); 997 | 998 | bool res = false; 999 | // Handle control input if any (Backspace to erase characters, Enter to confirm). 1000 | // Backspace 1001 | if(isInputable(id) && g_state.unicode == 0x08 && 1002 | g_state.unicode != g_state.lastUnicode && !usedSlice.empty) 1003 | { 1004 | usedSlice = usedSlice[0 .. $ - 1]; 1005 | } 1006 | // Pressing Enter "confirms" the input. 1007 | else if(isInputable(id) && g_state.unicode == 0x0D && g_state.unicode != g_state.lastUnicode) 1008 | { 1009 | g_state.inputable = 0; 1010 | res = true; 1011 | } 1012 | else if(isInputable(id) && g_state.unicode != 0 && g_state.unicode != g_state.lastUnicode) 1013 | { 1014 | import std.utf; 1015 | char[4] codePoints; 1016 | const codePointCount = std.utf.encode(codePoints, g_state.unicode); 1017 | // Only add the character into the buffer if we can fit it there. 1018 | if(buffer.length - usedSlice.length >= codePointCount) 1019 | { 1020 | usedSlice = buffer[0 .. usedSlice.length + codePointCount]; 1021 | usedSlice[$ - codePointCount .. $] = codePoints[0 .. codePointCount]; 1022 | } 1023 | } 1024 | 1025 | // Draw buffer data 1026 | uint labelLen = cast(uint)(imgui.engine.getTextLength(label) + 0.5f); 1027 | x += labelLen; 1028 | int w = g_state.widgetW - labelLen - DEFAULT_SPACING * 2; 1029 | int h = BUTTON_HEIGHT; 1030 | bool over = inRect(x, y, w, h); 1031 | textInputLogic(id, over, forceInputable); 1032 | addGfxCmdRoundedRect(cast(float)(x + DEFAULT_SPACING), cast(float)y, 1033 | cast(float)w, cast(float)h, 1034 | cast(float)BUTTON_HEIGHT / 2 - 1, 1035 | isInputable(id) ? colorScheme.textInput.back 1036 | : colorScheme.textInput.backDisabled); 1037 | addGfxCmdText(x + DEFAULT_SPACING * 2, y + BUTTON_HEIGHT / 2 - TEXT_HEIGHT / 2, 1038 | TextAlign.left, usedSlice, 1039 | isInputable(id) ? colorScheme.textInput.text 1040 | : colorScheme.textInput.textDisabled); 1041 | 1042 | g_state.widgetY -= BUTTON_HEIGHT + DEFAULT_SPACING; 1043 | return res; 1044 | } 1045 | 1046 | /** Add horizontal indentation for elements to be added. */ 1047 | void imguiIndent() 1048 | { 1049 | g_state.widgetX += INDENT_SIZE; 1050 | g_state.widgetW -= INDENT_SIZE; 1051 | } 1052 | 1053 | /** Remove horizontal indentation for elements to be added. */ 1054 | void imguiUnindent() 1055 | { 1056 | g_state.widgetX -= INDENT_SIZE; 1057 | g_state.widgetW += INDENT_SIZE; 1058 | } 1059 | 1060 | /** Add vertical space as a separator below the last element. */ 1061 | void imguiSeparator() 1062 | { 1063 | g_state.widgetY -= DEFAULT_SPACING * 3; 1064 | } 1065 | 1066 | /** 1067 | Add a horizontal line as a separator below the last element. 1068 | 1069 | Params: 1070 | colorScheme = Optionally override the current default color scheme when creating this element. 1071 | */ 1072 | void imguiSeparatorLine(const ref ColorScheme colorScheme = defaultColorScheme) 1073 | { 1074 | int x = g_state.widgetX; 1075 | int y = g_state.widgetY - DEFAULT_SPACING * 2; 1076 | int w = g_state.widgetW; 1077 | int h = 1; 1078 | g_state.widgetY -= DEFAULT_SPACING * 4; 1079 | 1080 | addGfxCmdRect(cast(float)x, cast(float)y, cast(float)w, cast(float)h, colorScheme.separator); 1081 | } 1082 | 1083 | /** 1084 | Draw text. 1085 | 1086 | Params: 1087 | color = Optionally override the current default text color when creating this element. 1088 | */ 1089 | void imguiDrawText(int xPos, int yPos, TextAlign textAlign, const(char)[] text, RGBA color = defaultColorScheme.generic.text) 1090 | { 1091 | addGfxCmdText(xPos, yPos, textAlign, text, color); 1092 | } 1093 | 1094 | /** 1095 | Draw a line. 1096 | 1097 | Params: 1098 | colorScheme = Optionally override the current default color scheme when creating this element. 1099 | */ 1100 | void imguiDrawLine(float x0, float y0, float x1, float y1, float r, RGBA color = defaultColorScheme.generic.line) 1101 | { 1102 | addGfxCmdLine(x0, y0, x1, y1, r, color); 1103 | } 1104 | 1105 | /** 1106 | Draw a rectangle. 1107 | 1108 | Params: 1109 | colorScheme = Optionally override the current default color scheme when creating this element. 1110 | */ 1111 | void imguiDrawRect(float xPos, float yPos, float width, float height, RGBA color = defaultColorScheme.generic.rect) 1112 | { 1113 | addGfxCmdRect(xPos, yPos, width, height, color); 1114 | } 1115 | 1116 | /** 1117 | Draw a rounded rectangle. 1118 | 1119 | Params: 1120 | colorScheme = Optionally override the current default color scheme when creating this element. 1121 | */ 1122 | void imguiDrawRoundedRect(float xPos, float yPos, float width, float height, float r, RGBA color = defaultColorScheme.generic.roundRect) 1123 | { 1124 | addGfxCmdRoundedRect(xPos, yPos, width, height, r, color); 1125 | } 1126 | -------------------------------------------------------------------------------- /src/imgui/engine.d: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009-2010 Mikko Mononen memon@inside.org 3 | * 4 | * This software is provided 'as-is', without any express or implied 5 | * warranty. In no event will the authors be held liable for any damages 6 | * arising from the use of this software. 7 | * Permission is granted to anyone to use this software for any purpose, 8 | * including commercial applications, and to alter it and redistribute it 9 | * freely, subject to the following restrictions: 10 | * 1. The origin of this software must not be misrepresented; you must not 11 | * claim that you wrote the original software. If you use this software 12 | * in a product, an acknowledgment in the product documentation would be 13 | * appreciated but is not required. 14 | * 2. Altered source versions must be plainly marked as such, and must not be 15 | * misrepresented as being the original software. 16 | * 3. This notice may not be removed or altered from any source distribution. 17 | */ 18 | module imgui.engine; 19 | 20 | import std.math; 21 | import std.stdio; 22 | import std.string; 23 | 24 | import imgui.api; 25 | import imgui.gl3_renderer; 26 | 27 | package: 28 | 29 | /** Globals start. */ 30 | 31 | __gshared imguiGfxCmd[GFXCMD_QUEUE_SIZE] g_gfxCmdQueue; 32 | __gshared uint g_gfxCmdQueueSize = 0; 33 | __gshared int g_scrollTop = 0; 34 | __gshared int g_scrollBottom = 0; 35 | __gshared int g_scrollRight = 0; 36 | __gshared int g_scrollAreaTop = 0; 37 | __gshared int* g_scrollVal = null; 38 | __gshared int g_focusTop = 0; 39 | __gshared int g_focusBottom = 0; 40 | __gshared uint g_scrollId = 0; 41 | __gshared bool g_insideScrollArea = false; 42 | __gshared GuiState g_state; 43 | 44 | /** Globals end. */ 45 | 46 | enum GFXCMD_QUEUE_SIZE = 5000; 47 | enum BUTTON_HEIGHT = 20; 48 | enum SLIDER_HEIGHT = 20; 49 | enum SLIDER_MARKER_WIDTH = 10; 50 | enum CHECK_SIZE = 8; 51 | enum DEFAULT_SPACING = 4; 52 | enum TEXT_HEIGHT = 8; 53 | enum SCROLL_AREA_PADDING = 6; 54 | enum INDENT_SIZE = 16; 55 | enum AREA_HEADER = 28; 56 | 57 | // Pull render interface. 58 | alias imguiGfxCmdType = int; 59 | enum : imguiGfxCmdType 60 | { 61 | IMGUI_GFXCMD_RECT, 62 | IMGUI_GFXCMD_TRIANGLE, 63 | IMGUI_GFXCMD_LINE, 64 | IMGUI_GFXCMD_TEXT, 65 | IMGUI_GFXCMD_SCISSOR, 66 | } 67 | 68 | struct imguiGfxRect 69 | { 70 | short x, y, w, h, r; 71 | } 72 | 73 | struct imguiGfxText 74 | { 75 | short x, y, align_; 76 | const(char)[] text; 77 | } 78 | 79 | struct imguiGfxLine 80 | { 81 | short x0, y0, x1, y1, r; 82 | } 83 | 84 | struct imguiGfxCmd 85 | { 86 | char type; 87 | char flags; 88 | byte[2] pad; 89 | uint col; 90 | 91 | union 92 | { 93 | imguiGfxLine line; 94 | imguiGfxRect rect; 95 | imguiGfxText text; 96 | } 97 | } 98 | 99 | void resetGfxCmdQueue() 100 | { 101 | g_gfxCmdQueueSize = 0; 102 | } 103 | 104 | public void addGfxCmdScissor(int x, int y, int w, int h) 105 | { 106 | if (g_gfxCmdQueueSize >= GFXCMD_QUEUE_SIZE) 107 | return; 108 | auto cmd = &g_gfxCmdQueue[g_gfxCmdQueueSize++]; 109 | cmd.type = IMGUI_GFXCMD_SCISSOR; 110 | cmd.flags = x < 0 ? 0 : 1; // on/off flag. 111 | cmd.col = 0; 112 | cmd.rect.x = cast(short)x; 113 | cmd.rect.y = cast(short)y; 114 | cmd.rect.w = cast(short)w; 115 | cmd.rect.h = cast(short)h; 116 | } 117 | 118 | public void addGfxCmdRect(float x, float y, float w, float h, RGBA color) 119 | { 120 | if (g_gfxCmdQueueSize >= GFXCMD_QUEUE_SIZE) 121 | return; 122 | auto cmd = &g_gfxCmdQueue[g_gfxCmdQueueSize++]; 123 | cmd.type = IMGUI_GFXCMD_RECT; 124 | cmd.flags = 0; 125 | cmd.col = color.toPackedRGBA(); 126 | cmd.rect.x = cast(short)(x * 8.0f); 127 | cmd.rect.y = cast(short)(y * 8.0f); 128 | cmd.rect.w = cast(short)(w * 8.0f); 129 | cmd.rect.h = cast(short)(h * 8.0f); 130 | cmd.rect.r = 0; 131 | } 132 | 133 | public void addGfxCmdLine(float x0, float y0, float x1, float y1, float r, RGBA color) 134 | { 135 | if (g_gfxCmdQueueSize >= GFXCMD_QUEUE_SIZE) 136 | return; 137 | auto cmd = &g_gfxCmdQueue[g_gfxCmdQueueSize++]; 138 | cmd.type = IMGUI_GFXCMD_LINE; 139 | cmd.flags = 0; 140 | cmd.col = color.toPackedRGBA(); 141 | cmd.line.x0 = cast(short)(x0 * 8.0f); 142 | cmd.line.y0 = cast(short)(y0 * 8.0f); 143 | cmd.line.x1 = cast(short)(x1 * 8.0f); 144 | cmd.line.y1 = cast(short)(y1 * 8.0f); 145 | cmd.line.r = cast(short)(r * 8.0f); 146 | } 147 | 148 | public void addGfxCmdRoundedRect(float x, float y, float w, float h, float r, RGBA color) 149 | { 150 | if (g_gfxCmdQueueSize >= GFXCMD_QUEUE_SIZE) 151 | return; 152 | auto cmd = &g_gfxCmdQueue[g_gfxCmdQueueSize++]; 153 | cmd.type = IMGUI_GFXCMD_RECT; 154 | cmd.flags = 0; 155 | cmd.col = color.toPackedRGBA(); 156 | cmd.rect.x = cast(short)(x * 8.0f); 157 | cmd.rect.y = cast(short)(y * 8.0f); 158 | cmd.rect.w = cast(short)(w * 8.0f); 159 | cmd.rect.h = cast(short)(h * 8.0f); 160 | cmd.rect.r = cast(short)(r * 8.0f); 161 | } 162 | 163 | public void addGfxCmdTriangle(int x, int y, int w, int h, int flags, RGBA color) 164 | { 165 | if (g_gfxCmdQueueSize >= GFXCMD_QUEUE_SIZE) 166 | return; 167 | auto cmd = &g_gfxCmdQueue[g_gfxCmdQueueSize++]; 168 | cmd.type = IMGUI_GFXCMD_TRIANGLE; 169 | cmd.flags = cast(byte)flags; 170 | cmd.col = color.toPackedRGBA(); 171 | cmd.rect.x = cast(short)(x * 8.0f); 172 | cmd.rect.y = cast(short)(y * 8.0f); 173 | cmd.rect.w = cast(short)(w * 8.0f); 174 | cmd.rect.h = cast(short)(h * 8.0f); 175 | } 176 | 177 | public void addGfxCmdText(int x, int y, int align_, const(char)[] text, RGBA color) 178 | { 179 | if (g_gfxCmdQueueSize >= GFXCMD_QUEUE_SIZE) 180 | return; 181 | auto cmd = &g_gfxCmdQueue[g_gfxCmdQueueSize++]; 182 | cmd.type = IMGUI_GFXCMD_TEXT; 183 | cmd.flags = 0; 184 | cmd.col = color.toPackedRGBA(); 185 | cmd.text.x = cast(short)x; 186 | cmd.text.y = cast(short)y; 187 | cmd.text.align_ = cast(short)align_; 188 | cmd.text.text = text; 189 | } 190 | 191 | struct GuiState 192 | { 193 | bool left; 194 | bool leftPressed, leftReleased; 195 | int mx = -1, my = -1; 196 | int scroll; 197 | // 'unicode' value passed to updateInput. 198 | dchar unicode; 199 | // 'unicode' value passed to updateInput on previous frame. 200 | // 201 | // Used to detect that unicode (text) input has changed. 202 | dchar lastUnicode; 203 | // ID of the 'inputable' widget (widget we're entering input into, e.g. text input). 204 | // 205 | // A text input becomes 'inputable' when it is 'hot' and left-clicked. 206 | // 207 | // 0 if no widget is inputable 208 | uint inputable; 209 | uint active; 210 | // The 'hot' widget (hovered over input widget). 211 | // 212 | // 0 if no widget is inputable 213 | uint hot; 214 | // The widget that will be 'hot' in the next frame. 215 | uint hotToBe; 216 | // These two are probably unused? (set but not read?) 217 | bool isHot; 218 | bool isActive; 219 | 220 | bool wentActive; 221 | int dragX, dragY; 222 | float dragOrig; 223 | int widgetX, widgetY, widgetW = 100; 224 | bool insideCurrentScroll; 225 | 226 | uint areaId; 227 | uint widgetId; 228 | } 229 | 230 | bool anyActive() 231 | { 232 | return g_state.active != 0; 233 | } 234 | 235 | bool isActive(uint id) 236 | { 237 | return g_state.active == id; 238 | } 239 | 240 | /// Is the widget with specified ID 'inputable' for e.g. text input? 241 | bool isInputable(uint id) 242 | { 243 | return g_state.inputable == id; 244 | } 245 | 246 | bool isHot(uint id) 247 | { 248 | return g_state.hot == id; 249 | } 250 | 251 | bool inRect(int x, int y, int w, int h, bool checkScroll = true) 252 | { 253 | return (!checkScroll || g_state.insideCurrentScroll) && g_state.mx >= x && g_state.mx <= x + w && g_state.my >= y && g_state.my <= y + h; 254 | } 255 | 256 | void clearInput() 257 | { 258 | g_state.leftPressed = false; 259 | g_state.leftReleased = false; 260 | g_state.scroll = 0; 261 | } 262 | 263 | void clearActive() 264 | { 265 | g_state.active = 0; 266 | 267 | // mark all UI for this frame as processed 268 | clearInput(); 269 | } 270 | 271 | void setActive(uint id) 272 | { 273 | g_state.active = id; 274 | g_state.inputable = 0; 275 | g_state.wentActive = true; 276 | } 277 | 278 | // Set the inputable widget to the widget with specified ID. 279 | // 280 | // A text input becomes 'inputable' when it is 'hot' and left-clicked. 281 | // 282 | // 0 if no widget is inputable 283 | void setInputable(uint id) 284 | { 285 | g_state.inputable = id; 286 | } 287 | 288 | void setHot(uint id) 289 | { 290 | g_state.hotToBe = id; 291 | } 292 | 293 | bool buttonLogic(uint id, bool over) 294 | { 295 | bool res = false; 296 | 297 | // process down 298 | if (!anyActive()) 299 | { 300 | if (over) 301 | setHot(id); 302 | 303 | if (isHot(id) && g_state.leftPressed) 304 | setActive(id); 305 | } 306 | 307 | // if button is active, then react on left up 308 | if (isActive(id)) 309 | { 310 | g_state.isActive = true; 311 | 312 | if (over) 313 | setHot(id); 314 | 315 | if (g_state.leftReleased) 316 | { 317 | if (isHot(id)) 318 | res = true; 319 | clearActive(); 320 | } 321 | } 322 | 323 | // Not sure if this does anything (g_state.isHot doesn't seem to be used). 324 | if (isHot(id)) 325 | g_state.isHot = true; 326 | 327 | return res; 328 | } 329 | 330 | /** Input logic for text input fields. 331 | * 332 | * Params: 333 | * 334 | * id = ID of the text input widget 335 | * over = Is the mouse hovering over the text input widget? 336 | * forceInputable = Force the text input widget to be inputable regardless of whether it's 337 | * hovered and clicked by the mouse or not. 338 | */ 339 | void textInputLogic(uint id, bool over, bool forceInputable) 340 | { 341 | // If nothing else is active, we check for mouse over to make the widget hot in the 342 | // next frame, and if both hot and LMB is pressed (or forced), make it inputable. 343 | if (!anyActive()) 344 | { 345 | if (over) { setHot(id); } 346 | if (forceInputable || isHot(id) && g_state.leftPressed) { setInputable(id); } 347 | } 348 | // Not sure if this does anything (g_state.isHot doesn't seem to be used). 349 | if (isHot(id)) { g_state.isHot = true; } 350 | } 351 | 352 | /* Update user input on the beginning of a frame. 353 | * 354 | * Params: 355 | * 356 | * mx = Mouse X position. 357 | * my = Mouse Y position. 358 | * mbut = Mouse buttons pressed (a combination of values of a $(D MouseButton)). 359 | * scroll = Mouse wheel movement. 360 | * unicodeChar = Unicode text input from the keyboard (usually the unicode result of last 361 | * keypress). 362 | */ 363 | void updateInput(int mx, int my, ubyte mbut, int scroll, dchar unicodeChar) 364 | { 365 | bool left = (mbut & MouseButton.left) != 0; 366 | 367 | g_state.mx = mx; 368 | g_state.my = my; 369 | g_state.leftPressed = !g_state.left && left; 370 | g_state.leftReleased = g_state.left && !left; 371 | g_state.left = left; 372 | 373 | g_state.scroll = scroll; 374 | 375 | // Ignore characters we can't draw 376 | if(unicodeChar > maxCharacterCount()) { unicodeChar = 0; } 377 | g_state.lastUnicode = g_state.unicode; 378 | g_state.unicode = unicodeChar; 379 | } 380 | 381 | // Separate from gl3_renderer.getTextLength so api doesn't directly call renderer. 382 | float getTextLength(const(char)[] text) 383 | { 384 | return imgui.gl3_renderer.getTextLength(text); 385 | } 386 | 387 | const(imguiGfxCmd*) imguiGetRenderQueue() 388 | { 389 | return g_gfxCmdQueue.ptr; 390 | } 391 | 392 | int imguiGetRenderQueueSize() 393 | { 394 | return g_gfxCmdQueueSize; 395 | } 396 | -------------------------------------------------------------------------------- /src/imgui/gl3_renderer.d: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009-2010 Mikko Mononen memon@inside.org 3 | * 4 | * This software is provided 'as-is', without any express or implied 5 | * warranty. In no event will the authors be held liable for any damages 6 | * arising from the use of this software. 7 | * Permission is granted to anyone to use this software for any purpose, 8 | * including commercial applications, and to alter it and redistribute it 9 | * freely, subject to the following restrictions: 10 | * 1. The origin of this software must not be misrepresented; you must not 11 | * claim that you wrote the original software. If you use this software 12 | * in a product, an acknowledgment in the product documentation would be 13 | * appreciated but is not required. 14 | * 2. Altered source versions must be plainly marked as such, and must not be 15 | * misrepresented as being the original software. 16 | * 3. This notice may not be removed or altered from any source distribution. 17 | */ 18 | module imgui.gl3_renderer; 19 | 20 | import core.stdc.stdlib; 21 | import core.stdc.string; 22 | 23 | import std.math; 24 | import std.stdio; 25 | 26 | import glad.gl.all; 27 | import glad.gl.loader; 28 | 29 | import imgui.api; 30 | import imgui.engine; 31 | import imgui.stdb_truetype; 32 | 33 | private: 34 | // Draw up to 65536 unicode glyphs. What this will actually do is draw *only glyphs the 35 | // font supports* until it will run out of glyphs or texture space (determined by 36 | // g_font_texture_size). The actual number of glyphs will be in thousands (ASCII is 37 | // guaranteed, the rest will depend mainly on what the font supports, e.g. if it 38 | // supports common European characters such as á or š they will be there because they 39 | // are "early" in Unicode) 40 | // 41 | // Note that g_cdata uses memory of stbtt_bakedchar.sizeof * MAX_CHARACTER_COUNT which 42 | // at the moment is 20 * 65536 or 1.25 MiB. 43 | enum MAX_CHARACTER_COUNT = 1024 * 16 * 4; 44 | enum FIRST_CHARACTER = 32; 45 | 46 | 47 | 48 | /** Globals start. */ 49 | 50 | // A 1024x1024 font texture takes 1MiB of memory, and should be enough for thousands of 51 | // glyphs (at the fixed 15.0f size imgui uses). 52 | // 53 | // Some examples: 54 | // 55 | // =================================================== ============ ============================= 56 | // Font Texture size Glyps fit 57 | // =================================================== ============ ============================= 58 | // GentiumPlus-R 512x512 2550 (all glyphs in the font) 59 | // GentiumPlus-R 256x256 709 60 | // DroidSans (the small version included for examples) 512x512 903 (all glyphs in the font) 61 | // DroidSans (the small version included for examples) 256x256 497 62 | // =================================================== ============ ============================= 63 | // 64 | // This was measured after the optimization to reuse null character glyph, which is in 65 | // BakeFontBitmap in stdb_truetype.d 66 | __gshared uint g_font_texture_size = 1024; 67 | __gshared float[TEMP_COORD_COUNT * 2] g_tempCoords; 68 | __gshared float[TEMP_COORD_COUNT * 2] g_tempNormals; 69 | __gshared float[TEMP_COORD_COUNT * 12 + (TEMP_COORD_COUNT - 2) * 6] g_tempVertices; 70 | __gshared float[TEMP_COORD_COUNT * 12 + (TEMP_COORD_COUNT - 2) * 6] g_tempTextureCoords; 71 | __gshared float[TEMP_COORD_COUNT * 24 + (TEMP_COORD_COUNT - 2) * 12] g_tempColors; 72 | __gshared float[CIRCLE_VERTS * 2] g_circleVerts; 73 | __gshared uint g_max_character_count = MAX_CHARACTER_COUNT; 74 | __gshared stbtt_bakedchar[MAX_CHARACTER_COUNT] g_cdata; 75 | __gshared GLuint g_ftex = 0; 76 | __gshared GLuint g_whitetex = 0; 77 | __gshared GLuint g_vao = 0; 78 | __gshared GLuint[3] g_vbos = [0, 0, 0]; 79 | __gshared GLuint g_program = 0; 80 | __gshared GLuint g_programViewportLocation = 0; 81 | __gshared GLuint g_programTextureLocation = 0; 82 | 83 | /** Globals end. */ 84 | 85 | enum TEMP_COORD_COUNT = 100; 86 | enum int CIRCLE_VERTS = 8 * 4; 87 | immutable float[4] g_tabStops = [150, 210, 270, 330]; 88 | 89 | package: 90 | 91 | uint maxCharacterCount() @trusted nothrow @nogc 92 | { 93 | return g_max_character_count; 94 | } 95 | 96 | void imguifree(void* ptr, void* /*userptr*/) 97 | { 98 | free(ptr); 99 | } 100 | 101 | void* imguimalloc(size_t size, void* /*userptr*/) 102 | { 103 | return malloc(size); 104 | } 105 | 106 | uint toPackedRGBA(RGBA color) 107 | { 108 | return (color.r) | (color.g << 8) | (color.b << 16) | (color.a << 24); 109 | } 110 | 111 | void drawPolygon(const(float)* coords, uint numCoords, float r, uint col) 112 | { 113 | if (numCoords > TEMP_COORD_COUNT) 114 | numCoords = TEMP_COORD_COUNT; 115 | 116 | for (uint i = 0, j = numCoords - 1; i < numCoords; j = i++) 117 | { 118 | const(float)* v0 = &coords[j * 2]; 119 | const(float)* v1 = &coords[i * 2]; 120 | float dx = v1[0] - v0[0]; 121 | float dy = v1[1] - v0[1]; 122 | float d = sqrt(dx * dx + dy * dy); 123 | 124 | if (d > 0) 125 | { 126 | d = 1.0f / d; 127 | dx *= d; 128 | dy *= d; 129 | } 130 | g_tempNormals[j * 2 + 0] = dy; 131 | g_tempNormals[j * 2 + 1] = -dx; 132 | } 133 | 134 | const float[4] colf = [cast(float)(col & 0xff) / 255.0, cast(float)((col >> 8) & 0xff) / 255.0, cast(float)((col >> 16) & 0xff) / 255.0, cast(float)((col >> 24) & 0xff) / 255.0]; 135 | const float[4] colTransf = [cast(float)(col & 0xff) / 255.0, cast(float)((col >> 8) & 0xff) / 255.0, cast(float)((col >> 16) & 0xff) / 255.0, 0]; 136 | 137 | for (uint i = 0, j = numCoords - 1; i < numCoords; j = i++) 138 | { 139 | float dlx0 = g_tempNormals[j * 2 + 0]; 140 | float dly0 = g_tempNormals[j * 2 + 1]; 141 | float dlx1 = g_tempNormals[i * 2 + 0]; 142 | float dly1 = g_tempNormals[i * 2 + 1]; 143 | float dmx = (dlx0 + dlx1) * 0.5f; 144 | float dmy = (dly0 + dly1) * 0.5f; 145 | float dmr2 = dmx * dmx + dmy * dmy; 146 | 147 | if (dmr2 > 0.000001f) 148 | { 149 | float scale = 1.0f / dmr2; 150 | 151 | if (scale > 10.0f) 152 | scale = 10.0f; 153 | dmx *= scale; 154 | dmy *= scale; 155 | } 156 | g_tempCoords[i * 2 + 0] = coords[i * 2 + 0] + dmx * r; 157 | g_tempCoords[i * 2 + 1] = coords[i * 2 + 1] + dmy * r; 158 | } 159 | 160 | int vSize = numCoords * 12 + (numCoords - 2) * 6; 161 | int uvSize = numCoords * 2 * 6 + (numCoords - 2) * 2 * 3; 162 | int cSize = numCoords * 4 * 6 + (numCoords - 2) * 4 * 3; 163 | float* v = g_tempVertices.ptr; 164 | float* uv = g_tempTextureCoords.ptr; 165 | memset(uv, 0, uvSize * float.sizeof); 166 | float* c = g_tempColors.ptr; 167 | memset(c, 1, cSize * float.sizeof); 168 | 169 | float* ptrV = v; 170 | float* ptrC = c; 171 | 172 | for (uint i = 0, j = numCoords - 1; i < numCoords; j = i++) 173 | { 174 | *ptrV = coords[i * 2]; 175 | *(ptrV + 1) = coords[i * 2 + 1]; 176 | ptrV += 2; 177 | *ptrV = coords[j * 2]; 178 | *(ptrV + 1) = coords[j * 2 + 1]; 179 | ptrV += 2; 180 | *ptrV = g_tempCoords[j * 2]; 181 | *(ptrV + 1) = g_tempCoords[j * 2 + 1]; 182 | ptrV += 2; 183 | *ptrV = g_tempCoords[j * 2]; 184 | *(ptrV + 1) = g_tempCoords[j * 2 + 1]; 185 | ptrV += 2; 186 | *ptrV = g_tempCoords[i * 2]; 187 | *(ptrV + 1) = g_tempCoords[i * 2 + 1]; 188 | ptrV += 2; 189 | *ptrV = coords[i * 2]; 190 | *(ptrV + 1) = coords[i * 2 + 1]; 191 | ptrV += 2; 192 | 193 | *ptrC = colf[0]; 194 | *(ptrC + 1) = colf[1]; 195 | *(ptrC + 2) = colf[2]; 196 | *(ptrC + 3) = colf[3]; 197 | ptrC += 4; 198 | *ptrC = colf[0]; 199 | *(ptrC + 1) = colf[1]; 200 | *(ptrC + 2) = colf[2]; 201 | *(ptrC + 3) = colf[3]; 202 | ptrC += 4; 203 | *ptrC = colTransf[0]; 204 | *(ptrC + 1) = colTransf[1]; 205 | *(ptrC + 2) = colTransf[2]; 206 | *(ptrC + 3) = colTransf[3]; 207 | ptrC += 4; 208 | *ptrC = colTransf[0]; 209 | *(ptrC + 1) = colTransf[1]; 210 | *(ptrC + 2) = colTransf[2]; 211 | *(ptrC + 3) = colTransf[3]; 212 | ptrC += 4; 213 | *ptrC = colTransf[0]; 214 | *(ptrC + 1) = colTransf[1]; 215 | *(ptrC + 2) = colTransf[2]; 216 | *(ptrC + 3) = colTransf[3]; 217 | ptrC += 4; 218 | *ptrC = colf[0]; 219 | *(ptrC + 1) = colf[1]; 220 | *(ptrC + 2) = colf[2]; 221 | *(ptrC + 3) = colf[3]; 222 | ptrC += 4; 223 | } 224 | 225 | for (uint i = 2; i < numCoords; ++i) 226 | { 227 | *ptrV = coords[0]; 228 | *(ptrV + 1) = coords[1]; 229 | ptrV += 2; 230 | *ptrV = coords[(i - 1) * 2]; 231 | *(ptrV + 1) = coords[(i - 1) * 2 + 1]; 232 | ptrV += 2; 233 | *ptrV = coords[i * 2]; 234 | *(ptrV + 1) = coords[i * 2 + 1]; 235 | ptrV += 2; 236 | 237 | *ptrC = colf[0]; 238 | *(ptrC + 1) = colf[1]; 239 | *(ptrC + 2) = colf[2]; 240 | *(ptrC + 3) = colf[3]; 241 | ptrC += 4; 242 | *ptrC = colf[0]; 243 | *(ptrC + 1) = colf[1]; 244 | *(ptrC + 2) = colf[2]; 245 | *(ptrC + 3) = colf[3]; 246 | ptrC += 4; 247 | *ptrC = colf[0]; 248 | *(ptrC + 1) = colf[1]; 249 | *(ptrC + 2) = colf[2]; 250 | *(ptrC + 3) = colf[3]; 251 | ptrC += 4; 252 | } 253 | 254 | glBindTexture(GL_TEXTURE_2D, g_whitetex); 255 | 256 | glBindVertexArray(g_vao); 257 | glBindBuffer(GL_ARRAY_BUFFER, g_vbos[0]); 258 | glBufferData(GL_ARRAY_BUFFER, vSize * float.sizeof, v, GL_STATIC_DRAW); 259 | glBindBuffer(GL_ARRAY_BUFFER, g_vbos[1]); 260 | glBufferData(GL_ARRAY_BUFFER, uvSize * float.sizeof, uv, GL_STATIC_DRAW); 261 | glBindBuffer(GL_ARRAY_BUFFER, g_vbos[2]); 262 | glBufferData(GL_ARRAY_BUFFER, cSize * float.sizeof, c, GL_STATIC_DRAW); 263 | glDrawArrays(GL_TRIANGLES, 0, (numCoords * 2 + numCoords - 2) * 3); 264 | } 265 | 266 | void drawRect(float x, float y, float w, float h, float fth, uint col) 267 | { 268 | const float[4 * 2] verts = 269 | [ 270 | x + 0.5f, y + 0.5f, 271 | x + w - 0.5f, y + 0.5f, 272 | x + w - 0.5f, y + h - 0.5f, 273 | x + 0.5f, y + h - 0.5f, 274 | ]; 275 | drawPolygon(verts.ptr, 4, fth, col); 276 | } 277 | 278 | /* 279 | void drawEllipse(float x, float y, float w, float h, float fth, uint col) 280 | { 281 | float verts[CIRCLE_VERTS*2]; 282 | const(float)* cverts = g_circleVerts; 283 | float* v = verts; 284 | 285 | for (int i = 0; i < CIRCLE_VERTS; ++i) 286 | { 287 | * v++ = x + cverts[i*2]*w; 288 | * v++ = y + cverts[i*2+1]*h; 289 | } 290 | 291 | drawPolygon(verts, CIRCLE_VERTS, fth, col); 292 | } 293 | */ 294 | 295 | void drawRoundedRect(float x, float y, float w, float h, float r, float fth, uint col) 296 | { 297 | const uint n = CIRCLE_VERTS / 4; 298 | float[(n + 1) * 4 * 2] verts; 299 | const(float)* cverts = g_circleVerts.ptr; 300 | float* v = verts.ptr; 301 | 302 | for (uint i = 0; i <= n; ++i) 303 | { 304 | *v++ = x + w - r + cverts[i * 2] * r; 305 | *v++ = y + h - r + cverts[i * 2 + 1] * r; 306 | } 307 | 308 | for (uint i = n; i <= n * 2; ++i) 309 | { 310 | *v++ = x + r + cverts[i * 2] * r; 311 | *v++ = y + h - r + cverts[i * 2 + 1] * r; 312 | } 313 | 314 | for (uint i = n * 2; i <= n * 3; ++i) 315 | { 316 | *v++ = x + r + cverts[i * 2] * r; 317 | *v++ = y + r + cverts[i * 2 + 1] * r; 318 | } 319 | 320 | for (uint i = n * 3; i < n * 4; ++i) 321 | { 322 | *v++ = x + w - r + cverts[i * 2] * r; 323 | *v++ = y + r + cverts[i * 2 + 1] * r; 324 | } 325 | 326 | *v++ = x + w - r + cverts[0] * r; 327 | *v++ = y + r + cverts[1] * r; 328 | 329 | drawPolygon(verts.ptr, (n + 1) * 4, fth, col); 330 | } 331 | 332 | void drawLine(float x0, float y0, float x1, float y1, float r, float fth, uint col) 333 | { 334 | float dx = x1 - x0; 335 | float dy = y1 - y0; 336 | float d = sqrt(dx * dx + dy * dy); 337 | 338 | if (d > 0.0001f) 339 | { 340 | d = 1.0f / d; 341 | dx *= d; 342 | dy *= d; 343 | } 344 | float nx = dy; 345 | float ny = -dx; 346 | float[4 * 2] verts; 347 | r -= fth; 348 | r *= 0.5f; 349 | 350 | if (r < 0.01f) 351 | r = 0.01f; 352 | dx *= r; 353 | dy *= r; 354 | nx *= r; 355 | ny *= r; 356 | 357 | verts[0] = x0 - dx - nx; 358 | verts[1] = y0 - dy - ny; 359 | 360 | verts[2] = x0 - dx + nx; 361 | verts[3] = y0 - dy + ny; 362 | 363 | verts[4] = x1 + dx + nx; 364 | verts[5] = y1 + dy + ny; 365 | 366 | verts[6] = x1 + dx - nx; 367 | verts[7] = y1 + dy - ny; 368 | 369 | drawPolygon(verts.ptr, 4, fth, col); 370 | } 371 | 372 | bool imguiRenderGLInit(const(char)[] fontpath, const uint fontTextureSize) 373 | { 374 | for (int i = 0; i < CIRCLE_VERTS; ++i) 375 | { 376 | float a = cast(float)i / cast(float)CIRCLE_VERTS * PI * 2; 377 | g_circleVerts[i * 2 + 0] = cos(a); 378 | g_circleVerts[i * 2 + 1] = sin(a); 379 | } 380 | 381 | // Load font. 382 | auto file = File(cast(string)fontpath, "rb"); 383 | g_font_texture_size = fontTextureSize; 384 | FILE* fp = file.getFP(); 385 | 386 | if (!fp) 387 | return false; 388 | fseek(fp, 0, SEEK_END); 389 | size_t size = cast(size_t)ftell(fp); 390 | fseek(fp, 0, SEEK_SET); 391 | 392 | ubyte* ttfBuffer = cast(ubyte*)malloc(size); 393 | 394 | if (!ttfBuffer) 395 | { 396 | return false; 397 | } 398 | 399 | fread(ttfBuffer, 1, size, fp); 400 | // fclose(fp); 401 | fp = null; 402 | 403 | ubyte* bmap = cast(ubyte*)malloc(g_font_texture_size * g_font_texture_size); 404 | 405 | if (!bmap) 406 | { 407 | free(ttfBuffer); 408 | return false; 409 | } 410 | 411 | const result = stbtt_BakeFontBitmap(ttfBuffer, 0, 15.0f, bmap, 412 | g_font_texture_size, g_font_texture_size, 413 | FIRST_CHARACTER, g_max_character_count, g_cdata.ptr); 414 | // If result is negative, we baked less than max characters so update the max 415 | // character count. 416 | if(result < 0) 417 | { 418 | g_max_character_count = -result; 419 | } 420 | 421 | // can free ttf_buffer at this point 422 | glGenTextures(1, &g_ftex); 423 | glBindTexture(GL_TEXTURE_2D, g_ftex); 424 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, 425 | g_font_texture_size, g_font_texture_size, 426 | 0, GL_RED, GL_UNSIGNED_BYTE, bmap); 427 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 428 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 429 | 430 | // can free ttf_buffer at this point 431 | ubyte white_alpha = 255; 432 | glGenTextures(1, &g_whitetex); 433 | glBindTexture(GL_TEXTURE_2D, g_whitetex); 434 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, 1, 1, 0, GL_RED, GL_UNSIGNED_BYTE, &white_alpha); 435 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 436 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 437 | 438 | glGenVertexArrays(1, &g_vao); 439 | glGenBuffers(3, g_vbos.ptr); 440 | 441 | glBindVertexArray(g_vao); 442 | glEnableVertexAttribArray(0); 443 | glEnableVertexAttribArray(1); 444 | glEnableVertexAttribArray(2); 445 | 446 | glBindBuffer(GL_ARRAY_BUFFER, g_vbos[0]); 447 | glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, GL_FLOAT.sizeof * 2, null); 448 | glBufferData(GL_ARRAY_BUFFER, 0, null, GL_STATIC_DRAW); 449 | glBindBuffer(GL_ARRAY_BUFFER, g_vbos[1]); 450 | glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, GL_FLOAT.sizeof * 2, null); 451 | glBufferData(GL_ARRAY_BUFFER, 0, null, GL_STATIC_DRAW); 452 | glBindBuffer(GL_ARRAY_BUFFER, g_vbos[2]); 453 | glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, GL_FLOAT.sizeof * 4, null); 454 | glBufferData(GL_ARRAY_BUFFER, 0, null, GL_STATIC_DRAW); 455 | g_program = glCreateProgram(); 456 | 457 | string vs = 458 | "#version 150\n" ~ 459 | "uniform vec2 Viewport;\n" ~ 460 | "in vec2 VertexPosition;\n" ~ 461 | "in vec2 VertexTexCoord;\n" ~ 462 | "in vec4 VertexColor;\n" ~ 463 | "out vec2 texCoord;\n" ~ 464 | "out vec4 vertexColor;\n" ~ 465 | "void main(void)\n" ~ 466 | "{\n" ~ 467 | " vertexColor = VertexColor;\n" ~ 468 | " texCoord = VertexTexCoord;\n" ~ 469 | " gl_Position = vec4(VertexPosition * 2.0 / Viewport - 1.0, 0.f, 1.0);\n" ~ 470 | "}\n"; 471 | GLuint vso = glCreateShader(GL_VERTEX_SHADER); 472 | auto vsPtr = vs.ptr; 473 | glShaderSource(vso, 1, &vsPtr, null); 474 | glCompileShader(vso); 475 | glAttachShader(g_program, vso); 476 | 477 | string fs = 478 | "#version 150\n" ~ 479 | "in vec2 texCoord;\n" ~ 480 | "in vec4 vertexColor;\n" ~ 481 | "uniform sampler2D Texture;\n" ~ 482 | "out vec4 Color;\n" ~ 483 | "void main(void)\n" ~ 484 | "{\n" ~ 485 | " float alpha = texture(Texture, texCoord).r;\n" ~ 486 | " Color = vec4(vertexColor.rgb, vertexColor.a * alpha);\n" ~ 487 | "}\n"; 488 | GLuint fso = glCreateShader(GL_FRAGMENT_SHADER); 489 | 490 | auto fsPtr = fs.ptr; 491 | glShaderSource(fso, 1, &fsPtr, null); 492 | glCompileShader(fso); 493 | glAttachShader(g_program, fso); 494 | 495 | glBindAttribLocation(g_program, 0, "VertexPosition"); 496 | glBindAttribLocation(g_program, 1, "VertexTexCoord"); 497 | glBindAttribLocation(g_program, 2, "VertexColor"); 498 | glBindFragDataLocation(g_program, 0, "Color"); 499 | glLinkProgram(g_program); 500 | glDeleteShader(vso); 501 | glDeleteShader(fso); 502 | 503 | glUseProgram(g_program); 504 | g_programViewportLocation = glGetUniformLocation(g_program, "Viewport"); 505 | g_programTextureLocation = glGetUniformLocation(g_program, "Texture"); 506 | 507 | glUseProgram(0); 508 | 509 | free(ttfBuffer); 510 | free(bmap); 511 | 512 | return true; 513 | } 514 | 515 | void imguiRenderGLDestroy() 516 | { 517 | if (g_ftex) 518 | { 519 | glDeleteTextures(1, &g_ftex); 520 | g_ftex = 0; 521 | } 522 | 523 | if (g_vao) 524 | { 525 | glDeleteVertexArrays(1, &g_vao); 526 | glDeleteBuffers(3, g_vbos.ptr); 527 | g_vao = 0; 528 | } 529 | 530 | if (g_program) 531 | { 532 | glDeleteProgram(g_program); 533 | g_program = 0; 534 | } 535 | } 536 | 537 | void getBakedQuad(stbtt_bakedchar* chardata, int pw, int ph, int char_index, 538 | float* xpos, float* ypos, stbtt_aligned_quad* q) 539 | { 540 | stbtt_bakedchar* b = chardata + char_index; 541 | int round_x = STBTT_ifloor(*xpos + b.xoff); 542 | int round_y = STBTT_ifloor(*ypos - b.yoff); 543 | 544 | q.x0 = cast(float)round_x; 545 | q.y0 = cast(float)round_y; 546 | q.x1 = cast(float)round_x + b.x1 - b.x0; 547 | q.y1 = cast(float)round_y - b.y1 + b.y0; 548 | 549 | q.s0 = b.x0 / cast(float)pw; 550 | q.t0 = b.y0 / cast(float)pw; 551 | q.s1 = b.x1 / cast(float)ph; 552 | q.t1 = b.y1 / cast(float)ph; 553 | 554 | *xpos += b.xadvance; 555 | } 556 | 557 | float getTextLength(stbtt_bakedchar* chardata, const(char)[] text) 558 | { 559 | float xpos = 0; 560 | float len = 0; 561 | 562 | // The cast(string) is only there for UTF-8 decoding. 563 | foreach (dchar c; cast(string)text) 564 | { 565 | if (c == '\t') 566 | { 567 | for (int i = 0; i < 4; ++i) 568 | { 569 | if (xpos < g_tabStops[i]) 570 | { 571 | xpos = g_tabStops[i]; 572 | break; 573 | } 574 | } 575 | } 576 | else if (cast(int)c >= FIRST_CHARACTER && cast(int)c < FIRST_CHARACTER + g_max_character_count) 577 | { 578 | stbtt_bakedchar* b = chardata + c - FIRST_CHARACTER; 579 | int round_x = STBTT_ifloor((xpos + b.xoff) + 0.5); 580 | len = round_x + b.x1 - b.x0 + 0.5f; 581 | xpos += b.xadvance; 582 | } 583 | } 584 | 585 | return len; 586 | } 587 | 588 | float getTextLength(const(char)[] text) 589 | { 590 | return getTextLength(g_cdata.ptr, text); 591 | } 592 | 593 | void drawText(float x, float y, const(char)[] text, int align_, uint col) 594 | { 595 | if (!g_ftex) 596 | return; 597 | 598 | if (!text) 599 | return; 600 | 601 | if (align_ == TextAlign.center) 602 | x -= getTextLength(g_cdata.ptr, text) / 2; 603 | else if (align_ == TextAlign.right) 604 | x -= getTextLength(g_cdata.ptr, text); 605 | 606 | float r = cast(float)(col & 0xff) / 255.0; 607 | float g = cast(float)((col >> 8) & 0xff) / 255.0; 608 | float b = cast(float)((col >> 16) & 0xff) / 255.0; 609 | float a = cast(float)((col >> 24) & 0xff) / 255.0; 610 | 611 | // assume orthographic projection with units = screen pixels, origin at top left 612 | glBindTexture(GL_TEXTURE_2D, g_ftex); 613 | 614 | const float ox = x; 615 | 616 | // The cast(string) is only there for UTF-8 decoding. 617 | //foreach (ubyte c; cast(ubyte[])text) 618 | foreach (dchar c; cast(string)text) 619 | { 620 | if (c == '\t') 621 | { 622 | for (int i = 0; i < 4; ++i) 623 | { 624 | if (x < g_tabStops[i] + ox) 625 | { 626 | x = g_tabStops[i] + ox; 627 | break; 628 | } 629 | } 630 | } 631 | else if (c >= FIRST_CHARACTER && c < FIRST_CHARACTER + g_max_character_count) 632 | { 633 | stbtt_aligned_quad q; 634 | getBakedQuad(g_cdata.ptr, g_font_texture_size, g_font_texture_size, 635 | c - FIRST_CHARACTER, &x, &y, &q); 636 | 637 | float[12] v = [ 638 | q.x0, q.y0, 639 | q.x1, q.y1, 640 | q.x1, q.y0, 641 | q.x0, q.y0, 642 | q.x0, q.y1, 643 | q.x1, q.y1, 644 | ]; 645 | float[12] uv = [ 646 | q.s0, q.t0, 647 | q.s1, q.t1, 648 | q.s1, q.t0, 649 | q.s0, q.t0, 650 | q.s0, q.t1, 651 | q.s1, q.t1, 652 | ]; 653 | float[24] cArr = [ 654 | r, g, b, a, 655 | r, g, b, a, 656 | r, g, b, a, 657 | r, g, b, a, 658 | r, g, b, a, 659 | r, g, b, a, 660 | ]; 661 | glBindVertexArray(g_vao); 662 | glBindBuffer(GL_ARRAY_BUFFER, g_vbos[0]); 663 | glBufferData(GL_ARRAY_BUFFER, 12 * float.sizeof, v.ptr, GL_STATIC_DRAW); 664 | glBindBuffer(GL_ARRAY_BUFFER, g_vbos[1]); 665 | glBufferData(GL_ARRAY_BUFFER, 12 * float.sizeof, uv.ptr, GL_STATIC_DRAW); 666 | glBindBuffer(GL_ARRAY_BUFFER, g_vbos[2]); 667 | glBufferData(GL_ARRAY_BUFFER, 24 * float.sizeof, cArr.ptr, GL_STATIC_DRAW); 668 | glDrawArrays(GL_TRIANGLES, 0, 6); 669 | } 670 | } 671 | 672 | // glEnd(); 673 | // glDisable(GL_TEXTURE_2D); 674 | } 675 | 676 | void imguiRenderGLDraw(int width, int height) 677 | { 678 | const imguiGfxCmd* q = imguiGetRenderQueue(); 679 | int nq = imguiGetRenderQueueSize(); 680 | 681 | const float s = 1.0f / 8.0f; 682 | 683 | glViewport(0, 0, width, height); 684 | glUseProgram(g_program); 685 | glActiveTexture(GL_TEXTURE0); 686 | glUniform2f(g_programViewportLocation, cast(float)width, cast(float)height); 687 | glUniform1i(g_programTextureLocation, 0); 688 | 689 | glDisable(GL_SCISSOR_TEST); 690 | 691 | for (int i = 0; i < nq; ++i) 692 | { 693 | auto cmd = &q[i]; 694 | 695 | if (cmd.type == IMGUI_GFXCMD_RECT) 696 | { 697 | if (cmd.rect.r == 0) 698 | { 699 | drawRect(cast(float)cmd.rect.x * s + 0.5f, cast(float)cmd.rect.y * s + 0.5f, 700 | cast(float)cmd.rect.w * s - 1, cast(float)cmd.rect.h * s - 1, 701 | 1.0f, cmd.col); 702 | } 703 | else 704 | { 705 | drawRoundedRect(cast(float)cmd.rect.x * s + 0.5f, cast(float)cmd.rect.y * s + 0.5f, 706 | cast(float)cmd.rect.w * s - 1, cast(float)cmd.rect.h * s - 1, 707 | cast(float)cmd.rect.r * s, 1.0f, cmd.col); 708 | } 709 | } 710 | else if (cmd.type == IMGUI_GFXCMD_LINE) 711 | { 712 | drawLine(cmd.line.x0 * s, cmd.line.y0 * s, cmd.line.x1 * s, cmd.line.y1 * s, cmd.line.r * s, 1.0f, cmd.col); 713 | } 714 | else if (cmd.type == IMGUI_GFXCMD_TRIANGLE) 715 | { 716 | if (cmd.flags == 1) 717 | { 718 | const float[3 * 2] verts = 719 | [ 720 | cast(float)cmd.rect.x * s + 0.5f, cast(float)cmd.rect.y * s + 0.5f, 721 | cast(float)cmd.rect.x * s + 0.5f + cast(float)cmd.rect.w * s - 1, cast(float)cmd.rect.y * s + 0.5f + cast(float)cmd.rect.h * s / 2 - 0.5f, 722 | cast(float)cmd.rect.x * s + 0.5f, cast(float)cmd.rect.y * s + 0.5f + cast(float)cmd.rect.h * s - 1, 723 | ]; 724 | drawPolygon(verts.ptr, 3, 1.0f, cmd.col); 725 | } 726 | 727 | if (cmd.flags == 2) 728 | { 729 | const float[3 * 2] verts = 730 | [ 731 | cast(float)cmd.rect.x * s + 0.5f, cast(float)cmd.rect.y * s + 0.5f + cast(float)cmd.rect.h * s - 1, 732 | cast(float)cmd.rect.x * s + 0.5f + cast(float)cmd.rect.w * s / 2 - 0.5f, cast(float)cmd.rect.y * s + 0.5f, 733 | cast(float)cmd.rect.x * s + 0.5f + cast(float)cmd.rect.w * s - 1, cast(float)cmd.rect.y * s + 0.5f + cast(float)cmd.rect.h * s - 1, 734 | ]; 735 | drawPolygon(verts.ptr, 3, 1.0f, cmd.col); 736 | } 737 | } 738 | else if (cmd.type == IMGUI_GFXCMD_TEXT) 739 | { 740 | drawText(cmd.text.x, cmd.text.y, cmd.text.text, cmd.text.align_, cmd.col); 741 | } 742 | else if (cmd.type == IMGUI_GFXCMD_SCISSOR) 743 | { 744 | if (cmd.flags) 745 | { 746 | glEnable(GL_SCISSOR_TEST); 747 | glScissor(cmd.rect.x, cmd.rect.y, cmd.rect.w, cmd.rect.h); 748 | } 749 | else 750 | { 751 | glDisable(GL_SCISSOR_TEST); 752 | } 753 | } 754 | } 755 | 756 | glDisable(GL_SCISSOR_TEST); 757 | } 758 | -------------------------------------------------------------------------------- /src/imgui/package.d: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009-2010 Mikko Mononen memon@inside.org 3 | * 4 | * This software is provided 'as-is', without any express or implied 5 | * warranty. In no event will the authors be held liable for any damages 6 | * arising from the use of this software. 7 | * Permission is granted to anyone to use this software for any purpose, 8 | * including commercial applications, and to alter it and redistribute it 9 | * freely, subject to the following restrictions: 10 | * 1. The origin of this software must not be misrepresented; you must not 11 | * claim that you wrote the original software. If you use this software 12 | * in a product, an acknowledgment in the product documentation would be 13 | * appreciated but is not required. 14 | * 2. Altered source versions must be plainly marked as such, and must not be 15 | * misrepresented as being the original software. 16 | * 3. This notice may not be removed or altered from any source distribution. 17 | */ 18 | module imgui; 19 | 20 | public 21 | { 22 | import imgui.api; 23 | } 24 | -------------------------------------------------------------------------------- /src/imgui/util.d: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Andrej Mitrovic 2014. 3 | * Distributed under the Boost Software License, Version 1.0. 4 | * (See accompanying file LICENSE_1_0.txt or copy at 5 | * http://www.boost.org/LICENSE_1_0.txt) 6 | */ 7 | module imgui.util; 8 | 9 | /** 10 | Note: This unfortunately doesn't work with more complex structures 11 | due to a DMD bug with an infinite loop problem. This isn't reported yet. 12 | */ 13 | 14 | import std.range; 15 | import std.stdio; 16 | 17 | auto ref fieldRange(S, T)(auto ref T sym) 18 | { 19 | static if (is(T == struct) && !is(T == S)) 20 | return fieldRange!S(sym.tupleof); 21 | else 22 | return only(sym); 23 | } 24 | 25 | auto ref fieldRange(S, T...)(auto ref T syms) if (T.length > 1) 26 | { 27 | return chain(fieldRange!S(syms[0]), 28 | fieldRange!S(syms[1 .. $])); 29 | } 30 | 31 | auto addrFieldRange(S, T)(ref T sym) 32 | { 33 | static if (is(T == struct) && !is(T == S)) 34 | return addrFieldRange!S(sym.tupleof); 35 | else 36 | return only(&sym); 37 | } 38 | 39 | auto addrFieldRange(S, T...)(ref T syms) if (T.length > 1) 40 | { 41 | return chain(addrFieldRange!S(syms[0]), 42 | addrFieldRange!S(syms[1 .. $])); 43 | } 44 | 45 | auto refFieldRange(S, T)(ref T sym) 46 | { 47 | alias Type = typeof(sym.fieldRange!S.front); 48 | 49 | static ref Type getRef(Type* elem) { return *elem; } 50 | 51 | return sym.addrFieldRange!S.map!getRef; 52 | } 53 | --------------------------------------------------------------------------------