diff options
author | Indrajith K L | 2022-12-03 17:00:20 +0530 |
---|---|---|
committer | Indrajith K L | 2022-12-03 17:00:20 +0530 |
commit | f5c4671bfbad96bf346bd7e9a21fc4317b4959df (patch) | |
tree | 2764fc62da58f2ba8da7ed341643fc359873142f /v_windows/v/vlib/gg | |
download | cli-tools-windows-master.tar.gz cli-tools-windows-master.tar.bz2 cli-tools-windows-master.zip |
Diffstat (limited to 'v_windows/v/vlib/gg')
-rw-r--r-- | v_windows/v/vlib/gg/enums.v | 161 | ||||
-rw-r--r-- | v_windows/v/vlib/gg/gg.c.v | 278 | ||||
-rw-r--r-- | v_windows/v/vlib/gg/gg.v | 750 | ||||
-rw-r--r-- | v_windows/v/vlib/gg/gg_android.c.v | 37 | ||||
-rw-r--r-- | v_windows/v/vlib/gg/gg_darwin.c.v | 23 | ||||
-rw-r--r-- | v_windows/v/vlib/gg/gg_darwin.m | 125 | ||||
-rw-r--r-- | v_windows/v/vlib/gg/image.c.v | 169 | ||||
-rw-r--r-- | v_windows/v/vlib/gg/image.v | 223 | ||||
-rw-r--r-- | v_windows/v/vlib/gg/m4/graphic.v | 110 | ||||
-rw-r--r-- | v_windows/v/vlib/gg/m4/m4_test.v | 235 | ||||
-rw-r--r-- | v_windows/v/vlib/gg/m4/matrix.v | 595 | ||||
-rw-r--r-- | v_windows/v/vlib/gg/m4/vector.v | 230 | ||||
-rw-r--r-- | v_windows/v/vlib/gg/text_rendering.c.v | 226 | ||||
-rw-r--r-- | v_windows/v/vlib/gg/text_rendering.v | 150 |
14 files changed, 3312 insertions, 0 deletions
diff --git a/v_windows/v/vlib/gg/enums.v b/v_windows/v/vlib/gg/enums.v new file mode 100644 index 0000000..c87f986 --- /dev/null +++ b/v_windows/v/vlib/gg/enums.v @@ -0,0 +1,161 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license that can be found in the LICENSE file. +module gg + +pub enum MouseButton { + left = 0 + right = 1 + middle = 2 + invalid = 256 +} + +// NB: unlike the MouseButton enum from above, +// the [flag]-ed enum here can have combined states, +// representing several pressed buttons at once. + +[flag] +pub enum MouseButtons { + left + right + middle +} + +[flag] +pub enum Modifier { + shift // (1<<0) + ctrl // (1<<1) + alt // (1<<2) + super // (1<<3) +} + +pub enum PenLineType { + solid + dashed + dotted +} + +pub enum KeyCode { + invalid = 0 + space = 32 + apostrophe = 39 //' + comma = 44 //, + minus = 45 //- + period = 46 //. + slash = 47 /// + _0 = 48 + _1 = 49 + _2 = 50 + _3 = 51 + _4 = 52 + _5 = 53 + _6 = 54 + _7 = 55 + _8 = 56 + _9 = 57 + semicolon = 59 //; + equal = 61 //= + a = 65 + b = 66 + c = 67 + d = 68 + e = 69 + f = 70 + g = 71 + h = 72 + i = 73 + j = 74 + k = 75 + l = 76 + m = 77 + n = 78 + o = 79 + p = 80 + q = 81 + r = 82 + s = 83 + t = 84 + u = 85 + v = 86 + w = 87 + x = 88 + y = 89 + z = 90 + left_bracket = 91 //[ + backslash = 92 //\ + right_bracket = 93 //] + grave_accent = 96 //` + world_1 = 161 // non-us #1 + world_2 = 162 // non-us #2 + escape = 256 + enter = 257 + tab = 258 + backspace = 259 + insert = 260 + delete = 261 + right = 262 + left = 263 + down = 264 + up = 265 + page_up = 266 + page_down = 267 + home = 268 + end = 269 + caps_lock = 280 + scroll_lock = 281 + num_lock = 282 + print_screen = 283 + pause = 284 + f1 = 290 + f2 = 291 + f3 = 292 + f4 = 293 + f5 = 294 + f6 = 295 + f7 = 296 + f8 = 297 + f9 = 298 + f10 = 299 + f11 = 300 + f12 = 301 + f13 = 302 + f14 = 303 + f15 = 304 + f16 = 305 + f17 = 306 + f18 = 307 + f19 = 308 + f20 = 309 + f21 = 310 + f22 = 311 + f23 = 312 + f24 = 313 + f25 = 314 + kp_0 = 320 + kp_1 = 321 + kp_2 = 322 + kp_3 = 323 + kp_4 = 324 + kp_5 = 325 + kp_6 = 326 + kp_7 = 327 + kp_8 = 328 + kp_9 = 329 + kp_decimal = 330 + kp_divide = 331 + kp_multiply = 332 + kp_subtract = 333 + kp_add = 334 + kp_enter = 335 + kp_equal = 336 + left_shift = 340 + left_control = 341 + left_alt = 342 + left_super = 343 + right_shift = 344 + right_control = 345 + right_alt = 346 + right_super = 347 + menu = 348 +} + +const key_code_max = 512 diff --git a/v_windows/v/vlib/gg/gg.c.v b/v_windows/v/vlib/gg/gg.c.v new file mode 100644 index 0000000..f6956e6 --- /dev/null +++ b/v_windows/v/vlib/gg/gg.c.v @@ -0,0 +1,278 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license that can be found in the LICENSE file. + +module gg + +import os +import gx +import sokol +import sokol.sapp +import sokol.sgl +import sokol.gfx +import math + +pub struct Event { +pub mut: + frame_count u64 + typ sapp.EventType + key_code KeyCode + char_code u32 + key_repeat bool + modifiers u32 + mouse_button MouseButton + mouse_x f32 + mouse_y f32 + mouse_dx f32 + mouse_dy f32 + scroll_x f32 + scroll_y f32 + num_touches int + touches [8]C.sapp_touchpoint + window_width int + window_height int + framebuffer_width int + framebuffer_height int +} + +[heap] +pub struct Context { +mut: + render_text bool = true + // a cache with all images created by the user. used for sokol image init and to save space + // (so that the user can store image ids, not entire Image objects) + image_cache []Image + needs_refresh bool = true + ticks int // for ui mode only +pub: + native_rendering bool +pub mut: + scale f32 = 1.0 + // will get set to 2.0 for retina, will remain 1.0 for normal + width int + height int + clear_pass C.sg_pass_action + window C.sapp_desc + timage_pip C.sgl_pipeline + config Config + ft &FT + font_inited bool + ui_mode bool // do not redraw everything 60 times/second, but only when the user requests + frame u64 // the current frame counted from the start of the application; always increasing + // + mbtn_mask byte + mouse_buttons MouseButtons // typed version of mbtn_mask; easier to use for user programs + mouse_pos_x int + mouse_pos_y int + mouse_dx int + mouse_dy int + scroll_x int + scroll_y int + // + key_modifiers Modifier // the current key modifiers + key_repeat bool // whether the pressed key was an autorepeated one + pressed_keys [key_code_max]bool // an array representing all currently pressed keys + pressed_keys_edge [key_code_max]bool // true when the previous state of pressed_keys, + // *before* the current event was different +} + +fn gg_init_sokol_window(user_data voidptr) { + mut g := unsafe { &Context(user_data) } + desc := sapp.create_desc() + /* + desc := C.sg_desc{ + mtl_device: sapp.metal_get_device() + mtl_renderpass_descriptor_cb: sapp.metal_get_renderpass_descriptor + mtl_drawable_cb: sapp.metal_get_drawable + d3d11_device: sapp.d3d11_get_device() + d3d11_device_context: sapp.d3d11_get_device_context() + d3d11_render_target_view_cb: sapp.d3d11_get_render_target_view + d3d11_depth_stencil_view_cb: sapp.d3d11_get_depth_stencil_view + } + */ + gfx.setup(&desc) + sgl_desc := C.sgl_desc_t{} + sgl.setup(&sgl_desc) + g.scale = dpi_scale() + // is_high_dpi := sapp.high_dpi() + // fb_w := sapp.width() + // fb_h := sapp.height() + // println('g.scale=$g.scale is_high_dpi=$is_high_dpi fb_w=$fb_w fb_h=$fb_h') + // if g.config.init_text { + // `os.is_file()` won't work on Android if the font file is embedded into the APK + exists := $if !android { os.is_file(g.config.font_path) } $else { true } + if g.config.font_path != '' && !exists { + g.render_text = false + } else if g.config.font_path != '' && exists { + // t := time.ticks() + g.ft = new_ft( + font_path: g.config.font_path + custom_bold_font_path: g.config.custom_bold_font_path + scale: dpi_scale() + ) or { panic(err) } + // println('FT took ${time.ticks()-t} ms') + g.font_inited = true + } else { + if g.config.font_bytes_normal.len > 0 { + g.ft = new_ft( + bytes_normal: g.config.font_bytes_normal + bytes_bold: g.config.font_bytes_bold + bytes_mono: g.config.font_bytes_mono + bytes_italic: g.config.font_bytes_italic + scale: sapp.dpi_scale() + ) or { panic(err) } + g.font_inited = true + } else { + sfont := system_font_path() + if g.config.font_path != '' { + eprintln('font file "$g.config.font_path" does not exist, the system font ($sfont) was used instead.') + } + + g.ft = new_ft( + font_path: sfont + custom_bold_font_path: g.config.custom_bold_font_path + scale: sapp.dpi_scale() + ) or { panic(err) } + g.font_inited = true + } + } + // + mut pipdesc := C.sg_pipeline_desc{ + label: c'alpha_image' + } + unsafe { vmemset(&pipdesc, 0, int(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 + + g.timage_pip = sgl.make_pipeline(&pipdesc) + // + if g.config.init_fn != voidptr(0) { + g.config.init_fn(g.config.user_data) + } + // Create images now that we can do that after sg is inited + if g.native_rendering { + return + } + + for i in 0 .. g.image_cache.len { + if g.image_cache[i].simg.id == 0 { + g.image_cache[i].init_sokol_image() + } + } +} + +// +pub fn new_context(cfg Config) &Context { + mut g := &Context{ + width: cfg.width + height: cfg.height + config: cfg + ft: 0 + ui_mode: cfg.ui_mode + native_rendering: cfg.native_rendering + } + g.set_bg_color(cfg.bg_color) + // C.printf('new_context() %p\n', cfg.user_data) + window := C.sapp_desc{ + user_data: g + init_userdata_cb: gg_init_sokol_window + frame_userdata_cb: gg_frame_fn + event_userdata_cb: gg_event_fn + fail_userdata_cb: gg_fail_fn + cleanup_userdata_cb: gg_cleanup_fn + window_title: &char(cfg.window_title.str) + html5_canvas_name: &char(cfg.window_title.str) + width: cfg.width + height: cfg.height + sample_count: cfg.sample_count + high_dpi: true + fullscreen: cfg.fullscreen + __v_native_render: cfg.native_rendering + } + g.window = window + return g +} + +pub fn (ctx &Context) draw_circle_line(x f32, y f32, r int, segments int, c gx.Color) { + $if macos { + if ctx.native_rendering { + C.darwin_draw_circle(x - r + 1, ctx.height - (y + r + 3), r, c) + return + } + } + if c.a != 255 { + sgl.load_pipeline(ctx.timage_pip) + } + sgl.c4b(c.r, c.g, c.b, c.a) + nx := x * ctx.scale + ny := y * ctx.scale + nr := r * ctx.scale + mut theta := f32(0) + mut xx := f32(0) + mut yy := f32(0) + sgl.begin_line_strip() + for i := 0; i < segments + 1; i++ { + theta = 2.0 * f32(math.pi) * f32(i) / f32(segments) + xx = nr * math.cosf(theta) + yy = nr * math.sinf(theta) + sgl.v2f(xx + nx, yy + ny) + } + sgl.end() +} + +pub fn high_dpi() bool { + return C.sapp_high_dpi() +} + +pub fn screen_size() Size { + $if macos { + return C.gg_get_screen_size() + } + // TODO windows, linux, etc + return Size{} +} + +fn C.WaitMessage() + +/* +pub fn wait_events() { + unsafe { + $if macos { + #NSEvent *event = [NSApp nextEventMatchingMask:NSEventMaskAny + #untilDate:[NSDate distantFuture] + #inMode:NSDefaultRunLoopMode + #dequeue:YES]; + #[NSApp sendEvent:event]; + } + $if windows { + C.WaitMessage() + } + } +} +*/ + +// TODO: Fix alpha +pub fn (ctx &Context) draw_rect(x f32, y f32, w f32, h f32, c gx.Color) { + $if macos { + if ctx.native_rendering { + C.darwin_draw_rect(x, ctx.height - (y + h), w, h, c) + return + } + } + if c.a != 255 { + sgl.load_pipeline(ctx.timage_pip) + } + sgl.c4b(c.r, c.g, c.b, c.a) + sgl.begin_quads() + sgl.v2f(x * ctx.scale, y * ctx.scale) + sgl.v2f((x + w) * ctx.scale, y * ctx.scale) + sgl.v2f((x + w) * ctx.scale, (y + h) * ctx.scale) + sgl.v2f(x * ctx.scale, (y + h) * ctx.scale) + sgl.end() +} diff --git a/v_windows/v/vlib/gg/gg.v b/v_windows/v/vlib/gg/gg.v new file mode 100644 index 0000000..d1dbaa4 --- /dev/null +++ b/v_windows/v/vlib/gg/gg.v @@ -0,0 +1,750 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license that can be found in the LICENSE file. + +module gg + +import gx +import sokol.sapp +import sokol.sgl +import sokol.gfx +import math + +pub type FNCb = fn (data voidptr) + +pub type FNEvent = fn (e &Event, data voidptr) + +pub type FNFail = fn (msg string, data voidptr) + +pub type FNKeyDown = fn (c KeyCode, m Modifier, data voidptr) + +pub type FNKeyUp = fn (c KeyCode, m Modifier, data voidptr) + +pub type FNMove = fn (x f32, y f32, data voidptr) + +pub type FNClick = fn (x f32, y f32, button MouseButton, data voidptr) + +pub type FNUnClick = fn (x f32, y f32, button MouseButton, data voidptr) + +pub type FNChar = fn (c u32, data voidptr) + +pub struct Config { +pub: + width int + height int + use_ortho bool // unused, still here just for backwards compatibility + retina bool + resizable bool + user_data voidptr + font_size int + create_window bool + // window_user_ptr voidptr + window_title string + borderless_window bool + always_on_top bool + bg_color gx.Color + init_fn FNCb = voidptr(0) + frame_fn FNCb = voidptr(0) + native_frame_fn FNCb = voidptr(0) + cleanup_fn FNCb = voidptr(0) + fail_fn FNFail = voidptr(0) + // + event_fn FNEvent = voidptr(0) + quit_fn FNEvent = voidptr(0) + // + keydown_fn FNKeyDown = voidptr(0) + keyup_fn FNKeyUp = voidptr(0) + char_fn FNChar = voidptr(0) + // + move_fn FNMove = voidptr(0) + click_fn FNClick = voidptr(0) + unclick_fn FNUnClick = voidptr(0) + leave_fn FNEvent = voidptr(0) + enter_fn FNEvent = voidptr(0) + resized_fn FNEvent = voidptr(0) + scroll_fn FNEvent = voidptr(0) + // wait_events bool // set this to true for UIs, to save power + fullscreen bool + scale f32 = 1.0 + sample_count int + // ved needs this + // init_text bool + font_path string + custom_bold_font_path string + ui_mode bool // refreshes only on events to save CPU usage + // font bytes for embedding + font_bytes_normal []byte + font_bytes_bold []byte + font_bytes_mono []byte + font_bytes_italic []byte + native_rendering bool // Cocoa on macOS/iOS, GDI+ on Windows +} + +pub struct PenConfig { + color gx.Color + line_type PenLineType = .solid + thickness int = 1 +} + +pub struct Size { +pub: + width int + height int +} + +fn gg_frame_fn(user_data voidptr) { + mut ctx := unsafe { &Context(user_data) } + ctx.frame++ + if ctx.config.frame_fn == voidptr(0) { + return + } + if ctx.native_rendering { + // return + } + + if ctx.ui_mode && !ctx.needs_refresh { + // Draw 3 more frames after the "stop refresh" command + ctx.ticks++ + if ctx.ticks > 3 { + return + } + } + ctx.config.frame_fn(ctx.config.user_data) + ctx.needs_refresh = false +} + +pub fn (mut ctx Context) refresh_ui() { + ctx.needs_refresh = true + ctx.ticks = 0 +} + +fn gg_event_fn(ce &C.sapp_event, user_data voidptr) { + // e := unsafe { &sapp.Event(ce) } + mut e := unsafe { &Event(ce) } + mut g := unsafe { &Context(user_data) } + if g.ui_mode { + g.refresh_ui() + } + if e.typ == .mouse_down { + bitplace := int(e.mouse_button) + g.mbtn_mask |= byte(1 << bitplace) + g.mouse_buttons = MouseButtons(g.mbtn_mask) + } + if e.typ == .mouse_up { + bitplace := int(e.mouse_button) + g.mbtn_mask &= ~(byte(1 << bitplace)) + g.mouse_buttons = MouseButtons(g.mbtn_mask) + } + if e.typ == .mouse_move && e.mouse_button == .invalid { + if g.mbtn_mask & 0x01 > 0 { + e.mouse_button = .left + } + if g.mbtn_mask & 0x02 > 0 { + e.mouse_button = .right + } + if g.mbtn_mask & 0x04 > 0 { + e.mouse_button = .middle + } + } + g.mouse_pos_x = int(e.mouse_x / g.scale) + g.mouse_pos_y = int(e.mouse_y / g.scale) + g.mouse_dx = int(e.mouse_dx / g.scale) + g.mouse_dy = int(e.mouse_dy / g.scale) + g.scroll_x = int(e.scroll_x / g.scale) + g.scroll_y = int(e.scroll_y / g.scale) + g.key_modifiers = Modifier(e.modifiers) + g.key_repeat = e.key_repeat + if e.typ in [.key_down, .key_up] { + key_idx := int(e.key_code) % key_code_max + prev := g.pressed_keys[key_idx] + next := e.typ == .key_down + g.pressed_keys[key_idx] = next + g.pressed_keys_edge[key_idx] = prev != next + } + if g.config.event_fn != voidptr(0) { + g.config.event_fn(e, g.config.user_data) + } + match e.typ { + .mouse_move { + if g.config.move_fn != voidptr(0) { + g.config.move_fn(e.mouse_x / g.scale, e.mouse_y / g.scale, g.config.user_data) + } + } + .mouse_down { + if g.config.click_fn != voidptr(0) { + g.config.click_fn(e.mouse_x / g.scale, e.mouse_y / g.scale, e.mouse_button, + g.config.user_data) + } + } + .mouse_up { + if g.config.unclick_fn != voidptr(0) { + g.config.unclick_fn(e.mouse_x / g.scale, e.mouse_y / g.scale, e.mouse_button, + g.config.user_data) + } + } + .mouse_leave { + if g.config.leave_fn != voidptr(0) { + g.config.leave_fn(e, g.config.user_data) + } + } + .mouse_enter { + if g.config.enter_fn != voidptr(0) { + g.config.enter_fn(e, g.config.user_data) + } + } + .mouse_scroll { + if g.config.scroll_fn != voidptr(0) { + g.config.scroll_fn(e, g.config.user_data) + } + } + .key_down { + if g.config.keydown_fn != voidptr(0) { + g.config.keydown_fn(e.key_code, Modifier(e.modifiers), g.config.user_data) + } + } + .key_up { + if g.config.keyup_fn != voidptr(0) { + g.config.keyup_fn(e.key_code, Modifier(e.modifiers), g.config.user_data) + } + } + .char { + if g.config.char_fn != voidptr(0) { + g.config.char_fn(e.char_code, g.config.user_data) + } + } + .resized { + if g.config.resized_fn != voidptr(0) { + g.config.resized_fn(e, g.config.user_data) + } + } + .quit_requested { + if g.config.quit_fn != voidptr(0) { + g.config.quit_fn(e, g.config.user_data) + } + } + else { + // dump(e) + } + } +} + +fn gg_cleanup_fn(user_data voidptr) { + mut g := unsafe { &Context(user_data) } + if g.config.cleanup_fn != voidptr(0) { + g.config.cleanup_fn(g.config.user_data) + } +} + +fn gg_fail_fn(msg &char, user_data voidptr) { + mut g := unsafe { &Context(user_data) } + vmsg := unsafe { tos3(msg) } + if g.config.fail_fn != voidptr(0) { + g.config.fail_fn(vmsg, g.config.user_data) + } else { + eprintln('gg error: $vmsg') + } +} + +pub fn (gg &Context) run() { + sapp.run(&gg.window) +} + +// quit closes the context window and exits the event loop for it +pub fn (ctx &Context) quit() { + sapp.request_quit() // does not require ctx right now, but sokol multi-window might in the future +} + +pub fn (mut ctx Context) set_bg_color(c gx.Color) { + ctx.clear_pass = gfx.create_clear_pass(f32(c.r) / 255.0, f32(c.g) / 255.0, f32(c.b) / 255.0, + f32(c.a) / 255.0) +} + +[inline] +pub fn (ctx &Context) draw_square(x f32, y f32, s f32, c gx.Color) { + ctx.draw_rect(x, y, s, s, c) +} + +[inline] +pub fn (ctx &Context) set_pixel(x f32, y f32, c gx.Color) { + ctx.draw_square(x, y, 1, c) +} + +pub fn (ctx &Context) set_pixels(points []f32, c gx.Color) { + assert points.len % 2 == 0 + len := points.len / 2 + + if c.a != 255 { + sgl.load_pipeline(ctx.timage_pip) + } + + sgl.c4b(c.r, c.g, c.b, c.a) + sgl.begin_quads() + for i in 0 .. len { + x, y := points[i * 2], points[i * 2 + 1] + + sgl.v2f(x * ctx.scale, y * ctx.scale) + sgl.v2f((x + 1) * ctx.scale, y * ctx.scale) + sgl.v2f((x + 1) * ctx.scale, (y + 1) * ctx.scale) + sgl.v2f(x * ctx.scale, (y + 1) * ctx.scale) + } + sgl.end() +} + +pub fn (ctx &Context) draw_triangle(x f32, y f32, x2 f32, y2 f32, x3 f32, y3 f32, c gx.Color) { + if c.a != 255 { + sgl.load_pipeline(ctx.timage_pip) + } + sgl.c4b(c.r, c.g, c.b, c.a) + sgl.begin_quads() + sgl.v2f(x * ctx.scale, y * ctx.scale) + sgl.v2f(x2 * ctx.scale, y2 * ctx.scale) + sgl.v2f(x3 * ctx.scale, y3 * ctx.scale) + sgl.end() +} + +pub fn (ctx &Context) draw_empty_rect(x f32, y f32, w f32, h f32, c gx.Color) { + if c.a != 255 { + sgl.load_pipeline(ctx.timage_pip) + } + sgl.c4b(c.r, c.g, c.b, c.a) + sgl.begin_line_strip() + sgl.v2f(x * ctx.scale, y * ctx.scale) + sgl.v2f((x + w) * ctx.scale, y * ctx.scale) + sgl.v2f((x + w) * ctx.scale, (y + h) * ctx.scale) + sgl.v2f(x * ctx.scale, (y + h) * ctx.scale) + sgl.v2f(x * ctx.scale, (y - 1) * ctx.scale) + sgl.end() +} + +[inline] +pub fn (ctx &Context) draw_empty_square(x f32, y f32, s f32, c gx.Color) { + ctx.draw_empty_rect(x, y, s, s, c) +} + +pub fn (ctx &Context) draw_circle(x f32, y f32, r f32, c gx.Color) { + ctx.draw_circle_with_segments(x, y, r, 10, c) +} + +pub fn (ctx &Context) draw_circle_with_segments(x f32, y f32, r f32, segments int, c gx.Color) { + if c.a != 255 { + sgl.load_pipeline(ctx.timage_pip) + } + sgl.c4b(c.r, c.g, c.b, c.a) + nx := x * ctx.scale + ny := y * ctx.scale + nr := r * ctx.scale + mut theta := f32(0) + mut xx := f32(0) + mut yy := f32(0) + sgl.begin_triangle_strip() + for i := 0; i < segments + 1; i++ { + theta = 2.0 * f32(math.pi) * f32(i) / f32(segments) + xx = nr * math.cosf(theta) + yy = nr * math.sinf(theta) + sgl.v2f(xx + nx, yy + ny) + sgl.v2f(nx, ny) + } + sgl.end() +} + +pub fn (ctx &Context) draw_arc_line(x f32, y f32, r int, start_angle f32, arc_angle f32, segments int, c gx.Color) { + if c.a != 255 { + sgl.load_pipeline(ctx.timage_pip) + } + sgl.c4b(c.r, c.g, c.b, c.a) + theta := f32(arc_angle / f32(segments)) + tan_factor := math.tanf(theta) + rad_factor := math.cosf(theta) + nx := x * ctx.scale + ny := y * ctx.scale + mut xx := f32(r * math.cosf(start_angle)) + mut yy := f32(r * math.sinf(start_angle)) + sgl.begin_line_strip() + for i := 0; i < segments + 1; i++ { + sgl.v2f(xx + nx, yy + ny) + tx := -yy + ty := xx + xx += tx * tan_factor + yy += ty * tan_factor + xx *= rad_factor + yy *= rad_factor + } + sgl.end() +} + +pub fn (ctx &Context) draw_arc(x f32, y f32, r int, start_angle f32, arc_angle f32, segments int, c gx.Color) { + if c.a != 255 { + sgl.load_pipeline(ctx.timage_pip) + } + sgl.c4b(c.r, c.g, c.b, c.a) + nx := x * ctx.scale + ny := y * ctx.scale + theta := f32(arc_angle / f32(segments)) + tan_factor := math.tanf(theta) + rad_factor := math.cosf(theta) + mut xx := f32(r * math.cosf(start_angle)) + mut yy := f32(r * math.sinf(start_angle)) + sgl.begin_triangle_strip() + for i := 0; i < segments + 1; i++ { + sgl.v2f(xx + nx, yy + ny) + sgl.v2f(nx, ny) + tx := -yy + ty := xx + xx += tx * tan_factor + yy += ty * tan_factor + xx *= rad_factor + yy *= rad_factor + } + sgl.end() +} + +pub fn (gg &Context) begin() { + if gg.render_text && gg.font_inited { + gg.ft.flush() + } + sgl.defaults() + sgl.matrix_mode_projection() + sgl.ortho(0.0, f32(sapp.width()), f32(sapp.height()), 0.0, -1.0, 1.0) +} + +pub fn (gg &Context) end() { + gfx.begin_default_pass(gg.clear_pass, sapp.width(), sapp.height()) + sgl.draw() + gfx.end_pass() + gfx.commit() + /* + if gg.config.wait_events { + // println('gg: waiting') + wait_events() + } + */ +} + +// resize the context's Window +pub fn (mut ctx Context) resize(width int, height int) { + ctx.width = width + ctx.height = height +} + +// draw_line draws a line between the points provided +pub fn (ctx &Context) draw_line(x f32, y f32, x2 f32, y2 f32, c gx.Color) { + $if macos { + if ctx.native_rendering { + // Make the line more clear on hi dpi screens: draw a rectangle + mut width := math.abs(x2 - x) + mut height := math.abs(y2 - y) + if width == 0 { + width = 1 + } else if height == 0 { + height = 1 + } + ctx.draw_rect(x, y, f32(width), f32(height), c) + return + } + } + if c.a != 255 { + sgl.load_pipeline(ctx.timage_pip) + } + sgl.c4b(c.r, c.g, c.b, c.a) + sgl.begin_line_strip() + sgl.v2f(x * ctx.scale, y * ctx.scale) + sgl.v2f(x2 * ctx.scale, y2 * ctx.scale) + sgl.end() +} + +// draw_line_with_config draws a line between the points provided with the PenConfig +pub fn (ctx &Context) draw_line_with_config(x f32, y f32, x2 f32, y2 f32, config PenConfig) { + if config.color.a != 255 { + sgl.load_pipeline(ctx.timage_pip) + } + + if config.thickness <= 0 { + return + } + + nx := x * ctx.scale + ny := y * ctx.scale + nx2 := x2 * ctx.scale + ny2 := y2 * ctx.scale + + dx := nx2 - nx + dy := ny2 - ny + length := math.sqrtf(math.powf(x2 - x, 2) + math.powf(y2 - y, 2)) + theta := f32(math.atan2(dy, dx)) + + sgl.push_matrix() + + sgl.translate(nx, ny, 0) + sgl.rotate(theta, 0, 0, 1) + sgl.translate(-nx, -ny, 0) + + if config.line_type == .solid { + ctx.draw_rect(x, y, length, config.thickness, config.color) + } else { + size := if config.line_type == .dotted { config.thickness } else { config.thickness * 3 } + space := if size == 1 { 2 } else { size } + + mut available := length + mut start_x := x + + for i := 0; available > 0; i++ { + if i % 2 == 0 { + ctx.draw_rect(start_x, y, size, config.thickness, config.color) + available -= size + start_x += size + continue + } + + available -= space + start_x += space + } + } + + sgl.pop_matrix() +} + +pub fn (ctx &Context) draw_rounded_rect(x f32, y f32, w f32, h f32, radius f32, color gx.Color) { + sgl.c4b(color.r, color.g, color.b, color.a) + sgl.begin_triangle_strip() + mut theta := f32(0) + mut xx := f32(0) + mut yy := f32(0) + r := radius * ctx.scale + nx := x * ctx.scale + ny := y * ctx.scale + width := w * ctx.scale + height := h * ctx.scale + segments := 2 * math.pi * r + segdiv := segments / 4 + rb := 0 + lb := int(rb + segdiv) + lt := int(lb + segdiv) + rt := int(lt + segdiv) + // left top + lx := nx + r + ly := ny + r + for i in lt .. rt { + theta = 2 * f32(math.pi) * f32(i) / segments + xx = r * math.cosf(theta) + yy = r * math.sinf(theta) + sgl.v2f(xx + lx, yy + ly) + sgl.v2f(lx, ly) + } + // right top + mut rx := nx + width - r + mut ry := ny + r + for i in rt .. int(segments) { + theta = 2 * f32(math.pi) * f32(i) / segments + xx = r * math.cosf(theta) + yy = r * math.sinf(theta) + sgl.v2f(xx + rx, yy + ry) + sgl.v2f(rx, ry) + } + // right bottom + mut rbx := rx + mut rby := ny + height - r + for i in rb .. lb { + theta = 2 * f32(math.pi) * f32(i) / segments + xx = r * math.cosf(theta) + yy = r * math.sinf(theta) + sgl.v2f(xx + rbx, yy + rby) + sgl.v2f(rbx, rby) + } + // left bottom + mut lbx := lx + mut lby := ny + height - r + for i in lb .. lt { + theta = 2 * f32(math.pi) * f32(i) / segments + xx = r * math.cosf(theta) + yy = r * math.sinf(theta) + sgl.v2f(xx + lbx, yy + lby) + sgl.v2f(lbx, lby) + } + sgl.v2f(lx + xx, ly) + sgl.v2f(lx, ly) + sgl.end() + sgl.begin_quads() + sgl.v2f(lx, ly) + sgl.v2f(rx, ry) + sgl.v2f(rbx, rby) + sgl.v2f(lbx, lby) + sgl.end() +} + +pub fn (ctx &Context) draw_empty_rounded_rect(x f32, y f32, w f32, h f32, radius f32, border_color gx.Color) { + mut theta := f32(0) + mut xx := f32(0) + mut yy := f32(0) + r := radius * ctx.scale + nx := x * ctx.scale + ny := y * ctx.scale + width := w * ctx.scale + height := h * ctx.scale + segments := 2 * math.pi * r + segdiv := segments / 4 + rb := 0 + lb := int(rb + segdiv) + lt := int(lb + segdiv) + rt := int(lt + segdiv) + sgl.c4b(border_color.r, border_color.g, border_color.b, border_color.a) + sgl.begin_line_strip() + // left top + lx := nx + r + ly := ny + r + for i in lt .. rt { + theta = 2 * f32(math.pi) * f32(i) / segments + xx = r * math.cosf(theta) + yy = r * math.sinf(theta) + sgl.v2f(xx + lx, yy + ly) + } + // right top + mut rx := nx + width - r + mut ry := ny + r + for i in rt .. int(segments) { + theta = 2 * f32(math.pi) * f32(i) / segments + xx = r * math.cosf(theta) + yy = r * math.sinf(theta) + sgl.v2f(xx + rx, yy + ry) + } + // right bottom + mut rbx := rx + mut rby := ny + height - r + for i in rb .. lb { + theta = 2 * f32(math.pi) * f32(i) / segments + xx = r * math.cosf(theta) + yy = r * math.sinf(theta) + sgl.v2f(xx + rbx, yy + rby) + } + // left bottom + mut lbx := lx + mut lby := ny + height - r + for i in lb .. lt { + theta = 2 * f32(math.pi) * f32(i) / segments + xx = r * math.cosf(theta) + yy = r * math.sinf(theta) + sgl.v2f(xx + lbx, yy + lby) + } + sgl.v2f(lx + xx, ly) + sgl.end() +} + +// draw_convex_poly draws a convex polygon, given an array of points, and a color. +// Note that the points must be given in clockwise order. +pub fn (ctx &Context) draw_convex_poly(points []f32, c gx.Color) { + assert points.len % 2 == 0 + len := points.len / 2 + assert len >= 3 + + if c.a != 255 { + sgl.load_pipeline(ctx.timage_pip) + } + sgl.c4b(c.r, c.g, c.b, c.a) + + sgl.begin_triangle_strip() + x0 := points[0] * ctx.scale + y0 := points[1] * ctx.scale + for i in 1 .. (len / 2 + 1) { + sgl.v2f(x0, y0) + sgl.v2f(points[i * 4 - 2] * ctx.scale, points[i * 4 - 1] * ctx.scale) + sgl.v2f(points[i * 4] * ctx.scale, points[i * 4 + 1] * ctx.scale) + } + + if len % 2 == 0 { + sgl.v2f(points[2 * len - 2] * ctx.scale, points[2 * len - 1] * ctx.scale) + } + sgl.end() +} + +// draw_empty_poly - draws the borders of a polygon, given an array of points, and a color. +// Note that the points must be given in clockwise order. +pub fn (ctx &Context) draw_empty_poly(points []f32, c gx.Color) { + assert points.len % 2 == 0 + len := points.len / 2 + assert len >= 3 + + if c.a != 255 { + sgl.load_pipeline(ctx.timage_pip) + } + sgl.c4b(c.r, c.g, c.b, c.a) + + sgl.begin_line_strip() + for i in 0 .. len { + sgl.v2f(points[2 * i] * ctx.scale, points[2 * i + 1] * ctx.scale) + } + sgl.v2f(points[0] * ctx.scale, points[1] * ctx.scale) + sgl.end() +} + +// draw_cubic_bezier draws a cubic Bézier curve, also known as a spline, from four points. +// The four points is provided as one `points` array which contains a stream of point pairs (x and y coordinates). +// Thus a cubic Bézier could be declared as: `points := [x1, y1, control_x1, control_y1, control_x2, control_y2, x2, y2]`. +// Please see `draw_cubic_bezier_in_steps` to control the amount of steps (segments) used to draw the curve. +pub fn (ctx &Context) draw_cubic_bezier(points []f32, c gx.Color) { + ctx.draw_cubic_bezier_in_steps(points, u32(30 * ctx.scale), c) +} + +// draw_cubic_bezier_in_steps draws a cubic Bézier curve, also known as a spline, from four points. +// The smoothness of the curve can be controlled with the `steps` parameter. `steps` determines how many iterations is +// taken to draw the curve. +// The four points is provided as one `points` array which contains a stream of point pairs (x and y coordinates). +// Thus a cubic Bézier could be declared as: `points := [x1, y1, control_x1, control_y1, control_x2, control_y2, x2, y2]`. +pub fn (ctx &Context) draw_cubic_bezier_in_steps(points []f32, steps u32, c gx.Color) { + assert steps > 0 + assert points.len == 8 + + if c.a != 255 { + sgl.load_pipeline(ctx.timage_pip) + } + sgl.c4b(c.r, c.g, c.b, c.a) + + sgl.begin_line_strip() + + p1_x, p1_y := points[0], points[1] + p2_x, p2_y := points[6], points[7] + + ctrl_p1_x, ctrl_p1_y := points[2], points[3] + ctrl_p2_x, ctrl_p2_y := points[4], points[5] + + // The constant 3 is actually points.len() - 1; + + step := f32(1.0) / steps + sgl.v2f(p1_x * ctx.scale, p1_y * ctx.scale) + for u := f32(0.0); u <= f32(1.0); u += step { + pow_2_u := u * u + pow_3_u := pow_2_u * u + + x := pow_3_u * (p2_x + 3 * (ctrl_p1_x - ctrl_p2_x) - p1_x) + + 3 * pow_2_u * (p1_x - 2 * ctrl_p1_x + ctrl_p2_x) + 3 * u * (ctrl_p1_x - p1_x) + p1_x + + y := pow_3_u * (p2_y + 3 * (ctrl_p1_y - ctrl_p2_y) - p1_y) + + 3 * pow_2_u * (p1_y - 2 * ctrl_p1_y + ctrl_p2_y) + 3 * u * (ctrl_p1_y - p1_y) + p1_y + + sgl.v2f(x * ctx.scale, y * ctx.scale) + } + sgl.v2f(p2_x * ctx.scale, p2_y * ctx.scale) + + sgl.end() +} + +// window_size returns the `Size` of the active window +pub fn window_size() Size { + s := dpi_scale() + return Size{int(sapp.width() / s), int(sapp.height() / s)} +} + +// window_size_real_pixels returns the `Size` of the active window without scale +pub fn window_size_real_pixels() Size { + return Size{sapp.width(), sapp.height()} +} + +pub fn dpi_scale() f32 { + mut s := sapp.dpi_scale() + $if android { + s *= android_dpi_scale() + } + // NB: on older X11, `Xft.dpi` from ~/.Xresources, that sokol uses, + // may not be set which leads to sapp.dpi_scale reporting incorrectly 0.0 + if s < 0.1 { + s = 1.0 + } + return s +} diff --git a/v_windows/v/vlib/gg/gg_android.c.v b/v_windows/v/vlib/gg/gg_android.c.v new file mode 100644 index 0000000..23450f2 --- /dev/null +++ b/v_windows/v/vlib/gg/gg_android.c.v @@ -0,0 +1,37 @@ +module gg + +import sokol.sapp + +#include <android/configuration.h> +#include <android/native_activity.h> + +fn C.AConfiguration_new() voidptr +fn C.AConfiguration_fromAssetManager(voidptr, voidptr) +fn C.AConfiguration_getDensity(voidptr) u32 +fn C.AConfiguration_delete(voidptr) + +struct C.AAssetManager {} + +// See https://developer.android.com/ndk/reference/struct/a-native-activity for more info. +struct C.ANativeActivity { +pub: + assetManager voidptr // Pointer to the Asset Manager instance for the application. (AAssetManager *) + callbacks voidptr // Pointer to the callback function table of the native application. (struct ANativeActivityCallbacks *) + clazz voidptr // The NativeActivity object handle. + env voidptr // JNI context for the main thread of the app. + externalDataPath &char // Path to this application's external (removable/mountable) data directory. + instance voidptr // This is the native instance of the application. + internalDataPath &char // Path to this application's internal data directory. + obbPath &char // Available starting with Honeycomb: path to the directory containing the application's OBB files (if any). + sdkVersion int // The platform's SDK version code. + vm voidptr // The global handle on the process's Java VM +} + +pub fn android_dpi_scale() f32 { + config := C.AConfiguration_new() + activity := &C.ANativeActivity(sapp.android_get_native_activity()) + C.AConfiguration_fromAssetManager(config, activity.assetManager) + density := C.AConfiguration_getDensity(config) + C.AConfiguration_delete(config) + return f32(density) / 160 +} diff --git a/v_windows/v/vlib/gg/gg_darwin.c.v b/v_windows/v/vlib/gg/gg_darwin.c.v new file mode 100644 index 0000000..d95b8c4 --- /dev/null +++ b/v_windows/v/vlib/gg/gg_darwin.c.v @@ -0,0 +1,23 @@ +module gg + +#include "@VEXEROOT/vlib/gg/gg_darwin.m" + +fn C.gg_get_screen_size() Size + +fn C.darwin_draw_string(x int, y int, s string, cfg voidptr) + +fn C.darwin_text_width(s string) int + +fn C.darwin_text_width_runes(r []rune) int + +fn C.darwin_window_refresh() + +fn C.darwin_draw_rect(f32, f32, f32, f32, voidptr) + +fn C.darwin_create_image(path string) Image + +fn C.darwin_draw_image(f32, f32, f32, f32, &Image) + +fn C.darwin_draw_circle(f32, f32, f32, voidptr) + +//, gx.Color c) diff --git a/v_windows/v/vlib/gg/gg_darwin.m b/v_windows/v/vlib/gg/gg_darwin.m new file mode 100644 index 0000000..3ec883d --- /dev/null +++ b/v_windows/v/vlib/gg/gg_darwin.m @@ -0,0 +1,125 @@ +#include <Cocoa/Cocoa.h> + +NSColor* nscolor(gx__Color c) { + float red= (float)c.r / 255.0f; + float green= (float)c.g / 255.0f; + float blue= (float)c.b / 255.0f; + return [NSColor colorWithDeviceRed:red green:green blue:blue alpha:1.0f]; +} + +NSString* nsstring(string s) { + return [ [ NSString alloc ] initWithBytesNoCopy:s.str length:s.len + encoding:NSUTF8StringEncoding freeWhenDone: false]; +} + + +gg__Size gg_get_screen_size() { + NSScreen *screen = [NSScreen mainScreen]; + NSDictionary *description = [screen deviceDescription]; + NSSize displayPixelSize = [[description objectForKey:NSDeviceSize] sizeValue]; + CGSize displayPhysicalSize = CGDisplayScreenSize( + [[description objectForKey:@"NSScreenNumber"] unsignedIntValue]); + gg__Size res; + res.width = displayPixelSize.width; + res.height = displayPixelSize.height; + return res; +} + +void darwin_draw_string(int x, int y, string s, gx__TextCfg cfg) { + NSFont* font = [NSFont userFontOfSize: 0]; //cfg.size]; + // # NSFont* font = [NSFont fontWithName:@"Roboto Mono" size:cfg.size]; + if (cfg.mono) { + // # font = [NSFont fontWithName:@"Roboto Mono" size:cfg.size]; + font = [NSFont fontWithName:@"Menlo" size:cfg.size-5]; + } +if (cfg.bold) { + font = [[NSFontManager sharedFontManager] convertFont:font toHaveTrait:NSBoldFontMask]; +} + + + NSDictionary* attr = @{ +NSForegroundColorAttributeName: nscolor(cfg.color), +//NSParagraphStyleAttributeName: paragraphStyle, +NSFontAttributeName: font, +}; + [nsstring(s) drawAtPoint:NSMakePoint(x,y-15) withAttributes:attr]; +} + +int darwin_text_width(string s) { + // println('text_width "$s" len=$s.len') + NSString* n = @""; + if (s.len == 1) { + // println('len=1') + n=[NSString stringWithFormat:@"%c" , s.str[0]]; + } + else { + n = nsstring(s); + } + /* + # if (!defaultFont){ + # defaultFont = [NSFont userFontOfSize: ui__DEFAULT_FONT_SIZE]; + # } + # NSDictionary *attrs = @{ + # NSFontAttributeName: defaultFont, + # }; + */ + NSSize size = [n sizeWithAttributes:nil]; + // # printf("!!!%f\n", ceil(size.width)); + return (int)(ceil(size.width)); +} + +void darwin_draw_rect(float x, float y, float width, float height, gx__Color c) { + NSColor* color = nscolor(c); + NSRect rect = NSMakeRect(x, y, width, height); + [color setFill]; + NSRectFill(rect); +} + + +void darwin_window_refresh() { + //[g_view setNeedsDisplay:YES]; + // update UI on the main thread TODO separate fn + + dispatch_async(dispatch_get_main_queue(), ^{ + [g_view setNeedsDisplay:YES]; + }); + + //puts("refresh"); + //[g_view drawRect:NSMakeRect(0,0,2000,2000)]; + //[[NSGraphicsContext currentContext] flushGraphics]; +} + +gg__Image darwin_create_image(string path_) { + // file = file.trim_space() + NSString* path = nsstring(path_); + NSImage* img = [[NSImage alloc] initWithContentsOfFile:path]; + if (img == 0) { + } + NSSize size = [img size]; + gg__Image res; + res.width = size.width; + res.height = size.height; + res.path = path_; + res.ok = true; + //printf("inited img width=%d\n", res.width) ; + // need __brige_retained so that the pointer is not freed by ARC + res.data = (__bridge_retained voidptr)(img); + return res; +} + +void darwin_draw_image(float x, float y, float w, float h, gg__Image* img) { + NSImage* i= (__bridge NSImage*)(img->data); + [i drawInRect:NSMakeRect(x,y,w,h)]; +} + +void darwin_draw_circle(float x, float y, float d, gx__Color color) { + NSColor* c = nscolor(color); + NSRect rect = NSMakeRect(x, y, d * 2, d * 2); + NSBezierPath* circlePath = [NSBezierPath bezierPath]; + [circlePath appendBezierPathWithOvalInRect: rect]; + [c setFill]; + // [circlePath stroke]; + [circlePath fill]; + // NSRectFill(rect); +} + diff --git a/v_windows/v/vlib/gg/image.c.v b/v_windows/v/vlib/gg/image.c.v new file mode 100644 index 0000000..352eac4 --- /dev/null +++ b/v_windows/v/vlib/gg/image.c.v @@ -0,0 +1,169 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license that can be found in the LICENSE file. +module gg + +import os +import stbi +import sokol.gfx + +fn C.sg_isvalid() bool + +// TODO return ?Image +pub fn (mut ctx Context) create_image(file string) Image { + // println('\ncreate_image("$file")') + if !os.exists(file) { + return Image{} + } + $if macos { + if ctx.native_rendering { + // return C.darwin_create_image(file) + mut img := C.darwin_create_image(file) + // println('created macos image: $img.path w=$img.width') + // C.printf('p = %p\n', img.data) + img.id = ctx.image_cache.len + ctx.image_cache << img + return img + } + } + if !C.sg_isvalid() { + // Sokol is not initialized yet, add stbi object to a queue/cache + // ctx.image_queue << file + stb_img := stbi.load(file) or { return Image{} } + img := Image{ + width: stb_img.width + height: stb_img.height + nr_channels: stb_img.nr_channels + ok: false + data: stb_img.data + ext: stb_img.ext + path: file + id: ctx.image_cache.len + } + ctx.image_cache << img + return img + } + mut img := create_image(file) + img.id = ctx.image_cache.len + ctx.image_cache << img + return img +} + +pub fn (mut img Image) init_sokol_image() &Image { + // println('\n init sokol image $img.path ok=$img.simg_ok') + mut img_desc := C.sg_image_desc{ + width: img.width + height: img.height + num_mipmaps: 0 + wrap_u: .clamp_to_edge + wrap_v: .clamp_to_edge + label: img.path.str + d3d11_texture: 0 + } + img_desc.data.subimage[0][0] = C.sg_range{ + ptr: img.data + size: size_t(img.nr_channels * img.width * img.height) + } + img.simg = C.sg_make_image(&img_desc) + img.simg_ok = true + img.ok = true + return img +} + +// draw_image draws the provided image onto the screen +pub fn (ctx &Context) draw_image(x f32, y f32, width f32, height f32, img_ &Image) { + $if macos { + if img_.id >= ctx.image_cache.len { + eprintln('gg: draw_image() bad img id $img_.id (img cache len = $ctx.image_cache.len)') + return + } + if ctx.native_rendering { + if img_.width == 0 { + return + } + if !os.exists(img_.path) { + return + } + C.darwin_draw_image(x, ctx.height - (y + height), width, height, img_) + return + } + } + + ctx.draw_image_with_config( + img: img_ + img_rect: Rect{x, y, width, height} + part_rect: Rect{0, 0, img_.width, img_.height} + ) +} + +// new_streaming_image returns a cached `image_idx` of a special image, that +// can be updated *each frame* by calling: gg.update_pixel_data(image_idx, buf) +// ... where buf is a pointer to the actual pixel data for the image. +// NB: you still need to call app.gg.draw_image after that, to actually draw it. +pub fn (mut ctx Context) new_streaming_image(w int, h int, channels int, sicfg StreamingImageConfig) int { + mut img := Image{} + img.width = w + img.height = h + img.nr_channels = channels // 4 bytes per pixel for .rgba8, see pixel_format + mut img_desc := C.sg_image_desc{ + width: img.width + height: img.height + pixel_format: sicfg.pixel_format + num_slices: 1 + num_mipmaps: 1 + usage: .stream + wrap_u: sicfg.wrap_u + wrap_v: sicfg.wrap_v + min_filter: sicfg.min_filter + mag_filter: sicfg.mag_filter + label: img.path.str + } + // Sokol requires that streamed images have NO .ptr/.size initially: + img_desc.data.subimage[0][0] = C.sg_range{ + ptr: 0 + size: size_t(0) + } + img.simg = C.sg_make_image(&img_desc) + img.simg_ok = true + img.ok = true + img_idx := ctx.cache_image(img) + return img_idx +} + +// update_pixel_data is a helper for working with image streams (i.e. images, +// that are updated dynamically by the CPU on each frame) +pub fn (mut ctx Context) update_pixel_data(cached_image_idx int, buf &byte) { + mut image := ctx.get_cached_image_by_idx(cached_image_idx) + image.update_pixel_data(buf) +} + +pub fn (mut img Image) update_pixel_data(buf &byte) { + mut data := C.sg_image_data{} + data.subimage[0][0].ptr = buf + data.subimage[0][0].size = size_t(img.width * img.height * img.nr_channels) + gfx.update_image(img.simg, &data) +} + +// TODO copypasta +pub fn (mut ctx Context) create_image_with_size(file string, width int, height int) Image { + if !C.sg_isvalid() { + // Sokol is not initialized yet, add stbi object to a queue/cache + // ctx.image_queue << file + stb_img := stbi.load(file) or { return Image{} } + img := Image{ + width: width + height: height + nr_channels: stb_img.nr_channels + ok: false + data: stb_img.data + ext: stb_img.ext + path: file + id: ctx.image_cache.len + } + ctx.image_cache << img + return img + } + mut img := create_image(file) + img.id = ctx.image_cache.len + ctx.image_cache << img + return img +} diff --git a/v_windows/v/vlib/gg/image.v b/v_windows/v/vlib/gg/image.v new file mode 100644 index 0000000..e4c2776 --- /dev/null +++ b/v_windows/v/vlib/gg/image.v @@ -0,0 +1,223 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license that can be found in the LICENSE file. +module gg + +// import sokol.sapp +import gx +import sokol.gfx +import os +import sokol +import sokol.sgl +import stbi + +[heap] +pub struct Image { +pub mut: + id int + width int + height int + nr_channels int + ok bool + data voidptr + ext string + simg_ok bool + simg C.sg_image + path string +} + +// DrawImageConfig struct defines the various options +// that can be used to draw an image onto the screen +pub struct DrawImageConfig { +pub: + flip_x bool + flip_y bool + img &Image = voidptr(0) + img_id int + img_rect Rect // defines the size and position on image when rendering to the screen + part_rect Rect // defines the size and position of part of the image to use when rendering + rotate int // amount to rotate the image in degrees + z f32 + color gx.Color = gx.white +} + +pub struct Rect { +pub: + x f32 + y f32 + width f32 + height f32 +} + +// TODO remove this +fn create_image(file string) Image { + if !os.exists(file) { + println('gg.create_image(): file not found: $file') + return Image{} // none + } + stb_img := stbi.load(file) or { return Image{} } + mut img := Image{ + width: stb_img.width + height: stb_img.height + nr_channels: stb_img.nr_channels + ok: stb_img.ok + data: stb_img.data + ext: stb_img.ext + path: file + } + img.init_sokol_image() + return img +} + +pub fn (mut ctx Context) create_image_from_memory(buf &byte, bufsize int) Image { + stb_img := stbi.load_from_memory(buf, bufsize) or { return Image{} } + mut img := Image{ + width: stb_img.width + height: stb_img.height + nr_channels: stb_img.nr_channels + ok: stb_img.ok + data: stb_img.data + ext: stb_img.ext + id: ctx.image_cache.len + } + ctx.image_cache << img + return img +} + +pub fn (mut ctx Context) create_image_from_byte_array(b []byte) Image { + return ctx.create_image_from_memory(b.data, b.len) +} + +pub fn (mut ctx Context) cache_image(img Image) int { + ctx.image_cache << img + image_idx := ctx.image_cache.len - 1 + ctx.image_cache[image_idx].id = image_idx + return image_idx +} + +pub fn (mut ctx Context) get_cached_image_by_idx(image_idx int) &Image { + return &ctx.image_cache[image_idx] +} + +pub struct StreamingImageConfig { + pixel_format gfx.PixelFormat = .rgba8 + wrap_u gfx.Wrap = .clamp_to_edge + wrap_v gfx.Wrap = .clamp_to_edge + min_filter gfx.Filter = .linear + mag_filter gfx.Filter = .linear + num_mipmaps int = 1 + num_slices int = 1 +} + +// draw_image_with_config takes in a config that details how the +// provided image should be drawn onto the screen +pub fn (ctx &Context) draw_image_with_config(config DrawImageConfig) { + id := if !isnil(config.img) { config.img.id } else { config.img_id } + if id >= ctx.image_cache.len { + eprintln('gg: draw_image() bad img id $id (img cache len = $ctx.image_cache.len)') + return + } + + img := ctx.image_cache[id] + if !img.simg_ok { + return + } + + mut img_rect := config.img_rect + if img_rect.width == 0 && img_rect.height == 0 { + img_rect = Rect{img_rect.x, img_rect.y, img.width, img.height} + } + + mut part_rect := config.part_rect + if part_rect.width == 0 && part_rect.height == 0 { + part_rect = Rect{part_rect.x, part_rect.y, img.width, img.height} + } + + u0 := part_rect.x / img.width + v0 := part_rect.y / img.height + u1 := (part_rect.x + part_rect.width) / img.width + v1 := (part_rect.y + part_rect.height) / img.height + x0 := img_rect.x * ctx.scale + y0 := img_rect.y * ctx.scale + x1 := (img_rect.x + img_rect.width) * ctx.scale + mut y1 := (img_rect.y + img_rect.height) * ctx.scale + if img_rect.height == 0 { + scale := f32(img.width) / f32(img_rect.width) + y1 = f32(img_rect.y + int(f32(img.height) / scale)) * ctx.scale + } + + flip_x := config.flip_x + flip_y := config.flip_y + + mut u0f := if !flip_x { u0 } else { u1 } + mut u1f := if !flip_x { u1 } else { u0 } + mut v0f := if !flip_y { v0 } else { v1 } + mut v1f := if !flip_y { v1 } else { v0 } + + sgl.load_pipeline(ctx.timage_pip) + sgl.enable_texture() + sgl.texture(img.simg) + + if config.rotate != 0 { + width := img_rect.width * ctx.scale + height := (if img_rect.height > 0 { img_rect.height } else { img.height }) * ctx.scale + + sgl.push_matrix() + sgl.translate(x0 + (width / 2), y0 + (height / 2), 0) + sgl.rotate(sgl.rad(-config.rotate), 0, 0, 1) + sgl.translate(-x0 - (width / 2), -y0 - (height / 2), 0) + } + + sgl.begin_quads() + sgl.c4b(config.color.r, config.color.g, config.color.b, config.color.a) + sgl.v3f_t2f(x0, y0, config.z, u0f, v0f) + sgl.v3f_t2f(x1, y0, config.z, u1f, v0f) + sgl.v3f_t2f(x1, y1, config.z, u1f, v1f) + sgl.v3f_t2f(x0, y1, config.z, u0f, v1f) + sgl.end() + + if config.rotate != 0 { + sgl.pop_matrix() + } + + sgl.disable_texture() +} + +// Draw part of an image using uv coordinates +// img_rect is the size and position (in pixels on screen) of the displayed rectangle (ie the draw_image args) +// part_rect is the size and position (in absolute pixels in the image) of the wanted part +// eg. On a 600*600 context, to display only the first 400*400 pixels of a 2000*2000 image +// on the entire context surface, call : +// draw_image_part(Rect{0, 0, 600, 600}, Rect{0, 0, 400, 400}, img) +pub fn (ctx &Context) draw_image_part(img_rect Rect, part_rect Rect, img_ &Image) { + ctx.draw_image_with_config( + img: img_ + img_rect: img_rect + part_rect: part_rect + ) +} + +// draw_image_flipped draws the provided image flipped horizontally (use `draw_image_with_config` to flip vertically) +pub fn (ctx &Context) draw_image_flipped(x f32, y f32, width f32, height f32, img_ &Image) { + ctx.draw_image_with_config( + flip_x: true + img: img_ + img_rect: Rect{x, y, width, height} + ) +} + +// draw_image_by_id draws an image by its id +pub fn (ctx &Context) draw_image_by_id(x f32, y f32, width f32, height f32, id int) { + ctx.draw_image_with_config( + img_id: id + img_rect: Rect{x, y, width, height} + ) +} + +// draw_image_3d draws an image with a z depth +pub fn (ctx &Context) draw_image_3d(x f32, y f32, z f32, width f32, height f32, img_ &Image) { + ctx.draw_image_with_config( + img: img_ + img_rect: Rect{x, y, width, height} + z: z + ) +} diff --git a/v_windows/v/vlib/gg/m4/graphic.v b/v_windows/v/vlib/gg/m4/graphic.v new file mode 100644 index 0000000..e134e80 --- /dev/null +++ b/v_windows/v/vlib/gg/m4/graphic.v @@ -0,0 +1,110 @@ +/********************************************************************** +* +* Simply vector/matrix graphic utility +* +* 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: +**********************************************************************/ +module m4 + +import math + +// Translate degrees to radians +[inline] +pub fn rad(deg f32) f32 { + return (math.pi / 180.0) * deg +} + +// Translate radians to degrees +[inline] +pub fn deg(grad f32) f32 { + return (180.0 / math.pi) * grad +} + +// calculate the Orthographic projection matrix +pub fn ortho(left f32, right f32, bottom f32, top f32, z_near f32, z_far f32) Mat4 { + rml := right - left + rpl := right + left + tmb := top - bottom + tpb := top + bottom + fmn := z_far - z_near + fpn := z_far + z_near + if fmn != 0 { + return Mat4{ e: [ + 2 / rml, 0 , 0, -(rpl / rml), + 0 , 2 / tmb, 0, -(tpb / tmb), + 0 , 0, 2 / fmn, -(fpn / fmn), + 0 , 0, 0, 1, + ]! + } + } + return Mat4{ e: [ + 2 / rml, 0 , 0, -(rpl / rml), + 0 , 2 / tmb, 0, -(tpb / tmb), + 0 , 0, 0, 0, + 0 , 0, 0, 1, + ]! + } +} + +// Calculate the perspective matrix using (fov:fov, ar:aspect_ratio ,n:near_pane, f:far_plane) as parameters +pub fn perspective(fov f32, ar f32, n f32, f f32) Mat4 { + ctan := f32(1.0 / math.tan(fov * (f32(math.pi) / 360.0))) // for the FOV we use 360 instead 180 + return Mat4{ e: [ + ctan / ar, 0, 0, 0, + 0, ctan, 0, 0, + 0, 0, (n + f) / (n - f), -1.0, + 0, 0, (2.0 * n * f) / (n - f), 0, + ]! + } +} + +// Calculate the look-at matrix +pub fn look_at(eye Vec4, center Vec4, up Vec4) Mat4 { + f := (center - eye).normalize3() + s := (f % up).normalize3() + u := (s % f) + + return Mat4{ e: [ + /* [0][0] */ s.e[0], + /* [0][1] */ u.e[0], + /* [0][2] */ - f.e[0], + /* [0][3] */ 0, + + /* [1][1] */ s.e[1], + /* [1][1] */ u.e[1], + /* [1][2] */ - f.e[1], + /* [1][3] */ 0, + + /* [2][0] */ s.e[2], + /* [2][1] */ u.e[2], + /* [2][2] */ - f.e[2], + /* [2][3] */ 0, + + /* [3][0] */ - (s * eye), + /* [3][1] */ - (u * eye), + /* [3][2] */ f * eye, + /* [3][3] */ 1, + ]! + } +} + + +// Get the complete transformation matrix for GLSL demos +pub fn calc_tr_matrices(w f32, h f32, rx f32, ry f32, in_scale f32) Mat4 { + proj := perspective(60, w / h, 0.01, 10.0) + view := look_at(Vec4{ e: [f32(0.0), 1.5, 6, 0]! }, Vec4{ e: [f32(0), 0, 0, 0]! }, Vec4{ e: [f32(0), 1.0, 0, 0]! }) + view_proj := view * proj + + rxm := rotate(rad(rx), Vec4{ e: [f32(1), 0, 0, 0]! }) + rym := rotate(rad(ry), Vec4{ e: [f32(0), 1, 0, 0]! }) + + model := rym * rxm + scale_m := scale(Vec4{ e: [in_scale, in_scale, in_scale, 1]! }) + + res := (scale_m * model) * view_proj + return res +} diff --git a/v_windows/v/vlib/gg/m4/m4_test.v b/v_windows/v/vlib/gg/m4/m4_test.v new file mode 100644 index 0000000..90b4640 --- /dev/null +++ b/v_windows/v/vlib/gg/m4/m4_test.v @@ -0,0 +1,235 @@ +import gg.m4 + +pub fn test_m4() { + unsafe { + // Test Mat4 + mut a := m4.Mat4{ e: [ + f32(0), 1, 2, 3, + 4, 5, 6, 7, + 8, 9, 10, 11, + 12, 13, 14, 15, + ]! + } + mut b := m4.Mat4{} + mut c := m4.Mat4{} + + // equal test + assert a.e == [ + f32(0), 1, 2, 3, + 4, 5, 6, 7, + 8, 9, 10, 11, + 12, 13, 14, 15, + ]! + + // copy test + b.copy(a) + assert a.e == b.e + + // test: transpose, scale + assert b.transpose().mul_scalar(2.0).mul_scalar(0.5).transpose().e == a.e + assert b.sum_all() == 120.0 + + // test rows/columns set/get + for i in 0 .. 4 { + b = m4.zero_m4() + b.set_row(i, m4.Vec4{ e: [f32(1.0), 2, 3, 4]! }) + assert b.get_f(0, i) == 1.0 + assert b.get_f(1, i) == 2.0 + assert b.get_f(2, i) == 3.0 + assert b.get_f(3, i) == 4.0 + // println(b) + c = m4.zero_m4() + c.set_col(i, m4.Vec4{ e: [f32(1.0), 2, 3, 4]! }) + assert c.get_f(i, 0) == 1.0 + assert c.get_f(i, 1) == 2.0 + assert c.get_f(i, 2) == 3.0 + assert c.get_f(i, 3) == 4.0 + // println(c) + } + } +} + +fn test_swap_col_row() { + unsafe { + // swap_col / swap_row + b := m4.Mat4{ e: [ + f32(1), 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + 13, 14, 15, 16, + ]! + } + b.swap_col(0, 2) + assert b.e == [ + f32(3), 2, 1, 4, + 7, 6, 5, 8, + 11, 10, 9, 12, + 15, 14, 13, 16, + ]! + b = m4.Mat4{ e: [ + f32(1), 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + 13, 14, 15, 16, + ]! + } + b.swap_row(0, 2) + assert b.e == [ + f32(9), 10, 11, 12, + 5, 6, 7, 8, + 1, 2, 3, 4, + 13, 14, 15, 16, + ]! + } +} + +fn test_sum_sub() { + unsafe { + // test sum/sub + b := m4.unit_m4() + c := m4.unit_m4() + assert m4.sub(m4.add(b, c), b).e == m4.unit_m4().e + assert (b + c - b).e == m4.unit_m4().e + } +} + +fn test_transpose() { + unsafe { + b := m4.Mat4{ e: [ + f32(0), 1, 2, 3, + 4, 5, 6, 7, + 8, 9, 10, 11, + 12, 13, 14, 15, + ]! + } + assert b.transpose().transpose().e == b.e + } +} + +fn test_multiplication() { + unsafe { + b := m4.Mat4{ e: [ + f32(1), 0, 0, 0, + 0, 2, 0, 0, + 0, 0, 3, 0, + 0, 0, 0, 4, + ]! + } + c := m4.Mat4{ e: [ + f32(1), 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + 13, 14, 15, 16, + ]! + } + + assert (c * c).e == [ + f32(90),100,110,120, + 202,228,254,280, + 314,356,398,440, + 426,484,542,600, + ]! + + assert m4.mul(c, c).e == [ + f32(90),100,110,120, + 202,228,254,280, + 314,356,398,440, + 426,484,542,600, + ]! + + assert m4.mul(b, c).e == [ + f32(1), 2, 3, 4, + 10, 12, 14, 16, + 27, 30, 33, 36, + 52, 56, 60, 64, + ]! + + assert (b * c).e == [ + f32(1), 2, 3, 4, + 10, 12, 14, 16, + 27, 30, 33, 36, + 52, 56, 60, 64, + ]! + + assert m4.det(b) == 24 + } +} + +fn test_det() { + unsafe { + b := m4.Mat4{ e: [ + f32(5), 6, 6, 8, + 2, 2, 2, 8, + 6, 6, 2, 8, + 2, 3, 6, 7, + ]! + } + assert m4.det(b) == -8 + + c := m4.Mat4{ e: [ + f32(1), 8, 2, 3, + 8, 2, 3, 1, + 2, 3, 3, 2, + 3, 1, 2, 4, + ]! + } + // println("*** INVERSE ****") + // println(m4.mul(b.inverse(),b)) + // println(m4.clean_small(m4.mul(c.inverse(),c))) + // println("****************") + assert m4.mul(b.inverse(), b).e == m4.unit_m4().e + assert m4.mul(c.inverse(), c).is_equal(m4.unit_m4()) + } +} + +fn test_vec4() { + // Test Vec4 + // println("*** Vector4 ****") + assert m4.vec3(1,2,3) == m4.Vec4{[f32(1), 2, 3, 1]!} + mut v := m4.Vec4{[f32(1), 2, 3, 4]!} + assert v * v.inv() == 4 + assert v.mul_scalar(1.0 / v.mod()).mod() == 1 + assert v + m4.Vec4{ e: [f32(5), 6, 7, 8]! } == m4.Vec4{ e: [f32(6), 8, 10, 12]! } + assert v - m4.Vec4{ e: [f32(1), 2, 3, 4]! } == m4.Vec4{ e: [f32(0), 0, 0, 0]! } + assert v.mul_vec4(m4.Vec4{ e: [f32(2), 2, 2, 2]! }) == m4.Vec4{ e: [f32(2), 4, 6, 8]! } + assert f32_abs(v.normalize().mod() - 1) < m4.precision + v = m4.Vec4{[f32(1), 2, 3, 0]!} + assert f32_abs(v.normalize3().mod3() - 1) < m4.precision + assert f32_abs(v.normalize3().mod() - 1) < m4.precision + // cross product + // x y z + // 1 2 3 ==> -3 6 -3 0 + // 4 5 6 + // println(m4.Vec4{[f32(1),2,3,2]!} % m4.Vec4{[f32(4),5,6,2]!}) + assert m4.Vec4{[f32(1), 2, 3, 0]!} % m4.Vec4{[f32(4), 5, 6, 0]!} == m4.Vec4{[ f32(-3), 6, -3, 0, ]!} + assert m4.Vec4{[f32(1), 2, 3, 13]!} % m4.Vec4{[f32(4), 5, 6, 11]!} == m4.Vec4{[ f32(-3), 6, -3, 0, ]!} + // matrix * vector + a := m4.Mat4{ e: [ + f32(1),2,3,4 + 5,6,7,8 + 9,10,11,12 + 13,14,15,16 + ]! + } + assert m4.mul_vec(a, m4.Vec4{[f32(1), 2, 3, 4]!}) == m4.Vec4{[ f32(30), 70, 110,150, ]!} + // Rotation + // println("*** Rotation ****") + rotx := m4.rotate(m4.rad(-90), m4.Vec4{ e: [f32(1.0), 0, 0, 0]! }).clean() + roty := m4.rotate(m4.rad(-90), m4.Vec4{ e: [f32(0), 1.0, 0, 0]! }).clean() + rotz := m4.rotate(m4.rad(-90), m4.Vec4{ e: [f32(0), 0, 1, 0]! }).clean() + // println( rotx ) + // println( roty ) + // println( rotz ) + // println( m4.mul_vec(rotx, m4.Vec4{e:[f32(0),0,1,0]!}).clean()) + assert m4.mul_vec(roty, m4.Vec4{ e: [f32(1.0), 0.0, 0, 0]! }).clean() == m4.Vec4{ e: [f32(0), 0.0, -1, 0]! } + assert m4.mul_vec(rotz, m4.Vec4{ e: [f32(1.0), 0.0, 0, 0]! }).clean() == m4.Vec4{ e: [f32(0), 1, 0, 0]! } + assert m4.mul_vec(rotx, m4.Vec4{ e: [f32(0), 0, 1, 0]! }).clean() == m4.Vec4{ e: [f32(0), -1, 0, 0]! } + // println("****************") +} + +fn test_proj() { + ort := m4.ortho(0,300,0,200,0,0) + assert m4.mul_vec(ort, m4.Vec4{[ f32(150), 100, 0, 1]!}) == m4.Vec4{[ f32(0), 0, 0, 1]!} + assert m4.mul_vec(ort, m4.Vec4{[ f32(0), 0, 0, 1]!}) == m4.Vec4{[ f32(-1), -1, 0, 1]!} + assert m4.mul_vec(ort, m4.Vec4{[ f32(300), 200, 0, 1]!}) == m4.Vec4{[ f32(1), 1, 0, 1]!} +} diff --git a/v_windows/v/vlib/gg/m4/matrix.v b/v_windows/v/vlib/gg/m4/matrix.v new file mode 100644 index 0000000..c839b9d --- /dev/null +++ b/v_windows/v/vlib/gg/m4/matrix.v @@ -0,0 +1,595 @@ +/********************************************************************** +* +* Simply vector/matrix utility +* +* 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: +**********************************************************************/ +module m4 + +import math + +pub union Mat4 { +pub mut: + e [16]f32 + f [4][4]f32 +} + +pub const precision = f32(10e-7) + +// default precision for the module + +/********************************************************************* +* +* Utility +* +*********************************************************************/ +// String representation of the matrix +pub fn (x Mat4) str() string { + unsafe { + return '|${x.e[0]:-6.3},${x.e[1]:-6.3},${x.e[2]:-6.3},${x.e[3]:-6.3}|\n' + + '|${x.e[4]:-6.3},${x.e[5]:-6.3},${x.e[6]:-6.3},${x.e[7]:-6.3}|\n' + + '|${x.e[8]:-6.3},${x.e[9]:-6.3},${x.e[10]:-6.3},${x.e[11]:-6.3}|\n' + + '|${x.e[12]:-6.3},${x.e[13]:-6.3},${x.e[14]:-6.3},${x.e[15]:-6.3}|' + } +} + +// Remove all the raw zeros +[direct_array_access] +pub fn (a Mat4) clean() Mat4 { + unsafe { + x := Mat4{} + for c, value in a.e { + if f32_abs(value) < m4.precision { + x.e[c] = 0 + } else { + x.e[c] = value + } + } + return x + } +} + +// Sum all the elements of the matrix +pub fn (x Mat4) sum_all() f32 { + mut res := f32(0) + for v in unsafe { x.e } { + res += v + } + return res +} + +// Check if two matrix are equal using module precision +[direct_array_access] +pub fn (x Mat4) is_equal(y Mat4) bool { + unsafe { + for c, value in x.e { + if f32_abs(value - y.e[c]) > m4.precision { + return false + } + } + return true + } +} + +//------------------------------------- +// Set/Get values +//------------------------------------- +// Get an element of the matrix using [0..15] indexes, one dimension +pub fn (x Mat4) get_e(elem_index int) f32 { + unsafe { + return x.e[elem_index] + } +} + +// Get an element of the matrix using [0..3][0..3] indexes, two dimension +pub fn (x Mat4) get_f(index_col int, index_row int) f32 { + unsafe { + return x.e[(index_row << 2) + index_col] + } +} + +// Set an element of the matrix using [0..15] indexes, one dimension +pub fn (mut x Mat4) set_e(index int, value f32) { + unsafe { + x.e[index] = value + } +} + +// Set an element of the matrix using [0..3][0..3] indexes, two dimension +pub fn (mut x Mat4) set_f(index_col int, index_row int, value f32) { + unsafe { + x.e[(index_row << 2) + index_col] = value + } +} + +// Copy a matrix elements from another matrix +pub fn (mut x Mat4) copy(y Mat4) { + unsafe { + x.e = [ + y.e[0 ], y.e[1 ], y.e[2 ], y.e[3 ], + y.e[4 ], y.e[5 ], y.e[6 ], y.e[7 ], + y.e[8 ], y.e[9 ], y.e[10], y.e[11], + y.e[12], y.e[13], y.e[14], y.e[15], + ]! + } +} + +// Set the trace of the matrix using a vec4 +pub fn (mut x Mat4) set_trace(v3 Vec4) { + unsafe { + x.e[0 ] = v3.e[0] + x.e[5 ] = v3.e[1] + x.e[10] = v3.e[2] + x.e[15] = v3.e[3] + } +} + +// Get the trace of the matrix +pub fn (x Mat4) get_trace() Vec4 { + unsafe { + return Vec4{ e: [ x.e[0], x.e[5], x.e[10], x.e[15], ]! } + } +} + +// Set all the matrix elements to value +pub fn (mut x Mat4) set_f32(value f32) { + unsafe { + x.e = [ + value, value, value, value, + value, value, value, value, + value, value, value, value, + value, value, value, value, + ]! + } +} + +//------------------------------------- +// Rows/Column access +//------------------------------------- +// Set the row as the input vec4 +[direct_array_access] +[unsafe] +pub fn (mut x Mat4) set_row(row int, v3 Vec4) { + unsafe { + x.e[row * 4 + 0] = v3.e[0] + x.e[row * 4 + 1] = v3.e[1] + x.e[row * 4 + 2] = v3.e[2] + x.e[row * 4 + 3] = v3.e[3] + } +} + +// Get a row from a matrix +[direct_array_access] +[unsafe] +pub fn (x Mat4) get_row(row int) Vec4 { + unsafe { + return Vec4{ + e: [ + x.e[row * 4 + 0], + x.e[row * 4 + 1], + x.e[row * 4 + 2], + x.e[row * 4 + 3], + ]! + } + } +} + +// Set the column as the input vec4 +[direct_array_access] +[unsafe] +pub fn (mut x Mat4) set_col(col int, v3 Vec4) { + unsafe { + x.e[col] = v3.e[0] + x.e[col + 4 ] = v3.e[1] + x.e[col + 8 ] = v3.e[2] + x.e[col + 12] = v3.e[3] + } +} + +// Get a column from a matrix +[direct_array_access] +[unsafe] +pub fn (x Mat4) get_col(col int) Vec4 { + unsafe { + return Vec4{ + e: [ + x.e[col], + x.e[col + 4 ], + x.e[col + 8 ], + x.e[col + 12], + ]! + } + } +} + +// Swap two columns in the matrix +[direct_array_access] +[unsafe] +pub fn (mut x Mat4) swap_col(col1 int, col2 int) { + unsafe { + v0 := x.e[col1] + v1 := x.e[col1 + 4 ] + v2 := x.e[col1 + 8 ] + v3 := x.e[col1 + 12] + + x.e[col1] = x.e[col2] + x.e[col1 + 4 ] = x.e[col2 + 4 ] + x.e[col1 + 8 ] = x.e[col2 + 8 ] + x.e[col1 + 12] = x.e[col2 + 12] + + x.e[col2] = v0 + x.e[col2 + 4 ] = v1 + x.e[col2 + 8 ] = v2 + x.e[col2 + 12] = v3 + } +} + +// Swap two rows in the matrix +[direct_array_access] +[unsafe] +pub fn (mut x Mat4) swap_row(row1 int, row2 int) { + unsafe { + v0 := x.e[row1 * 4 + 0] + v1 := x.e[row1 * 4 + 1] + v2 := x.e[row1 * 4 + 2] + v3 := x.e[row1 * 4 + 3] + + x.e[row1 * 4 + 0] = x.e[row2 * 4 + 0] + x.e[row1 * 4 + 1] = x.e[row2 * 4 + 1] + x.e[row1 * 4 + 2] = x.e[row2 * 4 + 2] + x.e[row1 * 4 + 3] = x.e[row2 * 4 + 3] + + x.e[row2 * 4 + 0] = v0 + x.e[row2 * 4 + 1] = v1 + x.e[row2 * 4 + 2] = v2 + x.e[row2 * 4 + 3] = v3 + } +} + +//------------------------------------- +// Modify data +//------------------------------------- +// Transpose the matrix +pub fn (x Mat4) transpose() Mat4 { + unsafe { + return Mat4{ e: [ + x.e[0 ], x.e[4 ], x.e[8 ], x.e[12], + x.e[1 ], x.e[5 ], x.e[9 ], x.e[13], + x.e[2 ], x.e[6 ], x.e[10], x.e[14], + x.e[3 ], x.e[7 ], x.e[11], x.e[15], + ]! + } + } +} + +// Multiply the all the elements of the matrix by a scalar +pub fn (x Mat4) mul_scalar(s f32) Mat4 { + unsafe { + return Mat4{ e: [ + x.e[0 ] * s, x.e[1 ] * s, x.e[2 ] * s, x.e[3 ] * s, + x.e[4 ] * s, x.e[5 ] * s, x.e[6 ] * s, x.e[7 ] * s, + x.e[8 ] * s, x.e[9 ] * s, x.e[10] * s, x.e[11] * s, + x.e[12] * s, x.e[13] * s, x.e[14] * s, x.e[15] * s, + ]! + } + } +} + +/********************************************************************* +* +* Init/set +* +*********************************************************************/ +// Return a zero matrix +pub fn zero_m4() Mat4 { + return Mat4{ e: [ + f32(0), 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + ]! + } +} + +// Return a unity matrix +pub fn unit_m4() Mat4 { + return Mat4{ e: [ + f32(1), 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1, + ]! + } +} + +// Return a matrix initialized with value +pub fn set_m4(value f32) Mat4 { + return Mat4{ e: [ + value, value, value, value, + value, value, value, value, + value, value, value, value, + value, value, value, value, + ]! + } +} + +/********************************************************************* +* +* Math +* +*********************************************************************/ + +// Sum of matrix, operator + +pub fn (a Mat4) + (b Mat4) Mat4 { + unsafe { + return Mat4{ e: [ + a.e[0 ] + b.e[0 ], a.e[1 ] + b.e[1 ], a.e[2 ] + b.e[2 ], a.e[3 ] + b.e[3 ], + a.e[4 ] + b.e[4 ], a.e[5 ] + b.e[5 ], a.e[6 ] + b.e[6 ], a.e[7 ] + b.e[7 ], + a.e[8 ] + b.e[8 ], a.e[9 ] + b.e[9 ], a.e[10] + b.e[10], a.e[11] + b.e[11], + a.e[12] + b.e[12], a.e[13] + b.e[13], a.e[14] + b.e[14], a.e[15] + b.e[15], + ]! + } + } +} + +// Subtraction of matrix, operator - +pub fn (a Mat4) - (b Mat4) Mat4 { + unsafe { + return Mat4{ e: [ + a.e[0 ] - b.e[0 ], a.e[1 ] - b.e[1 ], a.e[2 ] - b.e[2 ], a.e[3 ] - b.e[3 ], + a.e[4 ] - b.e[4 ], a.e[5 ] - b.e[5 ], a.e[6 ] - b.e[6 ], a.e[7 ] - b.e[7 ], + a.e[8 ] - b.e[8 ], a.e[9 ] - b.e[9 ], a.e[10] - b.e[10], a.e[11] - b.e[11], + a.e[12] - b.e[12], a.e[13] - b.e[13], a.e[14] - b.e[14], a.e[15] - b.e[15], + ]! + } + } +} + +// Multiplication of matrix, operator * +pub fn (a Mat4) * (b Mat4) Mat4 { + unsafe { + return Mat4{ + e: [ + /* [0][0] */ a.f[0][0] * b.f[0][0] + a.f[0][1] * b.f[1][0] + a.f[0][2] * b.f[2][0] + a.f[0][3] * b.f[3][0] + /* [0][1] */, a.f[0][0] * b.f[0][1] + a.f[0][1] * b.f[1][1] + a.f[0][2] * b.f[2][1] + a.f[0][3] * b.f[3][1] + /* [0][2] */, a.f[0][0] * b.f[0][2] + a.f[0][1] * b.f[1][2] + a.f[0][2] * b.f[2][2] + a.f[0][3] * b.f[3][2] + /* [0][3] */, a.f[0][0] * b.f[0][3] + a.f[0][1] * b.f[1][3] + a.f[0][2] * b.f[2][3] + a.f[0][3] * b.f[3][3] + + /* [1][0] */, a.f[1][0] * b.f[0][0] + a.f[1][1] * b.f[1][0] + a.f[1][2] * b.f[2][0] + a.f[1][3] * b.f[3][0] + /* [1][1] */, a.f[1][0] * b.f[0][1] + a.f[1][1] * b.f[1][1] + a.f[1][2] * b.f[2][1] + a.f[1][3] * b.f[3][1] + /* [1][2] */, a.f[1][0] * b.f[0][2] + a.f[1][1] * b.f[1][2] + a.f[1][2] * b.f[2][2] + a.f[1][3] * b.f[3][2] + /* [1][3] */, a.f[1][0] * b.f[0][3] + a.f[1][1] * b.f[1][3] + a.f[1][2] * b.f[2][3] + a.f[1][3] * b.f[3][3] + + /* [2][0] */, a.f[2][0] * b.f[0][0] + a.f[2][1] * b.f[1][0] + a.f[2][2] * b.f[2][0] + a.f[2][3] * b.f[3][0] + /* [2][1] */, a.f[2][0] * b.f[0][1] + a.f[2][1] * b.f[1][1] + a.f[2][2] * b.f[2][1] + a.f[2][3] * b.f[3][1] + /* [2][2] */, a.f[2][0] * b.f[0][2] + a.f[2][1] * b.f[1][2] + a.f[2][2] * b.f[2][2] + a.f[2][3] * b.f[3][2] + /* [2][3] */, a.f[2][0] * b.f[0][3] + a.f[2][1] * b.f[1][3] + a.f[2][2] * b.f[2][3] + a.f[2][3] * b.f[3][3] + + /* [3][0] */, a.f[3][0] * b.f[0][0] + a.f[3][1] * b.f[1][0] + a.f[3][2] * b.f[2][0] + a.f[3][3] * b.f[3][0] + /* [3][1] */, a.f[3][0] * b.f[0][1] + a.f[3][1] * b.f[1][1] + a.f[3][2] * b.f[2][1] + a.f[3][3] * b.f[3][1] + /* [3][2] */, a.f[3][0] * b.f[0][2] + a.f[3][1] * b.f[1][2] + a.f[3][2] * b.f[2][2] + a.f[3][3] * b.f[3][2] + /* [3][3] */, a.f[3][0] * b.f[0][3] + a.f[3][1] * b.f[1][3] + a.f[3][2] * b.f[2][3] + a.f[3][3] * b.f[3][3], + ]! + } + } +} + +// Sum of matrix function +pub fn add(a Mat4, b Mat4) Mat4 { + unsafe { + return a + b + } +} + +// Subtraction of matrix function +pub fn sub(a Mat4, b Mat4) Mat4 { + unsafe { + return a - b + } +} + +// Multiplication of matrix function +pub fn mul(a Mat4, b Mat4) Mat4 { + unsafe { + return a * b + } +} + +// Multiply a Matrix by a vector +pub fn mul_vec(a Mat4, v Vec4) Vec4 { + unsafe { + return Vec4{ e: [ + a.e[0 ] * v.e[0] + a.e[1 ] * v.e[1] + a.e[2 ] * v.e[2] + a.e[3 ] * v.e[3], + a.e[4 ] * v.e[0] + a.e[5 ] * v.e[1] + a.e[6 ] * v.e[2] + a.e[7 ] * v.e[3], + a.e[8 ] * v.e[0] + a.e[9 ] * v.e[1] + a.e[10] * v.e[2] + a.e[11] * v.e[3], + a.e[12] * v.e[0] + a.e[13] * v.e[1] + a.e[14] * v.e[2] + a.e[15] * v.e[3], + ]! + } + } +} + +// Calculate the determinant of the Matrix +pub fn det(x Mat4) f32 { + unsafe { + mut t := [6]f32{} + x00 := x.f[0][0] + x10 := x.f[1][0] + x20 := x.f[2][0] + x30 := x.f[3][0] + x01 := x.f[0][1] + x11 := x.f[1][1] + x21 := x.f[2][1] + x31 := x.f[3][1] + x02 := x.f[0][2] + x12 := x.f[1][2] + x22 := x.f[2][2] + x32 := x.f[3][2] + x03 := x.f[0][3] + x13 := x.f[1][3] + x23 := x.f[2][3] + x33 := x.f[3][3] + + t[0] = x22 * x33 - x23 * x32 + t[1] = x12 * x33 - x13 * x32 + t[2] = x12 * x23 - x13 * x22 + t[3] = x02 * x33 - x03 * x32 + t[4] = x02 * x23 - x03 * x22 + t[5] = x02 * x13 - x03 * x12 + + return 0.0 + + x00 * (x11 * t[0] - x21 * t[1] + x31 * t[2]) - + x10 * (x01 * t[0] - x21 * t[3] + x31 * t[4]) + + x20 * (x01 * t[1] - x11 * t[3] + x31 * t[5]) - + x30 * (x01 * t[2] - x11 * t[4] + x21 * t[5]) + } +} + +// Calculate the inverse of the Matrix +pub fn (x Mat4) inverse() Mat4 { + unsafe { + mut t := [6]f32{} + mut det := f32(0) + + a := x.f[0][0] + b := x.f[1][0] + c := x.f[2][0] + d := x.f[3][0] + e := x.f[0][1] + f := x.f[1][1] + g := x.f[2][1] + h := x.f[3][1] + i := x.f[0][2] + j := x.f[1][2] + k := x.f[2][2] + l := x.f[3][2] + m := x.f[0][3] + n := x.f[1][3] + o := x.f[2][3] + p := x.f[3][3] + + t[0] = k * p - o * l + t[1] = j * p - n * l + t[2] = j * o - n * k + t[3] = i * p - m * l + t[4] = i * o - m * k + t[5] = i * n - m * j + + mut dest := Mat4{} + dest.f[0][0] = f * t[0] - g * t[1] + h * t[2] + dest.f[0][1] = -(e * t[0] - g * t[3] + h * t[4]) + dest.f[0][2] = e * t[1] - f * t[3] + h * t[5] + dest.f[0][3] = -(e * t[2] - f * t[4] + g * t[5]) + + dest.f[1][0] = -(b * t[0] - c * t[1] + d * t[2]) + dest.f[1][1] = a * t[0] - c * t[3] + d * t[4] + dest.f[1][2] = -(a * t[1] - b * t[3] + d * t[5]) + dest.f[1][3] = a * t[2] - b * t[4] + c * t[5] + + t[0] = g * p - o * h + t[1] = f * p - n * h + t[2] = f * o - n * g + t[3] = e * p - m * h + t[4] = e * o - m * g + t[5] = e * n - m * f + + dest.f[2][0] = b * t[0] - c * t[1] + d * t[2] + dest.f[2][1] = -(a * t[0] - c * t[3] + d * t[4]) + dest.f[2][2] = a * t[1] - b * t[3] + d * t[5] + dest.f[2][3] = -(a * t[2] - b * t[4] + c * t[5]) + + t[0] = g * l - k * h + t[1] = f * l - j * h + t[2] = f * k - j * g + t[3] = e * l - i * h + t[4] = e * k - i * g + t[5] = e * j - i * f + + dest.f[3][0] = -(b * t[0] - c * t[1] + d * t[2]) + dest.f[3][1] = a * t[0] - c * t[3] + d * t[4] + dest.f[3][2] = -(a * t[1] - b * t[3] + d * t[5]) + dest.f[3][3] = a * t[2] - b * t[4] + c * t[5] + + tmp := (a * dest.f[0][0] + b * dest.f[0][1] + c * dest.f[0][2] + d * dest.f[0][3]) + if tmp != 0 { + det = f32(1.0) / tmp + } + return dest.mul_scalar(det) + } +} + +/********************************************************************* +* +* Transformations +* +*********************************************************************/ + +// Get a rotation matrix using w as rotation axis vector, the angle is in radians +pub fn rotate(angle f32, w Vec4) Mat4 { + cs := f32(math.cos(angle)) + sn := f32(math.sin(angle)) + cv := f32(1.0) - cs + axis := w.normalize3() + unsafe { + ax := axis.e[0] + ay := axis.e[1] + az := axis.e[2] + + return Mat4{ e: [ + /* [0][0] */ (ax * ax * cv) + cs + /* [0][1] */, (ax * ay * cv) + az * sn + /* [0][2] */, (ax * az * cv) - ay * sn + /* [0][3] */, 0 + + /* [1][0] */, (ay * ax * cv) - az * sn + /* [1][1] */, (ay * ay * cv) + cs + /* [1][2] */, (ay * az * cv) + ax * sn + /* [1][3] */, 0 + + /* [2][0] */, (az * ax * cv) + ay * sn + /* [2][1] */, (az * ay * cv) - ax * sn + /* [2][2] */, (az * az * cv) + cs + /* [2][3] */, 0 + + /* [3][0] */, 0 + /* [3][1] */, 0 + /* [3][2] */, 0 + /* [3][3] */, 1, + ]! + } + } +} + +/********************************************************************* +* +* Graphic +* +*********************************************************************/ +// Get a matrix translated by a vector w +pub fn (x Mat4) translate(w Vec4) Mat4 { + unsafe { + return Mat4{ e: [ + x.e[0], x.e[1], x.e[2 ], x.e[3 ] , + x.e[4], x.e[5], x.e[6 ], x.e[7 ] , + x.e[8], x.e[9], x.e[10], x.e[11] , + x.e[12] + w.e[0], x.e[13] + w.e[1], x.e[14] + w.e[2], x.e[15], + ]! + } + } +} + +// Get a scale matrix, the scale vector is w, only xyz are evaluated. +pub fn scale(w Vec4) Mat4 { + unsafe { + return Mat4{ e: [ + w.e[0], 0, 0, 0, + 0, w.e[1], 0, 0, + 0, 0, w.e[2], 0, + 0, 0, 0, 1, + ]! + } + } +} diff --git a/v_windows/v/vlib/gg/m4/vector.v b/v_windows/v/vlib/gg/m4/vector.v new file mode 100644 index 0000000..52e4c78 --- /dev/null +++ b/v_windows/v/vlib/gg/m4/vector.v @@ -0,0 +1,230 @@ +/********************************************************************** +* +* Simply vector/matrix utility +* +* 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: +**********************************************************************/ +module m4 + +import math + +pub struct Vec4 { +pub mut: + e [4]f32 +} + +/********************************************************************* +* +* Utility +* +*********************************************************************/ +pub fn (x Vec4) str() string { + return '|${x.e[0]:-6.3},${x.e[1]:-6.3},${x.e[2]:-6.3},${x.e[3]:-6.3}|' +} + +// create a Vec4 function passing x,y,z as parameteres. w is set to 1 +pub fn vec3(x f32, y f32, z f32) Vec4 { + return Vec4{ + e: [x, y, z, 1]! + } +} + +// Remove all the raw zeros +[direct_array_access] +pub fn (a Vec4) clean() Vec4 { + mut x := Vec4{} + for c, value in a.e { + if f32_abs(value) < precision { + x.e[c] = 0 + } else { + x.e[c] = value + } + } + return x +} + +// Set all elements to value +pub fn (mut x Vec4) copy(value f32) { + x.e = [value, value, value, value]! +} + +// Scale the vector using a scalar +pub fn (x Vec4) mul_scalar(value f32) Vec4 { + return Vec4{ + e: [x.e[0] * value, x.e[1] * value, x.e[2] * value, x.e[3] * value]! + } +} + +// Reciprocal of the vector +pub fn (x Vec4) inv() Vec4 { + return Vec4{ + e: [ + if x.e[0] != 0 { 1.0 / x.e[0] } else { f32(0) }, + if x.e[1] != 0 { 1.0 / x.e[1] } else { f32(0) }, + if x.e[2] != 0 { 1.0 / x.e[2] } else { f32(0) }, + if x.e[3] != 0 { 1.0 / x.e[3] } else { f32(0) }, + ]! + } +} + +// Normalize the vector +pub fn (x Vec4) normalize() Vec4 { + m := x.mod() + if m == 0 { + return zero_v4() + } + return Vec4{ + e: [ + x.e[0] * (1 / m), + x.e[1] * (1 / m), + x.e[2] * (1 / m), + x.e[3] * (1 / m), + ]! + } +} + +// Normalize only xyz, w set to 0 +pub fn (x Vec4) normalize3() Vec4 { + m := x.mod3() + if m == 0 { + return zero_v4() + } + return Vec4{ + e: [ + x.e[0] * (1 / m), + x.e[1] * (1 / m), + x.e[2] * (1 / m), + 0, + ]! + } +} + +// Module of the vector xyzw +pub fn (x Vec4) mod() f32 { + return f32(math.sqrt(x.e[0] * x.e[0] + x.e[1] * x.e[1] + x.e[2] * x.e[2] + x.e[3] * x.e[3])) +} + +// Module for 3d vector xyz, w ignored +pub fn (x Vec4) mod3() f32 { + return f32(math.sqrt(x.e[0] * x.e[0] + x.e[1] * x.e[1] + x.e[2] * x.e[2])) +} + +/********************************************************************* +* +* Math +* +*********************************************************************/ +// Return a zero vector +pub fn zero_v4() Vec4 { + return Vec4{ + e: [ + f32(0), + 0, + 0, + 0, + ]! + } +} + +// Return all one vector +pub fn one_v4() Vec4 { + return Vec4{ + e: [ + f32(1), + 1, + 1, + 1, + ]! + } +} + +// Return a blank vector +pub fn blank_v4() Vec4 { + return Vec4{ + e: [ + f32(0), + 0, + 0, + 1, + ]! + } +} + +// Set all elements to value +pub fn set_v4(value f32) Vec4 { + return Vec4{ + e: [ + value, + value, + value, + value, + ]! + } +} + +// Sum of all the elements +pub fn (x Vec4) sum() f32 { + return x.e[0] + x.e[1] + x.e[2] + x.e[3] +} + +/********************************************************************* +* +* Operators +* +*********************************************************************/ +// Addition +pub fn (a Vec4) + (b Vec4) Vec4 { + return Vec4{ + e: [ + a.e[0] + b.e[0], + a.e[1] + b.e[1], + a.e[2] + b.e[2], + a.e[3] + b.e[3], + ]! + } +} + +// Subtraction +pub fn (a Vec4) - (b Vec4) Vec4 { + return Vec4{ + e: [ + a.e[0] - b.e[0], + a.e[1] - b.e[1], + a.e[2] - b.e[2], + a.e[3] - b.e[3], + ]! + } +} + +// Dot product +pub fn (a Vec4) * (b Vec4) f32 { + return a.e[0] * b.e[0] + a.e[1] * b.e[1] + a.e[2] * b.e[2] + a.e[3] * b.e[3] +} + +// Cross product +pub fn (a Vec4) % (b Vec4) Vec4 { + return Vec4{ + e: [ + (a.e[1] * b.e[2]) - (a.e[2] * b.e[1]), + (a.e[2] * b.e[0]) - (a.e[0] * b.e[2]), + (a.e[0] * b.e[1]) - (a.e[1] * b.e[0]), + 0, + ]! + } +} + +// Components multiplication +pub fn (x Vec4) mul_vec4(y Vec4) Vec4 { + return Vec4{ + e: [ + x.e[0] * y.e[0], + x.e[1] * y.e[1], + x.e[2] * y.e[2], + x.e[3] * y.e[3], + ]! + } +} diff --git a/v_windows/v/vlib/gg/text_rendering.c.v b/v_windows/v/vlib/gg/text_rendering.c.v new file mode 100644 index 0000000..9530dd2 --- /dev/null +++ b/v_windows/v/vlib/gg/text_rendering.c.v @@ -0,0 +1,226 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license that can be found in the LICENSE file. +module gg + +import sokol.sfons +import sokol.sgl +import gx +import os + +struct FT { +pub: + fons &C.FONScontext + font_normal int + font_bold int + font_mono int + font_italic int + scale f32 = 1.0 +} + +fn new_ft(c FTConfig) ?&FT { + if c.font_path == '' { + if c.bytes_normal.len > 0 { + fons := sfons.create(512, 512, 1) + bytes_normal := c.bytes_normal + bytes_bold := if c.bytes_bold.len > 0 { + c.bytes_bold + } else { + debug_font_println('setting bold variant to normal') + bytes_normal + } + bytes_mono := if c.bytes_mono.len > 0 { + c.bytes_mono + } else { + debug_font_println('setting mono variant to normal') + bytes_normal + } + bytes_italic := if c.bytes_italic.len > 0 { + c.bytes_italic + } else { + debug_font_println('setting italic variant to normal') + bytes_normal + } + + return &FT{ + fons: fons + font_normal: C.fonsAddFontMem(fons, c'sans', bytes_normal.data, bytes_normal.len, + false) + font_bold: C.fonsAddFontMem(fons, c'sans', bytes_bold.data, bytes_bold.len, + false) + font_mono: C.fonsAddFontMem(fons, c'sans', bytes_mono.data, bytes_mono.len, + false) + font_italic: C.fonsAddFontMem(fons, c'sans', bytes_italic.data, bytes_italic.len, + false) + scale: c.scale + } + } else { + // Load default font + } + } + + if c.font_path == '' || !os.exists(c.font_path) { + $if !android { + println('failed to load font "$c.font_path"') + return none + } + } + + mut bytes := []byte{} + $if android { + // First try any filesystem paths + bytes = os.read_bytes(c.font_path) or { []byte{} } + if bytes.len == 0 { + // ... then try the APK asset path + bytes = os.read_apk_asset(c.font_path) or { + println('failed to load font "$c.font_path"') + return none + } + } + } $else { + bytes = os.read_bytes(c.font_path) or { + println('failed to load font "$c.font_path"') + return none + } + } + bold_path := if c.custom_bold_font_path != '' { + c.custom_bold_font_path + } else { + get_font_path_variant(c.font_path, .bold) + } + bytes_bold := os.read_bytes(bold_path) or { + debug_font_println('failed to load font "$bold_path"') + bytes + } + mono_path := get_font_path_variant(c.font_path, .mono) + bytes_mono := os.read_bytes(mono_path) or { + debug_font_println('failed to load font "$mono_path"') + bytes + } + italic_path := get_font_path_variant(c.font_path, .italic) + bytes_italic := os.read_bytes(italic_path) or { + debug_font_println('failed to load font "$italic_path"') + bytes + } + fons := sfons.create(512, 512, 1) + return &FT{ + fons: fons + font_normal: C.fonsAddFontMem(fons, c'sans', bytes.data, bytes.len, false) + font_bold: C.fonsAddFontMem(fons, c'sans', bytes_bold.data, bytes_bold.len, false) + font_mono: C.fonsAddFontMem(fons, c'sans', bytes_mono.data, bytes_mono.len, false) + font_italic: C.fonsAddFontMem(fons, c'sans', bytes_italic.data, bytes_italic.len, + false) + scale: c.scale + } +} + +pub fn (ctx &Context) set_cfg(cfg gx.TextCfg) { + if !ctx.font_inited { + return + } + if cfg.bold { + ctx.ft.fons.set_font(ctx.ft.font_bold) + } else if cfg.mono { + ctx.ft.fons.set_font(ctx.ft.font_mono) + } else if cfg.italic { + ctx.ft.fons.set_font(ctx.ft.font_italic) + } else { + ctx.ft.fons.set_font(ctx.ft.font_normal) + } + scale := if ctx.ft.scale == 0 { f32(1) } else { ctx.ft.scale } + size := if cfg.mono { cfg.size - 2 } else { cfg.size } + ctx.ft.fons.set_size(scale * f32(size)) + C.fonsSetAlign(ctx.ft.fons, int(cfg.align) | int(cfg.vertical_align)) + color := C.sfons_rgba(cfg.color.r, cfg.color.g, cfg.color.b, cfg.color.a) + if cfg.color.a != 255 { + sgl.load_pipeline(ctx.timage_pip) + } + C.fonsSetColor(ctx.ft.fons, color) + ascender := f32(0.0) + descender := f32(0.0) + lh := f32(0.0) + ctx.ft.fons.vert_metrics(&ascender, &descender, &lh) +} + +pub fn (ctx &Context) draw_text(x int, y int, text_ string, cfg gx.TextCfg) { + $if macos { + if ctx.native_rendering { + if cfg.align == gx.align_right { + width := ctx.text_width(text_) + C.darwin_draw_string(x - width, ctx.height - y, text_, cfg) + } else { + C.darwin_draw_string(x, ctx.height - y, text_, cfg) + } + return + } + } + if !ctx.font_inited { + eprintln('gg: draw_text(): font not initialized') + return + } + // text := text_.trim_space() // TODO remove/optimize + // mut text := text_ + // if text.contains('\t') { + // text = text.replace('\t', ' ') + // } + ctx.set_cfg(cfg) + scale := if ctx.ft.scale == 0 { f32(1) } else { ctx.ft.scale } + C.fonsDrawText(ctx.ft.fons, x * scale, y * scale, &char(text_.str), 0) // TODO: check offsets/alignment +} + +pub fn (ctx &Context) draw_text_def(x int, y int, text string) { + ctx.draw_text(x, y, text) +} + +/* +pub fn (mut gg FT) init_font() { +} +*/ +pub fn (ft &FT) flush() { + sfons.flush(ft.fons) +} + +pub fn (ctx &Context) text_width(s string) int { + $if macos { + if ctx.native_rendering { + return C.darwin_text_width(s) + } + } + // ctx.set_cfg(cfg) TODO + if !ctx.font_inited { + return 0 + } + mut buf := [4]f32{} + C.fonsTextBounds(ctx.ft.fons, 0, 0, &char(s.str), 0, &buf[0]) + if s.ends_with(' ') { + return int((buf[2] - buf[0]) / ctx.scale) + + ctx.text_width('i') // TODO fix this in fontstash? + } + res := int((buf[2] - buf[0]) / ctx.scale) + // println('TW "$s" = $res') + $if macos { + if ctx.native_rendering { + return res * 2 + } + } + return int((buf[2] - buf[0]) / ctx.scale) +} + +pub fn (ctx &Context) text_height(s string) int { + // ctx.set_cfg(cfg) TODO + if !ctx.font_inited { + return 0 + } + mut buf := [4]f32{} + C.fonsTextBounds(ctx.ft.fons, 0, 0, &char(s.str), 0, &buf[0]) + return int((buf[3] - buf[1]) / ctx.scale) +} + +pub fn (ctx &Context) text_size(s string) (int, int) { + // ctx.set_cfg(cfg) TODO + if !ctx.font_inited { + return 0, 0 + } + mut buf := [4]f32{} + C.fonsTextBounds(ctx.ft.fons, 0, 0, &char(s.str), 0, &buf[0]) + return int((buf[2] - buf[0]) / ctx.scale), int((buf[3] - buf[1]) / ctx.scale) +} diff --git a/v_windows/v/vlib/gg/text_rendering.v b/v_windows/v/vlib/gg/text_rendering.v new file mode 100644 index 0000000..a34bbbb --- /dev/null +++ b/v_windows/v/vlib/gg/text_rendering.v @@ -0,0 +1,150 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license that can be found in the LICENSE file. +module gg + +import os +import gx + +enum FontVariant { + normal = 0 + bold + mono + italic +} + +struct FTConfig { + font_path string + custom_bold_font_path string + scale f32 = 1.0 + font_size int + bytes_normal []byte + bytes_bold []byte + bytes_mono []byte + bytes_italic []byte +} + +struct StringToRender { + x int + y int + text string + cfg gx.TextCfg +} + +pub fn system_font_path() string { + env_font := os.getenv('VUI_FONT') + if env_font != '' && os.exists(env_font) { + return env_font + } + $if windows { + return 'C:\\Windows\\Fonts\\arial.ttf' + } + mut fonts := ['Ubuntu-R.ttf', 'Arial.ttf', 'LiberationSans-Regular.ttf', 'NotoSans-Regular.ttf', + 'FreeSans.ttf', 'DejaVuSans.ttf'] + $if macos { + fonts = ['/System/Library/Fonts/SFNS.ttf', '/System/Library/Fonts/SFNSText.ttf', + '/Library/Fonts/Arial.ttf', + ] + for font in fonts { + if os.is_file(font) { + return font + } + } + } + $if android { + xml_files := ['/system/etc/system_fonts.xml', '/system/etc/fonts.xml', + '/etc/system_fonts.xml', '/etc/fonts.xml', '/data/fonts/fonts.xml', + '/etc/fallback_fonts.xml', + ] + font_locations := ['/system/fonts', '/data/fonts'] + for xml_file in xml_files { + if os.is_file(xml_file) && os.is_readable(xml_file) { + xml := os.read_file(xml_file) or { continue } + lines := xml.split('\n') + mut candidate_font := '' + for line in lines { + if line.contains('<font') { + candidate_font = line.all_after('>').all_before('<').trim(' \n\t\r') + if candidate_font.contains('.ttf') { + for location in font_locations { + candidate_path := os.join_path(location, candidate_font) + if os.is_file(candidate_path) && os.is_readable(candidate_path) { + return candidate_path + } + } + } + } + } + } + } + } + s := os.execute('fc-list') + if s.exit_code != 0 { + panic('failed to fetch system fonts') + } + system_fonts := s.output.split('\n') + for line in system_fonts { + for font in fonts { + if line.contains(font) && line.contains(':') { + res := line.all_before(':') + println('Using font $res') + return res + } + } + } + panic('failed to init the font') +} + +fn get_font_path_variant(font_path string, variant FontVariant) string { + // TODO: find some way to make this shorter and more eye-pleasant + // NotoSans, LiberationSans, DejaVuSans, Arial and SFNS should work + mut file := os.file_name(font_path) + mut fpath := font_path.replace(file, '') + file = file.replace('.ttf', '') + + match variant { + .normal {} + .bold { + if fpath.ends_with('-Regular') { + file = file.replace('-Regular', '-Bold') + } else if file.starts_with('DejaVuSans') { + file += '-Bold' + } else if file.to_lower().starts_with('arial') { + file += 'bd' + } else { + file += '-bold' + } + $if macos { + if os.exists('SFNS-bold') { + file = 'SFNS-bold' + } + } + } + .italic { + if file.ends_with('-Regular') { + file = file.replace('-Regular', '-Italic') + } else if file.starts_with('DejaVuSans') { + file += '-Oblique' + } else if file.to_lower().starts_with('arial') { + file += 'i' + } else { + file += 'Italic' + } + } + .mono { + if !file.ends_with('Mono-Regular') && file.ends_with('-Regular') { + file = file.replace('-Regular', 'Mono-Regular') + } else if file.to_lower().starts_with('arial') { + // Arial has no mono variant + } else { + file += 'Mono' + } + } + } + return fpath + file + '.ttf' +} + +fn debug_font_println(s string) { + $if debug_font ? { + println(s) + } +} |