1 | /********************************************************************** |
2 | * |
3 | * simple Picture Viewer V. 0.9 |
4 | * |
5 | * Copyright (c) 2021 Dario Deledda. All rights reserved. |
6 | * Use of this source code is governed by an MIT license |
7 | * that can be found in the LICENSE file. |
8 | * |
9 | * TODO: |
10 | * - add an example with shaders |
11 | **********************************************************************/ |
12 | import os |
13 | import gg |
14 | import gx |
15 | import sokol.gfx |
16 | import sokol.sgl |
17 | import sokol.sapp |
18 | import stbi |
19 | import szip |
20 | import strings |
21 | |
22 | // Help text |
23 | const ( |
24 | help_text_rows = [ |
25 | 'Image Viewer 0.9 help.', |
26 | '', |
27 | 'ESC/q - Quit', |
28 | 'cur. right - Next image', |
29 | 'cur. left - Previous image', |
30 | 'cur. up - Next folder', |
31 | 'cur. down - Previous folder', |
32 | 'F - Toggle full screen', |
33 | 'R - Rotate image of 90 degree', |
34 | 'I - Toggle the info text', |
35 | '', |
36 | 'mouse wheel - next/previous images', |
37 | 'keep pressed left Mouse button - Pan on the image', |
38 | 'keep pressed right Mouse button - Zoom on the image', |
39 | ] |
40 | ) |
41 | |
42 | const ( |
43 | win_width = 800 |
44 | win_height = 800 |
45 | bg_color = gx.black |
46 | pi_2 = 3.14159265359 / 2.0 |
47 | uv = [f32(0), 0, 1, 0, 1, 1, 0, 1]! // used for zoom icon during rotations |
48 | |
49 | text_drop_files = 'Drop here some images/folder/zip to navigate in the pics' |
50 | text_scanning = 'Scanning...' |
51 | text_loading = 'Loading...' |
52 | ) |
53 | |
54 | enum Viewer_state { |
55 | loading |
56 | scanning |
57 | show |
58 | error |
59 | } |
60 | |
61 | struct App { |
62 | mut: |
63 | gg &gg.Context = unsafe { nil } |
64 | pip_viewer sgl.Pipeline |
65 | texture gfx.Image |
66 | init_flag bool |
67 | frame_count int |
68 | mouse_x int = -1 |
69 | mouse_y int = -1 |
70 | scroll_y int |
71 | |
72 | state Viewer_state = .scanning |
73 | // translation |
74 | tr_flag bool |
75 | tr_x f32 = 0.0 |
76 | tr_y f32 = 0.0 |
77 | last_tr_x f32 = 0.0 |
78 | last_tr_y f32 = 0.0 |
79 | // scaling |
80 | sc_flag bool |
81 | scale f32 = 1.0 |
82 | sc_x f32 = 0.0 |
83 | sc_y f32 = 0.0 |
84 | last_sc_x f32 = 0.0 |
85 | last_sc_y f32 = 0.0 |
86 | // loaded image |
87 | img_w int |
88 | img_h int |
89 | img_ratio f32 = 1.0 |
90 | // item list |
91 | item_list &Item_list = unsafe { nil } |
92 | // Text info and help |
93 | show_info_flag bool = true |
94 | show_help_flag bool |
95 | // zip container |
96 | zip &szip.Zip = unsafe { nil } // pointer to the szip structure |
97 | zip_index int = -1 // index of the zip container item |
98 | // memory buffer |
99 | mem_buf voidptr // buffer used to load items from files/containers |
100 | mem_buf_size int // size of the buffer |
101 | // font |
102 | font_path string // path to the temp font file |
103 | // logo |
104 | logo_path string // path of the temp font logo |
105 | logo_texture gfx.Image |
106 | logo_w int |
107 | logo_h int |
108 | logo_ratio f32 = 1.0 |
109 | // string builder |
110 | bl strings.Builder = strings.new_builder(512) |
111 | } |
112 | |
113 | /****************************************************************************** |
114 | * |
115 | * Texture functions |
116 | * |
117 | ******************************************************************************/ |
118 | fn create_texture(w int, h int, buf &u8) gfx.Image { |
119 | sz := w * h * 4 |
120 | mut img_desc := gfx.ImageDesc{ |
121 | width: w |
122 | height: h |
123 | num_mipmaps: 0 |
124 | min_filter: .linear |
125 | mag_filter: .linear |
126 | // usage: .dynamic |
127 | wrap_u: .clamp_to_edge |
128 | wrap_v: .clamp_to_edge |
129 | label: &u8(0) |
130 | d3d11_texture: 0 |
131 | } |
132 | // comment if .dynamic is enabled |
133 | img_desc.data.subimage[0][0] = gfx.Range{ |
134 | ptr: buf |
135 | size: usize(sz) |
136 | } |
137 | |
138 | sg_img := gfx.make_image(&img_desc) |
139 | return sg_img |
140 | } |
141 | |
142 | fn destroy_texture(sg_img gfx.Image) { |
143 | gfx.destroy_image(sg_img) |
144 | } |
145 | |
146 | // Use only if: .dynamic is enabled |
147 | fn update_text_texture(sg_img gfx.Image, w int, h int, buf &u8) { |
148 | sz := w * h * 4 |
149 | mut tmp_sbc := gfx.ImageData{} |
150 | tmp_sbc.subimage[0][0] = gfx.Range{ |
151 | ptr: buf |
152 | size: usize(sz) |
153 | } |
154 | gfx.update_image(sg_img, &tmp_sbc) |
155 | } |
156 | |
157 | /****************************************************************************** |
158 | * |
159 | * Memory buffer |
160 | * |
161 | ******************************************************************************/ |
162 | [inline] |
163 | fn (mut app App) resize_buf_if_needed(in_size int) { |
164 | // manage the memory buffer |
165 | if app.mem_buf_size < in_size { |
166 | println('Managing FILE memory buffer, allocated [${in_size}]Bytes') |
167 | // free previous buffer if any exist |
168 | if app.mem_buf_size > 0 { |
169 | unsafe { |
170 | free(app.mem_buf) |
171 | } |
172 | } |
173 | // allocate the memory |
174 | unsafe { |
175 | app.mem_buf = malloc(int(in_size)) |
176 | app.mem_buf_size = int(in_size) |
177 | } |
178 | } |
179 | } |
180 | |
181 | /****************************************************************************** |
182 | * |
183 | * Loading functions |
184 | * |
185 | ******************************************************************************/ |
186 | // read_bytes from file in `path` in the memory buffer of app. |
187 | [manualfree] |
188 | fn (mut app App) read_bytes(path string) bool { |
189 | mut fp := os.vfopen(path, 'rb') or { |
190 | eprintln('ERROR: Can not open the file [${path}].') |
191 | return false |
192 | } |
193 | defer { |
194 | C.fclose(fp) |
195 | } |
196 | cseek := C.fseek(fp, 0, C.SEEK_END) |
197 | if cseek != 0 { |
198 | eprintln('ERROR: Can not seek in the file [${path}].') |
199 | return false |
200 | } |
201 | fsize := C.ftell(fp) |
202 | if fsize < 0 { |
203 | eprintln('ERROR: File [${path}] has size is 0.') |
204 | return false |
205 | } |
206 | C.rewind(fp) |
207 | |
208 | app.resize_buf_if_needed(int(fsize)) |
209 | |
210 | nr_read_elements := int(C.fread(app.mem_buf, fsize, 1, fp)) |
211 | if nr_read_elements == 0 && fsize > 0 { |
212 | eprintln('ERROR: Can not read the file [${path}] in the memory buffer.') |
213 | return false |
214 | } |
215 | return true |
216 | } |
217 | |
218 | // read a file as []u8 |
219 | pub fn read_bytes_from_file(file_path string) []u8 { |
220 | mut buffer := []u8{} |
221 | buffer = os.read_bytes(file_path) or { |
222 | eprintln('ERROR: Texure file: [${file_path}] NOT FOUND.') |
223 | exit(0) |
224 | } |
225 | return buffer |
226 | } |
227 | |
228 | fn (mut app App) load_texture_from_buffer(buf voidptr, buf_len int) (gfx.Image, int, int) { |
229 | // load image |
230 | stbi.set_flip_vertically_on_load(true) |
231 | img := stbi.load_from_memory(buf, buf_len) or { |
232 | eprintln('ERROR: Can not load image from buffer, file: [${app.item_list.lst[app.item_list.item_index]}].') |
233 | return app.logo_texture, app.logo_w, app.logo_h |
234 | // exit(1) |
235 | } |
236 | res := create_texture(int(img.width), int(img.height), img.data) |
237 | unsafe { |
238 | img.free() |
239 | } |
240 | return res, int(img.width), int(img.height) |
241 | } |
242 | |
243 | pub fn (mut app App) load_texture_from_file(file_name string) (gfx.Image, int, int) { |
244 | app.read_bytes(file_name) |
245 | return app.load_texture_from_buffer(app.mem_buf, app.mem_buf_size) |
246 | } |
247 | |
248 | pub fn show_logo(mut app App) { |
249 | clear_modifier_params(mut app) |
250 | if app.texture != app.logo_texture { |
251 | destroy_texture(app.texture) |
252 | } |
253 | app.texture = app.logo_texture |
254 | app.img_w = app.logo_w |
255 | app.img_h = app.logo_h |
256 | app.img_ratio = f32(app.img_w) / f32(app.img_h) |
257 | // app.gg.refresh_ui() |
258 | } |
259 | |
260 | pub fn load_image(mut app App) { |
261 | if app.item_list.loaded == false || app.init_flag == false { |
262 | // show_logo(mut app) |
263 | // app.state = .show |
264 | return |
265 | } |
266 | app.state = .loading |
267 | clear_modifier_params(mut app) |
268 | // destroy the texture, avoid to destroy the logo |
269 | if app.texture != app.logo_texture { |
270 | destroy_texture(app.texture) |
271 | } |
272 | |
273 | // load from .ZIP file |
274 | if app.item_list.is_inside_a_container() == true { |
275 | app.texture, app.img_w, app.img_h = app.load_texture_from_zip() or { |
276 | eprintln('ERROR: Can not load image from .ZIP file [${app.item_list.lst[app.item_list.item_index]}].') |
277 | show_logo(mut app) |
278 | app.state = .show |
279 | return |
280 | } |
281 | app.img_ratio = f32(app.img_w) / f32(app.img_h) |
282 | app.state = .show |
283 | // app.gg.refresh_ui() |
284 | return |
285 | } |
286 | |
287 | // if we are out of the zip, close it |
288 | if app.zip_index >= 0 { |
289 | app.zip_index = -1 |
290 | app.zip.close() |
291 | } |
292 | |
293 | file_path := app.item_list.get_file_path() |
294 | if file_path.len > 0 { |
295 | // println("${app.item_list.lst[app.item_list.item_index]} $file_path ${app.item_list.lst.len}") |
296 | app.texture, app.img_w, app.img_h = app.load_texture_from_file(file_path) |
297 | app.img_ratio = f32(app.img_w) / f32(app.img_h) |
298 | // println("texture: [${app.img_w},${app.img_h}] ratio: ${app.img_ratio}") |
299 | } else { |
300 | app.texture = app.logo_texture |
301 | app.img_w = app.logo_w |
302 | app.img_h = app.logo_h |
303 | app.img_ratio = f32(app.img_w) / f32(app.img_h) |
304 | println('texture NOT FOUND: use logo!') |
305 | } |
306 | app.state = .show |
307 | } |
308 | |
309 | /****************************************************************************** |
310 | * |
311 | * Init / Cleanup |
312 | * |
313 | ******************************************************************************/ |
314 | fn app_init(mut app App) { |
315 | app.init_flag = true |
316 | |
317 | // 3d pipeline |
318 | mut pipdesc := gfx.PipelineDesc{} |
319 | unsafe { vmemset(&pipdesc, 0, int(sizeof(pipdesc))) } |
320 | |
321 | color_state := gfx.ColorState{ |
322 | blend: gfx.BlendState{ |
323 | enabled: true |
324 | src_factor_rgb: .src_alpha |
325 | dst_factor_rgb: .one_minus_src_alpha |
326 | } |
327 | } |
328 | pipdesc.colors[0] = color_state |
329 | |
330 | pipdesc.depth = gfx.DepthState{ |
331 | write_enabled: true |
332 | compare: .less_equal |
333 | } |
334 | pipdesc.cull_mode = .back |
335 | app.pip_viewer = sgl.make_pipeline(&pipdesc) |
336 | |
337 | // load logo |
338 | app.logo_texture, app.logo_w, app.logo_h = app.load_texture_from_file(app.logo_path) |
339 | app.logo_ratio = f32(app.img_w) / f32(app.img_h) |
340 | |
341 | app.img_w = app.logo_w |
342 | app.img_h = app.logo_h |
343 | app.img_ratio = app.logo_ratio |
344 | app.texture = app.logo_texture |
345 | |
346 | println('INIT DONE!') |
347 | |
348 | // init done, load the first image if any |
349 | load_image(mut app) |
350 | } |
351 | |
352 | fn cleanup(mut app App) { |
353 | gfx.shutdown() |
354 | |
355 | // delete temp files |
356 | os.rm(app.font_path) or { eprintln('ERROR: Can not delete temp font file.') } |
357 | os.rm(app.logo_path) or { eprintln('ERROR: Can not delete temp logo file.') } |
358 | println('Cleaning done.') |
359 | } |
360 | |
361 | /****************************************************************************** |
362 | * |
363 | * Draw functions |
364 | * |
365 | ******************************************************************************/ |
366 | [manualfree] |
367 | fn frame(mut app App) { |
368 | ws := gg.window_size_real_pixels() |
369 | if ws.width <= 0 || ws.height <= 0 { |
370 | return |
371 | } |
372 | |
373 | mut ratio := f32(ws.width) / ws.height |
374 | dw := ws.width |
375 | dh := ws.height |
376 | |
377 | app.gg.begin() |
378 | sgl.defaults() |
379 | |
380 | // set viewport |
381 | sgl.viewport(0, 0, dw, dh, true) |
382 | |
383 | // enable our pipeline |
384 | sgl.load_pipeline(app.pip_viewer) |
385 | sgl.enable_texture() |
386 | sgl.texture(app.texture) |
387 | |
388 | // translation |
389 | tr_x := app.tr_x / app.img_w |
390 | tr_y := -app.tr_y / app.img_h |
391 | sgl.push_matrix() |
392 | sgl.translate(tr_x, tr_y, 0.0) |
393 | // scaling/zoom |
394 | sgl.scale(2.0 * app.scale, 2.0 * app.scale, 0.0) |
395 | // rotation |
396 | mut rotation := 0 |
397 | if app.state == .show && app.item_list.n_item > 0 { |
398 | rotation = app.item_list.lst[app.item_list.item_index].rotation |
399 | sgl.rotate(pi_2 * f32(rotation), 0.0, 0.0, -1.0) |
400 | } |
401 | |
402 | // draw the image |
403 | mut w := f32(0.5) |
404 | mut h := f32(0.5) |
405 | |
406 | // for 90 and 270 degree invert w and h |
407 | // rotation change image ratio, manage it |
408 | if rotation & 1 == 1 { |
409 | tmp := w |
410 | w = h |
411 | h = tmp |
412 | h /= app.img_ratio * ratio |
413 | } else { |
414 | h /= app.img_ratio / ratio |
415 | } |
416 | |
417 | // manage image overflow in case of strange scales |
418 | if h > 0.5 { |
419 | reduction_factor := 0.5 / h |
420 | h = h * reduction_factor |
421 | w = w * reduction_factor |
422 | } |
423 | if w > 0.5 { |
424 | reduction_factor := 0.5 / w |
425 | h = h * reduction_factor |
426 | w = w * reduction_factor |
427 | } |
428 | |
429 | // println("$w,$h") |
430 | // white multiplicator for now |
431 | mut c := [u8(255), 255, 255]! |
432 | sgl.begin_quads() |
433 | sgl.v2f_t2f_c3b(-w, -h, 0, 0, c[0], c[1], c[2]) |
434 | sgl.v2f_t2f_c3b(w, -h, 1, 0, c[0], c[1], c[2]) |
435 | sgl.v2f_t2f_c3b(w, h, 1, 1, c[0], c[1], c[2]) |
436 | sgl.v2f_t2f_c3b(-w, h, 0, 1, c[0], c[1], c[2]) |
437 | sgl.end() |
438 | |
439 | // restore all the transformations |
440 | sgl.pop_matrix() |
441 | |
442 | // Zoom icon |
443 | /* |
444 | if app.show_info_flag == true && app.scale > 1 { |
445 | mut bw := f32(0.25) |
446 | mut bh := f32(0.25 / app.img_ratio) |
447 | |
448 | // manage the rotations |
449 | if rotation & 1 == 1 { |
450 | bw,bh = bh,bw |
451 | } |
452 | mut bx := f32(1 - bw) |
453 | mut by := f32(1 - bh) |
454 | if rotation & 1 == 1 { |
455 | bx,by = by,bx |
456 | } |
457 | |
458 | bh_old1 := bh |
459 | bh *= ratio |
460 | by += (bh_old1 - bh) |
461 | |
462 | // draw the zoom icon |
463 | sgl.begin_quads() |
464 | r := int(u32(rotation) << 1) |
465 | sgl.v2f_t2f_c3b(bx , by , uv[(0 + r) & 7] , uv[(1 + r) & 7], c[0], c[1], c[2]) |
466 | sgl.v2f_t2f_c3b(bx + bw, by , uv[(2 + r) & 7] , uv[(3 + r) & 7], c[0], c[1], c[2]) |
467 | sgl.v2f_t2f_c3b(bx + bw, by + bh, uv[(4 + r) & 7] , uv[(5 + r) & 7], c[0], c[1], c[2]) |
468 | sgl.v2f_t2f_c3b(bx , by + bh, uv[(6 + r) & 7] , uv[(7 + r) & 7], c[0], c[1], c[2]) |
469 | sgl.end() |
470 | |
471 | // draw the zoom rectangle |
472 | sgl.disable_texture() |
473 | |
474 | bw_old := bw |
475 | bh_old := bh |
476 | bw /= app.scale |
477 | bh /= app.scale |
478 | bx += (bw_old - bw) / 2 - (tr_x / 8) / app.scale |
479 | by += (bh_old - bh) / 2 - ((tr_y / 8) / app.scale) * ratio |
480 | |
481 | c = [u8(255),255,0]! // yellow |
482 | sgl.begin_line_strip() |
483 | sgl.v2f_c3b(bx , by , c[0], c[1], c[2]) |
484 | sgl.v2f_c3b(bx + bw, by , c[0], c[1], c[2]) |
485 | sgl.v2f_c3b(bx + bw, by + bh, c[0], c[1], c[2]) |
486 | sgl.v2f_c3b(bx , by + bh, c[0], c[1], c[2]) |
487 | sgl.v2f_c3b(bx , by , c[0], c[1], c[2]) |
488 | sgl.end() |
489 | } |
490 | */ |
491 | sgl.disable_texture() |
492 | |
493 | // |
494 | // Draw info text |
495 | // |
496 | x := 10 |
497 | y := 10 |
498 | |
499 | app.gg.begin() |
500 | |
501 | if app.state in [.scanning, .loading] { |
502 | if app.state == .scanning { |
503 | draw_text(mut app, text_scanning, x, y, 20) |
504 | } else { |
505 | draw_text(mut app, text_loading, x, y, 20) |
506 | } |
507 | } else if app.state == .show { |
508 | // print the info text if needed |
509 | if app.item_list.n_item > 0 && app.show_info_flag == true { |
510 | /* |
511 | // waiting for better autofree |
512 | num := app.item_list.lst[app.item_list.item_index].n_item |
513 | of_num := app.item_list.n_item |
514 | x_screen := int(w*2*app.scale*dw) |
515 | y_screen := int(h*2*app.scale*dw) |
516 | rotation_angle := 90 * rotation |
517 | scale_str := "${app.scale:.2}" |
518 | text := "${num}/${of_num} [${app.img_w},${app.img_h}]=>[${x_screen},${y_screen}] ${app.item_list.lst[app.item_list.item_index].name} scale: ${scale_str} rotation: ${rotation_angle}" |
519 | //text := "${num}/${of_num}" |
520 | draw_text(mut app, text, 10, 10, 20) |
521 | unsafe{ |
522 | text.free() |
523 | } |
524 | */ |
525 | |
526 | // Using string builder to avoid memory leak |
527 | num := app.item_list.lst[app.item_list.item_index].n_item |
528 | of_num := app.item_list.n_item |
529 | x_screen := int(w * 2 * app.scale * dw) |
530 | y_screen := int(h * 2 * app.scale * dw) |
531 | rotation_angle := 90 * rotation |
532 | scale_str := '${app.scale:.2}' |
533 | app.bl.clear() |
534 | app.bl.write_string('${num}/${of_num}') |
535 | app.bl.write_string(' [${app.img_w}x${app.img_h}]=>[${x_screen}x${y_screen}]') |
536 | app.bl.write_string(' ${app.item_list.lst[app.item_list.item_index].name}') |
537 | app.bl.write_string(' scale: ${scale_str} rotation: ${rotation_angle}') |
538 | draw_text(mut app, app.bl.str(), 10, 10, 20) |
539 | } else { |
540 | if app.item_list.n_item <= 0 { |
541 | draw_text(mut app, text_drop_files, 10, 10, 20) |
542 | } |
543 | } |
544 | } |
545 | |
546 | // |
547 | // Draw Help text |
548 | // |
549 | if app.show_help_flag == true { |
550 | mut txt_y := 30 |
551 | for r in help_text_rows { |
552 | draw_text(mut app, r, 10, txt_y, 20) |
553 | txt_y += 20 |
554 | } |
555 | } |
556 | |
557 | app.gg.end() |
558 | app.frame_count++ |
559 | } |
560 | |
561 | // draw readable text |
562 | fn draw_text(mut app App, in_txt string, in_x int, in_y int, fnt_sz f32) { |
563 | scale := app.gg.scale |
564 | font_size := int(fnt_sz * scale) |
565 | |
566 | mut txt_conf_c0 := gx.TextCfg{ |
567 | color: gx.white // gx.rgb( (c >> 16) & 0xff, (c >> 8) & 0xff, c & 0xff) |
568 | align: .left |
569 | size: font_size |
570 | } |
571 | mut txt_conf_c1 := gx.TextCfg{ |
572 | color: gx.black // gx.rgb( (c >> 16) & 0xff, (c >> 8) & 0xff, c & 0xff) |
573 | align: .left |
574 | size: font_size |
575 | } |
576 | |
577 | x := int(in_x * scale) |
578 | y := int(in_y * scale) |
579 | app.gg.draw_text(x + 2, y + 2, in_txt, txt_conf_c0) |
580 | app.gg.draw_text(x, y, in_txt, txt_conf_c1) |
581 | } |
582 | |
583 | /****************************************************************************** |
584 | * |
585 | * events management |
586 | * |
587 | ******************************************************************************/ |
588 | fn clear_modifier_params(mut app App) { |
589 | app.scale = 1.0 |
590 | |
591 | app.sc_flag = false |
592 | app.sc_x = 0 |
593 | app.sc_y = 0 |
594 | app.last_sc_x = 0 |
595 | app.last_sc_y = 0 |
596 | |
597 | app.tr_flag = false |
598 | app.tr_x = 0 |
599 | app.tr_y = 0 |
600 | app.last_tr_x = 0 |
601 | app.last_tr_y = 0 |
602 | } |
603 | |
604 | fn my_event_manager(mut ev gg.Event, mut app App) { |
605 | // navigation using the mouse wheel |
606 | app.scroll_y = int(ev.scroll_y) |
607 | if app.scroll_y != 0 { |
608 | inc := int(-1 * app.scroll_y / 4) |
609 | if app.item_list.n_item > 0 { |
610 | app.item_list.get_next_item(inc) |
611 | load_image(mut app) |
612 | } |
613 | } |
614 | |
615 | if ev.typ == .mouse_move { |
616 | app.mouse_x = int(ev.mouse_x) |
617 | app.mouse_y = int(ev.mouse_y) |
618 | } |
619 | if ev.typ == .touches_began || ev.typ == .touches_moved { |
620 | if ev.num_touches > 0 { |
621 | touch_point := ev.touches[0] |
622 | app.mouse_x = int(touch_point.pos_x) |
623 | app.mouse_y = int(touch_point.pos_y) |
624 | } |
625 | } |
626 | |
627 | // clear all parameters |
628 | if ev.typ == .mouse_down && ev.mouse_button == .middle { |
629 | clear_modifier_params(mut app) |
630 | } |
631 | |
632 | // ws := gg.window_size_real_pixels() |
633 | // ratio := f32(ws.width) / ws.height |
634 | // dw := ws.width |
635 | // dh := ws.height |
636 | |
637 | // --- translate --- |
638 | if ev.typ == .mouse_down && ev.mouse_button == .left { |
639 | app.tr_flag = true |
640 | app.last_tr_x = app.mouse_x |
641 | app.last_tr_y = app.mouse_y |
642 | } |
643 | if ev.typ == .mouse_up && ev.mouse_button == .left && app.tr_flag == true { |
644 | app.tr_flag = false |
645 | } |
646 | if ev.typ == .mouse_move && app.tr_flag == true { |
647 | app.tr_x += (app.mouse_x - app.last_tr_x) * 3 * app.gg.scale |
648 | app.tr_y += (app.mouse_y - app.last_tr_y) * 3 * app.gg.scale |
649 | app.last_tr_x = app.mouse_x |
650 | app.last_tr_y = app.mouse_y |
651 | // println("Translate: ${app.tr_x} ${app.tr_y}") |
652 | } |
653 | |
654 | // --- scaling --- |
655 | if ev.typ == .mouse_down && ev.mouse_button == .right && app.sc_flag == false { |
656 | app.sc_flag = true |
657 | app.last_sc_x = app.mouse_x |
658 | app.last_sc_y = app.mouse_y |
659 | } |
660 | if ev.typ == .mouse_up && ev.mouse_button == .right && app.sc_flag == true { |
661 | app.sc_flag = false |
662 | } |
663 | if ev.typ == .mouse_move && app.sc_flag == true { |
664 | app.sc_x = app.mouse_x - app.last_sc_x |
665 | app.sc_y = app.mouse_y - app.last_sc_y |
666 | app.last_sc_x = app.mouse_x |
667 | app.last_sc_y = app.mouse_y |
668 | |
669 | app.scale += f32(app.sc_x / 100) |
670 | if app.scale < 0.1 { |
671 | app.scale = 0.1 |
672 | } |
673 | if app.scale > 32 { |
674 | app.scale = 32 |
675 | } |
676 | } |
677 | |
678 | if ev.typ == .key_down { |
679 | // println(ev.key_code) |
680 | |
681 | // Exit using the ESC key or Q key |
682 | if ev.key_code == .escape || ev.key_code == .q { |
683 | cleanup(mut app) |
684 | exit(0) |
685 | } |
686 | // Toggle info text OSD |
687 | if ev.key_code == .i { |
688 | app.show_info_flag = !app.show_info_flag |
689 | } |
690 | // Toggle help text |
691 | if ev.key_code == .h { |
692 | app.show_help_flag = !app.show_help_flag |
693 | } |
694 | |
695 | // do actions only if there are items in the list |
696 | if app.item_list.loaded == true && app.item_list.n_item > 0 { |
697 | // show previous image |
698 | if ev.key_code == .left { |
699 | app.item_list.get_next_item(-1) |
700 | load_image(mut app) |
701 | } |
702 | // show next image |
703 | if ev.key_code == .right { |
704 | app.item_list.get_next_item(1) |
705 | load_image(mut app) |
706 | } |
707 | |
708 | // jump to the next container if possible |
709 | if ev.key_code == .up { |
710 | app.item_list.go_to_next_container(1) |
711 | load_image(mut app) |
712 | } |
713 | // jump to the previous container if possible |
714 | if ev.key_code == .down { |
715 | app.item_list.go_to_next_container(-1) |
716 | load_image(mut app) |
717 | } |
718 | |
719 | // rotate the image |
720 | if ev.key_code == .r { |
721 | app.item_list.rotate(1) |
722 | } |
723 | |
724 | // full screen |
725 | if ev.key_code == .f { |
726 | println('Full screen state: ${sapp.is_fullscreen()}') |
727 | sapp.toggle_fullscreen() |
728 | } |
729 | } |
730 | } |
731 | |
732 | // drag&drop |
733 | if ev.typ == .files_dropped { |
734 | app.state = .scanning |
735 | // set logo texture during scanning |
736 | show_logo(mut app) |
737 | |
738 | num := sapp.get_num_dropped_files() |
739 | mut file_list := []string{} |
740 | for i in 0 .. num { |
741 | file_list << sapp.get_dropped_file_path(i) |
742 | } |
743 | println('Scanning: ${file_list}') |
744 | app.item_list = &Item_list{} |
745 | app.item_list.loaded = false |
746 | |
747 | // load_image(mut app) |
748 | // go app.item_list.get_items_list(file_list) |
749 | |
750 | load_and_show(file_list, mut app) |
751 | } |
752 | } |
753 | |
754 | fn load_and_show(file_list []string, mut app App) { |
755 | app.item_list.get_items_list(file_list) |
756 | load_image(mut app) |
757 | } |
758 | |
759 | /****************************************************************************** |
760 | * |
761 | * Main |
762 | * |
763 | ******************************************************************************/ |
764 | fn main() { |
765 | // mut font_path := os.resource_abs_path(os.join_path('../assets/fonts/', 'RobotoMono-Regular.ttf')) |
766 | font_name := 'RobotoMono-Regular.ttf' |
767 | font_path := os.join_path(os.temp_dir(), font_name) |
768 | println('Temporary path for the font file: [${font_path}]') |
769 | |
770 | // if the font doesn't exist create it from the embedded one |
771 | if os.exists(font_path) == false { |
772 | println('Write font [${font_name}] in temp folder.') |
773 | embedded_file := $embed_file('../assets/fonts/RobotoMono-Regular.ttf') |
774 | os.write_file(font_path, embedded_file.to_string()) or { |
775 | eprintln('ERROR: not able to write font file to [${font_path}]') |
776 | exit(1) |
777 | } |
778 | } |
779 | |
780 | // logo image |
781 | logo_name := 'logo.png' |
782 | logo_path := os.join_path(os.temp_dir(), logo_name) |
783 | println('Temporary path for the logo: [${logo_path}]') |
784 | // if the logo doesn't exist create it from the embedded one |
785 | if os.exists(logo_path) == false { |
786 | println('Write logo [${logo_name}] in temp folder.') |
787 | embedded_file := $embed_file('../assets/logo.png') |
788 | os.write_file(logo_path, embedded_file.to_string()) or { |
789 | eprintln('ERROR: not able to write logo file to [${logo_path}]') |
790 | exit(1) |
791 | } |
792 | } |
793 | |
794 | // App init |
795 | mut app := &App{ |
796 | gg: 0 |
797 | // zip fields |
798 | zip: 0 |
799 | item_list: 0 |
800 | } |
801 | |
802 | app.state = .scanning |
803 | app.logo_path = logo_path |
804 | app.font_path = font_path |
805 | |
806 | // Scan all the arguments to find images |
807 | app.item_list = &Item_list{} |
808 | // app.item_list.get_items_list(os.args[1..]) |
809 | load_and_show(os.args[1..], mut app) |
810 | |
811 | app.gg = gg.new_context( |
812 | width: win_width |
813 | height: win_height |
814 | create_window: true |
815 | window_title: 'V Image viewer 0.8' |
816 | user_data: app |
817 | bg_color: bg_color |
818 | frame_fn: frame |
819 | init_fn: app_init |
820 | cleanup_fn: cleanup |
821 | event_fn: my_event_manager |
822 | font_path: font_path |
823 | enable_dragndrop: true |
824 | max_dropped_files: 64 |
825 | max_dropped_file_path_length: 2048 |
826 | // ui_mode: true |
827 | ) |
828 | |
829 | app.gg.run() |
830 | } |