v/vlib/gg/gg.v

621 lines
15 KiB
V
Raw Normal View History

// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved.
2020-07-06 20:29:05 +02:00
// Use of this source code is governed by an MIT license that can be found in the LICENSE file.
2019-06-22 20:20:28 +02:00
module gg
import os
import gx
import sokol
import sokol.sapp
import sokol.sgl
import sokol.gfx
2020-10-10 10:37:17 +02:00
import math
2019-06-22 20:20:28 +02:00
2020-08-01 23:40:25 +02:00
// import time
pub type FNCb = fn (x voidptr)
pub type FNEvent = fn (e voidptr, x voidptr)
2020-08-01 23:40:25 +02:00
pub type FNFail = fn (msg string, x voidptr)
pub type FNKeyDown = fn (c sapp.KeyCode, m sapp.Modifier, x voidptr)
pub type FNMove = fn (x f32, y f32, z voidptr)
2020-09-21 02:42:28 +02:00
2020-08-01 23:40:25 +02:00
pub type FNChar = fn (c u32, x voidptr)
2019-06-22 20:20:28 +02:00
pub struct Config {
2019-08-22 23:00:31 +02:00
pub:
width int
height int
use_ortho bool
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)
cleanup_fn FNCb = voidptr(0)
fail_fn FNFail = voidptr(0)
event_fn FNEvent = voidptr(0)
keydown_fn FNKeyDown = voidptr(0)
// special case of event_fn
char_fn FNChar = voidptr(0)
// special case of event_fn
move_fn FNMove = voidptr(0)
// special case of event_fn
click_fn FNMove = voidptr(0)
// special case of event_fn
2020-11-21 00:04:29 +01:00
// wait_events bool // set this to true for UIs, to save power
fullscreen bool
scale f32 = 1.0
sample_count int
// vid needs this
2020-08-01 23:40:25 +02:00
// init_text bool
2020-12-01 16:30:22 +01:00
font_path string
custom_bold_font_path string
ui_mode bool // refreshes only on events to save CPU usage
2019-06-22 20:20:28 +02:00
}
2020-06-04 20:26:18 +02:00
pub struct Context {
render_text bool
2020-08-05 16:00:24 +02:00
mut:
// 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
2019-08-22 23:00:31 +02:00
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
}
2020-08-01 23:40:25 +02:00
pub struct Size {
pub:
width int
height int
}
2020-06-04 19:57:13 +02:00
fn gg_init_sokol_window(user_data voidptr) {
mut g := unsafe { &Context(user_data) }
desc := sapp.create_desc()
2020-08-23 07:25:30 +02:00
/*
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
2019-06-22 20:20:28 +02:00
}
2020-08-23 07:25:30 +02:00
*/
gfx.setup(&desc)
sgl_desc := C.sgl_desc_t{}
sgl.setup(&sgl_desc)
2020-06-04 20:09:46 +02:00
g.scale = sapp.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 g.scale < 0.1 {
g.scale = 1.0
}
2020-08-01 23:40:25 +02:00
// 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 {
2020-08-01 23:40:25 +02:00
// t := time.ticks()
g.ft = new_ft(
font_path: g.config.font_path
custom_bold_font_path: g.config.custom_bold_font_path
scale: sapp.dpi_scale()
) or { panic(err) }
2020-08-01 23:40:25 +02:00
// println('FT took ${time.ticks()-t} ms')
2020-07-06 19:45:00 +02:00
g.font_inited = true
} else {
if !exists {
sfont := system_font_path()
eprintln('font file "$g.config.font_path" does not exist, the system font 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
}
2020-07-06 19:45:00 +02:00
}
//
mut pipdesc := C.sg_pipeline_desc{}
unsafe { C.memset(&pipdesc, 0, sizeof(pipdesc)) }
pipdesc.blend.enabled = true
pipdesc.blend.src_factor_rgb = gfx.BlendFactor(C.SG_BLENDFACTOR_SRC_ALPHA)
pipdesc.blend.dst_factor_rgb = gfx.BlendFactor(C.SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA)
g.timage_pip = sgl.make_pipeline(&pipdesc)
//
if g.config.init_fn != voidptr(0) {
2020-07-06 19:45:00 +02:00
g.config.init_fn(g.config.user_data)
2019-06-22 20:20:28 +02:00
}
2020-08-05 16:00:24 +02:00
// Create images now that we can do that after sg is inited
2020-10-10 10:37:17 +02:00
for i in 0 .. g.image_cache.len {
2020-08-05 16:00:24 +02:00
g.image_cache[i].init_sokol_image()
}
2019-06-22 20:20:28 +02:00
}
fn gg_frame_fn(user_data voidptr) {
mut ctx := unsafe { &Context(user_data) }
if ctx.config.frame_fn == voidptr(0) {
return
}
if ctx.ui_mode && !ctx.needs_refresh {
// Draw 3 more frames after the "stop refresh" command
ctx.ticks++
if ctx.ticks > 3 {
return
}
2019-08-22 23:00:31 +02:00
}
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
2019-08-22 23:00:31 +02:00
}
2020-08-01 23:40:25 +02:00
fn gg_event_fn(ce &C.sapp_event, user_data voidptr) {
e := unsafe { &sapp.Event(ce) }
mut g := unsafe { &Context(user_data) }
if g.config.event_fn != voidptr(0) {
g.config.event_fn(e, g.config.user_data)
2019-08-22 23:00:31 +02:00
}
match e.typ {
.key_down {
if g.config.keydown_fn != voidptr(0) {
kdfn := g.config.keydown_fn
kdfn(e.key_code, sapp.Modifier(e.modifiers), g.config.user_data)
}
}
.char {
if g.config.char_fn != voidptr(0) {
cfn := g.config.char_fn
cfn(e.char_code, g.config.user_data)
}
}
2020-10-10 10:37:17 +02:00
.mouse_move {
2020-09-21 02:42:28 +02:00
if g.config.move_fn != voidptr(0) {
cfn := g.config.move_fn
cfn(e.mouse_x / g.scale, e.mouse_y / g.scale, g.config.user_data)
}
}
2020-10-10 10:37:17 +02:00
.mouse_down {
2020-09-21 02:42:28 +02:00
if g.config.click_fn != voidptr(0) {
cfn := g.config.click_fn
cfn(e.mouse_x / g.scale, e.mouse_y / g.scale, g.config.user_data)
}
}
2020-08-01 23:40:25 +02:00
else {}
}
2019-06-22 20:20:28 +02:00
}
2020-08-01 23:40:25 +02:00
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)
2019-06-22 20:20:28 +02:00
}
}
2020-08-01 23:40:25 +02:00
fn gg_fail_fn(msg charptr, user_data voidptr) {
mut g := unsafe { &Context(user_data) }
vmsg := tos3(msg)
if g.config.fail_fn != voidptr(0) {
g.config.fail_fn(vmsg, g.config.user_data)
2020-08-01 23:40:25 +02:00
} else {
eprintln('gg error: $vmsg')
}
2019-06-22 20:20:28 +02:00
}
//
2020-08-01 23:40:25 +02:00
pub fn new_context(cfg Config) &Context {
2020-06-04 20:26:18 +02:00
mut g := &Context{
width: cfg.width
height: cfg.height
config: cfg
2020-07-06 19:45:00 +02:00
render_text: cfg.font_path != ''
ft: 0
ui_mode: cfg.ui_mode
}
2020-08-19 07:10:42 +02:00
g.set_bg_color(cfg.bg_color)
2020-08-01 23:40:25 +02:00
// 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: cfg.window_title.str
html5_canvas_name: cfg.window_title.str
width: cfg.width
height: cfg.height
sample_count: cfg.sample_count
2020-06-04 20:09:46 +02:00
high_dpi: true
2020-06-04 23:51:54 +02:00
fullscreen: cfg.fullscreen
}
2020-08-01 23:40:25 +02:00
if cfg.use_ortho {
} else {
}
g.window = window
return g
2019-06-22 20:20:28 +02:00
}
2020-06-04 20:26:18 +02:00
pub fn (gg &Context) run() {
sapp.run(&gg.window)
2019-06-22 20:20:28 +02:00
}
2020-08-19 07:10:42 +02:00
pub fn (mut ctx Context) set_bg_color(c gx.Color) {
2020-10-10 10:37:17 +02:00
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)
2020-08-19 07:10:42 +02:00
}
// TODO: Fix alpha
pub fn (ctx &Context) draw_rect(x f32, y f32, w f32, h f32, c gx.Color) {
if c.a != 255 {
sgl.load_pipeline(ctx.timage_pip)
}
2020-08-19 07:10:42 +02:00
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()
}
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)
}
2020-08-19 07:10:42 +02:00
sgl.c4b(c.r, c.g, c.b, c.a)
sgl.begin_line_strip()
2020-06-04 23:51:54 +02:00
if ctx.scale == 1 {
sgl.v2f(x, y)
sgl.v2f(x + w, y)
sgl.v2f(x + w, y + h)
sgl.v2f(x, y + h)
sgl.v2f(x, y)
2020-08-01 23:40:25 +02:00
} else {
2020-06-04 23:51:54 +02:00
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)
2020-08-01 23:40:25 +02:00
sgl.v2f(x * ctx.scale, y * ctx.scale)
2020-06-04 23:51:54 +02:00
}
sgl.end()
}
pub fn (ctx &Context) draw_circle_line(x f32, y f32, r int, 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)
mut theta := f32(0)
mut xx := f32(0)
mut yy := f32(0)
sgl.begin_line_strip()
2020-10-10 10:37:17 +02:00
for i := 0; i < segments + 1; i++ {
theta = 2.0 * f32(math.pi) * f32(i) / f32(segments)
xx = r * math.cosf(theta)
yy = r * math.sinf(theta)
sgl.v2f(xx + x, yy + y)
}
sgl.end()
}
pub fn (ctx &Context) draw_circle(x f32, y f32, r f32, c gx.Color) {
if ctx.scale == 1 {
2020-11-15 15:11:43 +01:00
ctx.draw_circle_with_segments(x, y, r, 10, c)
} else {
ctx.draw_circle_with_segments(x * f32(ctx.scale), y * f32(ctx.scale), r * ctx.scale,
10, c)
}
2020-10-18 21:22:37 +02:00
}
2020-11-15 15:11:43 +01:00
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)
mut theta := f32(0)
mut xx := f32(0)
mut yy := f32(0)
sgl.begin_triangle_strip()
2020-10-10 10:37:17 +02:00
for i := 0; i < segments + 1; i++ {
theta = 2.0 * f32(math.pi) * f32(i) / f32(segments)
xx = r * math.cosf(theta)
yy = r * math.sinf(theta)
sgl.v2f(xx + x, yy + y)
sgl.v2f(x, y)
}
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)
mut xx := f32(r * math.cosf(start_angle))
mut yy := f32(r * math.sinf(start_angle))
sgl.begin_line_strip()
2020-10-10 10:37:17 +02:00
for i := 0; i < segments + 1; i++ {
sgl.v2f(xx + x, yy + y)
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)
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()
2020-10-10 10:37:17 +02:00
for i := 0; i < segments + 1; i++ {
sgl.v2f(xx + x, yy + y)
sgl.v2f(x, y)
tx := -yy
ty := xx
xx += tx * tan_factor
yy += ty * tan_factor
xx *= rad_factor
yy *= rad_factor
}
sgl.end()
2020-07-13 01:02:47 +02:00
}
2020-06-04 20:26:18 +02:00
pub fn (gg &Context) begin() {
2020-07-06 19:45:00 +02:00
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)
2020-01-27 20:42:32 +01:00
}
2020-06-04 20:26:18 +02:00
pub fn (gg &Context) end() {
gfx.begin_default_pass(gg.clear_pass, sapp.width(), sapp.height())
sgl.draw()
gfx.end_pass()
gfx.commit()
2020-11-20 23:49:52 +01:00
/*
if gg.config.wait_events {
2020-08-01 23:40:25 +02:00
// println('gg: waiting')
wait_events()
}
2020-11-20 23:49:52 +01:00
*/
2019-06-22 20:20:28 +02:00
}
2020-08-23 04:57:12 +02:00
fn abs(a f32) f32 {
if a >= 0 {
return a
}
return -a
}
pub fn (ctx &Context) draw_line(x f32, y f32, x2 f32, y2 f32, c gx.Color) {
if c.a != 255 {
sgl.load_pipeline(ctx.timage_pip)
}
2020-08-23 04:57:12 +02:00
if ctx.scale > 1 {
// Make the line more clear on hi dpi screens: draw a rectangle
mut width := abs(x2 - x)
mut height := abs(y2 - y)
2020-10-10 10:37:17 +02:00
if width == 0 {
2020-08-23 04:57:12 +02:00
width = 1
2020-10-10 10:37:17 +02:00
} else if height == 0 {
2020-08-23 04:57:12 +02:00
height = 1
}
ctx.draw_rect(x, y, width, height, c)
return
}
2020-08-19 07:10:42 +02:00
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()
2020-08-01 23:40:25 +02:00
}
2020-11-22 20:13:40 +01:00
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 * f32(ctx.scale)
nx := x * f32(ctx.scale)
ny := y * f32(ctx.scale)
width := w * f32(ctx.scale)
height := h * f32(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 + 2 * 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 + 2 * 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 + 2 * 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()
2020-07-06 19:45:00 +02:00
}
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 * f32(ctx.scale)
nx := x * f32(ctx.scale)
ny := y * f32(ctx.scale)
width := w * f32(ctx.scale)
height := h * f32(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 + 2 * 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 + 2 * 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 + 2 * 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()
}
2020-12-03 23:17:00 +01:00
pub fn screen_size() Size {
$if macos {
return C.gg_get_screen_size()
}
// TODO windows, linux, etc
return Size{}
}
fn C.WaitMessage()
2020-11-20 23:49:52 +01:00
/*
pub fn wait_events() {
unsafe {
2020-08-01 23:40:25 +02:00
$if macos {
#NSEvent *event = [NSApp nextEventMatchingMask:NSEventMaskAny
#untilDate:[NSDate distantFuture]
#inMode:NSDefaultRunLoopMode
#dequeue:YES];
#[NSApp sendEvent:event];
}
$if windows {
C.WaitMessage()
}
}
2020-02-03 04:01:39 +01:00
}
2020-11-20 23:49:52 +01:00
*/