examples: add an image viewer program (#12797)

pull/12802/head
penguindark 2021-12-11 21:18:03 +01:00 committed by GitHub
parent f0969698e2
commit d3b769d1bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 1328 additions and 0 deletions

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Dario Deledda
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,54 @@
# vviewer
Image viewer for V
This is an example of a simple image viewer written in V.
## Usage
The program can be invoked by the command line:
file list: `viewer img1.jpg inmg2.bmp img3.tga`
folder list: `viewer folder1 folder2`
zip list: `viewer folder1.zip folder2.zip`
All folders/zips are scanned for images.
The user can mix files, folders, and zips.
mixed list: `viewer img1.jpg img2.bmp folder1 folder2 img2.tga folder1.zip`
## Interactive usage
Run the viewer then drag and drop files,folders and zips on it.
## Accepted image format
JPEG, PNG, BMP, PSD, TGA, GIF (not animated), HDR, PIC, PNM
#### Functions
The user can navigate through the files passed to the viewer.
The following operations can be performed on each image:
- **Pan**, move over the image
- **Zoom**, magnify or reduce the image
- **Rotate**, rotate by 90 degree steps
## Key bindings
**H** - show this help
**ESC/q** - Quit
**cursor right** - Next image
**cursor left** - Previous image
**cursor up** - Next folder
**cursor down** - Previous folder
**F** - Toggle full screen
**R** - Rotate image of 90 degree
**I** - Toggle the info text
**mouse wheel** - next/previous images
Hold **left Mouse button** - Pan on the image
Hold **right Mouse button** - Zoom on the image
#### Author:
Dario Deledda 2021 (c)

View File

@ -0,0 +1,343 @@
/**********************************************************************
*
* File scanner
*
* 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:
**********************************************************************/
import os
// STBI supported format
// STBI_NO_JPEG *
// STBI_NO_PNG *
// STBI_NO_BMP *
// STBI_NO_PSD
// STBI_NO_TGA *
// STBI_NO_GIF *
// STBI_NO_HDR *
// STBI_NO_PIC *
// STBI_NO_PNM *
/******************************************************************************
*
* Struct and Enums
*
******************************************************************************/
enum Item_type {
file = 0
folder
// archive format
zip = 16
archive_file
// graphic format, MUST stay after the other types!!
bmp = 32
jpg
png
gif
tga
ppm
pgm
pic
hdr
}
pub struct Item {
pub mut:
path string
name string
size u64
i_type Item_type = .file
container_index int // used if the item is in a container (.zip, .rar, etc)
container_item_index int // index in the container if the item is contained
need_extract bool // if true need to extraction from the container
drawable bool // if true the image can be showed
n_item int //
rotation int // number of rotation of PI/2
}
struct Item_list {
pub mut:
lst []Item
path_sep string
item_index int = -1 // image currently shown
n_item int // number of images scanned
loaded bool // flag that indicate that the list is ready to be used
}
/******************************************************************************
*
* Utility functions
*
******************************************************************************/
[inline]
fn modulo(x int, n int) int {
return (x % n + n) % n
}
[inline]
fn get_extension(x string) Item_type {
// 4 char extension check
if x.len > 4 {
ext4 := x[x.len - 4..].to_lower()
match ext4 {
// containers
'.zip' { return .zip }
// graphic formats
'.jpg' { return .jpg }
'.png' { return .png }
'.bmp' { return .bmp }
'.gif' { return .gif }
'.tga' { return .tga }
'.ppm' { return .ppm }
'.pgm' { return .pgm }
'.pic' { return .pic }
'.hdr' { return .hdr }
else {}
}
}
// 5 char extension check
if x.len > 5 {
ext5 := x[x.len - 5..].to_lower()
if ext5 == '.jpeg' {
{
return .jpg
}
}
}
return .file
}
[inline]
fn is_image(x Item_type) bool {
if int(x) >= int(Item_type.bmp) {
return true
}
return false
}
[inline]
fn is_container(x Item_type) bool {
if x in [.zip, .folder] {
return true
}
return false
}
[inline]
fn (item_list Item_list) is_inside_a_container() bool {
if item_list.lst.len <= 0 || item_list.n_item <= 0 {
return false
}
return item_list.lst[item_list.item_index].need_extract
}
[inline]
fn (item_list Item_list) get_file_path() string {
if item_list.lst.len <= 0 || item_list.n_item <= 0 {
return ''
}
if item_list.lst[item_list.item_index].path.len > 0 {
return '${item_list.lst[item_list.item_index].path}$item_list.path_sep${item_list.lst[item_list.item_index].name}'
}
return item_list.lst[item_list.item_index].name
}
/******************************************************************************
*
* Scan functions
*
******************************************************************************/
fn (mut item_list Item_list) scan_folder(path string, in_index int) ? {
println('Scanning [$path]')
mut folder_list := []string{}
lst := os.ls(path) ?
// manage the single files
for c, x in lst {
pt := '$path$item_list.path_sep$x'
mut item := Item{
path: path
name: x
container_index: in_index
container_item_index: c
}
if os.is_dir(pt) {
folder_list << x
} else {
ext := get_extension(x)
if ext == .zip {
item.i_type = .zip
item_list.lst << item
item_list.scan_zip(pt, item_list.lst.len - 1) ?
continue
}
if is_image(ext) == true {
item_list.n_item += 1
item.n_item = item_list.n_item
item.i_type = ext
item.drawable = true
item_list.lst << item
continue
}
}
}
// manage the folders
for x in folder_list {
pt := '$path$item_list.path_sep$x'
item := Item{
path: path
name: x
i_type: .folder
}
item_list.lst << item
item_list.scan_folder(pt, item_list.lst.len - 1) ?
}
// println(item_list.lst.len)
// println("==================================")
}
fn (item_list Item_list) print_list() {
println('================================')
for x in item_list.lst {
if x.i_type == .folder {
print('[]')
}
if x.i_type == .zip {
print('[ZIP]')
}
println('$x.path => $x.container_index $x.container_item_index $x.name ne:$x.need_extract')
}
println('n_item: $item_list.n_item index: $item_list.item_index')
println('================================')
}
fn (mut item_list Item_list) get_items_list(args []string) {
item_list.loaded = false
println('Args: $args')
item_list.path_sep = $if windows { '\\' } $else { '/' }
for x in args {
// scan folder
if os.is_dir(x) {
mut item := Item{
path: x
name: x
container_index: item_list.lst.len
i_type: .folder
}
item_list.lst << item
item_list.scan_folder(x, item_list.lst.len - 1) or {
eprintln('ERROR: scanning folder [$x]!')
continue
}
} else {
mut item := Item{
path: ''
name: x
container_index: -1
}
ext := get_extension(x)
// scan .zip
if ext == .zip {
item.i_type = .zip
item_list.lst << item
item_list.scan_zip(x, item_list.lst.len - 1) or {
eprintln('ERROR: scanning zip [$x]!')
continue
}
continue
}
// single images
if is_image(ext) == true {
item_list.n_item += 1
item.n_item = item_list.n_item
item.i_type = ext
item.drawable = true
item_list.lst << item
continue
}
}
}
// debug call for list all the loaded items
// item_list.print_list()
println('Items: $item_list.n_item')
println('Scanning done.')
item_list.get_next_item(1)
item_list.loaded = true
}
/******************************************************************************
*
* Navigation functions
*
******************************************************************************/
fn (mut item_list Item_list) get_next_item(in_inc int) {
// if empty exit
if item_list.lst.len <= 0 || item_list.n_item <= 0 {
return
}
inc := if in_inc > 0 { 1 } else { -1 }
mut i := item_list.item_index + in_inc
i = modulo(i, item_list.lst.len)
start := i
for {
if item_list.lst[i].drawable == true {
item_list.item_index = i
break
}
i = i + inc
i = modulo(i, item_list.lst.len)
// if we are in a loop break it
if i == start {
break
}
}
// println("Found: ${item_list.item_index}")
}
fn (mut item_list Item_list) go_to_next_container(in_inc int) {
// if empty exit
if item_list.lst.len <= 0 || item_list.n_item <= 0 {
return
}
inc := if in_inc > 0 { 1 } else { -1 }
mut i := item_list.item_index + in_inc
i = modulo(i, item_list.lst.len)
start := i
for {
// check if we found a folder
if is_container(item_list.lst[i].i_type) == true
&& i != item_list.lst[item_list.item_index].container_index {
item_list.item_index = i
item_list.get_next_item(1)
break
}
// continue to search
i = i + inc
i = modulo(i, item_list.lst.len)
// if we are in a loop break it
if i == start {
break
}
}
}
/******************************************************************************
*
* Other functions
*
******************************************************************************/
[inline]
fn (mut item_list Item_list) rotate(in_inc int) {
item_list.lst[item_list.item_index].rotation += in_inc
if item_list.lst[item_list.item_index].rotation >= 4 {
item_list.lst[item_list.item_index].rotation = 0
}
}

View File

@ -0,0 +1,7 @@
Module {
name: 'vviewer',
description: 'A simple image viewer written in V.',
version: '0.9',
repo_url: 'https://github.com/vlang/v/tree/master/examples/viewer',
dependencies: []
}

View File

@ -0,0 +1,832 @@
/**********************************************************************
*
* 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 C.sgl_pipeline
texture C.sg_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 C.sg_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) C.sg_image {
sz := w * h * 4
mut img_desc := C.sg_image_desc{
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: &byte(0)
d3d11_texture: 0
}
// comment if .dynamic is enabled
img_desc.data.subimage[0][0] = C.sg_range{
ptr: buf
size: usize(sz)
}
sg_img := C.sg_make_image(&img_desc)
return sg_img
}
fn destroy_texture(sg_img C.sg_image) {
C.sg_destroy_image(sg_img)
}
// Use only if: .dynamic is enabled
fn update_text_texture(sg_img C.sg_image, w int, h int, buf &byte) {
sz := w * h * 4
mut tmp_sbc := C.sg_image_data{}
tmp_sbc.subimage[0][0] = C.sg_range{
ptr: buf
size: usize(sz)
}
C.sg_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 []byte
pub fn read_bytes_from_file(file_path string) []byte {
mut buffer := []byte{}
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) (C.sg_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) (C.sg_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 := C.sg_pipeline_desc{}
unsafe { C.memset(&pipdesc, 0, sizeof(pipdesc)) }
color_state := C.sg_color_state{
blend: C.sg_blend_state{
enabled: true
src_factor_rgb: gfx.BlendFactor(C.SG_BLENDFACTOR_SRC_ALPHA)
dst_factor_rgb: gfx.BlendFactor(C.SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA)
}
}
pipdesc.colors[0] = color_state
pipdesc.depth = C.sg_depth_state{
write_enabled: true
compare: gfx.CompareFunc(C.SG_COMPAREFUNC_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 := [byte(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 = [byte(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()
}

View File

@ -0,0 +1,71 @@
/**********************************************************************
*
* Zip container manager
*
* 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:
**********************************************************************/
import szip
fn (mut il Item_list) scan_zip(path string, in_index int) ? {
println('Scanning ZIP [$path]')
mut zp := szip.open(path, szip.CompressionLevel.no_compression, szip.OpenMode.read_only) ?
n_entries := zp.total() ?
// println(n_entries)
for index in 0 .. n_entries {
zp.open_entry_by_index(index) ?
is_dir := zp.is_dir() ?
name := zp.name()
size := zp.size()
// println("$index ${name} ${size:10} $is_dir")
if !is_dir {
ext := get_extension(name)
if is_image(ext) == true {
il.n_item += 1
mut item := Item{
need_extract: true
path: path
name: name.clone()
container_index: in_index
container_item_index: index
i_type: ext
n_item: il.n_item
drawable: true
size: size
}
il.lst << item
}
}
// IMPORTANT NOTE: don't close the zip entry before we have used all the items!!
zp.close_entry()
}
zp.close()
}
fn (mut app App) load_texture_from_zip() ?(C.sg_image, int, int) {
item := app.item_list.lst[app.item_list.item_index]
// println("Load from zip [${item.path}]")
// open the zip
if app.zip_index != item.container_index {
if app.zip_index >= 0 {
app.zip.close()
}
app.zip_index = item.container_index
// println("Opening the zip [${item.path}]")
app.zip = szip.open(item.path, szip.CompressionLevel.no_compression, szip.OpenMode.read_only) ?
}
// println("Now get the image")
app.zip.open_entry_by_index(item.container_item_index) ?
zip_entry_size := int(item.size)
app.resize_buf_if_needed(zip_entry_size)
app.zip.read_entry_buf(app.mem_buf, app.mem_buf_size) ?
app.zip.close_entry()
return app.load_texture_from_buffer(app.mem_buf, zip_entry_size)
}