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         glfwGetWindowSize(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         glViewport(x, y, width, height);
198 
199         windowWidth = width;
200         windowHeight = height;
201     }
202 
203     void onScroll(double hOffset, double vOffset)
204     {
205         mouseScroll = -cast(int)vOffset;
206     }
207 
208 private:
209     Window window;
210     int windowWidth;
211     int windowHeight;
212 
213     int scrollArea1 = 0;
214     int scrollArea2 = 0;
215     int scrollArea3 = 0;
216     int scrollArea4 = 0;
217     int mouseScroll = 0;
218 }
219 
220 int main(string[] args)
221 {
222     int width = 1024, height = 768;
223 
224     auto window = createWindow("imgui", WindowMode.windowed, width, height);
225 
226     GUI gui = GUI(window);
227 
228     glfwSwapInterval(1);
229 
230     string fontPath = thisExePath().dirName().buildPath("../").buildPath("DroidSans.ttf");
231 
232     enforce(imguiInit(fontPath));
233 
234     glClearColor(0.8f, 0.8f, 0.8f, 1.0f);
235     glEnable(GL_BLEND);
236     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
237     glDisable(GL_DEPTH_TEST);
238 
239     while (!glfwWindowShouldClose(window.window))
240     {
241         gui.render();
242 
243         /* Swap front and back buffers. */
244         window.swap_buffers();
245 
246         /* Poll for and process events. */
247         glfwPollEvents();
248 
249         if (window.is_key_down(GLFW_KEY_ESCAPE))
250             glfwSetWindowShouldClose(window.window, true);
251     }
252 
253     // Clean UI
254     imguiDestroy();
255 
256     return 0;
257 }