833 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			V
		
	
	
			
		
		
	
	
			833 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			V
		
	
	
/**********************************************************************
 | 
						|
*
 | 
						|
* simple Picture Viewer V. 0.9
 | 
						|
*
 | 
						|
* Copyright (c) 2021 Dario Deledda. All rights reserved.
 | 
						|
* Use of this source code is governed by an MIT license
 | 
						|
* that can be found in the LICENSE file.
 | 
						|
*
 | 
						|
* TODO:
 | 
						|
* - add an example with shaders
 | 
						|
**********************************************************************/
 | 
						|
import os
 | 
						|
import gg
 | 
						|
import gx
 | 
						|
import sokol.gfx
 | 
						|
import sokol.sgl
 | 
						|
import sokol.sapp
 | 
						|
import stbi
 | 
						|
import szip
 | 
						|
import strings
 | 
						|
 | 
						|
// Help text
 | 
						|
const (
 | 
						|
	help_text_rows = [
 | 
						|
		'Image Viwer 0.9 help.',
 | 
						|
		'',
 | 
						|
		'ESC/q - Quit',
 | 
						|
		'cur. right - Next image',
 | 
						|
		'cur. left  - Previous image',
 | 
						|
		'cur. up    - Next folder',
 | 
						|
		'cur. down  - Previous folder',
 | 
						|
		'F - Toggle full screen',
 | 
						|
		'R - Rotate image of 90 degree',
 | 
						|
		'I - Toggle the info text',
 | 
						|
		'',
 | 
						|
		'mouse wheel - next/previous images',
 | 
						|
		'keep pressed left  Mouse button - Pan on the image',
 | 
						|
		'keep pressed rigth Mouse button - Zoom on the image',
 | 
						|
	]
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	win_width       = 800
 | 
						|
	win_height      = 800
 | 
						|
	bg_color        = gx.black
 | 
						|
	pi_2            = 3.14159265359 / 2.0
 | 
						|
	uv              = [f32(0), 0, 1, 0, 1, 1, 0, 1]! // used for zoom icon during rotations
 | 
						|
 | 
						|
	text_drop_files = 'Drop here some images/folder/zip to navigate in the pics'
 | 
						|
	text_scanning   = 'Scanning...'
 | 
						|
	text_loading    = 'Loading...'
 | 
						|
)
 | 
						|
 | 
						|
enum Viewer_state {
 | 
						|
	loading
 | 
						|
	scanning
 | 
						|
	show
 | 
						|
	error
 | 
						|
}
 | 
						|
 | 
						|
struct App {
 | 
						|
mut:
 | 
						|
	gg          &gg.Context
 | 
						|
	pip_viewer  sgl.Pipeline
 | 
						|
	texture     gfx.Image
 | 
						|
	init_flag   bool
 | 
						|
	frame_count int
 | 
						|
	mouse_x     int = -1
 | 
						|
	mouse_y     int = -1
 | 
						|
	scroll_y    int
 | 
						|
 | 
						|
	state Viewer_state = .scanning
 | 
						|
	// translation
 | 
						|
	tr_flag   bool
 | 
						|
	tr_x      f32 = 0.0
 | 
						|
	tr_y      f32 = 0.0
 | 
						|
	last_tr_x f32 = 0.0
 | 
						|
	last_tr_y f32 = 0.0
 | 
						|
	// scaling
 | 
						|
	sc_flag   bool
 | 
						|
	scale     f32 = 1.0
 | 
						|
	sc_x      f32 = 0.0
 | 
						|
	sc_y      f32 = 0.0
 | 
						|
	last_sc_x f32 = 0.0
 | 
						|
	last_sc_y f32 = 0.0
 | 
						|
	// loaded image
 | 
						|
	img_w     int
 | 
						|
	img_h     int
 | 
						|
	img_ratio f32 = 1.0
 | 
						|
	// item list
 | 
						|
	item_list &Item_list
 | 
						|
	// Text info and help
 | 
						|
	show_info_flag bool = true
 | 
						|
	show_help_flag bool
 | 
						|
	// zip container
 | 
						|
	zip       &szip.Zip // pointer to the szip structure
 | 
						|
	zip_index int = -1 // index of the zip contaire item
 | 
						|
	// memory buffer
 | 
						|
	mem_buf      voidptr // buffer used to load items from files/containers
 | 
						|
	mem_buf_size int     // size of the buffer
 | 
						|
	// font
 | 
						|
	font_path string // path to the temp font file
 | 
						|
	// logo
 | 
						|
	logo_path    string // path of the temp font logo
 | 
						|
	logo_texture gfx.Image
 | 
						|
	logo_w       int
 | 
						|
	logo_h       int
 | 
						|
	logo_ratio   f32 = 1.0
 | 
						|
	// string builder
 | 
						|
	bl strings.Builder = strings.new_builder(512)
 | 
						|
}
 | 
						|
 | 
						|
/******************************************************************************
 | 
						|
*
 | 
						|
* Texture functions
 | 
						|
*
 | 
						|
******************************************************************************/
 | 
						|
fn create_texture(w int, h int, buf &u8) gfx.Image {
 | 
						|
	sz := w * h * 4
 | 
						|
	mut img_desc := gfx.ImageDesc{
 | 
						|
		width: w
 | 
						|
		height: h
 | 
						|
		num_mipmaps: 0
 | 
						|
		min_filter: .linear
 | 
						|
		mag_filter: .linear
 | 
						|
		// usage: .dynamic
 | 
						|
		wrap_u: .clamp_to_edge
 | 
						|
		wrap_v: .clamp_to_edge
 | 
						|
		label: &u8(0)
 | 
						|
		d3d11_texture: 0
 | 
						|
	}
 | 
						|
	// comment if .dynamic is enabled
 | 
						|
	img_desc.data.subimage[0][0] = gfx.Range{
 | 
						|
		ptr: buf
 | 
						|
		size: usize(sz)
 | 
						|
	}
 | 
						|
 | 
						|
	sg_img := gfx.make_image(&img_desc)
 | 
						|
	return sg_img
 | 
						|
}
 | 
						|
 | 
						|
fn destroy_texture(sg_img gfx.Image) {
 | 
						|
	gfx.destroy_image(sg_img)
 | 
						|
}
 | 
						|
 | 
						|
// Use only if: .dynamic is enabled
 | 
						|
fn update_text_texture(sg_img gfx.Image, w int, h int, buf &u8) {
 | 
						|
	sz := w * h * 4
 | 
						|
	mut tmp_sbc := gfx.ImageData{}
 | 
						|
	tmp_sbc.subimage[0][0] = gfx.Range{
 | 
						|
		ptr: buf
 | 
						|
		size: usize(sz)
 | 
						|
	}
 | 
						|
	gfx.update_image(sg_img, &tmp_sbc)
 | 
						|
}
 | 
						|
 | 
						|
/******************************************************************************
 | 
						|
*
 | 
						|
* Memory buffer
 | 
						|
*
 | 
						|
******************************************************************************/
 | 
						|
[inline]
 | 
						|
fn (mut app App) resize_buf_if_needed(in_size int) {
 | 
						|
	// manage the memory buffer
 | 
						|
	if app.mem_buf_size < in_size {
 | 
						|
		println('Managing FILE memory buffer, allocated [$in_size]Bytes')
 | 
						|
		// free previous buffer if any exist
 | 
						|
		if app.mem_buf_size > 0 {
 | 
						|
			unsafe {
 | 
						|
				free(app.mem_buf)
 | 
						|
			}
 | 
						|
		}
 | 
						|
		// allocate the memory
 | 
						|
		unsafe {
 | 
						|
			app.mem_buf = malloc(int(in_size))
 | 
						|
			app.mem_buf_size = int(in_size)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/******************************************************************************
 | 
						|
*
 | 
						|
* Loading functions
 | 
						|
*
 | 
						|
******************************************************************************/
 | 
						|
// read_bytes from file in `path` in the memory buffer of app.
 | 
						|
[manualfree]
 | 
						|
fn (mut app App) read_bytes(path string) bool {
 | 
						|
	mut fp := os.vfopen(path, 'rb') or {
 | 
						|
		eprintln('ERROR: Can not open the file [$path].')
 | 
						|
		return false
 | 
						|
	}
 | 
						|
	defer {
 | 
						|
		C.fclose(fp)
 | 
						|
	}
 | 
						|
	cseek := C.fseek(fp, 0, C.SEEK_END)
 | 
						|
	if cseek != 0 {
 | 
						|
		eprintln('ERROR: Can not seek in the file [$path].')
 | 
						|
		return false
 | 
						|
	}
 | 
						|
	fsize := C.ftell(fp)
 | 
						|
	if fsize < 0 {
 | 
						|
		eprintln('ERROR: File [$path] has size is 0.')
 | 
						|
		return false
 | 
						|
	}
 | 
						|
	C.rewind(fp)
 | 
						|
 | 
						|
	app.resize_buf_if_needed(int(fsize))
 | 
						|
 | 
						|
	nr_read_elements := int(C.fread(app.mem_buf, fsize, 1, fp))
 | 
						|
	if nr_read_elements == 0 && fsize > 0 {
 | 
						|
		eprintln('ERROR: Can not read the file [$path] in the memory buffer.')
 | 
						|
		return false
 | 
						|
	}
 | 
						|
	return true
 | 
						|
}
 | 
						|
 | 
						|
// read a file as []u8
 | 
						|
pub fn read_bytes_from_file(file_path string) []u8 {
 | 
						|
	mut buffer := []u8{}
 | 
						|
	buffer = os.read_bytes(file_path) or {
 | 
						|
		eprintln('ERROR: Texure file: [$file_path] NOT FOUND.')
 | 
						|
		exit(0)
 | 
						|
	}
 | 
						|
	return buffer
 | 
						|
}
 | 
						|
 | 
						|
fn (mut app App) load_texture_from_buffer(buf voidptr, buf_len int) (gfx.Image, int, int) {
 | 
						|
	// load image
 | 
						|
	stbi.set_flip_vertically_on_load(true)
 | 
						|
	img := stbi.load_from_memory(buf, buf_len) or {
 | 
						|
		eprintln('ERROR: Can not load image from buffer, file: [${app.item_list.lst[app.item_list.item_index]}].')
 | 
						|
		return app.logo_texture, app.logo_w, app.logo_h
 | 
						|
		// exit(1)
 | 
						|
	}
 | 
						|
	res := create_texture(int(img.width), int(img.height), img.data)
 | 
						|
	unsafe {
 | 
						|
		img.free()
 | 
						|
	}
 | 
						|
	return res, int(img.width), int(img.height)
 | 
						|
}
 | 
						|
 | 
						|
pub fn (mut app App) load_texture_from_file(file_name string) (gfx.Image, int, int) {
 | 
						|
	app.read_bytes(file_name)
 | 
						|
	return app.load_texture_from_buffer(app.mem_buf, app.mem_buf_size)
 | 
						|
}
 | 
						|
 | 
						|
pub fn show_logo(mut app App) {
 | 
						|
	clear_modifier_params(mut app)
 | 
						|
	if app.texture != app.logo_texture {
 | 
						|
		destroy_texture(app.texture)
 | 
						|
	}
 | 
						|
	app.texture = app.logo_texture
 | 
						|
	app.img_w = app.logo_w
 | 
						|
	app.img_h = app.logo_h
 | 
						|
	app.img_ratio = f32(app.img_w) / f32(app.img_h)
 | 
						|
	// app.gg.refresh_ui()
 | 
						|
}
 | 
						|
 | 
						|
pub fn load_image(mut app App) {
 | 
						|
	if app.item_list.loaded == false || app.init_flag == false {
 | 
						|
		// show_logo(mut app)
 | 
						|
		// app.state = .show
 | 
						|
		return
 | 
						|
	}
 | 
						|
	app.state = .loading
 | 
						|
	clear_modifier_params(mut app)
 | 
						|
	// destroy the texture, avoid to destroy the logo
 | 
						|
	if app.texture != app.logo_texture {
 | 
						|
		destroy_texture(app.texture)
 | 
						|
	}
 | 
						|
 | 
						|
	// load from .ZIP file
 | 
						|
	if app.item_list.is_inside_a_container() == true {
 | 
						|
		app.texture, app.img_w, app.img_h = app.load_texture_from_zip() or {
 | 
						|
			eprintln('ERROR: Can not load image from .ZIP file [${app.item_list.lst[app.item_list.item_index]}].')
 | 
						|
			show_logo(mut app)
 | 
						|
			app.state = .show
 | 
						|
			return
 | 
						|
		}
 | 
						|
		app.img_ratio = f32(app.img_w) / f32(app.img_h)
 | 
						|
		app.state = .show
 | 
						|
		// app.gg.refresh_ui()
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	// if we are out of the zip, close it
 | 
						|
	if app.zip_index >= 0 {
 | 
						|
		app.zip_index = -1
 | 
						|
		app.zip.close()
 | 
						|
	}
 | 
						|
 | 
						|
	file_path := app.item_list.get_file_path()
 | 
						|
	if file_path.len > 0 {
 | 
						|
		// println("${app.item_list.lst[app.item_list.item_index]} $file_path ${app.item_list.lst.len}")
 | 
						|
		app.texture, app.img_w, app.img_h = app.load_texture_from_file(file_path)
 | 
						|
		app.img_ratio = f32(app.img_w) / f32(app.img_h)
 | 
						|
		// println("texture: [${app.img_w},${app.img_h}] ratio: ${app.img_ratio}")
 | 
						|
	} else {
 | 
						|
		app.texture = app.logo_texture
 | 
						|
		app.img_w = app.logo_w
 | 
						|
		app.img_h = app.logo_h
 | 
						|
		app.img_ratio = f32(app.img_w) / f32(app.img_h)
 | 
						|
		println('texture NOT FOUND: use logo!')
 | 
						|
	}
 | 
						|
	app.state = .show
 | 
						|
}
 | 
						|
 | 
						|
/******************************************************************************
 | 
						|
*
 | 
						|
* Init / Cleanup
 | 
						|
*
 | 
						|
******************************************************************************/
 | 
						|
fn app_init(mut app App) {
 | 
						|
	app.init_flag = true
 | 
						|
 | 
						|
	// 3d pipeline
 | 
						|
	mut pipdesc := gfx.PipelineDesc{}
 | 
						|
	unsafe { vmemset(&pipdesc, 0, int(sizeof(pipdesc))) }
 | 
						|
 | 
						|
	color_state := gfx.ColorState{
 | 
						|
		blend: gfx.BlendState{
 | 
						|
			enabled: true
 | 
						|
			src_factor_rgb: .src_alpha
 | 
						|
			dst_factor_rgb: .one_minus_src_alpha
 | 
						|
		}
 | 
						|
	}
 | 
						|
	pipdesc.colors[0] = color_state
 | 
						|
 | 
						|
	pipdesc.depth = gfx.DepthState{
 | 
						|
		write_enabled: true
 | 
						|
		compare: .less_equal
 | 
						|
	}
 | 
						|
	pipdesc.cull_mode = .back
 | 
						|
	app.pip_viewer = sgl.make_pipeline(&pipdesc)
 | 
						|
 | 
						|
	// load logo
 | 
						|
	app.logo_texture, app.logo_w, app.logo_h = app.load_texture_from_file(app.logo_path)
 | 
						|
	app.logo_ratio = f32(app.img_w) / f32(app.img_h)
 | 
						|
 | 
						|
	app.img_w = app.logo_w
 | 
						|
	app.img_h = app.logo_h
 | 
						|
	app.img_ratio = app.logo_ratio
 | 
						|
	app.texture = app.logo_texture
 | 
						|
 | 
						|
	println('INIT DONE!')
 | 
						|
 | 
						|
	// init done, load the first image if any
 | 
						|
	load_image(mut app)
 | 
						|
}
 | 
						|
 | 
						|
fn cleanup(mut app App) {
 | 
						|
	gfx.shutdown()
 | 
						|
 | 
						|
	// delete temp files
 | 
						|
	os.rm(app.font_path) or { eprintln('ERROR: Can not delete temp font file.') }
 | 
						|
	os.rm(app.logo_path) or { eprintln('ERROR: Can not delete temp logo file.') }
 | 
						|
	println('Cleaning done.')
 | 
						|
}
 | 
						|
 | 
						|
/******************************************************************************
 | 
						|
*
 | 
						|
* Draw functions
 | 
						|
*
 | 
						|
******************************************************************************/
 | 
						|
[manualfree]
 | 
						|
fn frame(mut app App) {
 | 
						|
	ws := gg.window_size_real_pixels()
 | 
						|
	if ws.width <= 0 || ws.height <= 0 {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	mut ratio := f32(ws.width) / ws.height
 | 
						|
	dw := ws.width
 | 
						|
	dh := ws.height
 | 
						|
 | 
						|
	app.gg.begin()
 | 
						|
	sgl.defaults()
 | 
						|
 | 
						|
	// set viewport
 | 
						|
	sgl.viewport(0, 0, dw, dh, true)
 | 
						|
 | 
						|
	// enable our pipeline
 | 
						|
	sgl.load_pipeline(app.pip_viewer)
 | 
						|
	sgl.enable_texture()
 | 
						|
	sgl.texture(app.texture)
 | 
						|
 | 
						|
	// translation
 | 
						|
	tr_x := app.tr_x / app.img_w
 | 
						|
	tr_y := -app.tr_y / app.img_h
 | 
						|
	sgl.push_matrix()
 | 
						|
	sgl.translate(tr_x, tr_y, 0.0)
 | 
						|
	// scaling/zoom
 | 
						|
	sgl.scale(2.0 * app.scale, 2.0 * app.scale, 0.0)
 | 
						|
	// roation
 | 
						|
	mut rotation := 0
 | 
						|
	if app.state == .show && app.item_list.n_item > 0 {
 | 
						|
		rotation = app.item_list.lst[app.item_list.item_index].rotation
 | 
						|
		sgl.rotate(pi_2 * f32(rotation), 0.0, 0.0, -1.0)
 | 
						|
	}
 | 
						|
 | 
						|
	// draw the image
 | 
						|
	mut w := f32(0.5)
 | 
						|
	mut h := f32(0.5)
 | 
						|
 | 
						|
	// for 90 and 270 degree invert w and h
 | 
						|
	// rotation change image ratio, manage it
 | 
						|
	if rotation & 1 == 1 {
 | 
						|
		tmp := w
 | 
						|
		w = h
 | 
						|
		h = tmp
 | 
						|
		h /= app.img_ratio * ratio
 | 
						|
	} else {
 | 
						|
		h /= app.img_ratio / ratio
 | 
						|
	}
 | 
						|
 | 
						|
	// manage image overflow in case of strange scales
 | 
						|
	if h > 0.5 {
 | 
						|
		reduction_factor := 0.5 / h
 | 
						|
		h = h * reduction_factor
 | 
						|
		w = w * reduction_factor
 | 
						|
	}
 | 
						|
	if w > 0.5 {
 | 
						|
		reduction_factor := 0.5 / w
 | 
						|
		h = h * reduction_factor
 | 
						|
		w = w * reduction_factor
 | 
						|
	}
 | 
						|
 | 
						|
	// println("$w,$h")
 | 
						|
	// white multiplicator for now
 | 
						|
	mut c := [u8(255), 255, 255]!
 | 
						|
	sgl.begin_quads()
 | 
						|
	sgl.v2f_t2f_c3b(-w, -h, 0, 0, c[0], c[1], c[2])
 | 
						|
	sgl.v2f_t2f_c3b(w, -h, 1, 0, c[0], c[1], c[2])
 | 
						|
	sgl.v2f_t2f_c3b(w, h, 1, 1, c[0], c[1], c[2])
 | 
						|
	sgl.v2f_t2f_c3b(-w, h, 0, 1, c[0], c[1], c[2])
 | 
						|
	sgl.end()
 | 
						|
 | 
						|
	// restore all the transformations
 | 
						|
	sgl.pop_matrix()
 | 
						|
 | 
						|
	// Zoom icon
 | 
						|
	/*
 | 
						|
	if app.show_info_flag == true && app.scale > 1 {
 | 
						|
		mut bw := f32(0.25)
 | 
						|
		mut bh := f32(0.25 / app.img_ratio)
 | 
						|
		
 | 
						|
		// manage the rotations
 | 
						|
		if rotation & 1 == 1 {
 | 
						|
			bw,bh = bh,bw
 | 
						|
		}
 | 
						|
		mut bx := f32(1 - bw)
 | 
						|
		mut by := f32(1 - bh)
 | 
						|
		if rotation & 1 == 1 {
 | 
						|
			bx,by = by,bx
 | 
						|
		}
 | 
						|
		
 | 
						|
		bh_old1 := bh
 | 
						|
		bh *= ratio
 | 
						|
		by += (bh_old1 - bh)
 | 
						|
		
 | 
						|
		// draw the zoom icon
 | 
						|
		sgl.begin_quads()
 | 
						|
		r := int(u32(rotation) << 1)
 | 
						|
		sgl.v2f_t2f_c3b(bx     , by     , uv[(0 + r) & 7] , uv[(1 + r) & 7], c[0], c[1], c[2])
 | 
						|
		sgl.v2f_t2f_c3b(bx + bw, by     , uv[(2 + r) & 7] , uv[(3 + r) & 7], c[0], c[1], c[2])
 | 
						|
		sgl.v2f_t2f_c3b(bx + bw, by + bh, uv[(4 + r) & 7] , uv[(5 + r) & 7], c[0], c[1], c[2])
 | 
						|
		sgl.v2f_t2f_c3b(bx     , by + bh, uv[(6 + r) & 7] , uv[(7 + r) & 7], c[0], c[1], c[2])
 | 
						|
		sgl.end()
 | 
						|
		
 | 
						|
		// draw the zoom rectangle
 | 
						|
		sgl.disable_texture()
 | 
						|
		
 | 
						|
		bw_old := bw
 | 
						|
		bh_old := bh
 | 
						|
		bw /=  app.scale
 | 
						|
		bh /=  app.scale
 | 
						|
		bx += (bw_old - bw) / 2 - (tr_x / 8) / app.scale
 | 
						|
		by += (bh_old - bh) / 2 - ((tr_y / 8) / app.scale) * ratio
 | 
						|
		
 | 
						|
		c = [u8(255),255,0]! // yellow
 | 
						|
		sgl.begin_line_strip()
 | 
						|
		sgl.v2f_c3b(bx     , by     , c[0], c[1], c[2])
 | 
						|
		sgl.v2f_c3b(bx + bw, by     , c[0], c[1], c[2])
 | 
						|
		sgl.v2f_c3b(bx + bw, by + bh, c[0], c[1], c[2])
 | 
						|
		sgl.v2f_c3b(bx     , by + bh, c[0], c[1], c[2])
 | 
						|
		sgl.v2f_c3b(bx     , by     , c[0], c[1], c[2])
 | 
						|
		sgl.end()
 | 
						|
	}
 | 
						|
	*/
 | 
						|
	sgl.disable_texture()
 | 
						|
 | 
						|
	//
 | 
						|
	// Draw info text
 | 
						|
	//
 | 
						|
	x := 10
 | 
						|
	y := 10
 | 
						|
 | 
						|
	app.gg.begin()
 | 
						|
 | 
						|
	if app.state in [.scanning, .loading] {
 | 
						|
		if app.state == .scanning {
 | 
						|
			draw_text(mut app, text_scanning, x, y, 20)
 | 
						|
		} else {
 | 
						|
			draw_text(mut app, text_loading, x, y, 20)
 | 
						|
		}
 | 
						|
	} else if app.state == .show {
 | 
						|
		// print the info text if needed
 | 
						|
		if app.item_list.n_item > 0 && app.show_info_flag == true {
 | 
						|
			/*
 | 
						|
			// waiting for better autofree
 | 
						|
			num := app.item_list.lst[app.item_list.item_index].n_item
 | 
						|
			of_num := app.item_list.n_item
 | 
						|
			x_screen := int(w*2*app.scale*dw)
 | 
						|
			y_screen := int(h*2*app.scale*dw)
 | 
						|
			rotation_angle := 90 * rotation
 | 
						|
			scale_str := "${app.scale:.2}"
 | 
						|
			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}"
 | 
						|
			//text := "${num}/${of_num}"
 | 
						|
			draw_text(mut app, text, 10, 10, 20)		
 | 
						|
			unsafe{
 | 
						|
				text.free()
 | 
						|
			}
 | 
						|
			*/
 | 
						|
 | 
						|
			// Using string builder to avoid memory leak
 | 
						|
			num := app.item_list.lst[app.item_list.item_index].n_item
 | 
						|
			of_num := app.item_list.n_item
 | 
						|
			x_screen := int(w * 2 * app.scale * dw)
 | 
						|
			y_screen := int(h * 2 * app.scale * dw)
 | 
						|
			rotation_angle := 90 * rotation
 | 
						|
			scale_str := '${app.scale:.2}'
 | 
						|
			app.bl.clear()
 | 
						|
			app.bl.write_string('$num/$of_num')
 | 
						|
			app.bl.write_string(' [${app.img_w}x$app.img_h]=>[${x_screen}x$y_screen]')
 | 
						|
			app.bl.write_string(' ${app.item_list.lst[app.item_list.item_index].name}')
 | 
						|
			app.bl.write_string(' scale: $scale_str rotation: $rotation_angle')
 | 
						|
			draw_text(mut app, app.bl.str(), 10, 10, 20)
 | 
						|
		} else {
 | 
						|
			if app.item_list.n_item <= 0 {
 | 
						|
				draw_text(mut app, text_drop_files, 10, 10, 20)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	//
 | 
						|
	// Draw Help text
 | 
						|
	//
 | 
						|
	if app.show_help_flag == true {
 | 
						|
		mut txt_y := 30
 | 
						|
		for r in help_text_rows {
 | 
						|
			draw_text(mut app, r, 10, txt_y, 20)
 | 
						|
			txt_y += 20
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	app.gg.end()
 | 
						|
	app.frame_count++
 | 
						|
}
 | 
						|
 | 
						|
// draw readable text
 | 
						|
fn draw_text(mut app App, in_txt string, in_x int, in_y int, fnt_sz f32) {
 | 
						|
	scale := app.gg.scale
 | 
						|
	font_size := int(fnt_sz * scale)
 | 
						|
 | 
						|
	mut txt_conf_c0 := gx.TextCfg{
 | 
						|
		color: gx.white // gx.rgb( (c >> 16) & 0xff, (c >> 8) & 0xff, c & 0xff)
 | 
						|
		align: .left
 | 
						|
		size: font_size
 | 
						|
	}
 | 
						|
	mut txt_conf_c1 := gx.TextCfg{
 | 
						|
		color: gx.black // gx.rgb( (c >> 16) & 0xff, (c >> 8) & 0xff, c & 0xff)
 | 
						|
		align: .left
 | 
						|
		size: font_size
 | 
						|
	}
 | 
						|
 | 
						|
	x := int(in_x * scale)
 | 
						|
	y := int(in_y * scale)
 | 
						|
	app.gg.draw_text(x + 2, y + 2, in_txt, txt_conf_c0)
 | 
						|
	app.gg.draw_text(x, y, in_txt, txt_conf_c1)
 | 
						|
}
 | 
						|
 | 
						|
/******************************************************************************
 | 
						|
*
 | 
						|
* events management
 | 
						|
*
 | 
						|
******************************************************************************/
 | 
						|
fn clear_modifier_params(mut app App) {
 | 
						|
	app.scale = 1.0
 | 
						|
 | 
						|
	app.sc_flag = false
 | 
						|
	app.sc_x = 0
 | 
						|
	app.sc_y = 0
 | 
						|
	app.last_sc_x = 0
 | 
						|
	app.last_sc_y = 0
 | 
						|
 | 
						|
	app.tr_flag = false
 | 
						|
	app.tr_x = 0
 | 
						|
	app.tr_y = 0
 | 
						|
	app.last_tr_x = 0
 | 
						|
	app.last_tr_y = 0
 | 
						|
}
 | 
						|
 | 
						|
fn my_event_manager(mut ev gg.Event, mut app App) {
 | 
						|
	// navigation using the mouse wheel
 | 
						|
	app.scroll_y = int(ev.scroll_y)
 | 
						|
	if app.scroll_y != 0 {
 | 
						|
		inc := int(-1 * app.scroll_y / 4)
 | 
						|
		if app.item_list.n_item > 0 {
 | 
						|
			app.item_list.get_next_item(inc)
 | 
						|
			load_image(mut app)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if ev.typ == .mouse_move {
 | 
						|
		app.mouse_x = int(ev.mouse_x)
 | 
						|
		app.mouse_y = int(ev.mouse_y)
 | 
						|
	}
 | 
						|
	if ev.typ == .touches_began || ev.typ == .touches_moved {
 | 
						|
		if ev.num_touches > 0 {
 | 
						|
			touch_point := ev.touches[0]
 | 
						|
			app.mouse_x = int(touch_point.pos_x)
 | 
						|
			app.mouse_y = int(touch_point.pos_y)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// clear all parameters
 | 
						|
	if ev.typ == .mouse_down && ev.mouse_button == .middle {
 | 
						|
		clear_modifier_params(mut app)
 | 
						|
	}
 | 
						|
 | 
						|
	// ws := gg.window_size_real_pixels()
 | 
						|
	// ratio := f32(ws.width) / ws.height
 | 
						|
	// dw := ws.width
 | 
						|
	// dh := ws.height
 | 
						|
 | 
						|
	// --- translate ---
 | 
						|
	if ev.typ == .mouse_down && ev.mouse_button == .left {
 | 
						|
		app.tr_flag = true
 | 
						|
		app.last_tr_x = app.mouse_x
 | 
						|
		app.last_tr_y = app.mouse_y
 | 
						|
	}
 | 
						|
	if ev.typ == .mouse_up && ev.mouse_button == .left && app.tr_flag == true {
 | 
						|
		app.tr_flag = false
 | 
						|
	}
 | 
						|
	if ev.typ == .mouse_move && app.tr_flag == true {
 | 
						|
		app.tr_x += (app.mouse_x - app.last_tr_x) * 3 * app.gg.scale
 | 
						|
		app.tr_y += (app.mouse_y - app.last_tr_y) * 3 * app.gg.scale
 | 
						|
		app.last_tr_x = app.mouse_x
 | 
						|
		app.last_tr_y = app.mouse_y
 | 
						|
		// println("Translate: ${app.tr_x} ${app.tr_y}")
 | 
						|
	}
 | 
						|
 | 
						|
	// --- scaling ---
 | 
						|
	if ev.typ == .mouse_down && ev.mouse_button == .right && app.sc_flag == false {
 | 
						|
		app.sc_flag = true
 | 
						|
		app.last_sc_x = app.mouse_x
 | 
						|
		app.last_sc_y = app.mouse_y
 | 
						|
	}
 | 
						|
	if ev.typ == .mouse_up && ev.mouse_button == .right && app.sc_flag == true {
 | 
						|
		app.sc_flag = false
 | 
						|
	}
 | 
						|
	if ev.typ == .mouse_move && app.sc_flag == true {
 | 
						|
		app.sc_x = app.mouse_x - app.last_sc_x
 | 
						|
		app.sc_y = app.mouse_y - app.last_sc_y
 | 
						|
		app.last_sc_x = app.mouse_x
 | 
						|
		app.last_sc_y = app.mouse_y
 | 
						|
 | 
						|
		app.scale += f32(app.sc_x / 100)
 | 
						|
		if app.scale < 0.1 {
 | 
						|
			app.scale = 0.1
 | 
						|
		}
 | 
						|
		if app.scale > 32 {
 | 
						|
			app.scale = 32
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if ev.typ == .key_down {
 | 
						|
		// println(ev.key_code)
 | 
						|
 | 
						|
		// Exit using the ESC key or Q key
 | 
						|
		if ev.key_code == .escape || ev.key_code == .q {
 | 
						|
			cleanup(mut app)
 | 
						|
			exit(0)
 | 
						|
		}
 | 
						|
		// Toggle info text OSD
 | 
						|
		if ev.key_code == .i {
 | 
						|
			app.show_info_flag = !app.show_info_flag
 | 
						|
		}
 | 
						|
		// Toggle help text
 | 
						|
		if ev.key_code == .h {
 | 
						|
			app.show_help_flag = !app.show_help_flag
 | 
						|
		}
 | 
						|
 | 
						|
		// do actions only if there are items in the list
 | 
						|
		if app.item_list.loaded == true && app.item_list.n_item > 0 {
 | 
						|
			// show previous image
 | 
						|
			if ev.key_code == .left {
 | 
						|
				app.item_list.get_next_item(-1)
 | 
						|
				load_image(mut app)
 | 
						|
			}
 | 
						|
			// show next image
 | 
						|
			if ev.key_code == .right {
 | 
						|
				app.item_list.get_next_item(1)
 | 
						|
				load_image(mut app)
 | 
						|
			}
 | 
						|
 | 
						|
			// jump to the next container if possible
 | 
						|
			if ev.key_code == .up {
 | 
						|
				app.item_list.go_to_next_container(1)
 | 
						|
				load_image(mut app)
 | 
						|
			}
 | 
						|
			// jump to the previous container if possible
 | 
						|
			if ev.key_code == .down {
 | 
						|
				app.item_list.go_to_next_container(-1)
 | 
						|
				load_image(mut app)
 | 
						|
			}
 | 
						|
 | 
						|
			// rotate the image
 | 
						|
			if ev.key_code == .r {
 | 
						|
				app.item_list.rotate(1)
 | 
						|
			}
 | 
						|
 | 
						|
			// full screen
 | 
						|
			if ev.key_code == .f {
 | 
						|
				println('Full screen state: $sapp.is_fullscreen()')
 | 
						|
				sapp.toggle_fullscreen()
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// drag&drop
 | 
						|
	if ev.typ == .files_droped {
 | 
						|
		app.state = .scanning
 | 
						|
		// set logo texture during scanning
 | 
						|
		show_logo(mut app)
 | 
						|
 | 
						|
		num := sapp.get_num_dropped_files()
 | 
						|
		mut file_list := []string{}
 | 
						|
		for i in 0 .. num {
 | 
						|
			file_list << sapp.get_dropped_file_path(i)
 | 
						|
		}
 | 
						|
		println('Scanning: $file_list')
 | 
						|
		app.item_list = &Item_list{}
 | 
						|
		app.item_list.loaded = false
 | 
						|
 | 
						|
		// load_image(mut app)
 | 
						|
		// go app.item_list.get_items_list(file_list)
 | 
						|
 | 
						|
		load_and_show(file_list, mut app)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
fn load_and_show(file_list []string, mut app App) {
 | 
						|
	app.item_list.get_items_list(file_list)
 | 
						|
	load_image(mut app)
 | 
						|
}
 | 
						|
 | 
						|
/******************************************************************************
 | 
						|
*
 | 
						|
* Main
 | 
						|
*
 | 
						|
******************************************************************************/
 | 
						|
// is needed for easier diagnostics on windows
 | 
						|
[console]
 | 
						|
fn main() {
 | 
						|
	// mut font_path := os.resource_abs_path(os.join_path('../assets/fonts/', 'RobotoMono-Regular.ttf'))
 | 
						|
	font_name := 'RobotoMono-Regular.ttf'
 | 
						|
	font_path := os.join_path(os.temp_dir(), font_name)
 | 
						|
	println('Temporary path for the font file: [$font_path]')
 | 
						|
 | 
						|
	// if the font doesn't exist create it from the ebedded one
 | 
						|
	if os.exists(font_path) == false {
 | 
						|
		println('Write font [$font_name] in temp folder.')
 | 
						|
		embedded_file := $embed_file('../assets/fonts/RobotoMono-Regular.ttf')
 | 
						|
		os.write_file(font_path, embedded_file.to_string()) or {
 | 
						|
			eprintln('ERROR: not able to write font file to [$font_path]')
 | 
						|
			exit(1)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// logo image
 | 
						|
	logo_name := 'logo.png'
 | 
						|
	logo_path := os.join_path(os.temp_dir(), logo_name)
 | 
						|
	println('Temporary path for the logo: [$logo_path]')
 | 
						|
	// if the logo doesn't exist create it from the ebedded one
 | 
						|
	if os.exists(logo_path) == false {
 | 
						|
		println('Write logo [$logo_name] in temp folder.')
 | 
						|
		embedded_file := $embed_file('../assets/logo.png')
 | 
						|
		os.write_file(logo_path, embedded_file.to_string()) or {
 | 
						|
			eprintln('ERROR: not able to write logo file to [$logo_path]')
 | 
						|
			exit(1)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// App init
 | 
						|
	mut app := &App{
 | 
						|
		gg: 0
 | 
						|
		// zip fields
 | 
						|
		zip: 0
 | 
						|
		item_list: 0
 | 
						|
	}
 | 
						|
 | 
						|
	app.state = .scanning
 | 
						|
	app.logo_path = logo_path
 | 
						|
	app.font_path = font_path
 | 
						|
 | 
						|
	// Scan all the arguments to find images
 | 
						|
	app.item_list = &Item_list{}
 | 
						|
	// app.item_list.get_items_list(os.args[1..])
 | 
						|
	load_and_show(os.args[1..], mut app)
 | 
						|
 | 
						|
	app.gg = gg.new_context(
 | 
						|
		width: win_width
 | 
						|
		height: win_height
 | 
						|
		create_window: true
 | 
						|
		window_title: 'V Image viewer 0.8'
 | 
						|
		user_data: app
 | 
						|
		bg_color: bg_color
 | 
						|
		frame_fn: frame
 | 
						|
		init_fn: app_init
 | 
						|
		cleanup_fn: cleanup
 | 
						|
		event_fn: my_event_manager
 | 
						|
		font_path: font_path
 | 
						|
		enable_dragndrop: true
 | 
						|
		max_dropped_files: 64
 | 
						|
		max_dropped_file_path_length: 2048
 | 
						|
		// ui_mode: true
 | 
						|
	)
 | 
						|
 | 
						|
	app.gg.run()
 | 
						|
}
 |