Compare commits
16 Commits
e41f83a5b3
...
621d719c25
| Author | SHA1 | Date |
|---|---|---|
|
|
621d719c25 | |
|
|
29aa6e62be | |
|
|
67aba5ef1f | |
|
|
6aa4625b76 | |
|
|
4c61bee512 | |
|
|
cc80964835 | |
|
|
326b692908 | |
|
|
dda5305256 | |
|
|
1f6175bfa5 | |
|
|
7f253da581 | |
|
|
fac6a9a457 | |
|
|
a8a257eefc | |
|
|
07e8207a08 | |
|
|
23d7882b93 | |
|
|
91461d0f87 | |
|
|
a08003bda4 |
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"gitea": {
|
||||||
|
"type": "stdio",
|
||||||
|
"command": "/home/john/Development/discord_for_claude/forgejo-mcp.linux.amd64",
|
||||||
|
"args": ["stdio", "--server", "https://gamedev.ffwf.net/gitea/", "--token", "f58ec698a5edee82db4960920b13d3f7d0d58d8e"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
105
CLAUDE.md
105
CLAUDE.md
|
|
@ -392,67 +392,102 @@ mcrfpy.setTimer("test", run_test, 100) # 0.1 seconds
|
||||||
|
|
||||||
## Documentation Guidelines
|
## Documentation Guidelines
|
||||||
|
|
||||||
### Inline C++ Documentation Format
|
### Documentation Macro System
|
||||||
|
|
||||||
When adding new methods or modifying existing ones in C++ source files, use this documentation format in PyMethodDef arrays:
|
**As of 2025-10-30, McRogueFace uses a macro-based documentation system** (`src/McRFPy_Doc.h`) that ensures consistent, complete docstrings across all Python bindings.
|
||||||
|
|
||||||
|
#### Include the Header
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
{"method_name", (PyCFunction)Class::method, METH_VARARGS | METH_KEYWORDS,
|
#include "McRFPy_Doc.h"
|
||||||
"method_name(arg1: type, arg2: type = default) -> return_type\n\n"
|
|
||||||
"Brief description of what the method does.\n\n"
|
|
||||||
"Args:\n"
|
|
||||||
" arg1: Description of first argument\n"
|
|
||||||
" arg2: Description of second argument (default: value)\n\n"
|
|
||||||
"Returns:\n"
|
|
||||||
" Description of return value\n\n"
|
|
||||||
"Example:\n"
|
|
||||||
" result = obj.method_name(value1, value2)\n\n"
|
|
||||||
"Note:\n"
|
|
||||||
" Any important notes or caveats"},
|
|
||||||
```
|
```
|
||||||
|
|
||||||
For properties in PyGetSetDef arrays:
|
#### Documenting Methods
|
||||||
|
|
||||||
|
For methods in PyMethodDef arrays, use `MCRF_METHOD`:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
{"method_name", (PyCFunction)Class::method, METH_VARARGS,
|
||||||
|
MCRF_METHOD(ClassName, method_name,
|
||||||
|
MCRF_SIG("(arg1: type, arg2: type)", "return_type"),
|
||||||
|
MCRF_DESC("Brief description of what the method does."),
|
||||||
|
MCRF_ARGS_START
|
||||||
|
MCRF_ARG("arg1", "Description of first argument")
|
||||||
|
MCRF_ARG("arg2", "Description of second argument")
|
||||||
|
MCRF_RETURNS("Description of return value")
|
||||||
|
MCRF_RAISES("ValueError", "Condition that raises this exception")
|
||||||
|
MCRF_NOTE("Important notes or caveats")
|
||||||
|
MCRF_LINK("docs/guide.md", "Related Documentation")
|
||||||
|
)},
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Documenting Properties
|
||||||
|
|
||||||
|
For properties in PyGetSetDef arrays, use `MCRF_PROPERTY`:
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
{"property_name", (getter)getter_func, (setter)setter_func,
|
{"property_name", (getter)getter_func, (setter)setter_func,
|
||||||
"Brief description of the property. "
|
MCRF_PROPERTY(property_name,
|
||||||
"Additional details about valid values, side effects, etc.", NULL},
|
"Brief description of the property. "
|
||||||
|
"Additional details about valid values, side effects, etc."
|
||||||
|
), NULL},
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Available Macros
|
||||||
|
|
||||||
|
- `MCRF_SIG(params, ret)` - Method signature
|
||||||
|
- `MCRF_DESC(text)` - Description paragraph
|
||||||
|
- `MCRF_ARGS_START` - Begin arguments section
|
||||||
|
- `MCRF_ARG(name, desc)` - Individual argument
|
||||||
|
- `MCRF_RETURNS(text)` - Return value description
|
||||||
|
- `MCRF_RAISES(exception, condition)` - Exception documentation
|
||||||
|
- `MCRF_NOTE(text)` - Important notes
|
||||||
|
- `MCRF_LINK(path, text)` - Reference to external documentation
|
||||||
|
|
||||||
|
#### Documentation Prose Guidelines
|
||||||
|
|
||||||
|
**Keep C++ docstrings concise** (1-2 sentences per section). For complex topics, use `MCRF_LINK` to reference external guides:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
MCRF_LINK("docs/animation-guide.md", "Animation System Tutorial")
|
||||||
|
```
|
||||||
|
|
||||||
|
**External documentation** (in `docs/`) can be verbose with examples, tutorials, and design rationale.
|
||||||
|
|
||||||
### Regenerating Documentation
|
### Regenerating Documentation
|
||||||
|
|
||||||
After modifying C++ inline documentation:
|
After modifying C++ inline documentation with MCRF_* macros:
|
||||||
|
|
||||||
1. **Rebuild the project**: `make -j$(nproc)`
|
1. **Rebuild the project**: `make -j$(nproc)`
|
||||||
|
|
||||||
2. **Generate stub files** (for IDE support):
|
2. **Generate documentation** (automatic from compiled module):
|
||||||
```bash
|
```bash
|
||||||
./build/mcrogueface --exec generate_stubs.py
|
./build/mcrogueface --headless --exec tools/generate_dynamic_docs.py
|
||||||
```
|
|
||||||
|
|
||||||
3. **Generate dynamic documentation** (recommended):
|
|
||||||
```bash
|
|
||||||
./build/mcrogueface --exec generate_dynamic_docs.py
|
|
||||||
```
|
```
|
||||||
This creates:
|
This creates:
|
||||||
- `docs/api_reference_dynamic.html`
|
- `docs/api_reference_dynamic.html`
|
||||||
- `docs/API_REFERENCE_DYNAMIC.md`
|
- `docs/API_REFERENCE_DYNAMIC.md`
|
||||||
|
|
||||||
4. **Update hardcoded documentation** (if still using old system):
|
3. **Generate stub files** (optional, for IDE support):
|
||||||
- `generate_complete_api_docs.py` - Update method dictionaries
|
```bash
|
||||||
- `generate_complete_markdown_docs.py` - Update method dictionaries
|
./build/mcrogueface --headless --exec tools/generate_stubs.py
|
||||||
|
```
|
||||||
|
Creates `.pyi` stub files for type checking and autocompletion
|
||||||
|
|
||||||
### Important Notes
|
### Important Notes
|
||||||
|
|
||||||
|
- **Single source of truth**: Documentation lives in C++ source files via MCRF_* macros
|
||||||
- **McRogueFace as Python interpreter**: Documentation scripts MUST be run using McRogueFace itself, not system Python
|
- **McRogueFace as Python interpreter**: Documentation scripts MUST be run using McRogueFace itself, not system Python
|
||||||
- **Use --exec flag**: `./build/mcrogueface --exec script.py` or `--headless --exec` for CI/automation
|
- **Use --headless --exec**: For non-interactive documentation generation
|
||||||
- **Dynamic is better**: The new `generate_dynamic_docs.py` extracts documentation directly from compiled module
|
- **Link transformation**: `MCRF_LINK` references are transformed to appropriate format (HTML, Markdown, etc.)
|
||||||
- **Keep docstrings consistent**: Follow the format above for automatic parsing
|
- **No manual dictionaries**: The old hardcoded documentation system has been removed
|
||||||
|
|
||||||
### Documentation Pipeline Architecture
|
### Documentation Pipeline Architecture
|
||||||
|
|
||||||
1. **C++ Source** → PyMethodDef/PyGetSetDef arrays with docstrings
|
1. **C++ Source** → MCRF_* macros in PyMethodDef/PyGetSetDef arrays
|
||||||
2. **Compilation** → Docstrings embedded in compiled module
|
2. **Compilation** → Macros expand to complete docstrings embedded in module
|
||||||
3. **Introspection** → Scripts use `dir()`, `getattr()`, `__doc__` to extract
|
3. **Introspection** → Scripts use `dir()`, `getattr()`, `__doc__` to extract
|
||||||
4. **Generation** → HTML/Markdown/Stub files created
|
4. **Generation** → HTML/Markdown/Stub files created with transformed links
|
||||||
|
5. **No drift**: Impossible for docs and code to disagree - they're the same file!
|
||||||
|
|
||||||
The documentation is only as good as the C++ inline docstrings!
|
The macro system ensures complete, consistent documentation across all Python bindings.
|
||||||
|
|
@ -108,7 +108,7 @@
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>McRogueFace API Reference</h1>
|
<h1>McRogueFace API Reference</h1>
|
||||||
<p><em>Generated on 2025-07-15 21:28:24</em></p>
|
<p><em>Generated on 2025-10-30 16:58:07</em></p>
|
||||||
<p><em>This documentation was dynamically generated from the compiled module.</em></p>
|
<p><em>This documentation was dynamically generated from the compiled module.</em></p>
|
||||||
|
|
||||||
<div class="toc">
|
<div class="toc">
|
||||||
|
|
@ -424,15 +424,37 @@ Attributes:
|
||||||
<h4>Methods:</h4>
|
<h4>Methods:</h4>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
<h5><code class="method-name">get_boundsGet bounding box as (x, y, width, height)</code></h5>
|
<h5><code class="method-name">get_boundsget_bounds() -> tuple</code></h5>
|
||||||
|
<p>Get the bounding rectangle of this drawable element.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Note:</p>
|
||||||
|
<p style='margin-left: 20px;'><span class='returns'>Returns:</span> tuple: (x, y, width, height) representing the element's bounds The bounds are in screen coordinates and account for current position and size.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
<h5><code class="method-name">moveMove by relative offset (dx, dy)</code></h5>
|
<h5><code class="method-name">movemove(dx: float, dy: float) -> None</code></h5>
|
||||||
|
<p>Move the element by a relative offset.
|
||||||
|
|
||||||
|
|
||||||
|
Note:</p>
|
||||||
|
<div style='margin-left: 20px;'>
|
||||||
|
<div><span class='arg-name'>dx</span>: Horizontal offset in pixels</div>
|
||||||
|
<div><span class='arg-name'>dy</span>: Vertical offset in pixels</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
<h5><code class="method-name">resizeResize to new dimensions (width, height)</code></h5>
|
<h5><code class="method-name">resizeresize(width: float, height: float) -> None</code></h5>
|
||||||
|
<p>Resize the element to new dimensions.
|
||||||
|
|
||||||
|
|
||||||
|
Note:</p>
|
||||||
|
<div style='margin-left: 20px;'>
|
||||||
|
<div><span class='arg-name'>width</span>: New width in pixels</div>
|
||||||
|
<div><span class='arg-name'>height</span>: New height in pixels</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -442,17 +464,38 @@ Attributes:
|
||||||
<h4>Methods:</h4>
|
<h4>Methods:</h4>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
<h5><code class="method-name">from_hexCreate Color from hex string (e.g., '#FF0000' or 'FF0000')</code></h5>
|
<h5><code class="method-name">from_hexfrom_hex(hex_string: str) -> Color</code></h5>
|
||||||
|
<p>Create a Color from a hexadecimal string.
|
||||||
|
|
||||||
|
|
||||||
|
Note:</p>
|
||||||
|
<div style='margin-left: 20px;'>
|
||||||
|
<div><span class='arg-name'>hex_string</span>: Hex color string (e.g., '#FF0000', 'FF0000', '#AABBCCDD' for RGBA)</div>
|
||||||
|
</div>
|
||||||
|
<p style='margin-left: 20px;'><span class='returns'>Returns:</span> Color: New Color object with values from hex string ValueError: If hex string is not 6 or 8 characters (RGB or RGBA) This is a class method. Call as Color.from_hex('#FF0000')</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
<h5><code class="method-name">lerp(...)</code></h5>
|
<h5><code class="method-name">lerplerp(other: Color, t: float) -> Color</code></h5>
|
||||||
<p>Linearly interpolate between this color and another</p>
|
<p>Linearly interpolate between this color and another.
|
||||||
|
|
||||||
|
|
||||||
|
Note:</p>
|
||||||
|
<div style='margin-left: 20px;'>
|
||||||
|
<div><span class='arg-name'>other</span>: The target Color to interpolate towards</div>
|
||||||
|
<div><span class='arg-name'>t</span>: Interpolation factor (0.0 = this color, 1.0 = other color). Automatically clamped to [0.0, 1.0]</div>
|
||||||
|
</div>
|
||||||
|
<p style='margin-left: 20px;'><span class='returns'>Returns:</span> Color: New Color representing the interpolated value All components (r, g, b, a) are interpolated independently</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
<h5><code class="method-name">to_hex(...)</code></h5>
|
<h5><code class="method-name">to_hexto_hex() -> str</code></h5>
|
||||||
<p>Convert Color to hex string</p>
|
<p>Convert this Color to a hexadecimal string.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Note:</p>
|
||||||
|
<p style='margin-left: 20px;'><span class='returns'>Returns:</span> str: Hex string in format '#RRGGBB' or '#RRGGBBAA' (if alpha < 255) Alpha component is only included if not fully opaque (< 255)</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -462,15 +505,37 @@ Attributes:
|
||||||
<h4>Methods:</h4>
|
<h4>Methods:</h4>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
<h5><code class="method-name">get_boundsGet bounding box as (x, y, width, height)</code></h5>
|
<h5><code class="method-name">get_boundsget_bounds() -> tuple</code></h5>
|
||||||
|
<p>Get the bounding rectangle of this drawable element.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Note:</p>
|
||||||
|
<p style='margin-left: 20px;'><span class='returns'>Returns:</span> tuple: (x, y, width, height) representing the element's bounds The bounds are in screen coordinates and account for current position and size.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
<h5><code class="method-name">moveMove by relative offset (dx, dy)</code></h5>
|
<h5><code class="method-name">movemove(dx: float, dy: float) -> None</code></h5>
|
||||||
|
<p>Move the element by a relative offset.
|
||||||
|
|
||||||
|
|
||||||
|
Note:</p>
|
||||||
|
<div style='margin-left: 20px;'>
|
||||||
|
<div><span class='arg-name'>dx</span>: Horizontal offset in pixels</div>
|
||||||
|
<div><span class='arg-name'>dy</span>: Vertical offset in pixels</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
<h5><code class="method-name">resizeResize to new dimensions (width, height)</code></h5>
|
<h5><code class="method-name">resizeresize(width: float, height: float) -> None</code></h5>
|
||||||
|
<p>Resize the element to new dimensions.
|
||||||
|
|
||||||
|
|
||||||
|
Note:</p>
|
||||||
|
<div style='margin-left: 20px;'>
|
||||||
|
<div><span class='arg-name'>width</span>: New width in pixels</div>
|
||||||
|
<div><span class='arg-name'>height</span>: New height in pixels</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -514,7 +579,13 @@ Attributes:
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
<h5><code class="method-name">get_boundsGet bounding box as (x, y, width, height)</code></h5>
|
<h5><code class="method-name">get_boundsget_bounds() -> tuple</code></h5>
|
||||||
|
<p>Get the bounding rectangle of this drawable element.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Note:</p>
|
||||||
|
<p style='margin-left: 20px;'><span class='returns'>Returns:</span> tuple: (x, y, width, height) representing the element's bounds The bounds are in screen coordinates and account for current position and size.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
|
|
@ -523,7 +594,15 @@ Attributes:
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
<h5><code class="method-name">moveMove by relative offset (dx, dy)</code></h5>
|
<h5><code class="method-name">movemove(dx: float, dy: float) -> None</code></h5>
|
||||||
|
<p>Move the element by a relative offset.
|
||||||
|
|
||||||
|
|
||||||
|
Note:</p>
|
||||||
|
<div style='margin-left: 20px;'>
|
||||||
|
<div><span class='arg-name'>dx</span>: Horizontal offset in pixels</div>
|
||||||
|
<div><span class='arg-name'>dy</span>: Vertical offset in pixels</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
|
|
@ -537,7 +616,15 @@ Attributes:
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
<h5><code class="method-name">resizeResize to new dimensions (width, height)</code></h5>
|
<h5><code class="method-name">resizeresize(width: float, height: float) -> None</code></h5>
|
||||||
|
<p>Resize the element to new dimensions.
|
||||||
|
|
||||||
|
|
||||||
|
Note:</p>
|
||||||
|
<div style='margin-left: 20px;'>
|
||||||
|
<div><span class='arg-name'>width</span>: New width in pixels</div>
|
||||||
|
<div><span class='arg-name'>height</span>: New height in pixels</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
|
|
@ -625,15 +712,37 @@ Attributes:
|
||||||
<h4>Methods:</h4>
|
<h4>Methods:</h4>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
<h5><code class="method-name">get_boundsGet bounding box as (x, y, width, height)</code></h5>
|
<h5><code class="method-name">get_boundsget_bounds() -> tuple</code></h5>
|
||||||
|
<p>Get the bounding rectangle of this drawable element.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Note:</p>
|
||||||
|
<p style='margin-left: 20px;'><span class='returns'>Returns:</span> tuple: (x, y, width, height) representing the element's bounds The bounds are in screen coordinates and account for current position and size.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
<h5><code class="method-name">moveMove by relative offset (dx, dy)</code></h5>
|
<h5><code class="method-name">movemove(dx: float, dy: float) -> None</code></h5>
|
||||||
|
<p>Move the element by a relative offset.
|
||||||
|
|
||||||
|
|
||||||
|
Note:</p>
|
||||||
|
<div style='margin-left: 20px;'>
|
||||||
|
<div><span class='arg-name'>dx</span>: Horizontal offset in pixels</div>
|
||||||
|
<div><span class='arg-name'>dy</span>: Vertical offset in pixels</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
<h5><code class="method-name">resizeResize to new dimensions (width, height)</code></h5>
|
<h5><code class="method-name">resizeresize(width: float, height: float) -> None</code></h5>
|
||||||
|
<p>Resize the element to new dimensions.
|
||||||
|
|
||||||
|
|
||||||
|
Note:</p>
|
||||||
|
<div style='margin-left: 20px;'>
|
||||||
|
<div><span class='arg-name'>width</span>: New width in pixels</div>
|
||||||
|
<div><span class='arg-name'>height</span>: New height in pixels</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -717,8 +826,8 @@ Attributes:
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
<h5><code class="method-name">compute_fovcompute_fov(x: int, y: int, radius: int = 0, light_walls: bool = True, algorithm: int = FOV_BASIC) -> None</code></h5>
|
<h5><code class="method-name">compute_fovcompute_fov(x: int, y: int, radius: int = 0, light_walls: bool = True, algorithm: int = FOV_BASIC) -> List[Tuple[int, int, bool, bool]]</code></h5>
|
||||||
<p>Compute field of view from a position.</p>
|
<p>Compute field of view from a position and return visible cells.</p>
|
||||||
<div style='margin-left: 20px;'>
|
<div style='margin-left: 20px;'>
|
||||||
<div><span class='arg-name'>x</span>: X coordinate of the viewer</div>
|
<div><span class='arg-name'>x</span>: X coordinate of the viewer</div>
|
||||||
<div><span class='arg-name'>y</span>: Y coordinate of the viewer</div>
|
<div><span class='arg-name'>y</span>: Y coordinate of the viewer</div>
|
||||||
|
|
@ -726,6 +835,7 @@ Attributes:
|
||||||
<div><span class='arg-name'>light_walls</span>: Whether walls are lit when visible</div>
|
<div><span class='arg-name'>light_walls</span>: Whether walls are lit when visible</div>
|
||||||
<div><span class='arg-name'>algorithm</span>: FOV algorithm to use (FOV_BASIC, FOV_DIAMOND, FOV_SHADOW, FOV_PERMISSIVE_0-8)</div>
|
<div><span class='arg-name'>algorithm</span>: FOV algorithm to use (FOV_BASIC, FOV_DIAMOND, FOV_SHADOW, FOV_PERMISSIVE_0-8)</div>
|
||||||
</div>
|
</div>
|
||||||
|
<p style='margin-left: 20px;'><span class='returns'>Returns:</span> List of tuples (x, y, visible, discovered) for all visible cells: - x, y: Grid coordinates - visible: True (all returned cells are visible) - discovered: True (FOV implies discovery)</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
|
|
@ -742,7 +852,13 @@ Attributes:
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
<h5><code class="method-name">get_boundsGet bounding box as (x, y, width, height)</code></h5>
|
<h5><code class="method-name">get_boundsget_bounds() -> tuple</code></h5>
|
||||||
|
<p>Get the bounding rectangle of this drawable element.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Note:</p>
|
||||||
|
<p style='margin-left: 20px;'><span class='returns'>Returns:</span> tuple: (x, y, width, height) representing the element's bounds The bounds are in screen coordinates and account for current position and size.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
|
|
@ -776,11 +892,27 @@ Attributes:
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
<h5><code class="method-name">moveMove by relative offset (dx, dy)</code></h5>
|
<h5><code class="method-name">movemove(dx: float, dy: float) -> None</code></h5>
|
||||||
|
<p>Move the element by a relative offset.
|
||||||
|
|
||||||
|
|
||||||
|
Note:</p>
|
||||||
|
<div style='margin-left: 20px;'>
|
||||||
|
<div><span class='arg-name'>dx</span>: Horizontal offset in pixels</div>
|
||||||
|
<div><span class='arg-name'>dy</span>: Vertical offset in pixels</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
<h5><code class="method-name">resizeResize to new dimensions (width, height)</code></h5>
|
<h5><code class="method-name">resizeresize(width: float, height: float) -> None</code></h5>
|
||||||
|
<p>Resize the element to new dimensions.
|
||||||
|
|
||||||
|
|
||||||
|
Note:</p>
|
||||||
|
<div style='margin-left: 20px;'>
|
||||||
|
<div><span class='arg-name'>width</span>: New width in pixels</div>
|
||||||
|
<div><span class='arg-name'>height</span>: New height in pixels</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -856,15 +988,37 @@ Attributes:
|
||||||
<h4>Methods:</h4>
|
<h4>Methods:</h4>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
<h5><code class="method-name">get_boundsGet bounding box as (x, y, width, height)</code></h5>
|
<h5><code class="method-name">get_boundsget_bounds() -> tuple</code></h5>
|
||||||
|
<p>Get the bounding rectangle of this drawable element.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Note:</p>
|
||||||
|
<p style='margin-left: 20px;'><span class='returns'>Returns:</span> tuple: (x, y, width, height) representing the element's bounds The bounds are in screen coordinates and account for current position and size.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
<h5><code class="method-name">moveMove by relative offset (dx, dy)</code></h5>
|
<h5><code class="method-name">movemove(dx: float, dy: float) -> None</code></h5>
|
||||||
|
<p>Move the element by a relative offset.
|
||||||
|
|
||||||
|
|
||||||
|
Note:</p>
|
||||||
|
<div style='margin-left: 20px;'>
|
||||||
|
<div><span class='arg-name'>dx</span>: Horizontal offset in pixels</div>
|
||||||
|
<div><span class='arg-name'>dy</span>: Vertical offset in pixels</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
<h5><code class="method-name">resizeResize to new dimensions (width, height)</code></h5>
|
<h5><code class="method-name">resizeresize(width: float, height: float) -> None</code></h5>
|
||||||
|
<p>Resize the element to new dimensions.
|
||||||
|
|
||||||
|
|
||||||
|
Note:</p>
|
||||||
|
<div style='margin-left: 20px;'>
|
||||||
|
<div><span class='arg-name'>width</span>: New width in pixels</div>
|
||||||
|
<div><span class='arg-name'>height</span>: New height in pixels</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -915,25 +1069,41 @@ Example:
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
<h5><code class="method-name">cancelcancel() -> None</code></h5>
|
<h5><code class="method-name">cancelcancel() -> None</code></h5>
|
||||||
<p>Cancel the timer and remove it from the timer system.
|
<p>Cancel the timer and remove it from the timer system.
|
||||||
The timer will no longer fire and cannot be restarted.</p>
|
|
||||||
|
|
||||||
|
|
||||||
|
Note:</p>
|
||||||
|
<p style='margin-left: 20px;'><span class='returns'>Returns:</span> None The timer will no longer fire and cannot be restarted. The callback will not be called again.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
<h5><code class="method-name">pausepause() -> None</code></h5>
|
<h5><code class="method-name">pausepause() -> None</code></h5>
|
||||||
<p>Pause the timer, preserving the time remaining until next trigger.
|
<p>Pause the timer, preserving the time remaining until next trigger.
|
||||||
The timer can be resumed later with resume().</p>
|
|
||||||
|
|
||||||
|
|
||||||
|
Note:</p>
|
||||||
|
<p style='margin-left: 20px;'><span class='returns'>Returns:</span> None The timer can be resumed later with resume(). Time spent paused does not count toward the interval.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
<h5><code class="method-name">restartrestart() -> None</code></h5>
|
<h5><code class="method-name">restartrestart() -> None</code></h5>
|
||||||
<p>Restart the timer from the beginning.
|
<p>Restart the timer from the beginning.
|
||||||
Resets the timer to fire after a full interval from now.</p>
|
|
||||||
|
|
||||||
|
|
||||||
|
Note:</p>
|
||||||
|
<p style='margin-left: 20px;'><span class='returns'>Returns:</span> None Resets the timer to fire after a full interval from now, regardless of remaining time.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
<h5><code class="method-name">resumeresume() -> None</code></h5>
|
<h5><code class="method-name">resumeresume() -> None</code></h5>
|
||||||
<p>Resume a paused timer from where it left off.
|
<p>Resume a paused timer from where it left off.
|
||||||
Has no effect if the timer is not paused.</p>
|
|
||||||
|
|
||||||
|
|
||||||
|
Note:</p>
|
||||||
|
<p style='margin-left: 20px;'><span class='returns'>Returns:</span> None Has no effect if the timer is not paused. Timer will fire after the remaining time elapses.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -981,38 +1151,59 @@ Has no effect if the timer is not paused.</p>
|
||||||
<h4>Methods:</h4>
|
<h4>Methods:</h4>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
<h5><code class="method-name">angle(...)</code></h5>
|
<h5><code class="method-name">angleangle() -> float</code></h5>
|
||||||
<p>Return the angle in radians from the positive X axis</p>
|
<p>Get the angle of this vector in radians.</p>
|
||||||
|
<p style='margin-left: 20px;'><span class='returns'>Returns:</span> float: Angle in radians from positive x-axis</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
<h5><code class="method-name">copy(...)</code></h5>
|
<h5><code class="method-name">copycopy() -> Vector</code></h5>
|
||||||
<p>Return a copy of this vector</p>
|
<p>Create a copy of this vector.</p>
|
||||||
|
<p style='margin-left: 20px;'><span class='returns'>Returns:</span> Vector: New Vector object with same x and y values</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
<h5><code class="method-name">distance_to(...)</code></h5>
|
<h5><code class="method-name">distance_todistance_to(other: Vector) -> float</code></h5>
|
||||||
<p>Return the distance to another vector</p>
|
<p>Calculate the distance to another vector.</p>
|
||||||
|
<div style='margin-left: 20px;'>
|
||||||
|
<div><span class='arg-name'>other</span>: The other vector</div>
|
||||||
|
</div>
|
||||||
|
<p style='margin-left: 20px;'><span class='returns'>Returns:</span> float: Distance between the two vectors</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
<h5><code class="method-name">dot(...)</code></h5>
|
<h5><code class="method-name">dotdot(other: Vector) -> float</code></h5>
|
||||||
<p>Return the dot product with another vector</p>
|
<p>Calculate the dot product with another vector.</p>
|
||||||
|
<div style='margin-left: 20px;'>
|
||||||
|
<div><span class='arg-name'>other</span>: The other vector</div>
|
||||||
|
</div>
|
||||||
|
<p style='margin-left: 20px;'><span class='returns'>Returns:</span> float: Dot product of the two vectors</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
<h5><code class="method-name">magnitude(...)</code></h5>
|
<h5><code class="method-name">magnitudemagnitude() -> float</code></h5>
|
||||||
<p>Return the length of the vector</p>
|
<p>Calculate the length/magnitude of this vector.</p>
|
||||||
|
<p style='margin-left: 20px;'><span class='returns'>Returns:</span> float: The magnitude of the vector</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
<h5><code class="method-name">magnitude_squared(...)</code></h5>
|
<h5><code class="method-name">magnitude_squaredmagnitude_squared() -> float</code></h5>
|
||||||
<p>Return the squared length of the vector</p>
|
<p>Calculate the squared magnitude of this vector.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Note:</p>
|
||||||
|
<p style='margin-left: 20px;'><span class='returns'>Returns:</span> float: The squared magnitude (faster than magnitude()) Use this for comparisons to avoid expensive square root calculation.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
<h5><code class="method-name">normalize(...)</code></h5>
|
<h5><code class="method-name">normalizenormalize() -> Vector</code></h5>
|
||||||
<p>Return a unit vector in the same direction</p>
|
<p>Return a unit vector in the same direction.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Note:</p>
|
||||||
|
<p style='margin-left: 20px;'><span class='returns'>Returns:</span> Vector: New normalized vector with magnitude 1.0 For zero vectors (magnitude 0.0), returns a zero vector rather than raising an exception</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -373,7 +373,7 @@ class Engine:
|
||||||
|
|
||||||
self.ui = mcrfpy.sceneUI("game")
|
self.ui = mcrfpy.sceneUI("game")
|
||||||
|
|
||||||
background = mcrfpy.Frame(0, 0, 1024, 768)
|
background = mcrfpy.Frame((0, 0), (1024, 768))
|
||||||
background.fill_color = mcrfpy.Color(0, 0, 0)
|
background.fill_color = mcrfpy.Color(0, 0, 0)
|
||||||
self.ui.append(background)
|
self.ui.append(background)
|
||||||
|
|
||||||
|
|
@ -565,4 +565,4 @@ class Engine:
|
||||||
# Create and run the game
|
# Create and run the game
|
||||||
engine = Engine()
|
engine = Engine()
|
||||||
print("Part 6: Combat System!")
|
print("Part 6: Combat System!")
|
||||||
print("Attack enemies to defeat them, but watch your HP!")
|
print("Attack enemies to defeat them, but watch your HP!")
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
#include "McRFPy_API.h"
|
#include "McRFPy_API.h"
|
||||||
#include "McRFPy_Automation.h"
|
#include "McRFPy_Automation.h"
|
||||||
#include "McRFPy_Libtcod.h"
|
#include "McRFPy_Libtcod.h"
|
||||||
|
#include "McRFPy_Doc.h"
|
||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
#include "PyAnimation.h"
|
#include "PyAnimation.h"
|
||||||
#include "PyDrawable.h"
|
#include "PyDrawable.h"
|
||||||
|
|
@ -27,188 +28,201 @@ PyObject* McRFPy_API::mcrf_module;
|
||||||
static PyMethodDef mcrfpyMethods[] = {
|
static PyMethodDef mcrfpyMethods[] = {
|
||||||
|
|
||||||
{"createSoundBuffer", McRFPy_API::_createSoundBuffer, METH_VARARGS,
|
{"createSoundBuffer", McRFPy_API::_createSoundBuffer, METH_VARARGS,
|
||||||
"createSoundBuffer(filename: str) -> int\n\n"
|
MCRF_FUNCTION(createSoundBuffer,
|
||||||
"Load a sound effect from a file and return its buffer ID.\n\n"
|
MCRF_SIG("(filename: str)", "int"),
|
||||||
"Args:\n"
|
MCRF_DESC("Load a sound effect from a file and return its buffer ID."),
|
||||||
" filename: Path to the sound file (WAV, OGG, FLAC)\n\n"
|
MCRF_ARGS_START
|
||||||
"Returns:\n"
|
MCRF_ARG("filename", "Path to the sound file (WAV, OGG, FLAC)")
|
||||||
" int: Buffer ID for use with playSound()\n\n"
|
MCRF_RETURNS("int: Buffer ID for use with playSound()")
|
||||||
"Raises:\n"
|
MCRF_RAISES("RuntimeError", "If the file cannot be loaded")
|
||||||
" RuntimeError: If the file cannot be loaded"},
|
)},
|
||||||
{"loadMusic", McRFPy_API::_loadMusic, METH_VARARGS,
|
{"loadMusic", McRFPy_API::_loadMusic, METH_VARARGS,
|
||||||
"loadMusic(filename: str) -> None\n\n"
|
MCRF_FUNCTION(loadMusic,
|
||||||
"Load and immediately play background music from a file.\n\n"
|
MCRF_SIG("(filename: str)", "None"),
|
||||||
"Args:\n"
|
MCRF_DESC("Load and immediately play background music from a file."),
|
||||||
" filename: Path to the music file (WAV, OGG, FLAC)\n\n"
|
MCRF_ARGS_START
|
||||||
"Note:\n"
|
MCRF_ARG("filename", "Path to the music file (WAV, OGG, FLAC)")
|
||||||
" Only one music track can play at a time. Loading new music stops the current track."},
|
MCRF_RETURNS("None")
|
||||||
|
MCRF_NOTE("Only one music track can play at a time. Loading new music stops the current track.")
|
||||||
|
)},
|
||||||
{"setMusicVolume", McRFPy_API::_setMusicVolume, METH_VARARGS,
|
{"setMusicVolume", McRFPy_API::_setMusicVolume, METH_VARARGS,
|
||||||
"setMusicVolume(volume: int) -> None\n\n"
|
MCRF_FUNCTION(setMusicVolume,
|
||||||
"Set the global music volume.\n\n"
|
MCRF_SIG("(volume: int)", "None"),
|
||||||
"Args:\n"
|
MCRF_DESC("Set the global music volume."),
|
||||||
" volume: Volume level from 0 (silent) to 100 (full volume)"},
|
MCRF_ARGS_START
|
||||||
|
MCRF_ARG("volume", "Volume level from 0 (silent) to 100 (full volume)")
|
||||||
|
MCRF_RETURNS("None")
|
||||||
|
)},
|
||||||
{"setSoundVolume", McRFPy_API::_setSoundVolume, METH_VARARGS,
|
{"setSoundVolume", McRFPy_API::_setSoundVolume, METH_VARARGS,
|
||||||
"setSoundVolume(volume: int) -> None\n\n"
|
MCRF_FUNCTION(setSoundVolume,
|
||||||
"Set the global sound effects volume.\n\n"
|
MCRF_SIG("(volume: int)", "None"),
|
||||||
"Args:\n"
|
MCRF_DESC("Set the global sound effects volume."),
|
||||||
" volume: Volume level from 0 (silent) to 100 (full volume)"},
|
MCRF_ARGS_START
|
||||||
|
MCRF_ARG("volume", "Volume level from 0 (silent) to 100 (full volume)")
|
||||||
|
MCRF_RETURNS("None")
|
||||||
|
)},
|
||||||
{"playSound", McRFPy_API::_playSound, METH_VARARGS,
|
{"playSound", McRFPy_API::_playSound, METH_VARARGS,
|
||||||
"playSound(buffer_id: int) -> None\n\n"
|
MCRF_FUNCTION(playSound,
|
||||||
"Play a sound effect using a previously loaded buffer.\n\n"
|
MCRF_SIG("(buffer_id: int)", "None"),
|
||||||
"Args:\n"
|
MCRF_DESC("Play a sound effect using a previously loaded buffer."),
|
||||||
" buffer_id: Sound buffer ID returned by createSoundBuffer()\n\n"
|
MCRF_ARGS_START
|
||||||
"Raises:\n"
|
MCRF_ARG("buffer_id", "Sound buffer ID returned by createSoundBuffer()")
|
||||||
" RuntimeError: If the buffer ID is invalid"},
|
MCRF_RETURNS("None")
|
||||||
|
MCRF_RAISES("RuntimeError", "If the buffer ID is invalid")
|
||||||
|
)},
|
||||||
{"getMusicVolume", McRFPy_API::_getMusicVolume, METH_NOARGS,
|
{"getMusicVolume", McRFPy_API::_getMusicVolume, METH_NOARGS,
|
||||||
"getMusicVolume() -> int\n\n"
|
MCRF_FUNCTION(getMusicVolume,
|
||||||
"Get the current music volume level.\n\n"
|
MCRF_SIG("()", "int"),
|
||||||
"Returns:\n"
|
MCRF_DESC("Get the current music volume level."),
|
||||||
" int: Current volume (0-100)"},
|
MCRF_RETURNS("int: Current volume (0-100)")
|
||||||
|
)},
|
||||||
{"getSoundVolume", McRFPy_API::_getSoundVolume, METH_NOARGS,
|
{"getSoundVolume", McRFPy_API::_getSoundVolume, METH_NOARGS,
|
||||||
"getSoundVolume() -> int\n\n"
|
MCRF_FUNCTION(getSoundVolume,
|
||||||
"Get the current sound effects volume level.\n\n"
|
MCRF_SIG("()", "int"),
|
||||||
"Returns:\n"
|
MCRF_DESC("Get the current sound effects volume level."),
|
||||||
" int: Current volume (0-100)"},
|
MCRF_RETURNS("int: Current volume (0-100)")
|
||||||
|
)},
|
||||||
|
|
||||||
{"sceneUI", McRFPy_API::_sceneUI, METH_VARARGS,
|
{"sceneUI", McRFPy_API::_sceneUI, METH_VARARGS,
|
||||||
"sceneUI(scene: str = None) -> list\n\n"
|
MCRF_FUNCTION(sceneUI,
|
||||||
"Get all UI elements for a scene.\n\n"
|
MCRF_SIG("(scene: str = None)", "list"),
|
||||||
"Args:\n"
|
MCRF_DESC("Get all UI elements for a scene."),
|
||||||
" scene: Scene name. If None, uses current scene\n\n"
|
MCRF_ARGS_START
|
||||||
"Returns:\n"
|
MCRF_ARG("scene", "Scene name. If None, uses current scene")
|
||||||
" list: All UI elements (Frame, Caption, Sprite, Grid) in the scene\n\n"
|
MCRF_RETURNS("list: All UI elements (Frame, Caption, Sprite, Grid) in the scene")
|
||||||
"Raises:\n"
|
MCRF_RAISES("KeyError", "If the specified scene doesn't exist")
|
||||||
" KeyError: If the specified scene doesn't exist"},
|
)},
|
||||||
|
|
||||||
{"currentScene", McRFPy_API::_currentScene, METH_NOARGS,
|
{"currentScene", McRFPy_API::_currentScene, METH_NOARGS,
|
||||||
"currentScene() -> str\n\n"
|
MCRF_FUNCTION(currentScene,
|
||||||
"Get the name of the currently active scene.\n\n"
|
MCRF_SIG("()", "str"),
|
||||||
"Returns:\n"
|
MCRF_DESC("Get the name of the currently active scene."),
|
||||||
" str: Name of the current scene"},
|
MCRF_RETURNS("str: Name of the current scene")
|
||||||
|
)},
|
||||||
{"setScene", McRFPy_API::_setScene, METH_VARARGS,
|
{"setScene", McRFPy_API::_setScene, METH_VARARGS,
|
||||||
"setScene(scene: str, transition: str = None, duration: float = 0.0) -> None\n\n"
|
MCRF_FUNCTION(setScene,
|
||||||
"Switch to a different scene with optional transition effect.\n\n"
|
MCRF_SIG("(scene: str, transition: str = None, duration: float = 0.0)", "None"),
|
||||||
"Args:\n"
|
MCRF_DESC("Switch to a different scene with optional transition effect."),
|
||||||
" scene: Name of the scene to switch to\n"
|
MCRF_ARGS_START
|
||||||
" transition: Transition type ('fade', 'slide_left', 'slide_right', 'slide_up', 'slide_down')\n"
|
MCRF_ARG("scene", "Name of the scene to switch to")
|
||||||
" duration: Transition duration in seconds (default: 0.0 for instant)\n\n"
|
MCRF_ARG("transition", "Transition type ('fade', 'slide_left', 'slide_right', 'slide_up', 'slide_down')")
|
||||||
"Raises:\n"
|
MCRF_ARG("duration", "Transition duration in seconds (default: 0.0 for instant)")
|
||||||
" KeyError: If the scene doesn't exist\n"
|
MCRF_RETURNS("None")
|
||||||
" ValueError: If the transition type is invalid"},
|
MCRF_RAISES("KeyError", "If the scene doesn't exist")
|
||||||
|
MCRF_RAISES("ValueError", "If the transition type is invalid")
|
||||||
|
)},
|
||||||
{"createScene", McRFPy_API::_createScene, METH_VARARGS,
|
{"createScene", McRFPy_API::_createScene, METH_VARARGS,
|
||||||
"createScene(name: str) -> None\n\n"
|
MCRF_FUNCTION(createScene,
|
||||||
"Create a new empty scene.\n\n"
|
MCRF_SIG("(name: str)", "None"),
|
||||||
"Args:\n"
|
MCRF_DESC("Create a new empty scene."),
|
||||||
" name: Unique name for the new scene\n\n"
|
MCRF_ARGS_START
|
||||||
"Raises:\n"
|
MCRF_ARG("name", "Unique name for the new scene")
|
||||||
" ValueError: If a scene with this name already exists\n\n"
|
MCRF_RETURNS("None")
|
||||||
"Note:\n"
|
MCRF_RAISES("ValueError", "If a scene with this name already exists")
|
||||||
" The scene is created but not made active. Use setScene() to switch to it."},
|
MCRF_NOTE("The scene is created but not made active. Use setScene() to switch to it.")
|
||||||
|
)},
|
||||||
{"keypressScene", McRFPy_API::_keypressScene, METH_VARARGS,
|
{"keypressScene", McRFPy_API::_keypressScene, METH_VARARGS,
|
||||||
"keypressScene(handler: callable) -> None\n\n"
|
MCRF_FUNCTION(keypressScene,
|
||||||
"Set the keyboard event handler for the current scene.\n\n"
|
MCRF_SIG("(handler: callable)", "None"),
|
||||||
"Args:\n"
|
MCRF_DESC("Set the keyboard event handler for the current scene."),
|
||||||
" handler: Callable that receives (key_name: str, is_pressed: bool)\n\n"
|
MCRF_ARGS_START
|
||||||
"Example:\n"
|
MCRF_ARG("handler", "Callable that receives (key_name: str, is_pressed: bool)")
|
||||||
" def on_key(key, pressed):\n"
|
MCRF_RETURNS("None")
|
||||||
" if key == 'A' and pressed:\n"
|
MCRF_NOTE("Example: def on_key(key, pressed): if key == 'A' and pressed: print('A key pressed') mcrfpy.keypressScene(on_key)")
|
||||||
" print('A key pressed')\n"
|
)},
|
||||||
" mcrfpy.keypressScene(on_key)"},
|
|
||||||
|
|
||||||
{"setTimer", McRFPy_API::_setTimer, METH_VARARGS,
|
{"setTimer", McRFPy_API::_setTimer, METH_VARARGS,
|
||||||
"setTimer(name: str, handler: callable, interval: int) -> None\n\n"
|
MCRF_FUNCTION(setTimer,
|
||||||
"Create or update a recurring timer.\n\n"
|
MCRF_SIG("(name: str, handler: callable, interval: int)", "None"),
|
||||||
"Args:\n"
|
MCRF_DESC("Create or update a recurring timer."),
|
||||||
" name: Unique identifier for the timer\n"
|
MCRF_ARGS_START
|
||||||
" handler: Function called with (runtime: float) parameter\n"
|
MCRF_ARG("name", "Unique identifier for the timer")
|
||||||
" interval: Time between calls in milliseconds\n\n"
|
MCRF_ARG("handler", "Function called with (runtime: float) parameter")
|
||||||
"Note:\n"
|
MCRF_ARG("interval", "Time between calls in milliseconds")
|
||||||
" If a timer with this name exists, it will be replaced.\n"
|
MCRF_RETURNS("None")
|
||||||
" The handler receives the total runtime in seconds as its argument."},
|
MCRF_NOTE("If a timer with this name exists, it will be replaced. The handler receives the total runtime in seconds as its argument.")
|
||||||
|
)},
|
||||||
{"delTimer", McRFPy_API::_delTimer, METH_VARARGS,
|
{"delTimer", McRFPy_API::_delTimer, METH_VARARGS,
|
||||||
"delTimer(name: str) -> None\n\n"
|
MCRF_FUNCTION(delTimer,
|
||||||
"Stop and remove a timer.\n\n"
|
MCRF_SIG("(name: str)", "None"),
|
||||||
"Args:\n"
|
MCRF_DESC("Stop and remove a timer."),
|
||||||
" name: Timer identifier to remove\n\n"
|
MCRF_ARGS_START
|
||||||
"Note:\n"
|
MCRF_ARG("name", "Timer identifier to remove")
|
||||||
" No error is raised if the timer doesn't exist."},
|
MCRF_RETURNS("None")
|
||||||
|
MCRF_NOTE("No error is raised if the timer doesn't exist.")
|
||||||
|
)},
|
||||||
{"exit", McRFPy_API::_exit, METH_NOARGS,
|
{"exit", McRFPy_API::_exit, METH_NOARGS,
|
||||||
"exit() -> None\n\n"
|
MCRF_FUNCTION(exit,
|
||||||
"Cleanly shut down the game engine and exit the application.\n\n"
|
MCRF_SIG("()", "None"),
|
||||||
"Note:\n"
|
MCRF_DESC("Cleanly shut down the game engine and exit the application."),
|
||||||
" This immediately closes the window and terminates the program."},
|
MCRF_RETURNS("None")
|
||||||
|
MCRF_NOTE("This immediately closes the window and terminates the program.")
|
||||||
|
)},
|
||||||
{"setScale", McRFPy_API::_setScale, METH_VARARGS,
|
{"setScale", McRFPy_API::_setScale, METH_VARARGS,
|
||||||
"setScale(multiplier: float) -> None\n\n"
|
MCRF_FUNCTION(setScale,
|
||||||
"Scale the game window size.\n\n"
|
MCRF_SIG("(multiplier: float)", "None"),
|
||||||
"Args:\n"
|
MCRF_DESC("Scale the game window size."),
|
||||||
" multiplier: Scale factor (e.g., 2.0 for double size)\n\n"
|
MCRF_ARGS_START
|
||||||
"Note:\n"
|
MCRF_ARG("multiplier", "Scale factor (e.g., 2.0 for double size)")
|
||||||
" The internal resolution remains 1024x768, but the window is scaled.\n"
|
MCRF_RETURNS("None")
|
||||||
" This is deprecated - use Window.resolution instead."},
|
MCRF_NOTE("The internal resolution remains 1024x768, but the window is scaled. This is deprecated - use Window.resolution instead.")
|
||||||
|
)},
|
||||||
|
|
||||||
{"find", McRFPy_API::_find, METH_VARARGS,
|
{"find", McRFPy_API::_find, METH_VARARGS,
|
||||||
"find(name: str, scene: str = None) -> UIDrawable | None\n\n"
|
MCRF_FUNCTION(find,
|
||||||
"Find the first UI element with the specified name.\n\n"
|
MCRF_SIG("(name: str, scene: str = None)", "UIDrawable | None"),
|
||||||
"Args:\n"
|
MCRF_DESC("Find the first UI element with the specified name."),
|
||||||
" name: Exact name to search for\n"
|
MCRF_ARGS_START
|
||||||
" scene: Scene to search in (default: current scene)\n\n"
|
MCRF_ARG("name", "Exact name to search for")
|
||||||
"Returns:\n"
|
MCRF_ARG("scene", "Scene to search in (default: current scene)")
|
||||||
" Frame, Caption, Sprite, Grid, or Entity if found; None otherwise\n\n"
|
MCRF_RETURNS("Frame, Caption, Sprite, Grid, or Entity if found; None otherwise")
|
||||||
"Note:\n"
|
MCRF_NOTE("Searches scene UI elements and entities within grids.")
|
||||||
" Searches scene UI elements and entities within grids."},
|
)},
|
||||||
{"findAll", McRFPy_API::_findAll, METH_VARARGS,
|
{"findAll", McRFPy_API::_findAll, METH_VARARGS,
|
||||||
"findAll(pattern: str, scene: str = None) -> list\n\n"
|
MCRF_FUNCTION(findAll,
|
||||||
"Find all UI elements matching a name pattern.\n\n"
|
MCRF_SIG("(pattern: str, scene: str = None)", "list"),
|
||||||
"Args:\n"
|
MCRF_DESC("Find all UI elements matching a name pattern."),
|
||||||
" pattern: Name pattern with optional wildcards (* matches any characters)\n"
|
MCRF_ARGS_START
|
||||||
" scene: Scene to search in (default: current scene)\n\n"
|
MCRF_ARG("pattern", "Name pattern with optional wildcards (* matches any characters)")
|
||||||
"Returns:\n"
|
MCRF_ARG("scene", "Scene to search in (default: current scene)")
|
||||||
" list: All matching UI elements and entities\n\n"
|
MCRF_RETURNS("list: All matching UI elements and entities")
|
||||||
"Example:\n"
|
MCRF_NOTE("Example: findAll('enemy*') finds all elements starting with 'enemy', findAll('*_button') finds all elements ending with '_button'")
|
||||||
" findAll('enemy*') # Find all elements starting with 'enemy'\n"
|
)},
|
||||||
" findAll('*_button') # Find all elements ending with '_button'"},
|
|
||||||
|
|
||||||
{"getMetrics", McRFPy_API::_getMetrics, METH_NOARGS,
|
{"getMetrics", McRFPy_API::_getMetrics, METH_NOARGS,
|
||||||
"getMetrics() -> dict\n\n"
|
MCRF_FUNCTION(getMetrics,
|
||||||
"Get current performance metrics.\n\n"
|
MCRF_SIG("()", "dict"),
|
||||||
"Returns:\n"
|
MCRF_DESC("Get current performance metrics."),
|
||||||
" dict: Performance data with keys:\n"
|
MCRF_RETURNS("dict: Performance data with keys: frame_time (last frame duration in seconds), avg_frame_time (average frame time), fps (frames per second), draw_calls (number of draw calls), ui_elements (total UI element count), visible_elements (visible element count), current_frame (frame counter), runtime (total runtime in seconds)")
|
||||||
" - frame_time: Last frame duration in seconds\n"
|
)},
|
||||||
" - avg_frame_time: Average frame time\n"
|
|
||||||
" - fps: Frames per second\n"
|
|
||||||
" - draw_calls: Number of draw calls\n"
|
|
||||||
" - ui_elements: Total UI element count\n"
|
|
||||||
" - visible_elements: Visible element count\n"
|
|
||||||
" - current_frame: Frame counter\n"
|
|
||||||
" - runtime: Total runtime in seconds"},
|
|
||||||
|
|
||||||
{NULL, NULL, 0, NULL}
|
{NULL, NULL, 0, NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
static PyModuleDef mcrfpyModule = {
|
static PyModuleDef mcrfpyModule = {
|
||||||
PyModuleDef_HEAD_INIT, /* m_base - Always initialize this member to PyModuleDef_HEAD_INIT. */
|
PyModuleDef_HEAD_INIT, /* m_base - Always initialize this member to PyModuleDef_HEAD_INIT. */
|
||||||
"mcrfpy", /* m_name */
|
"mcrfpy", /* m_name */
|
||||||
PyDoc_STR("McRogueFace Python API\\n\\n"
|
PyDoc_STR("McRogueFace Python API\n\n"
|
||||||
"Core game engine interface for creating roguelike games with Python.\\n\\n"
|
"Core game engine interface for creating roguelike games with Python.\n\n"
|
||||||
"This module provides:\\n"
|
"This module provides:\n"
|
||||||
"- Scene management (createScene, setScene, currentScene)\\n"
|
"- Scene management (createScene, setScene, currentScene)\n"
|
||||||
"- UI components (Frame, Caption, Sprite, Grid)\\n"
|
"- UI components (Frame, Caption, Sprite, Grid)\n"
|
||||||
"- Entity system for game objects\\n"
|
"- Entity system for game objects\n"
|
||||||
"- Audio playback (sound effects and music)\\n"
|
"- Audio playback (sound effects and music)\n"
|
||||||
"- Timer system for scheduled events\\n"
|
"- Timer system for scheduled events\n"
|
||||||
"- Input handling\\n"
|
"- Input handling\n"
|
||||||
"- Performance metrics\\n\\n"
|
"- Performance metrics\n\n"
|
||||||
"Example:\\n"
|
"Example:\n"
|
||||||
" import mcrfpy\\n"
|
" import mcrfpy\n"
|
||||||
" \\n"
|
" \n"
|
||||||
" # Create a new scene\\n"
|
" # Create a new scene\n"
|
||||||
" mcrfpy.createScene('game')\\n"
|
" mcrfpy.createScene('game')\n"
|
||||||
" mcrfpy.setScene('game')\\n"
|
" mcrfpy.setScene('game')\n"
|
||||||
" \\n"
|
" \n"
|
||||||
" # Add UI elements\\n"
|
" # Add UI elements\n"
|
||||||
" frame = mcrfpy.Frame(10, 10, 200, 100)\\n"
|
" frame = mcrfpy.Frame(10, 10, 200, 100)\n"
|
||||||
" caption = mcrfpy.Caption('Hello World', 50, 50)\\n"
|
" caption = mcrfpy.Caption('Hello World', 50, 50)\n"
|
||||||
" mcrfpy.sceneUI().extend([frame, caption])\\n"),
|
" mcrfpy.sceneUI().extend([frame, caption])\n"),
|
||||||
-1, /* m_size - Setting m_size to -1 means that the module does not support sub-interpreters, because it has global state. */
|
-1, /* m_size - Setting m_size to -1 means that the module does not support sub-interpreters, because it has global state. */
|
||||||
mcrfpyMethods, /* m_methods */
|
mcrfpyMethods, /* m_methods */
|
||||||
NULL, /* m_slots - An array of slot definitions ... When using single-phase initialization, m_slots must be NULL. */
|
NULL, /* m_slots - An array of slot definitions ... When using single-phase initialization, m_slots must be NULL. */
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
#ifndef MCRFPY_DOC_H
|
||||||
|
#define MCRFPY_DOC_H
|
||||||
|
|
||||||
|
// Section builders for documentation
|
||||||
|
#define MCRF_SIG(params, ret) params " -> " ret "\n\n"
|
||||||
|
#define MCRF_DESC(text) text "\n\n"
|
||||||
|
#define MCRF_ARGS_START "Args:\n"
|
||||||
|
#define MCRF_ARG(name, desc) " " name ": " desc "\n"
|
||||||
|
#define MCRF_RETURNS(text) "\nReturns:\n " text "\n"
|
||||||
|
#define MCRF_RAISES(exc, desc) "\nRaises:\n " exc ": " desc "\n"
|
||||||
|
#define MCRF_NOTE(text) "\nNote:\n " text "\n"
|
||||||
|
|
||||||
|
// Link to external documentation
|
||||||
|
// Format: MCRF_LINK("docs/file.md", "Link Text")
|
||||||
|
// Parsers detect this pattern and format per output type
|
||||||
|
#define MCRF_LINK(ref, text) "\nSee also: " text " (" ref ")\n"
|
||||||
|
|
||||||
|
// Main documentation macros
|
||||||
|
#define MCRF_METHOD_DOC(name, sig, desc, ...) \
|
||||||
|
name sig desc __VA_ARGS__
|
||||||
|
|
||||||
|
#define MCRF_FUNCTION(name, ...) \
|
||||||
|
MCRF_METHOD_DOC(#name, __VA_ARGS__)
|
||||||
|
|
||||||
|
#define MCRF_METHOD(cls, name, ...) \
|
||||||
|
MCRF_METHOD_DOC(#name, __VA_ARGS__)
|
||||||
|
|
||||||
|
#define MCRF_PROPERTY(name, desc) \
|
||||||
|
desc
|
||||||
|
|
||||||
|
#endif // MCRFPY_DOC_H
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#include "PyAnimation.h"
|
#include "PyAnimation.h"
|
||||||
#include "McRFPy_API.h"
|
#include "McRFPy_API.h"
|
||||||
|
#include "McRFPy_Doc.h"
|
||||||
#include "UIDrawable.h"
|
#include "UIDrawable.h"
|
||||||
#include "UIFrame.h"
|
#include "UIFrame.h"
|
||||||
#include "UICaption.h"
|
#include "UICaption.h"
|
||||||
|
|
@ -261,33 +262,58 @@ PyObject* PyAnimation::has_valid_target(PyAnimationObject* self, PyObject* args)
|
||||||
}
|
}
|
||||||
|
|
||||||
PyGetSetDef PyAnimation::getsetters[] = {
|
PyGetSetDef PyAnimation::getsetters[] = {
|
||||||
{"property", (getter)get_property, NULL, "Target property name", NULL},
|
{"property", (getter)get_property, NULL,
|
||||||
{"duration", (getter)get_duration, NULL, "Animation duration in seconds", NULL},
|
MCRF_PROPERTY(property, "Target property name (str, read-only). The property being animated (e.g., 'pos', 'opacity', 'sprite_index')."), NULL},
|
||||||
{"elapsed", (getter)get_elapsed, NULL, "Elapsed time in seconds", NULL},
|
{"duration", (getter)get_duration, NULL,
|
||||||
{"is_complete", (getter)get_is_complete, NULL, "Whether animation is complete", NULL},
|
MCRF_PROPERTY(duration, "Animation duration in seconds (float, read-only). Total time for the animation to complete."), NULL},
|
||||||
{"is_delta", (getter)get_is_delta, NULL, "Whether animation uses delta mode", NULL},
|
{"elapsed", (getter)get_elapsed, NULL,
|
||||||
|
MCRF_PROPERTY(elapsed, "Elapsed time in seconds (float, read-only). Time since the animation started."), NULL},
|
||||||
|
{"is_complete", (getter)get_is_complete, NULL,
|
||||||
|
MCRF_PROPERTY(is_complete, "Whether animation is complete (bool, read-only). True when elapsed >= duration or complete() was called."), NULL},
|
||||||
|
{"is_delta", (getter)get_is_delta, NULL,
|
||||||
|
MCRF_PROPERTY(is_delta, "Whether animation uses delta mode (bool, read-only). In delta mode, the target value is added to the starting value."), NULL},
|
||||||
{NULL}
|
{NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
PyMethodDef PyAnimation::methods[] = {
|
PyMethodDef PyAnimation::methods[] = {
|
||||||
{"start", (PyCFunction)start, METH_VARARGS,
|
{"start", (PyCFunction)start, METH_VARARGS,
|
||||||
"start(target) -> None\n\n"
|
MCRF_METHOD(Animation, start,
|
||||||
"Start the animation on a target UI element.\n\n"
|
MCRF_SIG("(target: UIDrawable)", "None"),
|
||||||
"Args:\n"
|
MCRF_DESC("Start the animation on a target UI element."),
|
||||||
" target: The UI element to animate (Frame, Caption, Sprite, Grid, or Entity)\n\n"
|
MCRF_ARGS_START
|
||||||
"Note:\n"
|
MCRF_ARG("target", "The UI element to animate (Frame, Caption, Sprite, Grid, or Entity)")
|
||||||
" The animation will automatically stop if the target is destroyed."},
|
MCRF_RETURNS("None")
|
||||||
|
MCRF_NOTE("The animation will automatically stop if the target is destroyed. Call AnimationManager.update(delta_time) each frame to progress animations.")
|
||||||
|
)},
|
||||||
{"update", (PyCFunction)update, METH_VARARGS,
|
{"update", (PyCFunction)update, METH_VARARGS,
|
||||||
"Update the animation by deltaTime (returns True if still running)"},
|
MCRF_METHOD(Animation, update,
|
||||||
|
MCRF_SIG("(delta_time: float)", "bool"),
|
||||||
|
MCRF_DESC("Update the animation by the given time delta."),
|
||||||
|
MCRF_ARGS_START
|
||||||
|
MCRF_ARG("delta_time", "Time elapsed since last update in seconds")
|
||||||
|
MCRF_RETURNS("bool: True if animation is still running, False if complete")
|
||||||
|
MCRF_NOTE("Typically called by AnimationManager automatically. Manual calls only needed for custom animation control.")
|
||||||
|
)},
|
||||||
{"get_current_value", (PyCFunction)get_current_value, METH_NOARGS,
|
{"get_current_value", (PyCFunction)get_current_value, METH_NOARGS,
|
||||||
"Get the current interpolated value"},
|
MCRF_METHOD(Animation, get_current_value,
|
||||||
|
MCRF_SIG("()", "Any"),
|
||||||
|
MCRF_DESC("Get the current interpolated value of the animation."),
|
||||||
|
MCRF_RETURNS("Any: Current value (type depends on property: float, int, Color tuple, Vector tuple, or str)")
|
||||||
|
MCRF_NOTE("Return type matches the target property type. For sprite_index returns int, for pos returns (x, y), for fill_color returns (r, g, b, a).")
|
||||||
|
)},
|
||||||
{"complete", (PyCFunction)complete, METH_NOARGS,
|
{"complete", (PyCFunction)complete, METH_NOARGS,
|
||||||
"complete() -> None\n\n"
|
MCRF_METHOD(Animation, complete,
|
||||||
"Complete the animation immediately by jumping to the final value."},
|
MCRF_SIG("()", "None"),
|
||||||
|
MCRF_DESC("Complete the animation immediately by jumping to the final value."),
|
||||||
|
MCRF_RETURNS("None")
|
||||||
|
MCRF_NOTE("Sets elapsed = duration and applies target value immediately. Completion callback will be called if set.")
|
||||||
|
)},
|
||||||
{"hasValidTarget", (PyCFunction)has_valid_target, METH_NOARGS,
|
{"hasValidTarget", (PyCFunction)has_valid_target, METH_NOARGS,
|
||||||
"hasValidTarget() -> bool\n\n"
|
MCRF_METHOD(Animation, hasValidTarget,
|
||||||
"Check if the animation still has a valid target.\n\n"
|
MCRF_SIG("()", "bool"),
|
||||||
"Returns:\n"
|
MCRF_DESC("Check if the animation still has a valid target."),
|
||||||
" True if the target still exists, False if it was destroyed."},
|
MCRF_RETURNS("bool: True if the target still exists, False if it was destroyed")
|
||||||
|
MCRF_NOTE("Animations automatically clean up when targets are destroyed. Use this to check if manual cleanup is needed.")
|
||||||
|
)},
|
||||||
{NULL}
|
{NULL}
|
||||||
};
|
};
|
||||||
|
|
@ -2,21 +2,50 @@
|
||||||
#include "McRFPy_API.h"
|
#include "McRFPy_API.h"
|
||||||
#include "PyObjectUtils.h"
|
#include "PyObjectUtils.h"
|
||||||
#include "PyRAII.h"
|
#include "PyRAII.h"
|
||||||
|
#include "McRFPy_Doc.h"
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
|
||||||
PyGetSetDef PyColor::getsetters[] = {
|
PyGetSetDef PyColor::getsetters[] = {
|
||||||
{"r", (getter)PyColor::get_member, (setter)PyColor::set_member, "Red component", (void*)0},
|
{"r", (getter)PyColor::get_member, (setter)PyColor::set_member,
|
||||||
{"g", (getter)PyColor::get_member, (setter)PyColor::set_member, "Green component", (void*)1},
|
MCRF_PROPERTY(r, "Red component (0-255). Automatically clamped to valid range."), (void*)0},
|
||||||
{"b", (getter)PyColor::get_member, (setter)PyColor::set_member, "Blue component", (void*)2},
|
{"g", (getter)PyColor::get_member, (setter)PyColor::set_member,
|
||||||
{"a", (getter)PyColor::get_member, (setter)PyColor::set_member, "Alpha component", (void*)3},
|
MCRF_PROPERTY(g, "Green component (0-255). Automatically clamped to valid range."), (void*)1},
|
||||||
|
{"b", (getter)PyColor::get_member, (setter)PyColor::set_member,
|
||||||
|
MCRF_PROPERTY(b, "Blue component (0-255). Automatically clamped to valid range."), (void*)2},
|
||||||
|
{"a", (getter)PyColor::get_member, (setter)PyColor::set_member,
|
||||||
|
MCRF_PROPERTY(a, "Alpha component (0-255, where 0=transparent, 255=opaque). Automatically clamped to valid range."), (void*)3},
|
||||||
{NULL}
|
{NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
PyMethodDef PyColor::methods[] = {
|
PyMethodDef PyColor::methods[] = {
|
||||||
{"from_hex", (PyCFunction)PyColor::from_hex, METH_VARARGS | METH_CLASS, "Create Color from hex string (e.g., '#FF0000' or 'FF0000')"},
|
{"from_hex", (PyCFunction)PyColor::from_hex, METH_VARARGS | METH_CLASS,
|
||||||
{"to_hex", (PyCFunction)PyColor::to_hex, METH_NOARGS, "Convert Color to hex string"},
|
MCRF_METHOD(Color, from_hex,
|
||||||
{"lerp", (PyCFunction)PyColor::lerp, METH_VARARGS, "Linearly interpolate between this color and another"},
|
MCRF_SIG("(hex_string: str)", "Color"),
|
||||||
|
MCRF_DESC("Create a Color from a hexadecimal string."),
|
||||||
|
MCRF_ARGS_START
|
||||||
|
MCRF_ARG("hex_string", "Hex color string (e.g., '#FF0000', 'FF0000', '#AABBCCDD' for RGBA)")
|
||||||
|
MCRF_RETURNS("Color: New Color object with values from hex string")
|
||||||
|
MCRF_RAISES("ValueError", "If hex string is not 6 or 8 characters (RGB or RGBA)")
|
||||||
|
MCRF_NOTE("This is a class method. Call as Color.from_hex('#FF0000')")
|
||||||
|
)},
|
||||||
|
{"to_hex", (PyCFunction)PyColor::to_hex, METH_NOARGS,
|
||||||
|
MCRF_METHOD(Color, to_hex,
|
||||||
|
MCRF_SIG("()", "str"),
|
||||||
|
MCRF_DESC("Convert this Color to a hexadecimal string."),
|
||||||
|
MCRF_RETURNS("str: Hex string in format '#RRGGBB' or '#RRGGBBAA' (if alpha < 255)")
|
||||||
|
MCRF_NOTE("Alpha component is only included if not fully opaque (< 255)")
|
||||||
|
)},
|
||||||
|
{"lerp", (PyCFunction)PyColor::lerp, METH_VARARGS,
|
||||||
|
MCRF_METHOD(Color, lerp,
|
||||||
|
MCRF_SIG("(other: Color, t: float)", "Color"),
|
||||||
|
MCRF_DESC("Linearly interpolate between this color and another."),
|
||||||
|
MCRF_ARGS_START
|
||||||
|
MCRF_ARG("other", "The target Color to interpolate towards")
|
||||||
|
MCRF_ARG("t", "Interpolation factor (0.0 = this color, 1.0 = other color). Automatically clamped to [0.0, 1.0]")
|
||||||
|
MCRF_RETURNS("Color: New Color representing the interpolated value")
|
||||||
|
MCRF_NOTE("All components (r, g, b, a) are interpolated independently")
|
||||||
|
)},
|
||||||
{NULL}
|
{NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#include "PyDrawable.h"
|
#include "PyDrawable.h"
|
||||||
#include "McRFPy_API.h"
|
#include "McRFPy_API.h"
|
||||||
|
#include "McRFPy_Doc.h"
|
||||||
|
|
||||||
// Click property getter
|
// Click property getter
|
||||||
static PyObject* PyDrawable_get_click(PyDrawableObject* self, void* closure)
|
static PyObject* PyDrawable_get_click(PyDrawableObject* self, void* closure)
|
||||||
|
|
@ -98,14 +99,26 @@ static int PyDrawable_set_opacity(PyDrawableObject* self, PyObject* value, void*
|
||||||
|
|
||||||
// GetSetDef array for properties
|
// GetSetDef array for properties
|
||||||
static PyGetSetDef PyDrawable_getsetters[] = {
|
static PyGetSetDef PyDrawable_getsetters[] = {
|
||||||
{"click", (getter)PyDrawable_get_click, (setter)PyDrawable_set_click,
|
{"click", (getter)PyDrawable_get_click, (setter)PyDrawable_set_click,
|
||||||
"Callable executed when object is clicked", NULL},
|
MCRF_PROPERTY(click,
|
||||||
|
"Callable executed when object is clicked. "
|
||||||
|
"Function receives (x, y) coordinates of click."
|
||||||
|
), NULL},
|
||||||
{"z_index", (getter)PyDrawable_get_z_index, (setter)PyDrawable_set_z_index,
|
{"z_index", (getter)PyDrawable_get_z_index, (setter)PyDrawable_set_z_index,
|
||||||
"Z-order for rendering (lower values rendered first)", NULL},
|
MCRF_PROPERTY(z_index,
|
||||||
|
"Z-order for rendering (lower values rendered first). "
|
||||||
|
"Automatically triggers scene resort when changed."
|
||||||
|
), NULL},
|
||||||
{"visible", (getter)PyDrawable_get_visible, (setter)PyDrawable_set_visible,
|
{"visible", (getter)PyDrawable_get_visible, (setter)PyDrawable_set_visible,
|
||||||
"Whether the object is visible", NULL},
|
MCRF_PROPERTY(visible,
|
||||||
|
"Whether the object is visible (bool). "
|
||||||
|
"Invisible objects are not rendered or clickable."
|
||||||
|
), NULL},
|
||||||
{"opacity", (getter)PyDrawable_get_opacity, (setter)PyDrawable_set_opacity,
|
{"opacity", (getter)PyDrawable_get_opacity, (setter)PyDrawable_set_opacity,
|
||||||
"Opacity level (0.0 = transparent, 1.0 = opaque)", NULL},
|
MCRF_PROPERTY(opacity,
|
||||||
|
"Opacity level (0.0 = transparent, 1.0 = opaque). "
|
||||||
|
"Automatically clamped to valid range [0.0, 1.0]."
|
||||||
|
), NULL},
|
||||||
{NULL} // Sentinel
|
{NULL} // Sentinel
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -143,11 +156,30 @@ static PyObject* PyDrawable_resize(PyDrawableObject* self, PyObject* args)
|
||||||
// Method definitions
|
// Method definitions
|
||||||
static PyMethodDef PyDrawable_methods[] = {
|
static PyMethodDef PyDrawable_methods[] = {
|
||||||
{"get_bounds", (PyCFunction)PyDrawable_get_bounds, METH_NOARGS,
|
{"get_bounds", (PyCFunction)PyDrawable_get_bounds, METH_NOARGS,
|
||||||
"Get bounding box as (x, y, width, height)"},
|
MCRF_METHOD(Drawable, get_bounds,
|
||||||
|
MCRF_SIG("()", "tuple"),
|
||||||
|
MCRF_DESC("Get the bounding rectangle of this drawable element."),
|
||||||
|
MCRF_RETURNS("tuple: (x, y, width, height) representing the element's bounds")
|
||||||
|
MCRF_NOTE("The bounds are in screen coordinates and account for current position and size.")
|
||||||
|
)},
|
||||||
{"move", (PyCFunction)PyDrawable_move, METH_VARARGS,
|
{"move", (PyCFunction)PyDrawable_move, METH_VARARGS,
|
||||||
"Move by relative offset (dx, dy)"},
|
MCRF_METHOD(Drawable, move,
|
||||||
|
MCRF_SIG("(dx: float, dy: float)", "None"),
|
||||||
|
MCRF_DESC("Move the element by a relative offset."),
|
||||||
|
MCRF_ARGS_START
|
||||||
|
MCRF_ARG("dx", "Horizontal offset in pixels")
|
||||||
|
MCRF_ARG("dy", "Vertical offset in pixels")
|
||||||
|
MCRF_NOTE("This modifies the x and y position properties by the given amounts.")
|
||||||
|
)},
|
||||||
{"resize", (PyCFunction)PyDrawable_resize, METH_VARARGS,
|
{"resize", (PyCFunction)PyDrawable_resize, METH_VARARGS,
|
||||||
"Resize to new dimensions (width, height)"},
|
MCRF_METHOD(Drawable, resize,
|
||||||
|
MCRF_SIG("(width: float, height: float)", "None"),
|
||||||
|
MCRF_DESC("Resize the element to new dimensions."),
|
||||||
|
MCRF_ARGS_START
|
||||||
|
MCRF_ARG("width", "New width in pixels")
|
||||||
|
MCRF_ARG("height", "New height in pixels")
|
||||||
|
MCRF_NOTE("For Caption and Sprite, this may not change actual size if determined by content.")
|
||||||
|
)},
|
||||||
{NULL} // Sentinel
|
{NULL} // Sentinel
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#include "PyFont.h"
|
#include "PyFont.h"
|
||||||
#include "McRFPy_API.h"
|
#include "McRFPy_API.h"
|
||||||
|
#include "McRFPy_Doc.h"
|
||||||
|
|
||||||
|
|
||||||
PyFont::PyFont(std::string filename)
|
PyFont::PyFont(std::string filename)
|
||||||
|
|
@ -73,7 +74,9 @@ PyObject* PyFont::get_source(PyFontObject* self, void* closure)
|
||||||
}
|
}
|
||||||
|
|
||||||
PyGetSetDef PyFont::getsetters[] = {
|
PyGetSetDef PyFont::getsetters[] = {
|
||||||
{"family", (getter)PyFont::get_family, NULL, "Font family name", NULL},
|
{"family", (getter)PyFont::get_family, NULL,
|
||||||
{"source", (getter)PyFont::get_source, NULL, "Source filename of the font", NULL},
|
MCRF_PROPERTY(family, "Font family name (str, read-only). Retrieved from font metadata."), NULL},
|
||||||
|
{"source", (getter)PyFont::get_source, NULL,
|
||||||
|
MCRF_PROPERTY(source, "Source filename path (str, read-only). The path used to load this font."), NULL},
|
||||||
{NULL} // Sentinel
|
{NULL} // Sentinel
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
#include "PyScene.h"
|
#include "PyScene.h"
|
||||||
#include "GameEngine.h"
|
#include "GameEngine.h"
|
||||||
#include "McRFPy_API.h"
|
#include "McRFPy_API.h"
|
||||||
|
#include "McRFPy_Doc.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
// Static map to store Python scene objects by name
|
// Static map to store Python scene objects by name
|
||||||
|
|
@ -213,19 +214,38 @@ void PySceneClass::call_on_resize(PySceneObject* self, int width, int height)
|
||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
PyGetSetDef PySceneClass::getsetters[] = {
|
PyGetSetDef PySceneClass::getsetters[] = {
|
||||||
{"name", (getter)get_name, NULL, "Scene name", NULL},
|
{"name", (getter)get_name, NULL,
|
||||||
{"active", (getter)get_active, NULL, "Whether this scene is currently active", NULL},
|
MCRF_PROPERTY(name, "Scene name (str, read-only). Unique identifier for this scene."), NULL},
|
||||||
|
{"active", (getter)get_active, NULL,
|
||||||
|
MCRF_PROPERTY(active, "Whether this scene is currently active (bool, read-only). Only one scene can be active at a time."), NULL},
|
||||||
{NULL}
|
{NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
PyMethodDef PySceneClass::methods[] = {
|
PyMethodDef PySceneClass::methods[] = {
|
||||||
{"activate", (PyCFunction)activate, METH_NOARGS,
|
{"activate", (PyCFunction)activate, METH_NOARGS,
|
||||||
"Make this the active scene"},
|
MCRF_METHOD(SceneClass, activate,
|
||||||
|
MCRF_SIG("()", "None"),
|
||||||
|
MCRF_DESC("Make this the active scene."),
|
||||||
|
MCRF_RETURNS("None")
|
||||||
|
MCRF_NOTE("Deactivates the current scene and activates this one. Scene transitions and lifecycle callbacks are triggered.")
|
||||||
|
)},
|
||||||
{"get_ui", (PyCFunction)get_ui, METH_NOARGS,
|
{"get_ui", (PyCFunction)get_ui, METH_NOARGS,
|
||||||
"Get the UI element collection for this scene"},
|
MCRF_METHOD(SceneClass, get_ui,
|
||||||
|
MCRF_SIG("()", "UICollection"),
|
||||||
|
MCRF_DESC("Get the UI element collection for this scene."),
|
||||||
|
MCRF_RETURNS("UICollection: Collection of UI elements (Frames, Captions, Sprites, Grids) in this scene")
|
||||||
|
MCRF_NOTE("Use to add, remove, or iterate over UI elements. Changes are reflected immediately.")
|
||||||
|
)},
|
||||||
{"register_keyboard", (PyCFunction)register_keyboard, METH_VARARGS,
|
{"register_keyboard", (PyCFunction)register_keyboard, METH_VARARGS,
|
||||||
"Register a keyboard handler function (alternative to overriding on_keypress)"},
|
MCRF_METHOD(SceneClass, register_keyboard,
|
||||||
|
MCRF_SIG("(callback: callable)", "None"),
|
||||||
|
MCRF_DESC("Register a keyboard event handler function."),
|
||||||
|
MCRF_ARGS_START
|
||||||
|
MCRF_ARG("callback", "Function that receives (key: str, pressed: bool) when keyboard events occur")
|
||||||
|
MCRF_RETURNS("None")
|
||||||
|
MCRF_NOTE("Alternative to overriding on_keypress() method. Handler is called for both key press and release events.")
|
||||||
|
)},
|
||||||
{NULL}
|
{NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#include "PyTexture.h"
|
#include "PyTexture.h"
|
||||||
#include "McRFPy_API.h"
|
#include "McRFPy_API.h"
|
||||||
|
#include "McRFPy_Doc.h"
|
||||||
|
|
||||||
PyTexture::PyTexture(std::string filename, int sprite_w, int sprite_h)
|
PyTexture::PyTexture(std::string filename, int sprite_w, int sprite_h)
|
||||||
: source(filename), sprite_width(sprite_w), sprite_height(sprite_h), sheet_width(0), sheet_height(0)
|
: source(filename), sprite_width(sprite_w), sprite_height(sprite_h), sheet_width(0), sheet_height(0)
|
||||||
|
|
@ -131,11 +132,17 @@ PyObject* PyTexture::get_source(PyTextureObject* self, void* closure)
|
||||||
}
|
}
|
||||||
|
|
||||||
PyGetSetDef PyTexture::getsetters[] = {
|
PyGetSetDef PyTexture::getsetters[] = {
|
||||||
{"sprite_width", (getter)PyTexture::get_sprite_width, NULL, "Width of each sprite in pixels", NULL},
|
{"sprite_width", (getter)PyTexture::get_sprite_width, NULL,
|
||||||
{"sprite_height", (getter)PyTexture::get_sprite_height, NULL, "Height of each sprite in pixels", NULL},
|
MCRF_PROPERTY(sprite_width, "Width of each sprite in pixels (int, read-only). Specified during texture initialization."), NULL},
|
||||||
{"sheet_width", (getter)PyTexture::get_sheet_width, NULL, "Number of sprite columns in the texture", NULL},
|
{"sprite_height", (getter)PyTexture::get_sprite_height, NULL,
|
||||||
{"sheet_height", (getter)PyTexture::get_sheet_height, NULL, "Number of sprite rows in the texture", NULL},
|
MCRF_PROPERTY(sprite_height, "Height of each sprite in pixels (int, read-only). Specified during texture initialization."), NULL},
|
||||||
{"sprite_count", (getter)PyTexture::get_sprite_count, NULL, "Total number of sprites in the texture", NULL},
|
{"sheet_width", (getter)PyTexture::get_sheet_width, NULL,
|
||||||
{"source", (getter)PyTexture::get_source, NULL, "Source filename of the texture", NULL},
|
MCRF_PROPERTY(sheet_width, "Number of sprite columns in the texture sheet (int, read-only). Calculated as texture_width / sprite_width."), NULL},
|
||||||
|
{"sheet_height", (getter)PyTexture::get_sheet_height, NULL,
|
||||||
|
MCRF_PROPERTY(sheet_height, "Number of sprite rows in the texture sheet (int, read-only). Calculated as texture_height / sprite_height."), NULL},
|
||||||
|
{"sprite_count", (getter)PyTexture::get_sprite_count, NULL,
|
||||||
|
MCRF_PROPERTY(sprite_count, "Total number of sprites in the texture sheet (int, read-only). Equals sheet_width * sheet_height."), NULL},
|
||||||
|
{"source", (getter)PyTexture::get_source, NULL,
|
||||||
|
MCRF_PROPERTY(source, "Source filename path (str, read-only). The path used to load this texture."), NULL},
|
||||||
{NULL} // Sentinel
|
{NULL} // Sentinel
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
#include "GameEngine.h"
|
#include "GameEngine.h"
|
||||||
#include "Resources.h"
|
#include "Resources.h"
|
||||||
#include "PythonObjectCache.h"
|
#include "PythonObjectCache.h"
|
||||||
|
#include "McRFPy_Doc.h"
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
PyObject* PyTimer::repr(PyObject* self) {
|
PyObject* PyTimer::repr(PyObject* self) {
|
||||||
|
|
@ -307,38 +308,50 @@ PyObject* PyTimer::get_name(PyTimerObject* self, void* closure) {
|
||||||
|
|
||||||
PyGetSetDef PyTimer::getsetters[] = {
|
PyGetSetDef PyTimer::getsetters[] = {
|
||||||
{"name", (getter)PyTimer::get_name, NULL,
|
{"name", (getter)PyTimer::get_name, NULL,
|
||||||
"Timer name (read-only)", NULL},
|
MCRF_PROPERTY(name, "Timer name (str, read-only). Unique identifier for this timer."), NULL},
|
||||||
{"interval", (getter)PyTimer::get_interval, (setter)PyTimer::set_interval,
|
{"interval", (getter)PyTimer::get_interval, (setter)PyTimer::set_interval,
|
||||||
"Timer interval in milliseconds", NULL},
|
MCRF_PROPERTY(interval, "Timer interval in milliseconds (int). Must be positive. Can be changed while timer is running."), NULL},
|
||||||
{"remaining", (getter)PyTimer::get_remaining, NULL,
|
{"remaining", (getter)PyTimer::get_remaining, NULL,
|
||||||
"Time remaining until next trigger in milliseconds", NULL},
|
MCRF_PROPERTY(remaining, "Time remaining until next trigger in milliseconds (int, read-only). Preserved when timer is paused."), NULL},
|
||||||
{"paused", (getter)PyTimer::get_paused, NULL,
|
{"paused", (getter)PyTimer::get_paused, NULL,
|
||||||
"Whether the timer is paused", NULL},
|
MCRF_PROPERTY(paused, "Whether the timer is paused (bool, read-only). Paused timers preserve their remaining time."), NULL},
|
||||||
{"active", (getter)PyTimer::get_active, NULL,
|
{"active", (getter)PyTimer::get_active, NULL,
|
||||||
"Whether the timer is active and not paused", NULL},
|
MCRF_PROPERTY(active, "Whether the timer is active and not paused (bool, read-only). False if cancelled or paused."), NULL},
|
||||||
{"callback", (getter)PyTimer::get_callback, (setter)PyTimer::set_callback,
|
{"callback", (getter)PyTimer::get_callback, (setter)PyTimer::set_callback,
|
||||||
"The callback function to be called", NULL},
|
MCRF_PROPERTY(callback, "The callback function to be called when timer fires (callable). Can be changed while timer is running."), NULL},
|
||||||
{"once", (getter)PyTimer::get_once, (setter)PyTimer::set_once,
|
{"once", (getter)PyTimer::get_once, (setter)PyTimer::set_once,
|
||||||
"Whether the timer stops after firing once", NULL},
|
MCRF_PROPERTY(once, "Whether the timer stops after firing once (bool). If False, timer repeats indefinitely."), NULL},
|
||||||
{NULL}
|
{NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
PyMethodDef PyTimer::methods[] = {
|
PyMethodDef PyTimer::methods[] = {
|
||||||
{"pause", (PyCFunction)PyTimer::pause, METH_NOARGS,
|
{"pause", (PyCFunction)PyTimer::pause, METH_NOARGS,
|
||||||
"pause() -> None\n\n"
|
MCRF_METHOD(Timer, pause,
|
||||||
"Pause the timer, preserving the time remaining until next trigger.\n"
|
MCRF_SIG("()", "None"),
|
||||||
"The timer can be resumed later with resume()."},
|
MCRF_DESC("Pause the timer, preserving the time remaining until next trigger."),
|
||||||
|
MCRF_RETURNS("None")
|
||||||
|
MCRF_NOTE("The timer can be resumed later with resume(). Time spent paused does not count toward the interval.")
|
||||||
|
)},
|
||||||
{"resume", (PyCFunction)PyTimer::resume, METH_NOARGS,
|
{"resume", (PyCFunction)PyTimer::resume, METH_NOARGS,
|
||||||
"resume() -> None\n\n"
|
MCRF_METHOD(Timer, resume,
|
||||||
"Resume a paused timer from where it left off.\n"
|
MCRF_SIG("()", "None"),
|
||||||
"Has no effect if the timer is not paused."},
|
MCRF_DESC("Resume a paused timer from where it left off."),
|
||||||
|
MCRF_RETURNS("None")
|
||||||
|
MCRF_NOTE("Has no effect if the timer is not paused. Timer will fire after the remaining time elapses.")
|
||||||
|
)},
|
||||||
{"cancel", (PyCFunction)PyTimer::cancel, METH_NOARGS,
|
{"cancel", (PyCFunction)PyTimer::cancel, METH_NOARGS,
|
||||||
"cancel() -> None\n\n"
|
MCRF_METHOD(Timer, cancel,
|
||||||
"Cancel the timer and remove it from the timer system.\n"
|
MCRF_SIG("()", "None"),
|
||||||
"The timer will no longer fire and cannot be restarted."},
|
MCRF_DESC("Cancel the timer and remove it from the timer system."),
|
||||||
|
MCRF_RETURNS("None")
|
||||||
|
MCRF_NOTE("The timer will no longer fire and cannot be restarted. The callback will not be called again.")
|
||||||
|
)},
|
||||||
{"restart", (PyCFunction)PyTimer::restart, METH_NOARGS,
|
{"restart", (PyCFunction)PyTimer::restart, METH_NOARGS,
|
||||||
"restart() -> None\n\n"
|
MCRF_METHOD(Timer, restart,
|
||||||
"Restart the timer from the beginning.\n"
|
MCRF_SIG("()", "None"),
|
||||||
"Resets the timer to fire after a full interval from now."},
|
MCRF_DESC("Restart the timer from the beginning."),
|
||||||
|
MCRF_RETURNS("None")
|
||||||
|
MCRF_NOTE("Resets the timer to fire after a full interval from now, regardless of remaining time.")
|
||||||
|
)},
|
||||||
{NULL}
|
{NULL}
|
||||||
};
|
};
|
||||||
|
|
@ -1,21 +1,65 @@
|
||||||
#include "PyVector.h"
|
#include "PyVector.h"
|
||||||
#include "PyObjectUtils.h"
|
#include "PyObjectUtils.h"
|
||||||
|
#include "McRFPy_Doc.h"
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
PyGetSetDef PyVector::getsetters[] = {
|
PyGetSetDef PyVector::getsetters[] = {
|
||||||
{"x", (getter)PyVector::get_member, (setter)PyVector::set_member, "X/horizontal component", (void*)0},
|
{"x", (getter)PyVector::get_member, (setter)PyVector::set_member,
|
||||||
{"y", (getter)PyVector::get_member, (setter)PyVector::set_member, "Y/vertical component", (void*)1},
|
MCRF_PROPERTY(x, "X coordinate of the vector (float)"), (void*)0},
|
||||||
|
{"y", (getter)PyVector::get_member, (setter)PyVector::set_member,
|
||||||
|
MCRF_PROPERTY(y, "Y coordinate of the vector (float)"), (void*)1},
|
||||||
{NULL}
|
{NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
PyMethodDef PyVector::methods[] = {
|
PyMethodDef PyVector::methods[] = {
|
||||||
{"magnitude", (PyCFunction)PyVector::magnitude, METH_NOARGS, "Return the length of the vector"},
|
{"magnitude", (PyCFunction)PyVector::magnitude, METH_NOARGS,
|
||||||
{"magnitude_squared", (PyCFunction)PyVector::magnitude_squared, METH_NOARGS, "Return the squared length of the vector"},
|
MCRF_METHOD(Vector, magnitude,
|
||||||
{"normalize", (PyCFunction)PyVector::normalize, METH_NOARGS, "Return a unit vector in the same direction"},
|
MCRF_SIG("()", "float"),
|
||||||
{"dot", (PyCFunction)PyVector::dot, METH_O, "Return the dot product with another vector"},
|
MCRF_DESC("Calculate the length/magnitude of this vector."),
|
||||||
{"distance_to", (PyCFunction)PyVector::distance_to, METH_O, "Return the distance to another vector"},
|
MCRF_RETURNS("float: The magnitude of the vector")
|
||||||
{"angle", (PyCFunction)PyVector::angle, METH_NOARGS, "Return the angle in radians from the positive X axis"},
|
)},
|
||||||
{"copy", (PyCFunction)PyVector::copy, METH_NOARGS, "Return a copy of this vector"},
|
{"magnitude_squared", (PyCFunction)PyVector::magnitude_squared, METH_NOARGS,
|
||||||
|
MCRF_METHOD(Vector, magnitude_squared,
|
||||||
|
MCRF_SIG("()", "float"),
|
||||||
|
MCRF_DESC("Calculate the squared magnitude of this vector."),
|
||||||
|
MCRF_RETURNS("float: The squared magnitude (faster than magnitude())")
|
||||||
|
MCRF_NOTE("Use this for comparisons to avoid expensive square root calculation.")
|
||||||
|
)},
|
||||||
|
{"normalize", (PyCFunction)PyVector::normalize, METH_NOARGS,
|
||||||
|
MCRF_METHOD(Vector, normalize,
|
||||||
|
MCRF_SIG("()", "Vector"),
|
||||||
|
MCRF_DESC("Return a unit vector in the same direction."),
|
||||||
|
MCRF_RETURNS("Vector: New normalized vector with magnitude 1.0")
|
||||||
|
MCRF_NOTE("For zero vectors (magnitude 0.0), returns a zero vector rather than raising an exception")
|
||||||
|
)},
|
||||||
|
{"dot", (PyCFunction)PyVector::dot, METH_O,
|
||||||
|
MCRF_METHOD(Vector, dot,
|
||||||
|
MCRF_SIG("(other: Vector)", "float"),
|
||||||
|
MCRF_DESC("Calculate the dot product with another vector."),
|
||||||
|
MCRF_ARGS_START
|
||||||
|
MCRF_ARG("other", "The other vector")
|
||||||
|
MCRF_RETURNS("float: Dot product of the two vectors")
|
||||||
|
)},
|
||||||
|
{"distance_to", (PyCFunction)PyVector::distance_to, METH_O,
|
||||||
|
MCRF_METHOD(Vector, distance_to,
|
||||||
|
MCRF_SIG("(other: Vector)", "float"),
|
||||||
|
MCRF_DESC("Calculate the distance to another vector."),
|
||||||
|
MCRF_ARGS_START
|
||||||
|
MCRF_ARG("other", "The other vector")
|
||||||
|
MCRF_RETURNS("float: Distance between the two vectors")
|
||||||
|
)},
|
||||||
|
{"angle", (PyCFunction)PyVector::angle, METH_NOARGS,
|
||||||
|
MCRF_METHOD(Vector, angle,
|
||||||
|
MCRF_SIG("()", "float"),
|
||||||
|
MCRF_DESC("Get the angle of this vector in radians."),
|
||||||
|
MCRF_RETURNS("float: Angle in radians from positive x-axis")
|
||||||
|
)},
|
||||||
|
{"copy", (PyCFunction)PyVector::copy, METH_NOARGS,
|
||||||
|
MCRF_METHOD(Vector, copy,
|
||||||
|
MCRF_SIG("()", "Vector"),
|
||||||
|
MCRF_DESC("Create a copy of this vector."),
|
||||||
|
MCRF_RETURNS("Vector: New Vector object with same x and y values")
|
||||||
|
)},
|
||||||
{NULL}
|
{NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
#include "PyWindow.h"
|
#include "PyWindow.h"
|
||||||
#include "GameEngine.h"
|
#include "GameEngine.h"
|
||||||
#include "McRFPy_API.h"
|
#include "McRFPy_API.h"
|
||||||
|
#include "McRFPy_Doc.h"
|
||||||
#include <SFML/Graphics.hpp>
|
#include <SFML/Graphics.hpp>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
|
|
@ -483,32 +484,49 @@ int PyWindow::set_scaling_mode(PyWindowObject* self, PyObject* value, void* clos
|
||||||
|
|
||||||
// Property definitions
|
// Property definitions
|
||||||
PyGetSetDef PyWindow::getsetters[] = {
|
PyGetSetDef PyWindow::getsetters[] = {
|
||||||
{"resolution", (getter)get_resolution, (setter)set_resolution,
|
{"resolution", (getter)get_resolution, (setter)set_resolution,
|
||||||
"Window resolution as (width, height) tuple", NULL},
|
MCRF_PROPERTY(resolution, "Window resolution as (width, height) tuple. Setting this recreates the window."), NULL},
|
||||||
{"fullscreen", (getter)get_fullscreen, (setter)set_fullscreen,
|
{"fullscreen", (getter)get_fullscreen, (setter)set_fullscreen,
|
||||||
"Window fullscreen state", NULL},
|
MCRF_PROPERTY(fullscreen, "Window fullscreen state (bool). Setting this recreates the window."), NULL},
|
||||||
{"vsync", (getter)get_vsync, (setter)set_vsync,
|
{"vsync", (getter)get_vsync, (setter)set_vsync,
|
||||||
"Vertical sync enabled state", NULL},
|
MCRF_PROPERTY(vsync, "Vertical sync enabled state (bool). Prevents screen tearing but may limit framerate."), NULL},
|
||||||
{"title", (getter)get_title, (setter)set_title,
|
{"title", (getter)get_title, (setter)set_title,
|
||||||
"Window title string", NULL},
|
MCRF_PROPERTY(title, "Window title string (str). Displayed in the window title bar."), NULL},
|
||||||
{"visible", (getter)get_visible, (setter)set_visible,
|
{"visible", (getter)get_visible, (setter)set_visible,
|
||||||
"Window visibility state", NULL},
|
MCRF_PROPERTY(visible, "Window visibility state (bool). Hidden windows still process events."), NULL},
|
||||||
{"framerate_limit", (getter)get_framerate_limit, (setter)set_framerate_limit,
|
{"framerate_limit", (getter)get_framerate_limit, (setter)set_framerate_limit,
|
||||||
"Frame rate limit (0 for unlimited)", NULL},
|
MCRF_PROPERTY(framerate_limit, "Frame rate limit in FPS (int, 0 for unlimited). Caps maximum frame rate."), NULL},
|
||||||
{"game_resolution", (getter)get_game_resolution, (setter)set_game_resolution,
|
{"game_resolution", (getter)get_game_resolution, (setter)set_game_resolution,
|
||||||
"Fixed game resolution as (width, height) tuple", NULL},
|
MCRF_PROPERTY(game_resolution, "Fixed game resolution as (width, height) tuple. Enables resolution-independent rendering with scaling."), NULL},
|
||||||
{"scaling_mode", (getter)get_scaling_mode, (setter)set_scaling_mode,
|
{"scaling_mode", (getter)get_scaling_mode, (setter)set_scaling_mode,
|
||||||
"Viewport scaling mode: 'center', 'stretch', or 'fit'", NULL},
|
MCRF_PROPERTY(scaling_mode, "Viewport scaling mode (str): 'center' (no scaling), 'stretch' (fill window), or 'fit' (maintain aspect ratio)."), NULL},
|
||||||
{NULL}
|
{NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Method definitions
|
// Method definitions
|
||||||
PyMethodDef PyWindow::methods[] = {
|
PyMethodDef PyWindow::methods[] = {
|
||||||
{"get", (PyCFunction)PyWindow::get, METH_VARARGS | METH_CLASS,
|
{"get", (PyCFunction)PyWindow::get, METH_VARARGS | METH_CLASS,
|
||||||
"Get the Window singleton instance"},
|
MCRF_METHOD(Window, get,
|
||||||
|
MCRF_SIG("()", "Window"),
|
||||||
|
MCRF_DESC("Get the Window singleton instance."),
|
||||||
|
MCRF_RETURNS("Window: The global window object")
|
||||||
|
MCRF_NOTE("This is a class method. Call as Window.get(). There is only one window instance per application.")
|
||||||
|
)},
|
||||||
{"center", (PyCFunction)PyWindow::center, METH_NOARGS,
|
{"center", (PyCFunction)PyWindow::center, METH_NOARGS,
|
||||||
"Center the window on the screen"},
|
MCRF_METHOD(Window, center,
|
||||||
|
MCRF_SIG("()", "None"),
|
||||||
|
MCRF_DESC("Center the window on the screen."),
|
||||||
|
MCRF_RETURNS("None")
|
||||||
|
MCRF_NOTE("Only works in windowed mode. Has no effect when fullscreen or in headless mode.")
|
||||||
|
)},
|
||||||
{"screenshot", (PyCFunction)PyWindow::screenshot, METH_VARARGS | METH_KEYWORDS,
|
{"screenshot", (PyCFunction)PyWindow::screenshot, METH_VARARGS | METH_KEYWORDS,
|
||||||
"Take a screenshot. Pass filename to save to file, or get raw bytes if no filename."},
|
MCRF_METHOD(Window, screenshot,
|
||||||
|
MCRF_SIG("(filename: str = None)", "bytes | None"),
|
||||||
|
MCRF_DESC("Take a screenshot of the current window contents."),
|
||||||
|
MCRF_ARGS_START
|
||||||
|
MCRF_ARG("filename", "Optional path to save screenshot. If omitted, returns raw RGBA bytes.")
|
||||||
|
MCRF_RETURNS("bytes | None: Raw RGBA pixel data if no filename given, otherwise None after saving")
|
||||||
|
MCRF_NOTE("Screenshot is taken at the actual window resolution. Use after render loop update for current frame.")
|
||||||
|
)},
|
||||||
{NULL}
|
{NULL}
|
||||||
};
|
};
|
||||||
36
src/UIBase.h
36
src/UIBase.h
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "Python.h"
|
#include "Python.h"
|
||||||
|
#include "McRFPy_Doc.h"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
class UIEntity;
|
class UIEntity;
|
||||||
|
|
@ -78,11 +79,30 @@ static PyObject* UIDrawable_resize(T* self, PyObject* args)
|
||||||
// Macro to add common UIDrawable methods to a method array
|
// Macro to add common UIDrawable methods to a method array
|
||||||
#define UIDRAWABLE_METHODS \
|
#define UIDRAWABLE_METHODS \
|
||||||
{"get_bounds", (PyCFunction)UIDrawable_get_bounds<PyObjectType>, METH_NOARGS, \
|
{"get_bounds", (PyCFunction)UIDrawable_get_bounds<PyObjectType>, METH_NOARGS, \
|
||||||
"Get bounding box as (x, y, width, height)"}, \
|
MCRF_METHOD(Drawable, get_bounds, \
|
||||||
|
MCRF_SIG("()", "tuple"), \
|
||||||
|
MCRF_DESC("Get the bounding rectangle of this drawable element."), \
|
||||||
|
MCRF_RETURNS("tuple: (x, y, width, height) representing the element's bounds") \
|
||||||
|
MCRF_NOTE("The bounds are in screen coordinates and account for current position and size.") \
|
||||||
|
)}, \
|
||||||
{"move", (PyCFunction)UIDrawable_move<PyObjectType>, METH_VARARGS, \
|
{"move", (PyCFunction)UIDrawable_move<PyObjectType>, METH_VARARGS, \
|
||||||
"Move by relative offset (dx, dy)"}, \
|
MCRF_METHOD(Drawable, move, \
|
||||||
|
MCRF_SIG("(dx: float, dy: float)", "None"), \
|
||||||
|
MCRF_DESC("Move the element by a relative offset."), \
|
||||||
|
MCRF_ARGS_START \
|
||||||
|
MCRF_ARG("dx", "Horizontal offset in pixels") \
|
||||||
|
MCRF_ARG("dy", "Vertical offset in pixels") \
|
||||||
|
MCRF_NOTE("This modifies the x and y position properties by the given amounts.") \
|
||||||
|
)}, \
|
||||||
{"resize", (PyCFunction)UIDrawable_resize<PyObjectType>, METH_VARARGS, \
|
{"resize", (PyCFunction)UIDrawable_resize<PyObjectType>, METH_VARARGS, \
|
||||||
"Resize to new dimensions (width, height)"}
|
MCRF_METHOD(Drawable, resize, \
|
||||||
|
MCRF_SIG("(width: float, height: float)", "None"), \
|
||||||
|
MCRF_DESC("Resize the element to new dimensions."), \
|
||||||
|
MCRF_ARGS_START \
|
||||||
|
MCRF_ARG("width", "New width in pixels") \
|
||||||
|
MCRF_ARG("height", "New height in pixels") \
|
||||||
|
MCRF_NOTE("For Caption and Sprite, this may not change actual size if determined by content.") \
|
||||||
|
)}
|
||||||
|
|
||||||
// Property getters/setters for visible and opacity
|
// Property getters/setters for visible and opacity
|
||||||
template<typename T>
|
template<typename T>
|
||||||
|
|
@ -132,8 +152,14 @@ static int UIDrawable_set_opacity(T* self, PyObject* value, void* closure)
|
||||||
// Macro to add common UIDrawable properties to a getsetters array
|
// Macro to add common UIDrawable properties to a getsetters array
|
||||||
#define UIDRAWABLE_GETSETTERS \
|
#define UIDRAWABLE_GETSETTERS \
|
||||||
{"visible", (getter)UIDrawable_get_visible<PyObjectType>, (setter)UIDrawable_set_visible<PyObjectType>, \
|
{"visible", (getter)UIDrawable_get_visible<PyObjectType>, (setter)UIDrawable_set_visible<PyObjectType>, \
|
||||||
"Visibility flag", NULL}, \
|
MCRF_PROPERTY(visible, \
|
||||||
|
"Whether the object is visible (bool). " \
|
||||||
|
"Invisible objects are not rendered or clickable." \
|
||||||
|
), NULL}, \
|
||||||
{"opacity", (getter)UIDrawable_get_opacity<PyObjectType>, (setter)UIDrawable_set_opacity<PyObjectType>, \
|
{"opacity", (getter)UIDrawable_get_opacity<PyObjectType>, (setter)UIDrawable_set_opacity<PyObjectType>, \
|
||||||
"Opacity (0.0 = transparent, 1.0 = opaque)", NULL}
|
MCRF_PROPERTY(opacity, \
|
||||||
|
"Opacity level (0.0 = transparent, 1.0 = opaque). " \
|
||||||
|
"Automatically clamped to valid range [0.0, 1.0]." \
|
||||||
|
), NULL}
|
||||||
|
|
||||||
// UIEntity specializations are defined in UIEntity.cpp after UIEntity class is complete
|
// UIEntity specializations are defined in UIEntity.cpp after UIEntity class is complete
|
||||||
|
|
|
||||||
|
|
@ -273,8 +273,16 @@ PyGetSetDef UICaption::getsetters[] = {
|
||||||
//{"children", (getter)PyUIFrame_get_children, NULL, "UICollection of objects on top of this one", NULL},
|
//{"children", (getter)PyUIFrame_get_children, NULL, "UICollection of objects on top of this one", NULL},
|
||||||
{"text", (getter)UICaption::get_text, (setter)UICaption::set_text, "The text displayed", NULL},
|
{"text", (getter)UICaption::get_text, (setter)UICaption::set_text, "The text displayed", NULL},
|
||||||
{"font_size", (getter)UICaption::get_float_member, (setter)UICaption::set_float_member, "Font size (integer) in points", (void*)5},
|
{"font_size", (getter)UICaption::get_float_member, (setter)UICaption::set_float_member, "Font size (integer) in points", (void*)5},
|
||||||
{"click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click, "Object called with (x, y, button) when clicked", (void*)PyObjectsEnum::UICAPTION},
|
{"click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click,
|
||||||
{"z_index", (getter)UIDrawable::get_int, (setter)UIDrawable::set_int, "Z-order for rendering (lower values rendered first)", (void*)PyObjectsEnum::UICAPTION},
|
MCRF_PROPERTY(click,
|
||||||
|
"Callable executed when object is clicked. "
|
||||||
|
"Function receives (x, y) coordinates of click."
|
||||||
|
), (void*)PyObjectsEnum::UICAPTION},
|
||||||
|
{"z_index", (getter)UIDrawable::get_int, (setter)UIDrawable::set_int,
|
||||||
|
MCRF_PROPERTY(z_index,
|
||||||
|
"Z-order for rendering (lower values rendered first). "
|
||||||
|
"Automatically triggers scene resort when changed."
|
||||||
|
), (void*)PyObjectsEnum::UICAPTION},
|
||||||
{"name", (getter)UIDrawable::get_name, (setter)UIDrawable::set_name, "Name for finding elements", (void*)PyObjectsEnum::UICAPTION},
|
{"name", (getter)UIDrawable::get_name, (setter)UIDrawable::set_name, "Name for finding elements", (void*)PyObjectsEnum::UICAPTION},
|
||||||
UIDRAWABLE_GETSETTERS,
|
UIDRAWABLE_GETSETTERS,
|
||||||
{NULL}
|
{NULL}
|
||||||
|
|
|
||||||
|
|
@ -398,8 +398,16 @@ PyGetSetDef UIFrame::getsetters[] = {
|
||||||
{"fill_color", (getter)UIFrame::get_color_member, (setter)UIFrame::set_color_member, "Fill color of the rectangle", (void*)0},
|
{"fill_color", (getter)UIFrame::get_color_member, (setter)UIFrame::set_color_member, "Fill color of the rectangle", (void*)0},
|
||||||
{"outline_color", (getter)UIFrame::get_color_member, (setter)UIFrame::set_color_member, "Outline color of the rectangle", (void*)1},
|
{"outline_color", (getter)UIFrame::get_color_member, (setter)UIFrame::set_color_member, "Outline color of the rectangle", (void*)1},
|
||||||
{"children", (getter)UIFrame::get_children, NULL, "UICollection of objects on top of this one", NULL},
|
{"children", (getter)UIFrame::get_children, NULL, "UICollection of objects on top of this one", NULL},
|
||||||
{"click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click, "Object called with (x, y, button) when clicked", (void*)PyObjectsEnum::UIFRAME},
|
{"click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click,
|
||||||
{"z_index", (getter)UIDrawable::get_int, (setter)UIDrawable::set_int, "Z-order for rendering (lower values rendered first)", (void*)PyObjectsEnum::UIFRAME},
|
MCRF_PROPERTY(click,
|
||||||
|
"Callable executed when object is clicked. "
|
||||||
|
"Function receives (x, y) coordinates of click."
|
||||||
|
), (void*)PyObjectsEnum::UIFRAME},
|
||||||
|
{"z_index", (getter)UIDrawable::get_int, (setter)UIDrawable::set_int,
|
||||||
|
MCRF_PROPERTY(z_index,
|
||||||
|
"Z-order for rendering (lower values rendered first). "
|
||||||
|
"Automatically triggers scene resort when changed."
|
||||||
|
), (void*)PyObjectsEnum::UIFRAME},
|
||||||
{"name", (getter)UIDrawable::get_name, (setter)UIDrawable::set_name, "Name for finding elements", (void*)PyObjectsEnum::UIFRAME},
|
{"name", (getter)UIDrawable::get_name, (setter)UIDrawable::set_name, "Name for finding elements", (void*)PyObjectsEnum::UIFRAME},
|
||||||
{"pos", (getter)UIDrawable::get_pos, (setter)UIDrawable::set_pos, "Position as a Vector", (void*)PyObjectsEnum::UIFRAME},
|
{"pos", (getter)UIDrawable::get_pos, (setter)UIDrawable::set_pos, "Position as a Vector", (void*)PyObjectsEnum::UIFRAME},
|
||||||
{"clip_children", (getter)UIFrame::get_clip_children, (setter)UIFrame::set_clip_children, "Whether to clip children to frame bounds", NULL},
|
{"clip_children", (getter)UIFrame::get_clip_children, (setter)UIFrame::set_clip_children, "Whether to clip children to frame bounds", NULL},
|
||||||
|
|
|
||||||
|
|
@ -1418,7 +1418,11 @@ PyGetSetDef UIGrid::getsetters[] = {
|
||||||
{"center_y", (getter)UIGrid::get_float_member, (setter)UIGrid::set_float_member, "center of the view Y-coordinate", (void*)5},
|
{"center_y", (getter)UIGrid::get_float_member, (setter)UIGrid::set_float_member, "center of the view Y-coordinate", (void*)5},
|
||||||
{"zoom", (getter)UIGrid::get_float_member, (setter)UIGrid::set_float_member, "zoom factor for displaying the Grid", (void*)6},
|
{"zoom", (getter)UIGrid::get_float_member, (setter)UIGrid::set_float_member, "zoom factor for displaying the Grid", (void*)6},
|
||||||
|
|
||||||
{"click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click, "Object called with (x, y, button) when clicked", (void*)PyObjectsEnum::UIGRID},
|
{"click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click,
|
||||||
|
MCRF_PROPERTY(click,
|
||||||
|
"Callable executed when object is clicked. "
|
||||||
|
"Function receives (x, y) coordinates of click."
|
||||||
|
), (void*)PyObjectsEnum::UIGRID},
|
||||||
|
|
||||||
{"texture", (getter)UIGrid::get_texture, NULL, "Texture of the grid", NULL}, //TODO 7DRL-day2-item5
|
{"texture", (getter)UIGrid::get_texture, NULL, "Texture of the grid", NULL}, //TODO 7DRL-day2-item5
|
||||||
{"fill_color", (getter)UIGrid::get_fill_color, (setter)UIGrid::set_fill_color, "Background fill color of the grid", NULL},
|
{"fill_color", (getter)UIGrid::get_fill_color, (setter)UIGrid::set_fill_color, "Background fill color of the grid", NULL},
|
||||||
|
|
@ -1428,7 +1432,11 @@ PyGetSetDef UIGrid::getsetters[] = {
|
||||||
{"perspective_enabled", (getter)UIGrid::get_perspective_enabled, (setter)UIGrid::set_perspective_enabled,
|
{"perspective_enabled", (getter)UIGrid::get_perspective_enabled, (setter)UIGrid::set_perspective_enabled,
|
||||||
"Whether to use perspective-based FOV rendering. When True with no valid entity, "
|
"Whether to use perspective-based FOV rendering. When True with no valid entity, "
|
||||||
"all cells appear undiscovered.", NULL},
|
"all cells appear undiscovered.", NULL},
|
||||||
{"z_index", (getter)UIDrawable::get_int, (setter)UIDrawable::set_int, "Z-order for rendering (lower values rendered first)", (void*)PyObjectsEnum::UIGRID},
|
{"z_index", (getter)UIDrawable::get_int, (setter)UIDrawable::set_int,
|
||||||
|
MCRF_PROPERTY(z_index,
|
||||||
|
"Z-order for rendering (lower values rendered first). "
|
||||||
|
"Automatically triggers scene resort when changed."
|
||||||
|
), (void*)PyObjectsEnum::UIGRID},
|
||||||
{"name", (getter)UIDrawable::get_name, (setter)UIDrawable::set_name, "Name for finding elements", (void*)PyObjectsEnum::UIGRID},
|
{"name", (getter)UIDrawable::get_name, (setter)UIDrawable::set_name, "Name for finding elements", (void*)PyObjectsEnum::UIGRID},
|
||||||
UIDRAWABLE_GETSETTERS,
|
UIDRAWABLE_GETSETTERS,
|
||||||
{NULL} /* Sentinel */
|
{NULL} /* Sentinel */
|
||||||
|
|
|
||||||
|
|
@ -339,8 +339,16 @@ PyGetSetDef UISprite::getsetters[] = {
|
||||||
{"sprite_index", (getter)UISprite::get_int_member, (setter)UISprite::set_int_member, "Which sprite on the texture is shown", NULL},
|
{"sprite_index", (getter)UISprite::get_int_member, (setter)UISprite::set_int_member, "Which sprite on the texture is shown", NULL},
|
||||||
{"sprite_number", (getter)UISprite::get_int_member, (setter)UISprite::set_int_member, "Sprite index (DEPRECATED: use sprite_index instead)", NULL},
|
{"sprite_number", (getter)UISprite::get_int_member, (setter)UISprite::set_int_member, "Sprite index (DEPRECATED: use sprite_index instead)", NULL},
|
||||||
{"texture", (getter)UISprite::get_texture, (setter)UISprite::set_texture, "Texture object", NULL},
|
{"texture", (getter)UISprite::get_texture, (setter)UISprite::set_texture, "Texture object", NULL},
|
||||||
{"click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click, "Object called with (x, y, button) when clicked", (void*)PyObjectsEnum::UISPRITE},
|
{"click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click,
|
||||||
{"z_index", (getter)UIDrawable::get_int, (setter)UIDrawable::set_int, "Z-order for rendering (lower values rendered first)", (void*)PyObjectsEnum::UISPRITE},
|
MCRF_PROPERTY(click,
|
||||||
|
"Callable executed when object is clicked. "
|
||||||
|
"Function receives (x, y) coordinates of click."
|
||||||
|
), (void*)PyObjectsEnum::UISPRITE},
|
||||||
|
{"z_index", (getter)UIDrawable::get_int, (setter)UIDrawable::set_int,
|
||||||
|
MCRF_PROPERTY(z_index,
|
||||||
|
"Z-order for rendering (lower values rendered first). "
|
||||||
|
"Automatically triggers scene resort when changed."
|
||||||
|
), (void*)PyObjectsEnum::UISPRITE},
|
||||||
{"name", (getter)UIDrawable::get_name, (setter)UIDrawable::set_name, "Name for finding elements", (void*)PyObjectsEnum::UISPRITE},
|
{"name", (getter)UIDrawable::get_name, (setter)UIDrawable::set_name, "Name for finding elements", (void*)PyObjectsEnum::UISPRITE},
|
||||||
{"pos", (getter)UIDrawable::get_pos, (setter)UIDrawable::set_pos, "Position as a Vector", (void*)PyObjectsEnum::UISPRITE},
|
{"pos", (getter)UIDrawable::get_pos, (setter)UIDrawable::set_pos, "Position as a Vector", (void*)PyObjectsEnum::UISPRITE},
|
||||||
UIDRAWABLE_GETSETTERS,
|
UIDRAWABLE_GETSETTERS,
|
||||||
|
|
|
||||||
|
|
@ -1,482 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
"""Generate API reference documentation for McRogueFace.
|
|
||||||
|
|
||||||
This script generates comprehensive API documentation in multiple formats:
|
|
||||||
- Markdown for GitHub/documentation sites
|
|
||||||
- HTML for local browsing
|
|
||||||
- RST for Sphinx integration (future)
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import inspect
|
|
||||||
import datetime
|
|
||||||
from typing import Dict, List, Any, Optional
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
# We need to run this with McRogueFace as the interpreter
|
|
||||||
# so mcrfpy is available
|
|
||||||
import mcrfpy
|
|
||||||
|
|
||||||
def escape_markdown(text: str) -> str:
|
|
||||||
"""Escape special markdown characters."""
|
|
||||||
if not text:
|
|
||||||
return ""
|
|
||||||
# Escape backticks in inline code
|
|
||||||
return text.replace("`", "\\`")
|
|
||||||
|
|
||||||
def format_signature(name: str, doc: str) -> str:
|
|
||||||
"""Extract and format function signature from docstring."""
|
|
||||||
if not doc:
|
|
||||||
return f"{name}(...)"
|
|
||||||
|
|
||||||
lines = doc.strip().split('\n')
|
|
||||||
if lines and '(' in lines[0]:
|
|
||||||
# First line contains signature
|
|
||||||
return lines[0].split('->')[0].strip()
|
|
||||||
|
|
||||||
return f"{name}(...)"
|
|
||||||
|
|
||||||
def get_class_info(cls: type) -> Dict[str, Any]:
|
|
||||||
"""Extract comprehensive information about a class."""
|
|
||||||
info = {
|
|
||||||
'name': cls.__name__,
|
|
||||||
'doc': cls.__doc__ or "",
|
|
||||||
'methods': [],
|
|
||||||
'properties': [],
|
|
||||||
'bases': [base.__name__ for base in cls.__bases__ if base.__name__ != 'object'],
|
|
||||||
}
|
|
||||||
|
|
||||||
# Get all attributes
|
|
||||||
for attr_name in sorted(dir(cls)):
|
|
||||||
if attr_name.startswith('_') and not attr_name.startswith('__'):
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
|
||||||
attr = getattr(cls, attr_name)
|
|
||||||
|
|
||||||
if isinstance(attr, property):
|
|
||||||
prop_info = {
|
|
||||||
'name': attr_name,
|
|
||||||
'doc': (attr.fget.__doc__ if attr.fget else "") or "",
|
|
||||||
'readonly': attr.fset is None
|
|
||||||
}
|
|
||||||
info['properties'].append(prop_info)
|
|
||||||
elif callable(attr) and not attr_name.startswith('__'):
|
|
||||||
method_info = {
|
|
||||||
'name': attr_name,
|
|
||||||
'doc': attr.__doc__ or "",
|
|
||||||
'signature': format_signature(attr_name, attr.__doc__)
|
|
||||||
}
|
|
||||||
info['methods'].append(method_info)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return info
|
|
||||||
|
|
||||||
def get_function_info(func: Any, name: str) -> Dict[str, Any]:
|
|
||||||
"""Extract information about a function."""
|
|
||||||
return {
|
|
||||||
'name': name,
|
|
||||||
'doc': func.__doc__ or "",
|
|
||||||
'signature': format_signature(name, func.__doc__)
|
|
||||||
}
|
|
||||||
|
|
||||||
def generate_markdown_class(cls_info: Dict[str, Any]) -> List[str]:
|
|
||||||
"""Generate markdown documentation for a class."""
|
|
||||||
lines = []
|
|
||||||
|
|
||||||
# Class header
|
|
||||||
lines.append(f"### class `{cls_info['name']}`")
|
|
||||||
if cls_info['bases']:
|
|
||||||
lines.append(f"*Inherits from: {', '.join(cls_info['bases'])}*")
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
# Class description
|
|
||||||
if cls_info['doc']:
|
|
||||||
doc_lines = cls_info['doc'].strip().split('\n')
|
|
||||||
# First line is usually the constructor signature
|
|
||||||
if doc_lines and '(' in doc_lines[0]:
|
|
||||||
lines.append(f"```python")
|
|
||||||
lines.append(doc_lines[0])
|
|
||||||
lines.append("```")
|
|
||||||
lines.append("")
|
|
||||||
# Rest is description
|
|
||||||
if len(doc_lines) > 2:
|
|
||||||
lines.extend(doc_lines[2:])
|
|
||||||
lines.append("")
|
|
||||||
else:
|
|
||||||
lines.extend(doc_lines)
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
# Properties
|
|
||||||
if cls_info['properties']:
|
|
||||||
lines.append("#### Properties")
|
|
||||||
lines.append("")
|
|
||||||
for prop in cls_info['properties']:
|
|
||||||
readonly = " *(readonly)*" if prop['readonly'] else ""
|
|
||||||
lines.append(f"- **`{prop['name']}`**{readonly}")
|
|
||||||
if prop['doc']:
|
|
||||||
lines.append(f" - {prop['doc'].strip()}")
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
# Methods
|
|
||||||
if cls_info['methods']:
|
|
||||||
lines.append("#### Methods")
|
|
||||||
lines.append("")
|
|
||||||
for method in cls_info['methods']:
|
|
||||||
lines.append(f"##### `{method['signature']}`")
|
|
||||||
if method['doc']:
|
|
||||||
# Parse docstring for better formatting
|
|
||||||
doc_lines = method['doc'].strip().split('\n')
|
|
||||||
# Skip the signature line if it's repeated
|
|
||||||
start = 1 if doc_lines and method['name'] in doc_lines[0] else 0
|
|
||||||
for line in doc_lines[start:]:
|
|
||||||
lines.append(line)
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
lines.append("---")
|
|
||||||
lines.append("")
|
|
||||||
return lines
|
|
||||||
|
|
||||||
def generate_markdown_function(func_info: Dict[str, Any]) -> List[str]:
|
|
||||||
"""Generate markdown documentation for a function."""
|
|
||||||
lines = []
|
|
||||||
|
|
||||||
lines.append(f"### `{func_info['signature']}`")
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
if func_info['doc']:
|
|
||||||
doc_lines = func_info['doc'].strip().split('\n')
|
|
||||||
# Skip signature line if present
|
|
||||||
start = 1 if doc_lines and func_info['name'] in doc_lines[0] else 0
|
|
||||||
|
|
||||||
# Process documentation sections
|
|
||||||
in_section = None
|
|
||||||
for line in doc_lines[start:]:
|
|
||||||
if line.strip() in ['Args:', 'Returns:', 'Raises:', 'Note:', 'Example:']:
|
|
||||||
in_section = line.strip()
|
|
||||||
lines.append(f"**{in_section}**")
|
|
||||||
elif in_section and line.strip():
|
|
||||||
# Indent content under sections
|
|
||||||
lines.append(f"{line}")
|
|
||||||
else:
|
|
||||||
lines.append(line)
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
lines.append("---")
|
|
||||||
lines.append("")
|
|
||||||
return lines
|
|
||||||
|
|
||||||
def generate_markdown_docs() -> str:
|
|
||||||
"""Generate complete markdown API documentation."""
|
|
||||||
lines = []
|
|
||||||
|
|
||||||
# Header
|
|
||||||
lines.append("# McRogueFace API Reference")
|
|
||||||
lines.append("")
|
|
||||||
lines.append(f"*Generated on {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*")
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
# Module description
|
|
||||||
if mcrfpy.__doc__:
|
|
||||||
lines.append("## Overview")
|
|
||||||
lines.append("")
|
|
||||||
lines.extend(mcrfpy.__doc__.strip().split('\n'))
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
# Table of contents
|
|
||||||
lines.append("## Table of Contents")
|
|
||||||
lines.append("")
|
|
||||||
lines.append("- [Classes](#classes)")
|
|
||||||
lines.append("- [Functions](#functions)")
|
|
||||||
lines.append("- [Automation Module](#automation-module)")
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
# Collect all components
|
|
||||||
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))
|
|
||||||
|
|
||||||
# Document classes
|
|
||||||
lines.append("## Classes")
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
# Group classes by category
|
|
||||||
ui_classes = []
|
|
||||||
collection_classes = []
|
|
||||||
system_classes = []
|
|
||||||
other_classes = []
|
|
||||||
|
|
||||||
for name, cls in classes:
|
|
||||||
if name in ['Frame', 'Caption', 'Sprite', 'Grid', 'Entity']:
|
|
||||||
ui_classes.append((name, cls))
|
|
||||||
elif 'Collection' in name:
|
|
||||||
collection_classes.append((name, cls))
|
|
||||||
elif name in ['Color', 'Vector', 'Texture', 'Font']:
|
|
||||||
system_classes.append((name, cls))
|
|
||||||
else:
|
|
||||||
other_classes.append((name, cls))
|
|
||||||
|
|
||||||
# UI Classes
|
|
||||||
if ui_classes:
|
|
||||||
lines.append("### UI Components")
|
|
||||||
lines.append("")
|
|
||||||
for name, cls in ui_classes:
|
|
||||||
lines.extend(generate_markdown_class(get_class_info(cls)))
|
|
||||||
|
|
||||||
# Collections
|
|
||||||
if collection_classes:
|
|
||||||
lines.append("### Collections")
|
|
||||||
lines.append("")
|
|
||||||
for name, cls in collection_classes:
|
|
||||||
lines.extend(generate_markdown_class(get_class_info(cls)))
|
|
||||||
|
|
||||||
# System Classes
|
|
||||||
if system_classes:
|
|
||||||
lines.append("### System Types")
|
|
||||||
lines.append("")
|
|
||||||
for name, cls in system_classes:
|
|
||||||
lines.extend(generate_markdown_class(get_class_info(cls)))
|
|
||||||
|
|
||||||
# Other Classes
|
|
||||||
if other_classes:
|
|
||||||
lines.append("### Other Classes")
|
|
||||||
lines.append("")
|
|
||||||
for name, cls in other_classes:
|
|
||||||
lines.extend(generate_markdown_class(get_class_info(cls)))
|
|
||||||
|
|
||||||
# Document functions
|
|
||||||
lines.append("## Functions")
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
# Group functions by category
|
|
||||||
scene_funcs = []
|
|
||||||
audio_funcs = []
|
|
||||||
ui_funcs = []
|
|
||||||
system_funcs = []
|
|
||||||
|
|
||||||
for name, func in functions:
|
|
||||||
if 'scene' in name.lower() or name in ['createScene', 'setScene']:
|
|
||||||
scene_funcs.append((name, func))
|
|
||||||
elif any(x in name.lower() for x in ['sound', 'music', 'volume']):
|
|
||||||
audio_funcs.append((name, func))
|
|
||||||
elif name in ['find', 'findAll']:
|
|
||||||
ui_funcs.append((name, func))
|
|
||||||
else:
|
|
||||||
system_funcs.append((name, func))
|
|
||||||
|
|
||||||
# Scene Management
|
|
||||||
if scene_funcs:
|
|
||||||
lines.append("### Scene Management")
|
|
||||||
lines.append("")
|
|
||||||
for name, func in scene_funcs:
|
|
||||||
lines.extend(generate_markdown_function(get_function_info(func, name)))
|
|
||||||
|
|
||||||
# Audio
|
|
||||||
if audio_funcs:
|
|
||||||
lines.append("### Audio")
|
|
||||||
lines.append("")
|
|
||||||
for name, func in audio_funcs:
|
|
||||||
lines.extend(generate_markdown_function(get_function_info(func, name)))
|
|
||||||
|
|
||||||
# UI Utilities
|
|
||||||
if ui_funcs:
|
|
||||||
lines.append("### UI Utilities")
|
|
||||||
lines.append("")
|
|
||||||
for name, func in ui_funcs:
|
|
||||||
lines.extend(generate_markdown_function(get_function_info(func, name)))
|
|
||||||
|
|
||||||
# System
|
|
||||||
if system_funcs:
|
|
||||||
lines.append("### System")
|
|
||||||
lines.append("")
|
|
||||||
for name, func in system_funcs:
|
|
||||||
lines.extend(generate_markdown_function(get_function_info(func, name)))
|
|
||||||
|
|
||||||
# Automation module
|
|
||||||
if hasattr(mcrfpy, 'automation'):
|
|
||||||
lines.append("## Automation Module")
|
|
||||||
lines.append("")
|
|
||||||
lines.append("The `mcrfpy.automation` module provides testing and automation capabilities.")
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
automation = mcrfpy.automation
|
|
||||||
auto_funcs = []
|
|
||||||
|
|
||||||
for name in sorted(dir(automation)):
|
|
||||||
if not name.startswith('_'):
|
|
||||||
obj = getattr(automation, name)
|
|
||||||
if callable(obj):
|
|
||||||
auto_funcs.append((name, obj))
|
|
||||||
|
|
||||||
for name, func in auto_funcs:
|
|
||||||
# Format as static method
|
|
||||||
func_info = get_function_info(func, name)
|
|
||||||
lines.append(f"### `automation.{func_info['signature']}`")
|
|
||||||
lines.append("")
|
|
||||||
if func_info['doc']:
|
|
||||||
lines.append(func_info['doc'])
|
|
||||||
lines.append("")
|
|
||||||
lines.append("---")
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
return '\n'.join(lines)
|
|
||||||
|
|
||||||
def generate_html_docs(markdown_content: str) -> str:
|
|
||||||
"""Convert markdown to HTML."""
|
|
||||||
# Simple conversion - in production use a proper markdown parser
|
|
||||||
html = ['<!DOCTYPE html>']
|
|
||||||
html.append('<html><head>')
|
|
||||||
html.append('<meta charset="UTF-8">')
|
|
||||||
html.append('<title>McRogueFace API Reference</title>')
|
|
||||||
html.append('<style>')
|
|
||||||
html.append('''
|
|
||||||
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
||||||
line-height: 1.6; color: #333; max-width: 900px; margin: 0 auto; padding: 20px; }
|
|
||||||
h1, h2, h3, h4, h5 { color: #2c3e50; margin-top: 24px; }
|
|
||||||
h1 { border-bottom: 2px solid #3498db; padding-bottom: 10px; }
|
|
||||||
h2 { border-bottom: 1px solid #ecf0f1; padding-bottom: 8px; }
|
|
||||||
code { background: #f4f4f4; padding: 2px 4px; border-radius: 3px; font-size: 90%; }
|
|
||||||
pre { background: #f4f4f4; padding: 12px; border-radius: 5px; overflow-x: auto; }
|
|
||||||
pre code { background: none; padding: 0; }
|
|
||||||
blockquote { border-left: 4px solid #3498db; margin: 0; padding-left: 16px; color: #7f8c8d; }
|
|
||||||
hr { border: none; border-top: 1px solid #ecf0f1; margin: 24px 0; }
|
|
||||||
a { color: #3498db; text-decoration: none; }
|
|
||||||
a:hover { text-decoration: underline; }
|
|
||||||
.property { color: #27ae60; }
|
|
||||||
.method { color: #2980b9; }
|
|
||||||
.class-name { color: #8e44ad; font-weight: bold; }
|
|
||||||
ul { padding-left: 24px; }
|
|
||||||
li { margin: 4px 0; }
|
|
||||||
''')
|
|
||||||
html.append('</style>')
|
|
||||||
html.append('</head><body>')
|
|
||||||
|
|
||||||
# Very basic markdown to HTML conversion
|
|
||||||
lines = markdown_content.split('\n')
|
|
||||||
in_code_block = False
|
|
||||||
in_list = False
|
|
||||||
|
|
||||||
for line in lines:
|
|
||||||
stripped = line.strip()
|
|
||||||
|
|
||||||
if stripped.startswith('```'):
|
|
||||||
if in_code_block:
|
|
||||||
html.append('</code></pre>')
|
|
||||||
in_code_block = False
|
|
||||||
else:
|
|
||||||
lang = stripped[3:] or 'python'
|
|
||||||
html.append(f'<pre><code class="language-{lang}">')
|
|
||||||
in_code_block = True
|
|
||||||
continue
|
|
||||||
|
|
||||||
if in_code_block:
|
|
||||||
html.append(line)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Headers
|
|
||||||
if stripped.startswith('#'):
|
|
||||||
level = len(stripped.split()[0])
|
|
||||||
text = stripped[level:].strip()
|
|
||||||
html.append(f'<h{level}>{text}</h{level}>')
|
|
||||||
# Lists
|
|
||||||
elif stripped.startswith('- '):
|
|
||||||
if not in_list:
|
|
||||||
html.append('<ul>')
|
|
||||||
in_list = True
|
|
||||||
html.append(f'<li>{stripped[2:]}</li>')
|
|
||||||
# Horizontal rule
|
|
||||||
elif stripped == '---':
|
|
||||||
if in_list:
|
|
||||||
html.append('</ul>')
|
|
||||||
in_list = False
|
|
||||||
html.append('<hr>')
|
|
||||||
# Emphasis
|
|
||||||
elif stripped.startswith('*') and stripped.endswith('*') and len(stripped) > 2:
|
|
||||||
html.append(f'<em>{stripped[1:-1]}</em>')
|
|
||||||
# Bold
|
|
||||||
elif stripped.startswith('**') and stripped.endswith('**'):
|
|
||||||
html.append(f'<strong>{stripped[2:-2]}</strong>')
|
|
||||||
# Regular paragraph
|
|
||||||
elif stripped:
|
|
||||||
if in_list:
|
|
||||||
html.append('</ul>')
|
|
||||||
in_list = False
|
|
||||||
# Convert inline code
|
|
||||||
text = stripped
|
|
||||||
if '`' in text:
|
|
||||||
import re
|
|
||||||
text = re.sub(r'`([^`]+)`', r'<code>\1</code>', text)
|
|
||||||
html.append(f'<p>{text}</p>')
|
|
||||||
else:
|
|
||||||
if in_list:
|
|
||||||
html.append('</ul>')
|
|
||||||
in_list = False
|
|
||||||
# Empty line
|
|
||||||
html.append('')
|
|
||||||
|
|
||||||
if in_list:
|
|
||||||
html.append('</ul>')
|
|
||||||
if in_code_block:
|
|
||||||
html.append('</code></pre>')
|
|
||||||
|
|
||||||
html.append('</body></html>')
|
|
||||||
return '\n'.join(html)
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""Generate API documentation in multiple formats."""
|
|
||||||
print("Generating McRogueFace API Documentation...")
|
|
||||||
|
|
||||||
# Create docs directory
|
|
||||||
docs_dir = Path("docs")
|
|
||||||
docs_dir.mkdir(exist_ok=True)
|
|
||||||
|
|
||||||
# Generate markdown documentation
|
|
||||||
print("- Generating Markdown documentation...")
|
|
||||||
markdown_content = generate_markdown_docs()
|
|
||||||
|
|
||||||
# Write markdown
|
|
||||||
md_path = docs_dir / "API_REFERENCE.md"
|
|
||||||
with open(md_path, 'w') as f:
|
|
||||||
f.write(markdown_content)
|
|
||||||
print(f" ✓ Written to {md_path}")
|
|
||||||
|
|
||||||
# Generate HTML
|
|
||||||
print("- Generating HTML documentation...")
|
|
||||||
html_content = generate_html_docs(markdown_content)
|
|
||||||
|
|
||||||
# Write HTML
|
|
||||||
html_path = docs_dir / "api_reference.html"
|
|
||||||
with open(html_path, 'w') as f:
|
|
||||||
f.write(html_content)
|
|
||||||
print(f" ✓ Written to {html_path}")
|
|
||||||
|
|
||||||
# Summary statistics
|
|
||||||
lines = markdown_content.split('\n')
|
|
||||||
class_count = markdown_content.count('### class')
|
|
||||||
func_count = len([l for l in lines if l.strip().startswith('### `') and 'class' not in l])
|
|
||||||
|
|
||||||
print("\nDocumentation Statistics:")
|
|
||||||
print(f"- Classes documented: {class_count}")
|
|
||||||
print(f"- Functions documented: {func_count}")
|
|
||||||
print(f"- Total lines: {len(lines)}")
|
|
||||||
print(f"- File size: {len(markdown_content):,} bytes")
|
|
||||||
|
|
||||||
print("\nAPI documentation generated successfully!")
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,119 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
"""Generate API reference documentation for McRogueFace - Simple version."""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import datetime
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import mcrfpy
|
|
||||||
|
|
||||||
def generate_markdown_docs():
|
|
||||||
"""Generate markdown API documentation."""
|
|
||||||
lines = []
|
|
||||||
|
|
||||||
# Header
|
|
||||||
lines.append("# McRogueFace API Reference")
|
|
||||||
lines.append("")
|
|
||||||
lines.append("*Generated on {}*".format(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')))
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
# Module description
|
|
||||||
if mcrfpy.__doc__:
|
|
||||||
lines.append("## Overview")
|
|
||||||
lines.append("")
|
|
||||||
lines.extend(mcrfpy.__doc__.strip().split('\n'))
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
# Collect all components
|
|
||||||
classes = []
|
|
||||||
functions = []
|
|
||||||
|
|
||||||
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))
|
|
||||||
|
|
||||||
# Document classes
|
|
||||||
lines.append("## Classes")
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
for name, cls in classes:
|
|
||||||
lines.append("### class {}".format(name))
|
|
||||||
if cls.__doc__:
|
|
||||||
doc_lines = cls.__doc__.strip().split('\n')
|
|
||||||
for line in doc_lines[:5]: # First 5 lines
|
|
||||||
lines.append(line)
|
|
||||||
lines.append("")
|
|
||||||
lines.append("---")
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
# Document functions
|
|
||||||
lines.append("## Functions")
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
for name, func in functions:
|
|
||||||
lines.append("### {}".format(name))
|
|
||||||
if func.__doc__:
|
|
||||||
doc_lines = func.__doc__.strip().split('\n')
|
|
||||||
for line in doc_lines[:5]: # First 5 lines
|
|
||||||
lines.append(line)
|
|
||||||
lines.append("")
|
|
||||||
lines.append("---")
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
# Automation module
|
|
||||||
if hasattr(mcrfpy, 'automation'):
|
|
||||||
lines.append("## Automation Module")
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
automation = mcrfpy.automation
|
|
||||||
for name in sorted(dir(automation)):
|
|
||||||
if not name.startswith('_'):
|
|
||||||
obj = getattr(automation, name)
|
|
||||||
if callable(obj):
|
|
||||||
lines.append("### automation.{}".format(name))
|
|
||||||
if obj.__doc__:
|
|
||||||
lines.append(obj.__doc__.strip().split('\n')[0])
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
return '\n'.join(lines)
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""Generate API documentation."""
|
|
||||||
print("Generating McRogueFace API Documentation...")
|
|
||||||
|
|
||||||
# Create docs directory
|
|
||||||
docs_dir = Path("docs")
|
|
||||||
docs_dir.mkdir(exist_ok=True)
|
|
||||||
|
|
||||||
# Generate markdown
|
|
||||||
markdown_content = generate_markdown_docs()
|
|
||||||
|
|
||||||
# Write markdown
|
|
||||||
md_path = docs_dir / "API_REFERENCE.md"
|
|
||||||
with open(md_path, 'w') as f:
|
|
||||||
f.write(markdown_content)
|
|
||||||
print("Written to {}".format(md_path))
|
|
||||||
|
|
||||||
# Summary
|
|
||||||
lines = markdown_content.split('\n')
|
|
||||||
class_count = markdown_content.count('### class')
|
|
||||||
func_count = markdown_content.count('### ') - class_count - markdown_content.count('### automation.')
|
|
||||||
|
|
||||||
print("\nDocumentation Statistics:")
|
|
||||||
print("- Classes documented: {}".format(class_count))
|
|
||||||
print("- Functions documented: {}".format(func_count))
|
|
||||||
print("- Total lines: {}".format(len(lines)))
|
|
||||||
|
|
||||||
print("\nAPI documentation generated successfully!")
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
|
|
@ -1,960 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
"""Generate COMPLETE HTML API reference documentation for McRogueFace with NO missing methods."""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import datetime
|
|
||||||
import html
|
|
||||||
from pathlib import Path
|
|
||||||
import mcrfpy
|
|
||||||
|
|
||||||
def escape_html(text: str) -> str:
|
|
||||||
"""Escape HTML special characters."""
|
|
||||||
return html.escape(text) if text else ""
|
|
||||||
|
|
||||||
def get_complete_method_documentation():
|
|
||||||
"""Return complete documentation for ALL methods across all classes."""
|
|
||||||
return {
|
|
||||||
# Base Drawable methods (inherited by all UI elements)
|
|
||||||
'Drawable': {
|
|
||||||
'get_bounds': {
|
|
||||||
'signature': 'get_bounds()',
|
|
||||||
'description': 'Get the bounding rectangle of this drawable element.',
|
|
||||||
'returns': 'tuple: (x, y, width, height) representing the element\'s bounds',
|
|
||||||
'note': 'The bounds are in screen coordinates and account for current position and size.'
|
|
||||||
},
|
|
||||||
'move': {
|
|
||||||
'signature': 'move(dx, dy)',
|
|
||||||
'description': 'Move the element by a relative offset.',
|
|
||||||
'args': [
|
|
||||||
('dx', 'float', 'Horizontal offset in pixels'),
|
|
||||||
('dy', 'float', 'Vertical offset in pixels')
|
|
||||||
],
|
|
||||||
'note': 'This modifies the x and y position properties by the given amounts.'
|
|
||||||
},
|
|
||||||
'resize': {
|
|
||||||
'signature': 'resize(width, height)',
|
|
||||||
'description': 'Resize the element to new dimensions.',
|
|
||||||
'args': [
|
|
||||||
('width', 'float', 'New width in pixels'),
|
|
||||||
('height', 'float', 'New height in pixels')
|
|
||||||
],
|
|
||||||
'note': 'For Caption and Sprite, this may not change actual size if determined by content.'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
# Entity-specific methods
|
|
||||||
'Entity': {
|
|
||||||
'at': {
|
|
||||||
'signature': 'at(x, y)',
|
|
||||||
'description': 'Check if this entity is at the specified grid coordinates.',
|
|
||||||
'args': [
|
|
||||||
('x', 'int', 'Grid x coordinate to check'),
|
|
||||||
('y', 'int', 'Grid y coordinate to check')
|
|
||||||
],
|
|
||||||
'returns': 'bool: True if entity is at position (x, y), False otherwise'
|
|
||||||
},
|
|
||||||
'die': {
|
|
||||||
'signature': 'die()',
|
|
||||||
'description': 'Remove this entity from its parent grid.',
|
|
||||||
'note': 'The entity object remains valid but is no longer rendered or updated.'
|
|
||||||
},
|
|
||||||
'index': {
|
|
||||||
'signature': 'index()',
|
|
||||||
'description': 'Get the index of this entity in its parent grid\'s entity list.',
|
|
||||||
'returns': 'int: Index position, or -1 if not in a grid'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
# Grid-specific methods
|
|
||||||
'Grid': {
|
|
||||||
'at': {
|
|
||||||
'signature': 'at(x, y)',
|
|
||||||
'description': 'Get the GridPoint at the specified grid coordinates.',
|
|
||||||
'args': [
|
|
||||||
('x', 'int', 'Grid x coordinate'),
|
|
||||||
('y', 'int', 'Grid y coordinate')
|
|
||||||
],
|
|
||||||
'returns': 'GridPoint or None: The grid point at (x, y), or None if out of bounds'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
# Collection methods
|
|
||||||
'EntityCollection': {
|
|
||||||
'append': {
|
|
||||||
'signature': 'append(entity)',
|
|
||||||
'description': 'Add an entity to the end of the collection.',
|
|
||||||
'args': [('entity', 'Entity', 'The entity to add')]
|
|
||||||
},
|
|
||||||
'remove': {
|
|
||||||
'signature': 'remove(entity)',
|
|
||||||
'description': 'Remove the first occurrence of an entity from the collection.',
|
|
||||||
'args': [('entity', 'Entity', 'The entity to remove')],
|
|
||||||
'raises': 'ValueError: If entity is not in collection'
|
|
||||||
},
|
|
||||||
'extend': {
|
|
||||||
'signature': 'extend(iterable)',
|
|
||||||
'description': 'Add all entities from an iterable to the collection.',
|
|
||||||
'args': [('iterable', 'Iterable[Entity]', 'Entities to add')]
|
|
||||||
},
|
|
||||||
'count': {
|
|
||||||
'signature': 'count(entity)',
|
|
||||||
'description': 'Count the number of occurrences of an entity in the collection.',
|
|
||||||
'args': [('entity', 'Entity', 'The entity to count')],
|
|
||||||
'returns': 'int: Number of times entity appears in collection'
|
|
||||||
},
|
|
||||||
'index': {
|
|
||||||
'signature': 'index(entity)',
|
|
||||||
'description': 'Find the index of the first occurrence of an entity.',
|
|
||||||
'args': [('entity', 'Entity', 'The entity to find')],
|
|
||||||
'returns': 'int: Index of entity in collection',
|
|
||||||
'raises': 'ValueError: If entity is not in collection'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
'UICollection': {
|
|
||||||
'append': {
|
|
||||||
'signature': 'append(drawable)',
|
|
||||||
'description': 'Add a drawable element to the end of the collection.',
|
|
||||||
'args': [('drawable', 'UIDrawable', 'The drawable element to add')]
|
|
||||||
},
|
|
||||||
'remove': {
|
|
||||||
'signature': 'remove(drawable)',
|
|
||||||
'description': 'Remove the first occurrence of a drawable from the collection.',
|
|
||||||
'args': [('drawable', 'UIDrawable', 'The drawable to remove')],
|
|
||||||
'raises': 'ValueError: If drawable is not in collection'
|
|
||||||
},
|
|
||||||
'extend': {
|
|
||||||
'signature': 'extend(iterable)',
|
|
||||||
'description': 'Add all drawables from an iterable to the collection.',
|
|
||||||
'args': [('iterable', 'Iterable[UIDrawable]', 'Drawables to add')]
|
|
||||||
},
|
|
||||||
'count': {
|
|
||||||
'signature': 'count(drawable)',
|
|
||||||
'description': 'Count the number of occurrences of a drawable in the collection.',
|
|
||||||
'args': [('drawable', 'UIDrawable', 'The drawable to count')],
|
|
||||||
'returns': 'int: Number of times drawable appears in collection'
|
|
||||||
},
|
|
||||||
'index': {
|
|
||||||
'signature': 'index(drawable)',
|
|
||||||
'description': 'Find the index of the first occurrence of a drawable.',
|
|
||||||
'args': [('drawable', 'UIDrawable', 'The drawable to find')],
|
|
||||||
'returns': 'int: Index of drawable in collection',
|
|
||||||
'raises': 'ValueError: If drawable is not in collection'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
# Animation methods
|
|
||||||
'Animation': {
|
|
||||||
'get_current_value': {
|
|
||||||
'signature': 'get_current_value()',
|
|
||||||
'description': 'Get the current interpolated value of the animation.',
|
|
||||||
'returns': 'float: Current animation value between start and end'
|
|
||||||
},
|
|
||||||
'start': {
|
|
||||||
'signature': 'start(target)',
|
|
||||||
'description': 'Start the animation on a target UI element.',
|
|
||||||
'args': [('target', 'UIDrawable', 'The UI element to animate')],
|
|
||||||
'note': 'The target must have the property specified in the animation constructor.'
|
|
||||||
},
|
|
||||||
'update': {
|
|
||||||
'signature': 'update(delta_time)',
|
|
||||||
'description': 'Update the animation by the given time delta.',
|
|
||||||
'args': [('delta_time', 'float', 'Time elapsed since last update in seconds')],
|
|
||||||
'returns': 'bool: True if animation is still running, False if finished'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
# Color methods
|
|
||||||
'Color': {
|
|
||||||
'from_hex': {
|
|
||||||
'signature': 'from_hex(hex_string)',
|
|
||||||
'description': 'Create a Color from a hexadecimal color string.',
|
|
||||||
'args': [('hex_string', 'str', 'Hex color string (e.g., "#FF0000" or "FF0000")')],
|
|
||||||
'returns': 'Color: New Color object from hex string',
|
|
||||||
'example': 'red = Color.from_hex("#FF0000")'
|
|
||||||
},
|
|
||||||
'to_hex': {
|
|
||||||
'signature': 'to_hex()',
|
|
||||||
'description': 'Convert this Color to a hexadecimal string.',
|
|
||||||
'returns': 'str: Hex color string in format "#RRGGBB"',
|
|
||||||
'example': 'hex_str = color.to_hex() # Returns "#FF0000"'
|
|
||||||
},
|
|
||||||
'lerp': {
|
|
||||||
'signature': 'lerp(other, t)',
|
|
||||||
'description': 'Linearly interpolate between this color and another.',
|
|
||||||
'args': [
|
|
||||||
('other', 'Color', 'The color to interpolate towards'),
|
|
||||||
('t', 'float', 'Interpolation factor from 0.0 to 1.0')
|
|
||||||
],
|
|
||||||
'returns': 'Color: New interpolated Color object',
|
|
||||||
'example': 'mixed = red.lerp(blue, 0.5) # 50% between red and blue'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
# Vector methods
|
|
||||||
'Vector': {
|
|
||||||
'magnitude': {
|
|
||||||
'signature': 'magnitude()',
|
|
||||||
'description': 'Calculate the length/magnitude of this vector.',
|
|
||||||
'returns': 'float: The magnitude of the vector',
|
|
||||||
'example': 'length = vector.magnitude()'
|
|
||||||
},
|
|
||||||
'magnitude_squared': {
|
|
||||||
'signature': 'magnitude_squared()',
|
|
||||||
'description': 'Calculate the squared magnitude of this vector.',
|
|
||||||
'returns': 'float: The squared magnitude (faster than magnitude())',
|
|
||||||
'note': 'Use this for comparisons to avoid expensive square root calculation.'
|
|
||||||
},
|
|
||||||
'normalize': {
|
|
||||||
'signature': 'normalize()',
|
|
||||||
'description': 'Return a unit vector in the same direction.',
|
|
||||||
'returns': 'Vector: New normalized vector with magnitude 1.0',
|
|
||||||
'raises': 'ValueError: If vector has zero magnitude'
|
|
||||||
},
|
|
||||||
'dot': {
|
|
||||||
'signature': 'dot(other)',
|
|
||||||
'description': 'Calculate the dot product with another vector.',
|
|
||||||
'args': [('other', 'Vector', 'The other vector')],
|
|
||||||
'returns': 'float: Dot product of the two vectors'
|
|
||||||
},
|
|
||||||
'distance_to': {
|
|
||||||
'signature': 'distance_to(other)',
|
|
||||||
'description': 'Calculate the distance to another vector.',
|
|
||||||
'args': [('other', 'Vector', 'The other vector')],
|
|
||||||
'returns': 'float: Distance between the two vectors'
|
|
||||||
},
|
|
||||||
'angle': {
|
|
||||||
'signature': 'angle()',
|
|
||||||
'description': 'Get the angle of this vector in radians.',
|
|
||||||
'returns': 'float: Angle in radians from positive x-axis'
|
|
||||||
},
|
|
||||||
'copy': {
|
|
||||||
'signature': 'copy()',
|
|
||||||
'description': 'Create a copy of this vector.',
|
|
||||||
'returns': 'Vector: New Vector object with same x and y values'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
# Scene methods
|
|
||||||
'Scene': {
|
|
||||||
'activate': {
|
|
||||||
'signature': 'activate()',
|
|
||||||
'description': 'Make this scene the active scene.',
|
|
||||||
'note': 'Equivalent to calling setScene() with this scene\'s name.'
|
|
||||||
},
|
|
||||||
'get_ui': {
|
|
||||||
'signature': 'get_ui()',
|
|
||||||
'description': 'Get the UI element collection for this scene.',
|
|
||||||
'returns': 'UICollection: Collection of all UI elements in this scene'
|
|
||||||
},
|
|
||||||
'keypress': {
|
|
||||||
'signature': 'keypress(handler)',
|
|
||||||
'description': 'Register a keyboard handler function for this scene.',
|
|
||||||
'args': [('handler', 'callable', 'Function that takes (key_name: str, is_pressed: bool)')],
|
|
||||||
'note': 'Alternative to overriding the on_keypress method.'
|
|
||||||
},
|
|
||||||
'register_keyboard': {
|
|
||||||
'signature': 'register_keyboard(callable)',
|
|
||||||
'description': 'Register a keyboard event handler function for the scene.',
|
|
||||||
'args': [('callable', 'callable', 'Function that takes (key: str, action: str) parameters')],
|
|
||||||
'note': 'Alternative to overriding the on_keypress method when subclassing Scene objects.',
|
|
||||||
'example': '''def handle_keyboard(key, action):
|
|
||||||
print(f"Key '{key}' was {action}")
|
|
||||||
if key == "q" and action == "press":
|
|
||||||
# Handle quit
|
|
||||||
pass
|
|
||||||
scene.register_keyboard(handle_keyboard)'''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
# Timer methods
|
|
||||||
'Timer': {
|
|
||||||
'pause': {
|
|
||||||
'signature': 'pause()',
|
|
||||||
'description': 'Pause the timer, stopping its callback execution.',
|
|
||||||
'note': 'Use resume() to continue the timer from where it was paused.'
|
|
||||||
},
|
|
||||||
'resume': {
|
|
||||||
'signature': 'resume()',
|
|
||||||
'description': 'Resume a paused timer.',
|
|
||||||
'note': 'Has no effect if timer is not paused.'
|
|
||||||
},
|
|
||||||
'cancel': {
|
|
||||||
'signature': 'cancel()',
|
|
||||||
'description': 'Cancel the timer and remove it from the system.',
|
|
||||||
'note': 'After cancelling, the timer object cannot be reused.'
|
|
||||||
},
|
|
||||||
'restart': {
|
|
||||||
'signature': 'restart()',
|
|
||||||
'description': 'Restart the timer from the beginning.',
|
|
||||||
'note': 'Resets the timer\'s internal clock to zero.'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
# Window methods
|
|
||||||
'Window': {
|
|
||||||
'get': {
|
|
||||||
'signature': 'get()',
|
|
||||||
'description': 'Get the Window singleton instance.',
|
|
||||||
'returns': 'Window: The singleton window object',
|
|
||||||
'note': 'This is a static method that returns the same instance every time.'
|
|
||||||
},
|
|
||||||
'center': {
|
|
||||||
'signature': 'center()',
|
|
||||||
'description': 'Center the window on the screen.',
|
|
||||||
'note': 'Only works if the window is not fullscreen.'
|
|
||||||
},
|
|
||||||
'screenshot': {
|
|
||||||
'signature': 'screenshot(filename)',
|
|
||||||
'description': 'Take a screenshot and save it to a file.',
|
|
||||||
'args': [('filename', 'str', 'Path where to save the screenshot')],
|
|
||||||
'note': 'Supports PNG, JPG, and BMP formats based on file extension.'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_complete_function_documentation():
|
|
||||||
"""Return complete documentation for ALL module functions."""
|
|
||||||
return {
|
|
||||||
# Scene Management
|
|
||||||
'createScene': {
|
|
||||||
'signature': 'createScene(name: str) -> None',
|
|
||||||
'description': 'Create a new empty scene with the given name.',
|
|
||||||
'args': [('name', 'str', 'Unique name for the new scene')],
|
|
||||||
'raises': 'ValueError: If a scene with this name already exists',
|
|
||||||
'note': 'The scene is created but not made active. Use setScene() to switch to it.',
|
|
||||||
'example': 'mcrfpy.createScene("game_over")'
|
|
||||||
},
|
|
||||||
'setScene': {
|
|
||||||
'signature': 'setScene(scene: str, transition: str = None, duration: float = 0.0) -> None',
|
|
||||||
'description': 'Switch to a different scene with optional transition effect.',
|
|
||||||
'args': [
|
|
||||||
('scene', 'str', 'Name of the scene to switch to'),
|
|
||||||
('transition', 'str', 'Transition type: "fade", "slide_left", "slide_right", "slide_up", "slide_down"'),
|
|
||||||
('duration', 'float', 'Transition duration in seconds (default: 0.0 for instant)')
|
|
||||||
],
|
|
||||||
'raises': 'KeyError: If the scene doesn\'t exist',
|
|
||||||
'example': 'mcrfpy.setScene("game", "fade", 0.5)'
|
|
||||||
},
|
|
||||||
'currentScene': {
|
|
||||||
'signature': 'currentScene() -> str',
|
|
||||||
'description': 'Get the name of the currently active scene.',
|
|
||||||
'returns': 'str: Name of the current scene',
|
|
||||||
'example': 'scene_name = mcrfpy.currentScene()'
|
|
||||||
},
|
|
||||||
'sceneUI': {
|
|
||||||
'signature': 'sceneUI(scene: str = None) -> UICollection',
|
|
||||||
'description': 'Get all UI elements for a scene.',
|
|
||||||
'args': [('scene', 'str', 'Scene name. If None, uses current scene')],
|
|
||||||
'returns': 'UICollection: All UI elements in the scene',
|
|
||||||
'raises': 'KeyError: If the specified scene doesn\'t exist',
|
|
||||||
'example': 'ui_elements = mcrfpy.sceneUI("game")'
|
|
||||||
},
|
|
||||||
'keypressScene': {
|
|
||||||
'signature': 'keypressScene(handler: callable) -> None',
|
|
||||||
'description': 'Set the keyboard event handler for the current scene.',
|
|
||||||
'args': [('handler', 'callable', 'Function that receives (key_name: str, is_pressed: bool)')],
|
|
||||||
'example': '''def on_key(key, pressed):
|
|
||||||
if key == "SPACE" and pressed:
|
|
||||||
player.jump()
|
|
||||||
mcrfpy.keypressScene(on_key)'''
|
|
||||||
},
|
|
||||||
|
|
||||||
# Audio Functions
|
|
||||||
'createSoundBuffer': {
|
|
||||||
'signature': 'createSoundBuffer(filename: str) -> int',
|
|
||||||
'description': 'Load a sound effect from a file and return its buffer ID.',
|
|
||||||
'args': [('filename', 'str', 'Path to the sound file (WAV, OGG, FLAC)')],
|
|
||||||
'returns': 'int: Buffer ID for use with playSound()',
|
|
||||||
'raises': 'RuntimeError: If the file cannot be loaded',
|
|
||||||
'example': 'jump_sound = mcrfpy.createSoundBuffer("assets/jump.wav")'
|
|
||||||
},
|
|
||||||
'loadMusic': {
|
|
||||||
'signature': 'loadMusic(filename: str, loop: bool = True) -> None',
|
|
||||||
'description': 'Load and immediately play background music from a file.',
|
|
||||||
'args': [
|
|
||||||
('filename', 'str', 'Path to the music file (WAV, OGG, FLAC)'),
|
|
||||||
('loop', 'bool', 'Whether to loop the music (default: True)')
|
|
||||||
],
|
|
||||||
'note': 'Only one music track can play at a time. Loading new music stops the current track.',
|
|
||||||
'example': 'mcrfpy.loadMusic("assets/background.ogg", True)'
|
|
||||||
},
|
|
||||||
'playSound': {
|
|
||||||
'signature': 'playSound(buffer_id: int) -> None',
|
|
||||||
'description': 'Play a sound effect using a previously loaded buffer.',
|
|
||||||
'args': [('buffer_id', 'int', 'Sound buffer ID returned by createSoundBuffer()')],
|
|
||||||
'raises': 'RuntimeError: If the buffer ID is invalid',
|
|
||||||
'example': 'mcrfpy.playSound(jump_sound)'
|
|
||||||
},
|
|
||||||
'getMusicVolume': {
|
|
||||||
'signature': 'getMusicVolume() -> int',
|
|
||||||
'description': 'Get the current music volume level.',
|
|
||||||
'returns': 'int: Current volume (0-100)',
|
|
||||||
'example': 'current_volume = mcrfpy.getMusicVolume()'
|
|
||||||
},
|
|
||||||
'getSoundVolume': {
|
|
||||||
'signature': 'getSoundVolume() -> int',
|
|
||||||
'description': 'Get the current sound effects volume level.',
|
|
||||||
'returns': 'int: Current volume (0-100)',
|
|
||||||
'example': 'current_volume = mcrfpy.getSoundVolume()'
|
|
||||||
},
|
|
||||||
'setMusicVolume': {
|
|
||||||
'signature': 'setMusicVolume(volume: int) -> None',
|
|
||||||
'description': 'Set the global music volume.',
|
|
||||||
'args': [('volume', 'int', 'Volume level from 0 (silent) to 100 (full volume)')],
|
|
||||||
'example': 'mcrfpy.setMusicVolume(50) # Set to 50% volume'
|
|
||||||
},
|
|
||||||
'setSoundVolume': {
|
|
||||||
'signature': 'setSoundVolume(volume: int) -> None',
|
|
||||||
'description': 'Set the global sound effects volume.',
|
|
||||||
'args': [('volume', 'int', 'Volume level from 0 (silent) to 100 (full volume)')],
|
|
||||||
'example': 'mcrfpy.setSoundVolume(75) # Set to 75% volume'
|
|
||||||
},
|
|
||||||
|
|
||||||
# UI Utilities
|
|
||||||
'find': {
|
|
||||||
'signature': 'find(name: str, scene: str = None) -> UIDrawable | None',
|
|
||||||
'description': 'Find the first UI element with the specified name.',
|
|
||||||
'args': [
|
|
||||||
('name', 'str', 'Exact name to search for'),
|
|
||||||
('scene', 'str', 'Scene to search in (default: current scene)')
|
|
||||||
],
|
|
||||||
'returns': 'UIDrawable or None: The found element, or None if not found',
|
|
||||||
'note': 'Searches scene UI elements and entities within grids.',
|
|
||||||
'example': 'button = mcrfpy.find("start_button")'
|
|
||||||
},
|
|
||||||
'findAll': {
|
|
||||||
'signature': 'findAll(pattern: str, scene: str = None) -> list',
|
|
||||||
'description': 'Find all UI elements matching a name pattern.',
|
|
||||||
'args': [
|
|
||||||
('pattern', 'str', 'Name pattern with optional wildcards (* matches any characters)'),
|
|
||||||
('scene', 'str', 'Scene to search in (default: current scene)')
|
|
||||||
],
|
|
||||||
'returns': 'list: All matching UI elements and entities',
|
|
||||||
'example': 'enemies = mcrfpy.findAll("enemy_*")'
|
|
||||||
},
|
|
||||||
|
|
||||||
# System Functions
|
|
||||||
'exit': {
|
|
||||||
'signature': 'exit() -> None',
|
|
||||||
'description': 'Cleanly shut down the game engine and exit the application.',
|
|
||||||
'note': 'This immediately closes the window and terminates the program.',
|
|
||||||
'example': 'mcrfpy.exit()'
|
|
||||||
},
|
|
||||||
'getMetrics': {
|
|
||||||
'signature': 'getMetrics() -> dict',
|
|
||||||
'description': 'Get current performance metrics.',
|
|
||||||
'returns': '''dict: Performance data with keys:
|
|
||||||
- frame_time: Last frame duration in seconds
|
|
||||||
- avg_frame_time: Average frame time
|
|
||||||
- fps: Frames per second
|
|
||||||
- draw_calls: Number of draw calls
|
|
||||||
- ui_elements: Total UI element count
|
|
||||||
- visible_elements: Visible element count
|
|
||||||
- current_frame: Frame counter
|
|
||||||
- runtime: Total runtime in seconds''',
|
|
||||||
'example': 'metrics = mcrfpy.getMetrics()'
|
|
||||||
},
|
|
||||||
'setTimer': {
|
|
||||||
'signature': 'setTimer(name: str, handler: callable, interval: int) -> None',
|
|
||||||
'description': 'Create or update a recurring timer.',
|
|
||||||
'args': [
|
|
||||||
('name', 'str', 'Unique identifier for the timer'),
|
|
||||||
('handler', 'callable', 'Function called with (runtime: float) parameter'),
|
|
||||||
('interval', 'int', 'Time between calls in milliseconds')
|
|
||||||
],
|
|
||||||
'note': 'If a timer with this name exists, it will be replaced.',
|
|
||||||
'example': '''def update_score(runtime):
|
|
||||||
score += 1
|
|
||||||
mcrfpy.setTimer("score_update", update_score, 1000)'''
|
|
||||||
},
|
|
||||||
'delTimer': {
|
|
||||||
'signature': 'delTimer(name: str) -> None',
|
|
||||||
'description': 'Stop and remove a timer.',
|
|
||||||
'args': [('name', 'str', 'Timer identifier to remove')],
|
|
||||||
'note': 'No error is raised if the timer doesn\'t exist.',
|
|
||||||
'example': 'mcrfpy.delTimer("score_update")'
|
|
||||||
},
|
|
||||||
'setScale': {
|
|
||||||
'signature': 'setScale(multiplier: float) -> None',
|
|
||||||
'description': 'Scale the game window size.',
|
|
||||||
'args': [('multiplier', 'float', 'Scale factor (e.g., 2.0 for double size)')],
|
|
||||||
'note': 'The internal resolution remains 1024x768, but the window is scaled.',
|
|
||||||
'example': 'mcrfpy.setScale(2.0) # Double the window size'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_complete_property_documentation():
|
|
||||||
"""Return complete documentation for ALL properties."""
|
|
||||||
return {
|
|
||||||
'Animation': {
|
|
||||||
'property': 'str: Name of the property being animated (e.g., "x", "y", "scale")',
|
|
||||||
'duration': 'float: Total duration of the animation in seconds',
|
|
||||||
'elapsed_time': 'float: Time elapsed since animation started (read-only)',
|
|
||||||
'current_value': 'float: Current interpolated value of the animation (read-only)',
|
|
||||||
'is_running': 'bool: True if animation is currently running (read-only)',
|
|
||||||
'is_finished': 'bool: True if animation has completed (read-only)'
|
|
||||||
},
|
|
||||||
'GridPoint': {
|
|
||||||
'x': 'int: Grid x coordinate of this point',
|
|
||||||
'y': 'int: Grid y coordinate of this point',
|
|
||||||
'texture_index': 'int: Index of the texture/sprite to display at this point',
|
|
||||||
'solid': 'bool: Whether this point blocks movement',
|
|
||||||
'transparent': 'bool: Whether this point allows light/vision through',
|
|
||||||
'color': 'Color: Color tint applied to the texture at this point'
|
|
||||||
},
|
|
||||||
'GridPointState': {
|
|
||||||
'visible': 'bool: Whether this point is currently visible to the player',
|
|
||||||
'discovered': 'bool: Whether this point has been discovered/explored',
|
|
||||||
'custom_flags': 'int: Bitfield for custom game-specific flags'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def generate_complete_html_documentation():
|
|
||||||
"""Generate complete HTML documentation with NO missing methods."""
|
|
||||||
|
|
||||||
# Get all documentation data
|
|
||||||
method_docs = get_complete_method_documentation()
|
|
||||||
function_docs = get_complete_function_documentation()
|
|
||||||
property_docs = get_complete_property_documentation()
|
|
||||||
|
|
||||||
html_parts = []
|
|
||||||
|
|
||||||
# HTML header with enhanced styling
|
|
||||||
html_parts.append('''<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>McRogueFace API Reference - Complete Documentation</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
||||||
line-height: 1.6;
|
|
||||||
color: #333;
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 20px;
|
|
||||||
background: #f8f9fa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
background: white;
|
|
||||||
padding: 30px;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
color: #2c3e50;
|
|
||||||
border-bottom: 3px solid #3498db;
|
|
||||||
padding-bottom: 15px;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
color: #34495e;
|
|
||||||
border-bottom: 2px solid #ecf0f1;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
margin-top: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
color: #2c3e50;
|
|
||||||
margin-top: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
color: #34495e;
|
|
||||||
margin-top: 20px;
|
|
||||||
font-size: 1.1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
h5 {
|
|
||||||
color: #555;
|
|
||||||
margin-top: 15px;
|
|
||||||
font-size: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
background: #f4f4f4;
|
|
||||||
padding: 2px 6px;
|
|
||||||
border-radius: 3px;
|
|
||||||
font-family: "SF Mono", Monaco, "Cascadia Code", "Roboto Mono", Consolas, monospace;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
|
||||||
background: #f8f8f8;
|
|
||||||
border: 1px solid #e1e4e8;
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 16px;
|
|
||||||
overflow-x: auto;
|
|
||||||
margin: 15px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre code {
|
|
||||||
background: none;
|
|
||||||
padding: 0;
|
|
||||||
font-size: 0.875em;
|
|
||||||
line-height: 1.45;
|
|
||||||
}
|
|
||||||
|
|
||||||
.class-name {
|
|
||||||
color: #8e44ad;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.property {
|
|
||||||
color: #27ae60;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.method {
|
|
||||||
color: #2980b9;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.function-signature {
|
|
||||||
color: #d73a49;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.method-section {
|
|
||||||
margin: 20px 0;
|
|
||||||
padding: 15px;
|
|
||||||
background: #f8f9fa;
|
|
||||||
border-radius: 6px;
|
|
||||||
border-left: 4px solid #3498db;
|
|
||||||
}
|
|
||||||
|
|
||||||
.arg-list {
|
|
||||||
margin: 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.arg-item {
|
|
||||||
margin: 8px 0;
|
|
||||||
padding: 8px;
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid #e1e4e8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.arg-name {
|
|
||||||
color: #d73a49;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.arg-type {
|
|
||||||
color: #6f42c1;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
.returns {
|
|
||||||
background: #e8f5e8;
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: 4px;
|
|
||||||
border-left: 4px solid #28a745;
|
|
||||||
margin: 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.note {
|
|
||||||
background: #fff3cd;
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: 4px;
|
|
||||||
border-left: 4px solid #ffc107;
|
|
||||||
margin: 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.example {
|
|
||||||
background: #e7f3ff;
|
|
||||||
padding: 15px;
|
|
||||||
border-radius: 4px;
|
|
||||||
border-left: 4px solid #0366d6;
|
|
||||||
margin: 15px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toc {
|
|
||||||
background: #f8f9fa;
|
|
||||||
border: 1px solid #e1e4e8;
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 20px;
|
|
||||||
margin: 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toc ul {
|
|
||||||
list-style: none;
|
|
||||||
padding-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toc li {
|
|
||||||
margin: 8px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toc a {
|
|
||||||
color: #3498db;
|
|
||||||
text-decoration: none;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toc a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
''')
|
|
||||||
|
|
||||||
# Title and overview
|
|
||||||
html_parts.append('<h1>McRogueFace API Reference - Complete Documentation</h1>')
|
|
||||||
html_parts.append(f'<p><em>Generated on {datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}</em></p>')
|
|
||||||
|
|
||||||
# Table of contents
|
|
||||||
html_parts.append('<div class="toc">')
|
|
||||||
html_parts.append('<h2>Table of Contents</h2>')
|
|
||||||
html_parts.append('<ul>')
|
|
||||||
html_parts.append('<li><a href="#functions">Functions</a></li>')
|
|
||||||
html_parts.append('<li><a href="#classes">Classes</a></li>')
|
|
||||||
html_parts.append('<li><a href="#automation">Automation Module</a></li>')
|
|
||||||
html_parts.append('</ul>')
|
|
||||||
html_parts.append('</div>')
|
|
||||||
|
|
||||||
# Functions section
|
|
||||||
html_parts.append('<h2 id="functions">Functions</h2>')
|
|
||||||
|
|
||||||
# Group functions by category
|
|
||||||
categories = {
|
|
||||||
'Scene Management': ['createScene', 'setScene', 'currentScene', 'sceneUI', 'keypressScene'],
|
|
||||||
'Audio': ['createSoundBuffer', 'loadMusic', 'playSound', 'getMusicVolume', 'getSoundVolume', 'setMusicVolume', 'setSoundVolume'],
|
|
||||||
'UI Utilities': ['find', 'findAll'],
|
|
||||||
'System': ['exit', 'getMetrics', 'setTimer', 'delTimer', 'setScale']
|
|
||||||
}
|
|
||||||
|
|
||||||
for category, functions in categories.items():
|
|
||||||
html_parts.append(f'<h3>{category}</h3>')
|
|
||||||
for func_name in functions:
|
|
||||||
if func_name in function_docs:
|
|
||||||
html_parts.append(format_function_html(func_name, function_docs[func_name]))
|
|
||||||
|
|
||||||
# Classes section
|
|
||||||
html_parts.append('<h2 id="classes">Classes</h2>')
|
|
||||||
|
|
||||||
# Get all classes from mcrfpy
|
|
||||||
classes = []
|
|
||||||
for name in sorted(dir(mcrfpy)):
|
|
||||||
if not name.startswith('_'):
|
|
||||||
obj = getattr(mcrfpy, name)
|
|
||||||
if isinstance(obj, type):
|
|
||||||
classes.append((name, obj))
|
|
||||||
|
|
||||||
# Generate class documentation
|
|
||||||
for class_name, cls in classes:
|
|
||||||
html_parts.append(format_class_html_complete(class_name, cls, method_docs, property_docs))
|
|
||||||
|
|
||||||
# Automation section
|
|
||||||
if hasattr(mcrfpy, 'automation'):
|
|
||||||
html_parts.append('<h2 id="automation">Automation Module</h2>')
|
|
||||||
html_parts.append('<p>The <code>mcrfpy.automation</code> module provides testing and automation capabilities.</p>')
|
|
||||||
|
|
||||||
automation = mcrfpy.automation
|
|
||||||
for name in sorted(dir(automation)):
|
|
||||||
if not name.startswith('_'):
|
|
||||||
obj = getattr(automation, name)
|
|
||||||
if callable(obj):
|
|
||||||
html_parts.append(f'<div class="method-section">')
|
|
||||||
html_parts.append(f'<h4><code class="function-signature">automation.{name}</code></h4>')
|
|
||||||
if obj.__doc__:
|
|
||||||
doc_parts = obj.__doc__.split(' - ')
|
|
||||||
if len(doc_parts) > 1:
|
|
||||||
html_parts.append(f'<p>{escape_html(doc_parts[1])}</p>')
|
|
||||||
else:
|
|
||||||
html_parts.append(f'<p>{escape_html(obj.__doc__)}</p>')
|
|
||||||
html_parts.append('</div>')
|
|
||||||
|
|
||||||
html_parts.append('</div>')
|
|
||||||
html_parts.append('</body>')
|
|
||||||
html_parts.append('</html>')
|
|
||||||
|
|
||||||
return '\n'.join(html_parts)
|
|
||||||
|
|
||||||
def format_function_html(func_name, func_doc):
|
|
||||||
"""Format a function with complete documentation."""
|
|
||||||
html_parts = []
|
|
||||||
|
|
||||||
html_parts.append('<div class="method-section">')
|
|
||||||
html_parts.append(f'<h4><code class="function-signature">{func_doc["signature"]}</code></h4>')
|
|
||||||
html_parts.append(f'<p>{escape_html(func_doc["description"])}</p>')
|
|
||||||
|
|
||||||
# Arguments
|
|
||||||
if 'args' in func_doc:
|
|
||||||
html_parts.append('<div class="arg-list">')
|
|
||||||
html_parts.append('<h5>Arguments:</h5>')
|
|
||||||
for arg in func_doc['args']:
|
|
||||||
html_parts.append('<div class="arg-item">')
|
|
||||||
html_parts.append(f'<span class="arg-name">{arg[0]}</span> ')
|
|
||||||
html_parts.append(f'<span class="arg-type">({arg[1]})</span>: ')
|
|
||||||
html_parts.append(f'{escape_html(arg[2])}')
|
|
||||||
html_parts.append('</div>')
|
|
||||||
html_parts.append('</div>')
|
|
||||||
|
|
||||||
# Returns
|
|
||||||
if 'returns' in func_doc:
|
|
||||||
html_parts.append('<div class="returns">')
|
|
||||||
html_parts.append(f'<strong>Returns:</strong> {escape_html(func_doc["returns"])}')
|
|
||||||
html_parts.append('</div>')
|
|
||||||
|
|
||||||
# Raises
|
|
||||||
if 'raises' in func_doc:
|
|
||||||
html_parts.append('<div class="note">')
|
|
||||||
html_parts.append(f'<strong>Raises:</strong> {escape_html(func_doc["raises"])}')
|
|
||||||
html_parts.append('</div>')
|
|
||||||
|
|
||||||
# Note
|
|
||||||
if 'note' in func_doc:
|
|
||||||
html_parts.append('<div class="note">')
|
|
||||||
html_parts.append(f'<strong>Note:</strong> {escape_html(func_doc["note"])}')
|
|
||||||
html_parts.append('</div>')
|
|
||||||
|
|
||||||
# Example
|
|
||||||
if 'example' in func_doc:
|
|
||||||
html_parts.append('<div class="example">')
|
|
||||||
html_parts.append('<h5>Example:</h5>')
|
|
||||||
html_parts.append('<pre><code>')
|
|
||||||
html_parts.append(escape_html(func_doc['example']))
|
|
||||||
html_parts.append('</code></pre>')
|
|
||||||
html_parts.append('</div>')
|
|
||||||
|
|
||||||
html_parts.append('</div>')
|
|
||||||
|
|
||||||
return '\n'.join(html_parts)
|
|
||||||
|
|
||||||
def format_class_html_complete(class_name, cls, method_docs, property_docs):
|
|
||||||
"""Format a class with complete documentation."""
|
|
||||||
html_parts = []
|
|
||||||
|
|
||||||
html_parts.append('<div class="method-section">')
|
|
||||||
html_parts.append(f'<h3><span class="class-name">{class_name}</span></h3>')
|
|
||||||
|
|
||||||
# Class description
|
|
||||||
if cls.__doc__:
|
|
||||||
html_parts.append(f'<p>{escape_html(cls.__doc__)}</p>')
|
|
||||||
|
|
||||||
# Properties
|
|
||||||
if class_name in property_docs:
|
|
||||||
html_parts.append('<h4>Properties:</h4>')
|
|
||||||
for prop_name, prop_desc in property_docs[class_name].items():
|
|
||||||
html_parts.append(f'<div class="arg-item">')
|
|
||||||
html_parts.append(f'<span class="property">{prop_name}</span>: {escape_html(prop_desc)}')
|
|
||||||
html_parts.append('</div>')
|
|
||||||
|
|
||||||
# Methods
|
|
||||||
methods_to_document = []
|
|
||||||
|
|
||||||
# Add inherited methods for UI classes
|
|
||||||
if any(base.__name__ == 'Drawable' for base in cls.__bases__ if hasattr(base, '__name__')):
|
|
||||||
methods_to_document.extend(['get_bounds', 'move', 'resize'])
|
|
||||||
|
|
||||||
# Add class-specific methods
|
|
||||||
if class_name in method_docs:
|
|
||||||
methods_to_document.extend(method_docs[class_name].keys())
|
|
||||||
|
|
||||||
# Add methods from introspection
|
|
||||||
for attr_name in dir(cls):
|
|
||||||
if not attr_name.startswith('_') and callable(getattr(cls, attr_name)):
|
|
||||||
if attr_name not in methods_to_document:
|
|
||||||
methods_to_document.append(attr_name)
|
|
||||||
|
|
||||||
if methods_to_document:
|
|
||||||
html_parts.append('<h4>Methods:</h4>')
|
|
||||||
for method_name in set(methods_to_document):
|
|
||||||
# Get method documentation
|
|
||||||
method_doc = None
|
|
||||||
if class_name in method_docs and method_name in method_docs[class_name]:
|
|
||||||
method_doc = method_docs[class_name][method_name]
|
|
||||||
elif method_name in method_docs.get('Drawable', {}):
|
|
||||||
method_doc = method_docs['Drawable'][method_name]
|
|
||||||
|
|
||||||
if method_doc:
|
|
||||||
html_parts.append(format_method_html(method_name, method_doc))
|
|
||||||
else:
|
|
||||||
# Basic method with no documentation
|
|
||||||
html_parts.append(f'<div class="arg-item">')
|
|
||||||
html_parts.append(f'<span class="method">{method_name}(...)</span>')
|
|
||||||
html_parts.append('</div>')
|
|
||||||
|
|
||||||
html_parts.append('</div>')
|
|
||||||
|
|
||||||
return '\n'.join(html_parts)
|
|
||||||
|
|
||||||
def format_method_html(method_name, method_doc):
|
|
||||||
"""Format a method with complete documentation."""
|
|
||||||
html_parts = []
|
|
||||||
|
|
||||||
html_parts.append('<div style="margin-left: 20px; margin-bottom: 15px;">')
|
|
||||||
html_parts.append(f'<h5><code class="method">{method_doc["signature"]}</code></h5>')
|
|
||||||
html_parts.append(f'<p>{escape_html(method_doc["description"])}</p>')
|
|
||||||
|
|
||||||
# Arguments
|
|
||||||
if 'args' in method_doc:
|
|
||||||
for arg in method_doc['args']:
|
|
||||||
html_parts.append(f'<div style="margin-left: 20px;">')
|
|
||||||
html_parts.append(f'<span class="arg-name">{arg[0]}</span> ')
|
|
||||||
html_parts.append(f'<span class="arg-type">({arg[1]})</span>: ')
|
|
||||||
html_parts.append(f'{escape_html(arg[2])}')
|
|
||||||
html_parts.append('</div>')
|
|
||||||
|
|
||||||
# Returns
|
|
||||||
if 'returns' in method_doc:
|
|
||||||
html_parts.append(f'<div style="margin-left: 20px; color: #28a745;">')
|
|
||||||
html_parts.append(f'<strong>Returns:</strong> {escape_html(method_doc["returns"])}')
|
|
||||||
html_parts.append('</div>')
|
|
||||||
|
|
||||||
# Note
|
|
||||||
if 'note' in method_doc:
|
|
||||||
html_parts.append(f'<div style="margin-left: 20px; color: #856404;">')
|
|
||||||
html_parts.append(f'<strong>Note:</strong> {escape_html(method_doc["note"])}')
|
|
||||||
html_parts.append('</div>')
|
|
||||||
|
|
||||||
# Example
|
|
||||||
if 'example' in method_doc:
|
|
||||||
html_parts.append(f'<div style="margin-left: 20px;">')
|
|
||||||
html_parts.append('<strong>Example:</strong>')
|
|
||||||
html_parts.append('<pre><code>')
|
|
||||||
html_parts.append(escape_html(method_doc['example']))
|
|
||||||
html_parts.append('</code></pre>')
|
|
||||||
html_parts.append('</div>')
|
|
||||||
|
|
||||||
html_parts.append('</div>')
|
|
||||||
|
|
||||||
return '\n'.join(html_parts)
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""Generate complete HTML documentation with zero missing methods."""
|
|
||||||
print("Generating COMPLETE HTML API documentation...")
|
|
||||||
|
|
||||||
# Generate HTML
|
|
||||||
html_content = generate_complete_html_documentation()
|
|
||||||
|
|
||||||
# Write to file
|
|
||||||
output_path = Path("docs/api_reference_complete.html")
|
|
||||||
output_path.parent.mkdir(exist_ok=True)
|
|
||||||
|
|
||||||
with open(output_path, 'w', encoding='utf-8') as f:
|
|
||||||
f.write(html_content)
|
|
||||||
|
|
||||||
print(f"✓ Generated {output_path}")
|
|
||||||
print(f" File size: {len(html_content):,} bytes")
|
|
||||||
|
|
||||||
# Count "..." instances
|
|
||||||
ellipsis_count = html_content.count('...')
|
|
||||||
print(f" Ellipsis instances: {ellipsis_count}")
|
|
||||||
|
|
||||||
if ellipsis_count == 0:
|
|
||||||
print("✅ SUCCESS: No missing documentation found!")
|
|
||||||
else:
|
|
||||||
print(f"❌ WARNING: {ellipsis_count} methods still need documentation")
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
|
|
@ -1,821 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
"""Generate COMPLETE Markdown API reference documentation for McRogueFace with NO missing methods."""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import datetime
|
|
||||||
from pathlib import Path
|
|
||||||
import mcrfpy
|
|
||||||
|
|
||||||
def get_complete_method_documentation():
|
|
||||||
"""Return complete documentation for ALL methods across all classes."""
|
|
||||||
return {
|
|
||||||
# Base Drawable methods (inherited by all UI elements)
|
|
||||||
'Drawable': {
|
|
||||||
'get_bounds': {
|
|
||||||
'signature': 'get_bounds()',
|
|
||||||
'description': 'Get the bounding rectangle of this drawable element.',
|
|
||||||
'returns': 'tuple: (x, y, width, height) representing the element\'s bounds',
|
|
||||||
'note': 'The bounds are in screen coordinates and account for current position and size.'
|
|
||||||
},
|
|
||||||
'move': {
|
|
||||||
'signature': 'move(dx, dy)',
|
|
||||||
'description': 'Move the element by a relative offset.',
|
|
||||||
'args': [
|
|
||||||
('dx', 'float', 'Horizontal offset in pixels'),
|
|
||||||
('dy', 'float', 'Vertical offset in pixels')
|
|
||||||
],
|
|
||||||
'note': 'This modifies the x and y position properties by the given amounts.'
|
|
||||||
},
|
|
||||||
'resize': {
|
|
||||||
'signature': 'resize(width, height)',
|
|
||||||
'description': 'Resize the element to new dimensions.',
|
|
||||||
'args': [
|
|
||||||
('width', 'float', 'New width in pixels'),
|
|
||||||
('height', 'float', 'New height in pixels')
|
|
||||||
],
|
|
||||||
'note': 'For Caption and Sprite, this may not change actual size if determined by content.'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
# Entity-specific methods
|
|
||||||
'Entity': {
|
|
||||||
'at': {
|
|
||||||
'signature': 'at(x, y)',
|
|
||||||
'description': 'Check if this entity is at the specified grid coordinates.',
|
|
||||||
'args': [
|
|
||||||
('x', 'int', 'Grid x coordinate to check'),
|
|
||||||
('y', 'int', 'Grid y coordinate to check')
|
|
||||||
],
|
|
||||||
'returns': 'bool: True if entity is at position (x, y), False otherwise'
|
|
||||||
},
|
|
||||||
'die': {
|
|
||||||
'signature': 'die()',
|
|
||||||
'description': 'Remove this entity from its parent grid.',
|
|
||||||
'note': 'The entity object remains valid but is no longer rendered or updated.'
|
|
||||||
},
|
|
||||||
'index': {
|
|
||||||
'signature': 'index()',
|
|
||||||
'description': 'Get the index of this entity in its parent grid\'s entity list.',
|
|
||||||
'returns': 'int: Index position, or -1 if not in a grid'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
# Grid-specific methods
|
|
||||||
'Grid': {
|
|
||||||
'at': {
|
|
||||||
'signature': 'at(x, y)',
|
|
||||||
'description': 'Get the GridPoint at the specified grid coordinates.',
|
|
||||||
'args': [
|
|
||||||
('x', 'int', 'Grid x coordinate'),
|
|
||||||
('y', 'int', 'Grid y coordinate')
|
|
||||||
],
|
|
||||||
'returns': 'GridPoint or None: The grid point at (x, y), or None if out of bounds'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
# Collection methods
|
|
||||||
'EntityCollection': {
|
|
||||||
'append': {
|
|
||||||
'signature': 'append(entity)',
|
|
||||||
'description': 'Add an entity to the end of the collection.',
|
|
||||||
'args': [('entity', 'Entity', 'The entity to add')]
|
|
||||||
},
|
|
||||||
'remove': {
|
|
||||||
'signature': 'remove(entity)',
|
|
||||||
'description': 'Remove the first occurrence of an entity from the collection.',
|
|
||||||
'args': [('entity', 'Entity', 'The entity to remove')],
|
|
||||||
'raises': 'ValueError: If entity is not in collection'
|
|
||||||
},
|
|
||||||
'extend': {
|
|
||||||
'signature': 'extend(iterable)',
|
|
||||||
'description': 'Add all entities from an iterable to the collection.',
|
|
||||||
'args': [('iterable', 'Iterable[Entity]', 'Entities to add')]
|
|
||||||
},
|
|
||||||
'count': {
|
|
||||||
'signature': 'count(entity)',
|
|
||||||
'description': 'Count the number of occurrences of an entity in the collection.',
|
|
||||||
'args': [('entity', 'Entity', 'The entity to count')],
|
|
||||||
'returns': 'int: Number of times entity appears in collection'
|
|
||||||
},
|
|
||||||
'index': {
|
|
||||||
'signature': 'index(entity)',
|
|
||||||
'description': 'Find the index of the first occurrence of an entity.',
|
|
||||||
'args': [('entity', 'Entity', 'The entity to find')],
|
|
||||||
'returns': 'int: Index of entity in collection',
|
|
||||||
'raises': 'ValueError: If entity is not in collection'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
'UICollection': {
|
|
||||||
'append': {
|
|
||||||
'signature': 'append(drawable)',
|
|
||||||
'description': 'Add a drawable element to the end of the collection.',
|
|
||||||
'args': [('drawable', 'UIDrawable', 'The drawable element to add')]
|
|
||||||
},
|
|
||||||
'remove': {
|
|
||||||
'signature': 'remove(drawable)',
|
|
||||||
'description': 'Remove the first occurrence of a drawable from the collection.',
|
|
||||||
'args': [('drawable', 'UIDrawable', 'The drawable to remove')],
|
|
||||||
'raises': 'ValueError: If drawable is not in collection'
|
|
||||||
},
|
|
||||||
'extend': {
|
|
||||||
'signature': 'extend(iterable)',
|
|
||||||
'description': 'Add all drawables from an iterable to the collection.',
|
|
||||||
'args': [('iterable', 'Iterable[UIDrawable]', 'Drawables to add')]
|
|
||||||
},
|
|
||||||
'count': {
|
|
||||||
'signature': 'count(drawable)',
|
|
||||||
'description': 'Count the number of occurrences of a drawable in the collection.',
|
|
||||||
'args': [('drawable', 'UIDrawable', 'The drawable to count')],
|
|
||||||
'returns': 'int: Number of times drawable appears in collection'
|
|
||||||
},
|
|
||||||
'index': {
|
|
||||||
'signature': 'index(drawable)',
|
|
||||||
'description': 'Find the index of the first occurrence of a drawable.',
|
|
||||||
'args': [('drawable', 'UIDrawable', 'The drawable to find')],
|
|
||||||
'returns': 'int: Index of drawable in collection',
|
|
||||||
'raises': 'ValueError: If drawable is not in collection'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
# Animation methods
|
|
||||||
'Animation': {
|
|
||||||
'get_current_value': {
|
|
||||||
'signature': 'get_current_value()',
|
|
||||||
'description': 'Get the current interpolated value of the animation.',
|
|
||||||
'returns': 'float: Current animation value between start and end'
|
|
||||||
},
|
|
||||||
'start': {
|
|
||||||
'signature': 'start(target)',
|
|
||||||
'description': 'Start the animation on a target UI element.',
|
|
||||||
'args': [('target', 'UIDrawable', 'The UI element to animate')],
|
|
||||||
'note': 'The target must have the property specified in the animation constructor.'
|
|
||||||
},
|
|
||||||
'update': {
|
|
||||||
'signature': 'update(delta_time)',
|
|
||||||
'description': 'Update the animation by the given time delta.',
|
|
||||||
'args': [('delta_time', 'float', 'Time elapsed since last update in seconds')],
|
|
||||||
'returns': 'bool: True if animation is still running, False if finished'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
# Color methods
|
|
||||||
'Color': {
|
|
||||||
'from_hex': {
|
|
||||||
'signature': 'from_hex(hex_string)',
|
|
||||||
'description': 'Create a Color from a hexadecimal color string.',
|
|
||||||
'args': [('hex_string', 'str', 'Hex color string (e.g., "#FF0000" or "FF0000")')],
|
|
||||||
'returns': 'Color: New Color object from hex string',
|
|
||||||
'example': 'red = Color.from_hex("#FF0000")'
|
|
||||||
},
|
|
||||||
'to_hex': {
|
|
||||||
'signature': 'to_hex()',
|
|
||||||
'description': 'Convert this Color to a hexadecimal string.',
|
|
||||||
'returns': 'str: Hex color string in format "#RRGGBB"',
|
|
||||||
'example': 'hex_str = color.to_hex() # Returns "#FF0000"'
|
|
||||||
},
|
|
||||||
'lerp': {
|
|
||||||
'signature': 'lerp(other, t)',
|
|
||||||
'description': 'Linearly interpolate between this color and another.',
|
|
||||||
'args': [
|
|
||||||
('other', 'Color', 'The color to interpolate towards'),
|
|
||||||
('t', 'float', 'Interpolation factor from 0.0 to 1.0')
|
|
||||||
],
|
|
||||||
'returns': 'Color: New interpolated Color object',
|
|
||||||
'example': 'mixed = red.lerp(blue, 0.5) # 50% between red and blue'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
# Vector methods
|
|
||||||
'Vector': {
|
|
||||||
'magnitude': {
|
|
||||||
'signature': 'magnitude()',
|
|
||||||
'description': 'Calculate the length/magnitude of this vector.',
|
|
||||||
'returns': 'float: The magnitude of the vector'
|
|
||||||
},
|
|
||||||
'magnitude_squared': {
|
|
||||||
'signature': 'magnitude_squared()',
|
|
||||||
'description': 'Calculate the squared magnitude of this vector.',
|
|
||||||
'returns': 'float: The squared magnitude (faster than magnitude())',
|
|
||||||
'note': 'Use this for comparisons to avoid expensive square root calculation.'
|
|
||||||
},
|
|
||||||
'normalize': {
|
|
||||||
'signature': 'normalize()',
|
|
||||||
'description': 'Return a unit vector in the same direction.',
|
|
||||||
'returns': 'Vector: New normalized vector with magnitude 1.0',
|
|
||||||
'raises': 'ValueError: If vector has zero magnitude'
|
|
||||||
},
|
|
||||||
'dot': {
|
|
||||||
'signature': 'dot(other)',
|
|
||||||
'description': 'Calculate the dot product with another vector.',
|
|
||||||
'args': [('other', 'Vector', 'The other vector')],
|
|
||||||
'returns': 'float: Dot product of the two vectors'
|
|
||||||
},
|
|
||||||
'distance_to': {
|
|
||||||
'signature': 'distance_to(other)',
|
|
||||||
'description': 'Calculate the distance to another vector.',
|
|
||||||
'args': [('other', 'Vector', 'The other vector')],
|
|
||||||
'returns': 'float: Distance between the two vectors'
|
|
||||||
},
|
|
||||||
'angle': {
|
|
||||||
'signature': 'angle()',
|
|
||||||
'description': 'Get the angle of this vector in radians.',
|
|
||||||
'returns': 'float: Angle in radians from positive x-axis'
|
|
||||||
},
|
|
||||||
'copy': {
|
|
||||||
'signature': 'copy()',
|
|
||||||
'description': 'Create a copy of this vector.',
|
|
||||||
'returns': 'Vector: New Vector object with same x and y values'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
# Scene methods
|
|
||||||
'Scene': {
|
|
||||||
'activate': {
|
|
||||||
'signature': 'activate()',
|
|
||||||
'description': 'Make this scene the active scene.',
|
|
||||||
'note': 'Equivalent to calling setScene() with this scene\'s name.'
|
|
||||||
},
|
|
||||||
'get_ui': {
|
|
||||||
'signature': 'get_ui()',
|
|
||||||
'description': 'Get the UI element collection for this scene.',
|
|
||||||
'returns': 'UICollection: Collection of all UI elements in this scene'
|
|
||||||
},
|
|
||||||
'keypress': {
|
|
||||||
'signature': 'keypress(handler)',
|
|
||||||
'description': 'Register a keyboard handler function for this scene.',
|
|
||||||
'args': [('handler', 'callable', 'Function that takes (key_name: str, is_pressed: bool)')],
|
|
||||||
'note': 'Alternative to overriding the on_keypress method.'
|
|
||||||
},
|
|
||||||
'register_keyboard': {
|
|
||||||
'signature': 'register_keyboard(callable)',
|
|
||||||
'description': 'Register a keyboard event handler function for the scene.',
|
|
||||||
'args': [('callable', 'callable', 'Function that takes (key: str, action: str) parameters')],
|
|
||||||
'note': 'Alternative to overriding the on_keypress method when subclassing Scene objects.',
|
|
||||||
'example': '''def handle_keyboard(key, action):
|
|
||||||
print(f"Key '{key}' was {action}")
|
|
||||||
scene.register_keyboard(handle_keyboard)'''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
# Timer methods
|
|
||||||
'Timer': {
|
|
||||||
'pause': {
|
|
||||||
'signature': 'pause()',
|
|
||||||
'description': 'Pause the timer, stopping its callback execution.',
|
|
||||||
'note': 'Use resume() to continue the timer from where it was paused.'
|
|
||||||
},
|
|
||||||
'resume': {
|
|
||||||
'signature': 'resume()',
|
|
||||||
'description': 'Resume a paused timer.',
|
|
||||||
'note': 'Has no effect if timer is not paused.'
|
|
||||||
},
|
|
||||||
'cancel': {
|
|
||||||
'signature': 'cancel()',
|
|
||||||
'description': 'Cancel the timer and remove it from the system.',
|
|
||||||
'note': 'After cancelling, the timer object cannot be reused.'
|
|
||||||
},
|
|
||||||
'restart': {
|
|
||||||
'signature': 'restart()',
|
|
||||||
'description': 'Restart the timer from the beginning.',
|
|
||||||
'note': 'Resets the timer\'s internal clock to zero.'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
# Window methods
|
|
||||||
'Window': {
|
|
||||||
'get': {
|
|
||||||
'signature': 'get()',
|
|
||||||
'description': 'Get the Window singleton instance.',
|
|
||||||
'returns': 'Window: The singleton window object',
|
|
||||||
'note': 'This is a static method that returns the same instance every time.'
|
|
||||||
},
|
|
||||||
'center': {
|
|
||||||
'signature': 'center()',
|
|
||||||
'description': 'Center the window on the screen.',
|
|
||||||
'note': 'Only works if the window is not fullscreen.'
|
|
||||||
},
|
|
||||||
'screenshot': {
|
|
||||||
'signature': 'screenshot(filename)',
|
|
||||||
'description': 'Take a screenshot and save it to a file.',
|
|
||||||
'args': [('filename', 'str', 'Path where to save the screenshot')],
|
|
||||||
'note': 'Supports PNG, JPG, and BMP formats based on file extension.'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_complete_function_documentation():
|
|
||||||
"""Return complete documentation for ALL module functions."""
|
|
||||||
return {
|
|
||||||
# Scene Management
|
|
||||||
'createScene': {
|
|
||||||
'signature': 'createScene(name: str) -> None',
|
|
||||||
'description': 'Create a new empty scene with the given name.',
|
|
||||||
'args': [('name', 'str', 'Unique name for the new scene')],
|
|
||||||
'raises': 'ValueError: If a scene with this name already exists',
|
|
||||||
'note': 'The scene is created but not made active. Use setScene() to switch to it.',
|
|
||||||
'example': 'mcrfpy.createScene("game_over")'
|
|
||||||
},
|
|
||||||
'setScene': {
|
|
||||||
'signature': 'setScene(scene: str, transition: str = None, duration: float = 0.0) -> None',
|
|
||||||
'description': 'Switch to a different scene with optional transition effect.',
|
|
||||||
'args': [
|
|
||||||
('scene', 'str', 'Name of the scene to switch to'),
|
|
||||||
('transition', 'str', 'Transition type: "fade", "slide_left", "slide_right", "slide_up", "slide_down"'),
|
|
||||||
('duration', 'float', 'Transition duration in seconds (default: 0.0 for instant)')
|
|
||||||
],
|
|
||||||
'raises': 'KeyError: If the scene doesn\'t exist',
|
|
||||||
'example': 'mcrfpy.setScene("game", "fade", 0.5)'
|
|
||||||
},
|
|
||||||
'currentScene': {
|
|
||||||
'signature': 'currentScene() -> str',
|
|
||||||
'description': 'Get the name of the currently active scene.',
|
|
||||||
'returns': 'str: Name of the current scene',
|
|
||||||
'example': 'scene_name = mcrfpy.currentScene()'
|
|
||||||
},
|
|
||||||
'sceneUI': {
|
|
||||||
'signature': 'sceneUI(scene: str = None) -> UICollection',
|
|
||||||
'description': 'Get all UI elements for a scene.',
|
|
||||||
'args': [('scene', 'str', 'Scene name. If None, uses current scene')],
|
|
||||||
'returns': 'UICollection: All UI elements in the scene',
|
|
||||||
'raises': 'KeyError: If the specified scene doesn\'t exist',
|
|
||||||
'example': 'ui_elements = mcrfpy.sceneUI("game")'
|
|
||||||
},
|
|
||||||
'keypressScene': {
|
|
||||||
'signature': 'keypressScene(handler: callable) -> None',
|
|
||||||
'description': 'Set the keyboard event handler for the current scene.',
|
|
||||||
'args': [('handler', 'callable', 'Function that receives (key_name: str, is_pressed: bool)')],
|
|
||||||
'example': '''def on_key(key, pressed):
|
|
||||||
if key == "SPACE" and pressed:
|
|
||||||
player.jump()
|
|
||||||
mcrfpy.keypressScene(on_key)'''
|
|
||||||
},
|
|
||||||
|
|
||||||
# Audio Functions
|
|
||||||
'createSoundBuffer': {
|
|
||||||
'signature': 'createSoundBuffer(filename: str) -> int',
|
|
||||||
'description': 'Load a sound effect from a file and return its buffer ID.',
|
|
||||||
'args': [('filename', 'str', 'Path to the sound file (WAV, OGG, FLAC)')],
|
|
||||||
'returns': 'int: Buffer ID for use with playSound()',
|
|
||||||
'raises': 'RuntimeError: If the file cannot be loaded',
|
|
||||||
'example': 'jump_sound = mcrfpy.createSoundBuffer("assets/jump.wav")'
|
|
||||||
},
|
|
||||||
'loadMusic': {
|
|
||||||
'signature': 'loadMusic(filename: str, loop: bool = True) -> None',
|
|
||||||
'description': 'Load and immediately play background music from a file.',
|
|
||||||
'args': [
|
|
||||||
('filename', 'str', 'Path to the music file (WAV, OGG, FLAC)'),
|
|
||||||
('loop', 'bool', 'Whether to loop the music (default: True)')
|
|
||||||
],
|
|
||||||
'note': 'Only one music track can play at a time. Loading new music stops the current track.',
|
|
||||||
'example': 'mcrfpy.loadMusic("assets/background.ogg", True)'
|
|
||||||
},
|
|
||||||
'playSound': {
|
|
||||||
'signature': 'playSound(buffer_id: int) -> None',
|
|
||||||
'description': 'Play a sound effect using a previously loaded buffer.',
|
|
||||||
'args': [('buffer_id', 'int', 'Sound buffer ID returned by createSoundBuffer()')],
|
|
||||||
'raises': 'RuntimeError: If the buffer ID is invalid',
|
|
||||||
'example': 'mcrfpy.playSound(jump_sound)'
|
|
||||||
},
|
|
||||||
'getMusicVolume': {
|
|
||||||
'signature': 'getMusicVolume() -> int',
|
|
||||||
'description': 'Get the current music volume level.',
|
|
||||||
'returns': 'int: Current volume (0-100)',
|
|
||||||
'example': 'current_volume = mcrfpy.getMusicVolume()'
|
|
||||||
},
|
|
||||||
'getSoundVolume': {
|
|
||||||
'signature': 'getSoundVolume() -> int',
|
|
||||||
'description': 'Get the current sound effects volume level.',
|
|
||||||
'returns': 'int: Current volume (0-100)',
|
|
||||||
'example': 'current_volume = mcrfpy.getSoundVolume()'
|
|
||||||
},
|
|
||||||
'setMusicVolume': {
|
|
||||||
'signature': 'setMusicVolume(volume: int) -> None',
|
|
||||||
'description': 'Set the global music volume.',
|
|
||||||
'args': [('volume', 'int', 'Volume level from 0 (silent) to 100 (full volume)')],
|
|
||||||
'example': 'mcrfpy.setMusicVolume(50) # Set to 50% volume'
|
|
||||||
},
|
|
||||||
'setSoundVolume': {
|
|
||||||
'signature': 'setSoundVolume(volume: int) -> None',
|
|
||||||
'description': 'Set the global sound effects volume.',
|
|
||||||
'args': [('volume', 'int', 'Volume level from 0 (silent) to 100 (full volume)')],
|
|
||||||
'example': 'mcrfpy.setSoundVolume(75) # Set to 75% volume'
|
|
||||||
},
|
|
||||||
|
|
||||||
# UI Utilities
|
|
||||||
'find': {
|
|
||||||
'signature': 'find(name: str, scene: str = None) -> UIDrawable | None',
|
|
||||||
'description': 'Find the first UI element with the specified name.',
|
|
||||||
'args': [
|
|
||||||
('name', 'str', 'Exact name to search for'),
|
|
||||||
('scene', 'str', 'Scene to search in (default: current scene)')
|
|
||||||
],
|
|
||||||
'returns': 'UIDrawable or None: The found element, or None if not found',
|
|
||||||
'note': 'Searches scene UI elements and entities within grids.',
|
|
||||||
'example': 'button = mcrfpy.find("start_button")'
|
|
||||||
},
|
|
||||||
'findAll': {
|
|
||||||
'signature': 'findAll(pattern: str, scene: str = None) -> list',
|
|
||||||
'description': 'Find all UI elements matching a name pattern.',
|
|
||||||
'args': [
|
|
||||||
('pattern', 'str', 'Name pattern with optional wildcards (* matches any characters)'),
|
|
||||||
('scene', 'str', 'Scene to search in (default: current scene)')
|
|
||||||
],
|
|
||||||
'returns': 'list: All matching UI elements and entities',
|
|
||||||
'example': 'enemies = mcrfpy.findAll("enemy_*")'
|
|
||||||
},
|
|
||||||
|
|
||||||
# System Functions
|
|
||||||
'exit': {
|
|
||||||
'signature': 'exit() -> None',
|
|
||||||
'description': 'Cleanly shut down the game engine and exit the application.',
|
|
||||||
'note': 'This immediately closes the window and terminates the program.',
|
|
||||||
'example': 'mcrfpy.exit()'
|
|
||||||
},
|
|
||||||
'getMetrics': {
|
|
||||||
'signature': 'getMetrics() -> dict',
|
|
||||||
'description': 'Get current performance metrics.',
|
|
||||||
'returns': '''dict: Performance data with keys:
|
|
||||||
- frame_time: Last frame duration in seconds
|
|
||||||
- avg_frame_time: Average frame time
|
|
||||||
- fps: Frames per second
|
|
||||||
- draw_calls: Number of draw calls
|
|
||||||
- ui_elements: Total UI element count
|
|
||||||
- visible_elements: Visible element count
|
|
||||||
- current_frame: Frame counter
|
|
||||||
- runtime: Total runtime in seconds''',
|
|
||||||
'example': 'metrics = mcrfpy.getMetrics()'
|
|
||||||
},
|
|
||||||
'setTimer': {
|
|
||||||
'signature': 'setTimer(name: str, handler: callable, interval: int) -> None',
|
|
||||||
'description': 'Create or update a recurring timer.',
|
|
||||||
'args': [
|
|
||||||
('name', 'str', 'Unique identifier for the timer'),
|
|
||||||
('handler', 'callable', 'Function called with (runtime: float) parameter'),
|
|
||||||
('interval', 'int', 'Time between calls in milliseconds')
|
|
||||||
],
|
|
||||||
'note': 'If a timer with this name exists, it will be replaced.',
|
|
||||||
'example': '''def update_score(runtime):
|
|
||||||
score += 1
|
|
||||||
mcrfpy.setTimer("score_update", update_score, 1000)'''
|
|
||||||
},
|
|
||||||
'delTimer': {
|
|
||||||
'signature': 'delTimer(name: str) -> None',
|
|
||||||
'description': 'Stop and remove a timer.',
|
|
||||||
'args': [('name', 'str', 'Timer identifier to remove')],
|
|
||||||
'note': 'No error is raised if the timer doesn\'t exist.',
|
|
||||||
'example': 'mcrfpy.delTimer("score_update")'
|
|
||||||
},
|
|
||||||
'setScale': {
|
|
||||||
'signature': 'setScale(multiplier: float) -> None',
|
|
||||||
'description': 'Scale the game window size.',
|
|
||||||
'args': [('multiplier', 'float', 'Scale factor (e.g., 2.0 for double size)')],
|
|
||||||
'note': 'The internal resolution remains 1024x768, but the window is scaled.',
|
|
||||||
'example': 'mcrfpy.setScale(2.0) # Double the window size'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_complete_property_documentation():
|
|
||||||
"""Return complete documentation for ALL properties."""
|
|
||||||
return {
|
|
||||||
'Animation': {
|
|
||||||
'property': 'str: Name of the property being animated (e.g., "x", "y", "scale")',
|
|
||||||
'duration': 'float: Total duration of the animation in seconds',
|
|
||||||
'elapsed_time': 'float: Time elapsed since animation started (read-only)',
|
|
||||||
'current_value': 'float: Current interpolated value of the animation (read-only)',
|
|
||||||
'is_running': 'bool: True if animation is currently running (read-only)',
|
|
||||||
'is_finished': 'bool: True if animation has completed (read-only)'
|
|
||||||
},
|
|
||||||
'GridPoint': {
|
|
||||||
'x': 'int: Grid x coordinate of this point',
|
|
||||||
'y': 'int: Grid y coordinate of this point',
|
|
||||||
'texture_index': 'int: Index of the texture/sprite to display at this point',
|
|
||||||
'solid': 'bool: Whether this point blocks movement',
|
|
||||||
'transparent': 'bool: Whether this point allows light/vision through',
|
|
||||||
'color': 'Color: Color tint applied to the texture at this point'
|
|
||||||
},
|
|
||||||
'GridPointState': {
|
|
||||||
'visible': 'bool: Whether this point is currently visible to the player',
|
|
||||||
'discovered': 'bool: Whether this point has been discovered/explored',
|
|
||||||
'custom_flags': 'int: Bitfield for custom game-specific flags'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def format_method_markdown(method_name, method_doc):
|
|
||||||
"""Format a method as markdown."""
|
|
||||||
lines = []
|
|
||||||
|
|
||||||
lines.append(f"#### `{method_doc['signature']}`")
|
|
||||||
lines.append("")
|
|
||||||
lines.append(method_doc['description'])
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
# Arguments
|
|
||||||
if 'args' in method_doc:
|
|
||||||
lines.append("**Arguments:**")
|
|
||||||
for arg in method_doc['args']:
|
|
||||||
lines.append(f"- `{arg[0]}` (*{arg[1]}*): {arg[2]}")
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
# Returns
|
|
||||||
if 'returns' in method_doc:
|
|
||||||
lines.append(f"**Returns:** {method_doc['returns']}")
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
# Raises
|
|
||||||
if 'raises' in method_doc:
|
|
||||||
lines.append(f"**Raises:** {method_doc['raises']}")
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
# Note
|
|
||||||
if 'note' in method_doc:
|
|
||||||
lines.append(f"**Note:** {method_doc['note']}")
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
# Example
|
|
||||||
if 'example' in method_doc:
|
|
||||||
lines.append("**Example:**")
|
|
||||||
lines.append("```python")
|
|
||||||
lines.append(method_doc['example'])
|
|
||||||
lines.append("```")
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
return lines
|
|
||||||
|
|
||||||
def format_function_markdown(func_name, func_doc):
|
|
||||||
"""Format a function as markdown."""
|
|
||||||
lines = []
|
|
||||||
|
|
||||||
lines.append(f"### `{func_doc['signature']}`")
|
|
||||||
lines.append("")
|
|
||||||
lines.append(func_doc['description'])
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
# Arguments
|
|
||||||
if 'args' in func_doc:
|
|
||||||
lines.append("**Arguments:**")
|
|
||||||
for arg in func_doc['args']:
|
|
||||||
lines.append(f"- `{arg[0]}` (*{arg[1]}*): {arg[2]}")
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
# Returns
|
|
||||||
if 'returns' in func_doc:
|
|
||||||
lines.append(f"**Returns:** {func_doc['returns']}")
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
# Raises
|
|
||||||
if 'raises' in func_doc:
|
|
||||||
lines.append(f"**Raises:** {func_doc['raises']}")
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
# Note
|
|
||||||
if 'note' in func_doc:
|
|
||||||
lines.append(f"**Note:** {func_doc['note']}")
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
# Example
|
|
||||||
if 'example' in func_doc:
|
|
||||||
lines.append("**Example:**")
|
|
||||||
lines.append("```python")
|
|
||||||
lines.append(func_doc['example'])
|
|
||||||
lines.append("```")
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
lines.append("---")
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
return lines
|
|
||||||
|
|
||||||
def generate_complete_markdown_documentation():
|
|
||||||
"""Generate complete markdown documentation with NO missing methods."""
|
|
||||||
|
|
||||||
# Get all documentation data
|
|
||||||
method_docs = get_complete_method_documentation()
|
|
||||||
function_docs = get_complete_function_documentation()
|
|
||||||
property_docs = get_complete_property_documentation()
|
|
||||||
|
|
||||||
lines = []
|
|
||||||
|
|
||||||
# Header
|
|
||||||
lines.append("# McRogueFace API Reference")
|
|
||||||
lines.append("")
|
|
||||||
lines.append(f"*Generated on {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*")
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
# Overview
|
|
||||||
if mcrfpy.__doc__:
|
|
||||||
lines.append("## Overview")
|
|
||||||
lines.append("")
|
|
||||||
# Process the docstring properly
|
|
||||||
doc_text = mcrfpy.__doc__.replace('\\n', '\n')
|
|
||||||
lines.append(doc_text)
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
# Table of Contents
|
|
||||||
lines.append("## Table of Contents")
|
|
||||||
lines.append("")
|
|
||||||
lines.append("- [Functions](#functions)")
|
|
||||||
lines.append(" - [Scene Management](#scene-management)")
|
|
||||||
lines.append(" - [Audio](#audio)")
|
|
||||||
lines.append(" - [UI Utilities](#ui-utilities)")
|
|
||||||
lines.append(" - [System](#system)")
|
|
||||||
lines.append("- [Classes](#classes)")
|
|
||||||
lines.append(" - [UI Components](#ui-components)")
|
|
||||||
lines.append(" - [Collections](#collections)")
|
|
||||||
lines.append(" - [System Types](#system-types)")
|
|
||||||
lines.append(" - [Other Classes](#other-classes)")
|
|
||||||
lines.append("- [Automation Module](#automation-module)")
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
# Functions section
|
|
||||||
lines.append("## Functions")
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
# Group functions by category
|
|
||||||
categories = {
|
|
||||||
'Scene Management': ['createScene', 'setScene', 'currentScene', 'sceneUI', 'keypressScene'],
|
|
||||||
'Audio': ['createSoundBuffer', 'loadMusic', 'playSound', 'getMusicVolume', 'getSoundVolume', 'setMusicVolume', 'setSoundVolume'],
|
|
||||||
'UI Utilities': ['find', 'findAll'],
|
|
||||||
'System': ['exit', 'getMetrics', 'setTimer', 'delTimer', 'setScale']
|
|
||||||
}
|
|
||||||
|
|
||||||
for category, functions in categories.items():
|
|
||||||
lines.append(f"### {category}")
|
|
||||||
lines.append("")
|
|
||||||
for func_name in functions:
|
|
||||||
if func_name in function_docs:
|
|
||||||
lines.extend(format_function_markdown(func_name, function_docs[func_name]))
|
|
||||||
|
|
||||||
# Classes section
|
|
||||||
lines.append("## Classes")
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
# Get all classes from mcrfpy
|
|
||||||
classes = []
|
|
||||||
for name in sorted(dir(mcrfpy)):
|
|
||||||
if not name.startswith('_'):
|
|
||||||
obj = getattr(mcrfpy, name)
|
|
||||||
if isinstance(obj, type):
|
|
||||||
classes.append((name, obj))
|
|
||||||
|
|
||||||
# Group classes
|
|
||||||
ui_classes = ['Frame', 'Caption', 'Sprite', 'Grid', 'Entity']
|
|
||||||
collection_classes = ['EntityCollection', 'UICollection', 'UICollectionIter', 'UIEntityCollectionIter']
|
|
||||||
system_classes = ['Color', 'Vector', 'Texture', 'Font']
|
|
||||||
other_classes = [name for name, _ in classes if name not in ui_classes + collection_classes + system_classes]
|
|
||||||
|
|
||||||
# UI Components
|
|
||||||
lines.append("### UI Components")
|
|
||||||
lines.append("")
|
|
||||||
for class_name in ui_classes:
|
|
||||||
if any(name == class_name for name, _ in classes):
|
|
||||||
lines.extend(format_class_markdown(class_name, method_docs, property_docs))
|
|
||||||
|
|
||||||
# Collections
|
|
||||||
lines.append("### Collections")
|
|
||||||
lines.append("")
|
|
||||||
for class_name in collection_classes:
|
|
||||||
if any(name == class_name for name, _ in classes):
|
|
||||||
lines.extend(format_class_markdown(class_name, method_docs, property_docs))
|
|
||||||
|
|
||||||
# System Types
|
|
||||||
lines.append("### System Types")
|
|
||||||
lines.append("")
|
|
||||||
for class_name in system_classes:
|
|
||||||
if any(name == class_name for name, _ in classes):
|
|
||||||
lines.extend(format_class_markdown(class_name, method_docs, property_docs))
|
|
||||||
|
|
||||||
# Other Classes
|
|
||||||
lines.append("### Other Classes")
|
|
||||||
lines.append("")
|
|
||||||
for class_name in other_classes:
|
|
||||||
lines.extend(format_class_markdown(class_name, method_docs, property_docs))
|
|
||||||
|
|
||||||
# Automation section
|
|
||||||
if hasattr(mcrfpy, 'automation'):
|
|
||||||
lines.append("## Automation Module")
|
|
||||||
lines.append("")
|
|
||||||
lines.append("The `mcrfpy.automation` module provides testing and automation capabilities.")
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
automation = mcrfpy.automation
|
|
||||||
for name in sorted(dir(automation)):
|
|
||||||
if not name.startswith('_'):
|
|
||||||
obj = getattr(automation, name)
|
|
||||||
if callable(obj):
|
|
||||||
lines.append(f"### `automation.{name}`")
|
|
||||||
lines.append("")
|
|
||||||
if obj.__doc__:
|
|
||||||
doc_parts = obj.__doc__.split(' - ')
|
|
||||||
if len(doc_parts) > 1:
|
|
||||||
lines.append(doc_parts[1])
|
|
||||||
else:
|
|
||||||
lines.append(obj.__doc__)
|
|
||||||
lines.append("")
|
|
||||||
lines.append("---")
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
return '\n'.join(lines)
|
|
||||||
|
|
||||||
def format_class_markdown(class_name, method_docs, property_docs):
|
|
||||||
"""Format a class as markdown."""
|
|
||||||
lines = []
|
|
||||||
|
|
||||||
lines.append(f"### class `{class_name}`")
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
# Class description from known info
|
|
||||||
class_descriptions = {
|
|
||||||
'Frame': 'A rectangular frame UI element that can contain other drawable elements.',
|
|
||||||
'Caption': 'A text display UI element with customizable font and styling.',
|
|
||||||
'Sprite': 'A sprite UI element that displays a texture or portion of a texture atlas.',
|
|
||||||
'Grid': 'A grid-based tilemap UI element for rendering tile-based levels and game worlds.',
|
|
||||||
'Entity': 'Game entity that can be placed in a Grid.',
|
|
||||||
'EntityCollection': 'Container for Entity objects in a Grid. Supports iteration and indexing.',
|
|
||||||
'UICollection': 'Container for UI drawable elements. Supports iteration and indexing.',
|
|
||||||
'UICollectionIter': 'Iterator for UICollection. Automatically created when iterating over a UICollection.',
|
|
||||||
'UIEntityCollectionIter': 'Iterator for EntityCollection. Automatically created when iterating over an EntityCollection.',
|
|
||||||
'Color': 'RGBA color representation.',
|
|
||||||
'Vector': '2D vector for positions and directions.',
|
|
||||||
'Font': 'Font object for text rendering.',
|
|
||||||
'Texture': 'Texture object for image data.',
|
|
||||||
'Animation': 'Animate UI element properties over time.',
|
|
||||||
'GridPoint': 'Represents a single tile in a Grid.',
|
|
||||||
'GridPointState': 'State information for a GridPoint.',
|
|
||||||
'Scene': 'Base class for object-oriented scenes.',
|
|
||||||
'Timer': 'Timer object for scheduled callbacks.',
|
|
||||||
'Window': 'Window singleton for accessing and modifying the game window properties.',
|
|
||||||
'Drawable': 'Base class for all drawable UI elements.'
|
|
||||||
}
|
|
||||||
|
|
||||||
if class_name in class_descriptions:
|
|
||||||
lines.append(class_descriptions[class_name])
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
# Properties
|
|
||||||
if class_name in property_docs:
|
|
||||||
lines.append("#### Properties")
|
|
||||||
lines.append("")
|
|
||||||
for prop_name, prop_desc in property_docs[class_name].items():
|
|
||||||
lines.append(f"- **`{prop_name}`**: {prop_desc}")
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
# Methods
|
|
||||||
methods_to_document = []
|
|
||||||
|
|
||||||
# Add inherited methods for UI classes
|
|
||||||
if class_name in ['Frame', 'Caption', 'Sprite', 'Grid', 'Entity']:
|
|
||||||
methods_to_document.extend(['get_bounds', 'move', 'resize'])
|
|
||||||
|
|
||||||
# Add class-specific methods
|
|
||||||
if class_name in method_docs:
|
|
||||||
methods_to_document.extend(method_docs[class_name].keys())
|
|
||||||
|
|
||||||
if methods_to_document:
|
|
||||||
lines.append("#### Methods")
|
|
||||||
lines.append("")
|
|
||||||
for method_name in set(methods_to_document):
|
|
||||||
# Get method documentation
|
|
||||||
method_doc = None
|
|
||||||
if class_name in method_docs and method_name in method_docs[class_name]:
|
|
||||||
method_doc = method_docs[class_name][method_name]
|
|
||||||
elif method_name in method_docs.get('Drawable', {}):
|
|
||||||
method_doc = method_docs['Drawable'][method_name]
|
|
||||||
|
|
||||||
if method_doc:
|
|
||||||
lines.extend(format_method_markdown(method_name, method_doc))
|
|
||||||
|
|
||||||
lines.append("---")
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
return lines
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""Generate complete markdown documentation with zero missing methods."""
|
|
||||||
print("Generating COMPLETE Markdown API documentation...")
|
|
||||||
|
|
||||||
# Generate markdown
|
|
||||||
markdown_content = generate_complete_markdown_documentation()
|
|
||||||
|
|
||||||
# Write to file
|
|
||||||
output_path = Path("docs/API_REFERENCE_COMPLETE.md")
|
|
||||||
output_path.parent.mkdir(exist_ok=True)
|
|
||||||
|
|
||||||
with open(output_path, 'w', encoding='utf-8') as f:
|
|
||||||
f.write(markdown_content)
|
|
||||||
|
|
||||||
print(f"✓ Generated {output_path}")
|
|
||||||
print(f" File size: {len(markdown_content):,} bytes")
|
|
||||||
|
|
||||||
# Count "..." instances
|
|
||||||
ellipsis_count = markdown_content.count('...')
|
|
||||||
print(f" Ellipsis instances: {ellipsis_count}")
|
|
||||||
|
|
||||||
if ellipsis_count == 0:
|
|
||||||
print("✅ SUCCESS: No missing documentation found!")
|
|
||||||
else:
|
|
||||||
print(f"❌ WARNING: {ellipsis_count} methods still need documentation")
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
|
|
@ -12,6 +12,67 @@ import html
|
||||||
import re
|
import re
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
def transform_doc_links(docstring, format='html', base_url=''):
|
||||||
|
"""Transform MCRF_LINK patterns based on output format.
|
||||||
|
|
||||||
|
Detects pattern: "See also: TEXT (docs/path.md)"
|
||||||
|
Transforms to appropriate format for output type.
|
||||||
|
|
||||||
|
For HTML/web formats, properly escapes content before inserting HTML tags.
|
||||||
|
"""
|
||||||
|
if not docstring:
|
||||||
|
return docstring
|
||||||
|
|
||||||
|
link_pattern = r'See also: ([^(]+) \(([^)]+)\)'
|
||||||
|
|
||||||
|
def replace_link(match):
|
||||||
|
text, ref = match.group(1).strip(), match.group(2).strip()
|
||||||
|
|
||||||
|
if format == 'html':
|
||||||
|
# Convert docs/foo.md → foo.html and escape for safe HTML
|
||||||
|
href = html.escape(ref.replace('docs/', '').replace('.md', '.html'), quote=True)
|
||||||
|
text_escaped = html.escape(text)
|
||||||
|
return f'<p class="see-also">See also: <a href="{href}">{text_escaped}</a></p>'
|
||||||
|
|
||||||
|
elif format == 'web':
|
||||||
|
# Link to hosted docs and escape for safe HTML
|
||||||
|
web_path = ref.replace('docs/', '').replace('.md', '')
|
||||||
|
href = html.escape(f"{base_url}/{web_path}", quote=True)
|
||||||
|
text_escaped = html.escape(text)
|
||||||
|
return f'<p class="see-also">See also: <a href="{href}">{text_escaped}</a></p>'
|
||||||
|
|
||||||
|
elif format == 'markdown':
|
||||||
|
# Markdown link
|
||||||
|
return f'\n**See also:** [{text}]({ref})'
|
||||||
|
|
||||||
|
else: # 'python' or default
|
||||||
|
# Keep as plain text for Python docstrings
|
||||||
|
return match.group(0)
|
||||||
|
|
||||||
|
# For HTML formats, escape the entire docstring first, then process links
|
||||||
|
if format in ('html', 'web'):
|
||||||
|
# Split by the link pattern, escape non-link parts, then reassemble
|
||||||
|
parts = []
|
||||||
|
last_end = 0
|
||||||
|
|
||||||
|
for match in re.finditer(link_pattern, docstring):
|
||||||
|
# Escape the text before this match
|
||||||
|
if match.start() > last_end:
|
||||||
|
parts.append(html.escape(docstring[last_end:match.start()]))
|
||||||
|
|
||||||
|
# Process the link (replace_link handles escaping internally)
|
||||||
|
parts.append(replace_link(match))
|
||||||
|
last_end = match.end()
|
||||||
|
|
||||||
|
# Escape any remaining text after the last match
|
||||||
|
if last_end < len(docstring):
|
||||||
|
parts.append(html.escape(docstring[last_end:]))
|
||||||
|
|
||||||
|
return ''.join(parts)
|
||||||
|
else:
|
||||||
|
# For non-HTML formats, just do simple replacement
|
||||||
|
return re.sub(link_pattern, replace_link, docstring)
|
||||||
|
|
||||||
# Must be run with McRogueFace as interpreter
|
# Must be run with McRogueFace as interpreter
|
||||||
try:
|
try:
|
||||||
import mcrfpy
|
import mcrfpy
|
||||||
|
|
@ -304,8 +365,10 @@ def generate_html_docs():
|
||||||
html_content += f"""
|
html_content += f"""
|
||||||
<div class="method-section">
|
<div class="method-section">
|
||||||
<h3><code class="function-signature">{func_name}{parsed['signature'] if parsed['signature'] else '(...)'}</code></h3>
|
<h3><code class="function-signature">{func_name}{parsed['signature'] if parsed['signature'] else '(...)'}</code></h3>
|
||||||
<p>{html.escape(parsed['description'])}</p>
|
|
||||||
"""
|
"""
|
||||||
|
if parsed['description']:
|
||||||
|
description = transform_doc_links(parsed['description'], format='html')
|
||||||
|
html_content += f" <p>{description}</p>\n"
|
||||||
|
|
||||||
if parsed['args']:
|
if parsed['args']:
|
||||||
html_content += " <h4>Arguments:</h4>\n <ul>\n"
|
html_content += " <h4>Arguments:</h4>\n <ul>\n"
|
||||||
|
|
@ -361,9 +424,10 @@ def generate_html_docs():
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
<h5><code class="method-name">{method_name}{parsed['signature'] if parsed['signature'] else '(...)'}</code></h5>
|
<h5><code class="method-name">{method_name}{parsed['signature'] if parsed['signature'] else '(...)'}</code></h5>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if parsed['description']:
|
if parsed['description']:
|
||||||
html_content += f" <p>{html.escape(parsed['description'])}</p>\n"
|
description = transform_doc_links(parsed['description'], format='html')
|
||||||
|
html_content += f" <p>{description}</p>\n"
|
||||||
|
|
||||||
if parsed['args']:
|
if parsed['args']:
|
||||||
html_content += " <div style='margin-left: 20px;'>\n"
|
html_content += " <div style='margin-left: 20px;'>\n"
|
||||||
|
|
@ -429,9 +493,10 @@ def generate_markdown_docs():
|
||||||
parsed = func_info["parsed"]
|
parsed = func_info["parsed"]
|
||||||
|
|
||||||
md_content += f"### `{func_name}{parsed['signature'] if parsed['signature'] else '(...)'}`\n\n"
|
md_content += f"### `{func_name}{parsed['signature'] if parsed['signature'] else '(...)'}`\n\n"
|
||||||
|
|
||||||
if parsed['description']:
|
if parsed['description']:
|
||||||
md_content += f"{parsed['description']}\n\n"
|
description = transform_doc_links(parsed['description'], format='markdown')
|
||||||
|
md_content += f"{description}\n\n"
|
||||||
|
|
||||||
if parsed['args']:
|
if parsed['args']:
|
||||||
md_content += "**Arguments:**\n"
|
md_content += "**Arguments:**\n"
|
||||||
|
|
@ -479,9 +544,10 @@ def generate_markdown_docs():
|
||||||
parsed = method_info['parsed']
|
parsed = method_info['parsed']
|
||||||
|
|
||||||
md_content += f"#### `{method_name}{parsed['signature'] if parsed['signature'] else '(...)'}`\n\n"
|
md_content += f"#### `{method_name}{parsed['signature'] if parsed['signature'] else '(...)'}`\n\n"
|
||||||
|
|
||||||
if parsed['description']:
|
if parsed['description']:
|
||||||
md_content += f"{parsed['description']}\n\n"
|
description = transform_doc_links(parsed['description'], format='markdown')
|
||||||
|
md_content += f"{description}\n\n"
|
||||||
|
|
||||||
if parsed['args']:
|
if parsed['args']:
|
||||||
md_content += "**Arguments:**\n"
|
md_content += "**Arguments:**\n"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Test script for link transformation function."""
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
def transform_doc_links(docstring, format='html', base_url=''):
|
||||||
|
"""Transform MCRF_LINK patterns based on output format.
|
||||||
|
|
||||||
|
Detects pattern: "See also: TEXT (docs/path.md)"
|
||||||
|
Transforms to appropriate format for output type.
|
||||||
|
"""
|
||||||
|
if not docstring:
|
||||||
|
return docstring
|
||||||
|
|
||||||
|
link_pattern = r'See also: ([^(]+) \(([^)]+)\)'
|
||||||
|
|
||||||
|
def replace_link(match):
|
||||||
|
text, ref = match.group(1).strip(), match.group(2).strip()
|
||||||
|
|
||||||
|
if format == 'html':
|
||||||
|
# Convert docs/foo.md → foo.html
|
||||||
|
href = ref.replace('docs/', '').replace('.md', '.html')
|
||||||
|
return f'<p class="see-also">See also: <a href="{href}">{text}</a></p>'
|
||||||
|
|
||||||
|
elif format == 'web':
|
||||||
|
# Link to hosted docs
|
||||||
|
web_path = ref.replace('docs/', '').replace('.md', '')
|
||||||
|
return f'<p class="see-also">See also: <a href="{base_url}/{web_path}">{text}</a></p>'
|
||||||
|
|
||||||
|
elif format == 'markdown':
|
||||||
|
# Markdown link
|
||||||
|
return f'\n**See also:** [{text}]({ref})'
|
||||||
|
|
||||||
|
else: # 'python' or default
|
||||||
|
# Keep as plain text for Python docstrings
|
||||||
|
return match.group(0)
|
||||||
|
|
||||||
|
return re.sub(link_pattern, replace_link, docstring)
|
||||||
|
|
||||||
|
# Test cases
|
||||||
|
test_doc = "Description text.\n\nSee also: Tutorial Guide (docs/guide.md)\n\nMore text."
|
||||||
|
|
||||||
|
html_result = transform_doc_links(test_doc, format='html')
|
||||||
|
print("HTML:", html_result)
|
||||||
|
assert '<a href="guide.html">Tutorial Guide</a>' in html_result
|
||||||
|
|
||||||
|
md_result = transform_doc_links(test_doc, format='markdown')
|
||||||
|
print("Markdown:", md_result)
|
||||||
|
assert '[Tutorial Guide](docs/guide.md)' in md_result
|
||||||
|
|
||||||
|
plain_result = transform_doc_links(test_doc, format='python')
|
||||||
|
print("Python:", plain_result)
|
||||||
|
assert 'See also: Tutorial Guide (docs/guide.md)' in plain_result
|
||||||
|
|
||||||
|
print("\nSUCCESS: All transformations work correctly")
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
import mcrfpy
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Check Vector.magnitude docstring
|
||||||
|
mag_doc = mcrfpy.Vector.magnitude.__doc__
|
||||||
|
print("magnitude doc:", mag_doc)
|
||||||
|
assert "magnitude()" in mag_doc
|
||||||
|
assert "Calculate the length/magnitude" in mag_doc
|
||||||
|
assert "Returns:" in mag_doc
|
||||||
|
|
||||||
|
# Check Vector.dot docstring
|
||||||
|
dot_doc = mcrfpy.Vector.dot.__doc__
|
||||||
|
print("dot doc:", dot_doc)
|
||||||
|
assert "dot(other: Vector)" in dot_doc
|
||||||
|
assert "Args:" in dot_doc
|
||||||
|
assert "other:" in dot_doc
|
||||||
|
|
||||||
|
# Check Vector.normalize docstring
|
||||||
|
normalize_doc = mcrfpy.Vector.normalize.__doc__
|
||||||
|
print("normalize doc:", normalize_doc)
|
||||||
|
assert "normalize()" in normalize_doc
|
||||||
|
assert "Return a unit vector" in normalize_doc
|
||||||
|
assert "Returns:" in normalize_doc
|
||||||
|
assert "Note:" in normalize_doc
|
||||||
|
|
||||||
|
# Check Vector.x property docstring
|
||||||
|
x_doc = mcrfpy.Vector.x.__doc__
|
||||||
|
print("x property doc:", x_doc)
|
||||||
|
assert "X coordinate of the vector" in x_doc
|
||||||
|
assert "float" in x_doc
|
||||||
|
|
||||||
|
# Check Vector.y property docstring
|
||||||
|
y_doc = mcrfpy.Vector.y.__doc__
|
||||||
|
print("y property doc:", y_doc)
|
||||||
|
assert "Y coordinate of the vector" in y_doc
|
||||||
|
assert "float" in y_doc
|
||||||
|
|
||||||
|
print("SUCCESS: All docstrings present and complete")
|
||||||
|
sys.exit(0)
|
||||||
Loading…
Reference in New Issue