Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions code/framework/src/input/button_state.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* MafiaHub OSS license
* Copyright (c) 2026, MafiaHub. All rights reserved.
*
* This file comes from MafiaHub, hosted at https://github.com/MafiaHub/Framework.
* See LICENSE file in the source repository for information regarding licensing.
*/

#pragma once

#include <array>
#include <cstddef>

namespace Framework::Input {
// Event state: clear edges after every consumer, never between consumers.
template <std::size_t Size>
class ButtonState final {
public:
bool IsDown(int index) const {
return Valid(index) && _down[index];
}
bool IsUp(int index) const {
return Valid(index) && !_down[index];
}
bool IsPressed(int index) const {
return Valid(index) && _pressed[index];
}
bool IsReleased(int index) const {
return Valid(index) && _released[index];
}

void Set(int index, bool down, bool recordEdge = true) {
if (!Valid(index) || _down[index] == down) {
return;
}
_down[index] = down;
if (!recordEdge) {
return;
}
if (down) {
_pressed[index] = true;
}
else {
_released[index] = true;
}
}

void ClearEdges() {
_pressed.fill(false);
_released.fill(false);
}

void ReleaseAll() {
for (std::size_t index = 0; index < Size; ++index) {
Set(static_cast<int>(index), false);
}
}

private:
static bool Valid(int index) {
return index >= 0 && static_cast<std::size_t>(index) < Size;
}
std::array<bool, Size> _down {};
std::array<bool, Size> _pressed {};
std::array<bool, Size> _released {};
};

// Sampled state: seed on acquisition/resume, so keys already held do not
// become new presses. Unavailable input does not publish release edges.
class KeySnapshot final {
public:
template <typename Reader>
void Update(Reader &&read, bool available) {
_keys.ClearEdges();
_available = available;
if (!available) {
_keys.ReleaseAll();
_keys.ClearEdges();
_resync = true;
return;
}
for (int key = 0; key < 256; ++key) {
_keys.Set(key, read(key));
}
if (_resync) {
_keys.ClearEdges();
}
_resync = false;
}

bool IsDown(int key) const {
return _available && _keys.IsDown(key);
}
bool IsPressed(int key) const {
return _available && _keys.IsPressed(key);
}
bool IsReleased(int key) const {
return _available && _keys.IsReleased(key);
}

private:
ButtonState<256> _keys;
bool _resync = true;
bool _available = false;
};
} // namespace Framework::Input
29 changes: 29 additions & 0 deletions code/framework/src/input/detail/win32_keys.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* MafiaHub OSS license
* Copyright (c) 2026, MafiaHub. All rights reserved.
*
* This file comes from MafiaHub, hosted at https://github.com/MafiaHub/Framework.
* See LICENSE file in the source repository for information regarding licensing.
*/

#pragma once

#include "../physical_keys.h"

namespace Framework::Input::detail {
inline bool IsForeground() {
DWORD pid = 0;
::GetWindowThreadProcessId(::GetForegroundWindow(), &pid);
return pid == ::GetCurrentProcessId();
}

inline bool ReadWin32Key(int key) {
DWORD pid = 0;
const DWORD thread = ::GetWindowThreadProcessId(::GetForegroundWindow(), &pid);
if (pid != ::GetCurrentProcessId()) {
return false;
}
const UINT code = PhysicalKeys::ToLayoutVirtualKey(static_cast<UINT>(key), ::GetKeyboardLayout(thread));
return code != 0 && (::GetAsyncKeyState(static_cast<int>(code)) & 0x8000) != 0;
}
} // namespace Framework::Input::detail
22 changes: 6 additions & 16 deletions code/framework/src/input/input.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,15 @@

#pragma once

#include <cstdint>

#include "input_keymap.h"

namespace Framework::Input {
class IInput {
public:
virtual ~IInput() = default;
virtual ~IInput() = default;
virtual void Update() = 0;

virtual void SetMousePosition(int x, int y) = 0;
virtual void SetMousePosition(int x, int y) = 0;
virtual void GetMousePosition(int &x, int &y) const = 0;
virtual void SetMouseVisible(bool visible) = 0;
virtual bool IsMouseVisible() const = 0;
Expand All @@ -27,17 +25,11 @@ namespace Framework::Input {
virtual void SetInputLocked(bool locked) = 0;
virtual bool IsInputLocked() const = 0;

// Opt-in: WndProc-fed modules can't answer a physical poll (no mouse VKs, no left/right
// modifiers, and keys latch when a focus loss eats their WM_KEYUP).
virtual bool ProvidesPhysicalKeyState() const {
return false;
}

// Device state frozen rather than idle; callers edge-detecting must hold, not latch.
virtual bool IsStateStale() const {
return false;
}
// Fresh source state is available. Edge detectors reseed after an unavailable period.
virtual bool IsAvailable() const = 0;

// Every adapter accepts FW_KEY_* codes and US physical letter/digit positions.
// Native layout and scan-code conversion belongs inside the adapter.
virtual bool IsKeyDown(int key) const = 0;
virtual bool IsKeyUp(int key) const = 0;
virtual bool IsKeyPressed(int key) const = 0;
Expand All @@ -47,7 +39,5 @@ namespace Framework::Input {
virtual bool IsMouseButtonUp(int button) const = 0;
virtual bool IsMouseButtonPressed(int button) const = 0;
virtual bool IsMouseButtonReleased(int button) const = 0;

virtual uint32_t MapKey(uint32_t key) const = 0;
};
} // namespace Framework::Input
98 changes: 98 additions & 0 deletions code/framework/src/input/physical_keys.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#pragma once

#include <input/input_keymap.h>
#include <utils/safe_win32.h>

namespace Framework::Input::PhysicalKeys {
Expand Down Expand Up @@ -90,6 +91,103 @@ namespace Framework::Input::PhysicalKeys {
return MapVirtualKeyW(virtualKey, MAPVK_VK_TO_VSC_EX);
}

// Native device codes are fixed by the DirectInput SDK, not by an active
// layout or MapVirtualKey's navigation/numpad interpretation.
inline UINT ToDirectInputCode(UINT key) {
for (const auto &position : detail::kPositions) {
if (position.virtualKey == key)
return position.scanCode;
}
switch (key) {
case FW_KEY_ESCAPE: return 0x01;
case FW_KEY_BACK: return 0x0E;
case FW_KEY_TAB: return 0x0F;
case FW_KEY_RETURN: return 0x1C;
case FW_KEY_CONTROL:
case FW_KEY_LCONTROL: return 0x1D;
case FW_KEY_SHIFT:
case FW_KEY_LSHIFT: return 0x2A;
case FW_KEY_RSHIFT: return 0x36;
case FW_KEY_MULTIPLY: return 0x37;
case FW_KEY_MENU:
case FW_KEY_LMENU: return 0x38;
case FW_KEY_SPACE: return 0x39;
case FW_KEY_CAPITAL: return 0x3A;
case FW_KEY_F1: return 0x3B;
case FW_KEY_F2: return 0x3C;
case FW_KEY_F3: return 0x3D;
case FW_KEY_F4: return 0x3E;
case FW_KEY_F5: return 0x3F;
case FW_KEY_F6: return 0x40;
case FW_KEY_F7: return 0x41;
case FW_KEY_F8: return 0x42;
case FW_KEY_F9: return 0x43;
case FW_KEY_F10: return 0x44;
case FW_KEY_NUMLOCK: return 0x45;
case FW_KEY_SCROLL: return 0x46;
case FW_KEY_NUMPAD7: return 0x47;
case FW_KEY_NUMPAD8: return 0x48;
case FW_KEY_NUMPAD9: return 0x49;
case FW_KEY_SUBTRACT: return 0x4A;
case FW_KEY_NUMPAD4: return 0x4B;
case FW_KEY_CLEAR:
case FW_KEY_NUMPAD5: return 0x4C;
case FW_KEY_NUMPAD6: return 0x4D;
case FW_KEY_ADD: return 0x4E;
case FW_KEY_NUMPAD1: return 0x4F;
case FW_KEY_NUMPAD2: return 0x50;
case FW_KEY_NUMPAD3: return 0x51;
case FW_KEY_NUMPAD0: return 0x52;
case FW_KEY_DECIMAL: return 0x53;
case FW_KEY_F11: return 0x57;
case FW_KEY_F12: return 0x58;
case FW_KEY_F13: return 0x64;
case FW_KEY_F14: return 0x65;
case FW_KEY_F15: return 0x66;
case FW_KEY_KANA: return 0x70;
case FW_KEY_CONVERT: return 0x79;
case FW_KEY_NONCONVERT: return 0x7B;
case FW_KEY_OEM_NEC_EQUAL: return 0x8D;
case FW_KEY_MEDIA_PREV_TRACK: return 0x90;
case FW_KEY_KANJI: return 0x94;
case FW_KEY_MEDIA_NEXT_TRACK: return 0x99;
case FW_KEY_RCONTROL: return 0x9D;
case FW_KEY_VOLUME_MUTE: return 0xA0;
case FW_KEY_MEDIA_PLAY_PAUSE: return 0xA2;
case FW_KEY_MEDIA_STOP: return 0xA4;
case FW_KEY_VOLUME_DOWN: return 0xAE;
case FW_KEY_VOLUME_UP: return 0xB0;
case FW_KEY_BROWSER_HOME: return 0xB2;
case FW_KEY_DIVIDE: return 0xB5;
case FW_KEY_SNAPSHOT: return 0xB7;
case FW_KEY_RMENU: return 0xB8;
case FW_KEY_PAUSE: return 0xC5;
case FW_KEY_HOME: return 0xC7;
case FW_KEY_UP: return 0xC8;
case FW_KEY_PRIOR: return 0xC9;
case FW_KEY_LEFT: return 0xCB;
case FW_KEY_RIGHT: return 0xCD;
case FW_KEY_END: return 0xCF;
case FW_KEY_DOWN: return 0xD0;
case FW_KEY_NEXT: return 0xD1;
case FW_KEY_INSERT: return 0xD2;
case FW_KEY_DELETE: return 0xD3;
case FW_KEY_LWIN: return 0xDB;
case FW_KEY_RWIN: return 0xDC;
case FW_KEY_APPS: return 0xDD;
case FW_KEY_SLEEP: return 0xDF;
case FW_KEY_BROWSER_SEARCH: return 0xE5;
case FW_KEY_BROWSER_FAVORITES: return 0xE6;
case FW_KEY_BROWSER_REFRESH: return 0xE7;
case FW_KEY_BROWSER_STOP: return 0xE8;
case FW_KEY_BROWSER_FORWARD: return 0xE9;
case FW_KEY_BROWSER_BACK: return 0xEA;
case FW_KEY_LAUNCH_MAIL: return 0xEC;
case FW_KEY_LAUNCH_MEDIA_SELECT: return 0xED;
default: return 0;
}
}

inline UINT FromScanCode(UINT scanCode) {
for (const auto &position : detail::kPositions) {
if (position.scanCode == scanCode) {
Expand Down
35 changes: 35 additions & 0 deletions code/framework/src/input/polling_input.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* MafiaHub OSS license
* Copyright (c) 2026, MafiaHub. All rights reserved.
*
* This file comes from MafiaHub, hosted at https://github.com/MafiaHub/Framework.
* See LICENSE file in the source repository for information regarding licensing.
*/

#pragma once

#include "button_state.h"
#include "win32_input.h"

namespace Framework::Input {
// UI acquisition which stays live while a native game device is paused.
class PollingInput: public Win32Input {
public:
void Update() override {
_keys.Update(
[this](int key) {
return ReadKeyDown(key);
},
IsAvailable());
}
bool IsKeyPressed(int key) const override {
return IsAvailable() && _keys.IsPressed(key);
}
bool IsKeyReleased(int key) const override {
return IsAvailable() && _keys.IsReleased(key);
}

private:
KeySnapshot _keys;
};
} // namespace Framework::Input
63 changes: 63 additions & 0 deletions code/framework/src/input/resource_holds.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* MafiaHub OSS license
* Copyright (c) 2026, MafiaHub. All rights reserved.
*
* This file comes from MafiaHub, hosted at https://github.com/MafiaHub/Framework.
* See LICENSE file in the source repository for information regarding licensing.
*/

#pragma once

#include <map>
#include <string>

namespace Framework::Input {
// Counts only the named owner's holds. The game applies each transition
// and releases TakeAll(owner) holds when that resource stops.
class ResourceHolds final {
public:
void Acquire(const std::string &owner) {
++_holds[owner];
}
bool Release(const std::string &owner) {
const auto held = _holds.find(owner);
if (held == _holds.end()) {
return false;
}
if (--held->second == 0) {
_holds.erase(held);
}
return true;
}
int TakeAll(const std::string &owner) {
const auto held = _holds.find(owner);
if (held == _holds.end()) {
return 0;
}
const int count = held->second;
_holds.erase(held);
return count;
}
// Idempotent ownership for setEnabled-style APIs.
void Set(const std::string &owner, bool held) {
if (held && !IsHeld(owner)) {
Acquire(owner);
}
else if (!held) {
TakeAll(owner);
}
}
bool IsHeld(const std::string &owner) const {
return _holds.find(owner) != _holds.end();
}
bool IsHeld() const {
return !_holds.empty();
}
void Clear() {
_holds.clear();
}

private:
std::map<std::string, int> _holds;
};
} // namespace Framework::Input
Loading
Loading