1 //------------------------------------------------------------------------------
2 //  droptest.d
3 //  Test drag'n'drop file loading.
4 //------------------------------------------------------------------------------
5 
6 module examples.droptest;
7 
8 private:
9 
10 import sg = sokol.gfx;
11 import sgapp = sokol.glue;
12 import sapp = sokol.app;
13 import sfetch = sokol.fetch;
14 import simgui = sokol.imgui;
15 import log = sokol.log;
16 import imgui;
17 
18 enum MAX_FILE_SIZE = 1024 * 1024;
19 
20 enum LoadState
21 {
22     Unknown = 0,
23     Success,
24     Failed,
25     FileTooBig,
26 }
27 
28 struct State
29 {
30     LoadState load_state = LoadState.Unknown;
31     uint size = 0;
32     ubyte[MAX_FILE_SIZE] buffer = 0;
33 }
34 
35 static State state;
36 
37 extern (C) void init() @safe @nogc nothrow
38 {
39     sg.Desc gfx = {
40         environment: sgapp.environment,
41         logger: {func: &log.slog_func}
42     };
43     sg.setup(gfx);
44     simgui.Desc imgui_desc = {0};
45     simgui.setup(imgui_desc);
46 
47     // ifndef emscripten
48     // dfmt off
49     static if((){version(Emscripten) return false; else return true;}())
50     {
51         sfetch.Desc fetch_desc = {
52             num_channels: 1,
53             num_lanes: 1,
54             logger: {func: &log.slog_func},
55         };
56         sfetch.setup(fetch_desc);
57     }
58     // dfmt on
59 }
60 
61 extern (C) void frame() @trusted
62 {
63     // ifndef emscripten
64     // dfmt off
65     static if((){version(Emscripten) return false;else return true;}())
66     {
67         sfetch.dowork;
68     }
69     simgui.FrameDesc imgui_desc = {
70         width: sapp.width(),
71         height: sapp.height(),
72         delta_time: sapp.frameDuration(),
73         dpi_scale: sapp.dpiScale(),
74     };
75     // dfmt on
76     simgui.newFrame(imgui_desc);
77 
78     // /*=== UI CODE STARTS HERE ===*/
79     const ImVec2 window_pos = {10, 10};
80     const ImVec2 window_size = {600, 500};
81     SetNextWindowPos(window_pos, ImGuiCond_.ImGuiCond_Once);
82     SetNextWindowSize(window_size, ImGuiCond_.ImGuiCond_Once);
83     Begin("Drop a file!".ptr, null, ImGuiWindowFlags_.ImGuiWindowFlags_None);
84     if (state.load_state != LoadState.Unknown)
85     {
86         Text("%s:", sapp.getDroppedFilePath(0));
87     }
88     switch (state.load_state)
89     {
90     case LoadState.Failed:
91         Text("LOAD FAILED!");
92         break;
93     case LoadState.FileTooBig:
94         Text("FILE TOO BIG!");
95         break;
96     case LoadState.Success:
97         Separator;
98         renderFileContent;
99         break;
100     default:
101         break;
102     }
103     End();
104     /*=== UI CODE ENDS HERE ===*/
105 
106     sg.Pass pass = {swapchain: sgapp.swapchain};
107     sg.beginPass(pass);
108     simgui.render;
109     sg.endPass;
110     sg.commit;
111 }
112 
113 extern (C) void event(const(sapp.Event)* ev) @trusted @nogc nothrow
114 {
115     simgui.simgui_handle_event(ev);
116     if (ev.type == sapp.EventType.Files_dropped)
117     {
118         version (Emscripten)
119         {
120             // on emscripten need to use the sokol-app helper function to load the file data
121             sapp.Html5FetchRequest req = {
122                 dropped_file_index: 0,
123                 callback: &emsc_load_callback,
124                 buffer: {ptr: &state.buffer[0], size: state.buffer.sizeof}};
125                 sapp.html5FetchDroppedFile(req);
126             }
127         else
128             {
129                 // native platform: use sokol-fetch to load file content
130                 sfetch.Request req = {
131                     path: sapp.getDroppedFilePath(0),
132                     callback: &native_load_callback,
133                     buffer: {ptr: &state.buffer[0], size: state.buffer.sizeof}
134             };
135             sfetch.send(req);
136         }
137     }
138 }
139 
140 extern (C) void cleanup() @safe @nogc nothrow
141 {
142     // ifndef emscripten
143     // dfmt off
144     static if((){version(Emscripten) return false;else return true;}())
145     {
146         sfetch.shutdown;
147     }
148     // dfmt on
149     simgui.shutdown;
150     sg.shutdown;
151 }
152 
153 extern (C) void main() @safe @nogc nothrow
154 {
155     // dfmt off
156     sapp.Desc runner = {
157         window_title: "droptest.d",
158         init_cb: &init,
159         frame_cb: &frame,
160         cleanup_cb: &cleanup,
161         event_cb: &event,
162         width: 800,
163         height: 600,
164         enable_dragndrop: true,
165         max_dropped_files: 1,
166         icon: {sokol_default: true},
167         logger: {func: &log.func}
168     };
169     // dfmt on
170     sapp.run(runner);
171 }
172 
173 void renderFileContent()
174 {
175     immutable int bytes_per_line = 16; // keep this 2^N
176     immutable int num_lines = (state.size + (bytes_per_line - 1)) / bytes_per_line;
177     ImVec2 sz = {0, 0};
178     BeginChild("##scrolling", sz, false, ImGuiWindowFlags_
179             .ImGuiWindowFlags_NoMove | ImGuiWindowFlags_
180             .ImGuiWindowFlags_NoNav);
181     ImGuiListClipper* clipper = null;
182     ImGuiListClipper_Begin(clipper, num_lines, GetTextLineHeight());
183     ImGuiListClipper_Step(clipper);
184     for (int line_i = clipper.DisplayStart; line_i < clipper.DisplayEnd; line_i++)
185     {
186         int start_offset = line_i * bytes_per_line;
187         int end_offset = start_offset + bytes_per_line;
188         if (end_offset >= state.size)
189         {
190             end_offset = state.size;
191         }
192         Text("%04X: ", start_offset);
193         for (int i = start_offset; i < end_offset; i++)
194         {
195             SameLineEx(0.0f, 0.0f);
196             Text("%02X ", state.buffer[i]);
197         }
198         SameLineEx((6 * 7.0f) + (bytes_per_line * 3 * 7.0f) + (2 * 7.0f), 0.0f);
199         for (int i = start_offset; i < end_offset; i++)
200         {
201             if (i != start_offset)
202             {
203                 SameLineEx(0.0f, 0.0f);
204             }
205             ubyte c = state.buffer[i];
206             if ((c < 32) || (c > 127))
207             {
208                 c = '.';
209             }
210             Text("%c", c);
211         }
212     }
213     Text("EOF\n");
214     ImGuiListClipper_End(clipper);
215     EndChild();
216 }
217 
218 version (Emscripten)
219 {
220     // the async-loading callback for sapp_html5_fetch_dropped_file
221     extern (C) void emsc_load_callback(const(sapp.Html5FetchResponse*) response) @nogc nothrow
222     {
223         if (response.succeeded)
224         {
225             state.load_state = LoadState.Success;
226             state.size = cast(uint) response.data.size;
227         }
228         else if (response.error_code == sapp.Html5FetchError.Fetch_error_buffer_too_small)
229         {
230             state.load_state = LoadState.FileTooBig;
231         }
232         else
233         {
234             state.load_state = LoadState.Failed;
235         }
236     }
237 }
238 else
239 {
240     // the async-loading callback for native platforms
241     extern (C) void native_load_callback(const(sfetch.Response*) response) @nogc nothrow
242     {
243         if (response.fetched)
244         {
245             state.load_state = LoadState.Success;
246             state.size = cast(uint) response.data.size;
247         }
248         else if (response.error_code == sfetch.Error.Buffer_too_small)
249         {
250             state.load_state = LoadState.FileTooBig;
251         }
252         else
253         {
254             state.load_state = LoadState.Failed;
255         }
256     }
257 }