From bbb174e3c49bca80515174e591f38af3ae1d9993 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Wed, 10 Dec 2025 16:04:32 +0200 Subject: [PATCH] gg, examples: add optional `update_fn: fn (dt f32, ctx &gg.Context)` to gg.Context and gg.Config . Pass consistently the current GG context to *all* callback functions, instead of nil. Previously, when the user_data field: was not set explicitly in the gg.new_context() call, it was still passed to *some* of the callbacks, which was harder to understand, and hindered simple GG apps. Now, when the user_data field is not set explicitly, all gg callbacks will receive a pointer to the current gg context, instead of nil. --- examples/gg/moving_square.v | 34 +++++++++++++++++++++++++ vlib/gg/gg.c.v | 49 +++++++++++++++++++++++-------------- vlib/gg/gg.v | 4 +++ 3 files changed, 68 insertions(+), 19 deletions(-) create mode 100644 examples/gg/moving_square.v diff --git a/examples/gg/moving_square.v b/examples/gg/moving_square.v new file mode 100644 index 000000000..2c5693a40 --- /dev/null +++ b/examples/gg/moving_square.v @@ -0,0 +1,34 @@ +import gg + +struct App { +mut: + x f64 = 100.0 + y f64 = 100.0 +} + +mut app := &App{} +gg.start( + window_title: 'Moving Square' + width: 640 + height: 480 + update_fn: fn [mut app] (dt f32, ctx &gg.Context) { + println(' frame: ${ctx.frame:6} | dt: ${dt:9.6f}s') + if ctx.pressed_keys[gg.KeyCode.right] { + app.x = app.x + 200 * dt + } + if ctx.pressed_keys[gg.KeyCode.left] { + app.x = app.x - 200 * dt + } + if ctx.pressed_keys[gg.KeyCode.down] { + app.y = app.y + 200 * dt + } + if ctx.pressed_keys[gg.KeyCode.up] { + app.y = app.y - 200 * dt + } + } + frame_fn: fn [mut app] (ctx &gg.Context) { + ctx.begin() + ctx.draw_rect_filled(int(app.x), int(app.y), 50, 50, gg.red) + ctx.end() + } +) diff --git a/vlib/gg/gg.c.v b/vlib/gg/gg.c.v index fa240a2e5..2e41f5fcc 100644 --- a/vlib/gg/gg.c.v +++ b/vlib/gg/gg.c.v @@ -101,6 +101,8 @@ pub: cleanup_fn FNCb = unsafe { nil } // Called once, after Sokol determines that the application is finished/closed. Put your app specific cleanup/free actions here. fail_fn FNFail = unsafe { nil } // Called once per Sokol error/log message. TODO: currently it does nothing with latest Sokol, reimplement using Sokol's new sapp_logger APIs. + update_fn FNUpdate = unsafe { nil } // Called once at the start of each frame, so usually ~60 times a second. The first argument is the delta `dt` time passed, since the *previous* update call (in seconds). + event_fn FNEvent = unsafe { nil } // Called once per each user initiated event, received by Sokol/GG. on_event FNEvent2 = unsafe { nil } // Called once per each user initiated event, received by Sokol/GG. Same as event_fn, just the parameter order is different. TODO: deprecate this, in favor of event_fn quit_fn FNEvent = unsafe { nil } // Called when the user closes the app window. @@ -202,7 +204,10 @@ pub mut: 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 - timer time.StopWatch + // + timer time.StopWatch // starts right after new_context, and can be controlled/stopped/restarted in whatever way the user wants. + update_timer time.StopWatch // measures how much time has passed since the start of the frame. + // Note: when there is an update_fn, this timer is reset by GG itself, at the start of each frame. mbtn_mask u8 mouse_buttons MouseButtons // typed version of mbtn_mask; easier to use for user programs @@ -287,6 +292,7 @@ fn gg_init_sokol_window(user_data voidptr) { ctx.pipeline.init_pipeline() ctx.timer = time.new_stopwatch() + ctx.update_timer = time.new_stopwatch() if ctx.config.init_fn != unsafe { nil } { $if android { // NOTE on Android sokol can emit resize events *before* the init function is @@ -348,6 +354,11 @@ fn gg_frame_fn(mut ctx Context) { return } } + if ctx.config.update_fn != unsafe { nil } { + dt := ctx.update_timer.elapsed().seconds() + ctx.update_timer.restart() + ctx.config.update_fn(f32(dt), ctx.user_data) + } ctx.config.frame_fn(ctx.user_data) ctx.needs_refresh = false } @@ -402,66 +413,66 @@ fn gg_event_fn(ce voidptr, user_data voidptr) { ctx.pressed_keys_edge[key_idx] = prev != next } if ctx.config.event_fn != unsafe { nil } { - ctx.config.event_fn(e, ctx.config.user_data) + ctx.config.event_fn(e, ctx.user_data) } else if ctx.config.on_event != unsafe { nil } { - ctx.config.on_event(ctx.config.user_data, e) + ctx.config.on_event(ctx.user_data, e) } match e.typ { .mouse_move { if ctx.config.move_fn != unsafe { nil } { - ctx.config.move_fn(e.mouse_x, e.mouse_y, ctx.config.user_data) + ctx.config.move_fn(e.mouse_x, e.mouse_y, ctx.user_data) } } .mouse_down { if ctx.config.click_fn != unsafe { nil } { - ctx.config.click_fn(e.mouse_x, e.mouse_y, e.mouse_button, ctx.config.user_data) + ctx.config.click_fn(e.mouse_x, e.mouse_y, e.mouse_button, ctx.user_data) } } .mouse_up { if ctx.config.unclick_fn != unsafe { nil } { - ctx.config.unclick_fn(e.mouse_x, e.mouse_y, e.mouse_button, ctx.config.user_data) + ctx.config.unclick_fn(e.mouse_x, e.mouse_y, e.mouse_button, ctx.user_data) } } .mouse_leave { if ctx.config.leave_fn != unsafe { nil } { - ctx.config.leave_fn(e, ctx.config.user_data) + ctx.config.leave_fn(e, ctx.user_data) } } .mouse_enter { if ctx.config.enter_fn != unsafe { nil } { - ctx.config.enter_fn(e, ctx.config.user_data) + ctx.config.enter_fn(e, ctx.user_data) } } .mouse_scroll { if ctx.config.scroll_fn != unsafe { nil } { - ctx.config.scroll_fn(e, ctx.config.user_data) + ctx.config.scroll_fn(e, ctx.user_data) } } .key_down { if ctx.config.keydown_fn != unsafe { nil } { - ctx.config.keydown_fn(e.key_code, unsafe { Modifier(e.modifiers) }, ctx.config.user_data) + ctx.config.keydown_fn(e.key_code, unsafe { Modifier(e.modifiers) }, ctx.user_data) } } .key_up { if ctx.config.keyup_fn != unsafe { nil } { - ctx.config.keyup_fn(e.key_code, unsafe { Modifier(e.modifiers) }, ctx.config.user_data) + ctx.config.keyup_fn(e.key_code, unsafe { Modifier(e.modifiers) }, ctx.user_data) } } .char { if ctx.config.char_fn != unsafe { nil } { - ctx.config.char_fn(e.char_code, ctx.config.user_data) + ctx.config.char_fn(e.char_code, ctx.user_data) } } .resized { ctx.scale = dpi_scale() ctx.ft.scale = ctx.scale if ctx.config.resized_fn != unsafe { nil } { - ctx.config.resized_fn(e, ctx.config.user_data) + ctx.config.resized_fn(e, ctx.user_data) } } .quit_requested { if ctx.config.quit_fn != unsafe { nil } { - ctx.config.quit_fn(e, ctx.config.user_data) + ctx.config.quit_fn(e, ctx.user_data) } } else { @@ -481,12 +492,12 @@ fn gg_event_fn(ce voidptr, user_data voidptr) { e.key_code = .invalid e.typ = .char if ctx.config.event_fn != unsafe { nil } { - ctx.config.event_fn(e, ctx.config.user_data) + ctx.config.event_fn(e, ctx.user_data) } else if ctx.config.on_event != unsafe { nil } { - ctx.config.on_event(ctx.config.user_data, e) + ctx.config.on_event(ctx.user_data, e) } if ctx.config.char_fn != unsafe { nil } { - ctx.config.char_fn(e.char_code, ctx.config.user_data) + ctx.config.char_fn(e.char_code, ctx.user_data) } } } @@ -495,7 +506,7 @@ fn gg_event_fn(ce voidptr, user_data voidptr) { fn gg_cleanup_fn(user_data voidptr) { mut ctx := unsafe { &Context(user_data) } if ctx.config.cleanup_fn != unsafe { nil } { - ctx.config.cleanup_fn(ctx.config.user_data) + ctx.config.cleanup_fn(ctx.user_data) } gfx.shutdown() } @@ -504,7 +515,7 @@ fn gg_fail_fn(msg &char, user_data voidptr) { mut ctx := unsafe { &Context(user_data) } vmsg := unsafe { tos3(msg) } if ctx.config.fail_fn != unsafe { nil } { - ctx.config.fail_fn(vmsg, ctx.config.user_data) + ctx.config.fail_fn(vmsg, ctx.user_data) } else { eprintln('gg error: ${vmsg}') } diff --git a/vlib/gg/gg.v b/vlib/gg/gg.v index 9fbbed7d5..297c164f8 100644 --- a/vlib/gg/gg.v +++ b/vlib/gg/gg.v @@ -32,6 +32,10 @@ pub type FNUnClick = fn (x f32, y f32, button MouseButton, data voidptr) // FNChar defines the type of a function that will be called once per character pub type FNChar = fn (c u32, data voidptr) +// FNUpdate defines the type of a function, that will be called at the start of each frame +// with an argument `dt`, that has the passed time in seconds, since the previous update. +pub type FNUpdate = fn (dt f32, data voidptr) + pub struct PenConfig { pub: color Color -- 2.39.5