mirror of
https://github.com/seekrs/MacroLibX.git
synced 2026-01-11 14:43:34 +00:00
adding python bindings base
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -23,3 +23,5 @@
|
|||||||
objs/
|
objs/
|
||||||
build/
|
build/
|
||||||
example/Test
|
example/Test
|
||||||
|
macrolibpy/__pycache__
|
||||||
|
example/__pycache__
|
||||||
|
|||||||
18
example/main.py
git.filemode.normal_file
18
example/main.py
git.filemode.normal_file
@@ -0,0 +1,18 @@
|
|||||||
|
import macrolibpy as mlpy
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
mlx = mlpy.Context.create()
|
||||||
|
win = mlx.new_window(800, 600, "MacroLibX window")
|
||||||
|
|
||||||
|
def onevent(ev):
|
||||||
|
if ev == 0:
|
||||||
|
mlx.loop_end()
|
||||||
|
|
||||||
|
def onupdate():
|
||||||
|
for x in range(0, 50):
|
||||||
|
for y in range(0, 50):
|
||||||
|
win.pixel_put(100 + x, 100 + y, 0xff0000ff)
|
||||||
|
|
||||||
|
win.on_event(mlpy.EventType.WINDOW_EVENT, onevent)
|
||||||
|
mlx.add_loop_hook(onupdate)
|
||||||
|
mlx.loop()
|
||||||
274
macrolibpy/__init__.py
git.filemode.normal_file
274
macrolibpy/__init__.py
git.filemode.normal_file
@@ -0,0 +1,274 @@
|
|||||||
|
"""
|
||||||
|
macrolibpy — thin, Pythonic bindings over MacroLibX.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
from enum import IntEnum
|
||||||
|
from typing import Callable, Optional, Any, List
|
||||||
|
import os, sys
|
||||||
|
from cffi import FFI
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
__all__ = ["Context", "Window", "Image", "Event"]
|
||||||
|
|
||||||
|
ffi = FFI()
|
||||||
|
|
||||||
|
# NOTE: This is a curated declaration set focused on the core API
|
||||||
|
ffi.cdef(
|
||||||
|
r"""
|
||||||
|
typedef unsigned char uint8_t;
|
||||||
|
typedef unsigned int uint32_t;
|
||||||
|
|
||||||
|
typedef void (*mlx_function)(void);
|
||||||
|
typedef struct mlx_context_handler* mlx_context;
|
||||||
|
typedef struct mlx_window_handler* mlx_window;
|
||||||
|
typedef struct mlx_image_handler* mlx_image;
|
||||||
|
|
||||||
|
typedef struct mlx_color
|
||||||
|
{
|
||||||
|
uint32_t rgba;
|
||||||
|
} mlx_color;
|
||||||
|
|
||||||
|
typedef enum mlx_event_type
|
||||||
|
{
|
||||||
|
MLX_KEYDOWN = 0,
|
||||||
|
MLX_KEYUP = 1,
|
||||||
|
MLX_MOUSEDOWN = 2,
|
||||||
|
MLX_MOUSEUP = 3,
|
||||||
|
MLX_MOUSEWHEEL = 4,
|
||||||
|
MLX_WINDOW_EVENT = 5
|
||||||
|
} mlx_event_type;
|
||||||
|
|
||||||
|
typedef struct mlx_window_create_info
|
||||||
|
{
|
||||||
|
mlx_image render_target;
|
||||||
|
const char* title;
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
_Bool is_fullscreen;
|
||||||
|
_Bool is_resizable;
|
||||||
|
} mlx_window_create_info;
|
||||||
|
|
||||||
|
mlx_context mlx_init();
|
||||||
|
void mlx_set_fps_goal(mlx_context mlx, int fps);
|
||||||
|
void mlx_destroy_context(mlx_context mlx);
|
||||||
|
mlx_window mlx_new_window(mlx_context mlx, const mlx_window_create_info* info);
|
||||||
|
void mlx_destroy_window(mlx_context mlx, mlx_window win);
|
||||||
|
void mlx_set_window_position(mlx_context mlx, mlx_window win, int x, int y);
|
||||||
|
void mlx_set_window_size(mlx_context mlx, mlx_window win, int width, int height);
|
||||||
|
void mlx_set_window_title(mlx_context mlx, mlx_window win, const char* title);
|
||||||
|
void mlx_set_window_fullscreen(mlx_context mlx, mlx_window win, _Bool enable);
|
||||||
|
void mlx_get_window_position(mlx_context mlx, mlx_window win, int* x, int* y);
|
||||||
|
void mlx_get_window_size(mlx_context mlx, mlx_window win, int* x, int* y);
|
||||||
|
void mlx_clear_window(mlx_context mlx, mlx_window win, mlx_color color);
|
||||||
|
void mlx_get_screen_size(mlx_context mlx, mlx_window win, int* w, int* h);
|
||||||
|
void mlx_add_loop_hook(mlx_context mlx, void(*f)(void*), void* param);
|
||||||
|
void mlx_loop(mlx_context mlx);
|
||||||
|
void mlx_loop_end(mlx_context mlx);
|
||||||
|
void mlx_mouse_show(mlx_context mlx);
|
||||||
|
void mlx_mouse_hide(mlx_context mlx);
|
||||||
|
void mlx_mouse_move(mlx_context mlx, mlx_window win, int x, int y);
|
||||||
|
void mlx_mouse_get_pos(mlx_context mlx, int* x, int* y);
|
||||||
|
void mlx_on_event(mlx_context mlx, mlx_window win, mlx_event_type event, void(*f)(int, void*), void* param);
|
||||||
|
void mlx_pixel_put(mlx_context mlx, mlx_window win, int x, int y, mlx_color color);
|
||||||
|
mlx_image mlx_new_image(mlx_context mlx, int width, int height);
|
||||||
|
mlx_image mlx_new_image_from_file(mlx_context mlx, char* filename, int* width, int* height);
|
||||||
|
void mlx_destroy_image(mlx_context mlx, mlx_image image);
|
||||||
|
mlx_color mlx_get_image_pixel(mlx_context mlx, mlx_image image, int x, int y);
|
||||||
|
void mlx_set_image_pixel(mlx_context mlx, mlx_image image, int x, int y, mlx_color color);
|
||||||
|
void mlx_put_image_to_window(mlx_context mlx, mlx_window win, mlx_image image, int x, int y);
|
||||||
|
void mlx_string_put(mlx_context mlx, mlx_window win, int x, int y, mlx_color color, char* str);
|
||||||
|
void mlx_set_font(mlx_context mlx, char* filepath);
|
||||||
|
void mlx_set_font_scale(mlx_context mlx, char* filepath, float scale);
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
def _candidateLibNames() -> List[str]:
|
||||||
|
if sys.platform.startswith("linux"):
|
||||||
|
return ["libmlx.so"]
|
||||||
|
if sys.platform == "darwin":
|
||||||
|
return ["libmlx.dylib"]
|
||||||
|
if sys.platform.startswith("win"):
|
||||||
|
# depending on build system, either of these may exist
|
||||||
|
return ["mlx.dll", "libmlx.dll"]
|
||||||
|
return []
|
||||||
|
|
||||||
|
def _candidateSdl2Names() -> List[str]:
|
||||||
|
if sys.platform.startswith("linux"):
|
||||||
|
# Try common SONAMEs (distro dependent)
|
||||||
|
return ["libSDL2-2.0.so.0", "libSDL2.so.0", "libSDL2.so"]
|
||||||
|
if sys.platform == "darwin":
|
||||||
|
return ["libSDL2-2.0.0.dylib", "libSDL2.dylib"]
|
||||||
|
if sys.platform.startswith("win"):
|
||||||
|
return ["SDL2.dll"]
|
||||||
|
return []
|
||||||
|
|
||||||
|
def _loadLibrary(candidates: List[str]):
|
||||||
|
last_err: Optional[BaseException] = None
|
||||||
|
RTLD_NOW = 2
|
||||||
|
RTLD_GLOBAL = 0x100
|
||||||
|
for path in candidates:
|
||||||
|
try:
|
||||||
|
return ffi.dlopen(path, RTLD_NOW | RTLD_GLOBAL)
|
||||||
|
except OSError as e:
|
||||||
|
last_err = e
|
||||||
|
raise OSError(
|
||||||
|
f"Could not load library:\n"
|
||||||
|
f"\tTried: {candidates}.\n"
|
||||||
|
f"\tLast error: {last_err}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _dlopenMlx():
|
||||||
|
_loadLibrary(_candidateSdl2Names())
|
||||||
|
candidates: List[str] = []
|
||||||
|
for name in _candidateLibNames():
|
||||||
|
candidates.append(os.path.join(pathlib.Path(__file__).parent.parent.resolve(), name))
|
||||||
|
return _loadLibrary(candidates)
|
||||||
|
|
||||||
|
lib = _dlopenMlx()
|
||||||
|
|
||||||
|
class EventType(IntEnum):
|
||||||
|
KEYDOWN = 0
|
||||||
|
KEYUP = 1
|
||||||
|
MOUSEDOWN = 2
|
||||||
|
MOUSEUP = 3
|
||||||
|
MOUSEWHEEL = 4
|
||||||
|
WINDOW_EVENT = 5
|
||||||
|
|
||||||
|
class MlxError(RuntimeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Keep Python references to cffi callbacks alive
|
||||||
|
_alive_callbacks: List[Any] = []
|
||||||
|
|
||||||
|
def _makeLoopCallback(pyfunc: Callable[[], None]):
|
||||||
|
@ffi.callback("void(void*)")
|
||||||
|
def cfunc(userdata):
|
||||||
|
pyfunc()
|
||||||
|
_alive_callbacks.append(cfunc)
|
||||||
|
return cfunc
|
||||||
|
|
||||||
|
def _makeEventCallback(pyfunc: Callable[[int], None]):
|
||||||
|
@ffi.callback("void(int, void*)")
|
||||||
|
def cfunc(event, userdata):
|
||||||
|
pyfunc(int(event))
|
||||||
|
_alive_callbacks.append(cfunc)
|
||||||
|
return cfunc
|
||||||
|
|
||||||
|
def _rgbaToColor(rgba: int):
|
||||||
|
c = ffi.new("mlx_color*")
|
||||||
|
c[0].rgba = int(rgba) & 0xFFFFFFFF
|
||||||
|
return c[0]
|
||||||
|
|
||||||
|
class Context:
|
||||||
|
__slots__ = ("_ctx",)
|
||||||
|
|
||||||
|
def __init__(self, _ptr) -> None:
|
||||||
|
self._ctx = _ptr
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create(cls) -> "Context":
|
||||||
|
ctx = lib.mlx_init()
|
||||||
|
if ctx == ffi.NULL:
|
||||||
|
raise MlxError("mlx_init failed")
|
||||||
|
return cls(ctx)
|
||||||
|
|
||||||
|
def set_fps_goal(self, fps: int) -> None:
|
||||||
|
lib.mlx_set_fps_goal(self._ctx, int(fps))
|
||||||
|
|
||||||
|
def add_loop_hook(self, fn: Callable[[], None]) -> None:
|
||||||
|
lib.mlx_add_loop_hook(self._ctx, _makeLoopCallback(fn), ffi.NULL)
|
||||||
|
|
||||||
|
def new_window(
|
||||||
|
self,
|
||||||
|
width: int,
|
||||||
|
height: int,
|
||||||
|
title: str,
|
||||||
|
resizable: bool = True,
|
||||||
|
fullscreen: bool = False,
|
||||||
|
) -> "Window":
|
||||||
|
info = ffi.new("mlx_window_create_info*")
|
||||||
|
info.render_target = ffi.NULL
|
||||||
|
info.title = ffi.from_buffer(title.encode("utf-8"))
|
||||||
|
info.width = int(width)
|
||||||
|
info.height = int(height)
|
||||||
|
info.is_fullscreen = bool(fullscreen)
|
||||||
|
info.is_resizable = bool(resizable)
|
||||||
|
win = lib.mlx_new_window(self._ctx, info)
|
||||||
|
if win == ffi.NULL:
|
||||||
|
raise MlxError("mlx_new_window failed")
|
||||||
|
return Window(self, win)
|
||||||
|
|
||||||
|
def new_image(self, width: int, height: int) -> "Image":
|
||||||
|
img = lib.mlx_new_image(self._ctx, int(width), int(height))
|
||||||
|
if img == ffi.NULL:
|
||||||
|
raise MlxError("mlx_new_image failed")
|
||||||
|
return Image(self, img)
|
||||||
|
|
||||||
|
def loop(self) -> None:
|
||||||
|
lib.mlx_loop(self._ctx)
|
||||||
|
|
||||||
|
def loop_end(self) -> None:
|
||||||
|
lib.mlx_loop_end(self._ctx)
|
||||||
|
|
||||||
|
def destroy(self) -> None:
|
||||||
|
if getattr(self, "_ctx", None):
|
||||||
|
lib.mlx_destroy_context(self._ctx)
|
||||||
|
self._ctx = ffi.NULL
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
try:
|
||||||
|
self.destroy()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Window:
|
||||||
|
__slots__ = ("_ctx", "_win")
|
||||||
|
|
||||||
|
def __init__(self, ctx: Context, win) -> None:
|
||||||
|
self._ctx = ctx
|
||||||
|
self._win = win
|
||||||
|
|
||||||
|
def set_title(self, title: str) -> None:
|
||||||
|
lib.mlx_set_window_title(self._ctx._ctx, self._win, ffi.from_buffer(title.encode("utf-8")))
|
||||||
|
|
||||||
|
def clear(self, rgba: int) -> None:
|
||||||
|
lib.mlx_clear_window(self._ctx._ctx, self._win, _rgbaToColor(rgba))
|
||||||
|
|
||||||
|
def pixel_put(self, x: int, y: int, rgba: int) -> None:
|
||||||
|
lib.mlx_pixel_put(self._ctx._ctx, self._win, int(x), int(y), _rgbaToColor(rgba))
|
||||||
|
|
||||||
|
def put_image(self, image: "Image", x: int = 0, y: int = 0) -> None:
|
||||||
|
lib.mlx_put_image_to_window(self._ctx._ctx, self._win, image._img, int(x), int(y))
|
||||||
|
|
||||||
|
def on_event(self, event_type: EventType | int, fn: Callable[[int], None]) -> None:
|
||||||
|
lib.mlx_on_event(self._ctx._ctx, self._win, int(event_type), _makeEventCallback(fn), ffi.NULL)
|
||||||
|
|
||||||
|
def destroy(self) -> None:
|
||||||
|
if getattr(self, "_win", None):
|
||||||
|
lib.mlx_destroy_window(self._ctx._ctx, self._win)
|
||||||
|
self._win = ffi.NULL
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
try:
|
||||||
|
self.destroy()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Image:
|
||||||
|
__slots__ = ("_ctx", "_img")
|
||||||
|
|
||||||
|
def __init__(self, ctx: Context, img) -> None:
|
||||||
|
self._ctx = ctx
|
||||||
|
self._img = img
|
||||||
|
|
||||||
|
def destroy(self) -> None:
|
||||||
|
if getattr(self, "_img", None):
|
||||||
|
lib.mlx_destroy_image(self._ctx._ctx, self._img)
|
||||||
|
self._img = ffi.NULL
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
try:
|
||||||
|
self.destroy()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
Reference in New Issue
Block a user