├── .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 | 
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 |
--------------------------------------------------------------------------------