module gg import gx import js.dom pub enum DOMEventType { invalid key_down key_up char mouse_down mouse_up mouse_scroll mouse_move mouse_enter mouse_leave touches_began touches_moved touches_ended touches_cancelled resized iconified restored focused unfocused suspended resumed update_cursor quit_requested clipboard_pasted files_droped num } pub struct Event { pub mut: frame_count u64 typ DOMEventType 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 // todo(playX): add touches API support in js.dom // num_touches int // touches [8]TouchPoint window_width int window_height int framebuffer_width int framebuffer_height int } pub enum DOMMouseButton { invalid = -1 left = 0 right = 1 middle = 2 } pub enum DOMModifier { shift = 1 //(1<<0) ctrl = 2 //(1<<1) alt = 4 //(1<<2) super = 8 //(1<<3) lmb = 0x100 rmb = 0x200 mmb = 0x400 } pub enum DOMKeyCode { 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 } 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 swap_interval int = 1 // 1 = 60fps, 2 = 30fps etc. The preferred swap interval (ignored on some platforms) // 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 // drag&drop enable_dragndrop bool // enable file dropping (drag'n'drop), default is false max_dropped_files int = 1 // max number of dropped files to process (default: 1) max_dropped_file_path_length int = 2048 // max length in bytes of a dropped UTF-8 file path (default: 2048) canvas string } const size = Size{0, 0} pub fn window_size() Size { return gg.size } pub struct Context { mut: render_text bool = true image_cache []Image needs_refresh bool = true ticks int pub mut: scale f32 = 1.0 width int height int window JS.Window [noinit] config Config user_data voidptr ui_mode bool frame u64 mbtn_mask byte mouse_buttons MouseButtons 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, context JS.CanvasRenderingContext2D [noinit] canvas JS.HTMLCanvasElement [noinit] // *before* the current event was different } fn get_canvas(elem JS.HTMLElement) &JS.HTMLCanvasElement { match elem { JS.HTMLCanvasElement { return elem } else { panic('gg: element is not an HTMLCanvasElement') } } } fn get_context(canvas JS.HTMLCanvasElement) JS.CanvasRenderingContext2D { ctx := canvas.getContext('2d'.str, js_undefined()) or { panic('cannot get context') } match ctx { JS.CanvasRenderingContext2D { return ctx } else { panic('failed to get 2D context') } } } pub fn new_context(cfg Config) &Context { mut g := &Context{} g.user_data = cfg.user_data g.width = cfg.width g.height = cfg.height g.ui_mode = cfg.ui_mode mut sz := gg.size sz.height = g.height sz.width = g.width g.config = cfg if isnil(cfg.user_data) { g.user_data = g } g.window = dom.window() document := dom.document canvas_elem := document.getElementById(cfg.canvas.str) or { panic('gg: cannot get canvas element') } canvas := get_canvas(canvas_elem) g.canvas = canvas g.context = get_context(g.canvas) mouse_down_event_handler := fn [mut g] (event JS.Event) { match event { JS.MouseEvent { e := g.handle_mouse_event(event, .mouse_down) if !isnil(g.config.event_fn) { f := g.config.event_fn f(e, g.config.user_data) } if !isnil(g.config.click_fn) { f := g.config.click_fn f(e.mouse_x, e.mouse_y, e.mouse_button, g.config.user_data) } } else {} } } mouse_up_event_handler := fn [mut g] (event JS.Event) { match event { JS.MouseEvent { e := g.handle_mouse_event(event, .mouse_up) if !isnil(g.config.event_fn) { f := g.config.event_fn f(e, g.config.user_data) } if !isnil(g.config.unclick_fn) { f := g.config.unclick_fn f(e.mouse_x, e.mouse_y, e.mouse_button, g.config.user_data) } } else {} } } mouse_move_event_handler := fn [mut g] (event JS.Event) { match event { JS.MouseEvent { e := g.handle_mouse_event(event, .mouse_move) if !isnil(g.config.event_fn) { f := g.config.event_fn f(e, g.config.user_data) } if !isnil(g.config.move_fn) { f := g.config.move_fn f(e.mouse_x, e.mouse_y, g.config.user_data) } } else {} } } mouse_leave_event_handler := fn [mut g] (event JS.Event) { match event { JS.MouseEvent { e := g.handle_mouse_event(event, .mouse_leave) if !isnil(g.config.event_fn) { f := g.config.event_fn f(e, g.config.user_data) } if !isnil(g.config.leave_fn) { f := g.config.leave_fn f(e, g.config.user_data) } } else {} } } mouse_enter_event_handler := fn [mut g] (event JS.Event) { match event { JS.MouseEvent { e := g.handle_mouse_event(event, .mouse_enter) if !isnil(g.config.event_fn) { f := g.config.event_fn f(e, g.config.user_data) } if !isnil(g.config.enter_fn) { f := g.config.enter_fn f(e, g.config.user_data) } } else {} } } keydown_event_handler := fn [mut g] (event JS.Event) { println('keyboard') match event { JS.KeyboardEvent { e := g.handle_keyboard_event(event, .key_down) if !isnil(g.config.event_fn) { f := g.config.event_fn f(e, g.config.user_data) } if !isnil(g.config.keydown_fn) { f := g.config.keydown_fn // todo: modifiers f(e.key_code, .super, g.config.user_data) } } else {} } } g.canvas.addEventListener('mousedown'.str, mouse_down_event_handler, JS.EventListenerOptions{}) dom.window().addEventListener('mouseup'.str, mouse_up_event_handler, JS.EventListenerOptions{}) g.canvas.addEventListener('mousemove'.str, mouse_move_event_handler, JS.EventListenerOptions{}) g.canvas.addEventListener('mouseleave'.str, mouse_leave_event_handler, JS.EventListenerOptions{}) g.canvas.addEventListener('mouseenter'.str, mouse_enter_event_handler, JS.EventListenerOptions{}) dom.document.addEventListener('keydown'.str, keydown_event_handler, JS.EventListenerOptions{}) return g } pub fn (mut ctx Context) run() { gg_animation_frame_fn(mut ctx) } pub fn (mut ctx Context) begin() { // ctx.context.beginPath() } pub fn (mut ctx Context) end() { // ctx.context.closePath() } pub fn (mut ctx Context) draw_line(x1 f32, y1 f32, x2 f32, y2 f32, c gx.Color) { ctx.context.beginPath() ctx.context.strokeStyle = c.to_css_string().str ctx.context.moveTo(x1, y1) ctx.context.lineTo(x2, y2) ctx.context.stroke() ctx.context.closePath() } pub fn (mut ctx Context) quit() { } pub fn (mut ctx Context) draw_rect(x f32, y f32, w f32, h f32, c gx.Color) { ctx.context.beginPath() ctx.context.fillStyle = c.to_css_string().str ctx.context.fillRect(x, y, w, h) ctx.context.closePath() } fn gg_animation_frame_fn(mut g Context) { g.frame++ g.context.clearRect(0, 0, g.config.width, g.config.height) // todo(playXE): handle events if !isnil(g.config.frame_fn) { f := g.config.frame_fn f(g.user_data) g.needs_refresh = false } g.window.requestAnimationFrame(fn [mut g] (time JS.Number) { gg_animation_frame_fn(mut g) }) } fn (mut g Context) handle_mouse_event(event JS.MouseEvent, typ DOMEventType) Event { mut e := Event{} e.typ = typ e.frame_count = g.frame match int(event.button) { 0 { e.mouse_button = .left } 1 { e.mouse_button = .middle } 2 { e.mouse_button = .right } else { e.mouse_button = .invalid } } e.mouse_x = int(event.offsetX) e.mouse_y = int(event.offsetY) e.mouse_dx = int(event.movementX) e.mouse_dy = int(event.movementY) bitplace := int(event.button) g.mbtn_mask |= u8(1 << bitplace) // g.mouse_buttons = MouseButtons(g.mbtn_mask) g.mouse_pos_x = int(event.offsetX) g.mouse_pos_y = int(event.offsetY) g.mouse_dx = int(event.movementX) g.mouse_dy = int(event.movementY) return e } fn (mut g Context) handle_keyboard_event(event JS.KeyboardEvent, typ DOMEventType) Event { mut e := Event{} e.typ = typ e.frame_count = g.frame match string(event.code) { 'Space' { e.key_code = .space } 'Minus' { e.key_code = .minus } 'Quote' { e.key_code = .apostrophe } 'Comma' { e.key_code = .comma } 'Period' { e.key_code = .period } 'Digit0' { e.key_code = ._0 } 'Digit1' { e.key_code = ._1 } 'Digit2' { e.key_code = ._2 } 'Digit3' { e.key_code = ._3 } 'Digit4' { e.key_code = ._4 } 'Digit5' { e.key_code = ._5 } 'Digit6' { e.key_code = ._6 } 'Digit7' { e.key_code = ._7 } 'Digit8' { e.key_code = ._8 } 'Digit9' { e.key_code = ._9 } 'Semicolon' { e.key_code = .semicolon } 'Equal' { e.key_code = .equal } 'KeyA' { e.key_code = .a } 'KeyB' { e.key_code = .b } 'KeyC' { e.key_code = .c } 'KeyD' { e.key_code = .d } 'KeyE' { e.key_code = .e } 'KeyF' { e.key_code = .f } 'KeyG' { e.key_code = .g } 'KeyH' { e.key_code = .h } 'KeyI' { e.key_code = .i } 'KeyJ' { e.key_code = .j } 'KeyK' { e.key_code = .k } 'KeyL' { e.key_code = .l } 'KeyM' { e.key_code = .m } 'KeyN' { e.key_code = .n } 'KeyO' { e.key_code = .o } 'KeyP' { e.key_code = .p } 'KeyQ' { e.key_code = .q } 'KeyR' { e.key_code = .r } 'KeyS' { e.key_code = .s } 'KeyT' { e.key_code = .t } 'KeyU' { e.key_code = .u } 'KeyV' { e.key_code = .v } 'KeyW' { e.key_code = .w } 'KeyX' { e.key_code = .x } 'KeyY' { e.key_code = .y } 'KeyZ' { e.key_code = .z } 'BracketLeft' { e.key_code = .left_bracket } 'BracketRight' { e.key_code = .right_bracket } 'Backslash' { e.key_code = .backslash } 'Backquote' { e.key_code = .grave_accent } 'Escape' { e.key_code = .escape } 'Enter' { e.key_code = .enter } 'Tab' { e.key_code = .tab } 'Backspace' { e.key_code = .backspace } 'Insert' { e.key_code = .insert } 'Delete' { e.key_code = .delete } 'ArrowRight' { e.key_code = .right } 'ArrowLeft' { e.key_code = .left } 'ArrowUp' { e.key_code = .up } 'ArrowDown' { e.key_code = .down } 'PageUp' { e.key_code = .page_up } 'PageDown' { e.key_code = .page_down } 'Home' { e.key_code = .home } 'End' { e.key_code = .end } 'CapsLock' { e.key_code = .caps_lock } 'ScrollLock' { e.key_code = .scroll_lock } 'NumLock' { e.key_code = .num_lock } 'PrintScreen' { e.key_code = .print_screen } 'Pause' { e.key_code = .pause } 'ShiftLeft' { e.key_code = .left_shift } 'ShiftRight' { e.key_code = .right_shift } 'AltLeft' { e.key_code = .left_alt } 'AltRight' { e.key_code = .right_alt } 'ControlLeft' { e.key_code = .left_control } 'ControlRight' { e.key_code = .right_control } else { panic('todo: more keycodes (${string(event.code)})') } } return e }