1 //------------------------------------------------------------------------------
2 //  droptest.d
3 //  Test drag'n'drop file loading.
4 //------------------------------------------------------------------------------
5 
6 module examples.droptest;
7 
8 import sg = sokol.gfx;
9 import sgapp = sokol.glue;
10 import sapp = sokol.app;
11 import sfetch = sokol.fetch;
12 import imgui = sokol.imgui;
13 import log = sokol.log;
14 
15 enum MAX_FILE_SIZE = 1024 * 1024;
16 
17 enum LoadState
18 {
19     Unknown = 0,
20     Success,
21     Failed,
22     FileTooBig,
23 }
24 
25 struct State
26 {
27     LoadState load_state = LoadState.Unknown;
28     uint size = 0;
29     ubyte[MAX_FILE_SIZE] buffer = 0;
30 }
31 
32 static State state;
33 
34 extern (C) void init() @safe @nogc nothrow
35 {
36     sg.Desc gfx = {
37         environment: sgapp.environment,
38         logger: {func: &log.slog_func}
39     };
40     sg.setup(gfx);
41     imgui.Desc imgui_desc = {0};
42     imgui.setup(imgui_desc);
43 
44     // ifndef emscripten
45     static if((){version(Emscripten) return false; else return true;}())
46     {
47         sfetch.Desc fetch_desc = {
48             num_channels: 1,
49             num_lanes: 1,
50             logger: {func: &log.slog_func},
51         };
52         sfetch.setup(fetch_desc);
53     }
54 }
55 
56 extern (C) void frame() @trusted @nogc nothrow
57 {
58     // ifndef emscripten
59     static if((){version(Emscripten) return false;else return true;}())
60     {
61         sfetch.dowork;
62     }
63     imgui.FrameDesc imgui_desc = {
64         width: sapp.width(),
65         height: sapp.height(),
66         delta_time: sapp.frameDuration(),
67         dpi_scale: sapp.dpiScale(),
68     };
69     imgui.newFrame(imgui_desc);
70 
71     // /*=== UI CODE STARTS HERE ===*/
72     const ImVec2 window_pos = {10, 10};
73     const ImVec2 window_pos_pivot = {0, 0};
74     const ImVec2 window_size = {600, 500};
75     igSetNextWindowPos(window_pos, ImGuiCond_.ImGuiCond_Once, window_pos_pivot);
76     igSetNextWindowSize(window_size, ImGuiCond_.ImGuiCond_Once);
77     igBegin("Drop a file!".ptr, null, ImGuiWindowFlags_.ImGuiWindowFlags_None);
78     if(state.load_state != LoadState.Unknown)
79     {
80         igText("%s:", sapp.getDroppedFilePath(0));
81     }
82     switch (state.load_state) {
83         case LoadState.Failed:
84             igText("LOAD FAILED!");
85             break;
86         case LoadState.FileTooBig:
87             igText("FILE TOO BIG!");
88             break;
89         case LoadState.Success:
90             igSeparator;
91             renderFileContent;
92             break;
93         default:
94             break;
95     }
96     igEnd();
97     /*=== UI CODE ENDS HERE ===*/
98 
99     sg.Pass pass = {swapchain: sgapp.swapchain};
100     sg.beginPass(pass);
101     imgui.render;
102     sg.endPass;
103     sg.commit;
104 }
105 
106 extern (C) void event(const(sapp.Event)* ev) @trusted @nogc nothrow
107 {
108     imgui.simgui_handle_event(ev);
109     if (ev.type == sapp.EventType.Files_dropped)
110     {
111         version (Emscripten)
112         {
113             // on emscripten need to use the sokol-app helper function to load the file data
114             sapp.Html5FetchRequest req = {
115                 dropped_file_index: 0,
116                 callback: &emsc_load_callback,
117                 buffer: {ptr: &state.buffer[0], size: state.buffer.sizeof}
118             };
119             sapp.html5FetchDroppedFile(req);
120         }
121         else
122         {
123             // native platform: use sokol-fetch to load file content
124             sfetch.Request req = {
125                 path: sapp.getDroppedFilePath(0),
126                 callback: &native_load_callback,
127                 buffer: {ptr: &state.buffer[0], size: state.buffer.sizeof}
128             };
129             sfetch.send(req);
130         }
131     }
132 }
133 
134 extern (C) void cleanup() @safe @nogc nothrow
135 {
136     // ifndef emscripten
137     static if((){version(Emscripten) return false;else return true;}())
138     {
139         sfetch.shutdown;
140     }
141     imgui.shutdown;
142     sg.shutdown;
143 }
144 
145 extern (C) void main() @safe @nogc nothrow
146 {
147     sapp.Desc runner = {
148         window_title: "droptest.d",
149         init_cb: &init,
150         frame_cb: &frame,
151         cleanup_cb: &cleanup,
152         event_cb: &event,
153         width: 800,
154         height: 600,
155         enable_dragndrop: true,
156         max_dropped_files: 1,
157         icon: {sokol_default: true},
158         logger: {func: &log.func}
159     };
160     sapp.run(runner);
161 }
162 
163 void renderFileContent() @nogc nothrow
164 {
165     immutable int bytes_per_line = 16; // keep this 2^N
166     immutable int num_lines = (state.size + (bytes_per_line - 1)) / bytes_per_line;
167     ImVec2 sz = {0, 0};
168     igBeginChild_Str("##scrolling", sz, false, ImGuiWindowFlags_.ImGuiWindowFlags_NoMove | ImGuiWindowFlags_
169             .ImGuiWindowFlags_NoNav);
170     ImGuiListClipper* clipper = ImGuiListClipper_ImGuiListClipper();
171     ImGuiListClipper_Begin(clipper, num_lines, igGetTextLineHeight());
172     ImGuiListClipper_Step(clipper);
173     for (int line_i = clipper.DisplayStart; line_i < clipper.DisplayEnd; line_i++)
174     {
175         int start_offset = line_i * bytes_per_line;
176         int end_offset = start_offset + bytes_per_line;
177         if (end_offset >= state.size)
178         {
179             end_offset = state.size;
180         }
181         igText("%04X: ", start_offset);
182         for (int i = start_offset; i < end_offset; i++)
183         {
184             igSameLine(0.0f, 0.0f);
185             igText("%02X ", state.buffer[i]);
186         }
187         igSameLine((6 * 7.0f) + (bytes_per_line * 3 * 7.0f) + (2 * 7.0f), 0.0f);
188         for (int i = start_offset; i < end_offset; i++)
189         {
190             if (i != start_offset)
191             {
192                 igSameLine(0.0f, 0.0f);
193             }
194             ubyte c = state.buffer[i];
195             if ((c < 32) || (c > 127))
196             {
197                 c = '.';
198             }
199             igText("%c", c);
200         }
201     }
202     igText("EOF\n");
203     ImGuiListClipper_End(clipper);
204     ImGuiListClipper_destroy(clipper);
205     igEndChild();
206 }
207 
208 version (Emscripten)
209 {
210     // the async-loading callback for sapp_html5_fetch_dropped_file
211     extern (C) void emsc_load_callback(const (sapp.Html5FetchResponse*) response) @nogc nothrow
212     {
213         if (response.succeeded)
214         {
215             state.load_state = LoadState.Success;
216             state.size = cast(uint) response.data.size;
217         }
218         else if (response.error_code == sapp.Html5FetchError.Fetch_error_buffer_too_small)
219         {
220             state.load_state = LoadState.FileTooBig;
221         }
222         else
223         {
224             state.load_state = LoadState.Failed;
225         }
226     }
227 }
228 else
229 {
230     // the async-loading callback for native platforms
231     extern (C) void native_load_callback(const (sfetch.Response*) response) @nogc nothrow
232     {
233         if (response.fetched)
234         {
235             state.load_state = LoadState.Success;
236             state.size = cast(uint) response.data.size;
237         }
238         else if (response.error_code == sfetch.Error.Buffer_too_small)
239         {
240             state.load_state = LoadState.FileTooBig;
241         }
242         else
243         {
244             state.load_state = LoadState.Failed;
245         }
246     }
247 }
248 
249 // -- CIMGUI -- //
250 @nogc nothrow:
251 extern (C) bool igBeginChild_Str(scope const(char)* str_id, const ImVec2 size, bool border, ImGuiWindowFlags flags);
252 extern (C) bool igBegin(const(char)* name, scope bool* p_open, ImGuiWindowFlags flags);
253 extern (C) void igEnd();
254 extern (C) void igSetNextWindowPos(const ImVec2 pos, ImGuiCond cond, const ImVec2 pivot);
255 extern (C) void igSetNextWindowSize(const ImVec2 size, ImGuiCond cond);
256 extern (C) void igText(scope const(char)* fmt, ...);
257 extern (C) void igSameLine(float offset_from_start_x, float spacing);
258 extern (C) scope ImGuiListClipper* ImGuiListClipper_ImGuiListClipper();
259 extern (C) void ImGuiListClipper_destroy(scope ImGuiListClipper* self);
260 extern (C) void ImGuiListClipper_Begin(scope ImGuiListClipper* self, int items_count, float items_height);
261 extern (C) void ImGuiListClipper_End(scope ImGuiListClipper* self);
262 extern (C) void igEndChild();
263 extern (C) void igSeparator();
264 extern (C) float igGetTextLineHeight();
265 extern (C) bool ImGuiListClipper_Step(scope ImGuiListClipper* self);
266 
267 extern (C):
268 struct ImVec2
269 {
270     float x = 0.0f;
271     float y = 0.0f;
272 }
273 
274 enum ImGuiColorEditFlags_
275 {
276     ImGuiColorEditFlags_None = 0,
277     ImGuiColorEditFlags_NoAlpha = 1 << 1,
278     ImGuiColorEditFlags_NoPicker = 1 << 2,
279     ImGuiColorEditFlags_NoOptions = 1 << 3,
280     ImGuiColorEditFlags_NoSmallPreview = 1 << 4,
281     ImGuiColorEditFlags_NoInputs = 1 << 5,
282     ImGuiColorEditFlags_NoTooltip = 1 << 6,
283     ImGuiColorEditFlags_NoLabel = 1 << 7,
284     ImGuiColorEditFlags_NoSidePreview = 1 << 8,
285     ImGuiColorEditFlags_NoDragDrop = 1 << 9,
286     ImGuiColorEditFlags_NoBorder = 1 << 10,
287     ImGuiColorEditFlags_AlphaBar = 1 << 16,
288     ImGuiColorEditFlags_AlphaPreview = 1 << 17,
289     ImGuiColorEditFlags_AlphaPreviewHalf = 1 << 18,
290     ImGuiColorEditFlags_HDR = 1 << 19,
291     ImGuiColorEditFlags_DisplayRGB = 1 << 20,
292     ImGuiColorEditFlags_DisplayHSV = 1 << 21,
293     ImGuiColorEditFlags_DisplayHex = 1 << 22,
294     ImGuiColorEditFlags_Uint8 = 1 << 23,
295     ImGuiColorEditFlags_Float = 1 << 24,
296     ImGuiColorEditFlags_PickerHueBar = 1 << 25,
297     ImGuiColorEditFlags_PickerHueWheel = 1 << 26,
298     ImGuiColorEditFlags_InputRGB = 1 << 27,
299     ImGuiColorEditFlags_InputHSV = 1 << 28,
300     ImGuiColorEditFlags_DefaultOptions_
301         = ImGuiColorEditFlags_Uint8 | ImGuiColorEditFlags_DisplayRGB
302         | ImGuiColorEditFlags_InputRGB | ImGuiColorEditFlags_PickerHueBar,
303     ImGuiColorEditFlags_DisplayMask_ = ImGuiColorEditFlags_DisplayRGB
304         | ImGuiColorEditFlags_DisplayHSV | ImGuiColorEditFlags_DisplayHex,
305     ImGuiColorEditFlags_DataTypeMask_ = ImGuiColorEditFlags_Uint8 | ImGuiColorEditFlags_Float,
306     ImGuiColorEditFlags_PickerMask_
307         = ImGuiColorEditFlags_PickerHueWheel | ImGuiColorEditFlags_PickerHueBar,
308     ImGuiColorEditFlags_InputMask_ = ImGuiColorEditFlags_InputRGB | ImGuiColorEditFlags_InputHSV
309 }
310 
311 enum ImGuiWindowFlags_
312 {
313     ImGuiWindowFlags_None = 0,
314     ImGuiWindowFlags_NoTitleBar = 1 << 0,
315     ImGuiWindowFlags_NoResize = 1 << 1,
316     ImGuiWindowFlags_NoMove = 1 << 2,
317     ImGuiWindowFlags_NoScrollbar = 1 << 3,
318     ImGuiWindowFlags_NoScrollWithMouse = 1 << 4,
319     ImGuiWindowFlags_NoCollapse = 1 << 5,
320     ImGuiWindowFlags_AlwaysAutoResize = 1 << 6,
321     ImGuiWindowFlags_NoBackground = 1 << 7,
322     ImGuiWindowFlags_NoSavedSettings = 1 << 8,
323     ImGuiWindowFlags_NoMouseInputs = 1 << 9,
324     ImGuiWindowFlags_MenuBar = 1 << 10,
325     ImGuiWindowFlags_HorizontalScrollbar = 1 << 11,
326     ImGuiWindowFlags_NoFocusOnAppearing = 1 << 12,
327     ImGuiWindowFlags_NoBringToFrontOnFocus = 1 << 13,
328     ImGuiWindowFlags_AlwaysVerticalScrollbar = 1 << 14,
329     ImGuiWindowFlags_AlwaysHorizontalScrollbar = 1 << 15,
330     ImGuiWindowFlags_AlwaysUseWindowPadding = 1 << 16,
331     ImGuiWindowFlags_NoNavInputs = 1 << 18,
332     ImGuiWindowFlags_NoNavFocus = 1 << 19,
333     ImGuiWindowFlags_UnsavedDocument = 1 << 20,
334     ImGuiWindowFlags_NoNav = ImGuiWindowFlags_NoNavInputs | ImGuiWindowFlags_NoNavFocus,
335     ImGuiWindowFlags_NoDecoration = ImGuiWindowFlags_NoTitleBar
336         | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoCollapse,
337     ImGuiWindowFlags_NoInputs = ImGuiWindowFlags_NoMouseInputs
338         | ImGuiWindowFlags_NoNavInputs | ImGuiWindowFlags_NoNavFocus,
339     ImGuiWindowFlags_NavFlattened = 1 << 23,
340     ImGuiWindowFlags_ChildWindow = 1 << 24,
341     ImGuiWindowFlags_Tooltip = 1 << 25,
342     ImGuiWindowFlags_Popup = 1 << 26,
343     ImGuiWindowFlags_Modal = 1 << 27,
344     ImGuiWindowFlags_ChildMenu = 1 << 28
345 }
346 
347 enum ImGuiCond_
348 {
349     ImGuiCond_None = 0,
350     ImGuiCond_Always = 1 << 0,
351     ImGuiCond_Once = 1 << 1,
352     ImGuiCond_FirstUseEver = 1 << 2,
353     ImGuiCond_Appearing = 1 << 3
354 }
355 
356 alias ImGuiCond = int;
357 alias ImGuiColorEditFlags = int;
358 alias ImGuiWindowFlags = int;
359 struct ImGuiListClipper
360 {
361     ImGuiContext* Ctx = null;
362     int DisplayStart = 0;
363     int DisplayEnd = 0;
364     int ItemsCount = 0;
365     float ItemsHeight = 0.0f;
366     float StartPosY = 0.0f;
367     void* TempData = null;
368 }
369 
370 struct ImGuiContext;