diff --git a/generate_stubs.py b/generate_stubs.py new file mode 100644 index 0000000..1ddacf7 --- /dev/null +++ b/generate_stubs.py @@ -0,0 +1,268 @@ +#!/usr/bin/env python3 +"""Generate .pyi type stub files for McRogueFace Python API. + +This script introspects the mcrfpy module and generates type stubs +for better IDE support and type checking. +""" + +import os +import sys +import inspect +import types +from typing import Dict, List, Set, Any + +# Add the build directory to path to import mcrfpy +sys.path.insert(0, './build') + +try: + import mcrfpy +except ImportError: + print("Error: Could not import mcrfpy. Make sure to run this from the project root after building.") + sys.exit(1) + +def parse_docstring_signature(doc: str) -> tuple[str, str]: + """Extract signature and description from docstring.""" + if not doc: + return "", "" + + lines = doc.strip().split('\n') + if lines: + # First line often contains the signature + first_line = lines[0] + if '(' in first_line and ')' in first_line: + # Extract just the part after the function name + start = first_line.find('(') + end = first_line.rfind(')') + 1 + if start != -1 and end != 0: + sig = first_line[start:end] + # Get return type if present + if '->' in first_line: + ret_start = first_line.find('->') + ret_type = first_line[ret_start:].strip() + return sig, ret_type + return sig, "" + return "", "" + +def get_type_hint(obj_type: type) -> str: + """Convert Python type to type hint string.""" + if obj_type == int: + return "int" + elif obj_type == float: + return "float" + elif obj_type == str: + return "str" + elif obj_type == bool: + return "bool" + elif obj_type == list: + return "List[Any]" + elif obj_type == dict: + return "Dict[Any, Any]" + elif obj_type == tuple: + return "Tuple[Any, ...]" + elif obj_type == type(None): + return "None" + else: + return "Any" + +def generate_class_stub(class_name: str, cls: type) -> List[str]: + """Generate stub for a class.""" + lines = [] + + # Get class docstring + if cls.__doc__: + doc_lines = cls.__doc__.strip().split('\n') + # Use only the first paragraph for the stub + lines.append(f'class {class_name}:') + lines.append(f' """{doc_lines[0]}"""') + else: + lines.append(f'class {class_name}:') + + # Check for __init__ method + if hasattr(cls, '__init__'): + init_doc = cls.__init__.__doc__ or cls.__doc__ + if init_doc: + sig, ret = parse_docstring_signature(init_doc) + if sig: + lines.append(f' def __init__(self{sig[1:-1]}) -> None: ...') + else: + lines.append(f' def __init__(self, *args, **kwargs) -> None: ...') + else: + lines.append(f' def __init__(self, *args, **kwargs) -> None: ...') + + # Get properties and methods + properties = [] + methods = [] + + for attr_name in dir(cls): + if attr_name.startswith('_') and not attr_name.startswith('__'): + continue + + try: + attr = getattr(cls, attr_name) + + if isinstance(attr, property): + properties.append((attr_name, attr)) + elif callable(attr) and not attr_name.startswith('__'): + methods.append((attr_name, attr)) + except: + pass + + # Add properties + if properties: + lines.append('') + for prop_name, prop in properties: + # Try to determine property type from docstring + if prop.fget and prop.fget.__doc__: + lines.append(f' @property') + lines.append(f' def {prop_name}(self) -> Any: ...') + if prop.fset: + lines.append(f' @{prop_name}.setter') + lines.append(f' def {prop_name}(self, value: Any) -> None: ...') + else: + lines.append(f' {prop_name}: Any') + + # Add methods + if methods: + lines.append('') + for method_name, method in methods: + if method.__doc__: + sig, ret = parse_docstring_signature(method.__doc__) + if sig and ret: + lines.append(f' def {method_name}(self{sig[1:-1]}) {ret}: ...') + elif sig: + lines.append(f' def {method_name}(self{sig[1:-1]}) -> Any: ...') + else: + lines.append(f' def {method_name}(self, *args, **kwargs) -> Any: ...') + else: + lines.append(f' def {method_name}(self, *args, **kwargs) -> Any: ...') + + lines.append('') + return lines + +def generate_function_stub(func_name: str, func: Any) -> str: + """Generate stub for a function.""" + if func.__doc__: + sig, ret = parse_docstring_signature(func.__doc__) + if sig and ret: + return f'def {func_name}{sig} {ret}: ...' + elif sig: + return f'def {func_name}{sig} -> Any: ...' + + return f'def {func_name}(*args, **kwargs) -> Any: ...' + +def generate_stubs(): + """Generate the main mcrfpy.pyi file.""" + lines = [ + '"""Type stubs for McRogueFace Python API.', + '', + 'Auto-generated - do not edit directly.', + '"""', + '', + 'from typing import Any, List, Dict, Tuple, Optional, Callable, Union', + '', + '# Module documentation', + ] + + # Add module docstring as comment + if mcrfpy.__doc__: + for line in mcrfpy.__doc__.strip().split('\n')[:3]: + lines.append(f'# {line}') + + lines.extend(['', '# Classes', '']) + + # Collect all classes + classes = [] + functions = [] + constants = [] + + for name in sorted(dir(mcrfpy)): + if name.startswith('_'): + continue + + obj = getattr(mcrfpy, name) + + if isinstance(obj, type): + classes.append((name, obj)) + elif callable(obj): + functions.append((name, obj)) + elif not inspect.ismodule(obj): + constants.append((name, obj)) + + # Generate class stubs + for class_name, cls in classes: + lines.extend(generate_class_stub(class_name, cls)) + + # Generate function stubs + if functions: + lines.extend(['# Functions', '']) + for func_name, func in functions: + lines.append(generate_function_stub(func_name, func)) + lines.append('') + + # Generate constants + if constants: + lines.extend(['# Constants', '']) + for const_name, const in constants: + const_type = get_type_hint(type(const)) + lines.append(f'{const_name}: {const_type}') + + return '\n'.join(lines) + +def generate_automation_stubs(): + """Generate stubs for the automation submodule.""" + if not hasattr(mcrfpy, 'automation'): + return None + + automation = mcrfpy.automation + + lines = [ + '"""Type stubs for McRogueFace automation API."""', + '', + 'from typing import Optional, Tuple', + '', + ] + + # Get all automation functions + for name in sorted(dir(automation)): + if name.startswith('_'): + continue + + obj = getattr(automation, name) + if callable(obj): + lines.append(generate_function_stub(name, obj)) + + return '\n'.join(lines) + +def main(): + """Main entry point.""" + print("Generating type stubs for McRogueFace...") + + # Generate main module stubs + stubs = generate_stubs() + + # Create stubs directory + os.makedirs('stubs', exist_ok=True) + + # Write main module stubs + with open('stubs/mcrfpy.pyi', 'w') as f: + f.write(stubs) + print("Generated stubs/mcrfpy.pyi") + + # Generate automation module stubs if available + automation_stubs = generate_automation_stubs() + if automation_stubs: + os.makedirs('stubs/mcrfpy', exist_ok=True) + with open('stubs/mcrfpy/__init__.pyi', 'w') as f: + f.write(stubs) + with open('stubs/mcrfpy/automation.pyi', 'w') as f: + f.write(automation_stubs) + print("Generated stubs/mcrfpy/automation.pyi") + + print("\nType stubs generated successfully!") + print("\nTo use in your IDE:") + print("1. Add the 'stubs' directory to your PYTHONPATH") + print("2. Or configure your IDE to look for stubs in the 'stubs' directory") + print("3. Most IDEs will automatically detect .pyi files") + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/generate_stubs_v2.py b/generate_stubs_v2.py new file mode 100644 index 0000000..5abd852 --- /dev/null +++ b/generate_stubs_v2.py @@ -0,0 +1,574 @@ +#!/usr/bin/env python3 +"""Generate .pyi type stub files for McRogueFace Python API - Version 2. + +This script creates properly formatted type stubs by manually defining +the API based on the documentation we've created. +""" + +import os +import mcrfpy + +def generate_mcrfpy_stub(): + """Generate the main mcrfpy.pyi stub file.""" + return '''"""Type stubs for McRogueFace Python API. + +Core game engine interface for creating roguelike games with Python. +""" + +from typing import Any, List, Dict, Tuple, Optional, Callable, Union, overload + +# Type aliases +UIElement = Union['Frame', 'Caption', 'Sprite', 'Grid'] +Transition = Union[str, None] + +# Classes + +class Color: + """SFML Color Object for RGBA colors.""" + + r: int + g: int + b: int + a: int + + @overload + def __init__(self) -> None: ... + @overload + def __init__(self, r: int, g: int, b: int, a: int = 255) -> None: ... + + def from_hex(self, hex_string: str) -> 'Color': + """Create color from hex string (e.g., '#FF0000' or 'FF0000').""" + ... + + def to_hex(self) -> str: + """Convert color to hex string format.""" + ... + + def lerp(self, other: 'Color', t: float) -> 'Color': + """Linear interpolation between two colors.""" + ... + +class Vector: + """SFML Vector Object for 2D coordinates.""" + + x: float + y: float + + @overload + def __init__(self) -> None: ... + @overload + def __init__(self, x: float, y: float) -> None: ... + + def add(self, other: 'Vector') -> 'Vector': ... + def subtract(self, other: 'Vector') -> 'Vector': ... + def multiply(self, scalar: float) -> 'Vector': ... + def divide(self, scalar: float) -> 'Vector': ... + def distance(self, other: 'Vector') -> float: ... + def normalize(self) -> 'Vector': ... + def dot(self, other: 'Vector') -> float: ... + +class Texture: + """SFML Texture Object for images.""" + + def __init__(self, filename: str) -> None: ... + + filename: str + width: int + height: int + sprite_count: int + +class Font: + """SFML Font Object for text rendering.""" + + def __init__(self, filename: str) -> None: ... + + filename: str + family: str + +class Drawable: + """Base class for all drawable UI elements.""" + + x: float + y: float + visible: bool + z_index: int + name: str + pos: Vector + + def get_bounds(self) -> Tuple[float, float, float, float]: + """Get bounding box as (x, y, width, height).""" + ... + + def move(self, dx: float, dy: float) -> None: + """Move by relative offset (dx, dy).""" + ... + + def resize(self, width: float, height: float) -> None: + """Resize to new dimensions (width, height).""" + ... + +class Frame(Drawable): + """Frame(x=0, y=0, w=0, h=0, fill_color=None, outline_color=None, outline=0, click=None, children=None) + + A rectangular frame UI element that can contain other drawable elements. + """ + + @overload + def __init__(self) -> None: ... + @overload + def __init__(self, x: float = 0, y: float = 0, w: float = 0, h: float = 0, + fill_color: Optional[Color] = None, outline_color: Optional[Color] = None, + outline: float = 0, click: Optional[Callable] = None, + children: Optional[List[UIElement]] = None) -> None: ... + + w: float + h: float + fill_color: Color + outline_color: Color + outline: float + click: Optional[Callable[[float, float, int], None]] + children: 'UICollection' + clip_children: bool + +class Caption(Drawable): + """Caption(text='', x=0, y=0, font=None, fill_color=None, outline_color=None, outline=0, click=None) + + A text display UI element with customizable font and styling. + """ + + @overload + def __init__(self) -> None: ... + @overload + def __init__(self, text: str = '', x: float = 0, y: float = 0, + font: Optional[Font] = None, fill_color: Optional[Color] = None, + outline_color: Optional[Color] = None, outline: float = 0, + click: Optional[Callable] = None) -> None: ... + + text: str + font: Font + fill_color: Color + outline_color: Color + outline: float + click: Optional[Callable[[float, float, int], None]] + w: float # Read-only, computed from text + h: float # Read-only, computed from text + +class Sprite(Drawable): + """Sprite(x=0, y=0, texture=None, sprite_index=0, scale=1.0, click=None) + + A sprite UI element that displays a texture or portion of a texture atlas. + """ + + @overload + def __init__(self) -> None: ... + @overload + def __init__(self, x: float = 0, y: float = 0, texture: Optional[Texture] = None, + sprite_index: int = 0, scale: float = 1.0, + click: Optional[Callable] = None) -> None: ... + + texture: Texture + sprite_index: int + scale: float + click: Optional[Callable[[float, float, int], None]] + w: float # Read-only, computed from texture + h: float # Read-only, computed from texture + +class Grid(Drawable): + """Grid(x=0, y=0, grid_size=(20, 20), texture=None, tile_width=16, tile_height=16, scale=1.0, click=None) + + A grid-based tilemap UI element for rendering tile-based levels and game worlds. + """ + + @overload + def __init__(self) -> None: ... + @overload + def __init__(self, x: float = 0, y: float = 0, grid_size: Tuple[int, int] = (20, 20), + texture: Optional[Texture] = None, tile_width: int = 16, tile_height: int = 16, + scale: float = 1.0, click: Optional[Callable] = None) -> None: ... + + grid_size: Tuple[int, int] + tile_width: int + tile_height: int + texture: Texture + scale: float + points: List[List['GridPoint']] + entities: 'EntityCollection' + background_color: Color + click: Optional[Callable[[int, int, int], None]] + + def at(self, x: int, y: int) -> 'GridPoint': + """Get grid point at tile coordinates.""" + ... + +class GridPoint: + """Grid point representing a single tile.""" + + texture_index: int + solid: bool + color: Color + +class GridPointState: + """State information for a grid point.""" + + texture_index: int + color: Color + +class Entity(Drawable): + """Entity(grid_x=0, grid_y=0, texture=None, sprite_index=0, name='') + + Game entity that lives within a Grid. + """ + + @overload + def __init__(self) -> None: ... + @overload + def __init__(self, grid_x: float = 0, grid_y: float = 0, texture: Optional[Texture] = None, + sprite_index: int = 0, name: str = '') -> None: ... + + grid_x: float + grid_y: float + texture: Texture + sprite_index: int + grid: Optional[Grid] + + def at(self, grid_x: float, grid_y: float) -> None: + """Move entity to grid position.""" + ... + + def die(self) -> None: + """Remove entity from its grid.""" + ... + + def index(self) -> int: + """Get index in parent grid's entity collection.""" + ... + +class UICollection: + """Collection of UI drawable elements (Frame, Caption, Sprite, Grid).""" + + def __len__(self) -> int: ... + def __getitem__(self, index: int) -> UIElement: ... + def __setitem__(self, index: int, value: UIElement) -> None: ... + def __delitem__(self, index: int) -> None: ... + def __contains__(self, item: UIElement) -> bool: ... + def __iter__(self) -> Any: ... + def __add__(self, other: 'UICollection') -> 'UICollection': ... + def __iadd__(self, other: 'UICollection') -> 'UICollection': ... + + def append(self, item: UIElement) -> None: ... + def extend(self, items: List[UIElement]) -> None: ... + def remove(self, item: UIElement) -> None: ... + def index(self, item: UIElement) -> int: ... + def count(self, item: UIElement) -> int: ... + +class EntityCollection: + """Collection of Entity objects.""" + + def __len__(self) -> int: ... + def __getitem__(self, index: int) -> Entity: ... + def __setitem__(self, index: int, value: Entity) -> None: ... + def __delitem__(self, index: int) -> None: ... + def __contains__(self, item: Entity) -> bool: ... + def __iter__(self) -> Any: ... + def __add__(self, other: 'EntityCollection') -> 'EntityCollection': ... + def __iadd__(self, other: 'EntityCollection') -> 'EntityCollection': ... + + def append(self, item: Entity) -> None: ... + def extend(self, items: List[Entity]) -> None: ... + def remove(self, item: Entity) -> None: ... + def index(self, item: Entity) -> int: ... + def count(self, item: Entity) -> int: ... + +class Scene: + """Base class for object-oriented scenes.""" + + name: str + + def __init__(self, name: str) -> None: ... + + def activate(self) -> None: + """Called when scene becomes active.""" + ... + + def deactivate(self) -> None: + """Called when scene becomes inactive.""" + ... + + def get_ui(self) -> UICollection: + """Get UI elements collection.""" + ... + + def on_keypress(self, key: str, pressed: bool) -> None: + """Handle keyboard events.""" + ... + + def on_click(self, x: float, y: float, button: int) -> None: + """Handle mouse clicks.""" + ... + + def on_enter(self) -> None: + """Called when entering the scene.""" + ... + + def on_exit(self) -> None: + """Called when leaving the scene.""" + ... + + def on_resize(self, width: int, height: int) -> None: + """Handle window resize events.""" + ... + + def update(self, dt: float) -> None: + """Update scene logic.""" + ... + +class Timer: + """Timer object for scheduled callbacks.""" + + name: str + interval: int + active: bool + + def __init__(self, name: str, callback: Callable[[float], None], interval: int) -> None: ... + + def pause(self) -> None: + """Pause the timer.""" + ... + + def resume(self) -> None: + """Resume the timer.""" + ... + + def cancel(self) -> None: + """Cancel and remove the timer.""" + ... + +class Window: + """Window singleton for managing the game window.""" + + resolution: Tuple[int, int] + fullscreen: bool + vsync: bool + title: str + fps_limit: int + game_resolution: Tuple[int, int] + scaling_mode: str + + @staticmethod + def get() -> 'Window': + """Get the window singleton instance.""" + ... + +class Animation: + """Animation object for animating UI properties.""" + + target: Any + property: str + duration: float + easing: str + loop: bool + on_complete: Optional[Callable] + + def __init__(self, target: Any, property: str, start_value: Any, end_value: Any, + duration: float, easing: str = 'linear', loop: bool = False, + on_complete: Optional[Callable] = None) -> None: ... + + def start(self) -> None: + """Start the animation.""" + ... + + def update(self, dt: float) -> bool: + """Update animation, returns True if still running.""" + ... + + def get_current_value(self) -> Any: + """Get the current interpolated value.""" + ... + +# Module functions + +def createSoundBuffer(filename: str) -> int: + """Load a sound effect from a file and return its buffer ID.""" + ... + +def loadMusic(filename: str) -> None: + """Load and immediately play background music from a file.""" + ... + +def setMusicVolume(volume: int) -> None: + """Set the global music volume (0-100).""" + ... + +def setSoundVolume(volume: int) -> None: + """Set the global sound effects volume (0-100).""" + ... + +def playSound(buffer_id: int) -> None: + """Play a sound effect using a previously loaded buffer.""" + ... + +def getMusicVolume() -> int: + """Get the current music volume level (0-100).""" + ... + +def getSoundVolume() -> int: + """Get the current sound effects volume level (0-100).""" + ... + +def sceneUI(scene: Optional[str] = None) -> UICollection: + """Get all UI elements for a scene.""" + ... + +def currentScene() -> str: + """Get the name of the currently active scene.""" + ... + +def setScene(scene: str, transition: Optional[str] = None, duration: float = 0.0) -> None: + """Switch to a different scene with optional transition effect.""" + ... + +def createScene(name: str) -> None: + """Create a new empty scene.""" + ... + +def keypressScene(handler: Callable[[str, bool], None]) -> None: + """Set the keyboard event handler for the current scene.""" + ... + +def setTimer(name: str, handler: Callable[[float], None], interval: int) -> None: + """Create or update a recurring timer.""" + ... + +def delTimer(name: str) -> None: + """Stop and remove a timer.""" + ... + +def exit() -> None: + """Cleanly shut down the game engine and exit the application.""" + ... + +def setScale(multiplier: float) -> None: + """Scale the game window size (deprecated - use Window.resolution).""" + ... + +def find(name: str, scene: Optional[str] = None) -> Optional[UIElement]: + """Find the first UI element with the specified name.""" + ... + +def findAll(pattern: str, scene: Optional[str] = None) -> List[UIElement]: + """Find all UI elements matching a name pattern (supports * wildcards).""" + ... + +def getMetrics() -> Dict[str, Union[int, float]]: + """Get current performance metrics.""" + ... + +# Submodule +class automation: + """Automation API for testing and scripting.""" + + @staticmethod + def screenshot(filename: str) -> bool: + """Save a screenshot to the specified file.""" + ... + + @staticmethod + def position() -> Tuple[int, int]: + """Get current mouse position as (x, y) tuple.""" + ... + + @staticmethod + def size() -> Tuple[int, int]: + """Get screen size as (width, height) tuple.""" + ... + + @staticmethod + def onScreen(x: int, y: int) -> bool: + """Check if coordinates are within screen bounds.""" + ... + + @staticmethod + def moveTo(x: int, y: int, duration: float = 0.0) -> None: + """Move mouse to absolute position.""" + ... + + @staticmethod + def moveRel(xOffset: int, yOffset: int, duration: float = 0.0) -> None: + """Move mouse relative to current position.""" + ... + + @staticmethod + def dragTo(x: int, y: int, duration: float = 0.0, button: str = 'left') -> None: + """Drag mouse to position.""" + ... + + @staticmethod + def dragRel(xOffset: int, yOffset: int, duration: float = 0.0, button: str = 'left') -> None: + """Drag mouse relative to current position.""" + ... + + @staticmethod + def click(x: Optional[int] = None, y: Optional[int] = None, clicks: int = 1, + interval: float = 0.0, button: str = 'left') -> None: + """Click mouse at position.""" + ... + + @staticmethod + def mouseDown(x: Optional[int] = None, y: Optional[int] = None, button: str = 'left') -> None: + """Press mouse button down.""" + ... + + @staticmethod + def mouseUp(x: Optional[int] = None, y: Optional[int] = None, button: str = 'left') -> None: + """Release mouse button.""" + ... + + @staticmethod + def keyDown(key: str) -> None: + """Press key down.""" + ... + + @staticmethod + def keyUp(key: str) -> None: + """Release key.""" + ... + + @staticmethod + def press(key: str) -> None: + """Press and release a key.""" + ... + + @staticmethod + def typewrite(text: str, interval: float = 0.0) -> None: + """Type text with optional interval between characters.""" + ... +''' + +def main(): + """Generate type stubs.""" + print("Generating comprehensive type stubs for McRogueFace...") + + # Create stubs directory + os.makedirs('stubs', exist_ok=True) + + # Write main stub file + with open('stubs/mcrfpy.pyi', 'w') as f: + f.write(generate_mcrfpy_stub()) + + print("Generated stubs/mcrfpy.pyi") + + # Create py.typed marker + with open('stubs/py.typed', 'w') as f: + f.write('') + + print("Created py.typed marker") + + print("\nType stubs generated successfully!") + print("\nTo use in your IDE:") + print("1. Add the 'stubs' directory to your project") + print("2. Most IDEs will automatically detect the .pyi files") + print("3. For VS Code: add to python.analysis.extraPaths in settings.json") + print("4. For PyCharm: mark 'stubs' directory as Sources Root") + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/stubs/mcrfpy.pyi b/stubs/mcrfpy.pyi new file mode 100644 index 0000000..919794b --- /dev/null +++ b/stubs/mcrfpy.pyi @@ -0,0 +1,532 @@ +"""Type stubs for McRogueFace Python API. + +Core game engine interface for creating roguelike games with Python. +""" + +from typing import Any, List, Dict, Tuple, Optional, Callable, Union, overload + +# Type aliases +UIElement = Union['Frame', 'Caption', 'Sprite', 'Grid'] +Transition = Union[str, None] + +# Classes + +class Color: + """SFML Color Object for RGBA colors.""" + + r: int + g: int + b: int + a: int + + @overload + def __init__(self) -> None: ... + @overload + def __init__(self, r: int, g: int, b: int, a: int = 255) -> None: ... + + def from_hex(self, hex_string: str) -> 'Color': + """Create color from hex string (e.g., '#FF0000' or 'FF0000').""" + ... + + def to_hex(self) -> str: + """Convert color to hex string format.""" + ... + + def lerp(self, other: 'Color', t: float) -> 'Color': + """Linear interpolation between two colors.""" + ... + +class Vector: + """SFML Vector Object for 2D coordinates.""" + + x: float + y: float + + @overload + def __init__(self) -> None: ... + @overload + def __init__(self, x: float, y: float) -> None: ... + + def add(self, other: 'Vector') -> 'Vector': ... + def subtract(self, other: 'Vector') -> 'Vector': ... + def multiply(self, scalar: float) -> 'Vector': ... + def divide(self, scalar: float) -> 'Vector': ... + def distance(self, other: 'Vector') -> float: ... + def normalize(self) -> 'Vector': ... + def dot(self, other: 'Vector') -> float: ... + +class Texture: + """SFML Texture Object for images.""" + + def __init__(self, filename: str) -> None: ... + + filename: str + width: int + height: int + sprite_count: int + +class Font: + """SFML Font Object for text rendering.""" + + def __init__(self, filename: str) -> None: ... + + filename: str + family: str + +class Drawable: + """Base class for all drawable UI elements.""" + + x: float + y: float + visible: bool + z_index: int + name: str + pos: Vector + + def get_bounds(self) -> Tuple[float, float, float, float]: + """Get bounding box as (x, y, width, height).""" + ... + + def move(self, dx: float, dy: float) -> None: + """Move by relative offset (dx, dy).""" + ... + + def resize(self, width: float, height: float) -> None: + """Resize to new dimensions (width, height).""" + ... + +class Frame(Drawable): + """Frame(x=0, y=0, w=0, h=0, fill_color=None, outline_color=None, outline=0, click=None, children=None) + + A rectangular frame UI element that can contain other drawable elements. + """ + + @overload + def __init__(self) -> None: ... + @overload + def __init__(self, x: float = 0, y: float = 0, w: float = 0, h: float = 0, + fill_color: Optional[Color] = None, outline_color: Optional[Color] = None, + outline: float = 0, click: Optional[Callable] = None, + children: Optional[List[UIElement]] = None) -> None: ... + + w: float + h: float + fill_color: Color + outline_color: Color + outline: float + click: Optional[Callable[[float, float, int], None]] + children: 'UICollection' + clip_children: bool + +class Caption(Drawable): + """Caption(text='', x=0, y=0, font=None, fill_color=None, outline_color=None, outline=0, click=None) + + A text display UI element with customizable font and styling. + """ + + @overload + def __init__(self) -> None: ... + @overload + def __init__(self, text: str = '', x: float = 0, y: float = 0, + font: Optional[Font] = None, fill_color: Optional[Color] = None, + outline_color: Optional[Color] = None, outline: float = 0, + click: Optional[Callable] = None) -> None: ... + + text: str + font: Font + fill_color: Color + outline_color: Color + outline: float + click: Optional[Callable[[float, float, int], None]] + w: float # Read-only, computed from text + h: float # Read-only, computed from text + +class Sprite(Drawable): + """Sprite(x=0, y=0, texture=None, sprite_index=0, scale=1.0, click=None) + + A sprite UI element that displays a texture or portion of a texture atlas. + """ + + @overload + def __init__(self) -> None: ... + @overload + def __init__(self, x: float = 0, y: float = 0, texture: Optional[Texture] = None, + sprite_index: int = 0, scale: float = 1.0, + click: Optional[Callable] = None) -> None: ... + + texture: Texture + sprite_index: int + scale: float + click: Optional[Callable[[float, float, int], None]] + w: float # Read-only, computed from texture + h: float # Read-only, computed from texture + +class Grid(Drawable): + """Grid(x=0, y=0, grid_size=(20, 20), texture=None, tile_width=16, tile_height=16, scale=1.0, click=None) + + A grid-based tilemap UI element for rendering tile-based levels and game worlds. + """ + + @overload + def __init__(self) -> None: ... + @overload + def __init__(self, x: float = 0, y: float = 0, grid_size: Tuple[int, int] = (20, 20), + texture: Optional[Texture] = None, tile_width: int = 16, tile_height: int = 16, + scale: float = 1.0, click: Optional[Callable] = None) -> None: ... + + grid_size: Tuple[int, int] + tile_width: int + tile_height: int + texture: Texture + scale: float + points: List[List['GridPoint']] + entities: 'EntityCollection' + background_color: Color + click: Optional[Callable[[int, int, int], None]] + + def at(self, x: int, y: int) -> 'GridPoint': + """Get grid point at tile coordinates.""" + ... + +class GridPoint: + """Grid point representing a single tile.""" + + texture_index: int + solid: bool + color: Color + +class GridPointState: + """State information for a grid point.""" + + texture_index: int + color: Color + +class Entity(Drawable): + """Entity(grid_x=0, grid_y=0, texture=None, sprite_index=0, name='') + + Game entity that lives within a Grid. + """ + + @overload + def __init__(self) -> None: ... + @overload + def __init__(self, grid_x: float = 0, grid_y: float = 0, texture: Optional[Texture] = None, + sprite_index: int = 0, name: str = '') -> None: ... + + grid_x: float + grid_y: float + texture: Texture + sprite_index: int + grid: Optional[Grid] + + def at(self, grid_x: float, grid_y: float) -> None: + """Move entity to grid position.""" + ... + + def die(self) -> None: + """Remove entity from its grid.""" + ... + + def index(self) -> int: + """Get index in parent grid's entity collection.""" + ... + +class UICollection: + """Collection of UI drawable elements (Frame, Caption, Sprite, Grid).""" + + def __len__(self) -> int: ... + def __getitem__(self, index: int) -> UIElement: ... + def __setitem__(self, index: int, value: UIElement) -> None: ... + def __delitem__(self, index: int) -> None: ... + def __contains__(self, item: UIElement) -> bool: ... + def __iter__(self) -> Any: ... + def __add__(self, other: 'UICollection') -> 'UICollection': ... + def __iadd__(self, other: 'UICollection') -> 'UICollection': ... + + def append(self, item: UIElement) -> None: ... + def extend(self, items: List[UIElement]) -> None: ... + def remove(self, item: UIElement) -> None: ... + def index(self, item: UIElement) -> int: ... + def count(self, item: UIElement) -> int: ... + +class EntityCollection: + """Collection of Entity objects.""" + + def __len__(self) -> int: ... + def __getitem__(self, index: int) -> Entity: ... + def __setitem__(self, index: int, value: Entity) -> None: ... + def __delitem__(self, index: int) -> None: ... + def __contains__(self, item: Entity) -> bool: ... + def __iter__(self) -> Any: ... + def __add__(self, other: 'EntityCollection') -> 'EntityCollection': ... + def __iadd__(self, other: 'EntityCollection') -> 'EntityCollection': ... + + def append(self, item: Entity) -> None: ... + def extend(self, items: List[Entity]) -> None: ... + def remove(self, item: Entity) -> None: ... + def index(self, item: Entity) -> int: ... + def count(self, item: Entity) -> int: ... + +class Scene: + """Base class for object-oriented scenes.""" + + name: str + + def __init__(self, name: str) -> None: ... + + def activate(self) -> None: + """Called when scene becomes active.""" + ... + + def deactivate(self) -> None: + """Called when scene becomes inactive.""" + ... + + def get_ui(self) -> UICollection: + """Get UI elements collection.""" + ... + + def on_keypress(self, key: str, pressed: bool) -> None: + """Handle keyboard events.""" + ... + + def on_click(self, x: float, y: float, button: int) -> None: + """Handle mouse clicks.""" + ... + + def on_enter(self) -> None: + """Called when entering the scene.""" + ... + + def on_exit(self) -> None: + """Called when leaving the scene.""" + ... + + def on_resize(self, width: int, height: int) -> None: + """Handle window resize events.""" + ... + + def update(self, dt: float) -> None: + """Update scene logic.""" + ... + +class Timer: + """Timer object for scheduled callbacks.""" + + name: str + interval: int + active: bool + + def __init__(self, name: str, callback: Callable[[float], None], interval: int) -> None: ... + + def pause(self) -> None: + """Pause the timer.""" + ... + + def resume(self) -> None: + """Resume the timer.""" + ... + + def cancel(self) -> None: + """Cancel and remove the timer.""" + ... + +class Window: + """Window singleton for managing the game window.""" + + resolution: Tuple[int, int] + fullscreen: bool + vsync: bool + title: str + fps_limit: int + game_resolution: Tuple[int, int] + scaling_mode: str + + @staticmethod + def get() -> 'Window': + """Get the window singleton instance.""" + ... + +class Animation: + """Animation object for animating UI properties.""" + + target: Any + property: str + duration: float + easing: str + loop: bool + on_complete: Optional[Callable] + + def __init__(self, target: Any, property: str, start_value: Any, end_value: Any, + duration: float, easing: str = 'linear', loop: bool = False, + on_complete: Optional[Callable] = None) -> None: ... + + def start(self) -> None: + """Start the animation.""" + ... + + def update(self, dt: float) -> bool: + """Update animation, returns True if still running.""" + ... + + def get_current_value(self) -> Any: + """Get the current interpolated value.""" + ... + +# Module functions + +def createSoundBuffer(filename: str) -> int: + """Load a sound effect from a file and return its buffer ID.""" + ... + +def loadMusic(filename: str) -> None: + """Load and immediately play background music from a file.""" + ... + +def setMusicVolume(volume: int) -> None: + """Set the global music volume (0-100).""" + ... + +def setSoundVolume(volume: int) -> None: + """Set the global sound effects volume (0-100).""" + ... + +def playSound(buffer_id: int) -> None: + """Play a sound effect using a previously loaded buffer.""" + ... + +def getMusicVolume() -> int: + """Get the current music volume level (0-100).""" + ... + +def getSoundVolume() -> int: + """Get the current sound effects volume level (0-100).""" + ... + +def sceneUI(scene: Optional[str] = None) -> UICollection: + """Get all UI elements for a scene.""" + ... + +def currentScene() -> str: + """Get the name of the currently active scene.""" + ... + +def setScene(scene: str, transition: Optional[str] = None, duration: float = 0.0) -> None: + """Switch to a different scene with optional transition effect.""" + ... + +def createScene(name: str) -> None: + """Create a new empty scene.""" + ... + +def keypressScene(handler: Callable[[str, bool], None]) -> None: + """Set the keyboard event handler for the current scene.""" + ... + +def setTimer(name: str, handler: Callable[[float], None], interval: int) -> None: + """Create or update a recurring timer.""" + ... + +def delTimer(name: str) -> None: + """Stop and remove a timer.""" + ... + +def exit() -> None: + """Cleanly shut down the game engine and exit the application.""" + ... + +def setScale(multiplier: float) -> None: + """Scale the game window size (deprecated - use Window.resolution).""" + ... + +def find(name: str, scene: Optional[str] = None) -> Optional[UIElement]: + """Find the first UI element with the specified name.""" + ... + +def findAll(pattern: str, scene: Optional[str] = None) -> List[UIElement]: + """Find all UI elements matching a name pattern (supports * wildcards).""" + ... + +def getMetrics() -> Dict[str, Union[int, float]]: + """Get current performance metrics.""" + ... + +# Submodule +class automation: + """Automation API for testing and scripting.""" + + @staticmethod + def screenshot(filename: str) -> bool: + """Save a screenshot to the specified file.""" + ... + + @staticmethod + def position() -> Tuple[int, int]: + """Get current mouse position as (x, y) tuple.""" + ... + + @staticmethod + def size() -> Tuple[int, int]: + """Get screen size as (width, height) tuple.""" + ... + + @staticmethod + def onScreen(x: int, y: int) -> bool: + """Check if coordinates are within screen bounds.""" + ... + + @staticmethod + def moveTo(x: int, y: int, duration: float = 0.0) -> None: + """Move mouse to absolute position.""" + ... + + @staticmethod + def moveRel(xOffset: int, yOffset: int, duration: float = 0.0) -> None: + """Move mouse relative to current position.""" + ... + + @staticmethod + def dragTo(x: int, y: int, duration: float = 0.0, button: str = 'left') -> None: + """Drag mouse to position.""" + ... + + @staticmethod + def dragRel(xOffset: int, yOffset: int, duration: float = 0.0, button: str = 'left') -> None: + """Drag mouse relative to current position.""" + ... + + @staticmethod + def click(x: Optional[int] = None, y: Optional[int] = None, clicks: int = 1, + interval: float = 0.0, button: str = 'left') -> None: + """Click mouse at position.""" + ... + + @staticmethod + def mouseDown(x: Optional[int] = None, y: Optional[int] = None, button: str = 'left') -> None: + """Press mouse button down.""" + ... + + @staticmethod + def mouseUp(x: Optional[int] = None, y: Optional[int] = None, button: str = 'left') -> None: + """Release mouse button.""" + ... + + @staticmethod + def keyDown(key: str) -> None: + """Press key down.""" + ... + + @staticmethod + def keyUp(key: str) -> None: + """Release key.""" + ... + + @staticmethod + def press(key: str) -> None: + """Press and release a key.""" + ... + + @staticmethod + def typewrite(text: str, interval: float = 0.0) -> None: + """Type text with optional interval between characters.""" + ... diff --git a/stubs/mcrfpy/__init__.pyi b/stubs/mcrfpy/__init__.pyi new file mode 100644 index 0000000..24afc5f --- /dev/null +++ b/stubs/mcrfpy/__init__.pyi @@ -0,0 +1,187 @@ +"""Type stubs for McRogueFace Python API. + +Auto-generated - do not edit directly. +""" + +from typing import Any, List, Dict, Tuple, Optional, Callable, Union + +# Module documentation +# McRogueFace Python API\n\nCore game engine interface for creating roguelike games with Python.\n\nThis module provides:\n- Scene management (createScene, setScene, currentScene)\n- UI components (Frame, Caption, Sprite, Grid)\n- Entity system for game objects\n- Audio playback (sound effects and music)\n- Timer system for scheduled events\n- Input handling\n- Performance metrics\n\nExample:\n import mcrfpy\n \n # Create a new scene\n mcrfpy.createScene('game')\n mcrfpy.setScene('game')\n \n # Add UI elements\n frame = mcrfpy.Frame(10, 10, 200, 100)\n caption = mcrfpy.Caption('Hello World', 50, 50)\n mcrfpy.sceneUI().extend([frame, caption])\n + +# Classes + +class Animation: + """Animation object for animating UI properties""" + def __init__(selftype(self)) -> None: ... + + def get_current_value(self, *args, **kwargs) -> Any: ... + def start(self, *args, **kwargs) -> Any: ... + def update(selfreturns True if still running) -> Any: ... + +class Caption: + """Caption(text='', x=0, y=0, font=None, fill_color=None, outline_color=None, outline=0, click=None)""" + def __init__(selftype(self)) -> None: ... + + def get_bounds(selfx, y, width, height) -> Any: ... + def move(selfdx, dy) -> Any: ... + def resize(selfwidth, height) -> Any: ... + +class Color: + """SFML Color Object""" + def __init__(selftype(self)) -> None: ... + + def from_hex(selfe.g., '#FF0000' or 'FF0000') -> Any: ... + def lerp(self, *args, **kwargs) -> Any: ... + def to_hex(self, *args, **kwargs) -> Any: ... + +class Drawable: + """Base class for all drawable UI elements""" + def __init__(selftype(self)) -> None: ... + + def get_bounds(selfx, y, width, height) -> Any: ... + def move(selfdx, dy) -> Any: ... + def resize(selfwidth, height) -> Any: ... + +class Entity: + """UIEntity objects""" + def __init__(selftype(self)) -> None: ... + + def at(self, *args, **kwargs) -> Any: ... + def die(self, *args, **kwargs) -> Any: ... + def get_bounds(selfx, y, width, height) -> Any: ... + def index(self, *args, **kwargs) -> Any: ... + def move(selfdx, dy) -> Any: ... + def resize(selfwidth, height) -> Any: ... + +class EntityCollection: + """Iterable, indexable collection of Entities""" + def __init__(selftype(self)) -> None: ... + + def append(self, *args, **kwargs) -> Any: ... + def count(self, *args, **kwargs) -> Any: ... + def extend(self, *args, **kwargs) -> Any: ... + def index(self, *args, **kwargs) -> Any: ... + def remove(self, *args, **kwargs) -> Any: ... + +class Font: + """SFML Font Object""" + def __init__(selftype(self)) -> None: ... + +class Frame: + """Frame(x=0, y=0, w=0, h=0, fill_color=None, outline_color=None, outline=0, click=None, children=None)""" + def __init__(selftype(self)) -> None: ... + + def get_bounds(selfx, y, width, height) -> Any: ... + def move(selfdx, dy) -> Any: ... + def resize(selfwidth, height) -> Any: ... + +class Grid: + """Grid(x=0, y=0, grid_size=(20, 20), texture=None, tile_width=16, tile_height=16, scale=1.0, click=None)""" + def __init__(selftype(self)) -> None: ... + + def at(self, *args, **kwargs) -> Any: ... + def get_bounds(selfx, y, width, height) -> Any: ... + def move(selfdx, dy) -> Any: ... + def resize(selfwidth, height) -> Any: ... + +class GridPoint: + """UIGridPoint object""" + def __init__(selftype(self)) -> None: ... + +class GridPointState: + """UIGridPointState object""" + def __init__(selftype(self)) -> None: ... + +class Scene: + """Base class for object-oriented scenes""" + def __init__(selftype(self)) -> None: ... + + def activate(self, *args, **kwargs) -> Any: ... + def get_ui(self, *args, **kwargs) -> Any: ... + def register_keyboard(selfalternative to overriding on_keypress) -> Any: ... + +class Sprite: + """Sprite(x=0, y=0, texture=None, sprite_index=0, scale=1.0, click=None)""" + def __init__(selftype(self)) -> None: ... + + def get_bounds(selfx, y, width, height) -> Any: ... + def move(selfdx, dy) -> Any: ... + def resize(selfwidth, height) -> Any: ... + +class Texture: + """SFML Texture Object""" + def __init__(selftype(self)) -> None: ... + +class Timer: + """Timer object for scheduled callbacks""" + def __init__(selftype(self)) -> None: ... + + def cancel(self, *args, **kwargs) -> Any: ... + def pause(self, *args, **kwargs) -> Any: ... + def restart(self, *args, **kwargs) -> Any: ... + def resume(self, *args, **kwargs) -> Any: ... + +class UICollection: + """Iterable, indexable collection of UI objects""" + def __init__(selftype(self)) -> None: ... + + def append(self, *args, **kwargs) -> Any: ... + def count(self, *args, **kwargs) -> Any: ... + def extend(self, *args, **kwargs) -> Any: ... + def index(self, *args, **kwargs) -> Any: ... + def remove(self, *args, **kwargs) -> Any: ... + +class UICollectionIter: + """Iterator for a collection of UI objects""" + def __init__(selftype(self)) -> None: ... + +class UIEntityCollectionIter: + """Iterator for a collection of UI objects""" + def __init__(selftype(self)) -> None: ... + +class Vector: + """SFML Vector Object""" + def __init__(selftype(self)) -> None: ... + + def angle(self, *args, **kwargs) -> Any: ... + def copy(self, *args, **kwargs) -> Any: ... + def distance_to(self, *args, **kwargs) -> Any: ... + def dot(self, *args, **kwargs) -> Any: ... + def magnitude(self, *args, **kwargs) -> Any: ... + def magnitude_squared(self, *args, **kwargs) -> Any: ... + def normalize(self, *args, **kwargs) -> Any: ... + +class Window: + """Window singleton for accessing and modifying the game window properties""" + def __init__(selftype(self)) -> None: ... + + def center(self, *args, **kwargs) -> Any: ... + def get(self, *args, **kwargs) -> Any: ... + def screenshot(self, *args, **kwargs) -> Any: ... + +# Functions + +def createScene(name: str) -> None: ... +def createSoundBuffer(filename: str) -> int: ... +def currentScene() -> str: ... +def delTimer(name: str) -> None: ... +def exit() -> None: ... +def find(name: str, scene: str = None) -> UIDrawable | None: ... +def findAll(pattern: str, scene: str = None) -> list: ... +def getMetrics() -> dict: ... +def getMusicVolume() -> int: ... +def getSoundVolume() -> int: ... +def keypressScene(handler: callable) -> None: ... +def loadMusic(filename: str) -> None: ... +def playSound(buffer_id: int) -> None: ... +def sceneUI(scene: str = None) -> list: ... +def setMusicVolume(volume: int) -> None: ... +def setScale(multiplier: float) -> None: ... +def setScene(scene: str, transition: str = None, duration: float = 0.0) -> None: ... +def setSoundVolume(volume: int) -> None: ... +def setTimer(name: str, handler: callable, interval: int) -> None: ... + +# Constants + +default_font: Any +default_texture: Any \ No newline at end of file diff --git a/stubs/mcrfpy/automation.pyi b/stubs/mcrfpy/automation.pyi new file mode 100644 index 0000000..57ed71a --- /dev/null +++ b/stubs/mcrfpy/automation.pyi @@ -0,0 +1,24 @@ +"""Type stubs for McRogueFace automation API.""" + +from typing import Optional, Tuple + +def click(x=None, y=None, clicks=1, interval=0.0, button='left') -> Any: ... +def doubleClick(x=None, y=None) -> Any: ... +def dragRel(xOffset, yOffset, duration=0.0, button='left') -> Any: ... +def dragTo(x, y, duration=0.0, button='left') -> Any: ... +def hotkey(*keys) - Press a hotkey combination (e.g., hotkey('ctrl', 'c')) -> Any: ... +def keyDown(key) -> Any: ... +def keyUp(key) -> Any: ... +def middleClick(x=None, y=None) -> Any: ... +def mouseDown(x=None, y=None, button='left') -> Any: ... +def mouseUp(x=None, y=None, button='left') -> Any: ... +def moveRel(xOffset, yOffset, duration=0.0) -> Any: ... +def moveTo(x, y, duration=0.0) -> Any: ... +def onScreen(x, y) -> Any: ... +def position() - Get current mouse position as (x, y) -> Any: ... +def rightClick(x=None, y=None) -> Any: ... +def screenshot(filename) -> Any: ... +def scroll(clicks, x=None, y=None) -> Any: ... +def size() - Get screen size as (width, height) -> Any: ... +def tripleClick(x=None, y=None) -> Any: ... +def typewrite(message, interval=0.0) -> Any: ... \ No newline at end of file diff --git a/stubs/py.typed b/stubs/py.typed new file mode 100644 index 0000000..e69de29