mcrfpy.Mouse: a new class built for symmetry with mcrfpy.Keyboard. Closes #186
This commit is contained in:
parent
b0b17f4633
commit
75127ac9d1
4 changed files with 286 additions and 0 deletions
|
|
@ -14,6 +14,7 @@
|
|||
#include "PySound.h"
|
||||
#include "PyMusic.h"
|
||||
#include "PyKeyboard.h"
|
||||
#include "PyMouse.h"
|
||||
#include "McRogueFaceVersion.h"
|
||||
#include "GameEngine.h"
|
||||
#include "ImGuiConsole.h"
|
||||
|
|
@ -341,6 +342,9 @@ PyObject* PyInit_mcrfpy()
|
|||
/*keyboard state (#160)*/
|
||||
&PyKeyboardType,
|
||||
|
||||
/*mouse state (#186)*/
|
||||
&PyMouseType,
|
||||
|
||||
nullptr};
|
||||
|
||||
// Types that are used internally but NOT exported to module namespace (#189)
|
||||
|
|
@ -415,6 +419,12 @@ PyObject* PyInit_mcrfpy()
|
|||
PyModule_AddObject(m, "keyboard", keyboard_instance);
|
||||
}
|
||||
|
||||
// Add mouse singleton (#186)
|
||||
PyObject* mouse_instance = PyObject_CallObject((PyObject*)&PyMouseType, NULL);
|
||||
if (mouse_instance) {
|
||||
PyModule_AddObject(m, "mouse", mouse_instance);
|
||||
}
|
||||
|
||||
// Add window singleton (#184)
|
||||
// Use tp_alloc directly to bypass tp_new which blocks user instantiation
|
||||
PyObject* window_instance = PyWindowType.tp_alloc(&PyWindowType, 0);
|
||||
|
|
|
|||
185
src/PyMouse.cpp
Normal file
185
src/PyMouse.cpp
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
#include "PyMouse.h"
|
||||
#include "McRFPy_API.h"
|
||||
#include "McRFPy_Automation.h"
|
||||
#include "GameEngine.h"
|
||||
#include "McRFPy_Doc.h"
|
||||
#include "PyVector.h"
|
||||
|
||||
int PyMouse::init(PyMouseObject* self, PyObject* args, PyObject* kwds)
|
||||
{
|
||||
// Initialize tracked state to SFML defaults
|
||||
self->cursor_visible = true;
|
||||
self->cursor_grabbed = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Helper to get current mouse position, handling headless mode
|
||||
static sf::Vector2i getMousePosition()
|
||||
{
|
||||
GameEngine* engine = McRFPy_API::game;
|
||||
|
||||
if (!engine || !engine->getRenderTargetPtr()) {
|
||||
return McRFPy_Automation::getSimulatedMousePosition();
|
||||
}
|
||||
|
||||
if (engine->isHeadless()) {
|
||||
// In headless mode, return simulated position from automation
|
||||
return McRFPy_Automation::getSimulatedMousePosition();
|
||||
}
|
||||
|
||||
// In windowed mode, get actual mouse position relative to window
|
||||
if (auto* window = dynamic_cast<sf::RenderWindow*>(engine->getRenderTargetPtr())) {
|
||||
return sf::Mouse::getPosition(*window);
|
||||
}
|
||||
|
||||
// Fallback to simulated position
|
||||
return McRFPy_Automation::getSimulatedMousePosition();
|
||||
}
|
||||
|
||||
PyObject* PyMouse::repr(PyObject* obj)
|
||||
{
|
||||
sf::Vector2i pos = getMousePosition();
|
||||
bool left = sf::Mouse::isButtonPressed(sf::Mouse::Left);
|
||||
bool right = sf::Mouse::isButtonPressed(sf::Mouse::Right);
|
||||
bool middle = sf::Mouse::isButtonPressed(sf::Mouse::Middle);
|
||||
|
||||
PyMouseObject* self = (PyMouseObject*)obj;
|
||||
|
||||
std::ostringstream ss;
|
||||
ss << "<Mouse pos=(" << pos.x << ", " << pos.y << ")"
|
||||
<< " left=" << (left ? "True" : "False")
|
||||
<< " right=" << (right ? "True" : "False")
|
||||
<< " middle=" << (middle ? "True" : "False")
|
||||
<< " visible=" << (self->cursor_visible ? "True" : "False")
|
||||
<< " grabbed=" << (self->cursor_grabbed ? "True" : "False") << ">";
|
||||
|
||||
std::string repr_str = ss.str();
|
||||
return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
|
||||
}
|
||||
|
||||
PyObject* PyMouse::get_x(PyObject* self, void* closure)
|
||||
{
|
||||
sf::Vector2i pos = getMousePosition();
|
||||
return PyLong_FromLong(pos.x);
|
||||
}
|
||||
|
||||
PyObject* PyMouse::get_y(PyObject* self, void* closure)
|
||||
{
|
||||
sf::Vector2i pos = getMousePosition();
|
||||
return PyLong_FromLong(pos.y);
|
||||
}
|
||||
|
||||
PyObject* PyMouse::get_pos(PyObject* self, void* closure)
|
||||
{
|
||||
sf::Vector2i pos = getMousePosition();
|
||||
|
||||
// Return a Vector object
|
||||
auto vector_type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Vector");
|
||||
if (!vector_type) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Vector type not found in mcrfpy module");
|
||||
return NULL;
|
||||
}
|
||||
PyObject* result = PyObject_CallFunction((PyObject*)vector_type, "ff", (float)pos.x, (float)pos.y);
|
||||
Py_DECREF(vector_type);
|
||||
return result;
|
||||
}
|
||||
|
||||
PyObject* PyMouse::get_left(PyObject* self, void* closure)
|
||||
{
|
||||
bool pressed = sf::Mouse::isButtonPressed(sf::Mouse::Left);
|
||||
return PyBool_FromLong(pressed);
|
||||
}
|
||||
|
||||
PyObject* PyMouse::get_right(PyObject* self, void* closure)
|
||||
{
|
||||
bool pressed = sf::Mouse::isButtonPressed(sf::Mouse::Right);
|
||||
return PyBool_FromLong(pressed);
|
||||
}
|
||||
|
||||
PyObject* PyMouse::get_middle(PyObject* self, void* closure)
|
||||
{
|
||||
bool pressed = sf::Mouse::isButtonPressed(sf::Mouse::Middle);
|
||||
return PyBool_FromLong(pressed);
|
||||
}
|
||||
|
||||
PyObject* PyMouse::get_visible(PyObject* self, void* closure)
|
||||
{
|
||||
PyMouseObject* mouse = (PyMouseObject*)self;
|
||||
return PyBool_FromLong(mouse->cursor_visible);
|
||||
}
|
||||
|
||||
int PyMouse::set_visible(PyObject* self, PyObject* value, void* closure)
|
||||
{
|
||||
if (!PyBool_Check(value)) {
|
||||
PyErr_SetString(PyExc_TypeError, "visible must be a boolean");
|
||||
return -1;
|
||||
}
|
||||
|
||||
PyMouseObject* mouse = (PyMouseObject*)self;
|
||||
bool visible = PyObject_IsTrue(value);
|
||||
mouse->cursor_visible = visible;
|
||||
|
||||
// Apply to window if available
|
||||
GameEngine* engine = McRFPy_API::game;
|
||||
if (engine && !engine->isHeadless()) {
|
||||
if (auto* window = dynamic_cast<sf::RenderWindow*>(engine->getRenderTargetPtr())) {
|
||||
window->setMouseCursorVisible(visible);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
PyObject* PyMouse::get_grabbed(PyObject* self, void* closure)
|
||||
{
|
||||
PyMouseObject* mouse = (PyMouseObject*)self;
|
||||
return PyBool_FromLong(mouse->cursor_grabbed);
|
||||
}
|
||||
|
||||
int PyMouse::set_grabbed(PyObject* self, PyObject* value, void* closure)
|
||||
{
|
||||
if (!PyBool_Check(value)) {
|
||||
PyErr_SetString(PyExc_TypeError, "grabbed must be a boolean");
|
||||
return -1;
|
||||
}
|
||||
|
||||
PyMouseObject* mouse = (PyMouseObject*)self;
|
||||
bool grabbed = PyObject_IsTrue(value);
|
||||
mouse->cursor_grabbed = grabbed;
|
||||
|
||||
// Apply to window if available
|
||||
GameEngine* engine = McRFPy_API::game;
|
||||
if (engine && !engine->isHeadless()) {
|
||||
if (auto* window = dynamic_cast<sf::RenderWindow*>(engine->getRenderTargetPtr())) {
|
||||
window->setMouseCursorGrabbed(grabbed);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
PyGetSetDef PyMouse::getsetters[] = {
|
||||
// Position (read-only)
|
||||
{"x", (getter)PyMouse::get_x, NULL,
|
||||
MCRF_PROPERTY(x, "Current mouse X position in window coordinates (read-only)."), NULL},
|
||||
{"y", (getter)PyMouse::get_y, NULL,
|
||||
MCRF_PROPERTY(y, "Current mouse Y position in window coordinates (read-only)."), NULL},
|
||||
{"pos", (getter)PyMouse::get_pos, NULL,
|
||||
MCRF_PROPERTY(pos, "Current mouse position as Vector (read-only)."), NULL},
|
||||
|
||||
// Button state (read-only)
|
||||
{"left", (getter)PyMouse::get_left, NULL,
|
||||
MCRF_PROPERTY(left, "True if left mouse button is currently pressed (read-only)."), NULL},
|
||||
{"right", (getter)PyMouse::get_right, NULL,
|
||||
MCRF_PROPERTY(right, "True if right mouse button is currently pressed (read-only)."), NULL},
|
||||
{"middle", (getter)PyMouse::get_middle, NULL,
|
||||
MCRF_PROPERTY(middle, "True if middle mouse button is currently pressed (read-only)."), NULL},
|
||||
|
||||
// Cursor control (read-write)
|
||||
{"visible", (getter)PyMouse::get_visible, (setter)PyMouse::set_visible,
|
||||
MCRF_PROPERTY(visible, "Whether the mouse cursor is visible (default: True)."), NULL},
|
||||
{"grabbed", (getter)PyMouse::get_grabbed, (setter)PyMouse::set_grabbed,
|
||||
MCRF_PROPERTY(grabbed, "Whether the mouse cursor is confined to the window (default: False)."), NULL},
|
||||
|
||||
{NULL}
|
||||
};
|
||||
50
src/PyMouse.h
Normal file
50
src/PyMouse.h
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
#pragma once
|
||||
#include "Common.h"
|
||||
#include "Python.h"
|
||||
|
||||
// Singleton mouse state object
|
||||
typedef struct {
|
||||
PyObject_HEAD
|
||||
// Track state for properties without SFML getters
|
||||
bool cursor_visible;
|
||||
bool cursor_grabbed;
|
||||
} PyMouseObject;
|
||||
|
||||
class PyMouse
|
||||
{
|
||||
public:
|
||||
// Python getters - query real-time mouse state via SFML
|
||||
static PyObject* get_x(PyObject* self, void* closure);
|
||||
static PyObject* get_y(PyObject* self, void* closure);
|
||||
static PyObject* get_pos(PyObject* self, void* closure);
|
||||
|
||||
// Button state getters
|
||||
static PyObject* get_left(PyObject* self, void* closure);
|
||||
static PyObject* get_right(PyObject* self, void* closure);
|
||||
static PyObject* get_middle(PyObject* self, void* closure);
|
||||
|
||||
// Cursor visibility/grab (read-write, tracked internally)
|
||||
static PyObject* get_visible(PyObject* self, void* closure);
|
||||
static int set_visible(PyObject* self, PyObject* value, void* closure);
|
||||
static PyObject* get_grabbed(PyObject* self, void* closure);
|
||||
static int set_grabbed(PyObject* self, PyObject* value, void* closure);
|
||||
|
||||
static PyObject* repr(PyObject* obj);
|
||||
static int init(PyMouseObject* self, PyObject* args, PyObject* kwds);
|
||||
static PyGetSetDef getsetters[];
|
||||
};
|
||||
|
||||
namespace mcrfpydef {
|
||||
static PyTypeObject PyMouseType = {
|
||||
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
|
||||
.tp_name = "mcrfpy.Mouse",
|
||||
.tp_basicsize = sizeof(PyMouseObject),
|
||||
.tp_itemsize = 0,
|
||||
.tp_repr = PyMouse::repr,
|
||||
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||
.tp_doc = PyDoc_STR("Mouse state singleton for reading button/position state and controlling cursor visibility"),
|
||||
.tp_getset = PyMouse::getsetters,
|
||||
.tp_init = (initproc)PyMouse::init,
|
||||
.tp_new = PyType_GenericNew,
|
||||
};
|
||||
}
|
||||
|
|
@ -204,6 +204,41 @@ class Keyboard:
|
|||
system: bool
|
||||
"""True if either System key (Win/Cmd) is currently pressed (read-only)."""
|
||||
|
||||
class Mouse:
|
||||
"""Mouse state singleton for reading button/position state and controlling cursor.
|
||||
|
||||
Access via mcrfpy.mouse (singleton instance).
|
||||
Queries real-time mouse state from SFML. In headless mode, returns
|
||||
simulated position from mcrfpy.automation calls.
|
||||
"""
|
||||
|
||||
# Position (read-only)
|
||||
x: int
|
||||
"""Current mouse X position in window coordinates (read-only)."""
|
||||
|
||||
y: int
|
||||
"""Current mouse Y position in window coordinates (read-only)."""
|
||||
|
||||
pos: Vector
|
||||
"""Current mouse position as Vector (read-only)."""
|
||||
|
||||
# Button state (read-only)
|
||||
left: bool
|
||||
"""True if left mouse button is currently pressed (read-only)."""
|
||||
|
||||
right: bool
|
||||
"""True if right mouse button is currently pressed (read-only)."""
|
||||
|
||||
middle: bool
|
||||
"""True if middle mouse button is currently pressed (read-only)."""
|
||||
|
||||
# Cursor control (read-write)
|
||||
visible: bool
|
||||
"""Whether the mouse cursor is visible (default: True)."""
|
||||
|
||||
grabbed: bool
|
||||
"""Whether the mouse cursor is confined to the window (default: False)."""
|
||||
|
||||
class Drawable:
|
||||
"""Base class for all drawable UI elements."""
|
||||
|
||||
|
|
@ -672,6 +707,12 @@ __version__: str
|
|||
keyboard: Keyboard
|
||||
"""Keyboard state singleton for checking modifier keys."""
|
||||
|
||||
mouse: Mouse
|
||||
"""Mouse state singleton for reading button/position state and controlling cursor."""
|
||||
|
||||
window: Window
|
||||
"""Window singleton for controlling window properties."""
|
||||
|
||||
# Module functions
|
||||
|
||||
def sceneUI(scene: Optional[str] = None) -> UICollection:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue