| 1 | // Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved. |
| 2 | // Use of this source code is governed by an MIT license that can be found in the LICENSE file. |
| 3 | |
| 4 | module gg |
| 5 | |
| 6 | import os |
| 7 | import os.font |
| 8 | import time |
| 9 | import sokol.sapp |
| 10 | import sokol.sgl |
| 11 | import sokol.gfx |
| 12 | |
| 13 | @[typedef] |
| 14 | struct C.XRRScreenResources { |
| 15 | noutput int |
| 16 | outputs &int |
| 17 | } |
| 18 | |
| 19 | @[typedef] |
| 20 | struct C.XRROutputInfo { |
| 21 | crtc u64 |
| 22 | } |
| 23 | |
| 24 | @[typedef] |
| 25 | struct C.XRRCrtcInfo { |
| 26 | width u32 |
| 27 | height u32 |
| 28 | } |
| 29 | |
| 30 | fn C.XOpenDisplay(i32) voidptr |
| 31 | fn C.XCloseDisplay(voidptr) i32 |
| 32 | fn C.DefaultScreen(voidptr) i32 |
| 33 | fn C.DefaultRootWindow(voidptr) u64 |
| 34 | fn C.XRRGetScreenResources(voidptr, u64) &C.XRRScreenResources |
| 35 | fn C.XRRGetOutputPrimary(voidptr, u64) u64 |
| 36 | fn C.XRRFreeScreenResources(&C.XRRScreenResources) |
| 37 | fn C.XRRGetOutputInfo(voidptr, &C.XRRScreenResources, u64) &C.XRROutputInfo |
| 38 | fn C.XRRFreeOutputInfo(&C.XRROutputInfo) |
| 39 | fn C.XRRGetCrtcInfo(voidptr, &C.XRRScreenResources, u64) &C.XRRCrtcInfo |
| 40 | fn C.XRRFreeCrtcInfo(&C.XRRCrtcInfo) |
| 41 | |
| 42 | $if linux { |
| 43 | #flag -lXrandr |
| 44 | #include <X11/extensions/Xrandr.h> |
| 45 | } |
| 46 | |
| 47 | $if windows { |
| 48 | #flag -lgdi32 |
| 49 | #include "windows.h" |
| 50 | } |
| 51 | |
| 52 | #flag wasm32_emscripten --embed-file @VEXEROOT/examples/assets/fonts/RobotoMono-Regular.ttf@/assets/fonts/RobotoMono-Regular.ttf |
| 53 | |
| 54 | $if macos { |
| 55 | fn C.gg_macos_resize_window(window voidptr, width int, height int) |
| 56 | fn C.gg_macos_set_window_resizable(window voidptr, resizable bool) |
| 57 | } |
| 58 | |
| 59 | $if windows { |
| 60 | struct C.RECT { |
| 61 | left i32 |
| 62 | top i32 |
| 63 | right i32 |
| 64 | bottom i32 |
| 65 | } |
| 66 | |
| 67 | fn C.GetWindowRect(hwnd voidptr, rect &C.RECT) int |
| 68 | fn C.GetWindowLongW(hwnd voidptr, index int) i32 |
| 69 | fn C.SetWindowLongW(hwnd voidptr, index int, new_long i32) i32 |
| 70 | fn C.AdjustWindowRectEx(rect &C.RECT, style u32, menu int, ex_style u32) int |
| 71 | fn C.SetWindowPos(hwnd voidptr, hwnd_insert_after voidptr, x int, y int, cx int, cy int, flags u32) int |
| 72 | } |
| 73 | |
| 74 | // call Windows API to get screen size |
| 75 | fn C.GetSystemMetrics(i32) i32 |
| 76 | fn C.XResizeWindow(display voidptr, window u64, width u32, height u32) int |
| 77 | fn C.XFlush(display voidptr) int |
| 78 | |
| 79 | pub type TouchPoint = C.sapp_touchpoint |
| 80 | |
| 81 | pub struct Event { |
| 82 | pub mut: |
| 83 | frame_count u64 |
| 84 | typ sapp.EventType |
| 85 | key_code KeyCode |
| 86 | char_code u32 |
| 87 | key_repeat bool |
| 88 | modifiers u32 |
| 89 | mouse_button MouseButton |
| 90 | mouse_x f32 |
| 91 | mouse_y f32 |
| 92 | mouse_dx f32 |
| 93 | mouse_dy f32 |
| 94 | scroll_x f32 |
| 95 | scroll_y f32 |
| 96 | num_touches int |
| 97 | touches [8]TouchPoint |
| 98 | window_width int |
| 99 | window_height int |
| 100 | framebuffer_width int |
| 101 | framebuffer_height int |
| 102 | } |
| 103 | |
| 104 | pub struct Config { |
| 105 | pub: |
| 106 | width int = 800 // desired start width of the window |
| 107 | height int = 600 // desired start height of the window |
| 108 | retina bool // TODO: implement or deprecate |
| 109 | resizable bool = true // prevent user-initiated resizing when supported by the platform backend |
| 110 | user_data voidptr // a custom pointer to the application data/instance. When it is not set explicitly, it will default to a pointer to the current gg.Context instance. |
| 111 | font_size int // TODO: implement or deprecate |
| 112 | create_window bool // TODO: implement or deprecate |
| 113 | // window_user_ptr voidptr |
| 114 | window_title string = 'A GG Window. Set window_title: to change it.' // the desired title of the window |
| 115 | icon sapp.IconDesc |
| 116 | html5_canvas_name string = 'canvas' |
| 117 | borderless_window bool // create the window without native decorations when supported by the platform backend |
| 118 | always_on_top bool // TODO: implement or deprecate |
| 119 | bg_color Color // The background color of the window. By default, the first thing gg does in ctx.begin(), is clear the whole buffer with that color. |
| 120 | init_fn FNCb = unsafe { nil } // Called once, after Sokol has finished its setup. Some gg and Sokol functions have to be called *in this* callback, or after this callback, but not before |
| 121 | frame_fn FNCb = unsafe { nil } // Called once per frame, usually 60 times a second (depends on swap_interval). See also https://dri.freedesktop.org/wiki/ConfigurationOptions/#synchronizationwithverticalrefreshswapintervals |
| 122 | native_frame_fn FNCb = unsafe { nil } |
| 123 | cleanup_fn FNCb = unsafe { nil } // Called once, after Sokol determines that the application is finished/closed. Put your app specific cleanup/free actions here. |
| 124 | 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. |
| 125 | |
| 126 | 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). |
| 127 | |
| 128 | event_fn FNEvent = unsafe { nil } // Called once per each user initiated event, received by Sokol/GG. |
| 129 | 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 |
| 130 | quit_fn FNEvent = unsafe { nil } // Called when the user closes the app window. |
| 131 | |
| 132 | keydown_fn FNKeyDown = unsafe { nil } // Called once per key press, no matter how long the key is held down. Note that here you can access the scan code/physical key, but not the logical character. |
| 133 | keyup_fn FNKeyUp = unsafe { nil } // Called once per key press, when the key is released. |
| 134 | char_fn FNChar = unsafe { nil } // Called once per character (after the key is pressed down, and then released). Note that you can access the character/utf8 rune here, not just the scan code. |
| 135 | |
| 136 | move_fn FNMove = unsafe { nil } // Called while the mouse/touch point is moving. |
| 137 | click_fn FNClick = unsafe { nil } // Called once when the mouse/touch button is clicked. |
| 138 | unclick_fn FNUnClick = unsafe { nil } // Called once when the mouse/touch button is released. |
| 139 | leave_fn FNEvent = unsafe { nil } // Called once when the mouse/touch point leaves the window. |
| 140 | enter_fn FNEvent = unsafe { nil } // Called once when the mouse/touch point enters again the window. |
| 141 | resized_fn FNEvent = unsafe { nil } // Called once when the window has changed its size. |
| 142 | scroll_fn FNEvent = unsafe { nil } // Called while the user is scrolling. The direction of scrolling is indicated by either 1 or -1. |
| 143 | // wait_events bool // set this to true for UIs, to save power |
| 144 | fullscreen bool // set this to true, if you want your window to start in fullscreen mode (suitable for games/demos/screensavers) |
| 145 | scale f32 = 1.0 |
| 146 | sample_count int // bigger values usually have performance impact, but can produce smoother/antialiased lines, if you draw lines or polygons (2 is usually good enough) |
| 147 | texture_filter TextureFilter = .linear // default texture filter for newly created images; use `.nearest` for pixel art scaling |
| 148 | swap_interval int = 1 // 1 = 60fps, 2 = 30fps etc. Honored on Windows, macOS, Linux, iOS, and HTML5; Android support is not implemented yet. |
| 149 | // ved needs this |
| 150 | // init_text bool |
| 151 | font_path string |
| 152 | custom_bold_font_path string |
| 153 | ui_mode bool // refreshes only on events to save CPU usage |
| 154 | // font bytes for embedding |
| 155 | font_bytes_normal []u8 |
| 156 | font_bytes_bold []u8 |
| 157 | font_bytes_mono []u8 |
| 158 | font_bytes_italic []u8 |
| 159 | native_rendering bool // Cocoa on macOS/iOS, GDI+ on Windows |
| 160 | // drag&drop |
| 161 | enable_dragndrop bool // enable file dropping (drag'n'drop), default is false |
| 162 | max_dropped_files int = 1 // max number of dropped files to process (default: 1) |
| 163 | max_dropped_file_path_length int = 2048 // max length in bytes of a dropped UTF-8 file path (default: 2048) |
| 164 | |
| 165 | min_width int // desired minimum width of the window |
| 166 | min_height int // desired minimum height of the window |
| 167 | } |
| 168 | |
| 169 | @[heap] |
| 170 | pub struct PipelineContainer { |
| 171 | pub mut: |
| 172 | alpha sgl.Pipeline |
| 173 | add sgl.Pipeline |
| 174 | } |
| 175 | |
| 176 | fn (mut container PipelineContainer) init_pipeline() { |
| 177 | // FIXME(FireRedz): this looks kinda funny, find a better way to initialize pipeline. |
| 178 | |
| 179 | // Alpha |
| 180 | mut alpha_pipdesc := gfx.PipelineDesc{} |
| 181 | unsafe { vmemset(&alpha_pipdesc, 0, int(sizeof(alpha_pipdesc))) } |
| 182 | alpha_pipdesc.label = c'alpha-pipeline' |
| 183 | alpha_pipdesc.colors[0] = gfx.ColorTargetState{ |
| 184 | blend: gfx.BlendState{ |
| 185 | enabled: true |
| 186 | src_factor_rgb: .src_alpha |
| 187 | dst_factor_rgb: .one_minus_src_alpha |
| 188 | } |
| 189 | } |
| 190 | container.alpha = sgl.make_pipeline(&alpha_pipdesc) |
| 191 | |
| 192 | // Add |
| 193 | mut add_pipdesc := gfx.PipelineDesc{} |
| 194 | unsafe { vmemset(&add_pipdesc, 0, int(sizeof(add_pipdesc))) } |
| 195 | add_pipdesc.label = c'additive-pipeline' |
| 196 | add_pipdesc.colors[0] = gfx.ColorTargetState{ |
| 197 | blend: gfx.BlendState{ |
| 198 | enabled: true |
| 199 | src_factor_rgb: .src_alpha |
| 200 | dst_factor_rgb: .one |
| 201 | } |
| 202 | } |
| 203 | container.add = sgl.make_pipeline(&add_pipdesc) |
| 204 | } |
| 205 | |
| 206 | @[heap] |
| 207 | pub struct Context { |
| 208 | mut: |
| 209 | render_text bool = true |
| 210 | // a cache with all images created by the user. used for sokol image init and to save space |
| 211 | // (so that the user can store image ids, not entire Image objects) |
| 212 | image_cache []Image |
| 213 | needs_refresh bool = true |
| 214 | ticks int // for ui mode only |
| 215 | last_bg_overlay_frame u64 |
| 216 | translucent_bg_seed_pending bool |
| 217 | pub: |
| 218 | native_rendering bool |
| 219 | pub mut: |
| 220 | scale f32 = 1.0 // will get set to 2.0 for retina, will remain 1.0 for normal |
| 221 | width int |
| 222 | height int |
| 223 | clear_pass gfx.PassAction |
| 224 | bg_color Color |
| 225 | window sapp.Desc |
| 226 | pipeline &PipelineContainer = unsafe { nil } |
| 227 | config Config |
| 228 | user_data voidptr |
| 229 | ft &FT = unsafe { nil } |
| 230 | font_inited bool |
| 231 | ui_mode bool // do not redraw everything 60 times/second, but only when the user requests |
| 232 | frame u64 // the current frame counted from the start of the application; always increasing |
| 233 | // |
| 234 | timer time.StopWatch // starts right after new_context, and can be controlled/stopped/restarted in whatever way the user wants. |
| 235 | update_timer time.StopWatch // measures how much time has passed since the start of the frame. |
| 236 | frame_timer time.StopWatch // enforces swap_interval as a fallback when the platform ignores vsync. |
| 237 | // Note: when there is an update_fn, this timer is reset by GG itself, at the start of each frame. |
| 238 | |
| 239 | mbtn_mask u8 |
| 240 | mouse_buttons MouseButtons // typed version of mbtn_mask; easier to use for user programs |
| 241 | mouse_pos_x int |
| 242 | mouse_pos_y int |
| 243 | mouse_dx int |
| 244 | mouse_dy int |
| 245 | scroll_x int |
| 246 | scroll_y int |
| 247 | |
| 248 | key_modifiers Modifier // the current key modifiers |
| 249 | key_repeat bool // whether the pressed key was an autorepeated one |
| 250 | pressed_keys [key_code_max]bool // an array representing all currently pressed keys |
| 251 | pressed_keys_edge [key_code_max]bool // true when the previous state of pressed_keys, |
| 252 | // *before* the current event was different |
| 253 | fps FPSConfig |
| 254 | has_started bool |
| 255 | } |
| 256 | |
| 257 | fn gg_init_sokol_window(user_data voidptr) { |
| 258 | mut ctx := unsafe { &Context(user_data) } |
| 259 | desc := sapp.create_desc() |
| 260 | /* |
| 261 | desc := gfx.Desc{ |
| 262 | mtl_device: sapp.metal_get_device() |
| 263 | mtl_renderpass_descriptor_cb: sapp.metal_get_renderpass_descriptor |
| 264 | mtl_drawable_cb: sapp.metal_get_drawable |
| 265 | d3d11_device: sapp.d3d11_get_device() |
| 266 | d3d11_device_context: sapp.d3d11_get_device_context() |
| 267 | d3d11_render_target_view_cb: sapp.d3d11_get_render_target_view |
| 268 | d3d11_depth_stencil_view_cb: sapp.d3d11_get_depth_stencil_view |
| 269 | } |
| 270 | */ |
| 271 | gfx.setup(&desc) |
| 272 | sgl_desc := sgl.Desc{} |
| 273 | sgl.setup(&sgl_desc) |
| 274 | ctx.set_scale() |
| 275 | if !ctx.config.resizable { |
| 276 | gg_apply_window_resizable() |
| 277 | } |
| 278 | // is_high_dpi := sapp.high_dpi() |
| 279 | // fb_w := sapp.width() |
| 280 | // fb_h := sapp.height() |
| 281 | // println('ctx.scale=${ctx.scale} is_high_dpi=${is_high_dpi} fb_w=${fb_w} fb_h=${fb_h}') |
| 282 | // if ctx.config.init_text { |
| 283 | // `os.is_file()` won't work on Android if the font file is embedded into the APK |
| 284 | exists := $if !android { os.is_file(ctx.config.font_path) } $else { true } |
| 285 | if ctx.config.font_path != '' && exists { |
| 286 | // t := time.ticks() |
| 287 | ctx.ft = new_ft( |
| 288 | font_path: ctx.config.font_path |
| 289 | custom_bold_font_path: ctx.config.custom_bold_font_path |
| 290 | scale: ctx.scale |
| 291 | ) or { panic(err) } |
| 292 | // println('FT took ${time.ticks()-t} ms') |
| 293 | ctx.font_inited = true |
| 294 | } else { |
| 295 | if ctx.config.font_bytes_normal.len > 0 { |
| 296 | ctx.ft = new_ft( |
| 297 | bytes_normal: ctx.config.font_bytes_normal |
| 298 | bytes_bold: ctx.config.font_bytes_bold |
| 299 | bytes_mono: ctx.config.font_bytes_mono |
| 300 | bytes_italic: ctx.config.font_bytes_italic |
| 301 | scale: sapp.dpi_scale() |
| 302 | ) or { panic(err) } |
| 303 | ctx.font_inited = true |
| 304 | } else { |
| 305 | sfont := font.default() |
| 306 | if ctx.config.font_path != '' { |
| 307 | eprintln('font file "${ctx.config.font_path}" does not exist, the system font (${sfont}) was used instead.') |
| 308 | } |
| 309 | |
| 310 | ctx.ft = new_ft( |
| 311 | font_path: sfont |
| 312 | custom_bold_font_path: ctx.config.custom_bold_font_path |
| 313 | scale: sapp.dpi_scale() |
| 314 | ) or { panic(err) } |
| 315 | ctx.font_inited = true |
| 316 | } |
| 317 | } |
| 318 | |
| 319 | // Pipeline |
| 320 | ctx.pipeline = &PipelineContainer{} |
| 321 | ctx.pipeline.init_pipeline() |
| 322 | |
| 323 | ctx.timer = time.new_stopwatch() |
| 324 | ctx.update_timer = time.new_stopwatch() |
| 325 | if ctx.config.init_fn != unsafe { nil } { |
| 326 | $if android { |
| 327 | // NOTE on Android sokol can emit resize events *before* the init function is |
| 328 | // called (Android has to initialize a lot more through the Activity system to |
| 329 | // reach a valid coontext) and thus the user's code will miss the resize event. |
| 330 | // To prevent this we emit a custom window resize event, if the screen size has |
| 331 | // changed meanwhile. |
| 332 | win_size := ctx.window_size() |
| 333 | if ctx.width != win_size.width || ctx.height != win_size.height { |
| 334 | ctx.set_cached_window_size(win_size.width, win_size.height) |
| 335 | if ctx.config.resized_fn != unsafe { nil } { |
| 336 | e := Event{ |
| 337 | typ: .resized |
| 338 | window_width: ctx.width |
| 339 | window_height: ctx.height |
| 340 | } |
| 341 | ctx.config.resized_fn(&e, ctx.user_data) |
| 342 | } |
| 343 | } |
| 344 | } |
| 345 | ctx.config.init_fn(ctx.user_data) |
| 346 | } |
| 347 | ctx.has_started = true |
| 348 | // Create images now that we can do that after sg is inited |
| 349 | if ctx.native_rendering { |
| 350 | return |
| 351 | } |
| 352 | |
| 353 | for i in 0 .. ctx.image_cache.len { |
| 354 | if ctx.image_cache[i].simg.id == 0 { |
| 355 | ctx.image_cache[i].init_sokol_image() |
| 356 | } |
| 357 | } |
| 358 | } |
| 359 | |
| 360 | fn gg_frame_fn(mut ctx Context) { |
| 361 | ctx.frame++ |
| 362 | if ctx.config.frame_fn == unsafe { nil } { |
| 363 | return |
| 364 | } |
| 365 | ctx.pace_frame() |
| 366 | if ctx.native_rendering { |
| 367 | // return |
| 368 | } |
| 369 | defer { |
| 370 | ctx.mouse_dx = 0 |
| 371 | ctx.mouse_dy = 0 |
| 372 | ctx.scroll_x = 0 |
| 373 | ctx.scroll_y = 0 |
| 374 | } |
| 375 | |
| 376 | ctx.record_frame() |
| 377 | ctx.memory_trace_frame() |
| 378 | |
| 379 | if ctx.ui_mode && !ctx.needs_refresh { |
| 380 | // println('ui mode, exiting') |
| 381 | // Draw 3 more frames after the "stop refresh" command |
| 382 | ctx.ticks++ |
| 383 | if ctx.ticks > 3 { |
| 384 | return |
| 385 | } |
| 386 | } |
| 387 | if ctx.config.update_fn != unsafe { nil } { |
| 388 | dt := ctx.update_timer.elapsed().seconds() |
| 389 | ctx.update_timer.restart() |
| 390 | ctx.config.update_fn(f32(dt), ctx.user_data) |
| 391 | } |
| 392 | ctx.config.frame_fn(ctx.user_data) |
| 393 | ctx.needs_refresh = false |
| 394 | } |
| 395 | |
| 396 | fn swap_interval_frame_budget(swap_interval int) time.Duration { |
| 397 | if swap_interval <= 0 { |
| 398 | return time.Duration(0) |
| 399 | } |
| 400 | return time.Duration(swap_interval) * time.second / 60 |
| 401 | } |
| 402 | |
| 403 | fn (mut ctx Context) pace_frame() { |
| 404 | frame_budget := swap_interval_frame_budget(ctx.config.swap_interval) |
| 405 | if frame_budget <= 0 || ctx.frame <= 1 { |
| 406 | ctx.frame_timer.restart() |
| 407 | return |
| 408 | } |
| 409 | elapsed := ctx.frame_timer.elapsed() |
| 410 | $if !emscripten ? { |
| 411 | if elapsed < frame_budget { |
| 412 | time.sleep(frame_budget - elapsed) |
| 413 | } |
| 414 | } |
| 415 | ctx.frame_timer.restart() |
| 416 | } |
| 417 | |
| 418 | fn gg_event_fn(ce voidptr, user_data voidptr) { |
| 419 | // e := unsafe { &sapp.Event(ce) } |
| 420 | mut e := unsafe { &Event(ce) } |
| 421 | mut ctx := unsafe { &Context(user_data) } |
| 422 | if ctx.ui_mode { |
| 423 | ctx.refresh_ui() |
| 424 | } |
| 425 | if e.typ == .mouse_down { |
| 426 | bitplace := int(e.mouse_button) |
| 427 | ctx.mbtn_mask |= u8(1 << bitplace) |
| 428 | ctx.mouse_buttons = unsafe { MouseButtons(ctx.mbtn_mask) } |
| 429 | } |
| 430 | if e.typ == .mouse_up { |
| 431 | bitplace := int(e.mouse_button) |
| 432 | ctx.mbtn_mask &= ~(u8(1 << bitplace)) |
| 433 | ctx.mouse_buttons = unsafe { MouseButtons(ctx.mbtn_mask) } |
| 434 | } |
| 435 | if e.typ == .mouse_move && e.mouse_button == .invalid { |
| 436 | if ctx.mbtn_mask & 0x01 > 0 { |
| 437 | e.mouse_button = .left |
| 438 | } |
| 439 | if ctx.mbtn_mask & 0x02 > 0 { |
| 440 | e.mouse_button = .right |
| 441 | } |
| 442 | if ctx.mbtn_mask & 0x04 > 0 { |
| 443 | e.mouse_button = .middle |
| 444 | } |
| 445 | } |
| 446 | e.mouse_x /= ctx.scale |
| 447 | e.mouse_y /= ctx.scale |
| 448 | e.mouse_dx /= ctx.scale |
| 449 | e.mouse_dy /= ctx.scale |
| 450 | e.scroll_x /= ctx.scale |
| 451 | e.scroll_y /= ctx.scale |
| 452 | ctx.mouse_pos_x = int(e.mouse_x) |
| 453 | ctx.mouse_pos_y = int(e.mouse_y) |
| 454 | ctx.mouse_dx = int(e.mouse_dx) |
| 455 | ctx.mouse_dy = int(e.mouse_dy) |
| 456 | ctx.scroll_x = int(e.scroll_x) |
| 457 | ctx.scroll_y = int(e.scroll_y) |
| 458 | ctx.key_modifiers = unsafe { Modifier(e.modifiers) } |
| 459 | ctx.key_repeat = e.key_repeat |
| 460 | if e.typ in [.key_down, .key_up] { |
| 461 | key_idx := int(e.key_code) % key_code_max |
| 462 | prev := ctx.pressed_keys[key_idx] |
| 463 | next := e.typ == .key_down |
| 464 | ctx.pressed_keys[key_idx] = next |
| 465 | ctx.pressed_keys_edge[key_idx] = prev != next |
| 466 | } |
| 467 | if ctx.config.event_fn != unsafe { nil } { |
| 468 | ctx.config.event_fn(e, ctx.user_data) |
| 469 | } else if ctx.config.on_event != unsafe { nil } { |
| 470 | ctx.config.on_event(ctx.user_data, e) |
| 471 | } |
| 472 | match e.typ { |
| 473 | .mouse_move { |
| 474 | if ctx.config.move_fn != unsafe { nil } { |
| 475 | ctx.config.move_fn(e.mouse_x, e.mouse_y, ctx.user_data) |
| 476 | } |
| 477 | } |
| 478 | .mouse_down { |
| 479 | if ctx.config.click_fn != unsafe { nil } { |
| 480 | ctx.config.click_fn(e.mouse_x, e.mouse_y, e.mouse_button, ctx.user_data) |
| 481 | } |
| 482 | } |
| 483 | .mouse_up { |
| 484 | if ctx.config.unclick_fn != unsafe { nil } { |
| 485 | ctx.config.unclick_fn(e.mouse_x, e.mouse_y, e.mouse_button, ctx.user_data) |
| 486 | } |
| 487 | } |
| 488 | .mouse_leave { |
| 489 | if ctx.config.leave_fn != unsafe { nil } { |
| 490 | ctx.config.leave_fn(e, ctx.user_data) |
| 491 | } |
| 492 | } |
| 493 | .mouse_enter { |
| 494 | if ctx.config.enter_fn != unsafe { nil } { |
| 495 | ctx.config.enter_fn(e, ctx.user_data) |
| 496 | } |
| 497 | } |
| 498 | .mouse_scroll { |
| 499 | if ctx.config.scroll_fn != unsafe { nil } { |
| 500 | ctx.config.scroll_fn(e, ctx.user_data) |
| 501 | } |
| 502 | } |
| 503 | .key_down { |
| 504 | if ctx.config.keydown_fn != unsafe { nil } { |
| 505 | ctx.config.keydown_fn(e.key_code, unsafe { Modifier(e.modifiers) }, ctx.user_data) |
| 506 | } |
| 507 | } |
| 508 | .key_up { |
| 509 | if ctx.config.keyup_fn != unsafe { nil } { |
| 510 | ctx.config.keyup_fn(e.key_code, unsafe { Modifier(e.modifiers) }, ctx.user_data) |
| 511 | } |
| 512 | } |
| 513 | .char { |
| 514 | if ctx.config.char_fn != unsafe { nil } { |
| 515 | ctx.config.char_fn(e.char_code, ctx.user_data) |
| 516 | } |
| 517 | } |
| 518 | .resized { |
| 519 | ctx.scale = dpi_scale() |
| 520 | ctx.ft.scale = ctx.scale |
| 521 | ctx.set_cached_window_size(e.window_width, e.window_height) |
| 522 | if ctx.config.resized_fn != unsafe { nil } { |
| 523 | ctx.config.resized_fn(e, ctx.user_data) |
| 524 | } |
| 525 | } |
| 526 | .quit_requested { |
| 527 | if ctx.config.quit_fn != unsafe { nil } { |
| 528 | ctx.config.quit_fn(e, ctx.user_data) |
| 529 | } |
| 530 | } |
| 531 | else { |
| 532 | // dump(e) |
| 533 | } |
| 534 | } |
| 535 | |
| 536 | $if windows || (linux && !sokol_wayland ?) { |
| 537 | if e.typ == .key_down && e.key_code in [.backspace, .delete, .enter, .tab] { |
| 538 | // with Win32 and X11, sokol does not send .char events for some keys; |
| 539 | // we will emulate them for consistency here: |
| 540 | // NOTE: on Wayland (sokol_wayland), the backend already sends real CHAR events for these |
| 541 | // keys via xkb_state_key_get_utf8, so this block must not run there or events double-fire. |
| 542 | e.char_code = match e.key_code { |
| 543 | .backspace { u32(8) } |
| 544 | .tab { 9 } |
| 545 | .enter { 13 } |
| 546 | .delete { 127 } |
| 547 | else { u32(e.key_code) } |
| 548 | } |
| 549 | |
| 550 | e.key_code = .invalid |
| 551 | e.typ = .char |
| 552 | if ctx.config.event_fn != unsafe { nil } { |
| 553 | ctx.config.event_fn(e, ctx.user_data) |
| 554 | } else if ctx.config.on_event != unsafe { nil } { |
| 555 | ctx.config.on_event(ctx.user_data, e) |
| 556 | } |
| 557 | if ctx.config.char_fn != unsafe { nil } { |
| 558 | ctx.config.char_fn(e.char_code, ctx.user_data) |
| 559 | } |
| 560 | } |
| 561 | } |
| 562 | } |
| 563 | |
| 564 | fn gg_cleanup_fn(user_data voidptr) { |
| 565 | mut ctx := unsafe { &Context(user_data) } |
| 566 | if ctx.config.cleanup_fn != unsafe { nil } { |
| 567 | ctx.config.cleanup_fn(ctx.user_data) |
| 568 | } |
| 569 | gfx.shutdown() |
| 570 | } |
| 571 | |
| 572 | fn gg_fail_fn(msg &char, user_data voidptr) { |
| 573 | mut ctx := unsafe { &Context(user_data) } |
| 574 | vmsg := unsafe { tos3(msg) } |
| 575 | if ctx.config.fail_fn != unsafe { nil } { |
| 576 | ctx.config.fail_fn(vmsg, ctx.user_data) |
| 577 | } else { |
| 578 | eprintln('gg error: ${vmsg}') |
| 579 | } |
| 580 | } |
| 581 | |
| 582 | //---- public methods |
| 583 | |
| 584 | // start creates a new context and runs it right away. |
| 585 | // It is a convenient way to start short/throwaway gg based prototypes, |
| 586 | // that do not need to keep and update their own state, like simple |
| 587 | // animations/visualisations that depend only on the time, or the ctx.frame counter. |
| 588 | // Use gg.new_context() for more complex ones. |
| 589 | pub fn start(cfg Config) { |
| 590 | mut ctx := new_context(cfg) |
| 591 | ctx.run() |
| 592 | } |
| 593 | |
| 594 | // new_context returns an initialized `Context` allocated on the heap. |
| 595 | pub fn new_context(cfg Config) &Context { |
| 596 | mut ctx := &Context{ |
| 597 | user_data: cfg.user_data |
| 598 | width: cfg.width |
| 599 | height: cfg.height |
| 600 | config: cfg |
| 601 | ft: unsafe { nil } |
| 602 | ui_mode: cfg.ui_mode |
| 603 | native_rendering: cfg.native_rendering |
| 604 | window: sapp.Desc{ |
| 605 | init_userdata_cb: gg_init_sokol_window |
| 606 | frame_userdata_cb: gg_frame_fn |
| 607 | event_userdata_cb: gg_event_fn |
| 608 | // fail_userdata_cb: gg_fail_fn |
| 609 | cleanup_userdata_cb: gg_cleanup_fn |
| 610 | window_title: &char(cfg.window_title.str) |
| 611 | icon: cfg.icon |
| 612 | html5: sapp.Html5Desc{ |
| 613 | canvas_selector: &char(cfg.html5_canvas_name.str) |
| 614 | } |
| 615 | width: cfg.width |
| 616 | height: cfg.height |
| 617 | sample_count: cfg.sample_count |
| 618 | high_dpi: true |
| 619 | fullscreen: cfg.fullscreen |
| 620 | __v_native_render: cfg.native_rendering |
| 621 | min_width: cfg.min_width |
| 622 | min_height: cfg.min_height |
| 623 | borderless_window: cfg.borderless_window |
| 624 | // drag&drop |
| 625 | enable_dragndrop: cfg.enable_dragndrop |
| 626 | max_dropped_files: cfg.max_dropped_files |
| 627 | max_dropped_file_path_length: cfg.max_dropped_file_path_length |
| 628 | swap_interval: cfg.swap_interval |
| 629 | } |
| 630 | } |
| 631 | ctx.set_bg_color(cfg.bg_color) |
| 632 | // C.printf('new_context() %p\n', cfg.user_data) |
| 633 | return ctx |
| 634 | } |
| 635 | |
| 636 | fn (mut ctx Context) set_cached_window_size(width int, height int) { |
| 637 | ctx.width = width |
| 638 | ctx.height = height |
| 639 | ctx.window.width = width |
| 640 | ctx.window.height = height |
| 641 | } |
| 642 | |
| 643 | fn gg_apply_window_resizable() { |
| 644 | $if macos { |
| 645 | window := sapp.macos_get_window() |
| 646 | if window != unsafe { nil } { |
| 647 | C.gg_macos_set_window_resizable(window, false) |
| 648 | } |
| 649 | } $else $if windows { |
| 650 | hwnd := sapp.win32_get_hwnd() |
| 651 | if hwnd == unsafe { nil } { |
| 652 | return |
| 653 | } |
| 654 | mut style := u32(C.GetWindowLongW(hwnd, C.GWL_STYLE)) |
| 655 | style &= ~u32(C.WS_SIZEBOX | C.WS_MAXIMIZEBOX) |
| 656 | C.SetWindowLongW(hwnd, C.GWL_STYLE, i32(style)) |
| 657 | C.SetWindowPos(hwnd, unsafe { nil }, 0, 0, 0, 0, |
| 658 | u32(C.SWP_NOMOVE | C.SWP_NOSIZE | C.SWP_NOZORDER | C.SWP_NOACTIVATE | C.SWP_FRAMECHANGED)) |
| 659 | } $else $if linux { |
| 660 | sapp.set_resizable(false) |
| 661 | } |
| 662 | } |
| 663 | |
| 664 | fn gg_resize_window(width int, height int) { |
| 665 | if width <= 0 || height <= 0 { |
| 666 | return |
| 667 | } |
| 668 | $if macos { |
| 669 | window := sapp.macos_get_window() |
| 670 | if window != unsafe { nil } { |
| 671 | C.gg_macos_resize_window(window, width, height) |
| 672 | } |
| 673 | } $else $if windows { |
| 674 | hwnd := sapp.win32_get_hwnd() |
| 675 | if hwnd == unsafe { nil } { |
| 676 | return |
| 677 | } |
| 678 | scale := dpi_scale() |
| 679 | client_width := int(f32(width) * scale + 0.5) |
| 680 | client_height := int(f32(height) * scale + 0.5) |
| 681 | mut rect := C.RECT{ |
| 682 | right: i32(client_width) |
| 683 | bottom: i32(client_height) |
| 684 | } |
| 685 | style := u32(C.GetWindowLongW(hwnd, C.GWL_STYLE)) |
| 686 | ex_style := u32(C.GetWindowLongW(hwnd, C.GWL_EXSTYLE)) |
| 687 | if C.AdjustWindowRectEx(&rect, style, 0, ex_style) == 0 { |
| 688 | return |
| 689 | } |
| 690 | mut current_rect := C.RECT{} |
| 691 | if C.GetWindowRect(hwnd, ¤t_rect) == 0 { |
| 692 | return |
| 693 | } |
| 694 | C.SetWindowPos(hwnd, unsafe { nil }, int(current_rect.left), int(current_rect.top), |
| 695 | int(rect.right - rect.left), int(rect.bottom - rect.top), |
| 696 | u32(C.SWP_NOZORDER | C.SWP_NOACTIVATE)) |
| 697 | } $else $if linux && !sokol_wayland ? { |
| 698 | display := sapp.x11_get_display() |
| 699 | window := u64(usize(sapp.x11_get_window())) |
| 700 | if display == unsafe { nil } || window == 0 { |
| 701 | return |
| 702 | } |
| 703 | scale := dpi_scale() |
| 704 | x11_width := int(f32(width) * scale + 0.5) |
| 705 | x11_height := int(f32(height) * scale + 0.5) |
| 706 | C.XResizeWindow(display, window, u32(x11_width), u32(x11_height)) |
| 707 | C.XFlush(display) |
| 708 | } |
| 709 | } |
| 710 | |
| 711 | // run starts the main loop of the context. |
| 712 | pub fn (mut ctx Context) run() { |
| 713 | // set context late, in case it changed (e.g., due to embedding) |
| 714 | ctx.window = sapp.Desc{ |
| 715 | ...ctx.window |
| 716 | user_data: ctx |
| 717 | } |
| 718 | if ctx.user_data == unsafe { nil } { |
| 719 | ctx.user_data = ctx |
| 720 | } |
| 721 | sapp.run(&ctx.window) |
| 722 | } |
| 723 | |
| 724 | // quit closes the context window and exits the event loop for it |
| 725 | pub fn (ctx &Context) quit() { |
| 726 | sapp.request_quit() // does not require ctx right now, but sokol multi-window might in the future |
| 727 | } |
| 728 | |
| 729 | // set_bg_color sets the color of the window background to `c`. |
| 730 | pub fn (mut ctx Context) set_bg_color(c Color) { |
| 731 | ctx.bg_color = c |
| 732 | ctx.clear_pass = gfx.create_clear_pass_action(f32(c.r) / 255.0, f32(c.g) / 255.0, |
| 733 | f32(c.b) / 255.0, f32(c.a) / 255.0) |
| 734 | ctx.translucent_bg_seed_pending = c.a < 255 |
| 735 | } |
| 736 | |
| 737 | // Resize the context's Window |
| 738 | pub fn (mut ctx Context) resize(width int, height int) { |
| 739 | ctx.set_cached_window_size(width, height) |
| 740 | gg_resize_window(width, height) |
| 741 | } |
| 742 | |
| 743 | // refresh_ui requests a complete re-draw of the window contents. |
| 744 | pub fn (mut ctx Context) refresh_ui() { |
| 745 | ctx.needs_refresh = true |
| 746 | ctx.ticks = 0 |
| 747 | } |
| 748 | |
| 749 | // begin prepares the context for drawing. |
| 750 | pub fn (ctx &Context) begin() { |
| 751 | if ctx.render_text && ctx.font_inited { |
| 752 | ctx.ft.flush() |
| 753 | } |
| 754 | sgl.defaults() |
| 755 | sgl.matrix_mode_projection() |
| 756 | sgl.ortho(0.0, f32(sapp.width()), f32(sapp.height()), 0.0, -1.0, 1.0) |
| 757 | if ctx.bg_color.a < 255 && ctx.last_bg_overlay_frame != ctx.frame { |
| 758 | mut ctx_mut := unsafe { &Context(ctx) } |
| 759 | ctx_mut.last_bg_overlay_frame = ctx.frame |
| 760 | // Sokol clear actions replace pixels and do not blend. |
| 761 | // Draw a translucent fullscreen quad once per frame to get trail/fade behavior. |
| 762 | ctx.draw_rect_filled(0, 0, f32(ctx.width), f32(ctx.height), ctx.bg_color) |
| 763 | } |
| 764 | } |
| 765 | |
| 766 | pub enum EndEnum { |
| 767 | clear |
| 768 | passthru |
| 769 | } |
| 770 | |
| 771 | @[params] |
| 772 | pub struct EndOptions { |
| 773 | pub: |
| 774 | how EndEnum |
| 775 | } |
| 776 | |
| 777 | const dontcare_pass = gfx.PassAction{ |
| 778 | colors: [ |
| 779 | gfx.ColorAttachmentAction{ |
| 780 | load_action: .dontcare |
| 781 | clear_value: gfx.Color{1.0, 1.0, 1.0, 1.0} |
| 782 | }, |
| 783 | gfx.ColorAttachmentAction{ |
| 784 | load_action: .dontcare |
| 785 | clear_value: gfx.Color{1.0, 1.0, 1.0, 1.0} |
| 786 | }, |
| 787 | gfx.ColorAttachmentAction{ |
| 788 | load_action: .dontcare |
| 789 | clear_value: gfx.Color{1.0, 1.0, 1.0, 1.0} |
| 790 | }, |
| 791 | gfx.ColorAttachmentAction{ |
| 792 | load_action: .dontcare |
| 793 | clear_value: gfx.Color{1.0, 1.0, 1.0, 1.0} |
| 794 | }, |
| 795 | ]! |
| 796 | } |
| 797 | |
| 798 | const load_pass = gfx.PassAction{ |
| 799 | colors: [ |
| 800 | gfx.ColorAttachmentAction{ |
| 801 | load_action: .load |
| 802 | clear_value: gfx.Color{1.0, 1.0, 1.0, 1.0} |
| 803 | }, |
| 804 | gfx.ColorAttachmentAction{ |
| 805 | load_action: .load |
| 806 | clear_value: gfx.Color{1.0, 1.0, 1.0, 1.0} |
| 807 | }, |
| 808 | gfx.ColorAttachmentAction{ |
| 809 | load_action: .load |
| 810 | clear_value: gfx.Color{1.0, 1.0, 1.0, 1.0} |
| 811 | }, |
| 812 | gfx.ColorAttachmentAction{ |
| 813 | load_action: .load |
| 814 | clear_value: gfx.Color{1.0, 1.0, 1.0, 1.0} |
| 815 | }, |
| 816 | ]! |
| 817 | } |
| 818 | |
| 819 | pub fn create_default_pass(action gfx.PassAction) gfx.Pass { |
| 820 | return sapp.create_default_pass(action) |
| 821 | } |
| 822 | |
| 823 | // end finishes all the drawing for the context ctx. |
| 824 | // All accumulated draw calls before ctx.end(), will be done in a separate Sokol pass. |
| 825 | // |
| 826 | // Note: each Sokol pass, has a limit on the number of draw calls, that can be done in it. |
| 827 | // Once that limit is reached, the whole pass will not draw anything, which can be frustrating. |
| 828 | // |
| 829 | // To overcome this limitation, you may use *several passes*, when you want to make thousands |
| 830 | // of draw calls (for example, if you need to draw thousands of circles/rectangles/sprites etc), |
| 831 | // where each pass will render just a limited amount of primitives. |
| 832 | // |
| 833 | // In the context of the gg module (without dropping to using sgl and gfx directly), it means, |
| 834 | // that you will need a new pair of ctx.begin() and ctx.end() calls, surrounding all the draw |
| 835 | // calls, that should be done in each pass. |
| 836 | // |
| 837 | // The default ctx.end() is equivalent to ctx.end(how:.clear). It will erase the existing |
| 838 | // rendered content with the background color, before drawing anything else. |
| 839 | // You can call ctx.end(how:.passthru) for a pass, that *will not* erase the previously |
| 840 | // rendered content in the context. |
| 841 | pub fn (ctx &Context) end(options EndOptions) { |
| 842 | $if show_fps ? { |
| 843 | ctx.show_fps() |
| 844 | } $else { |
| 845 | if ctx.fps.show { |
| 846 | ctx.show_fps() |
| 847 | } |
| 848 | } |
| 849 | pass := match options.how { |
| 850 | .clear { |
| 851 | if ctx.bg_color.a < 255 { |
| 852 | if ctx.translucent_bg_seed_pending { |
| 853 | mut ctx_mut := unsafe { &Context(ctx) } |
| 854 | ctx_mut.translucent_bg_seed_pending = false |
| 855 | create_default_pass(ctx.clear_pass) |
| 856 | } else { |
| 857 | create_default_pass(load_pass) |
| 858 | } |
| 859 | } else { |
| 860 | create_default_pass(ctx.clear_pass) |
| 861 | } |
| 862 | } |
| 863 | .passthru { |
| 864 | create_default_pass(dontcare_pass) |
| 865 | } |
| 866 | } |
| 867 | |
| 868 | gfx.begin_pass(pass) |
| 869 | sgl.draw() |
| 870 | gfx.end_pass() |
| 871 | gfx.commit() |
| 872 | /* |
| 873 | if gg.config.wait_events { |
| 874 | // println('gg: waiting') |
| 875 | wait_events() |
| 876 | } |
| 877 | */ |
| 878 | } |
| 879 | |
| 880 | pub struct FPSConfig { |
| 881 | pub mut: |
| 882 | x int // horizontal position on screen |
| 883 | y int // vertical position on screen |
| 884 | width int // minimum width |
| 885 | height int // minimum height |
| 886 | show bool // do not show by default, use `-d show_fps` or set it manually in your app to override with: `app.gg.fps.show = true` |
| 887 | text_config TextCfg = TextCfg{ |
| 888 | color: yellow |
| 889 | size: 20 |
| 890 | align: .center |
| 891 | vertical_align: .middle |
| 892 | } |
| 893 | background_color Color = Color{ |
| 894 | r: 0 |
| 895 | g: 0 |
| 896 | b: 0 |
| 897 | a: 128 |
| 898 | } |
| 899 | } |
| 900 | |
| 901 | pub fn (ctx &Context) show_fps() { |
| 902 | if !ctx.font_inited { |
| 903 | return |
| 904 | } |
| 905 | frame_duration := sapp.frame_duration() |
| 906 | sgl.defaults() |
| 907 | sgl.matrix_mode_projection() |
| 908 | sgl.ortho(0.0, f32(sapp.width()), f32(sapp.height()), 0.0, -1.0, 1.0) |
| 909 | ctx.set_text_cfg(ctx.fps.text_config) |
| 910 | fps_text := int(0.5 + 1.0 / frame_duration).str() |
| 911 | if ctx.fps.width == 0 { |
| 912 | mut fps := unsafe { &ctx.fps } |
| 913 | fps.width, fps.height = |
| 914 | ctx.text_size('00') // usual size; prevents blinking on variable width fonts |
| 915 | } |
| 916 | char_width := ctx.fps.text_config.size / 2 |
| 917 | mut full_width := ctx.fps.width |
| 918 | if char_width * fps_text.len > ctx.fps.width { |
| 919 | full_width += (fps_text.len - 2) * char_width |
| 920 | } |
| 921 | ctx.draw_rect_filled(ctx.fps.x, ctx.fps.y, full_width + 2, ctx.fps.height + 4, |
| 922 | ctx.fps.background_color) |
| 923 | ctx.draw_text(ctx.fps.x + full_width / 2 + 1, ctx.fps.y + ctx.fps.height / 2 + 2, fps_text, |
| 924 | ctx.fps.text_config) |
| 925 | } |
| 926 | |
| 927 | fn (mut ctx Context) set_scale() { |
| 928 | mut s := sapp.dpi_scale() |
| 929 | $if android { |
| 930 | w := ctx.config.width |
| 931 | h := ctx.config.height |
| 932 | dw := sapp.width() |
| 933 | dh := sapp.height() |
| 934 | if dw <= dh { |
| 935 | if w <= 0 { |
| 936 | s = 1.0 |
| 937 | } else { |
| 938 | s = f32(dw) / w |
| 939 | } |
| 940 | } else { |
| 941 | if h <= 0 { |
| 942 | s = 1.0 |
| 943 | } else { |
| 944 | s = f32(dh) / h |
| 945 | } |
| 946 | } |
| 947 | } |
| 948 | // Note: on older X11, `Xft.dpi` from ~/.Xresources, that sokol uses, |
| 949 | // may not be set which leads to sapp.dpi_scale reporting incorrectly 0.0 |
| 950 | if s < 0.1 { |
| 951 | s = 1.0 |
| 952 | } |
| 953 | ctx.scale = s |
| 954 | } |
| 955 | |
| 956 | // window_size returns the current dimensions of the window. |
| 957 | pub fn (ctx Context) window_size() Size { |
| 958 | s := ctx.scale |
| 959 | return Size{int(sapp.width() / s), int(sapp.height() / s)} |
| 960 | } |
| 961 | |
| 962 | //---- public module functions |
| 963 | |
| 964 | // dpi_scale returns the DPI scale coefficient for the screen. |
| 965 | // Do not use for Android development, use `Context.scale` instead. |
| 966 | pub fn dpi_scale() f32 { |
| 967 | mut s := sapp.dpi_scale() |
| 968 | $if android { |
| 969 | s *= android_dpi_scale() |
| 970 | } |
| 971 | // Note: on older X11, `Xft.dpi` from ~/.Xresources, that sokol uses, |
| 972 | // may not be set which leads to sapp.dpi_scale reporting incorrectly 0.0 |
| 973 | if s < 0.1 { |
| 974 | s = 1.0 |
| 975 | } |
| 976 | return s |
| 977 | } |
| 978 | |
| 979 | // high_dpi returns true if `gg` is running on a high DPI monitor or screen. |
| 980 | pub fn high_dpi() bool { |
| 981 | return C.sapp_high_dpi() |
| 982 | } |
| 983 | |
| 984 | // screen_size returns the size of the active screen. |
| 985 | pub fn screen_size() Size { |
| 986 | $if macos { |
| 987 | return C.gg_get_screen_size() |
| 988 | } |
| 989 | $if windows { |
| 990 | return Size{ |
| 991 | width: int(C.GetSystemMetrics(C.SM_CXSCREEN)) |
| 992 | height: int(C.GetSystemMetrics(C.SM_CYSCREEN)) |
| 993 | } |
| 994 | } |
| 995 | $if linux { |
| 996 | display := C.XOpenDisplay(0) |
| 997 | if display == unsafe { nil } { |
| 998 | return Size{} |
| 999 | } |
| 1000 | defer { C.XCloseDisplay(display) } |
| 1001 | root := C.DefaultRootWindow(display) |
| 1002 | resources := C.XRRGetScreenResources(display, root) |
| 1003 | if resources == unsafe { nil } { |
| 1004 | return Size{} |
| 1005 | } |
| 1006 | defer { C.XRRFreeScreenResources(resources) } |
| 1007 | primary_output := C.XRRGetOutputPrimary(display, root) |
| 1008 | if primary_output == 0 { |
| 1009 | return Size{} |
| 1010 | } |
| 1011 | for i := 0; i < resources.noutput; i++ { |
| 1012 | if unsafe { u64(resources.outputs[i]) } == primary_output { |
| 1013 | output_info := C.XRRGetOutputInfo(display, resources, |
| 1014 | unsafe { resources.outputs[i] }) |
| 1015 | if output_info == unsafe { nil } { |
| 1016 | return Size{} |
| 1017 | } |
| 1018 | crtc_info := C.XRRGetCrtcInfo(display, resources, output_info.crtc) |
| 1019 | C.XRRFreeOutputInfo(output_info) |
| 1020 | if crtc_info == unsafe { nil } { |
| 1021 | return Size{} |
| 1022 | } |
| 1023 | res := Size{ |
| 1024 | width: unsafe { int(crtc_info.width) } |
| 1025 | height: unsafe { int(crtc_info.height) } |
| 1026 | } |
| 1027 | C.XRRFreeCrtcInfo(crtc_info) |
| 1028 | return res |
| 1029 | } |
| 1030 | } |
| 1031 | } |
| 1032 | return Size{} |
| 1033 | } |
| 1034 | |
| 1035 | // window_size returns the `Size` of the active window. |
| 1036 | // Do not use for Android development, use `Context.window_size()` instead. |
| 1037 | pub fn window_size() Size { |
| 1038 | s := dpi_scale() |
| 1039 | return Size{int(sapp.width() / s), int(sapp.height() / s)} |
| 1040 | } |
| 1041 | |
| 1042 | // set_window_title sets main window's title |
| 1043 | pub fn set_window_title(title string) { |
| 1044 | C.sapp_set_window_title(&char(title.str)) |
| 1045 | } |
| 1046 | |
| 1047 | // window_size_real_pixels returns the `Size` of the active window without scale |
| 1048 | pub fn window_size_real_pixels() Size { |
| 1049 | return Size{sapp.width(), sapp.height()} |
| 1050 | } |
| 1051 | |
| 1052 | // is it fullscreen |
| 1053 | pub fn is_fullscreen() bool { |
| 1054 | return sapp.is_fullscreen() |
| 1055 | } |
| 1056 | |
| 1057 | // toggle fullscreen |
| 1058 | pub fn toggle_fullscreen() { |
| 1059 | sapp.toggle_fullscreen() |
| 1060 | } |
| 1061 | |
| 1062 | /* |
| 1063 | pub fn wait_events() { |
| 1064 | unsafe { |
| 1065 | $if macos { |
| 1066 | #NSEvent *event = [NSApp nextEventMatchingMask:NSEventMaskAny |
| 1067 | #untilDate:[NSDate distantFuture] |
| 1068 | #inMode:NSDefaultRunLoopMode |
| 1069 | #dequeue:YES]; |
| 1070 | #[NSApp sendEvent:event]; |
| 1071 | } |
| 1072 | $if windows { |
| 1073 | C.WaitMessage() |
| 1074 | } |
| 1075 | } |
| 1076 | } |
| 1077 | */ |
| 1078 | |
| 1079 | // memory_trace_frame creates a small allocation at the start of each frame, |
| 1080 | // that is easy to search for in memdump.bin files, created with: |
| 1081 | // -prealloc -d prealloc_memset -d prealloc_memset_value=65 -d prealloc_dump -d gg_memory_trace_frame |
| 1082 | @[if gg_memory_trace_frame ?; manualfree] |
| 1083 | fn (mut ctx Context) memory_trace_frame() { |
| 1084 | frame_tag_size := 61 // uneven, and easy to spot in heaptrack histograms as well |
| 1085 | unsafe { |
| 1086 | frame_tag := &u8(vcalloc(frame_tag_size)) |
| 1087 | C.snprintf(frame_tag, frame_tag_size, c'@@ gg_memory_trace_frame: %06d ', ctx.frame) |
| 1088 | frame_tag[frame_tag_size - 2] = `@` |
| 1089 | frame_tag[frame_tag_size - 1] = `@` |
| 1090 | free(frame_tag) |
| 1091 | } |
| 1092 | } |
| 1093 | |