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 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 
47     void render()
48     {
49         glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
50 
51         // Mouse states
52         ubyte mousebutton = 0;
53         double mouseX;
54         double mouseY;
55         glfwGetCursorPos(window.window, &mouseX, &mouseY);
56 
57         const scrollAreaWidth = (windowWidth / 4) - 10;  // -10 to allow room for the scrollbar
58         const scrollAreaHeight = windowHeight - 20;
59 
60         int mousex = cast(int)mouseX;
61         int mousey = cast(int)mouseY;
62 
63         mousey = windowHeight - mousey;
64         int leftButton   = glfwGetMouseButton(window.window, GLFW_MOUSE_BUTTON_LEFT);
65         int rightButton  = glfwGetMouseButton(window.window, GLFW_MOUSE_BUTTON_RIGHT);
66         int middleButton = glfwGetMouseButton(window.window, GLFW_MOUSE_BUTTON_MIDDLE);
67 
68         if (leftButton == GLFW_PRESS)
69             mousebutton |= MouseButton.left;
70 
71         imguiBeginFrame(mousex, mousey, mousebutton, mouseScroll);
72 
73         if (mouseScroll != 0)
74             mouseScroll = 0;
75 
76         /// Improper memory management.
77         displayArea1(scrollAreaWidth, scrollAreaHeight);
78 
79         /// Attempted workaround, but still improper memory management.
80         char[128] buffer;
81         displayArea2(scrollAreaWidth, scrollAreaHeight, buffer);
82 
83         /// Proper memory management.
84         char[128][100] buffers;
85         displayArea3(scrollAreaWidth, scrollAreaHeight, buffers);
86 
87         /// Alternatively you may use 'string', which is guaranteed to be immutable
88         /// and will outlive any stack scope since the garbage collector will keep
89         /// a reference to it.
90         displayArea4(scrollAreaWidth, scrollAreaHeight);
91 
92         imguiEndFrame();
93 
94         imguiRender(windowWidth, windowHeight);
95     }
96 
97     void displayArea1(int scrollAreaWidth, int scrollAreaHeight)
98     {
99         imguiBeginScrollArea("Improper memory management 1", 10, 10, scrollAreaWidth, scrollAreaHeight, &scrollArea1);
100 
101         imguiSeparatorLine();
102         imguiSeparator();
103 
104         /// Note: improper memory management: 'buffer' is scoped to this function,
105         /// but imguiLabel will keep a reference to the 'buffer' until 'imguiRender'
106         /// is called. 'imguiRender' is only called after 'displayArea1' returns,
107         /// after which 'buffer' will not be usable (it's memory allocated on the stack!).
108         /// Result: Random text being displayed or even crashes are possible.
109         char[128] buffer;
110         auto text = buffer.sformat("This is my text: %s", "more text");
111         imguiLabel(text);
112 
113         imguiEndScrollArea();
114     }
115 
116     void displayArea2(int scrollAreaWidth, int scrollAreaHeight, ref char[128] buffer)
117     {
118         imguiBeginScrollArea("Improper memory management 2", 20 + (1 * scrollAreaWidth), 10, scrollAreaWidth, scrollAreaHeight, &scrollArea2);
119 
120         imguiSeparatorLine();
121         imguiSeparator();
122 
123         foreach (idx; 0 .. 100)
124         {
125             /// Note: improper memory management: 'buffer' will be re-used in each
126             /// iteration of this loop, but imguiLabel will just keep a reference
127             /// to the same memory location on each call.
128             /// Result: Typically the same bit of text is displayed 100 times.
129             auto text = buffer.sformat("Item number %s", idx);
130             imguiLabel(text);
131         }
132 
133         imguiEndScrollArea();
134     }
135 
136     void displayArea3(int scrollAreaWidth, int scrollAreaHeight, ref char[128][100] buffers)
137     {
138         imguiBeginScrollArea("Proper memory management 1", 30 + (2 * scrollAreaWidth), 10, scrollAreaWidth, scrollAreaHeight, &scrollArea3);
139 
140         imguiSeparatorLine();
141         imguiSeparator();
142 
143         foreach (idx, ref buffer; buffers)
144         {
145             /// Note: Proper memory management: 'buffer' is unique for all the items,
146             /// and imguiLabel can safely store a reference to each string since each
147             /// buffer will be valid until the exit of the scope where the 'imguiRender'
148             /// call is emitted.
149             auto text = buffer.sformat("Item number %s", idx);
150             imguiLabel(text);
151         }
152 
153         imguiEndScrollArea();
154     }
155 
156     void displayArea4(int scrollAreaWidth, int scrollAreaHeight)
157     {
158         imguiBeginScrollArea("Proper memory management 2", 40 + (3 * scrollAreaWidth), 10, scrollAreaWidth, scrollAreaHeight, &scrollArea4);
159 
160         imguiSeparatorLine();
161         imguiSeparator();
162 
163         foreach (idx; 0 .. 100)
164         {
165             /// Note: Proper memory management: the string will not be prematurely
166             /// garbage-collected since the GC will know that 'imguiLabel' will store
167             /// a refererence to this string for use in a later 'imguiRender call.
168             string str = "This is just some text";
169             imguiLabel(str);
170         }
171 
172         imguiEndScrollArea();
173     }
174 
175     /**
176         This tells OpenGL what area of the available area we are
177         rendering to. In this case, we change it to match the
178         full available area. Without this function call resizing
179         the window would have no effect on the rendering.
180     */
181     void onWindowResize(int width, int height)
182     {
183         // bottom-left position.
184         enum int x = 0;
185         enum int y = 0;
186 
187         /**
188             This function defines the current viewport transform.
189             It defines as a region of the window, specified by the
190             bottom-left position and a width/height.
191 
192             Note about the viewport transform:
193             It is the process of transforming vertex data from normalized
194             device coordinate space to window space. It specifies the
195             viewable region of a window.
196         */
197         glfwGetFramebufferSize(window.window, &width, &height);
198         glViewport(x, y, width, height);
199 
200         windowWidth = width;
201         windowHeight = height;
202     }
203 
204     void onScroll(double hOffset, double vOffset)
205     {
206         mouseScroll = -cast(int)vOffset;
207     }
208 
209 private:
210     Window window;
211     int windowWidth;
212     int windowHeight;
213 
214     int scrollArea1 = 0;
215     int scrollArea2 = 0;
216     int scrollArea3 = 0;
217     int scrollArea4 = 0;
218     int mouseScroll = 0;
219 }
220 
221 int main(string[] args)
222 {
223     int width = 1024, height = 768;
224 
225     auto window = createWindow("imgui", WindowMode.windowed, width, height);
226 
227     GUI gui = GUI(window);
228 
229     glfwSwapInterval(1);
230 
231     string fontPath = thisExePath().dirName().buildPath("../").buildPath("DroidSans.ttf");
232 
233     enforce(imguiInit(fontPath));
234 
235     glClearColor(0.8f, 0.8f, 0.8f, 1.0f);
236     glEnable(GL_BLEND);
237     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
238     glDisable(GL_DEPTH_TEST);
239 
240     while (!glfwWindowShouldClose(window.window))
241     {
242         gui.render();
243 
244         /* Swap front and back buffers. */
245         window.swap_buffers();
246 
247         /* Poll for and process events. */
248         glfwPollEvents();
249 
250         if (window.is_key_down(GLFW_KEY_ESCAPE))
251             glfwSetWindowShouldClose(window.window, true);
252     }
253 
254     // Clean UI
255     imguiDestroy();
256 
257     return 0;
258 }