diff --git a/code2/sectr/input/binding.odin b/code2/sectr/input/binding.odin new file mode 100644 index 0000000..1b61d0c --- /dev/null +++ b/code2/sectr/input/binding.odin @@ -0,0 +1,90 @@ +package sectr + +InputBindSig :: distinct u128 + +InputBind :: struct { + keys: [4]KeyCode, + mouse_btns: [4]MouseBtn, + scroll: [2]AnalogAxis, + modifiers: ModifierCodeFlags, + label: string, +} + +InputBindStatus :: struct { + detected: b32, + consumed: b32, + frame_id: u64, +} + +InputActionProc :: #type proc(user_ptr: rawptr) +InputAction :: struct { + id: int, + user_ptr: rawptr, + cb: InputActionProc, + always: b32, +} + +InputContext :: struct { + binds: []InputBind, + status: []InputBindStatus, + onpush_action: []InputAction, + onpop_action: []InputAction, + signature: []InputBindSig, +} + +inputbind_signature :: proc(binding: InputBind) -> InputBindSig { + // TODO(Ed): Figure out best hasher for this... + return cast(InputBindSig) 0 +} + +// Note(Ed): Bindings should be remade for a context when a user modifies any in configuration. + +inputcontext_init :: proc(ctx: ^InputContext, binds: []InputBind, onpush: []InputAction = {}, onpop: []InputAction = {}) { + ctx.binds = binds + ctx.onpush_action = onpush + ctx.onpop_action = onpop + + for bind, id in ctx.binds { + ctx.signature[id] = inputbind_signature(bind) + } +} + +inputcontext_make :: #force_inline proc(binds: []InputBind, onpush: []InputAction = {}, onpop: []InputAction = {}) -> InputContext { + ctx: InputContext; inputcontext_init(& ctx, binds, onpush, onpop); return ctx +} + +// Should be called by the user explicitly during frame cleanup. +inputcontext_clear_status :: #force_inline proc "contextless" (ctx: ^InputContext) { + zero(ctx.status) +} + +inputbinding_status :: #force_inline proc(id: int) -> InputBindStatus { + return get_input_binds().status[id] +} + +inputcontext_inherit :: proc(dst: ^InputContext, src: ^InputContext) { + for dst_id, dst_sig in dst.signature + { + for src_id, src_sig in src.signature + { + if dst_sig != src_sig { + continue + } + dst.status[dst_id] = src.status[src_id] + } + } +} + +inputcontext_push :: proc(ctx: ^InputContext, dont_inherit_status: b32 = false) { + // push context stack + // clear binding status for context + // optionally inherit status + // detect status + // Dispatch push actions meeting conditions +} + +inputcontext_pop :: proc(ctx: ^InputContext, dont_inherit_status: b32 = false) { + // Dispatch pop actions meeting conditions + // parent inherit consumed statuses + // pop context stack +} diff --git a/code2/sectr/input/events.odin b/code2/sectr/input/events.odin new file mode 100644 index 0000000..02a79f2 --- /dev/null +++ b/code2/sectr/input/events.odin @@ -0,0 +1,298 @@ +package sectr + +InputEventType :: enum u32 { + Key_Pressed, + Key_Released, + Mouse_Pressed, + Mouse_Released, + Mouse_Scroll, + Mouse_Move, + Mouse_Enter, + Mouse_Leave, + Unicode, +} + +InputEvent :: struct +{ + frame_id : u64, + type : InputEventType, + key : KeyCode, + modifiers : ModifierCodeFlags, + mouse : struct { + btn : MouseBtn, + pos : V2_F4, + delta : V2_F4, + scroll : V2_F4, + }, + codepoint : rune, + + // num_touches : u32, + // touches : Touchpoint, + + _sokol_frame_id : u64, +} + +// TODO(Ed): May just use input event exclusively in the future and have pointers for key and mouse event filters +// I'm on the fence about this as I don't want to force + +InputKeyEvent :: struct { + frame_id : u64, + type : InputEventType, + key : KeyCode, + modifiers : ModifierCodeFlags, +} + +InputMouseEvent :: struct { + frame_id : u64, + type : InputEventType, + btn : MouseBtn, + pos : V2_F4, + delta : V2_F4, + scroll : V2_F4, + modifiers : ModifierCodeFlags, +} + +// Lets see if we need more than this.. +InputEvents :: struct { + events : FRingBuffer(InputEvent, 64), + key_events : FRingBuffer(InputKeyEvent, 32), + mouse_events : FRingBuffer(InputMouseEvent, 32), + + codes_pressed : Array(rune), +} + +// Note(Ed): There is a staged_input_events : Array(InputEvent), in the state.odin's State struct + +append_staged_input_events :: #force_inline proc(event: InputEvent) { + append( & memory.client_memory.staged_input_events, event ) +} + +pull_staged_input_events :: proc( input: ^InputState, using input_events: ^InputEvents, using staged_events : Array(InputEvent) ) +{ + staged_events_slice := array_to_slice(staged_events) + push( & input_events.events, staged_events_slice ) + + // using input_events + + for event in staged_events_slice + { + switch event.type { + case .Key_Pressed: + push( & key_events, InputKeyEvent { + frame_id = event.frame_id, + type = event.type, + key = event.key, + modifiers = event.modifiers + }) + // logf("Key pressed(event pushed): %v", event.key) + // logf("last key event frame: %v", peek_back(& key_events).frame_id) + // logf("last event frame: %v", peek_back(& events).frame_id) + + case .Key_Released: + push( & key_events, InputKeyEvent { + frame_id = event.frame_id, + type = event.type, + key = event.key, + modifiers = event.modifiers + }) + // logf("Key released(event rpushed): %v", event.key) + // logf("last key event frame: %v", peek_back(& key_events).frame_id) + // logf("last event frame: %v", peek_back(& events).frame_id) + + case .Unicode: + append( & codes_pressed, event.codepoint ) + + case .Mouse_Pressed: + push( & mouse_events, InputMouseEvent { + frame_id = event.frame_id, + type = event.type, + btn = event.mouse.btn, + pos = event.mouse.pos, + delta = event.mouse.delta, + scroll = event.mouse.scroll, + modifiers = event.modifiers, + }) + + case .Mouse_Released: + push( & mouse_events, InputMouseEvent { + frame_id = event.frame_id, + type = event.type, + btn = event.mouse.btn, + pos = event.mouse.pos, + delta = event.mouse.delta, + scroll = event.mouse.scroll, + modifiers = event.modifiers, + }) + + case .Mouse_Scroll: + push( & mouse_events, InputMouseEvent { + frame_id = event.frame_id, + type = event.type, + btn = event.mouse.btn, + pos = event.mouse.pos, + delta = event.mouse.delta, + scroll = event.mouse.scroll, + modifiers = event.modifiers, + }) + // logf("Detected scroll: %v", event.mouse.scroll) + + case .Mouse_Move: + push( & mouse_events, InputMouseEvent { + frame_id = event.frame_id, + type = event.type, + btn = event.mouse.btn, + pos = event.mouse.pos, + delta = event.mouse.delta, + scroll = event.mouse.scroll, + modifiers = event.modifiers, + }) + + case .Mouse_Enter: + push( & mouse_events, InputMouseEvent { + frame_id = event.frame_id, + type = event.type, + btn = event.mouse.btn, + pos = event.mouse.pos, + delta = event.mouse.delta, + scroll = event.mouse.scroll, + modifiers = event.modifiers, + }) + + case .Mouse_Leave: + push( & mouse_events, InputMouseEvent { + frame_id = event.frame_id, + type = event.type, + btn = event.mouse.btn, + pos = event.mouse.pos, + delta = event.mouse.delta, + scroll = event.mouse.scroll, + modifiers = event.modifiers, + }) + } + } + clear( staged_events ) +} + +poll_input_events :: proc( input, prev_input : ^InputState, input_events : InputEvents ) +{ + input.keyboard = {} + input.mouse = {} + + // logf("m's value is: %v (prev)", prev_input.keyboard.keys[KeyCode.M] ) + + for prev_key, id in prev_input.keyboard.keys { + input.keyboard.keys[id].ended_down = prev_key.ended_down + } + + for prev_btn, id in prev_input.mouse.btns { + input.mouse.btns[id].ended_down = prev_btn.ended_down + } + + input.mouse.raw_pos = prev_input.mouse.raw_pos + input.mouse.pos = prev_input.mouse.pos + + input_events := input_events + using input_events + + @static prev_frame : u64 = 0 + + last_frame : u64 = 0 + if events.num > 0 { + last_frame = peek_back( events).frame_id + } + + // No new events, don't update + if last_frame == prev_frame do return + + Iterate_Key_Events: + { + iter_obj := iterator( & key_events ); iter := & iter_obj + for event := next( iter ); event != nil; event = next( iter ) + { + // logf("last_frame (iter): %v", last_frame) + // logf("frame (iter): %v", event.frame_id ) + if last_frame > event.frame_id { + break + } + key := & input.keyboard.keys[event.key] + prev_key := prev_input.keyboard.keys[event.key] + + // logf("key event: %v", event) + + first_transition := key.half_transitions == 0 + + #partial switch event.type { + case .Key_Pressed: + key.half_transitions += 1 + key.ended_down = true + + case .Key_Released: + key.half_transitions += 1 + key.ended_down = false + } + } + } + + Iterate_Mouse_Events: + { + iter_obj := iterator( & mouse_events ); iter := & iter_obj + for event := next( iter ); event != nil; event = next( iter ) + { + if last_frame > event.frame_id { + break + } + + process_digital_btn :: proc( btn : ^DigitalBtn, prev_btn : DigitalBtn, ended_down : b32 ) + { + first_transition := btn.half_transitions == 0 + + btn.half_transitions += 1 + btn.ended_down = ended_down + } + + // logf("mouse event: %v", event) + + #partial switch event.type { + case .Mouse_Pressed: + btn := & input.mouse.btns[event.btn] + prev_btn := prev_input.mouse.btns[event.btn] + process_digital_btn( btn, prev_btn, true ) + + case .Mouse_Released: + btn := & input.mouse.btns[event.btn] + prev_btn := prev_input.mouse.btns[event.btn] + process_digital_btn( btn, prev_btn, false ) + + case .Mouse_Scroll: + input.mouse.scroll += event.scroll + + case .Mouse_Move: + case .Mouse_Enter: + case .Mouse_Leave: + // Handled below + } + + input.mouse.raw_pos = event.pos + input.mouse.pos = render_to_screen_pos( event.pos, memory.client_memory.app_window.extent ) + input.mouse.delta = event.delta * { 1, -1 } + } + } + + prev_frame = last_frame +} + +input_event_iter :: #force_inline proc () -> FRingBufferIterator(InputEvent) { + return iterator_ringbuf_fixed( & memory.client_memory.input_events.events ) +} + +input_key_event_iter :: #force_inline proc() -> FRingBufferIterator(InputKeyEvent) { + return iterator_ringbuf_fixed( & memory.client_memory.input_events.key_events ) +} + +input_mouse_event_iter :: #force_inline proc() -> FRingBufferIterator(InputMouseEvent) { + return iterator_ringbuf_fixed( & memory.client_memory.input_events.mouse_events ) +} + +input_codes_pressed_slice :: #force_inline proc() -> []rune { + return to_slice( memory.client_memory.input_events.codes_pressed ) +} diff --git a/code2/sectr/input/input.odin b/code2/sectr/input/input.odin new file mode 100644 index 0000000..561dd91 --- /dev/null +++ b/code2/sectr/input/input.odin @@ -0,0 +1,186 @@ +// TODO(Ed) : This if its gets larget can be moved to its own package +package sectr + +import "base:runtime" + +AnalogAxis :: f32 +AnalogStick :: struct { + X, Y : f32 +} + +DigitalBtn :: struct { + half_transitions : i32, + ended_down : b32, +} + +btn_pressed :: #force_inline proc "contextless" (btn: DigitalBtn) -> b32 { return btn.ended_down && btn.half_transitions > 0 } +btn_released :: #force_inline proc "contextless" (btn: DigitalBtn) -> b32 { return btn.ended_down == false && btn.half_transitions > 0 } + +MaxMouseBtns :: 16 +MouseBtn :: enum u32 { + Left = 0x0, + Middle = 0x1, + Right = 0x2, + Side = 0x3, + Forward = 0x4, + Back = 0x5, + Extra = 0x6, + + Invalid = 0x100, + + count +} + +KeyboardState :: struct #raw_union { + keys : [KeyCode.count] DigitalBtn, + using individual : struct { + null : DigitalBtn, // 0x00 + ignored : DigitalBtn, // 0x01 + + // GFLW / Sokol + menu, + world_1, world_2 : DigitalBtn, + // 0x02 - 0x04 + + __0x05_0x07_Unassigned__ : [ 3 * size_of( DigitalBtn)] u8, + + tab, backspace : DigitalBtn, + // 0x08 - 0x09 + + right, left, up, down : DigitalBtn, + // 0x0A - 0x0D + + enter : DigitalBtn, // 0x0E + + __0x0F_Unassigned__ : [ 1 * size_of( DigitalBtn)] u8, + + caps_lock, + scroll_lock, + num_lock : DigitalBtn, + // 0x10 - 0x12 + + left_alt, + left_shift, + left_control, + right_alt, + right_shift, + right_control : DigitalBtn, + // 0x13 - 0x18 + + print_screen, + pause, + escape, + home, + end, + page_up, + page_down, + space : DigitalBtn, + // 0x19 - 0x20 + + exlamation, + quote_dbl, + hash, + dollar, + percent, + ampersand, + quote, + paren_open, + paren_close, + asterisk, + plus, + comma, + minus, + period, + slash : DigitalBtn, + // 0x21 - 0x2F + + nrow_0, // 0x30 + nrow_1, // 0x31 + nrow_2, // 0x32 + nrow_3, // 0x33 + nrow_4, // 0x34 + nrow_5, // 0x35 + nrow_6, // 0x36 + nrow_7, // 0x37 + nrow_8, // 0x38 + nrow_9, // 0x39 + + __0x3A_Unassigned__ : [ 1 * size_of(DigitalBtn)] u8, + + semicolon, + less, + equals, + greater, + question, + at : DigitalBtn, + + A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z : DigitalBtn, + + bracket_open, + backslash, + bracket_close, + underscore, + backtick : DigitalBtn, + + kpad_0, + kpad_1, + kpad_2, + kpad_3, + kpad_4, + kpad_5, + kpad_6, + kpad_7, + kpad_8, + kpad_9, + kpad_decimal, + kpad_equals, + kpad_plus, + kpad_minus, + kpad_multiply, + kpad_divide, + kpad_enter : DigitalBtn, + + F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12 : DigitalBtn, + + insert, delete : DigitalBtn, + + F13, F14, F15, F16, F17, F18, F19, F20, F21, F22, F23, F24, F25 : DigitalBtn, + } +} + +ModifierCode :: enum u32 { + Shift, + Control, + Alt, + Left_Mouse, + Right_Mouse, + Middle_Mouse, + Left_Shift, + Right_Shift, + Left_Control, + Right_Control, + Left_Alt, + Right_Alt, +} +ModifierCodeFlags :: bit_set[ModifierCode; u32] + +MouseState :: struct { + using _ : struct #raw_union { + btns : [16] DigitalBtn, + using individual : struct { + left, middle, right : DigitalBtn, + side, forward, back, extra : DigitalBtn, + } + }, + raw_pos, pos, delta : V2_F4, + scroll : [2]AnalogAxis, +} + +mouse_world_delta :: #force_inline proc "contextless" (mouse_delta: V2_F4, cam: ^Camera) -> V2_F4 { + return mouse_delta * ( 1 / cam.zoom ) +} + +InputState :: struct { + keyboard : KeyboardState, + mouse : MouseState, +} diff --git a/code2/sectr/input/input_sokol.odin b/code2/sectr/input/input_sokol.odin new file mode 100644 index 0000000..92762a9 --- /dev/null +++ b/code2/sectr/input/input_sokol.odin @@ -0,0 +1,84 @@ +package sectr + +import "base:runtime" +import "core:os" +import "core:c/libc" +import sokol_app "thirdparty:sokol/app" + +to_modifiers_code_from_sokol :: proc( sokol_modifiers : u32 ) -> ( modifiers : ModifierCodeFlags ) +{ + if sokol_modifiers & sokol_app.MODIFIER_SHIFT != 0 do modifiers |= { .Shift } + if sokol_modifiers & sokol_app.MODIFIER_CTRL != 0 do modifiers |= { .Control } + if sokol_modifiers & sokol_app.MODIFIER_ALT != 0 do modifiers |= { .Alt } + if sokol_modifiers & sokol_app.MODIFIER_LMB != 0 do modifiers |= { .Left_Mouse } + if sokol_modifiers & sokol_app.MODIFIER_RMB != 0 do modifiers |= { .Right_Mouse } + if sokol_modifiers & sokol_app.MODIFIER_MMB != 0 do modifiers |= { .Middle_Mouse } + if sokol_modifiers & sokol_app.MODIFIER_LSHIFT != 0 do modifiers |= { .Left_Shift } + if sokol_modifiers & sokol_app.MODIFIER_RSHIFT != 0 do modifiers |= { .Right_Shift } + if sokol_modifiers & sokol_app.MODIFIER_LCTRL != 0 do modifiers |= { .Left_Control } + if sokol_modifiers & sokol_app.MODIFIER_RCTRL != 0 do modifiers |= { .Right_Control } + if sokol_modifiers & sokol_app.MODIFIER_LALT != 0 do modifiers |= { .Left_Alt } + if sokol_modifiers & sokol_app.MODIFIER_RALT != 0 do modifiers |= { .Right_Alt } + return +} + +to_key_from_sokol :: proc( sokol_key : sokol_app.Keycode ) -> ( key : KeyCode ) +{ + world_code_offset :: i32(sokol_app.Keycode.WORLD_1) - i32(KeyCode.world_1) + arrow_code_offset :: i32(sokol_app.Keycode.RIGHT) - i32(KeyCode.right) + func_row_code_offset :: i32(sokol_app.Keycode.F1) - i32(KeyCode.F1) + func_extra_code_offset :: i32(sokol_app.Keycode.F13) - i32(KeyCode.F25) + keypad_num_offset :: i32(sokol_app.Keycode.KP_0) - i32(KeyCode.kpad_0) + + switch sokol_key { + case .INVALID ..= .GRAVE_ACCENT : key = transmute(KeyCode) sokol_key + case .WORLD_1, .WORLD_2 : key = transmute(KeyCode) (i32(sokol_key) - world_code_offset) + case .ESCAPE : key = .escape + case .ENTER : key = .enter + case .TAB : key = .tab + case .BACKSPACE : key = .backspace + case .INSERT : key = .insert + case .DELETE : key = .delete + case .RIGHT ..= .UP : key = transmute(KeyCode) (i32(sokol_key) - arrow_code_offset) + case .PAGE_UP : key = .page_up + case .PAGE_DOWN : key = .page_down + case .HOME : key = .home + case .END : key = .end + case .CAPS_LOCK : key = .caps_lock + case .SCROLL_LOCK : key = .scroll_lock + case .NUM_LOCK : key = .num_lock + case .PRINT_SCREEN : key = .print_screen + case .PAUSE : key = .pause + case .F1 ..= .F12 : key = transmute(KeyCode) (i32(sokol_key) - func_row_code_offset) + case .F13 ..= .F25 : key = transmute(KeyCode) (i32(sokol_key) - func_extra_code_offset) + case .KP_0 ..= .KP_9 : key = transmute(KeyCode) (i32(sokol_key) - keypad_num_offset) + case .KP_DECIMAL : key = .kpad_decimal + case .KP_DIVIDE : key = .kpad_divide + case .KP_MULTIPLY : key = .kpad_multiply + case .KP_SUBTRACT : key = .kpad_minus + case .KP_ADD : key = .kpad_plus + case .KP_ENTER : key = .kpad_enter + case .KP_EQUAL : key = .kpad_equals + case .LEFT_SHIFT : key = .left_shift + case .LEFT_CONTROL : key = .left_control + case .LEFT_ALT : key = .left_alt + case .LEFT_SUPER : key = .ignored + case .RIGHT_SHIFT : key = .right_shift + case .RIGHT_CONTROL : key = .right_control + case .RIGHT_ALT : key = .right_alt + case .RIGHT_SUPER : key = .ignored + case .MENU : key = .menu + } + return +} + +to_mouse_btn_from_sokol :: proc( sokol_mouse : sokol_app.Mousebutton ) -> ( btn : MouseBtn ) +{ + switch sokol_mouse { + case .LEFT : btn = .Left + case .MIDDLE : btn = .Middle + case .RIGHT : btn = .Right + case .INVALID : btn = .Invalid + } + return +} diff --git a/code2/sectr/input/keyboard_qwerty.odin b/code2/sectr/input/keyboard_qwerty.odin new file mode 100644 index 0000000..c4bf0ee --- /dev/null +++ b/code2/sectr/input/keyboard_qwerty.odin @@ -0,0 +1,239 @@ +package sectr + +// Based off of SDL2's Scancode; which is based off of: +// https://usb.org/sites/default/files/hut1_12.pdf +// I gutted values I would never use +QeurtyCode :: enum u32 { + unknown = 0, + + A = 4, + B = 5, + C = 6, + D = 7, + E = 8, + F = 9, + G = 10, + H = 11, + I = 12, + J = 13, + K = 14, + L = 15, + M = 16, + N = 17, + O = 18, + P = 19, + Q = 20, + R = 21, + S = 22, + T = 23, + U = 24, + V = 25, + W = 26, + X = 27, + Y = 28, + Z = 29, + + nrow_1 = 30, + nrow_2 = 31, + nrow_3 = 32, + nrow_4 = 33, + nrow_5 = 34, + nrow_6 = 35, + nrow_7 = 36, + nrow_8 = 37, + nrow_9 = 38, + nrow_0 = 39, + + enter = 40, + escape = 41, + backspace = 42, + tab = 43, + space = 44, + + minus = 45, + equals = 46, + bracket_open = 47, + bracket_close = 48, + backslash = 49, + NONUSHASH = 50, + semicolon = 51, + apostrophe = 52, + grave = 53, + comma = 54, + period = 55, + slash = 56, + + capslock = 57, + + F1 = 58, + F2 = 59, + F3 = 60, + F4 = 61, + F5 = 62, + F6 = 63, + F7 = 64, + F8 = 65, + F9 = 66, + F10 = 67, + F11 = 68, + F12 = 69, + + // print_screen = 70, + // scroll_lock = 71, + pause = 72, + insert = 73, + home = 74, + page_up = 75, + delete = 76, + end = 77, + page_down = 78, + right = 79, + left = 80, + down = 81, + up = 82, + + numlock_clear = 83, + kpad_divide = 84, + kpad_multiply = 85, + kpad_minus = 86, + kpad_plus = 87, + kpad_enter = 88, + kpad_1 = 89, + kpad_2 = 90, + kpad_3 = 91, + kpad_4 = 92, + kpad_5 = 93, + kpad_6 = 94, + kpad_7 = 95, + kpad_8 = 96, + kpad_9 = 97, + kpad_0 = 98, + kpad_period = 99, + + // NONUSBACKSLASH = 100, + // OS_Compose = 101, + // power = 102, + kpad_equals = 103, + + // F13 = 104, + // F14 = 105, + // F15 = 106, + // F16 = 107, + // F17 = 108, + // F18 = 109, + // F19 = 110, + // F20 = 111, + // F21 = 112, + // F22 = 113, + // F23 = 114, + // F24 = 115, + + // execute = 116, + // help = 117, + // menu = 118, + // select = 119, + // stop = 120, + // again = 121, + // undo = 122, + // cut = 123, + // copy = 124, + // paste = 125, + // find = 126, + // mute = 127, + // volume_up = 128, + // volume_down = 129, + /* LOCKINGCAPSLOCK = 130, */ + /* LOCKINGNUMLOCK = 131, */ + /* LOCKINGSCROLLLOCK = 132, */ + // kpad_comma = 133, + // kpad_equals_AS400 = 134, + + // international_1 = 135, + // international_2 = 136, + // international_3 = 137, + // international_4 = 138, + // international_5 = 139, + // international_6 = 140, + // international_7 = 141, + // international_8 = 142, + // international_9 = 143, + // lang_1 = 144, + // lang_2 = 145, + // lang_3 = 146, + // lang_4 = 147, + // lang_5 = 148, + // lang_6 = 149, + // lang_7 = 150, + // lang_8 = 151, + // lang_9 = 152, + + // alt_erase = 153, + // sysreq = 154, + // cancel = 155, + // clear = 156, + // prior = 157, + // return_2 = 158, + // separator = 159, + // out = 160, + // OPER = 161, + // clear_again = 162, + // CRSEL = 163, + // EXSEL = 164, + + // KP_00 = 176, + // KP_000 = 177, + // THOUSANDSSEPARATOR = 178, + // DECIMALSEPARATOR = 179, + // CURRENCYUNIT = 180, + // CURRENCYSUBUNIT = 181, + // KP_LEFTPAREN = 182, + // KP_RIGHTPAREN = 183, + // KP_LEFTBRACE = 184, + // KP_RIGHTBRACE = 185, + // KP_TAB = 186, + // KP_BACKSPACE = 187, + // KP_A = 188, + // KP_B = 189, + // KP_C = 190, + // KP_D = 191, + // KP_E = 192, + // KP_F = 193, + // KP_XOR = 194, + // KP_POWER = 195, + // KP_PERCENT = 196, + // KP_LESS = 197, + // KP_GREATER = 198, + // KP_AMPERSAND = 199, + // KP_DBLAMPERSAND = 200, + // KP_VERTICALBAR = 201, + // KP_DBLVERTICALBAR = 202, + // KP_COLON = 203, + // KP_HASH = 204, + // KP_SPACE = 205, + // KP_AT = 206, + // KP_EXCLAM = 207, + // KP_MEMSTORE = 208, + // KP_MEMRECALL = 209, + // KP_MEMCLEAR = 210, + // KP_MEMADD = 211, + // KP_MEMSUBTRACT = 212, + // KP_MEMMULTIPLY = 213, + // KP_MEMDIVIDE = 214, + // KP_PLUSMINUS = 215, + // KP_CLEAR = 216, + // KP_CLEARENTRY = 217, + // KP_BINARY = 218, + // KP_OCTAL = 219, + // KP_DECIMAL = 220, + // KP_HEXADECIMAL = 221, + + left_control = 224, + left_shift = 225, + left_alt = 226, + // LGUI = 227, + right_control = 228, + right_shift = 229, + right_alt = 230, + + count = 512, +} diff --git a/code2/sectr/input/keycode.odin b/code2/sectr/input/keycode.odin new file mode 100644 index 0000000..c0708c7 --- /dev/null +++ b/code2/sectr/input/keycode.odin @@ -0,0 +1,168 @@ +package sectr + +MaxKeyboardKeys :: 512 + +KeyCode :: enum u32 { + null = 0x00, + + ignored = 0x01, + menu = 0x02, + world_1 = 0x03, + world_2 = 0x04, + + // 0x05 + // 0x06 + // 0x07 + + backspace = '\b', // 0x08 + tab = '\t', // 0x09 + + right = 0x0A, + left = 0x0B, + down = 0x0C, + up = 0x0D, + + enter = '\r', // 0x0E + + // 0x0F + + caps_lock = 0x10, + scroll_lock = 0x11, + num_lock = 0x12, + + left_alt = 0x13, + left_shift = 0x14, + left_control = 0x15, + right_alt = 0x16, + right_shift = 0x17, + right_control = 0x18, + + print_screen = 0x19, + pause = 0x1A, + escape = '\x1B', // 0x1B + home = 0x1C, + end = 0x1D, + page_up = 0x1E, + page_down = 0x1F, + space = ' ', // 0x20 + + exclamation = '!', // 0x21 + quote_dbl = '"', // 0x22 + hash = '#', // 0x23 + dollar = '$', // 0x24 + percent = '%', // 0x25 + ampersand = '&', // 0x26 + quote = '\'', // 0x27 + paren_open = '(', // 0x28 + paren_close = ')', // 0x29 + asterisk = '*', // 0x2A + plus = '+', // 0x2B + comma = ',', // 0x2C + minus = '-', // 0x2D + period = '.', // 0x2E + slash = '/', // 0x2F + + nrow_0 = '0', // 0x30 + nrow_1 = '1', // 0x31 + nrow_2 = '2', // 0x32 + nrow_3 = '3', // 0x33 + nrow_4 = '4', // 0x34 + nrow_5 = '5', // 0x35 + nrow_6 = '6', // 0x36 + nrow_7 = '7', // 0x37 + nrow_8 = '8', // 0x38 + nrow_9 = '9', // 0x39 + + // 0x3A + + semicolon = ';', // 0x3B + less = '<', // 0x3C + equals = '=', // 0x3D + greater = '>', // 0x3E + question = '?', // 0x3F + at = '@', // 0x40 + + A = 'A', // 0x41 + B = 'B', // 0x42 + C = 'C', // 0x43 + D = 'D', // 0x44 + E = 'E', // 0x45 + F = 'F', // 0x46 + G = 'G', // 0x47 + H = 'H', // 0x48 + I = 'I', // 0x49 + J = 'J', // 0x4A + K = 'K', // 0x4B + L = 'L', // 0x4C + M = 'M', // 0x4D + N = 'N', // 0x4E + O = 'O', // 0x4F + P = 'P', // 0x50 + Q = 'Q', // 0x51 + R = 'R', // 0x52 + S = 'S', // 0x53 + T = 'T', // 0x54 + U = 'U', // 0x55 + V = 'V', // 0x56 + W = 'W', // 0x57 + X = 'X', // 0x58 + Y = 'Y', // 0x59 + Z = 'Z', // 0x5A + + bracket_open = '[', // 0x5B + backslash = '\\', // 0x5C + bracket_close = ']', // 0x5D + caret = '^', // 0x5E + underscore = '_', // 0x5F + backtick = '`', // 0x60 + + kpad_0 = 0x61, + kpad_1 = 0x62, + kpad_2 = 0x63, + kpad_3 = 0x64, + kpad_4 = 0x65, + kpad_5 = 0x66, + kpad_6 = 0x67, + kpad_7 = 0x68, + kpad_8 = 0x69, + kpad_9 = 0x6A, + kpad_decimal = 0x6B, + kpad_equals = 0x6C, + kpad_plus = 0x6D, + kpad_minus = 0x6E, + kpad_multiply = 0x6F, + kpad_divide = 0x70, + kpad_enter = 0x71, + + F1 = 0x72, + F2 = 0x73, + F3 = 0x74, + F4 = 0x75, + F5 = 0x76, + F6 = 0x77, + F7 = 0x78, + F8 = 0x79, + F9 = 0x7A, + F10 = 0x7B, + F11 = 0x7C, + F12 = 0x7D, + + insert = 0x7E, + delete = 0x7F, + + F13 = 0x80, + F14 = 0x81, + F15 = 0x82, + F16 = 0x83, + F17 = 0x84, + F18 = 0x85, + F19 = 0x86, + F20 = 0x87, + F21 = 0x88, + F22 = 0x89, + F23 = 0x8A, + F24 = 0x8B, + F25 = 0x8C, + + count = 0x8D, +} diff --git a/code2/sectr/math.odin b/code2/sectr/math.odin index 1ea65ec..a8cb3a4 100644 --- a/code2/sectr/math.odin +++ b/code2/sectr/math.odin @@ -28,8 +28,7 @@ f32_Min :: 0x00800000 // Note(Ed) : I don't see an intrinsict available anywhere for this. So I'll be using the Terathon non-sse impl // Inverse Square Root // C++ Source https://github.com/EricLengyel/Terathon-Math-Library/blob/main/TSMath.cpp#L191 -inverse_sqrt_f32 :: proc "contextless" ( value: f32 ) -> f32 -{ +inverse_sqrt_f32 :: proc "contextless" ( value: f32 ) -> f32 { if ( value < f32_Min) { return f32_Infinity } value_u32 := transmute(u32) value diff --git a/code2/sectr/space.odin b/code2/sectr/space.odin index 0bbd432..bc30e76 100644 --- a/code2/sectr/space.odin +++ b/code2/sectr/space.odin @@ -24,12 +24,35 @@ when ODIN_OS == .Windows { // 1 inch = 2.54 cm, 96 inch * 2.54 = 243.84 DPCM } +//region Unit Conversion Impl + +// cm_to_points :: proc( cm : f32 ) -> f32 { +// } +// points_to_cm :: proc( points : f32 ) -> f32 { +// screen_dpc := get_state().app_window.dpc +// cm_per_pixel := 1.0 / screen_dpc +// pixels := points * DPT_DPC * cm_per_pixel +// return points * +// } +f32_cm_to_pixels :: #force_inline proc "contextless"(cm, screen_ppcm: f32) -> f32 { return cm * screen_ppcm } +f32_pixels_to_cm :: #force_inline proc "contextless"(pixels, screen_ppcm: f32) -> f32 { return pixels * (1.0 / screen_ppcm) } +f32_points_to_pixels :: #force_inline proc "contextless"(points, screen_ppcm: f32) -> f32 { return points * DPT_PPCM * (1.0 / screen_ppcm) } +f32_pixels_to_points :: #force_inline proc "contextless"(pixels, screen_ppcm: f32) -> f32 { return pixels * (1.0 / screen_ppcm) * Points_Per_CM } +v2f4_cm_to_pixels :: #force_inline proc "contextless"(v: V2_F4, screen_ppcm: f32) -> V2_F4 { return v * screen_ppcm } +v2f4_pixels_to_cm :: #force_inline proc "contextless"(v: V2_F4, screen_ppcm: f32) -> V2_F4 { return v * (1.0 / screen_ppcm) } +v2f4_points_to_pixels :: #force_inline proc "contextless"(vpoints: V2_F4, screen_ppcm: f32) -> V2_F4 { return vpoints * DPT_PPCM * (1.0 / screen_ppcm) } +r2f4_cm_to_pixels :: #force_inline proc "contextless"(range: R2_F4, screen_ppcm: f32) -> R2_F4 { return R2_F4 { range.p0 * screen_ppcm, range.p1 * screen_ppcm } } +range2_pixels_to_cm :: #force_inline proc "contextless"(range: R2_F4, screen_ppcm: f32) -> R2_F4 { cm_per_pixel := 1.0 / screen_ppcm; return R2_F4 { range.p0 * cm_per_pixel, range.p1 * cm_per_pixel } } +// vec2_points_to_cm :: proc( vpoints : Vec2 ) -> Vec2 { +// } + +//endregion Unit Conversion Impl + AreaSize :: V2_F4 Bounds2 :: struct { top_left, bottom_right: V2_F4, } - BoundsCorners2 :: struct { top_left, top_right, bottom_left, bottom_right: V2_F4, } @@ -57,3 +80,66 @@ CameraZoomMode :: enum u32 { Extents2_F4 :: V2_F4 Extents2_S4 :: V2_S4 + + +bounds2_radius :: #force_inline proc "contextless" (bounds: Bounds2) -> f32 { return max( bounds.bottom_right.x, bounds.top_left.y ) } +extent_from_size :: #force_inline proc "contextless" (size: AreaSize) -> Extents2_F4 { return transmute(Extents2_F4) (size * 2.0) } +screen_size :: #force_inline proc "contextless" (screen_extent: Extents2_F4) -> AreaSize { return transmute(AreaSize) (screen_extent * 2.0) } +screen_get_bounds :: #force_inline proc "contextless" (screen_extent: Extents2_F4) -> R2_F4 { return R2_F4 { { -screen_extent.x, -screen_extent.y} /*bottom_left*/, { screen_extent.x, screen_extent.y} /*top_right*/ } } +screen_get_corners :: #force_inline proc "contextless"(screen_extent: Extents2_F4) -> BoundsCorners2 { return { + top_left = { -screen_extent.x, screen_extent.y }, + top_right = { screen_extent.x, screen_extent.y }, + bottom_left = { -screen_extent.x, -screen_extent.y }, + bottom_right = { screen_extent.x, -screen_extent.y }, +}} +view_get_bounds :: #force_inline proc "contextless"(cam: Camera, screen_extent: Extents2_F4) -> R2_F4 { + cam_zoom_ratio := 1.0 / cam.zoom + bottom_left := V2_F4 { -screen_extent.x, -screen_extent.y} + top_right := V2_F4 { screen_extent.x, screen_extent.y} + bottom_left = screen_to_ws_view_pos(bottom_left, cam.position, cam.zoom) + top_right = screen_to_ws_view_pos(top_right, cam.position, cam.zoom) + return R2_F4{bottom_left, top_right} +} +view_get_corners :: #force_inline proc "contextless"(cam: Camera, screen_extent: Extents2_F4) -> BoundsCorners2 { + cam_zoom_ratio := 1.0 / cam.zoom + zoomed_extent := screen_extent * cam_zoom_ratio + top_left := cam.position + V2_F4 { -zoomed_extent.x, zoomed_extent.y } + top_right := cam.position + V2_F4 { zoomed_extent.x, zoomed_extent.y } + bottom_left := cam.position + V2_F4 { -zoomed_extent.x, -zoomed_extent.y } + bottom_right := cam.position + V2_F4 { zoomed_extent.x, -zoomed_extent.y } + return { top_left, top_right, bottom_left, bottom_right } +} +render_to_screen_pos :: #force_inline proc "contextless" (pos: V2_F4, screen_extent: Extents2_F4) -> V2_F4 { return V2_F4 { pos.x - screen_extent.x, (pos.y * -1) + screen_extent.y } } +render_to_ws_view_pos :: #force_inline proc "contextless" (pos: V2_F4) -> V2_F4 { return {} } //TODO(Ed): Implement? +screen_to_ws_view_pos :: #force_inline proc "contextless" (pos: V2_F4, cam_pos: V2_F4, cam_zoom: f32, ) -> V2_F4 { return pos * (/*Camera Zoom Ratio*/1.0 / cam_zoom) - cam_pos } // TODO(Ed): Doesn't take into account view extent. +screen_to_render_pos :: #force_inline proc "contextless" (pos: V2_F4, screen_extent: Extents2_F4) -> V2_F4 { return pos + screen_extent } // Centered screen space to conventional screen space used for rendering + +// TODO(Ed): These should assume a cam_context or have the ability to provide it in params +ws_view_extent :: #force_inline proc "contextless" (cam_view: Extents2_F4, cam_zoom: f32) -> Extents2_F4 { return cam_view * (/*Camera Zoom Ratio*/1.0 / cam_zoom) } +ws_view_to_screen_pos :: #force_inline proc "contextless" (ws_pos : V2_F4, cam: Camera) -> V2_F4 { + // Apply camera transformation + view_pos := (ws_pos - cam.position) * cam.zoom + // TODO(Ed): properly take into account cam.view + screen_pos := view_pos + return screen_pos +} +ws_view_to_render_pos :: #force_inline proc "contextless"(position: V2_F4, cam: Camera, screen_extent: Extents2_F4) -> V2_F4 { + extent_offset: V2_F4 = { screen_extent.x, screen_extent.y } * { 1, 1 } + position := V2_F4 { position.x, position.y } + cam_offset := V2_F4 { cam.position.x, cam.position.y } + return extent_offset + (position + cam_offset) * cam.zoom +} + +// Workspace view to screen space position (zoom agnostic) +// TODO(Ed): Support a position which would not be centered on the screen if in a viewport +ws_view_to_screen_pos_no_zoom :: #force_inline proc "contextless"(position: V2_F4, cam: Camera) -> V2_F4 { + cam_zoom_ratio := 1.0 / cam.zoom + return { position.x, position.y } * cam_zoom_ratio +} + +// Workspace view to render space position (zoom agnostic) +// TODO(Ed): Support a position which would not be centered on the screen if in a viewport +ws_view_to_render_pos_no_zoom :: #force_inline proc "contextless"(position: V2_F4, cam: Camera) -> V2_F4 { + cam_zoom_ratio := 1.0 / cam.zoom + return { position.x, position.y } * cam_zoom_ratio +} diff --git a/code2/sectr/state.odin b/code2/sectr/state.odin index 70c1fc8..463265e 100644 --- a/code2/sectr/state.odin +++ b/code2/sectr/state.odin @@ -2,8 +2,8 @@ package sectr //region STATIC MEMORY // This should be the only global on client module side. - memory: ^ProcessMemory -@(thread_local) thread: ^ThreadMemory +@(private) memory: ^ProcessMemory +@(private, thread_local) thread: ^ThreadMemory //endregion STATIC MEMORy MemoryConfig :: struct { @@ -70,16 +70,28 @@ FrameTime :: struct { } State :: struct { + sokol_frame_count: i64, + sokol_context: Context, + config: AppConfig, app_window: AppWindow, + logger: Logger, + // Overall frametime of the tick frame (currently main thread's) using frametime : FrameTime, - logger: Logger, - sokol_frame_count: i64, - sokol_context: Context, + input_data : [2]InputState, + input_prev : ^InputState, + input : ^InputState, // TODO(Ed): Rename to indicate its the device's signal state for the frame? + + input_events: InputEvents, + input_binds_stack: Array(InputContext), + + // Note(Ed): Do not modify directly, use its interface in app/event.odin + staged_input_events : Array(InputEvent), + // TODO(Ed): Add a multi-threaded guard for accessing or mutating staged_input_events. } ThreadState :: struct { @@ -96,3 +108,7 @@ ThreadState :: struct { app_config :: #force_inline proc "contextless" () -> AppConfig { return memory.client_memory.config } get_frametime :: #force_inline proc "contextless" () -> FrameTime { return memory.client_memory.frametime } +// get_state :: #force_inline proc "contextless" () -> ^State { return memory.client_memory } + +get_input_binds :: #force_inline proc "contextless" () -> InputContext { return array_back (memory.client_memory.input_binds_stack) } +get_input_binds_stack :: #force_inline proc "contextless" () -> []InputContext { return array_to_slice(memory.client_memory.input_binds_stack) }