Compare commits
10 Commits
af6a5e090b
...
9bd1561bfc
Author | SHA1 | Date |
---|---|---|
|
9bd1561bfc | |
|
43321487eb | |
|
90c318104b | |
|
2a48138011 | |
|
e4482e7189 | |
|
38d44777f5 | |
|
70cf44f8f0 | |
|
dd3c64784d | |
|
05bddae511 | |
|
0d26d51bc3 |
Binary file not shown.
After Width: | Height: | Size: 31 KiB |
Binary file not shown.
After Width: | Height: | Size: 31 KiB |
99
README.md
99
README.md
|
@ -1,30 +1,85 @@
|
||||||
# McRogueFace - 2D Game Engine
|
# McRogueFace
|
||||||
|
|
||||||
An experimental prototype game engine built for my own use in 7DRL 2023.
|
A Python-powered 2D game engine for creating roguelike games, built with C++ and SFML.
|
||||||
|
|
||||||
*Blame my wife for the name*
|
**Latest Release**: Successfully completed 7DRL 2025 with *"Crypt of Sokoban"* - a unique roguelike that blends Sokoban puzzle mechanics with dungeon crawling!
|
||||||
|
|
||||||
## Tenets:
|
## Features
|
||||||
|
|
||||||
* C++ first, Python close behind.
|
- **Python-First Design**: Write your game logic in Python while leveraging C++ performance
|
||||||
* Entity-Component system based on David Churchill's Memorial University COMP4300 course lectures available on Youtube.
|
- **Rich UI System**: Sprites, Grids, Frames, and Captions with full animation support
|
||||||
* Graphics, particles and shaders provided by SFML.
|
- **Entity-Component Architecture**: Flexible game object system with Python integration
|
||||||
* Pathfinding, noise generation, and other Roguelike goodness provided by TCOD.
|
- **Built-in Roguelike Support**: Dungeon generation, pathfinding, and field-of-view via libtcod
|
||||||
|
- **Automation API**: PyAutoGUI-compatible testing and demo recording
|
||||||
|
- **Interactive Development**: Python REPL integration for live game debugging
|
||||||
|
|
||||||
## Why?
|
## Quick Start
|
||||||
|
|
||||||
I did the r/RoguelikeDev TCOD tutorial in Python. I loved it, but I did not want to be limited to ASCII. I want to be able to draw pixels on top of my tiles (like lines or circles) and eventually incorporate even more polish.
|
```bash
|
||||||
|
# Clone and build
|
||||||
|
git clone https://github.com/jmcb/McRogueFace.git
|
||||||
|
cd McRogueFace
|
||||||
|
make
|
||||||
|
|
||||||
## To-do
|
# Run the example game
|
||||||
|
cd build
|
||||||
|
./mcrogueface
|
||||||
|
```
|
||||||
|
|
||||||
* ✅ Initial Commit
|
## Example: Creating a Simple Scene
|
||||||
* ✅ Integrate scene, action, entity, component system from COMP4300 engine
|
|
||||||
* ✅ Windows / Visual Studio project
|
```python
|
||||||
* ✅ Draw Sprites
|
import mcrfpy
|
||||||
* ✅ Play Sounds
|
|
||||||
* ✅ Draw UI, spawn entity from Python code
|
# Create a new scene
|
||||||
* ❌ Python AI for entities (NPCs on set paths, enemies towards player)
|
mcrfpy.createScene("intro")
|
||||||
* ✅ Walking / Collision
|
|
||||||
* ❌ "Boards" (stairs / doors / walk off edge of screen)
|
# Add a text caption
|
||||||
* ❌ Cutscenes - interrupt normal controls, text scroll, character portraits
|
caption = mcrfpy.Caption(50, 50, "Welcome to McRogueFace!")
|
||||||
* ❌ Mouse integration - tooltips, zoom, click to select targets, cursors
|
caption.font = mcrfpy.default_font
|
||||||
|
caption.font_color = (255, 255, 255)
|
||||||
|
|
||||||
|
# Add to scene
|
||||||
|
mcrfpy.sceneUI("intro").append(caption)
|
||||||
|
|
||||||
|
# Switch to the scene
|
||||||
|
mcrfpy.setScene("intro")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
For comprehensive documentation, tutorials, and API reference, visit:
|
||||||
|
**[https://mcrogueface.github.io](https://mcrogueface.github.io)**
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- C++17 compiler (GCC 7+ or Clang 5+)
|
||||||
|
- CMake 3.14+
|
||||||
|
- Python 3.12+
|
||||||
|
- SFML 2.5+
|
||||||
|
- Linux or Windows (macOS untested)
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
McRogueFace/
|
||||||
|
├── src/ # C++ engine source
|
||||||
|
├── scripts/ # Python game scripts
|
||||||
|
├── assets/ # Sprites, fonts, audio
|
||||||
|
├── build/ # Build output directory
|
||||||
|
└── tests/ # Automated test suite
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
McRogueFace is under active development. Check the [ROADMAP.md](ROADMAP.md) for current priorities and open issues.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is licensed under the MIT License - see LICENSE file for details.
|
||||||
|
|
||||||
|
## Acknowledgments
|
||||||
|
|
||||||
|
- Developed for 7-Day Roguelike Challenge 2025
|
||||||
|
- Built with [SFML](https://www.sfml-dev.org/), [libtcod](https://github.com/libtcod/libtcod), and Python
|
||||||
|
- Inspired by David Churchill's COMP4300 game engine lectures
|
137
ROADMAP.md
137
ROADMAP.md
|
@ -1,38 +1,54 @@
|
||||||
# McRogueFace - Development Roadmap
|
# McRogueFace - Development Roadmap
|
||||||
|
|
||||||
## Project Status: Post-7DRL 2025 "Crypt of Sokoban"
|
## Project Status: 🎉 ALPHA 0.1 RELEASE! 🎉
|
||||||
|
|
||||||
**Current State**: Successful 7DRL completion with Python/C++ game engine
|
**Current State**: Alpha release achieved! All critical blockers resolved!
|
||||||
**Latest Update**: Fixed 12+ critical bugs in one day! (2025-01-03)
|
**Latest Update**: Moved RenderTexture (#6) to Beta - Alpha is READY! (2025-07-05)
|
||||||
**Branch**: interpreter_mode (comprehensive test suite + major stability fixes)
|
**Branch**: interpreter_mode (ready for alpha release merge)
|
||||||
**Open Issues**: ~48 remaining from original 64 (closed 14 + fixed 14 today)
|
**Open Issues**: ~46 remaining (non-blocking quality-of-life improvements)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🎉 TODAY'S ACHIEVEMENTS (2025-01-03)
|
## Recent Achievements
|
||||||
|
|
||||||
In a single productive session, we fixed 12+ critical bugs and implemented missing features:
|
### 2025-07-05: ALPHA 0.1 ACHIEVED! 🎊🍾
|
||||||
|
**All Alpha Blockers Resolved!**
|
||||||
|
- Z-order rendering with performance optimization (Issue #63)
|
||||||
|
- Python Sequence Protocol for collections (Issue #69)
|
||||||
|
- Comprehensive Animation System (Issue #59)
|
||||||
|
- Moved RenderTexture to Beta (not needed for Alpha)
|
||||||
|
- **McRogueFace is ready for Alpha release!**
|
||||||
|
|
||||||
### Critical Bug Fixes:
|
### 2025-07-05: Z-order Rendering Complete! 🎉
|
||||||
- **Grid Segfault** - Fixed crash when texture is None/null, added default 16x16 cell dimensions
|
**Issue #63 Resolved**: Consistent z-order rendering with performance optimization
|
||||||
- **Issue #78** - Fixed middle mouse click incorrectly sending 'C' keyboard event (SFML event union bug)
|
- Dirty flag pattern prevents unnecessary per-frame sorting
|
||||||
- **Issue #77** - Fixed error message copy/paste bug in Grid validation
|
- Lazy sorting for both Scene elements and Frame children
|
||||||
- **Issue #74** - Added missing Grid.grid_y property (closes #74)
|
- Frame children now respect z_index (fixed inconsistency)
|
||||||
- **Entity Setters** - Fixed "new style getargs format" error with proper PyVector conversion
|
- Automatic dirty marking on z_index changes and collection modifications
|
||||||
- **PyVector** - Implemented missing x/y property getters and setters
|
- Performance: O(1) check for static scenes vs O(n log n) every frame
|
||||||
- **Sprite Texture** - Fixed setter returning -1 without setting exception
|
|
||||||
- **keypressScene** - Added validation to reject non-callable arguments
|
|
||||||
|
|
||||||
### New Features Implemented:
|
### 2025-07-05: Python Sequence Protocol Complete! 🎉
|
||||||
- **Issue #73** - Entity.index() method for finding position in collection (closes #73)
|
**Issue #69 Resolved**: Full sequence protocol implementation for collections
|
||||||
- **Issue #27** - EntityCollection.extend() for adding multiple entities at once (closes #27)
|
- Complete __setitem__, __delitem__, __contains__ support
|
||||||
- **Issue #33** - Sprite index validation against texture bounds (closes #33)
|
- Slice operations with extended slice support (step != 1)
|
||||||
- **Issue #3** - Removed deprecated player_input and turn-based functions (closes #3)
|
- Concatenation (+) and in-place concatenation (+=) with validation
|
||||||
- **Issue #2** - Removed entire registerPyAction/registerInputAction system (closes #2)
|
- Negative indexing throughout, index() and count() methods
|
||||||
|
- Type safety: UICollection (Frame/Caption/Sprite/Grid), EntityCollection (Entity only)
|
||||||
|
- Default value support: None for texture/font parameters uses engine defaults
|
||||||
|
|
||||||
### Test-Driven Development:
|
### 2025-07-05: Animation System Complete! 🎉
|
||||||
Every fix was accompanied by a comprehensive test using the timer callback pattern.
|
**Issue #59 Resolved**: Comprehensive animation system with 30+ easing functions
|
||||||
All tests verify the fix and ensure no regressions.
|
- Property-based animations for all UI classes (Frame, Caption, Sprite, Grid, Entity)
|
||||||
|
- Individual color component animation (r/g/b/a)
|
||||||
|
- Sprite sequence animation and text typewriter effects
|
||||||
|
- Pure C++ execution without Python callbacks
|
||||||
|
- Delta animation support for relative values
|
||||||
|
|
||||||
|
### 2025-01-03: Major Stability Update
|
||||||
|
**Major Cleanup**: Removed deprecated registerPyAction system (-180 lines)
|
||||||
|
**Bug Fixes**: 12 critical issues including Grid segfault, Issue #78 (middle click), Entity setters
|
||||||
|
**New Features**: Entity.index() (#73), EntityCollection.extend() (#27), Sprite validation (#33)
|
||||||
|
**Test Coverage**: Comprehensive test suite with timer callback pattern established
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -59,6 +75,8 @@ All tests verify the fix and ensure no regressions.
|
||||||
2. **Honor system for scripts** - Scripts must return control to C++ render loop
|
2. **Honor system for scripts** - Scripts must return control to C++ render loop
|
||||||
3. **Shared Python state** - All --exec scripts share the same interpreter
|
3. **Shared Python state** - All --exec scripts share the same interpreter
|
||||||
4. **No threading complexity** - Chose simplicity over parallelism (see THREADING_FOOTGUNS.md)
|
4. **No threading complexity** - Chose simplicity over parallelism (see THREADING_FOOTGUNS.md)
|
||||||
|
5. **Animation system in pure C++** - All interpolation happens in C++ for performance
|
||||||
|
6. **Property-based animation** - Unified interface for all UI element properties
|
||||||
|
|
||||||
#### Key Files Created:
|
#### Key Files Created:
|
||||||
- `src/McRFPy_Automation.h/cpp` - Complete automation API implementation
|
- `src/McRFPy_Automation.h/cpp` - Complete automation API implementation
|
||||||
|
@ -90,44 +108,49 @@ Created comprehensive test suite with 13 tests covering all Python-exposed metho
|
||||||
- #41: UICollection.find(name) method
|
- #41: UICollection.find(name) method
|
||||||
- #38: Frame 'children' constructor parameter
|
- #38: Frame 'children' constructor parameter
|
||||||
- #33: Sprite index validation
|
- #33: Sprite index validation
|
||||||
- #69: Partial Sequence Protocol (no slicing, 'in' operator)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🚧 IMMEDIATE PRIORITY: Critical Bugfixes & Iterator Completion
|
## 🚀 NEXT PHASE: Beta Features & Polish
|
||||||
|
|
||||||
### 🔥 Critical Bugfixes (Complete First)
|
### Alpha Complete! Moving to Beta Priorities:
|
||||||
- [ ] **CRITICAL: Grid Segfault** - Grid class crashes on instantiation (blocks ALL Grid functionality) - *High Priority*
|
1. ~~**#69** - Python Sequence Protocol for collections~~ - *Completed! (2025-07-05)*
|
||||||
- [ ] **#78** - Middle Mouse Click sends "C" keyboard event to scene event handler - *Confirmed Bug*
|
2. ~~**#63** - Z-order rendering for UIDrawables~~ - *Completed! (2025-07-05)*
|
||||||
- [ ] **#77** - Fix error message copy/paste bug (`x value out of range (0, Grid.grid_y)`) - *Isolated Fix*
|
3. ~~**#59** - Animation system~~ - *Completed! (2025-07-05)*
|
||||||
- [ ] **#74** - Add missing `Grid.grid_y` property referenced in error messages - *Isolated Fix*
|
4. **#6** - RenderTexture concept - *Extensive Overhaul*
|
||||||
|
5. ~~**#47** - New README.md for Alpha release~~ - *Completed*
|
||||||
|
- [x] **#78** - Middle Mouse Click sends "C" keyboard event - *Fixed*
|
||||||
|
- [x] **#77** - Fix error message copy/paste bug - *Fixed*
|
||||||
|
- [x] **#74** - Add missing `Grid.grid_y` property - *Fixed*
|
||||||
- [ ] **#37** - Fix Windows build module import from "scripts" directory - *Isolated Fix*
|
- [ ] **#37** - Fix Windows build module import from "scripts" directory - *Isolated Fix*
|
||||||
- [ ] **Entity Property Setters** - Fix "new style getargs format" error - *Multiple Fixes*
|
- [x] **Entity Property Setters** - Fix "new style getargs format" error - *Fixed*
|
||||||
- [ ] **Sprite Texture Setter** - Fix "error return without exception set" - *Isolated Fix*
|
- [x] **Sprite Texture Setter** - Fix "error return without exception set" - *Fixed*
|
||||||
- [ ] **keypressScene() Validation** - Add proper error handling for non-callable arguments - *Isolated Fix*
|
- [x] **keypressScene() Validation** - Add proper error handling - *Fixed*
|
||||||
|
|
||||||
### 🔄 Complete Iterator System
|
### 🔄 Complete Iterator System
|
||||||
**Status**: Core iterators complete (#72 closed), Grid point iterators still pending
|
**Status**: Core iterators complete (#72 closed), Grid point iterators still pending
|
||||||
|
|
||||||
- [ ] **Grid Point Iterator Implementation** - Complete the remaining grid iteration work
|
- [ ] **Grid Point Iterator Implementation** - Complete the remaining grid iteration work
|
||||||
- [ ] **#73** - Add `entity.index()` method for collection removal - *Isolated Fix*
|
- [x] **#73** - Add `entity.index()` method for collection removal - *Fixed*
|
||||||
- [ ] **#69** ⚠️ **Alpha Blocker** - Refactor all collections to use Python Sequence Protocol - *Extensive Overhaul*
|
- [x] **#69** ⚠️ **Alpha Blocker** - Refactor all collections to use Python Sequence Protocol - *Completed! (2025-07-05)*
|
||||||
|
|
||||||
**Dependencies**: Grid point iterators → #73 entity.index() → #69 Sequence Protocol overhaul
|
**Dependencies**: Grid point iterators → #73 entity.index() → #69 Sequence Protocol overhaul
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🎯 ALPHA 0.1 RELEASE BLOCKERS (6 Issues)
|
## ✅ ALPHA 0.1 RELEASE ACHIEVED! (All Blockers Complete)
|
||||||
|
|
||||||
### ⚠️ Must Complete Before Alpha Release
|
### ✅ All Alpha Requirements Complete!
|
||||||
- [ ] **#69** - Collections use Python Sequence Protocol - *Extensive Overhaul*
|
- [x] **#69** - Collections use Python Sequence Protocol - *Completed! (2025-07-05)*
|
||||||
- [ ] **#63** - Z-order rendering for UIDrawables - *Multiple Integrations*
|
- [x] **#63** - Z-order rendering for UIDrawables - *Completed! (2025-07-05)*
|
||||||
- [ ] **#59** - Animation system for arbitrary UIDrawable fields - *Extensive Overhaul*
|
- [x] **#59** - Animation system for arbitrary UIDrawable fields - *Completed! (2025-07-05)*
|
||||||
- [ ] **#6** - RenderTexture concept for all UIDrawables - *Extensive Overhaul*
|
- [x] **#47** - New README.md for Alpha release - *Completed*
|
||||||
- [ ] **#47** - New README.md for Alpha release - *Isolated Fix*
|
|
||||||
- [x] **#3** - Remove deprecated `McRFPy_API::player_input` - *Completed*
|
- [x] **#3** - Remove deprecated `McRFPy_API::player_input` - *Completed*
|
||||||
- [x] **#2** - Remove `registerPyAction` system - *Completed*
|
- [x] **#2** - Remove `registerPyAction` system - *Completed*
|
||||||
|
|
||||||
|
### 📋 Moved to Beta:
|
||||||
|
- [ ] **#6** - RenderTexture concept - *Moved to Beta (not needed for Alpha)*
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🗂 ISSUE TRIAGE BY SYSTEM (78 Total Issues)
|
## 🗂 ISSUE TRIAGE BY SYSTEM (78 Total Issues)
|
||||||
|
@ -135,8 +158,8 @@ Created comprehensive test suite with 13 tests covering all Python-exposed metho
|
||||||
### 🎮 Core Engine Systems
|
### 🎮 Core Engine Systems
|
||||||
|
|
||||||
#### Iterator/Collection System (2 issues)
|
#### Iterator/Collection System (2 issues)
|
||||||
- [ ] **#73** - Entity index() method for removal - *Isolated Fix*
|
- [x] **#73** - Entity index() method for removal - *Fixed*
|
||||||
- [ ] **#69** ⚠️ **Alpha Blocker** - Sequence Protocol refactor - *Extensive Overhaul*
|
- [x] **#69** ⚠️ **Alpha Blocker** - Sequence Protocol refactor - *Completed! (2025-07-05)*
|
||||||
|
|
||||||
#### Python/C++ Integration (7 issues)
|
#### Python/C++ Integration (7 issues)
|
||||||
- [ ] **#76** - UIEntity derived type preservation in collections - *Multiple Integrations*
|
- [ ] **#76** - UIEntity derived type preservation in collections - *Multiple Integrations*
|
||||||
|
@ -149,7 +172,7 @@ Created comprehensive test suite with 13 tests covering all Python-exposed metho
|
||||||
|
|
||||||
#### UI/Rendering System (12 issues)
|
#### UI/Rendering System (12 issues)
|
||||||
- [ ] **#63** ⚠️ **Alpha Blocker** - Z-order for UIDrawables - *Multiple Integrations*
|
- [ ] **#63** ⚠️ **Alpha Blocker** - Z-order for UIDrawables - *Multiple Integrations*
|
||||||
- [ ] **#59** ⚠️ **Alpha Blocker** - Animation system - *Extensive Overhaul*
|
- [x] **#59** ⚠️ **Alpha Blocker** - Animation system - *Completed! (2025-07-05)*
|
||||||
- [ ] **#6** ⚠️ **Alpha Blocker** - RenderTexture for all UIDrawables - *Extensive Overhaul*
|
- [ ] **#6** ⚠️ **Alpha Blocker** - RenderTexture for all UIDrawables - *Extensive Overhaul*
|
||||||
- [ ] **#10** - UIDrawable visibility/AABB system - *Extensive Overhaul*
|
- [ ] **#10** - UIDrawable visibility/AABB system - *Extensive Overhaul*
|
||||||
- [ ] **#8** - UIGrid RenderTexture viewport sizing - *Multiple Integrations*
|
- [ ] **#8** - UIGrid RenderTexture viewport sizing - *Multiple Integrations*
|
||||||
|
@ -158,7 +181,7 @@ Created comprehensive test suite with 13 tests covering all Python-exposed metho
|
||||||
- [ ] **#50** - UIGrid background color field - *Isolated Fix*
|
- [ ] **#50** - UIGrid background color field - *Isolated Fix*
|
||||||
- [ ] **#19** - Sprite get/set texture methods - *Multiple Integrations*
|
- [ ] **#19** - Sprite get/set texture methods - *Multiple Integrations*
|
||||||
- [ ] **#17** - Move UISprite position into sf::Sprite - *Isolated Fix*
|
- [ ] **#17** - Move UISprite position into sf::Sprite - *Isolated Fix*
|
||||||
- [ ] **#33** - Sprite index validation against texture range - *Isolated Fix*
|
- [x] **#33** - Sprite index validation against texture range - *Fixed*
|
||||||
|
|
||||||
#### Grid/Entity System (6 issues)
|
#### Grid/Entity System (6 issues)
|
||||||
- [ ] **#30** - Entity/Grid association management (.die() method) - *Extensive Overhaul*
|
- [ ] **#30** - Entity/Grid association management (.die() method) - *Extensive Overhaul*
|
||||||
|
@ -183,7 +206,7 @@ Created comprehensive test suite with 13 tests covering all Python-exposed metho
|
||||||
- [ ] **#41** - `.find(name)` method for collections - *Multiple Integrations*
|
- [ ] **#41** - `.find(name)` method for collections - *Multiple Integrations*
|
||||||
- [ ] **#38** - `children` arg for Frame initialization - *Isolated Fix*
|
- [ ] **#38** - `children` arg for Frame initialization - *Isolated Fix*
|
||||||
- [ ] **#42** - Click callback arg for UIDrawable init - *Isolated Fix*
|
- [ ] **#42** - Click callback arg for UIDrawable init - *Isolated Fix*
|
||||||
- [ ] **#27** - UIEntityCollection.extend() method - *Isolated Fix*
|
- [x] **#27** - UIEntityCollection.extend() method - *Fixed*
|
||||||
- [ ] **#28** - UICollectionIter for scene ui iteration - *Isolated Fix*
|
- [ ] **#28** - UICollectionIter for scene ui iteration - *Isolated Fix*
|
||||||
- [ ] **#26** - UIEntityCollectionIter implementation - *Isolated Fix*
|
- [ ] **#26** - UIEntityCollectionIter implementation - *Isolated Fix*
|
||||||
|
|
||||||
|
@ -238,8 +261,8 @@ REMAINING IN PHASE 1:
|
||||||
1. Collections Sequence Protocol (#69) - Major refactor, alpha blocker
|
1. Collections Sequence Protocol (#69) - Major refactor, alpha blocker
|
||||||
2. Z-order rendering (#63) - Essential UI improvement, alpha blocker
|
2. Z-order rendering (#63) - Essential UI improvement, alpha blocker
|
||||||
3. RenderTexture overhaul (#6) - Core rendering improvement, alpha blocker
|
3. RenderTexture overhaul (#6) - Core rendering improvement, alpha blocker
|
||||||
4. Animation system (#59) - Major feature, alpha blocker
|
4. ✅ Animation system (#59) - COMPLETE! 30+ easing functions, all UI properties
|
||||||
5. Documentation (#47, #48) - Complete alpha release docs
|
5. ✅ Documentation (#47) - README.md complete, #48 dependency docs remaining
|
||||||
```
|
```
|
||||||
|
|
||||||
### Phase 3: Engine Architecture (6-8 weeks)
|
### Phase 3: Engine Architecture (6-8 weeks)
|
||||||
|
@ -303,7 +326,7 @@ REMAINING IN PHASE 1:
|
||||||
4. **Multi-Platform**: Windows/Linux feature parity maintained
|
4. **Multi-Platform**: Windows/Linux feature parity maintained
|
||||||
|
|
||||||
### Success Metrics for Alpha 0.1
|
### Success Metrics for Alpha 0.1
|
||||||
- [ ] All 7 Alpha Blocker issues resolved
|
- [ ] All Alpha Blocker issues resolved (5 of 7 complete: #69, #59, #47, #3, #2)
|
||||||
- [ ] Grid point iteration complete and tested
|
- [ ] Grid point iteration complete and tested
|
||||||
- [ ] Clean build on Windows and Linux
|
- [ ] Clean build on Windows and Linux
|
||||||
- [ ] Documentation sufficient for external developers
|
- [ ] Documentation sufficient for external developers
|
||||||
|
@ -331,9 +354,9 @@ REMAINING IN PHASE 1:
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*Last Updated: 2025-07-03*
|
*Last Updated: 2025-07-05*
|
||||||
*Total Open Issues: 64* (from original 78)
|
*Total Open Issues: 62* (from original 78)
|
||||||
*Alpha Blockers: 7*
|
*Alpha Status: 🎉 COMPLETE! All blockers resolved!*
|
||||||
*Current Work: Python interpreter mode features (--exec flag, automation API)*
|
*Achievement Unlocked: Alpha 0.1 Release Ready*
|
||||||
*Next Session: Continue interpreter mode or switch to critical bugfixes*
|
*Next Phase: Beta features including RenderTexture (#6), advanced UI patterns, and platform polish*
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,527 @@
|
||||||
|
#include "Animation.h"
|
||||||
|
#include "UIDrawable.h"
|
||||||
|
#include "UIEntity.h"
|
||||||
|
#include <cmath>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#ifndef M_PI
|
||||||
|
#define M_PI 3.14159265358979323846
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Animation implementation
|
||||||
|
Animation::Animation(const std::string& targetProperty,
|
||||||
|
const AnimationValue& targetValue,
|
||||||
|
float duration,
|
||||||
|
EasingFunction easingFunc,
|
||||||
|
bool delta)
|
||||||
|
: targetProperty(targetProperty)
|
||||||
|
, targetValue(targetValue)
|
||||||
|
, duration(duration)
|
||||||
|
, easingFunc(easingFunc)
|
||||||
|
, delta(delta)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void Animation::start(UIDrawable* target) {
|
||||||
|
currentTarget = target;
|
||||||
|
elapsed = 0.0f;
|
||||||
|
|
||||||
|
// Capture startValue from target based on targetProperty
|
||||||
|
if (!currentTarget) return;
|
||||||
|
|
||||||
|
// Try to get the current value based on the expected type
|
||||||
|
std::visit([this](const auto& targetVal) {
|
||||||
|
using T = std::decay_t<decltype(targetVal)>;
|
||||||
|
|
||||||
|
if constexpr (std::is_same_v<T, float>) {
|
||||||
|
float value;
|
||||||
|
if (currentTarget->getProperty(targetProperty, value)) {
|
||||||
|
startValue = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, int>) {
|
||||||
|
int value;
|
||||||
|
if (currentTarget->getProperty(targetProperty, value)) {
|
||||||
|
startValue = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, std::vector<int>>) {
|
||||||
|
// For sprite animation, get current sprite index
|
||||||
|
int value;
|
||||||
|
if (currentTarget->getProperty(targetProperty, value)) {
|
||||||
|
startValue = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, sf::Color>) {
|
||||||
|
sf::Color value;
|
||||||
|
if (currentTarget->getProperty(targetProperty, value)) {
|
||||||
|
startValue = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, sf::Vector2f>) {
|
||||||
|
sf::Vector2f value;
|
||||||
|
if (currentTarget->getProperty(targetProperty, value)) {
|
||||||
|
startValue = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, std::string>) {
|
||||||
|
std::string value;
|
||||||
|
if (currentTarget->getProperty(targetProperty, value)) {
|
||||||
|
startValue = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, targetValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Animation::startEntity(UIEntity* target) {
|
||||||
|
currentEntityTarget = target;
|
||||||
|
currentTarget = nullptr; // Clear drawable target
|
||||||
|
elapsed = 0.0f;
|
||||||
|
|
||||||
|
// Capture the starting value from the entity
|
||||||
|
std::visit([this, target](const auto& val) {
|
||||||
|
using T = std::decay_t<decltype(val)>;
|
||||||
|
|
||||||
|
if constexpr (std::is_same_v<T, float>) {
|
||||||
|
float value = 0.0f;
|
||||||
|
if (target->getProperty(targetProperty, value)) {
|
||||||
|
startValue = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, int>) {
|
||||||
|
// For entities, we might need to handle sprite_number differently
|
||||||
|
if (targetProperty == "sprite_number") {
|
||||||
|
startValue = target->sprite.getSpriteIndex();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Entities don't support other types yet
|
||||||
|
}, targetValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Animation::update(float deltaTime) {
|
||||||
|
if ((!currentTarget && !currentEntityTarget) || isComplete()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
elapsed += deltaTime;
|
||||||
|
elapsed = std::min(elapsed, duration);
|
||||||
|
|
||||||
|
// Calculate easing value (0.0 to 1.0)
|
||||||
|
float t = duration > 0 ? elapsed / duration : 1.0f;
|
||||||
|
float easedT = easingFunc(t);
|
||||||
|
|
||||||
|
// Get interpolated value
|
||||||
|
AnimationValue currentValue = interpolate(easedT);
|
||||||
|
|
||||||
|
// Apply currentValue to target (either drawable or entity)
|
||||||
|
std::visit([this](const auto& value) {
|
||||||
|
using T = std::decay_t<decltype(value)>;
|
||||||
|
|
||||||
|
if (currentTarget) {
|
||||||
|
// Handle UIDrawable targets
|
||||||
|
if constexpr (std::is_same_v<T, float>) {
|
||||||
|
currentTarget->setProperty(targetProperty, value);
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, int>) {
|
||||||
|
currentTarget->setProperty(targetProperty, value);
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, sf::Color>) {
|
||||||
|
currentTarget->setProperty(targetProperty, value);
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, sf::Vector2f>) {
|
||||||
|
currentTarget->setProperty(targetProperty, value);
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, std::string>) {
|
||||||
|
currentTarget->setProperty(targetProperty, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (currentEntityTarget) {
|
||||||
|
// Handle UIEntity targets
|
||||||
|
if constexpr (std::is_same_v<T, float>) {
|
||||||
|
currentEntityTarget->setProperty(targetProperty, value);
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, int>) {
|
||||||
|
currentEntityTarget->setProperty(targetProperty, value);
|
||||||
|
}
|
||||||
|
// Entities don't support other types yet
|
||||||
|
}
|
||||||
|
}, currentValue);
|
||||||
|
|
||||||
|
return !isComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimationValue Animation::getCurrentValue() const {
|
||||||
|
float t = duration > 0 ? elapsed / duration : 1.0f;
|
||||||
|
float easedT = easingFunc(t);
|
||||||
|
return interpolate(easedT);
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimationValue Animation::interpolate(float t) const {
|
||||||
|
// Visit the variant to perform type-specific interpolation
|
||||||
|
return std::visit([this, t](const auto& target) -> AnimationValue {
|
||||||
|
using T = std::decay_t<decltype(target)>;
|
||||||
|
|
||||||
|
if constexpr (std::is_same_v<T, float>) {
|
||||||
|
// Interpolate float
|
||||||
|
const float* start = std::get_if<float>(&startValue);
|
||||||
|
if (!start) return target; // Type mismatch
|
||||||
|
|
||||||
|
if (delta) {
|
||||||
|
return *start + target * t;
|
||||||
|
} else {
|
||||||
|
return *start + (target - *start) * t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, int>) {
|
||||||
|
// Interpolate integer
|
||||||
|
const int* start = std::get_if<int>(&startValue);
|
||||||
|
if (!start) return target;
|
||||||
|
|
||||||
|
float result;
|
||||||
|
if (delta) {
|
||||||
|
result = *start + target * t;
|
||||||
|
} else {
|
||||||
|
result = *start + (target - *start) * t;
|
||||||
|
}
|
||||||
|
return static_cast<int>(std::round(result));
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, std::vector<int>>) {
|
||||||
|
// For sprite animation, interpolate through the list
|
||||||
|
if (target.empty()) return target;
|
||||||
|
|
||||||
|
// Map t to an index in the vector
|
||||||
|
size_t index = static_cast<size_t>(t * (target.size() - 1));
|
||||||
|
index = std::min(index, target.size() - 1);
|
||||||
|
return static_cast<int>(target[index]);
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, sf::Color>) {
|
||||||
|
// Interpolate color
|
||||||
|
const sf::Color* start = std::get_if<sf::Color>(&startValue);
|
||||||
|
if (!start) return target;
|
||||||
|
|
||||||
|
sf::Color result;
|
||||||
|
if (delta) {
|
||||||
|
result.r = std::clamp(start->r + target.r * t, 0.0f, 255.0f);
|
||||||
|
result.g = std::clamp(start->g + target.g * t, 0.0f, 255.0f);
|
||||||
|
result.b = std::clamp(start->b + target.b * t, 0.0f, 255.0f);
|
||||||
|
result.a = std::clamp(start->a + target.a * t, 0.0f, 255.0f);
|
||||||
|
} else {
|
||||||
|
result.r = start->r + (target.r - start->r) * t;
|
||||||
|
result.g = start->g + (target.g - start->g) * t;
|
||||||
|
result.b = start->b + (target.b - start->b) * t;
|
||||||
|
result.a = start->a + (target.a - start->a) * t;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, sf::Vector2f>) {
|
||||||
|
// Interpolate vector
|
||||||
|
const sf::Vector2f* start = std::get_if<sf::Vector2f>(&startValue);
|
||||||
|
if (!start) return target;
|
||||||
|
|
||||||
|
if (delta) {
|
||||||
|
return sf::Vector2f(start->x + target.x * t,
|
||||||
|
start->y + target.y * t);
|
||||||
|
} else {
|
||||||
|
return sf::Vector2f(start->x + (target.x - start->x) * t,
|
||||||
|
start->y + (target.y - start->y) * t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, std::string>) {
|
||||||
|
// For text, show characters based on t
|
||||||
|
const std::string* start = std::get_if<std::string>(&startValue);
|
||||||
|
if (!start) return target;
|
||||||
|
|
||||||
|
// If delta mode, append characters from target
|
||||||
|
if (delta) {
|
||||||
|
size_t chars = static_cast<size_t>(target.length() * t);
|
||||||
|
return *start + target.substr(0, chars);
|
||||||
|
} else {
|
||||||
|
// Transition from start text to target text
|
||||||
|
if (t < 0.5f) {
|
||||||
|
// First half: remove characters from start
|
||||||
|
size_t chars = static_cast<size_t>(start->length() * (1.0f - t * 2.0f));
|
||||||
|
return start->substr(0, chars);
|
||||||
|
} else {
|
||||||
|
// Second half: add characters to target
|
||||||
|
size_t chars = static_cast<size_t>(target.length() * ((t - 0.5f) * 2.0f));
|
||||||
|
return target.substr(0, chars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return target; // Fallback
|
||||||
|
}, targetValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Easing functions implementation
|
||||||
|
namespace EasingFunctions {
|
||||||
|
|
||||||
|
float linear(float t) {
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeIn(float t) {
|
||||||
|
return t * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeOut(float t) {
|
||||||
|
return t * (2.0f - t);
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeInOut(float t) {
|
||||||
|
return t < 0.5f ? 2.0f * t * t : -1.0f + (4.0f - 2.0f * t) * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quadratic
|
||||||
|
float easeInQuad(float t) {
|
||||||
|
return t * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeOutQuad(float t) {
|
||||||
|
return t * (2.0f - t);
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeInOutQuad(float t) {
|
||||||
|
return t < 0.5f ? 2.0f * t * t : -1.0f + (4.0f - 2.0f * t) * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cubic
|
||||||
|
float easeInCubic(float t) {
|
||||||
|
return t * t * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeOutCubic(float t) {
|
||||||
|
float t1 = t - 1.0f;
|
||||||
|
return t1 * t1 * t1 + 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeInOutCubic(float t) {
|
||||||
|
return t < 0.5f ? 4.0f * t * t * t : (t - 1.0f) * (2.0f * t - 2.0f) * (2.0f * t - 2.0f) + 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quartic
|
||||||
|
float easeInQuart(float t) {
|
||||||
|
return t * t * t * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeOutQuart(float t) {
|
||||||
|
float t1 = t - 1.0f;
|
||||||
|
return 1.0f - t1 * t1 * t1 * t1;
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeInOutQuart(float t) {
|
||||||
|
return t < 0.5f ? 8.0f * t * t * t * t : 1.0f - 8.0f * (t - 1.0f) * (t - 1.0f) * (t - 1.0f) * (t - 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sine
|
||||||
|
float easeInSine(float t) {
|
||||||
|
return 1.0f - std::cos(t * M_PI / 2.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeOutSine(float t) {
|
||||||
|
return std::sin(t * M_PI / 2.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeInOutSine(float t) {
|
||||||
|
return 0.5f * (1.0f - std::cos(M_PI * t));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exponential
|
||||||
|
float easeInExpo(float t) {
|
||||||
|
return t == 0.0f ? 0.0f : std::pow(2.0f, 10.0f * (t - 1.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeOutExpo(float t) {
|
||||||
|
return t == 1.0f ? 1.0f : 1.0f - std::pow(2.0f, -10.0f * t);
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeInOutExpo(float t) {
|
||||||
|
if (t == 0.0f) return 0.0f;
|
||||||
|
if (t == 1.0f) return 1.0f;
|
||||||
|
if (t < 0.5f) {
|
||||||
|
return 0.5f * std::pow(2.0f, 20.0f * t - 10.0f);
|
||||||
|
} else {
|
||||||
|
return 1.0f - 0.5f * std::pow(2.0f, -20.0f * t + 10.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Circular
|
||||||
|
float easeInCirc(float t) {
|
||||||
|
return 1.0f - std::sqrt(1.0f - t * t);
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeOutCirc(float t) {
|
||||||
|
float t1 = t - 1.0f;
|
||||||
|
return std::sqrt(1.0f - t1 * t1);
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeInOutCirc(float t) {
|
||||||
|
if (t < 0.5f) {
|
||||||
|
return 0.5f * (1.0f - std::sqrt(1.0f - 4.0f * t * t));
|
||||||
|
} else {
|
||||||
|
return 0.5f * (std::sqrt(1.0f - (2.0f * t - 2.0f) * (2.0f * t - 2.0f)) + 1.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Elastic
|
||||||
|
float easeInElastic(float t) {
|
||||||
|
if (t == 0.0f) return 0.0f;
|
||||||
|
if (t == 1.0f) return 1.0f;
|
||||||
|
float p = 0.3f;
|
||||||
|
float a = 1.0f;
|
||||||
|
float s = p / 4.0f;
|
||||||
|
float t1 = t - 1.0f;
|
||||||
|
return -(a * std::pow(2.0f, 10.0f * t1) * std::sin((t1 - s) * (2.0f * M_PI) / p));
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeOutElastic(float t) {
|
||||||
|
if (t == 0.0f) return 0.0f;
|
||||||
|
if (t == 1.0f) return 1.0f;
|
||||||
|
float p = 0.3f;
|
||||||
|
float a = 1.0f;
|
||||||
|
float s = p / 4.0f;
|
||||||
|
return a * std::pow(2.0f, -10.0f * t) * std::sin((t - s) * (2.0f * M_PI) / p) + 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeInOutElastic(float t) {
|
||||||
|
if (t == 0.0f) return 0.0f;
|
||||||
|
if (t == 1.0f) return 1.0f;
|
||||||
|
float p = 0.45f;
|
||||||
|
float a = 1.0f;
|
||||||
|
float s = p / 4.0f;
|
||||||
|
|
||||||
|
if (t < 0.5f) {
|
||||||
|
float t1 = 2.0f * t - 1.0f;
|
||||||
|
return -0.5f * (a * std::pow(2.0f, 10.0f * t1) * std::sin((t1 - s) * (2.0f * M_PI) / p));
|
||||||
|
} else {
|
||||||
|
float t1 = 2.0f * t - 1.0f;
|
||||||
|
return a * std::pow(2.0f, -10.0f * t1) * std::sin((t1 - s) * (2.0f * M_PI) / p) * 0.5f + 1.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Back (overshooting)
|
||||||
|
float easeInBack(float t) {
|
||||||
|
const float s = 1.70158f;
|
||||||
|
return t * t * ((s + 1.0f) * t - s);
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeOutBack(float t) {
|
||||||
|
const float s = 1.70158f;
|
||||||
|
float t1 = t - 1.0f;
|
||||||
|
return t1 * t1 * ((s + 1.0f) * t1 + s) + 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeInOutBack(float t) {
|
||||||
|
const float s = 1.70158f * 1.525f;
|
||||||
|
if (t < 0.5f) {
|
||||||
|
return 0.5f * (4.0f * t * t * ((s + 1.0f) * 2.0f * t - s));
|
||||||
|
} else {
|
||||||
|
float t1 = 2.0f * t - 2.0f;
|
||||||
|
return 0.5f * (t1 * t1 * ((s + 1.0f) * t1 + s) + 2.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bounce
|
||||||
|
float easeOutBounce(float t) {
|
||||||
|
if (t < 1.0f / 2.75f) {
|
||||||
|
return 7.5625f * t * t;
|
||||||
|
} else if (t < 2.0f / 2.75f) {
|
||||||
|
float t1 = t - 1.5f / 2.75f;
|
||||||
|
return 7.5625f * t1 * t1 + 0.75f;
|
||||||
|
} else if (t < 2.5f / 2.75f) {
|
||||||
|
float t1 = t - 2.25f / 2.75f;
|
||||||
|
return 7.5625f * t1 * t1 + 0.9375f;
|
||||||
|
} else {
|
||||||
|
float t1 = t - 2.625f / 2.75f;
|
||||||
|
return 7.5625f * t1 * t1 + 0.984375f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeInBounce(float t) {
|
||||||
|
return 1.0f - easeOutBounce(1.0f - t);
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeInOutBounce(float t) {
|
||||||
|
if (t < 0.5f) {
|
||||||
|
return 0.5f * easeInBounce(2.0f * t);
|
||||||
|
} else {
|
||||||
|
return 0.5f * easeOutBounce(2.0f * t - 1.0f) + 0.5f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get easing function by name
|
||||||
|
EasingFunction getByName(const std::string& name) {
|
||||||
|
static std::unordered_map<std::string, EasingFunction> easingMap = {
|
||||||
|
{"linear", linear},
|
||||||
|
{"easeIn", easeIn},
|
||||||
|
{"easeOut", easeOut},
|
||||||
|
{"easeInOut", easeInOut},
|
||||||
|
{"easeInQuad", easeInQuad},
|
||||||
|
{"easeOutQuad", easeOutQuad},
|
||||||
|
{"easeInOutQuad", easeInOutQuad},
|
||||||
|
{"easeInCubic", easeInCubic},
|
||||||
|
{"easeOutCubic", easeOutCubic},
|
||||||
|
{"easeInOutCubic", easeInOutCubic},
|
||||||
|
{"easeInQuart", easeInQuart},
|
||||||
|
{"easeOutQuart", easeOutQuart},
|
||||||
|
{"easeInOutQuart", easeInOutQuart},
|
||||||
|
{"easeInSine", easeInSine},
|
||||||
|
{"easeOutSine", easeOutSine},
|
||||||
|
{"easeInOutSine", easeInOutSine},
|
||||||
|
{"easeInExpo", easeInExpo},
|
||||||
|
{"easeOutExpo", easeOutExpo},
|
||||||
|
{"easeInOutExpo", easeInOutExpo},
|
||||||
|
{"easeInCirc", easeInCirc},
|
||||||
|
{"easeOutCirc", easeOutCirc},
|
||||||
|
{"easeInOutCirc", easeInOutCirc},
|
||||||
|
{"easeInElastic", easeInElastic},
|
||||||
|
{"easeOutElastic", easeOutElastic},
|
||||||
|
{"easeInOutElastic", easeInOutElastic},
|
||||||
|
{"easeInBack", easeInBack},
|
||||||
|
{"easeOutBack", easeOutBack},
|
||||||
|
{"easeInOutBack", easeInOutBack},
|
||||||
|
{"easeInBounce", easeInBounce},
|
||||||
|
{"easeOutBounce", easeOutBounce},
|
||||||
|
{"easeInOutBounce", easeInOutBounce}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto it = easingMap.find(name);
|
||||||
|
if (it != easingMap.end()) {
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
return linear; // Default to linear
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace EasingFunctions
|
||||||
|
|
||||||
|
// AnimationManager implementation
|
||||||
|
AnimationManager& AnimationManager::getInstance() {
|
||||||
|
static AnimationManager instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimationManager::addAnimation(std::shared_ptr<Animation> animation) {
|
||||||
|
activeAnimations.push_back(animation);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimationManager::update(float deltaTime) {
|
||||||
|
for (auto& anim : activeAnimations) {
|
||||||
|
anim->update(deltaTime);
|
||||||
|
}
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimationManager::cleanup() {
|
||||||
|
activeAnimations.erase(
|
||||||
|
std::remove_if(activeAnimations.begin(), activeAnimations.end(),
|
||||||
|
[](const std::shared_ptr<Animation>& anim) {
|
||||||
|
return anim->isComplete();
|
||||||
|
}),
|
||||||
|
activeAnimations.end()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimationManager::clear() {
|
||||||
|
activeAnimations.clear();
|
||||||
|
}
|
|
@ -0,0 +1,146 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <variant>
|
||||||
|
#include <vector>
|
||||||
|
#include <SFML/Graphics.hpp>
|
||||||
|
|
||||||
|
// Forward declarations
|
||||||
|
class UIDrawable;
|
||||||
|
class UIEntity;
|
||||||
|
|
||||||
|
// Forward declare namespace
|
||||||
|
namespace EasingFunctions {
|
||||||
|
float linear(float t);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Easing function type
|
||||||
|
typedef std::function<float(float)> EasingFunction;
|
||||||
|
|
||||||
|
// Animation target value can be various types
|
||||||
|
typedef std::variant<
|
||||||
|
float, // Single float value
|
||||||
|
int, // Single integer value
|
||||||
|
std::vector<int>, // List of integers (for sprite animation)
|
||||||
|
sf::Color, // Color animation
|
||||||
|
sf::Vector2f, // Vector animation
|
||||||
|
std::string // String animation (for text)
|
||||||
|
> AnimationValue;
|
||||||
|
|
||||||
|
class Animation {
|
||||||
|
public:
|
||||||
|
// Constructor
|
||||||
|
Animation(const std::string& targetProperty,
|
||||||
|
const AnimationValue& targetValue,
|
||||||
|
float duration,
|
||||||
|
EasingFunction easingFunc = EasingFunctions::linear,
|
||||||
|
bool delta = false);
|
||||||
|
|
||||||
|
// Apply this animation to a drawable
|
||||||
|
void start(UIDrawable* target);
|
||||||
|
|
||||||
|
// Apply this animation to an entity (special case since Entity doesn't inherit from UIDrawable)
|
||||||
|
void startEntity(UIEntity* target);
|
||||||
|
|
||||||
|
// Update animation (called each frame)
|
||||||
|
// Returns true if animation is still running, false if complete
|
||||||
|
bool update(float deltaTime);
|
||||||
|
|
||||||
|
// Get current interpolated value
|
||||||
|
AnimationValue getCurrentValue() const;
|
||||||
|
|
||||||
|
// Animation properties
|
||||||
|
std::string getTargetProperty() const { return targetProperty; }
|
||||||
|
float getDuration() const { return duration; }
|
||||||
|
float getElapsed() const { return elapsed; }
|
||||||
|
bool isComplete() const { return elapsed >= duration; }
|
||||||
|
bool isDelta() const { return delta; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string targetProperty; // Property name to animate (e.g., "x", "color.r", "sprite_number")
|
||||||
|
AnimationValue startValue; // Starting value (captured when animation starts)
|
||||||
|
AnimationValue targetValue; // Target value to animate to
|
||||||
|
float duration; // Animation duration in seconds
|
||||||
|
float elapsed = 0.0f; // Elapsed time
|
||||||
|
EasingFunction easingFunc; // Easing function to use
|
||||||
|
bool delta; // If true, targetValue is relative to start
|
||||||
|
|
||||||
|
UIDrawable* currentTarget = nullptr; // Current target being animated
|
||||||
|
UIEntity* currentEntityTarget = nullptr; // Current entity target (alternative to drawable)
|
||||||
|
|
||||||
|
// Helper to interpolate between values
|
||||||
|
AnimationValue interpolate(float t) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Easing functions library
|
||||||
|
namespace EasingFunctions {
|
||||||
|
// Basic easing functions
|
||||||
|
float linear(float t);
|
||||||
|
float easeIn(float t);
|
||||||
|
float easeOut(float t);
|
||||||
|
float easeInOut(float t);
|
||||||
|
|
||||||
|
// Advanced easing functions
|
||||||
|
float easeInQuad(float t);
|
||||||
|
float easeOutQuad(float t);
|
||||||
|
float easeInOutQuad(float t);
|
||||||
|
|
||||||
|
float easeInCubic(float t);
|
||||||
|
float easeOutCubic(float t);
|
||||||
|
float easeInOutCubic(float t);
|
||||||
|
|
||||||
|
float easeInQuart(float t);
|
||||||
|
float easeOutQuart(float t);
|
||||||
|
float easeInOutQuart(float t);
|
||||||
|
|
||||||
|
float easeInSine(float t);
|
||||||
|
float easeOutSine(float t);
|
||||||
|
float easeInOutSine(float t);
|
||||||
|
|
||||||
|
float easeInExpo(float t);
|
||||||
|
float easeOutExpo(float t);
|
||||||
|
float easeInOutExpo(float t);
|
||||||
|
|
||||||
|
float easeInCirc(float t);
|
||||||
|
float easeOutCirc(float t);
|
||||||
|
float easeInOutCirc(float t);
|
||||||
|
|
||||||
|
float easeInElastic(float t);
|
||||||
|
float easeOutElastic(float t);
|
||||||
|
float easeInOutElastic(float t);
|
||||||
|
|
||||||
|
float easeInBack(float t);
|
||||||
|
float easeOutBack(float t);
|
||||||
|
float easeInOutBack(float t);
|
||||||
|
|
||||||
|
float easeInBounce(float t);
|
||||||
|
float easeOutBounce(float t);
|
||||||
|
float easeInOutBounce(float t);
|
||||||
|
|
||||||
|
// Get easing function by name
|
||||||
|
EasingFunction getByName(const std::string& name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Animation manager to handle active animations
|
||||||
|
class AnimationManager {
|
||||||
|
public:
|
||||||
|
static AnimationManager& getInstance();
|
||||||
|
|
||||||
|
// Add an animation to be managed
|
||||||
|
void addAnimation(std::shared_ptr<Animation> animation);
|
||||||
|
|
||||||
|
// Update all animations
|
||||||
|
void update(float deltaTime);
|
||||||
|
|
||||||
|
// Remove completed animations
|
||||||
|
void cleanup();
|
||||||
|
|
||||||
|
// Clear all animations
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
private:
|
||||||
|
AnimationManager() = default;
|
||||||
|
std::vector<std::shared_ptr<Animation>> activeAnimations;
|
||||||
|
};
|
|
@ -4,6 +4,7 @@
|
||||||
#include "PyScene.h"
|
#include "PyScene.h"
|
||||||
#include "UITestScene.h"
|
#include "UITestScene.h"
|
||||||
#include "Resources.h"
|
#include "Resources.h"
|
||||||
|
#include "Animation.h"
|
||||||
|
|
||||||
GameEngine::GameEngine() : GameEngine(McRogueFaceConfig{})
|
GameEngine::GameEngine() : GameEngine(McRogueFaceConfig{})
|
||||||
{
|
{
|
||||||
|
@ -114,11 +115,18 @@ void GameEngine::run()
|
||||||
{
|
{
|
||||||
std::cout << "GameEngine::run() starting main loop..." << std::endl;
|
std::cout << "GameEngine::run() starting main loop..." << std::endl;
|
||||||
float fps = 0.0;
|
float fps = 0.0;
|
||||||
|
frameTime = 0.016f; // Initialize to ~60 FPS
|
||||||
clock.restart();
|
clock.restart();
|
||||||
while (running)
|
while (running)
|
||||||
{
|
{
|
||||||
currentScene()->update();
|
currentScene()->update();
|
||||||
testTimers();
|
testTimers();
|
||||||
|
|
||||||
|
// Update animations (only if frameTime is valid)
|
||||||
|
if (frameTime > 0.0f && frameTime < 1.0f) {
|
||||||
|
AnimationManager::getInstance().update(frameTime);
|
||||||
|
}
|
||||||
|
|
||||||
if (!headless) {
|
if (!headless) {
|
||||||
sUserInput();
|
sUserInput();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
#include "McRFPy_API.h"
|
#include "McRFPy_API.h"
|
||||||
#include "McRFPy_Automation.h"
|
#include "McRFPy_Automation.h"
|
||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
|
#include "PyAnimation.h"
|
||||||
#include "GameEngine.h"
|
#include "GameEngine.h"
|
||||||
#include "UI.h"
|
#include "UI.h"
|
||||||
#include "Resources.h"
|
#include "Resources.h"
|
||||||
|
#include "PyScene.h"
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
|
@ -76,6 +78,9 @@ PyObject* PyInit_mcrfpy()
|
||||||
/*collections & iterators*/
|
/*collections & iterators*/
|
||||||
&PyUICollectionType, &PyUICollectionIterType,
|
&PyUICollectionType, &PyUICollectionIterType,
|
||||||
&PyUIEntityCollectionType, &PyUIEntityCollectionIterType,
|
&PyUIEntityCollectionType, &PyUIEntityCollectionIterType,
|
||||||
|
|
||||||
|
/*animation*/
|
||||||
|
&PyAnimationType,
|
||||||
nullptr};
|
nullptr};
|
||||||
int i = 0;
|
int i = 0;
|
||||||
auto t = pytypes[i];
|
auto t = pytypes[i];
|
||||||
|
@ -535,3 +540,15 @@ PyObject* McRFPy_API::_setScale(PyObject* self, PyObject* args) {
|
||||||
Py_INCREF(Py_None);
|
Py_INCREF(Py_None);
|
||||||
return Py_None;
|
return Py_None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void McRFPy_API::markSceneNeedsSort() {
|
||||||
|
// Mark the current scene as needing a z_index sort
|
||||||
|
auto scene = game->currentScene();
|
||||||
|
if (scene && scene->ui_elements) {
|
||||||
|
// Cast to PyScene to access ui_elements_need_sort
|
||||||
|
PyScene* pyscene = dynamic_cast<PyScene*>(scene);
|
||||||
|
if (pyscene) {
|
||||||
|
pyscene->ui_elements_need_sort = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -70,4 +70,7 @@ public:
|
||||||
|
|
||||||
static void executeScript(std::string);
|
static void executeScript(std::string);
|
||||||
static void executePyString(std::string);
|
static void executePyString(std::string);
|
||||||
|
|
||||||
|
// Helper to mark scenes as needing z_index resort
|
||||||
|
static void markSceneNeedsSort();
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,234 @@
|
||||||
|
#include "PyAnimation.h"
|
||||||
|
#include "McRFPy_API.h"
|
||||||
|
#include "UIDrawable.h"
|
||||||
|
#include "UIFrame.h"
|
||||||
|
#include "UICaption.h"
|
||||||
|
#include "UISprite.h"
|
||||||
|
#include "UIGrid.h"
|
||||||
|
#include "UIEntity.h"
|
||||||
|
#include "UI.h" // For the PyTypeObject definitions
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
PyObject* PyAnimation::create(PyTypeObject* type, PyObject* args, PyObject* kwds) {
|
||||||
|
PyAnimationObject* self = (PyAnimationObject*)type->tp_alloc(type, 0);
|
||||||
|
if (self != NULL) {
|
||||||
|
// Will be initialized in init
|
||||||
|
}
|
||||||
|
return (PyObject*)self;
|
||||||
|
}
|
||||||
|
|
||||||
|
int PyAnimation::init(PyAnimationObject* self, PyObject* args, PyObject* kwds) {
|
||||||
|
static const char* keywords[] = {"property", "target", "duration", "easing", "delta", nullptr};
|
||||||
|
|
||||||
|
const char* property_name;
|
||||||
|
PyObject* target_value;
|
||||||
|
float duration;
|
||||||
|
const char* easing_name = "linear";
|
||||||
|
int delta = 0;
|
||||||
|
|
||||||
|
if (!PyArg_ParseTupleAndKeywords(args, kwds, "sOf|sp", const_cast<char**>(keywords),
|
||||||
|
&property_name, &target_value, &duration, &easing_name, &delta)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert Python target value to AnimationValue
|
||||||
|
AnimationValue animValue;
|
||||||
|
|
||||||
|
if (PyFloat_Check(target_value)) {
|
||||||
|
animValue = static_cast<float>(PyFloat_AsDouble(target_value));
|
||||||
|
}
|
||||||
|
else if (PyLong_Check(target_value)) {
|
||||||
|
animValue = static_cast<int>(PyLong_AsLong(target_value));
|
||||||
|
}
|
||||||
|
else if (PyList_Check(target_value)) {
|
||||||
|
// List of integers for sprite animation
|
||||||
|
std::vector<int> indices;
|
||||||
|
Py_ssize_t size = PyList_Size(target_value);
|
||||||
|
for (Py_ssize_t i = 0; i < size; i++) {
|
||||||
|
PyObject* item = PyList_GetItem(target_value, i);
|
||||||
|
if (PyLong_Check(item)) {
|
||||||
|
indices.push_back(PyLong_AsLong(item));
|
||||||
|
} else {
|
||||||
|
PyErr_SetString(PyExc_TypeError, "Sprite animation list must contain only integers");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
animValue = indices;
|
||||||
|
}
|
||||||
|
else if (PyTuple_Check(target_value)) {
|
||||||
|
Py_ssize_t size = PyTuple_Size(target_value);
|
||||||
|
if (size == 2) {
|
||||||
|
// Vector2f
|
||||||
|
float x = PyFloat_AsDouble(PyTuple_GetItem(target_value, 0));
|
||||||
|
float y = PyFloat_AsDouble(PyTuple_GetItem(target_value, 1));
|
||||||
|
animValue = sf::Vector2f(x, y);
|
||||||
|
}
|
||||||
|
else if (size == 3 || size == 4) {
|
||||||
|
// Color (RGB or RGBA)
|
||||||
|
int r = PyLong_AsLong(PyTuple_GetItem(target_value, 0));
|
||||||
|
int g = PyLong_AsLong(PyTuple_GetItem(target_value, 1));
|
||||||
|
int b = PyLong_AsLong(PyTuple_GetItem(target_value, 2));
|
||||||
|
int a = size == 4 ? PyLong_AsLong(PyTuple_GetItem(target_value, 3)) : 255;
|
||||||
|
animValue = sf::Color(r, g, b, a);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
PyErr_SetString(PyExc_ValueError, "Tuple must have 2 elements (vector) or 3-4 elements (color)");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (PyUnicode_Check(target_value)) {
|
||||||
|
// String for text animation
|
||||||
|
const char* str = PyUnicode_AsUTF8(target_value);
|
||||||
|
animValue = std::string(str);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
PyErr_SetString(PyExc_TypeError, "Target value must be float, int, list, tuple, or string");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get easing function
|
||||||
|
EasingFunction easingFunc = EasingFunctions::getByName(easing_name);
|
||||||
|
|
||||||
|
// Create the Animation
|
||||||
|
self->data = std::make_shared<Animation>(property_name, animValue, duration, easingFunc, delta != 0);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PyAnimation::dealloc(PyAnimationObject* self) {
|
||||||
|
self->data.reset();
|
||||||
|
Py_TYPE(self)->tp_free((PyObject*)self);
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* PyAnimation::get_property(PyAnimationObject* self, void* closure) {
|
||||||
|
return PyUnicode_FromString(self->data->getTargetProperty().c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* PyAnimation::get_duration(PyAnimationObject* self, void* closure) {
|
||||||
|
return PyFloat_FromDouble(self->data->getDuration());
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* PyAnimation::get_elapsed(PyAnimationObject* self, void* closure) {
|
||||||
|
return PyFloat_FromDouble(self->data->getElapsed());
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* PyAnimation::get_is_complete(PyAnimationObject* self, void* closure) {
|
||||||
|
return PyBool_FromLong(self->data->isComplete());
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* PyAnimation::get_is_delta(PyAnimationObject* self, void* closure) {
|
||||||
|
return PyBool_FromLong(self->data->isDelta());
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* PyAnimation::start(PyAnimationObject* self, PyObject* args) {
|
||||||
|
PyObject* target_obj;
|
||||||
|
if (!PyArg_ParseTuple(args, "O", &target_obj)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the UIDrawable from the Python object
|
||||||
|
UIDrawable* drawable = nullptr;
|
||||||
|
|
||||||
|
// Check type by comparing type names
|
||||||
|
const char* type_name = Py_TYPE(target_obj)->tp_name;
|
||||||
|
|
||||||
|
if (strcmp(type_name, "mcrfpy.Frame") == 0) {
|
||||||
|
PyUIFrameObject* frame = (PyUIFrameObject*)target_obj;
|
||||||
|
drawable = frame->data.get();
|
||||||
|
}
|
||||||
|
else if (strcmp(type_name, "mcrfpy.Caption") == 0) {
|
||||||
|
PyUICaptionObject* caption = (PyUICaptionObject*)target_obj;
|
||||||
|
drawable = caption->data.get();
|
||||||
|
}
|
||||||
|
else if (strcmp(type_name, "mcrfpy.Sprite") == 0) {
|
||||||
|
PyUISpriteObject* sprite = (PyUISpriteObject*)target_obj;
|
||||||
|
drawable = sprite->data.get();
|
||||||
|
}
|
||||||
|
else if (strcmp(type_name, "mcrfpy.Grid") == 0) {
|
||||||
|
PyUIGridObject* grid = (PyUIGridObject*)target_obj;
|
||||||
|
drawable = grid->data.get();
|
||||||
|
}
|
||||||
|
else if (strcmp(type_name, "mcrfpy.Entity") == 0) {
|
||||||
|
// Special handling for Entity since it doesn't inherit from UIDrawable
|
||||||
|
PyUIEntityObject* entity = (PyUIEntityObject*)target_obj;
|
||||||
|
// Start the animation directly on the entity
|
||||||
|
self->data->startEntity(entity->data.get());
|
||||||
|
|
||||||
|
// Add to AnimationManager
|
||||||
|
AnimationManager::getInstance().addAnimation(self->data);
|
||||||
|
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
PyErr_SetString(PyExc_TypeError, "Target must be a Frame, Caption, Sprite, Grid, or Entity");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the animation
|
||||||
|
self->data->start(drawable);
|
||||||
|
|
||||||
|
// Add to AnimationManager
|
||||||
|
AnimationManager::getInstance().addAnimation(self->data);
|
||||||
|
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* PyAnimation::update(PyAnimationObject* self, PyObject* args) {
|
||||||
|
float deltaTime;
|
||||||
|
if (!PyArg_ParseTuple(args, "f", &deltaTime)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool still_running = self->data->update(deltaTime);
|
||||||
|
return PyBool_FromLong(still_running);
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* PyAnimation::get_current_value(PyAnimationObject* self, PyObject* args) {
|
||||||
|
AnimationValue value = self->data->getCurrentValue();
|
||||||
|
|
||||||
|
// Convert AnimationValue back to Python
|
||||||
|
return std::visit([](const auto& val) -> PyObject* {
|
||||||
|
using T = std::decay_t<decltype(val)>;
|
||||||
|
|
||||||
|
if constexpr (std::is_same_v<T, float>) {
|
||||||
|
return PyFloat_FromDouble(val);
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, int>) {
|
||||||
|
return PyLong_FromLong(val);
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, std::vector<int>>) {
|
||||||
|
// This shouldn't happen as we interpolate to int
|
||||||
|
return PyLong_FromLong(0);
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, sf::Color>) {
|
||||||
|
return Py_BuildValue("(iiii)", val.r, val.g, val.b, val.a);
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, sf::Vector2f>) {
|
||||||
|
return Py_BuildValue("(ff)", val.x, val.y);
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, std::string>) {
|
||||||
|
return PyUnicode_FromString(val.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
PyGetSetDef PyAnimation::getsetters[] = {
|
||||||
|
{"property", (getter)get_property, NULL, "Target property name", NULL},
|
||||||
|
{"duration", (getter)get_duration, NULL, "Animation duration in seconds", NULL},
|
||||||
|
{"elapsed", (getter)get_elapsed, NULL, "Elapsed time in seconds", NULL},
|
||||||
|
{"is_complete", (getter)get_is_complete, NULL, "Whether animation is complete", NULL},
|
||||||
|
{"is_delta", (getter)get_is_delta, NULL, "Whether animation uses delta mode", NULL},
|
||||||
|
{NULL}
|
||||||
|
};
|
||||||
|
|
||||||
|
PyMethodDef PyAnimation::methods[] = {
|
||||||
|
{"start", (PyCFunction)start, METH_VARARGS,
|
||||||
|
"Start the animation on a target UIDrawable"},
|
||||||
|
{"update", (PyCFunction)update, METH_VARARGS,
|
||||||
|
"Update the animation by deltaTime (returns True if still running)"},
|
||||||
|
{"get_current_value", (PyCFunction)get_current_value, METH_NOARGS,
|
||||||
|
"Get the current interpolated value"},
|
||||||
|
{NULL}
|
||||||
|
};
|
|
@ -0,0 +1,50 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Common.h"
|
||||||
|
#include "Python.h"
|
||||||
|
#include "structmember.h"
|
||||||
|
#include "Animation.h"
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
PyObject_HEAD
|
||||||
|
std::shared_ptr<Animation> data;
|
||||||
|
} PyAnimationObject;
|
||||||
|
|
||||||
|
class PyAnimation {
|
||||||
|
public:
|
||||||
|
static PyObject* create(PyTypeObject* type, PyObject* args, PyObject* kwds);
|
||||||
|
static int init(PyAnimationObject* self, PyObject* args, PyObject* kwds);
|
||||||
|
static void dealloc(PyAnimationObject* self);
|
||||||
|
|
||||||
|
// Properties
|
||||||
|
static PyObject* get_property(PyAnimationObject* self, void* closure);
|
||||||
|
static PyObject* get_duration(PyAnimationObject* self, void* closure);
|
||||||
|
static PyObject* get_elapsed(PyAnimationObject* self, void* closure);
|
||||||
|
static PyObject* get_is_complete(PyAnimationObject* self, void* closure);
|
||||||
|
static PyObject* get_is_delta(PyAnimationObject* self, void* closure);
|
||||||
|
|
||||||
|
// Methods
|
||||||
|
static PyObject* start(PyAnimationObject* self, PyObject* args);
|
||||||
|
static PyObject* update(PyAnimationObject* self, PyObject* args);
|
||||||
|
static PyObject* get_current_value(PyAnimationObject* self, PyObject* args);
|
||||||
|
|
||||||
|
static PyGetSetDef getsetters[];
|
||||||
|
static PyMethodDef methods[];
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace mcrfpydef {
|
||||||
|
static PyTypeObject PyAnimationType = {
|
||||||
|
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
|
||||||
|
.tp_name = "mcrfpy.Animation",
|
||||||
|
.tp_basicsize = sizeof(PyAnimationObject),
|
||||||
|
.tp_itemsize = 0,
|
||||||
|
.tp_dealloc = (destructor)PyAnimation::dealloc,
|
||||||
|
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||||
|
.tp_doc = PyDoc_STR("Animation object for animating UI properties"),
|
||||||
|
.tp_methods = PyAnimation::methods,
|
||||||
|
.tp_getset = PyAnimation::getsetters,
|
||||||
|
.tp_init = (initproc)PyAnimation::init,
|
||||||
|
.tp_new = PyAnimation::create,
|
||||||
|
};
|
||||||
|
}
|
|
@ -2,6 +2,7 @@
|
||||||
#include "ActionCode.h"
|
#include "ActionCode.h"
|
||||||
#include "Resources.h"
|
#include "Resources.h"
|
||||||
#include "PyCallable.h"
|
#include "PyCallable.h"
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
PyScene::PyScene(GameEngine* g) : Scene(g)
|
PyScene::PyScene(GameEngine* g) : Scene(g)
|
||||||
{
|
{
|
||||||
|
@ -66,8 +67,17 @@ void PyScene::render()
|
||||||
{
|
{
|
||||||
game->getRenderTarget().clear();
|
game->getRenderTarget().clear();
|
||||||
|
|
||||||
auto vec = *ui_elements;
|
// Only sort if z_index values have changed
|
||||||
for (auto e: vec)
|
if (ui_elements_need_sort) {
|
||||||
|
std::sort(ui_elements->begin(), ui_elements->end(),
|
||||||
|
[](const std::shared_ptr<UIDrawable>& a, const std::shared_ptr<UIDrawable>& b) {
|
||||||
|
return a->z_index < b->z_index;
|
||||||
|
});
|
||||||
|
ui_elements_need_sort = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render in sorted order (no need to copy anymore)
|
||||||
|
for (auto e: *ui_elements)
|
||||||
{
|
{
|
||||||
if (e)
|
if (e)
|
||||||
e->render();
|
e->render();
|
||||||
|
|
|
@ -14,4 +14,7 @@ public:
|
||||||
void render() override final;
|
void render() override final;
|
||||||
|
|
||||||
void do_mouse_input(std::string, std::string);
|
void do_mouse_input(std::string, std::string);
|
||||||
|
|
||||||
|
// Dirty flag for z_index sorting optimization
|
||||||
|
bool ui_elements_need_sort = true;
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include "PyColor.h"
|
#include "PyColor.h"
|
||||||
#include "PyVector.h"
|
#include "PyVector.h"
|
||||||
#include "PyFont.h"
|
#include "PyFont.h"
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
UIDrawable* UICaption::click_at(sf::Vector2f point)
|
UIDrawable* UICaption::click_at(sf::Vector2f point)
|
||||||
{
|
{
|
||||||
|
@ -198,6 +199,7 @@ PyGetSetDef UICaption::getsetters[] = {
|
||||||
{"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},
|
||||||
{"size", (getter)UICaption::get_float_member, (setter)UICaption::set_float_member, "Text size (integer) in points", (void*)5},
|
{"size", (getter)UICaption::get_float_member, (setter)UICaption::set_float_member, "Text 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, "Object called with (x, y, button) when clicked", (void*)PyObjectsEnum::UICAPTION},
|
||||||
|
{"z_index", (getter)UIDrawable::get_int, (setter)UIDrawable::set_int, "Z-order for rendering (lower values rendered first)", (void*)PyObjectsEnum::UICAPTION},
|
||||||
{NULL}
|
{NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -234,7 +236,7 @@ int UICaption::init(PyUICaptionObject* self, PyObject* args, PyObject* kwds)
|
||||||
|
|
||||||
//if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ffzOOOf",
|
//if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ffzOOOf",
|
||||||
// const_cast<char**>(keywords), &x, &y, &text, &font, &fill_color, &outline_color, &outline))
|
// const_cast<char**>(keywords), &x, &y, &text, &font, &fill_color, &outline_color, &outline))
|
||||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|zOOOf",
|
if (!PyArg_ParseTupleAndKeywords(args, kwds, "Oz|OOOf",
|
||||||
const_cast<char**>(keywords), &pos, &text, &font, &fill_color, &outline_color, &outline))
|
const_cast<char**>(keywords), &pos, &text, &font, &fill_color, &outline_color, &outline))
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -250,10 +252,10 @@ int UICaption::init(PyUICaptionObject* self, PyObject* args, PyObject* kwds)
|
||||||
// check types for font, fill_color, outline_color
|
// check types for font, fill_color, outline_color
|
||||||
|
|
||||||
//std::cout << PyUnicode_AsUTF8(PyObject_Repr(font)) << std::endl;
|
//std::cout << PyUnicode_AsUTF8(PyObject_Repr(font)) << std::endl;
|
||||||
if (font != NULL && !PyObject_IsInstance(font, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Font")/*(PyObject*)&PyFontType)*/)){
|
if (font != NULL && font != Py_None && !PyObject_IsInstance(font, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Font")/*(PyObject*)&PyFontType)*/)){
|
||||||
PyErr_SetString(PyExc_TypeError, "font must be a mcrfpy.Font instance");
|
PyErr_SetString(PyExc_TypeError, "font must be a mcrfpy.Font instance or None");
|
||||||
return -1;
|
return -1;
|
||||||
} else if (font != NULL)
|
} else if (font != NULL && font != Py_None)
|
||||||
{
|
{
|
||||||
auto font_obj = (PyFontObject*)font;
|
auto font_obj = (PyFontObject*)font;
|
||||||
self->data->text.setFont(font_obj->data->font);
|
self->data->text.setFont(font_obj->data->font);
|
||||||
|
@ -261,8 +263,16 @@ int UICaption::init(PyUICaptionObject* self, PyObject* args, PyObject* kwds)
|
||||||
Py_INCREF(font);
|
Py_INCREF(font);
|
||||||
} else
|
} else
|
||||||
{
|
{
|
||||||
// default font
|
// Use default font when None or not provided
|
||||||
//self->data->text.setFont(Resources::game->getFont());
|
if (McRFPy_API::default_font) {
|
||||||
|
self->data->text.setFont(McRFPy_API::default_font->font);
|
||||||
|
// Store reference to default font
|
||||||
|
PyObject* default_font_obj = PyObject_GetAttrString(McRFPy_API::mcrf_module, "default_font");
|
||||||
|
if (default_font_obj) {
|
||||||
|
self->font = default_font_obj;
|
||||||
|
// Don't need to DECREF since we're storing it
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self->data->text.setString((std::string)text);
|
self->data->text.setString((std::string)text);
|
||||||
|
@ -294,3 +304,172 @@ int UICaption::init(PyUICaptionObject* self, PyObject* args, PyObject* kwds)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Property system implementation for animations
|
||||||
|
bool UICaption::setProperty(const std::string& name, float value) {
|
||||||
|
if (name == "x") {
|
||||||
|
text.setPosition(sf::Vector2f(value, text.getPosition().y));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "y") {
|
||||||
|
text.setPosition(sf::Vector2f(text.getPosition().x, value));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "size") {
|
||||||
|
text.setCharacterSize(static_cast<unsigned int>(value));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "outline") {
|
||||||
|
text.setOutlineThickness(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "fill_color.r") {
|
||||||
|
auto color = text.getFillColor();
|
||||||
|
color.r = static_cast<sf::Uint8>(std::clamp(value, 0.0f, 255.0f));
|
||||||
|
text.setFillColor(color);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "fill_color.g") {
|
||||||
|
auto color = text.getFillColor();
|
||||||
|
color.g = static_cast<sf::Uint8>(std::clamp(value, 0.0f, 255.0f));
|
||||||
|
text.setFillColor(color);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "fill_color.b") {
|
||||||
|
auto color = text.getFillColor();
|
||||||
|
color.b = static_cast<sf::Uint8>(std::clamp(value, 0.0f, 255.0f));
|
||||||
|
text.setFillColor(color);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "fill_color.a") {
|
||||||
|
auto color = text.getFillColor();
|
||||||
|
color.a = static_cast<sf::Uint8>(std::clamp(value, 0.0f, 255.0f));
|
||||||
|
text.setFillColor(color);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "outline_color.r") {
|
||||||
|
auto color = text.getOutlineColor();
|
||||||
|
color.r = static_cast<sf::Uint8>(std::clamp(value, 0.0f, 255.0f));
|
||||||
|
text.setOutlineColor(color);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "outline_color.g") {
|
||||||
|
auto color = text.getOutlineColor();
|
||||||
|
color.g = static_cast<sf::Uint8>(std::clamp(value, 0.0f, 255.0f));
|
||||||
|
text.setOutlineColor(color);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "outline_color.b") {
|
||||||
|
auto color = text.getOutlineColor();
|
||||||
|
color.b = static_cast<sf::Uint8>(std::clamp(value, 0.0f, 255.0f));
|
||||||
|
text.setOutlineColor(color);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "outline_color.a") {
|
||||||
|
auto color = text.getOutlineColor();
|
||||||
|
color.a = static_cast<sf::Uint8>(std::clamp(value, 0.0f, 255.0f));
|
||||||
|
text.setOutlineColor(color);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "z_index") {
|
||||||
|
z_index = static_cast<int>(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UICaption::setProperty(const std::string& name, const sf::Color& value) {
|
||||||
|
if (name == "fill_color") {
|
||||||
|
text.setFillColor(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "outline_color") {
|
||||||
|
text.setOutlineColor(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UICaption::setProperty(const std::string& name, const std::string& value) {
|
||||||
|
if (name == "text") {
|
||||||
|
text.setString(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UICaption::getProperty(const std::string& name, float& value) const {
|
||||||
|
if (name == "x") {
|
||||||
|
value = text.getPosition().x;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "y") {
|
||||||
|
value = text.getPosition().y;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "size") {
|
||||||
|
value = static_cast<float>(text.getCharacterSize());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "outline") {
|
||||||
|
value = text.getOutlineThickness();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "fill_color.r") {
|
||||||
|
value = text.getFillColor().r;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "fill_color.g") {
|
||||||
|
value = text.getFillColor().g;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "fill_color.b") {
|
||||||
|
value = text.getFillColor().b;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "fill_color.a") {
|
||||||
|
value = text.getFillColor().a;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "outline_color.r") {
|
||||||
|
value = text.getOutlineColor().r;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "outline_color.g") {
|
||||||
|
value = text.getOutlineColor().g;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "outline_color.b") {
|
||||||
|
value = text.getOutlineColor().b;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "outline_color.a") {
|
||||||
|
value = text.getOutlineColor().a;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "z_index") {
|
||||||
|
value = static_cast<float>(z_index);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UICaption::getProperty(const std::string& name, sf::Color& value) const {
|
||||||
|
if (name == "fill_color") {
|
||||||
|
value = text.getFillColor();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "outline_color") {
|
||||||
|
value = text.getOutlineColor();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UICaption::getProperty(const std::string& name, std::string& value) const {
|
||||||
|
if (name == "text") {
|
||||||
|
value = text.getString();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,15 @@ public:
|
||||||
void render(sf::Vector2f, sf::RenderTarget&) override final;
|
void render(sf::Vector2f, sf::RenderTarget&) override final;
|
||||||
PyObjectsEnum derived_type() override final;
|
PyObjectsEnum derived_type() override final;
|
||||||
virtual UIDrawable* click_at(sf::Vector2f point) override final;
|
virtual UIDrawable* click_at(sf::Vector2f point) override final;
|
||||||
|
|
||||||
|
// Property system for animations
|
||||||
|
bool setProperty(const std::string& name, float value) override;
|
||||||
|
bool setProperty(const std::string& name, const sf::Color& value) override;
|
||||||
|
bool setProperty(const std::string& name, const std::string& value) override;
|
||||||
|
|
||||||
|
bool getProperty(const std::string& name, float& value) const override;
|
||||||
|
bool getProperty(const std::string& name, sf::Color& value) const override;
|
||||||
|
bool getProperty(const std::string& name, std::string& value) const override;
|
||||||
|
|
||||||
static PyObject* get_float_member(PyUICaptionObject* self, void* closure);
|
static PyObject* get_float_member(PyUICaptionObject* self, void* closure);
|
||||||
static int set_float_member(PyUICaptionObject* self, PyObject* value, void* closure);
|
static int set_float_member(PyUICaptionObject* self, PyObject* value, void* closure);
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
#include "UIGrid.h"
|
#include "UIGrid.h"
|
||||||
#include "McRFPy_API.h"
|
#include "McRFPy_API.h"
|
||||||
#include "PyObjectUtils.h"
|
#include "PyObjectUtils.h"
|
||||||
|
#include <climits>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
using namespace mcrfpydef;
|
using namespace mcrfpydef;
|
||||||
|
|
||||||
|
@ -148,15 +150,394 @@ PyObject* UICollection::getitem(PyUICollectionObject* self, Py_ssize_t index) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int UICollection::setitem(PyUICollectionObject* self, Py_ssize_t index, PyObject* value) {
|
||||||
|
auto vec = self->data.get();
|
||||||
|
if (!vec) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle negative indexing
|
||||||
|
while (index < 0) index += self->data->size();
|
||||||
|
|
||||||
|
// Bounds check
|
||||||
|
if (index >= self->data->size()) {
|
||||||
|
PyErr_SetString(PyExc_IndexError, "UICollection assignment index out of range");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle deletion
|
||||||
|
if (value == NULL) {
|
||||||
|
self->data->erase(self->data->begin() + index);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type checking - must be a UIDrawable subclass
|
||||||
|
if (!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame")) &&
|
||||||
|
!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite")) &&
|
||||||
|
!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption")) &&
|
||||||
|
!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) {
|
||||||
|
PyErr_SetString(PyExc_TypeError, "UICollection can only contain Frame, Caption, Sprite, and Grid objects");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the C++ object from the Python object
|
||||||
|
std::shared_ptr<UIDrawable> new_drawable = nullptr;
|
||||||
|
int old_z_index = (*vec)[index]->z_index; // Preserve the z_index
|
||||||
|
|
||||||
|
if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame"))) {
|
||||||
|
PyUIFrameObject* frame = (PyUIFrameObject*)value;
|
||||||
|
new_drawable = frame->data;
|
||||||
|
} else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption"))) {
|
||||||
|
PyUICaptionObject* caption = (PyUICaptionObject*)value;
|
||||||
|
new_drawable = caption->data;
|
||||||
|
} else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite"))) {
|
||||||
|
PyUISpriteObject* sprite = (PyUISpriteObject*)value;
|
||||||
|
new_drawable = sprite->data;
|
||||||
|
} else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) {
|
||||||
|
PyUIGridObject* grid = (PyUIGridObject*)value;
|
||||||
|
new_drawable = grid->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!new_drawable) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, "Failed to extract C++ object from Python object");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preserve the z_index of the replaced element
|
||||||
|
new_drawable->z_index = old_z_index;
|
||||||
|
|
||||||
|
// Replace the element
|
||||||
|
(*vec)[index] = new_drawable;
|
||||||
|
|
||||||
|
// Mark scene as needing resort after replacing element
|
||||||
|
McRFPy_API::markSceneNeedsSort();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int UICollection::contains(PyUICollectionObject* self, PyObject* value) {
|
||||||
|
auto vec = self->data.get();
|
||||||
|
if (!vec) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type checking - must be a UIDrawable subclass
|
||||||
|
if (!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame")) &&
|
||||||
|
!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite")) &&
|
||||||
|
!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption")) &&
|
||||||
|
!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) {
|
||||||
|
// Not a valid type, so it can't be in the collection
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the C++ object from the Python object
|
||||||
|
std::shared_ptr<UIDrawable> search_drawable = nullptr;
|
||||||
|
|
||||||
|
if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame"))) {
|
||||||
|
PyUIFrameObject* frame = (PyUIFrameObject*)value;
|
||||||
|
search_drawable = frame->data;
|
||||||
|
} else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption"))) {
|
||||||
|
PyUICaptionObject* caption = (PyUICaptionObject*)value;
|
||||||
|
search_drawable = caption->data;
|
||||||
|
} else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite"))) {
|
||||||
|
PyUISpriteObject* sprite = (PyUISpriteObject*)value;
|
||||||
|
search_drawable = sprite->data;
|
||||||
|
} else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) {
|
||||||
|
PyUIGridObject* grid = (PyUIGridObject*)value;
|
||||||
|
search_drawable = grid->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!search_drawable) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search for the object by comparing C++ pointers
|
||||||
|
for (const auto& drawable : *vec) {
|
||||||
|
if (drawable.get() == search_drawable.get()) {
|
||||||
|
return 1; // Found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0; // Not found
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* UICollection::concat(PyUICollectionObject* self, PyObject* other) {
|
||||||
|
// Create a new Python list containing elements from both collections
|
||||||
|
if (!PySequence_Check(other)) {
|
||||||
|
PyErr_SetString(PyExc_TypeError, "can only concatenate sequence to UICollection");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_ssize_t self_len = self->data->size();
|
||||||
|
Py_ssize_t other_len = PySequence_Length(other);
|
||||||
|
if (other_len == -1) {
|
||||||
|
return NULL; // Error already set
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* result_list = PyList_New(self_len + other_len);
|
||||||
|
if (!result_list) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all elements from self
|
||||||
|
for (Py_ssize_t i = 0; i < self_len; i++) {
|
||||||
|
PyObject* item = convertDrawableToPython((*self->data)[i]);
|
||||||
|
if (!item) {
|
||||||
|
Py_DECREF(result_list);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
PyList_SET_ITEM(result_list, i, item); // Steals reference
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all elements from other
|
||||||
|
for (Py_ssize_t i = 0; i < other_len; i++) {
|
||||||
|
PyObject* item = PySequence_GetItem(other, i);
|
||||||
|
if (!item) {
|
||||||
|
Py_DECREF(result_list);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
PyList_SET_ITEM(result_list, self_len + i, item); // Steals reference
|
||||||
|
}
|
||||||
|
|
||||||
|
return result_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* UICollection::inplace_concat(PyUICollectionObject* self, PyObject* other) {
|
||||||
|
if (!PySequence_Check(other)) {
|
||||||
|
PyErr_SetString(PyExc_TypeError, "can only concatenate sequence to UICollection");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// First, validate ALL items in the sequence before modifying anything
|
||||||
|
Py_ssize_t other_len = PySequence_Length(other);
|
||||||
|
if (other_len == -1) {
|
||||||
|
return NULL; // Error already set
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate all items first
|
||||||
|
for (Py_ssize_t i = 0; i < other_len; i++) {
|
||||||
|
PyObject* item = PySequence_GetItem(other, i);
|
||||||
|
if (!item) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type check
|
||||||
|
if (!PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame")) &&
|
||||||
|
!PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite")) &&
|
||||||
|
!PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption")) &&
|
||||||
|
!PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) {
|
||||||
|
Py_DECREF(item);
|
||||||
|
PyErr_Format(PyExc_TypeError,
|
||||||
|
"UICollection can only contain Frame, Caption, Sprite, and Grid objects; "
|
||||||
|
"got %s at index %zd", Py_TYPE(item)->tp_name, i);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
Py_DECREF(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
// All items validated, now we can safely add them
|
||||||
|
for (Py_ssize_t i = 0; i < other_len; i++) {
|
||||||
|
PyObject* item = PySequence_GetItem(other, i);
|
||||||
|
if (!item) {
|
||||||
|
return NULL; // Shouldn't happen, but be safe
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the existing append method which handles z_index assignment
|
||||||
|
PyObject* result = append(self, item);
|
||||||
|
Py_DECREF(item);
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return NULL; // append() failed
|
||||||
|
}
|
||||||
|
Py_DECREF(result); // append returns Py_None
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_INCREF(self);
|
||||||
|
return (PyObject*)self;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* UICollection::subscript(PyUICollectionObject* self, PyObject* key) {
|
||||||
|
if (PyLong_Check(key)) {
|
||||||
|
// Single index - delegate to sq_item
|
||||||
|
Py_ssize_t index = PyLong_AsSsize_t(key);
|
||||||
|
if (index == -1 && PyErr_Occurred()) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return getitem(self, index);
|
||||||
|
} else if (PySlice_Check(key)) {
|
||||||
|
// Handle slice
|
||||||
|
Py_ssize_t start, stop, step, slicelength;
|
||||||
|
|
||||||
|
if (PySlice_GetIndicesEx(key, self->data->size(), &start, &stop, &step, &slicelength) < 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* result_list = PyList_New(slicelength);
|
||||||
|
if (!result_list) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Py_ssize_t i = 0, cur = start; i < slicelength; i++, cur += step) {
|
||||||
|
PyObject* item = convertDrawableToPython((*self->data)[cur]);
|
||||||
|
if (!item) {
|
||||||
|
Py_DECREF(result_list);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
PyList_SET_ITEM(result_list, i, item); // Steals reference
|
||||||
|
}
|
||||||
|
|
||||||
|
return result_list;
|
||||||
|
} else {
|
||||||
|
PyErr_Format(PyExc_TypeError, "UICollection indices must be integers or slices, not %.200s",
|
||||||
|
Py_TYPE(key)->tp_name);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int UICollection::ass_subscript(PyUICollectionObject* self, PyObject* key, PyObject* value) {
|
||||||
|
if (PyLong_Check(key)) {
|
||||||
|
// Single index - delegate to sq_ass_item
|
||||||
|
Py_ssize_t index = PyLong_AsSsize_t(key);
|
||||||
|
if (index == -1 && PyErr_Occurred()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return setitem(self, index, value);
|
||||||
|
} else if (PySlice_Check(key)) {
|
||||||
|
// Handle slice assignment/deletion
|
||||||
|
Py_ssize_t start, stop, step, slicelength;
|
||||||
|
|
||||||
|
if (PySlice_GetIndicesEx(key, self->data->size(), &start, &stop, &step, &slicelength) < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value == NULL) {
|
||||||
|
// Deletion
|
||||||
|
if (step != 1) {
|
||||||
|
// For non-contiguous slices, delete from highest to lowest to maintain indices
|
||||||
|
std::vector<Py_ssize_t> indices;
|
||||||
|
for (Py_ssize_t i = 0, cur = start; i < slicelength; i++, cur += step) {
|
||||||
|
indices.push_back(cur);
|
||||||
|
}
|
||||||
|
// Sort in descending order and delete
|
||||||
|
std::sort(indices.begin(), indices.end(), std::greater<Py_ssize_t>());
|
||||||
|
for (Py_ssize_t idx : indices) {
|
||||||
|
self->data->erase(self->data->begin() + idx);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Contiguous slice - can delete in one go
|
||||||
|
self->data->erase(self->data->begin() + start, self->data->begin() + stop);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark scene as needing resort after slice deletion
|
||||||
|
McRFPy_API::markSceneNeedsSort();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
// Assignment
|
||||||
|
if (!PySequence_Check(value)) {
|
||||||
|
PyErr_SetString(PyExc_TypeError, "can only assign sequence to slice");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_ssize_t value_len = PySequence_Length(value);
|
||||||
|
if (value_len == -1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate all items first
|
||||||
|
std::vector<std::shared_ptr<UIDrawable>> new_items;
|
||||||
|
for (Py_ssize_t i = 0; i < value_len; i++) {
|
||||||
|
PyObject* item = PySequence_GetItem(value, i);
|
||||||
|
if (!item) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type check and extract C++ object
|
||||||
|
std::shared_ptr<UIDrawable> drawable = nullptr;
|
||||||
|
|
||||||
|
if (PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame"))) {
|
||||||
|
drawable = ((PyUIFrameObject*)item)->data;
|
||||||
|
} else if (PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption"))) {
|
||||||
|
drawable = ((PyUICaptionObject*)item)->data;
|
||||||
|
} else if (PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite"))) {
|
||||||
|
drawable = ((PyUISpriteObject*)item)->data;
|
||||||
|
} else if (PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) {
|
||||||
|
drawable = ((PyUIGridObject*)item)->data;
|
||||||
|
} else {
|
||||||
|
Py_DECREF(item);
|
||||||
|
PyErr_Format(PyExc_TypeError,
|
||||||
|
"UICollection can only contain Frame, Caption, Sprite, and Grid objects; "
|
||||||
|
"got %s at index %zd", Py_TYPE(item)->tp_name, i);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_DECREF(item);
|
||||||
|
new_items.push_back(drawable);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now perform the assignment
|
||||||
|
if (step == 1) {
|
||||||
|
// Contiguous slice
|
||||||
|
if (slicelength != value_len) {
|
||||||
|
// Need to resize
|
||||||
|
auto it_start = self->data->begin() + start;
|
||||||
|
auto it_stop = self->data->begin() + stop;
|
||||||
|
self->data->erase(it_start, it_stop);
|
||||||
|
self->data->insert(self->data->begin() + start, new_items.begin(), new_items.end());
|
||||||
|
} else {
|
||||||
|
// Same size, just replace
|
||||||
|
for (Py_ssize_t i = 0; i < slicelength; i++) {
|
||||||
|
// Preserve z_index
|
||||||
|
new_items[i]->z_index = (*self->data)[start + i]->z_index;
|
||||||
|
(*self->data)[start + i] = new_items[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Extended slice
|
||||||
|
if (slicelength != value_len) {
|
||||||
|
PyErr_Format(PyExc_ValueError,
|
||||||
|
"attempt to assign sequence of size %zd to extended slice of size %zd",
|
||||||
|
value_len, slicelength);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
for (Py_ssize_t i = 0, cur = start; i < slicelength; i++, cur += step) {
|
||||||
|
// Preserve z_index
|
||||||
|
new_items[i]->z_index = (*self->data)[cur]->z_index;
|
||||||
|
(*self->data)[cur] = new_items[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark scene as needing resort after slice assignment
|
||||||
|
McRFPy_API::markSceneNeedsSort();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
PyErr_Format(PyExc_TypeError, "UICollection indices must be integers or slices, not %.200s",
|
||||||
|
Py_TYPE(key)->tp_name);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PyMappingMethods UICollection::mpmethods = {
|
||||||
|
.mp_length = (lenfunc)UICollection::len,
|
||||||
|
.mp_subscript = (binaryfunc)UICollection::subscript,
|
||||||
|
.mp_ass_subscript = (objobjargproc)UICollection::ass_subscript
|
||||||
|
};
|
||||||
|
|
||||||
PySequenceMethods UICollection::sqmethods = {
|
PySequenceMethods UICollection::sqmethods = {
|
||||||
.sq_length = (lenfunc)UICollection::len,
|
.sq_length = (lenfunc)UICollection::len,
|
||||||
|
.sq_concat = (binaryfunc)UICollection::concat,
|
||||||
|
.sq_repeat = NULL,
|
||||||
.sq_item = (ssizeargfunc)UICollection::getitem,
|
.sq_item = (ssizeargfunc)UICollection::getitem,
|
||||||
//.sq_item_by_index = PyUICollection_getitem
|
.was_sq_slice = NULL,
|
||||||
//.sq_slice - return a subset of the iterable
|
.sq_ass_item = (ssizeobjargproc)UICollection::setitem,
|
||||||
//.sq_ass_item - called when `o[x] = y` is executed (x is any object type)
|
.was_sq_ass_slice = NULL,
|
||||||
//.sq_ass_slice - cool; no thanks, for now
|
.sq_contains = (objobjproc)UICollection::contains,
|
||||||
//.sq_contains - called when `x in o` is executed
|
.sq_inplace_concat = (binaryfunc)UICollection::inplace_concat,
|
||||||
//.sq_ass_item_by_index - called when `o[x] = y` is executed (x is explictly an integer)
|
.sq_inplace_repeat = NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Idiomatic way to fetch complete types from the API rather than referencing their PyTypeObject struct
|
/* Idiomatic way to fetch complete types from the API rather than referencing their PyTypeObject struct
|
||||||
|
@ -173,6 +554,12 @@ PyObject* UICollection::append(PyUICollectionObject* self, PyObject* o)
|
||||||
// if not UIDrawable subclass, reject it
|
// if not UIDrawable subclass, reject it
|
||||||
// self->data->push_back( c++ object inside o );
|
// self->data->push_back( c++ object inside o );
|
||||||
|
|
||||||
|
// Ensure module is initialized
|
||||||
|
if (!McRFPy_API::mcrf_module) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, "mcrfpy module not initialized");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
// this would be a great use case for .tp_base
|
// this would be a great use case for .tp_base
|
||||||
if (!PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame")) &&
|
if (!PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame")) &&
|
||||||
!PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite")) &&
|
!PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite")) &&
|
||||||
|
@ -184,26 +571,45 @@ PyObject* UICollection::append(PyUICollectionObject* self, PyObject* o)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calculate z_index for the new element
|
||||||
|
int new_z_index = 0;
|
||||||
|
if (!self->data->empty()) {
|
||||||
|
// Get the z_index of the last element and add 10
|
||||||
|
int last_z = self->data->back()->z_index;
|
||||||
|
if (last_z <= INT_MAX - 10) {
|
||||||
|
new_z_index = last_z + 10;
|
||||||
|
} else {
|
||||||
|
new_z_index = INT_MAX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame")))
|
if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame")))
|
||||||
{
|
{
|
||||||
PyUIFrameObject* frame = (PyUIFrameObject*)o;
|
PyUIFrameObject* frame = (PyUIFrameObject*)o;
|
||||||
|
frame->data->z_index = new_z_index;
|
||||||
self->data->push_back(frame->data);
|
self->data->push_back(frame->data);
|
||||||
}
|
}
|
||||||
if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption")))
|
if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption")))
|
||||||
{
|
{
|
||||||
PyUICaptionObject* caption = (PyUICaptionObject*)o;
|
PyUICaptionObject* caption = (PyUICaptionObject*)o;
|
||||||
|
caption->data->z_index = new_z_index;
|
||||||
self->data->push_back(caption->data);
|
self->data->push_back(caption->data);
|
||||||
}
|
}
|
||||||
if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite")))
|
if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite")))
|
||||||
{
|
{
|
||||||
PyUISpriteObject* sprite = (PyUISpriteObject*)o;
|
PyUISpriteObject* sprite = (PyUISpriteObject*)o;
|
||||||
|
sprite->data->z_index = new_z_index;
|
||||||
self->data->push_back(sprite->data);
|
self->data->push_back(sprite->data);
|
||||||
}
|
}
|
||||||
if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid")))
|
if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid")))
|
||||||
{
|
{
|
||||||
PyUIGridObject* grid = (PyUIGridObject*)o;
|
PyUIGridObject* grid = (PyUIGridObject*)o;
|
||||||
|
grid->data->z_index = new_z_index;
|
||||||
self->data->push_back(grid->data);
|
self->data->push_back(grid->data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mark scene as needing resort after adding element
|
||||||
|
McRFPy_API::markSceneNeedsSort();
|
||||||
|
|
||||||
Py_INCREF(Py_None);
|
Py_INCREF(Py_None);
|
||||||
return Py_None;
|
return Py_None;
|
||||||
|
@ -217,27 +623,121 @@ PyObject* UICollection::remove(PyUICollectionObject* self, PyObject* o)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
long index = PyLong_AsLong(o);
|
long index = PyLong_AsLong(o);
|
||||||
|
|
||||||
|
// Handle negative indexing
|
||||||
|
while (index < 0) index += self->data->size();
|
||||||
|
|
||||||
if (index >= self->data->size())
|
if (index >= self->data->size())
|
||||||
{
|
{
|
||||||
PyErr_SetString(PyExc_ValueError, "Index out of range");
|
PyErr_SetString(PyExc_ValueError, "Index out of range");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
else if (index < 0)
|
|
||||||
{
|
|
||||||
PyErr_SetString(PyExc_NotImplementedError, "reverse indexing is not implemented.");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// release the shared pointer at self->data[index];
|
// release the shared pointer at self->data[index];
|
||||||
self->data->erase(self->data->begin() + index);
|
self->data->erase(self->data->begin() + index);
|
||||||
|
|
||||||
|
// Mark scene as needing resort after removing element
|
||||||
|
McRFPy_API::markSceneNeedsSort();
|
||||||
|
|
||||||
Py_INCREF(Py_None);
|
Py_INCREF(Py_None);
|
||||||
return Py_None;
|
return Py_None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyObject* UICollection::index_method(PyUICollectionObject* self, PyObject* value) {
|
||||||
|
auto vec = self->data.get();
|
||||||
|
if (!vec) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type checking - must be a UIDrawable subclass
|
||||||
|
if (!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame")) &&
|
||||||
|
!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite")) &&
|
||||||
|
!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption")) &&
|
||||||
|
!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) {
|
||||||
|
PyErr_SetString(PyExc_TypeError, "UICollection.index requires a Frame, Caption, Sprite, or Grid object");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the C++ object from the Python object
|
||||||
|
std::shared_ptr<UIDrawable> search_drawable = nullptr;
|
||||||
|
|
||||||
|
if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame"))) {
|
||||||
|
search_drawable = ((PyUIFrameObject*)value)->data;
|
||||||
|
} else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption"))) {
|
||||||
|
search_drawable = ((PyUICaptionObject*)value)->data;
|
||||||
|
} else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite"))) {
|
||||||
|
search_drawable = ((PyUISpriteObject*)value)->data;
|
||||||
|
} else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) {
|
||||||
|
search_drawable = ((PyUIGridObject*)value)->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!search_drawable) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, "Failed to extract C++ object from Python object");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search for the object
|
||||||
|
for (size_t i = 0; i < vec->size(); i++) {
|
||||||
|
if ((*vec)[i].get() == search_drawable.get()) {
|
||||||
|
return PyLong_FromSsize_t(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PyErr_SetString(PyExc_ValueError, "value not in UICollection");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* UICollection::count(PyUICollectionObject* self, PyObject* value) {
|
||||||
|
auto vec = self->data.get();
|
||||||
|
if (!vec) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type checking - must be a UIDrawable subclass
|
||||||
|
if (!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame")) &&
|
||||||
|
!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite")) &&
|
||||||
|
!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption")) &&
|
||||||
|
!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) {
|
||||||
|
// Not a valid type, so count is 0
|
||||||
|
return PyLong_FromLong(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the C++ object from the Python object
|
||||||
|
std::shared_ptr<UIDrawable> search_drawable = nullptr;
|
||||||
|
|
||||||
|
if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame"))) {
|
||||||
|
search_drawable = ((PyUIFrameObject*)value)->data;
|
||||||
|
} else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption"))) {
|
||||||
|
search_drawable = ((PyUICaptionObject*)value)->data;
|
||||||
|
} else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite"))) {
|
||||||
|
search_drawable = ((PyUISpriteObject*)value)->data;
|
||||||
|
} else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) {
|
||||||
|
search_drawable = ((PyUIGridObject*)value)->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!search_drawable) {
|
||||||
|
return PyLong_FromLong(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count occurrences
|
||||||
|
Py_ssize_t count = 0;
|
||||||
|
for (const auto& drawable : *vec) {
|
||||||
|
if (drawable.get() == search_drawable.get()) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return PyLong_FromSsize_t(count);
|
||||||
|
}
|
||||||
|
|
||||||
PyMethodDef UICollection::methods[] = {
|
PyMethodDef UICollection::methods[] = {
|
||||||
{"append", (PyCFunction)UICollection::append, METH_O},
|
{"append", (PyCFunction)UICollection::append, METH_O},
|
||||||
//{"extend", (PyCFunction)PyUICollection_extend, METH_O}, // TODO
|
//{"extend", (PyCFunction)PyUICollection_extend, METH_O}, // TODO
|
||||||
{"remove", (PyCFunction)UICollection::remove, METH_O},
|
{"remove", (PyCFunction)UICollection::remove, METH_O},
|
||||||
|
{"index", (PyCFunction)UICollection::index_method, METH_O},
|
||||||
|
{"count", (PyCFunction)UICollection::count, METH_O},
|
||||||
{NULL, NULL, 0, NULL}
|
{NULL, NULL, 0, NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -19,9 +19,18 @@ class UICollection
|
||||||
public:
|
public:
|
||||||
static Py_ssize_t len(PyUICollectionObject* self);
|
static Py_ssize_t len(PyUICollectionObject* self);
|
||||||
static PyObject* getitem(PyUICollectionObject* self, Py_ssize_t index);
|
static PyObject* getitem(PyUICollectionObject* self, Py_ssize_t index);
|
||||||
|
static int setitem(PyUICollectionObject* self, Py_ssize_t index, PyObject* value);
|
||||||
|
static int contains(PyUICollectionObject* self, PyObject* value);
|
||||||
|
static PyObject* concat(PyUICollectionObject* self, PyObject* other);
|
||||||
|
static PyObject* inplace_concat(PyUICollectionObject* self, PyObject* other);
|
||||||
static PySequenceMethods sqmethods;
|
static PySequenceMethods sqmethods;
|
||||||
|
static PyMappingMethods mpmethods;
|
||||||
|
static PyObject* subscript(PyUICollectionObject* self, PyObject* key);
|
||||||
|
static int ass_subscript(PyUICollectionObject* self, PyObject* key, PyObject* value);
|
||||||
static PyObject* append(PyUICollectionObject* self, PyObject* o);
|
static PyObject* append(PyUICollectionObject* self, PyObject* o);
|
||||||
static PyObject* remove(PyUICollectionObject* self, PyObject* o);
|
static PyObject* remove(PyUICollectionObject* self, PyObject* o);
|
||||||
|
static PyObject* index_method(PyUICollectionObject* self, PyObject* value);
|
||||||
|
static PyObject* count(PyUICollectionObject* self, PyObject* value);
|
||||||
static PyMethodDef methods[];
|
static PyMethodDef methods[];
|
||||||
static PyObject* repr(PyUICollectionObject* self);
|
static PyObject* repr(PyUICollectionObject* self);
|
||||||
static int init(PyUICollectionObject* self, PyObject* args, PyObject* kwds);
|
static int init(PyUICollectionObject* self, PyObject* args, PyObject* kwds);
|
||||||
|
@ -71,6 +80,7 @@ namespace mcrfpydef {
|
||||||
},
|
},
|
||||||
.tp_repr = (reprfunc)UICollection::repr,
|
.tp_repr = (reprfunc)UICollection::repr,
|
||||||
.tp_as_sequence = &UICollection::sqmethods,
|
.tp_as_sequence = &UICollection::sqmethods,
|
||||||
|
.tp_as_mapping = &UICollection::mpmethods,
|
||||||
.tp_flags = Py_TPFLAGS_DEFAULT,
|
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||||
.tp_doc = PyDoc_STR("Iterable, indexable collection of UI objects"),
|
.tp_doc = PyDoc_STR("Iterable, indexable collection of UI objects"),
|
||||||
.tp_iter = (getiterfunc)UICollection::iter,
|
.tp_iter = (getiterfunc)UICollection::iter,
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#include "UISprite.h"
|
#include "UISprite.h"
|
||||||
#include "UIGrid.h"
|
#include "UIGrid.h"
|
||||||
#include "GameEngine.h"
|
#include "GameEngine.h"
|
||||||
|
#include "McRFPy_API.h"
|
||||||
|
|
||||||
UIDrawable::UIDrawable() { click_callable = NULL; }
|
UIDrawable::UIDrawable() { click_callable = NULL; }
|
||||||
|
|
||||||
|
@ -80,3 +81,85 @@ void UIDrawable::click_register(PyObject* callable)
|
||||||
{
|
{
|
||||||
click_callable = std::make_unique<PyClickCallable>(callable);
|
click_callable = std::make_unique<PyClickCallable>(callable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyObject* UIDrawable::get_int(PyObject* self, void* closure) {
|
||||||
|
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<long>(closure));
|
||||||
|
UIDrawable* drawable = nullptr;
|
||||||
|
|
||||||
|
switch (objtype) {
|
||||||
|
case PyObjectsEnum::UIFRAME:
|
||||||
|
drawable = ((PyUIFrameObject*)self)->data.get();
|
||||||
|
break;
|
||||||
|
case PyObjectsEnum::UICAPTION:
|
||||||
|
drawable = ((PyUICaptionObject*)self)->data.get();
|
||||||
|
break;
|
||||||
|
case PyObjectsEnum::UISPRITE:
|
||||||
|
drawable = ((PyUISpriteObject*)self)->data.get();
|
||||||
|
break;
|
||||||
|
case PyObjectsEnum::UIGRID:
|
||||||
|
drawable = ((PyUIGridObject*)self)->data.get();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
PyErr_SetString(PyExc_TypeError, "Invalid UIDrawable derived instance");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PyLong_FromLong(drawable->z_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
int UIDrawable::set_int(PyObject* self, PyObject* value, void* closure) {
|
||||||
|
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<long>(closure));
|
||||||
|
UIDrawable* drawable = nullptr;
|
||||||
|
|
||||||
|
switch (objtype) {
|
||||||
|
case PyObjectsEnum::UIFRAME:
|
||||||
|
drawable = ((PyUIFrameObject*)self)->data.get();
|
||||||
|
break;
|
||||||
|
case PyObjectsEnum::UICAPTION:
|
||||||
|
drawable = ((PyUICaptionObject*)self)->data.get();
|
||||||
|
break;
|
||||||
|
case PyObjectsEnum::UISPRITE:
|
||||||
|
drawable = ((PyUISpriteObject*)self)->data.get();
|
||||||
|
break;
|
||||||
|
case PyObjectsEnum::UIGRID:
|
||||||
|
drawable = ((PyUIGridObject*)self)->data.get();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
PyErr_SetString(PyExc_TypeError, "Invalid UIDrawable derived instance");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!PyLong_Check(value)) {
|
||||||
|
PyErr_SetString(PyExc_TypeError, "z_index must be an integer");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
long z = PyLong_AsLong(value);
|
||||||
|
if (z == -1 && PyErr_Occurred()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clamp to int range
|
||||||
|
if (z < INT_MIN) z = INT_MIN;
|
||||||
|
if (z > INT_MAX) z = INT_MAX;
|
||||||
|
|
||||||
|
int old_z_index = drawable->z_index;
|
||||||
|
drawable->z_index = static_cast<int>(z);
|
||||||
|
|
||||||
|
// Notify of z_index change
|
||||||
|
if (old_z_index != drawable->z_index) {
|
||||||
|
drawable->notifyZIndexChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UIDrawable::notifyZIndexChanged() {
|
||||||
|
// Mark the current scene as needing sort
|
||||||
|
// This works for elements in the scene's ui_elements collection
|
||||||
|
McRFPy_API::markSceneNeedsSort();
|
||||||
|
|
||||||
|
// TODO: In the future, we could add parent tracking to handle Frame children
|
||||||
|
// For now, Frame children will need manual sorting or collection modification
|
||||||
|
// to trigger a resort
|
||||||
|
}
|
||||||
|
|
|
@ -42,6 +42,27 @@ public:
|
||||||
|
|
||||||
static PyObject* get_click(PyObject* self, void* closure);
|
static PyObject* get_click(PyObject* self, void* closure);
|
||||||
static int set_click(PyObject* self, PyObject* value, void* closure);
|
static int set_click(PyObject* self, PyObject* value, void* closure);
|
||||||
|
static PyObject* get_int(PyObject* self, void* closure);
|
||||||
|
static int set_int(PyObject* self, PyObject* value, void* closure);
|
||||||
|
|
||||||
|
// Z-order for rendering (lower values rendered first, higher values on top)
|
||||||
|
int z_index = 0;
|
||||||
|
|
||||||
|
// Notification for z_index changes
|
||||||
|
void notifyZIndexChanged();
|
||||||
|
|
||||||
|
// Animation support
|
||||||
|
virtual bool setProperty(const std::string& name, float value) { return false; }
|
||||||
|
virtual bool setProperty(const std::string& name, int value) { return false; }
|
||||||
|
virtual bool setProperty(const std::string& name, const sf::Color& value) { return false; }
|
||||||
|
virtual bool setProperty(const std::string& name, const sf::Vector2f& value) { return false; }
|
||||||
|
virtual bool setProperty(const std::string& name, const std::string& value) { return false; }
|
||||||
|
|
||||||
|
virtual bool getProperty(const std::string& name, float& value) const { return false; }
|
||||||
|
virtual bool getProperty(const std::string& name, int& value) const { return false; }
|
||||||
|
virtual bool getProperty(const std::string& name, sf::Color& value) const { return false; }
|
||||||
|
virtual bool getProperty(const std::string& name, sf::Vector2f& value) const { return false; }
|
||||||
|
virtual bool getProperty(const std::string& name, std::string& value) const { return false; }
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|
|
@ -75,7 +75,7 @@ int UIEntity::init(PyUIEntityObject* self, PyObject* args, PyObject* kwds) {
|
||||||
|
|
||||||
//if (!PyArg_ParseTupleAndKeywords(args, kwds, "ffOi|O",
|
//if (!PyArg_ParseTupleAndKeywords(args, kwds, "ffOi|O",
|
||||||
// const_cast<char**>(keywords), &x, &y, &texture, &sprite_index, &grid))
|
// const_cast<char**>(keywords), &x, &y, &texture, &sprite_index, &grid))
|
||||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "OOi|O",
|
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|OiO",
|
||||||
const_cast<char**>(keywords), &pos, &texture, &sprite_index, &grid))
|
const_cast<char**>(keywords), &pos, &texture, &sprite_index, &grid))
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -90,33 +90,37 @@ int UIEntity::init(PyUIEntityObject* self, PyObject* args, PyObject* kwds) {
|
||||||
|
|
||||||
// check types for texture
|
// check types for texture
|
||||||
//
|
//
|
||||||
// Set Texture
|
// Set Texture - allow None or use default
|
||||||
//
|
//
|
||||||
if (texture != NULL && !PyObject_IsInstance(texture, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture"))){
|
std::shared_ptr<PyTexture> texture_ptr = nullptr;
|
||||||
PyErr_SetString(PyExc_TypeError, "texture must be a mcrfpy.Texture instance");
|
if (texture != NULL && texture != Py_None && !PyObject_IsInstance(texture, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture"))){
|
||||||
|
PyErr_SetString(PyExc_TypeError, "texture must be a mcrfpy.Texture instance or None");
|
||||||
return -1;
|
return -1;
|
||||||
} /*else if (texture != NULL) // this section needs to go; texture isn't optional and isn't managed by the UI objects anymore
|
} else if (texture != NULL && texture != Py_None) {
|
||||||
{
|
auto pytexture = (PyTextureObject*)texture;
|
||||||
self->texture = texture;
|
texture_ptr = pytexture->data;
|
||||||
Py_INCREF(texture);
|
} else {
|
||||||
} else
|
// Use default texture when None or not provided
|
||||||
{
|
texture_ptr = McRFPy_API::default_texture;
|
||||||
// default tex?
|
}
|
||||||
}*/
|
|
||||||
|
if (!texture_ptr) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, "No texture provided and no default texture available");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
if (grid != NULL && !PyObject_IsInstance(grid, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) {
|
if (grid != NULL && !PyObject_IsInstance(grid, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) {
|
||||||
PyErr_SetString(PyExc_TypeError, "grid must be a mcrfpy.Grid instance");
|
PyErr_SetString(PyExc_TypeError, "grid must be a mcrfpy.Grid instance");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto pytexture = (PyTextureObject*)texture;
|
|
||||||
if (grid == NULL)
|
if (grid == NULL)
|
||||||
self->data = std::make_shared<UIEntity>();
|
self->data = std::make_shared<UIEntity>();
|
||||||
else
|
else
|
||||||
self->data = std::make_shared<UIEntity>(*((PyUIGridObject*)grid)->data);
|
self->data = std::make_shared<UIEntity>(*((PyUIGridObject*)grid)->data);
|
||||||
|
|
||||||
// TODO - PyTextureObjects and IndexTextures are a little bit of a mess with shared/unshared pointers
|
// TODO - PyTextureObjects and IndexTextures are a little bit of a mess with shared/unshared pointers
|
||||||
self->data->sprite = UISprite(pytexture->data, sprite_index, sf::Vector2f(0,0), 1.0);
|
self->data->sprite = UISprite(texture_ptr, sprite_index, sf::Vector2f(0,0), 1.0);
|
||||||
self->data->position = pos_result->data;
|
self->data->position = pos_result->data;
|
||||||
if (grid != NULL) {
|
if (grid != NULL) {
|
||||||
PyUIGridObject* pygrid = (PyUIGridObject*)grid;
|
PyUIGridObject* pygrid = (PyUIGridObject*)grid;
|
||||||
|
@ -261,3 +265,51 @@ PyObject* UIEntity::repr(PyUIEntityObject* self) {
|
||||||
std::string repr_str = ss.str();
|
std::string repr_str = ss.str();
|
||||||
return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
|
return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Property system implementation for animations
|
||||||
|
bool UIEntity::setProperty(const std::string& name, float value) {
|
||||||
|
if (name == "x") {
|
||||||
|
position.x = value;
|
||||||
|
collision_pos.x = static_cast<int>(value);
|
||||||
|
// Update sprite position based on grid position
|
||||||
|
// Note: This is a simplified version - actual grid-to-pixel conversion depends on grid properties
|
||||||
|
sprite.setPosition(sf::Vector2f(position.x, position.y));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "y") {
|
||||||
|
position.y = value;
|
||||||
|
collision_pos.y = static_cast<int>(value);
|
||||||
|
// Update sprite position based on grid position
|
||||||
|
sprite.setPosition(sf::Vector2f(position.x, position.y));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "sprite_scale") {
|
||||||
|
sprite.setScale(sf::Vector2f(value, value));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UIEntity::setProperty(const std::string& name, int value) {
|
||||||
|
if (name == "sprite_number") {
|
||||||
|
sprite.setSpriteIndex(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UIEntity::getProperty(const std::string& name, float& value) const {
|
||||||
|
if (name == "x") {
|
||||||
|
value = position.x;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "y") {
|
||||||
|
value = position.y;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "sprite_scale") {
|
||||||
|
value = sprite.getScale().x; // Assuming uniform scale
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
|
@ -46,6 +46,11 @@ public:
|
||||||
UIEntity();
|
UIEntity();
|
||||||
UIEntity(UIGrid&);
|
UIEntity(UIGrid&);
|
||||||
|
|
||||||
|
// Property system for animations
|
||||||
|
bool setProperty(const std::string& name, float value);
|
||||||
|
bool setProperty(const std::string& name, int value);
|
||||||
|
bool getProperty(const std::string& name, float& value) const;
|
||||||
|
|
||||||
static PyObject* at(PyUIEntityObject* self, PyObject* o);
|
static PyObject* at(PyUIEntityObject* self, PyObject* o);
|
||||||
static PyObject* index(PyUIEntityObject* self, PyObject* Py_UNUSED(ignored));
|
static PyObject* index(PyUIEntityObject* self, PyObject* Py_UNUSED(ignored));
|
||||||
static int init(PyUIEntityObject* self, PyObject* args, PyObject* kwds);
|
static int init(PyUIEntityObject* self, PyObject* args, PyObject* kwds);
|
||||||
|
|
159
src/UIFrame.cpp
159
src/UIFrame.cpp
|
@ -51,6 +51,15 @@ void UIFrame::render(sf::Vector2f offset, sf::RenderTarget& target)
|
||||||
target.draw(box);
|
target.draw(box);
|
||||||
box.move(-offset);
|
box.move(-offset);
|
||||||
|
|
||||||
|
// Sort children by z_index if needed
|
||||||
|
if (children_need_sort && !children->empty()) {
|
||||||
|
std::sort(children->begin(), children->end(),
|
||||||
|
[](const std::shared_ptr<UIDrawable>& a, const std::shared_ptr<UIDrawable>& b) {
|
||||||
|
return a->z_index < b->z_index;
|
||||||
|
});
|
||||||
|
children_need_sort = false;
|
||||||
|
}
|
||||||
|
|
||||||
for (auto drawable : *children) {
|
for (auto drawable : *children) {
|
||||||
drawable->render(offset + box.getPosition(), target);
|
drawable->render(offset + box.getPosition(), target);
|
||||||
}
|
}
|
||||||
|
@ -215,6 +224,7 @@ PyGetSetDef UIFrame::getsetters[] = {
|
||||||
{"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, "Object called with (x, y, button) when clicked", (void*)PyObjectsEnum::UIFRAME},
|
||||||
|
{"z_index", (getter)UIDrawable::get_int, (setter)UIDrawable::set_int, "Z-order for rendering (lower values rendered first)", (void*)PyObjectsEnum::UIFRAME},
|
||||||
{NULL}
|
{NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -264,3 +274,152 @@ int UIFrame::init(PyUIFrameObject* self, PyObject* args, PyObject* kwds)
|
||||||
if (err_val) return err_val;
|
if (err_val) return err_val;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Animation property system implementation
|
||||||
|
bool UIFrame::setProperty(const std::string& name, float value) {
|
||||||
|
if (name == "x") {
|
||||||
|
box.setPosition(sf::Vector2f(value, box.getPosition().y));
|
||||||
|
return true;
|
||||||
|
} else if (name == "y") {
|
||||||
|
box.setPosition(sf::Vector2f(box.getPosition().x, value));
|
||||||
|
return true;
|
||||||
|
} else if (name == "w") {
|
||||||
|
box.setSize(sf::Vector2f(value, box.getSize().y));
|
||||||
|
return true;
|
||||||
|
} else if (name == "h") {
|
||||||
|
box.setSize(sf::Vector2f(box.getSize().x, value));
|
||||||
|
return true;
|
||||||
|
} else if (name == "outline") {
|
||||||
|
box.setOutlineThickness(value);
|
||||||
|
return true;
|
||||||
|
} else if (name == "fill_color.r") {
|
||||||
|
auto color = box.getFillColor();
|
||||||
|
color.r = std::clamp(static_cast<int>(value), 0, 255);
|
||||||
|
box.setFillColor(color);
|
||||||
|
return true;
|
||||||
|
} else if (name == "fill_color.g") {
|
||||||
|
auto color = box.getFillColor();
|
||||||
|
color.g = std::clamp(static_cast<int>(value), 0, 255);
|
||||||
|
box.setFillColor(color);
|
||||||
|
return true;
|
||||||
|
} else if (name == "fill_color.b") {
|
||||||
|
auto color = box.getFillColor();
|
||||||
|
color.b = std::clamp(static_cast<int>(value), 0, 255);
|
||||||
|
box.setFillColor(color);
|
||||||
|
return true;
|
||||||
|
} else if (name == "fill_color.a") {
|
||||||
|
auto color = box.getFillColor();
|
||||||
|
color.a = std::clamp(static_cast<int>(value), 0, 255);
|
||||||
|
box.setFillColor(color);
|
||||||
|
return true;
|
||||||
|
} else if (name == "outline_color.r") {
|
||||||
|
auto color = box.getOutlineColor();
|
||||||
|
color.r = std::clamp(static_cast<int>(value), 0, 255);
|
||||||
|
box.setOutlineColor(color);
|
||||||
|
return true;
|
||||||
|
} else if (name == "outline_color.g") {
|
||||||
|
auto color = box.getOutlineColor();
|
||||||
|
color.g = std::clamp(static_cast<int>(value), 0, 255);
|
||||||
|
box.setOutlineColor(color);
|
||||||
|
return true;
|
||||||
|
} else if (name == "outline_color.b") {
|
||||||
|
auto color = box.getOutlineColor();
|
||||||
|
color.b = std::clamp(static_cast<int>(value), 0, 255);
|
||||||
|
box.setOutlineColor(color);
|
||||||
|
return true;
|
||||||
|
} else if (name == "outline_color.a") {
|
||||||
|
auto color = box.getOutlineColor();
|
||||||
|
color.a = std::clamp(static_cast<int>(value), 0, 255);
|
||||||
|
box.setOutlineColor(color);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UIFrame::setProperty(const std::string& name, const sf::Color& value) {
|
||||||
|
if (name == "fill_color") {
|
||||||
|
box.setFillColor(value);
|
||||||
|
return true;
|
||||||
|
} else if (name == "outline_color") {
|
||||||
|
box.setOutlineColor(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UIFrame::setProperty(const std::string& name, const sf::Vector2f& value) {
|
||||||
|
if (name == "position") {
|
||||||
|
box.setPosition(value);
|
||||||
|
return true;
|
||||||
|
} else if (name == "size") {
|
||||||
|
box.setSize(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UIFrame::getProperty(const std::string& name, float& value) const {
|
||||||
|
if (name == "x") {
|
||||||
|
value = box.getPosition().x;
|
||||||
|
return true;
|
||||||
|
} else if (name == "y") {
|
||||||
|
value = box.getPosition().y;
|
||||||
|
return true;
|
||||||
|
} else if (name == "w") {
|
||||||
|
value = box.getSize().x;
|
||||||
|
return true;
|
||||||
|
} else if (name == "h") {
|
||||||
|
value = box.getSize().y;
|
||||||
|
return true;
|
||||||
|
} else if (name == "outline") {
|
||||||
|
value = box.getOutlineThickness();
|
||||||
|
return true;
|
||||||
|
} else if (name == "fill_color.r") {
|
||||||
|
value = box.getFillColor().r;
|
||||||
|
return true;
|
||||||
|
} else if (name == "fill_color.g") {
|
||||||
|
value = box.getFillColor().g;
|
||||||
|
return true;
|
||||||
|
} else if (name == "fill_color.b") {
|
||||||
|
value = box.getFillColor().b;
|
||||||
|
return true;
|
||||||
|
} else if (name == "fill_color.a") {
|
||||||
|
value = box.getFillColor().a;
|
||||||
|
return true;
|
||||||
|
} else if (name == "outline_color.r") {
|
||||||
|
value = box.getOutlineColor().r;
|
||||||
|
return true;
|
||||||
|
} else if (name == "outline_color.g") {
|
||||||
|
value = box.getOutlineColor().g;
|
||||||
|
return true;
|
||||||
|
} else if (name == "outline_color.b") {
|
||||||
|
value = box.getOutlineColor().b;
|
||||||
|
return true;
|
||||||
|
} else if (name == "outline_color.a") {
|
||||||
|
value = box.getOutlineColor().a;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UIFrame::getProperty(const std::string& name, sf::Color& value) const {
|
||||||
|
if (name == "fill_color") {
|
||||||
|
value = box.getFillColor();
|
||||||
|
return true;
|
||||||
|
} else if (name == "outline_color") {
|
||||||
|
value = box.getOutlineColor();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UIFrame::getProperty(const std::string& name, sf::Vector2f& value) const {
|
||||||
|
if (name == "position") {
|
||||||
|
value = box.getPosition();
|
||||||
|
return true;
|
||||||
|
} else if (name == "size") {
|
||||||
|
value = box.getSize();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ public:
|
||||||
sf::RectangleShape box;
|
sf::RectangleShape box;
|
||||||
float outline;
|
float outline;
|
||||||
std::shared_ptr<std::vector<std::shared_ptr<UIDrawable>>> children;
|
std::shared_ptr<std::vector<std::shared_ptr<UIDrawable>>> children;
|
||||||
|
bool children_need_sort = true; // Dirty flag for z_index sorting optimization
|
||||||
void render(sf::Vector2f, sf::RenderTarget&) override final;
|
void render(sf::Vector2f, sf::RenderTarget&) override final;
|
||||||
void move(sf::Vector2f);
|
void move(sf::Vector2f);
|
||||||
PyObjectsEnum derived_type() override final;
|
PyObjectsEnum derived_type() override final;
|
||||||
|
@ -42,6 +43,15 @@ public:
|
||||||
static PyGetSetDef getsetters[];
|
static PyGetSetDef getsetters[];
|
||||||
static PyObject* repr(PyUIFrameObject* self);
|
static PyObject* repr(PyUIFrameObject* self);
|
||||||
static int init(PyUIFrameObject* self, PyObject* args, PyObject* kwds);
|
static int init(PyUIFrameObject* self, PyObject* args, PyObject* kwds);
|
||||||
|
|
||||||
|
// Animation property system
|
||||||
|
bool setProperty(const std::string& name, float value) override;
|
||||||
|
bool setProperty(const std::string& name, const sf::Color& value) override;
|
||||||
|
bool setProperty(const std::string& name, const sf::Vector2f& value) override;
|
||||||
|
|
||||||
|
bool getProperty(const std::string& name, float& value) const override;
|
||||||
|
bool getProperty(const std::string& name, sf::Color& value) const override;
|
||||||
|
bool getProperty(const std::string& name, sf::Vector2f& value) const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace mcrfpydef {
|
namespace mcrfpydef {
|
||||||
|
|
662
src/UIGrid.cpp
662
src/UIGrid.cpp
|
@ -1,6 +1,7 @@
|
||||||
#include "UIGrid.h"
|
#include "UIGrid.h"
|
||||||
#include "GameEngine.h"
|
#include "GameEngine.h"
|
||||||
#include "McRFPy_API.h"
|
#include "McRFPy_API.h"
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
UIGrid::UIGrid() {}
|
UIGrid::UIGrid() {}
|
||||||
|
|
||||||
|
@ -218,27 +219,66 @@ UIDrawable* UIGrid::click_at(sf::Vector2f point)
|
||||||
|
|
||||||
int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) {
|
int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) {
|
||||||
int grid_x, grid_y;
|
int grid_x, grid_y;
|
||||||
PyObject* textureObj;
|
PyObject* textureObj = Py_None;
|
||||||
//float box_x, box_y, box_w, box_h;
|
//float box_x, box_y, box_w, box_h;
|
||||||
PyObject* pos, *size;
|
PyObject* pos = NULL;
|
||||||
|
PyObject* size = NULL;
|
||||||
|
|
||||||
//if (!PyArg_ParseTuple(args, "iiOffff", &grid_x, &grid_y, &textureObj, &box_x, &box_y, &box_w, &box_h)) {
|
//if (!PyArg_ParseTuple(args, "iiOffff", &grid_x, &grid_y, &textureObj, &box_x, &box_y, &box_w, &box_h)) {
|
||||||
if (!PyArg_ParseTuple(args, "iiOOO", &grid_x, &grid_y, &textureObj, &pos, &size)) {
|
if (!PyArg_ParseTuple(args, "ii|OOO", &grid_x, &grid_y, &textureObj, &pos, &size)) {
|
||||||
return -1; // If parsing fails, return an error
|
return -1; // If parsing fails, return an error
|
||||||
}
|
}
|
||||||
|
|
||||||
PyVectorObject* pos_result = PyVector::from_arg(pos);
|
// Default position and size if not provided
|
||||||
if (!pos_result)
|
PyVectorObject* pos_result = NULL;
|
||||||
{
|
PyVectorObject* size_result = NULL;
|
||||||
PyErr_SetString(PyExc_TypeError, "pos must be a mcrfpy.Vector instance or arguments to mcrfpy.Vector.__init__");
|
|
||||||
return -1;
|
if (pos) {
|
||||||
|
pos_result = PyVector::from_arg(pos);
|
||||||
|
if (!pos_result)
|
||||||
|
{
|
||||||
|
PyErr_SetString(PyExc_TypeError, "pos must be a mcrfpy.Vector instance or arguments to mcrfpy.Vector.__init__");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Default position (0, 0)
|
||||||
|
PyObject* vector_class = PyObject_GetAttrString(McRFPy_API::mcrf_module, "Vector");
|
||||||
|
if (vector_class) {
|
||||||
|
PyObject* pos_obj = PyObject_CallFunction(vector_class, "ff", 0.0f, 0.0f);
|
||||||
|
Py_DECREF(vector_class);
|
||||||
|
if (pos_obj) {
|
||||||
|
pos_result = (PyVectorObject*)pos_obj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!pos_result) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, "Failed to create default position vector");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PyVectorObject* size_result = PyVector::from_arg(size);
|
if (size) {
|
||||||
if (!size_result)
|
size_result = PyVector::from_arg(size);
|
||||||
{
|
if (!size_result)
|
||||||
PyErr_SetString(PyExc_TypeError, "pos must be a mcrfpy.Vector instance or arguments to mcrfpy.Vector.__init__");
|
{
|
||||||
return -1;
|
PyErr_SetString(PyExc_TypeError, "size must be a mcrfpy.Vector instance or arguments to mcrfpy.Vector.__init__");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Default size based on grid dimensions
|
||||||
|
float default_w = grid_x * 16.0f; // Assuming 16 pixel tiles
|
||||||
|
float default_h = grid_y * 16.0f;
|
||||||
|
PyObject* vector_class = PyObject_GetAttrString(McRFPy_API::mcrf_module, "Vector");
|
||||||
|
if (vector_class) {
|
||||||
|
PyObject* size_obj = PyObject_CallFunction(vector_class, "ff", default_w, default_h);
|
||||||
|
Py_DECREF(vector_class);
|
||||||
|
if (size_obj) {
|
||||||
|
size_result = (PyVectorObject*)size_obj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!size_result) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, "Failed to create default size vector");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert PyObject texture to IndexTexture*
|
// Convert PyObject texture to IndexTexture*
|
||||||
|
@ -246,7 +286,7 @@ int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) {
|
||||||
|
|
||||||
std::shared_ptr<PyTexture> texture_ptr = nullptr;
|
std::shared_ptr<PyTexture> texture_ptr = nullptr;
|
||||||
|
|
||||||
// Allow None for texture
|
// Allow None for texture - use default texture in that case
|
||||||
if (textureObj != Py_None) {
|
if (textureObj != Py_None) {
|
||||||
//if (!PyObject_IsInstance(textureObj, (PyObject*)&PyTextureType)) {
|
//if (!PyObject_IsInstance(textureObj, (PyObject*)&PyTextureType)) {
|
||||||
if (!PyObject_IsInstance(textureObj, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture"))) {
|
if (!PyObject_IsInstance(textureObj, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture"))) {
|
||||||
|
@ -255,6 +295,9 @@ int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) {
|
||||||
}
|
}
|
||||||
PyTextureObject* pyTexture = reinterpret_cast<PyTextureObject*>(textureObj);
|
PyTextureObject* pyTexture = reinterpret_cast<PyTextureObject*>(textureObj);
|
||||||
texture_ptr = pyTexture->data;
|
texture_ptr = pyTexture->data;
|
||||||
|
} else {
|
||||||
|
// Use default texture when None is provided
|
||||||
|
texture_ptr = McRFPy_API::default_texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize UIGrid - texture_ptr will be nullptr if texture was None
|
// Initialize UIGrid - texture_ptr will be nullptr if texture was None
|
||||||
|
@ -458,6 +501,7 @@ PyGetSetDef UIGrid::getsetters[] = {
|
||||||
{"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, "Object called with (x, y, button) when clicked", (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
|
||||||
|
{"z_index", (getter)UIDrawable::get_int, (setter)UIDrawable::set_int, "Z-order for rendering (lower values rendered first)", (void*)PyObjectsEnum::UIGRID},
|
||||||
{NULL} /* Sentinel */
|
{NULL} /* Sentinel */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -581,15 +625,196 @@ return NULL;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int UIEntityCollection::setitem(PyUIEntityCollectionObject* self, Py_ssize_t index, PyObject* value) {
|
||||||
|
auto list = self->data.get();
|
||||||
|
if (!list) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle negative indexing
|
||||||
|
while (index < 0) index += list->size();
|
||||||
|
|
||||||
|
// Bounds check
|
||||||
|
if (index >= list->size()) {
|
||||||
|
PyErr_SetString(PyExc_IndexError, "EntityCollection assignment index out of range");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get iterator to the target position
|
||||||
|
auto it = list->begin();
|
||||||
|
std::advance(it, index);
|
||||||
|
|
||||||
|
// Handle deletion
|
||||||
|
if (value == NULL) {
|
||||||
|
// Clear grid reference from the entity being removed
|
||||||
|
(*it)->grid = nullptr;
|
||||||
|
list->erase(it);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type checking - must be an Entity
|
||||||
|
if (!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity"))) {
|
||||||
|
PyErr_SetString(PyExc_TypeError, "EntityCollection can only contain Entity objects");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the C++ object from the Python object
|
||||||
|
PyUIEntityObject* entity = (PyUIEntityObject*)value;
|
||||||
|
if (!entity->data) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, "Invalid Entity object");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear grid reference from the old entity
|
||||||
|
(*it)->grid = nullptr;
|
||||||
|
|
||||||
|
// Replace the element and set grid reference
|
||||||
|
*it = entity->data;
|
||||||
|
entity->data->grid = self->grid;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int UIEntityCollection::contains(PyUIEntityCollectionObject* self, PyObject* value) {
|
||||||
|
auto list = self->data.get();
|
||||||
|
if (!list) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type checking - must be an Entity
|
||||||
|
if (!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity"))) {
|
||||||
|
// Not an Entity, so it can't be in the collection
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the C++ object from the Python object
|
||||||
|
PyUIEntityObject* entity = (PyUIEntityObject*)value;
|
||||||
|
if (!entity->data) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search for the object by comparing C++ pointers
|
||||||
|
for (const auto& ent : *list) {
|
||||||
|
if (ent.get() == entity->data.get()) {
|
||||||
|
return 1; // Found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0; // Not found
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* UIEntityCollection::concat(PyUIEntityCollectionObject* self, PyObject* other) {
|
||||||
|
// Create a new Python list containing elements from both collections
|
||||||
|
if (!PySequence_Check(other)) {
|
||||||
|
PyErr_SetString(PyExc_TypeError, "can only concatenate sequence to EntityCollection");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_ssize_t self_len = self->data->size();
|
||||||
|
Py_ssize_t other_len = PySequence_Length(other);
|
||||||
|
if (other_len == -1) {
|
||||||
|
return NULL; // Error already set
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* result_list = PyList_New(self_len + other_len);
|
||||||
|
if (!result_list) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all elements from self
|
||||||
|
Py_ssize_t idx = 0;
|
||||||
|
for (const auto& entity : *self->data) {
|
||||||
|
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity");
|
||||||
|
auto obj = (PyUIEntityObject*)type->tp_alloc(type, 0);
|
||||||
|
if (obj) {
|
||||||
|
obj->data = entity;
|
||||||
|
PyList_SET_ITEM(result_list, idx, (PyObject*)obj); // Steals reference
|
||||||
|
} else {
|
||||||
|
Py_DECREF(result_list);
|
||||||
|
Py_DECREF(type);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
Py_DECREF(type);
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all elements from other
|
||||||
|
for (Py_ssize_t i = 0; i < other_len; i++) {
|
||||||
|
PyObject* item = PySequence_GetItem(other, i);
|
||||||
|
if (!item) {
|
||||||
|
Py_DECREF(result_list);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
PyList_SET_ITEM(result_list, self_len + i, item); // Steals reference
|
||||||
|
}
|
||||||
|
|
||||||
|
return result_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* UIEntityCollection::inplace_concat(PyUIEntityCollectionObject* self, PyObject* other) {
|
||||||
|
if (!PySequence_Check(other)) {
|
||||||
|
PyErr_SetString(PyExc_TypeError, "can only concatenate sequence to EntityCollection");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// First, validate ALL items in the sequence before modifying anything
|
||||||
|
Py_ssize_t other_len = PySequence_Length(other);
|
||||||
|
if (other_len == -1) {
|
||||||
|
return NULL; // Error already set
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate all items first
|
||||||
|
for (Py_ssize_t i = 0; i < other_len; i++) {
|
||||||
|
PyObject* item = PySequence_GetItem(other, i);
|
||||||
|
if (!item) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type check
|
||||||
|
if (!PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity"))) {
|
||||||
|
Py_DECREF(item);
|
||||||
|
PyErr_Format(PyExc_TypeError,
|
||||||
|
"EntityCollection can only contain Entity objects; "
|
||||||
|
"got %s at index %zd", Py_TYPE(item)->tp_name, i);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
Py_DECREF(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
// All items validated, now we can safely add them
|
||||||
|
for (Py_ssize_t i = 0; i < other_len; i++) {
|
||||||
|
PyObject* item = PySequence_GetItem(other, i);
|
||||||
|
if (!item) {
|
||||||
|
return NULL; // Shouldn't happen, but be safe
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the existing append method which handles grid references
|
||||||
|
PyObject* result = append(self, item);
|
||||||
|
Py_DECREF(item);
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return NULL; // append() failed
|
||||||
|
}
|
||||||
|
Py_DECREF(result); // append returns Py_None
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_INCREF(self);
|
||||||
|
return (PyObject*)self;
|
||||||
|
}
|
||||||
|
|
||||||
PySequenceMethods UIEntityCollection::sqmethods = {
|
PySequenceMethods UIEntityCollection::sqmethods = {
|
||||||
.sq_length = (lenfunc)UIEntityCollection::len,
|
.sq_length = (lenfunc)UIEntityCollection::len,
|
||||||
|
.sq_concat = (binaryfunc)UIEntityCollection::concat,
|
||||||
|
.sq_repeat = NULL,
|
||||||
.sq_item = (ssizeargfunc)UIEntityCollection::getitem,
|
.sq_item = (ssizeargfunc)UIEntityCollection::getitem,
|
||||||
//.sq_item_by_index = UIEntityCollection::getitem
|
.was_sq_slice = NULL,
|
||||||
//.sq_slice - return a subset of the iterable
|
.sq_ass_item = (ssizeobjargproc)UIEntityCollection::setitem,
|
||||||
//.sq_ass_item - called when `o[x] = y` is executed (x is any object type)
|
.was_sq_ass_slice = NULL,
|
||||||
//.sq_ass_slice - cool; no thanks, for now
|
.sq_contains = (objobjproc)UIEntityCollection::contains,
|
||||||
//.sq_contains - called when `x in o` is executed
|
.sq_inplace_concat = (binaryfunc)UIEntityCollection::inplace_concat,
|
||||||
//.sq_ass_item_by_index - called when `o[x] = y` is executed (x is explictly an integer)
|
.sq_inplace_repeat = NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
PyObject* UIEntityCollection::append(PyUIEntityCollectionObject* self, PyObject* o)
|
PyObject* UIEntityCollection::append(PyUIEntityCollectionObject* self, PyObject* o)
|
||||||
|
@ -616,23 +841,29 @@ PyObject* UIEntityCollection::remove(PyUIEntityCollectionObject* self, PyObject*
|
||||||
{
|
{
|
||||||
if (!PyLong_Check(o))
|
if (!PyLong_Check(o))
|
||||||
{
|
{
|
||||||
PyErr_SetString(PyExc_TypeError, "UICollection.remove requires an integer index to remove");
|
PyErr_SetString(PyExc_TypeError, "EntityCollection.remove requires an integer index to remove");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
long index = PyLong_AsLong(o);
|
long index = PyLong_AsLong(o);
|
||||||
|
|
||||||
|
// Handle negative indexing
|
||||||
|
while (index < 0) index += self->data->size();
|
||||||
|
|
||||||
if (index >= self->data->size())
|
if (index >= self->data->size())
|
||||||
{
|
{
|
||||||
PyErr_SetString(PyExc_ValueError, "Index out of range");
|
PyErr_SetString(PyExc_ValueError, "Index out of range");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
else if (index < 0)
|
|
||||||
{
|
|
||||||
PyErr_SetString(PyExc_NotImplementedError, "reverse indexing is not implemented.");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Get iterator to the entity to remove
|
||||||
|
auto it = self->data->begin();
|
||||||
|
std::advance(it, index);
|
||||||
|
|
||||||
|
// Clear grid reference before removing
|
||||||
|
(*it)->grid = nullptr;
|
||||||
|
|
||||||
// release the shared pointer at correct part of the list
|
// release the shared pointer at correct part of the list
|
||||||
self->data->erase(std::next(self->data->begin(), index));
|
self->data->erase(it);
|
||||||
Py_INCREF(Py_None);
|
Py_INCREF(Py_None);
|
||||||
return Py_None;
|
return Py_None;
|
||||||
}
|
}
|
||||||
|
@ -675,10 +906,275 @@ PyObject* UIEntityCollection::extend(PyUIEntityCollectionObject* self, PyObject*
|
||||||
return Py_None;
|
return Py_None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyObject* UIEntityCollection::index_method(PyUIEntityCollectionObject* self, PyObject* value) {
|
||||||
|
auto list = self->data.get();
|
||||||
|
if (!list) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type checking - must be an Entity
|
||||||
|
if (!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity"))) {
|
||||||
|
PyErr_SetString(PyExc_TypeError, "EntityCollection.index requires an Entity object");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the C++ object from the Python object
|
||||||
|
PyUIEntityObject* entity = (PyUIEntityObject*)value;
|
||||||
|
if (!entity->data) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, "Invalid Entity object");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search for the object
|
||||||
|
Py_ssize_t idx = 0;
|
||||||
|
for (const auto& ent : *list) {
|
||||||
|
if (ent.get() == entity->data.get()) {
|
||||||
|
return PyLong_FromSsize_t(idx);
|
||||||
|
}
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyErr_SetString(PyExc_ValueError, "Entity not in EntityCollection");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* UIEntityCollection::count(PyUIEntityCollectionObject* self, PyObject* value) {
|
||||||
|
auto list = self->data.get();
|
||||||
|
if (!list) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type checking - must be an Entity
|
||||||
|
if (!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity"))) {
|
||||||
|
// Not an Entity, so count is 0
|
||||||
|
return PyLong_FromLong(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the C++ object from the Python object
|
||||||
|
PyUIEntityObject* entity = (PyUIEntityObject*)value;
|
||||||
|
if (!entity->data) {
|
||||||
|
return PyLong_FromLong(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count occurrences
|
||||||
|
Py_ssize_t count = 0;
|
||||||
|
for (const auto& ent : *list) {
|
||||||
|
if (ent.get() == entity->data.get()) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return PyLong_FromSsize_t(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* UIEntityCollection::subscript(PyUIEntityCollectionObject* self, PyObject* key) {
|
||||||
|
if (PyLong_Check(key)) {
|
||||||
|
// Single index - delegate to sq_item
|
||||||
|
Py_ssize_t index = PyLong_AsSsize_t(key);
|
||||||
|
if (index == -1 && PyErr_Occurred()) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return getitem(self, index);
|
||||||
|
} else if (PySlice_Check(key)) {
|
||||||
|
// Handle slice
|
||||||
|
Py_ssize_t start, stop, step, slicelength;
|
||||||
|
|
||||||
|
if (PySlice_GetIndicesEx(key, self->data->size(), &start, &stop, &step, &slicelength) < 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* result_list = PyList_New(slicelength);
|
||||||
|
if (!result_list) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate through the list with slice parameters
|
||||||
|
auto it = self->data->begin();
|
||||||
|
for (Py_ssize_t i = 0, cur = start; i < slicelength; i++, cur += step) {
|
||||||
|
auto cur_it = it;
|
||||||
|
std::advance(cur_it, cur);
|
||||||
|
|
||||||
|
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity");
|
||||||
|
auto obj = (PyUIEntityObject*)type->tp_alloc(type, 0);
|
||||||
|
if (obj) {
|
||||||
|
obj->data = *cur_it;
|
||||||
|
PyList_SET_ITEM(result_list, i, (PyObject*)obj); // Steals reference
|
||||||
|
} else {
|
||||||
|
Py_DECREF(result_list);
|
||||||
|
Py_DECREF(type);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
Py_DECREF(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result_list;
|
||||||
|
} else {
|
||||||
|
PyErr_Format(PyExc_TypeError, "EntityCollection indices must be integers or slices, not %.200s",
|
||||||
|
Py_TYPE(key)->tp_name);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int UIEntityCollection::ass_subscript(PyUIEntityCollectionObject* self, PyObject* key, PyObject* value) {
|
||||||
|
if (PyLong_Check(key)) {
|
||||||
|
// Single index - delegate to sq_ass_item
|
||||||
|
Py_ssize_t index = PyLong_AsSsize_t(key);
|
||||||
|
if (index == -1 && PyErr_Occurred()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return setitem(self, index, value);
|
||||||
|
} else if (PySlice_Check(key)) {
|
||||||
|
// Handle slice assignment/deletion
|
||||||
|
Py_ssize_t start, stop, step, slicelength;
|
||||||
|
|
||||||
|
if (PySlice_GetIndicesEx(key, self->data->size(), &start, &stop, &step, &slicelength) < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value == NULL) {
|
||||||
|
// Deletion
|
||||||
|
if (step != 1) {
|
||||||
|
// For non-contiguous slices, delete from highest to lowest to maintain indices
|
||||||
|
std::vector<Py_ssize_t> indices;
|
||||||
|
for (Py_ssize_t i = 0, cur = start; i < slicelength; i++, cur += step) {
|
||||||
|
indices.push_back(cur);
|
||||||
|
}
|
||||||
|
// Sort in descending order
|
||||||
|
std::sort(indices.begin(), indices.end(), std::greater<Py_ssize_t>());
|
||||||
|
|
||||||
|
// Delete each index
|
||||||
|
for (Py_ssize_t idx : indices) {
|
||||||
|
auto it = self->data->begin();
|
||||||
|
std::advance(it, idx);
|
||||||
|
(*it)->grid = nullptr; // Clear grid reference
|
||||||
|
self->data->erase(it);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Contiguous slice - delete range
|
||||||
|
auto it_start = self->data->begin();
|
||||||
|
auto it_stop = self->data->begin();
|
||||||
|
std::advance(it_start, start);
|
||||||
|
std::advance(it_stop, stop);
|
||||||
|
|
||||||
|
// Clear grid references
|
||||||
|
for (auto it = it_start; it != it_stop; ++it) {
|
||||||
|
(*it)->grid = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
self->data->erase(it_start, it_stop);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
// Assignment
|
||||||
|
if (!PySequence_Check(value)) {
|
||||||
|
PyErr_SetString(PyExc_TypeError, "can only assign sequence to slice");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_ssize_t value_len = PySequence_Length(value);
|
||||||
|
if (value_len == -1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate all items first
|
||||||
|
std::vector<std::shared_ptr<UIEntity>> new_items;
|
||||||
|
for (Py_ssize_t i = 0; i < value_len; i++) {
|
||||||
|
PyObject* item = PySequence_GetItem(value, i);
|
||||||
|
if (!item) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type check
|
||||||
|
if (!PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity"))) {
|
||||||
|
Py_DECREF(item);
|
||||||
|
PyErr_Format(PyExc_TypeError,
|
||||||
|
"EntityCollection can only contain Entity objects; "
|
||||||
|
"got %s at index %zd", Py_TYPE(item)->tp_name, i);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyUIEntityObject* entity = (PyUIEntityObject*)item;
|
||||||
|
Py_DECREF(item);
|
||||||
|
new_items.push_back(entity->data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now perform the assignment
|
||||||
|
if (step == 1) {
|
||||||
|
// Contiguous slice
|
||||||
|
if (slicelength != value_len) {
|
||||||
|
// Need to resize - remove old items and insert new ones
|
||||||
|
auto it_start = self->data->begin();
|
||||||
|
auto it_stop = self->data->begin();
|
||||||
|
std::advance(it_start, start);
|
||||||
|
std::advance(it_stop, stop);
|
||||||
|
|
||||||
|
// Clear grid references from old items
|
||||||
|
for (auto it = it_start; it != it_stop; ++it) {
|
||||||
|
(*it)->grid = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Erase old range
|
||||||
|
it_start = self->data->erase(it_start, it_stop);
|
||||||
|
|
||||||
|
// Insert new items
|
||||||
|
for (const auto& entity : new_items) {
|
||||||
|
entity->grid = self->grid;
|
||||||
|
it_start = self->data->insert(it_start, entity);
|
||||||
|
++it_start;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Same size, just replace
|
||||||
|
auto it = self->data->begin();
|
||||||
|
std::advance(it, start);
|
||||||
|
for (const auto& entity : new_items) {
|
||||||
|
(*it)->grid = nullptr; // Clear old grid ref
|
||||||
|
*it = entity;
|
||||||
|
entity->grid = self->grid; // Set new grid ref
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Extended slice
|
||||||
|
if (slicelength != value_len) {
|
||||||
|
PyErr_Format(PyExc_ValueError,
|
||||||
|
"attempt to assign sequence of size %zd to extended slice of size %zd",
|
||||||
|
value_len, slicelength);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto list_it = self->data->begin();
|
||||||
|
for (Py_ssize_t i = 0, cur = start; i < slicelength; i++, cur += step) {
|
||||||
|
auto cur_it = list_it;
|
||||||
|
std::advance(cur_it, cur);
|
||||||
|
(*cur_it)->grid = nullptr; // Clear old grid ref
|
||||||
|
*cur_it = new_items[i];
|
||||||
|
new_items[i]->grid = self->grid; // Set new grid ref
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
PyErr_Format(PyExc_TypeError, "EntityCollection indices must be integers or slices, not %.200s",
|
||||||
|
Py_TYPE(key)->tp_name);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PyMappingMethods UIEntityCollection::mpmethods = {
|
||||||
|
.mp_length = (lenfunc)UIEntityCollection::len,
|
||||||
|
.mp_subscript = (binaryfunc)UIEntityCollection::subscript,
|
||||||
|
.mp_ass_subscript = (objobjargproc)UIEntityCollection::ass_subscript
|
||||||
|
};
|
||||||
|
|
||||||
PyMethodDef UIEntityCollection::methods[] = {
|
PyMethodDef UIEntityCollection::methods[] = {
|
||||||
{"append", (PyCFunction)UIEntityCollection::append, METH_O},
|
{"append", (PyCFunction)UIEntityCollection::append, METH_O},
|
||||||
{"extend", (PyCFunction)UIEntityCollection::extend, METH_O},
|
{"extend", (PyCFunction)UIEntityCollection::extend, METH_O},
|
||||||
{"remove", (PyCFunction)UIEntityCollection::remove, METH_O},
|
{"remove", (PyCFunction)UIEntityCollection::remove, METH_O},
|
||||||
|
{"index", (PyCFunction)UIEntityCollection::index_method, METH_O},
|
||||||
|
{"count", (PyCFunction)UIEntityCollection::count, METH_O},
|
||||||
{NULL, NULL, 0, NULL}
|
{NULL, NULL, 0, NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -723,3 +1219,115 @@ PyObject* UIEntityCollection::iter(PyUIEntityCollectionObject* self)
|
||||||
Py_DECREF(iterType);
|
Py_DECREF(iterType);
|
||||||
return (PyObject*)iterObj;
|
return (PyObject*)iterObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Property system implementation for animations
|
||||||
|
bool UIGrid::setProperty(const std::string& name, float value) {
|
||||||
|
if (name == "x") {
|
||||||
|
box.setPosition(sf::Vector2f(value, box.getPosition().y));
|
||||||
|
output.setPosition(box.getPosition());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "y") {
|
||||||
|
box.setPosition(sf::Vector2f(box.getPosition().x, value));
|
||||||
|
output.setPosition(box.getPosition());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "w" || name == "width") {
|
||||||
|
box.setSize(sf::Vector2f(value, box.getSize().y));
|
||||||
|
output.setTextureRect(sf::IntRect(0, 0, box.getSize().x, box.getSize().y));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "h" || name == "height") {
|
||||||
|
box.setSize(sf::Vector2f(box.getSize().x, value));
|
||||||
|
output.setTextureRect(sf::IntRect(0, 0, box.getSize().x, box.getSize().y));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "center_x") {
|
||||||
|
center_x = value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "center_y") {
|
||||||
|
center_y = value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "zoom") {
|
||||||
|
zoom = value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "z_index") {
|
||||||
|
z_index = static_cast<int>(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UIGrid::setProperty(const std::string& name, const sf::Vector2f& value) {
|
||||||
|
if (name == "position") {
|
||||||
|
box.setPosition(value);
|
||||||
|
output.setPosition(box.getPosition());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "size") {
|
||||||
|
box.setSize(value);
|
||||||
|
output.setTextureRect(sf::IntRect(0, 0, box.getSize().x, box.getSize().y));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "center") {
|
||||||
|
center_x = value.x;
|
||||||
|
center_y = value.y;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UIGrid::getProperty(const std::string& name, float& value) const {
|
||||||
|
if (name == "x") {
|
||||||
|
value = box.getPosition().x;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "y") {
|
||||||
|
value = box.getPosition().y;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "w" || name == "width") {
|
||||||
|
value = box.getSize().x;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "h" || name == "height") {
|
||||||
|
value = box.getSize().y;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "center_x") {
|
||||||
|
value = center_x;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "center_y") {
|
||||||
|
value = center_y;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "zoom") {
|
||||||
|
value = zoom;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "z_index") {
|
||||||
|
value = static_cast<float>(z_index);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UIGrid::getProperty(const std::string& name, sf::Vector2f& value) const {
|
||||||
|
if (name == "position") {
|
||||||
|
value = box.getPosition();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "size") {
|
||||||
|
value = box.getSize();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "center") {
|
||||||
|
value = sf::Vector2f(center_x, center_y);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
16
src/UIGrid.h
16
src/UIGrid.h
|
@ -45,6 +45,12 @@ public:
|
||||||
sf::RenderTexture renderTexture;
|
sf::RenderTexture renderTexture;
|
||||||
std::vector<UIGridPoint> points;
|
std::vector<UIGridPoint> points;
|
||||||
std::shared_ptr<std::list<std::shared_ptr<UIEntity>>> entities;
|
std::shared_ptr<std::list<std::shared_ptr<UIEntity>>> entities;
|
||||||
|
|
||||||
|
// Property system for animations
|
||||||
|
bool setProperty(const std::string& name, float value) override;
|
||||||
|
bool setProperty(const std::string& name, const sf::Vector2f& value) override;
|
||||||
|
bool getProperty(const std::string& name, float& value) const override;
|
||||||
|
bool getProperty(const std::string& name, sf::Vector2f& value) const override;
|
||||||
|
|
||||||
static int init(PyUIGridObject* self, PyObject* args, PyObject* kwds);
|
static int init(PyUIGridObject* self, PyObject* args, PyObject* kwds);
|
||||||
static PyObject* get_grid_size(PyUIGridObject* self, void* closure);
|
static PyObject* get_grid_size(PyUIGridObject* self, void* closure);
|
||||||
|
@ -76,15 +82,24 @@ typedef struct {
|
||||||
class UIEntityCollection {
|
class UIEntityCollection {
|
||||||
public:
|
public:
|
||||||
static PySequenceMethods sqmethods;
|
static PySequenceMethods sqmethods;
|
||||||
|
static PyMappingMethods mpmethods;
|
||||||
static PyObject* append(PyUIEntityCollectionObject* self, PyObject* o);
|
static PyObject* append(PyUIEntityCollectionObject* self, PyObject* o);
|
||||||
static PyObject* extend(PyUIEntityCollectionObject* self, PyObject* o);
|
static PyObject* extend(PyUIEntityCollectionObject* self, PyObject* o);
|
||||||
static PyObject* remove(PyUIEntityCollectionObject* self, PyObject* o);
|
static PyObject* remove(PyUIEntityCollectionObject* self, PyObject* o);
|
||||||
|
static PyObject* index_method(PyUIEntityCollectionObject* self, PyObject* value);
|
||||||
|
static PyObject* count(PyUIEntityCollectionObject* self, PyObject* value);
|
||||||
static PyMethodDef methods[];
|
static PyMethodDef methods[];
|
||||||
static PyObject* repr(PyUIEntityCollectionObject* self);
|
static PyObject* repr(PyUIEntityCollectionObject* self);
|
||||||
static int init(PyUIEntityCollectionObject* self, PyObject* args, PyObject* kwds);
|
static int init(PyUIEntityCollectionObject* self, PyObject* args, PyObject* kwds);
|
||||||
static PyObject* iter(PyUIEntityCollectionObject* self);
|
static PyObject* iter(PyUIEntityCollectionObject* self);
|
||||||
static Py_ssize_t len(PyUIEntityCollectionObject* self);
|
static Py_ssize_t len(PyUIEntityCollectionObject* self);
|
||||||
static PyObject* getitem(PyUIEntityCollectionObject* self, Py_ssize_t index);
|
static PyObject* getitem(PyUIEntityCollectionObject* self, Py_ssize_t index);
|
||||||
|
static int setitem(PyUIEntityCollectionObject* self, Py_ssize_t index, PyObject* value);
|
||||||
|
static int contains(PyUIEntityCollectionObject* self, PyObject* value);
|
||||||
|
static PyObject* concat(PyUIEntityCollectionObject* self, PyObject* other);
|
||||||
|
static PyObject* inplace_concat(PyUIEntityCollectionObject* self, PyObject* other);
|
||||||
|
static PyObject* subscript(PyUIEntityCollectionObject* self, PyObject* key);
|
||||||
|
static int ass_subscript(PyUIEntityCollectionObject* self, PyObject* key, PyObject* value);
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
@ -174,6 +189,7 @@ namespace mcrfpydef {
|
||||||
},
|
},
|
||||||
.tp_repr = (reprfunc)UIEntityCollection::repr,
|
.tp_repr = (reprfunc)UIEntityCollection::repr,
|
||||||
.tp_as_sequence = &UIEntityCollection::sqmethods,
|
.tp_as_sequence = &UIEntityCollection::sqmethods,
|
||||||
|
.tp_as_mapping = &UIEntityCollection::mpmethods,
|
||||||
.tp_flags = Py_TPFLAGS_DEFAULT,
|
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||||
.tp_doc = PyDoc_STR("Iterable, indexable collection of Entities"),
|
.tp_doc = PyDoc_STR("Iterable, indexable collection of Entities"),
|
||||||
.tp_iter = (getiterfunc)UIEntityCollection::iter,
|
.tp_iter = (getiterfunc)UIEntityCollection::iter,
|
||||||
|
|
111
src/UISprite.cpp
111
src/UISprite.cpp
|
@ -58,7 +58,7 @@ void UISprite::setSpriteIndex(int _sprite_index)
|
||||||
sprite = ptex->sprite(sprite_index, sprite.getPosition(), sprite.getScale());
|
sprite = ptex->sprite(sprite_index, sprite.getPosition(), sprite.getScale());
|
||||||
}
|
}
|
||||||
|
|
||||||
sf::Vector2f UISprite::getScale()
|
sf::Vector2f UISprite::getScale() const
|
||||||
{
|
{
|
||||||
return sprite.getScale();
|
return sprite.getScale();
|
||||||
}
|
}
|
||||||
|
@ -202,6 +202,7 @@ PyGetSetDef UISprite::getsetters[] = {
|
||||||
{"sprite_number", (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, "Which sprite on the texture is shown", 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, "Object called with (x, y, button) when clicked", (void*)PyObjectsEnum::UISPRITE},
|
||||||
|
{"z_index", (getter)UIDrawable::get_int, (setter)UIDrawable::set_int, "Z-order for rendering (lower values rendered first)", (void*)PyObjectsEnum::UISPRITE},
|
||||||
{NULL}
|
{NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -224,8 +225,8 @@ int UISprite::init(PyUISpriteObject* self, PyObject* args, PyObject* kwds)
|
||||||
//std::cout << "Init called\n";
|
//std::cout << "Init called\n";
|
||||||
static const char* keywords[] = { "x", "y", "texture", "sprite_index", "scale", nullptr };
|
static const char* keywords[] = { "x", "y", "texture", "sprite_index", "scale", nullptr };
|
||||||
float x = 0.0f, y = 0.0f, scale = 1.0f;
|
float x = 0.0f, y = 0.0f, scale = 1.0f;
|
||||||
int sprite_index;
|
int sprite_index = 0;
|
||||||
PyObject* texture;
|
PyObject* texture = NULL;
|
||||||
|
|
||||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ffOif",
|
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ffOif",
|
||||||
const_cast<char**>(keywords), &x, &y, &texture, &sprite_index, &scale))
|
const_cast<char**>(keywords), &x, &y, &texture, &sprite_index, &scale))
|
||||||
|
@ -233,15 +234,107 @@ int UISprite::init(PyUISpriteObject* self, PyObject* args, PyObject* kwds)
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check types for texture
|
// Handle texture - allow None or use default
|
||||||
//if (texture != NULL && !PyObject_IsInstance(texture, (PyObject*)&PyTextureType)){
|
std::shared_ptr<PyTexture> texture_ptr = nullptr;
|
||||||
if (texture != NULL && !PyObject_IsInstance(texture, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture"))){
|
if (texture != NULL && texture != Py_None && !PyObject_IsInstance(texture, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture"))){
|
||||||
PyErr_SetString(PyExc_TypeError, "texture must be a mcrfpy.Texture instance");
|
PyErr_SetString(PyExc_TypeError, "texture must be a mcrfpy.Texture instance or None");
|
||||||
|
return -1;
|
||||||
|
} else if (texture != NULL && texture != Py_None) {
|
||||||
|
auto pytexture = (PyTextureObject*)texture;
|
||||||
|
texture_ptr = pytexture->data;
|
||||||
|
} else {
|
||||||
|
// Use default texture when None or not provided
|
||||||
|
texture_ptr = McRFPy_API::default_texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!texture_ptr) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, "No texture provided and no default texture available");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
auto pytexture = (PyTextureObject*)texture;
|
|
||||||
self->data = std::make_shared<UISprite>(pytexture->data, sprite_index, sf::Vector2f(x, y), scale);
|
self->data = std::make_shared<UISprite>(texture_ptr, sprite_index, sf::Vector2f(x, y), scale);
|
||||||
self->data->setPosition(sf::Vector2f(x, y));
|
self->data->setPosition(sf::Vector2f(x, y));
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Property system implementation for animations
|
||||||
|
bool UISprite::setProperty(const std::string& name, float value) {
|
||||||
|
if (name == "x") {
|
||||||
|
sprite.setPosition(sf::Vector2f(value, sprite.getPosition().y));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "y") {
|
||||||
|
sprite.setPosition(sf::Vector2f(sprite.getPosition().x, value));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "scale") {
|
||||||
|
sprite.setScale(sf::Vector2f(value, value));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "scale_x") {
|
||||||
|
sprite.setScale(sf::Vector2f(value, sprite.getScale().y));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "scale_y") {
|
||||||
|
sprite.setScale(sf::Vector2f(sprite.getScale().x, value));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "z_index") {
|
||||||
|
z_index = static_cast<int>(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UISprite::setProperty(const std::string& name, int value) {
|
||||||
|
if (name == "sprite_number") {
|
||||||
|
setSpriteIndex(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "z_index") {
|
||||||
|
z_index = value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UISprite::getProperty(const std::string& name, float& value) const {
|
||||||
|
if (name == "x") {
|
||||||
|
value = sprite.getPosition().x;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "y") {
|
||||||
|
value = sprite.getPosition().y;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "scale") {
|
||||||
|
value = sprite.getScale().x; // Assuming uniform scale
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "scale_x") {
|
||||||
|
value = sprite.getScale().x;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "scale_y") {
|
||||||
|
value = sprite.getScale().y;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "z_index") {
|
||||||
|
value = static_cast<float>(z_index);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UISprite::getProperty(const std::string& name, int& value) const {
|
||||||
|
if (name == "sprite_number") {
|
||||||
|
value = sprite_index;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "z_index") {
|
||||||
|
value = z_index;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ public:
|
||||||
void setPosition(sf::Vector2f);
|
void setPosition(sf::Vector2f);
|
||||||
sf::Vector2f getPosition();
|
sf::Vector2f getPosition();
|
||||||
void setScale(sf::Vector2f);
|
void setScale(sf::Vector2f);
|
||||||
sf::Vector2f getScale();
|
sf::Vector2f getScale() const;
|
||||||
void setSpriteIndex(int);
|
void setSpriteIndex(int);
|
||||||
int getSpriteIndex();
|
int getSpriteIndex();
|
||||||
|
|
||||||
|
@ -41,6 +41,12 @@ public:
|
||||||
std::shared_ptr<PyTexture> getTexture();
|
std::shared_ptr<PyTexture> getTexture();
|
||||||
|
|
||||||
PyObjectsEnum derived_type() override final;
|
PyObjectsEnum derived_type() override final;
|
||||||
|
|
||||||
|
// Property system for animations
|
||||||
|
bool setProperty(const std::string& name, float value) override;
|
||||||
|
bool setProperty(const std::string& name, int value) override;
|
||||||
|
bool getProperty(const std::string& name, float& value) const override;
|
||||||
|
bool getProperty(const std::string& name, int& value) const override;
|
||||||
|
|
||||||
|
|
||||||
static PyObject* get_float_member(PyUISpriteObject* self, void* closure);
|
static PyObject* get_float_member(PyUISpriteObject* self, void* closure);
|
||||||
|
|
26
src/main.cpp
26
src/main.cpp
|
@ -3,6 +3,8 @@
|
||||||
#include "CommandLineParser.h"
|
#include "CommandLineParser.h"
|
||||||
#include "McRogueFaceConfig.h"
|
#include "McRogueFaceConfig.h"
|
||||||
#include "McRFPy_API.h"
|
#include "McRFPy_API.h"
|
||||||
|
#include "PyFont.h"
|
||||||
|
#include "PyTexture.h"
|
||||||
#include <Python.h>
|
#include <Python.h>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
@ -44,14 +46,27 @@ int run_game_engine(const McRogueFaceConfig& config)
|
||||||
|
|
||||||
int run_python_interpreter(const McRogueFaceConfig& config, int argc, char* argv[])
|
int run_python_interpreter(const McRogueFaceConfig& config, int argc, char* argv[])
|
||||||
{
|
{
|
||||||
// Create a headless game engine for automation API support
|
// Create a game engine with the requested configuration
|
||||||
McRogueFaceConfig engine_config = config;
|
GameEngine* engine = new GameEngine(config);
|
||||||
engine_config.headless = true; // Force headless mode for Python interpreter
|
|
||||||
GameEngine* engine = new GameEngine(engine_config);
|
|
||||||
|
|
||||||
// Initialize Python with configuration
|
// Initialize Python with configuration
|
||||||
McRFPy_API::init_python_with_config(config, argc, argv);
|
McRFPy_API::init_python_with_config(config, argc, argv);
|
||||||
|
|
||||||
|
// Import mcrfpy module and store reference
|
||||||
|
McRFPy_API::mcrf_module = PyImport_ImportModule("mcrfpy");
|
||||||
|
if (!McRFPy_API::mcrf_module) {
|
||||||
|
PyErr_Print();
|
||||||
|
std::cerr << "Failed to import mcrfpy module" << std::endl;
|
||||||
|
} else {
|
||||||
|
// Set up default_font and default_texture if not already done
|
||||||
|
if (!McRFPy_API::default_font) {
|
||||||
|
McRFPy_API::default_font = std::make_shared<PyFont>("assets/JetbrainsMono.ttf");
|
||||||
|
McRFPy_API::default_texture = std::make_shared<PyTexture>("assets/kenney_tinydungeon.png", 16, 16);
|
||||||
|
}
|
||||||
|
PyObject_SetAttrString(McRFPy_API::mcrf_module, "default_font", McRFPy_API::default_font->pyObject());
|
||||||
|
PyObject_SetAttrString(McRFPy_API::mcrf_module, "default_texture", McRFPy_API::default_texture->pyObject());
|
||||||
|
}
|
||||||
|
|
||||||
// Handle different Python modes
|
// Handle different Python modes
|
||||||
if (!config.python_command.empty()) {
|
if (!config.python_command.empty()) {
|
||||||
// Execute command from -c
|
// Execute command from -c
|
||||||
|
@ -161,6 +176,9 @@ int run_python_interpreter(const McRogueFaceConfig& config, int argc, char* argv
|
||||||
PyRun_InteractiveLoop(stdin, "<stdin>");
|
PyRun_InteractiveLoop(stdin, "<stdin>");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run the game engine after script execution
|
||||||
|
engine->run();
|
||||||
|
|
||||||
Py_Finalize();
|
Py_Finalize();
|
||||||
delete engine;
|
delete engine;
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -0,0 +1,165 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Animation System Demo - Shows all animation capabilities"""
|
||||||
|
|
||||||
|
import mcrfpy
|
||||||
|
import math
|
||||||
|
|
||||||
|
# Create main scene
|
||||||
|
mcrfpy.createScene("animation_demo")
|
||||||
|
ui = mcrfpy.sceneUI("animation_demo")
|
||||||
|
mcrfpy.setScene("animation_demo")
|
||||||
|
|
||||||
|
# Title
|
||||||
|
title = mcrfpy.Caption((400, 30), "McRogueFace Animation System Demo", mcrfpy.default_font)
|
||||||
|
title.size = 24
|
||||||
|
title.fill_color = (255, 255, 255)
|
||||||
|
# Note: centered property doesn't exist for Caption
|
||||||
|
ui.append(title)
|
||||||
|
|
||||||
|
# 1. Position Animation Demo
|
||||||
|
pos_frame = mcrfpy.Frame(50, 100, 80, 80)
|
||||||
|
pos_frame.fill_color = (255, 100, 100)
|
||||||
|
pos_frame.outline = 2
|
||||||
|
ui.append(pos_frame)
|
||||||
|
|
||||||
|
pos_label = mcrfpy.Caption((50, 80), "Position Animation", mcrfpy.default_font)
|
||||||
|
pos_label.fill_color = (200, 200, 200)
|
||||||
|
ui.append(pos_label)
|
||||||
|
|
||||||
|
# 2. Size Animation Demo
|
||||||
|
size_frame = mcrfpy.Frame(200, 100, 50, 50)
|
||||||
|
size_frame.fill_color = (100, 255, 100)
|
||||||
|
size_frame.outline = 2
|
||||||
|
ui.append(size_frame)
|
||||||
|
|
||||||
|
size_label = mcrfpy.Caption((200, 80), "Size Animation", mcrfpy.default_font)
|
||||||
|
size_label.fill_color = (200, 200, 200)
|
||||||
|
ui.append(size_label)
|
||||||
|
|
||||||
|
# 3. Color Animation Demo
|
||||||
|
color_frame = mcrfpy.Frame(350, 100, 80, 80)
|
||||||
|
color_frame.fill_color = (255, 0, 0)
|
||||||
|
ui.append(color_frame)
|
||||||
|
|
||||||
|
color_label = mcrfpy.Caption((350, 80), "Color Animation", mcrfpy.default_font)
|
||||||
|
color_label.fill_color = (200, 200, 200)
|
||||||
|
ui.append(color_label)
|
||||||
|
|
||||||
|
# 4. Easing Functions Demo
|
||||||
|
easing_y = 250
|
||||||
|
easing_frames = []
|
||||||
|
easings = ["linear", "easeIn", "easeOut", "easeInOut", "easeInElastic", "easeOutBounce"]
|
||||||
|
|
||||||
|
for i, easing in enumerate(easings):
|
||||||
|
x = 50 + i * 120
|
||||||
|
|
||||||
|
frame = mcrfpy.Frame(x, easing_y, 20, 20)
|
||||||
|
frame.fill_color = (100, 150, 255)
|
||||||
|
ui.append(frame)
|
||||||
|
easing_frames.append((frame, easing))
|
||||||
|
|
||||||
|
label = mcrfpy.Caption((x, easing_y - 20), easing, mcrfpy.default_font)
|
||||||
|
label.size = 12
|
||||||
|
label.fill_color = (200, 200, 200)
|
||||||
|
ui.append(label)
|
||||||
|
|
||||||
|
# 5. Complex Animation Demo
|
||||||
|
complex_frame = mcrfpy.Frame(300, 350, 100, 100)
|
||||||
|
complex_frame.fill_color = (128, 128, 255)
|
||||||
|
complex_frame.outline = 3
|
||||||
|
ui.append(complex_frame)
|
||||||
|
|
||||||
|
complex_label = mcrfpy.Caption((300, 330), "Complex Multi-Property", mcrfpy.default_font)
|
||||||
|
complex_label.fill_color = (200, 200, 200)
|
||||||
|
ui.append(complex_label)
|
||||||
|
|
||||||
|
# Start animations
|
||||||
|
def start_animations(runtime):
|
||||||
|
# 1. Position animation - back and forth
|
||||||
|
x_anim = mcrfpy.Animation("x", 500.0, 3.0, "easeInOut")
|
||||||
|
x_anim.start(pos_frame)
|
||||||
|
|
||||||
|
# 2. Size animation - pulsing
|
||||||
|
w_anim = mcrfpy.Animation("w", 150.0, 2.0, "easeInOut")
|
||||||
|
h_anim = mcrfpy.Animation("h", 150.0, 2.0, "easeInOut")
|
||||||
|
w_anim.start(size_frame)
|
||||||
|
h_anim.start(size_frame)
|
||||||
|
|
||||||
|
# 3. Color animation - rainbow cycle
|
||||||
|
color_anim = mcrfpy.Animation("fill_color", (0, 255, 255, 255), 2.0, "linear")
|
||||||
|
color_anim.start(color_frame)
|
||||||
|
|
||||||
|
# 4. Easing demos - all move up with different easings
|
||||||
|
for frame, easing in easing_frames:
|
||||||
|
y_anim = mcrfpy.Animation("y", 150.0, 2.0, easing)
|
||||||
|
y_anim.start(frame)
|
||||||
|
|
||||||
|
# 5. Complex animation - multiple properties
|
||||||
|
cx_anim = mcrfpy.Animation("x", 500.0, 4.0, "easeInOut")
|
||||||
|
cy_anim = mcrfpy.Animation("y", 400.0, 4.0, "easeOut")
|
||||||
|
cw_anim = mcrfpy.Animation("w", 150.0, 4.0, "easeInElastic")
|
||||||
|
ch_anim = mcrfpy.Animation("h", 150.0, 4.0, "easeInElastic")
|
||||||
|
outline_anim = mcrfpy.Animation("outline", 10.0, 4.0, "linear")
|
||||||
|
|
||||||
|
cx_anim.start(complex_frame)
|
||||||
|
cy_anim.start(complex_frame)
|
||||||
|
cw_anim.start(complex_frame)
|
||||||
|
ch_anim.start(complex_frame)
|
||||||
|
outline_anim.start(complex_frame)
|
||||||
|
|
||||||
|
# Individual color component animations
|
||||||
|
r_anim = mcrfpy.Animation("fill_color.r", 255.0, 4.0, "easeInOut")
|
||||||
|
g_anim = mcrfpy.Animation("fill_color.g", 100.0, 4.0, "easeInOut")
|
||||||
|
b_anim = mcrfpy.Animation("fill_color.b", 50.0, 4.0, "easeInOut")
|
||||||
|
|
||||||
|
r_anim.start(complex_frame)
|
||||||
|
g_anim.start(complex_frame)
|
||||||
|
b_anim.start(complex_frame)
|
||||||
|
|
||||||
|
print("All animations started!")
|
||||||
|
|
||||||
|
# Reverse some animations
|
||||||
|
def reverse_animations(runtime):
|
||||||
|
# Position back
|
||||||
|
x_anim = mcrfpy.Animation("x", 50.0, 3.0, "easeInOut")
|
||||||
|
x_anim.start(pos_frame)
|
||||||
|
|
||||||
|
# Size back
|
||||||
|
w_anim = mcrfpy.Animation("w", 50.0, 2.0, "easeInOut")
|
||||||
|
h_anim = mcrfpy.Animation("h", 50.0, 2.0, "easeInOut")
|
||||||
|
w_anim.start(size_frame)
|
||||||
|
h_anim.start(size_frame)
|
||||||
|
|
||||||
|
# Color cycle continues
|
||||||
|
color_anim = mcrfpy.Animation("fill_color", (255, 0, 255, 255), 2.0, "linear")
|
||||||
|
color_anim.start(color_frame)
|
||||||
|
|
||||||
|
# Easing frames back down
|
||||||
|
for frame, easing in easing_frames:
|
||||||
|
y_anim = mcrfpy.Animation("y", 250.0, 2.0, easing)
|
||||||
|
y_anim.start(frame)
|
||||||
|
|
||||||
|
# Continue color cycle
|
||||||
|
def cycle_colors(runtime):
|
||||||
|
color_anim = mcrfpy.Animation("fill_color", (255, 255, 0, 255), 2.0, "linear")
|
||||||
|
color_anim.start(color_frame)
|
||||||
|
|
||||||
|
# Info text
|
||||||
|
info = mcrfpy.Caption((400, 550), "Watch as different properties animate with various easing functions!", mcrfpy.default_font)
|
||||||
|
info.fill_color = (255, 255, 200)
|
||||||
|
# Note: centered property doesn't exist for Caption
|
||||||
|
ui.append(info)
|
||||||
|
|
||||||
|
# Schedule animations
|
||||||
|
mcrfpy.setTimer("start", start_animations, 500)
|
||||||
|
mcrfpy.setTimer("reverse", reverse_animations, 4000)
|
||||||
|
mcrfpy.setTimer("cycle", cycle_colors, 2500)
|
||||||
|
|
||||||
|
# Exit handler
|
||||||
|
def on_key(key):
|
||||||
|
if key == "Escape":
|
||||||
|
mcrfpy.exit()
|
||||||
|
|
||||||
|
mcrfpy.keypressScene(on_key)
|
||||||
|
|
||||||
|
print("Animation demo started! Press Escape to exit.")
|
|
@ -0,0 +1,129 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Generate caption documentation screenshot with proper font"""
|
||||||
|
|
||||||
|
import mcrfpy
|
||||||
|
from mcrfpy import automation
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def capture_caption(runtime):
|
||||||
|
"""Capture caption example after render loop starts"""
|
||||||
|
|
||||||
|
# Take screenshot
|
||||||
|
automation.screenshot("mcrogueface.github.io/images/ui_caption_example.png")
|
||||||
|
print("Caption screenshot saved!")
|
||||||
|
|
||||||
|
# Exit after capturing
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
# Create scene
|
||||||
|
mcrfpy.createScene("captions")
|
||||||
|
|
||||||
|
# Title
|
||||||
|
title = mcrfpy.Caption(400, 30, "Caption Examples")
|
||||||
|
title.font = mcrfpy.default_font
|
||||||
|
title.font_size = 28
|
||||||
|
title.font_color = (255, 255, 255)
|
||||||
|
|
||||||
|
# Different sizes
|
||||||
|
size_label = mcrfpy.Caption(100, 100, "Different Sizes:")
|
||||||
|
size_label.font = mcrfpy.default_font
|
||||||
|
size_label.font_color = (200, 200, 200)
|
||||||
|
|
||||||
|
large = mcrfpy.Caption(300, 100, "Large Text (24pt)")
|
||||||
|
large.font = mcrfpy.default_font
|
||||||
|
large.font_size = 24
|
||||||
|
large.font_color = (255, 255, 255)
|
||||||
|
|
||||||
|
medium = mcrfpy.Caption(300, 140, "Medium Text (18pt)")
|
||||||
|
medium.font = mcrfpy.default_font
|
||||||
|
medium.font_size = 18
|
||||||
|
medium.font_color = (255, 255, 255)
|
||||||
|
|
||||||
|
small = mcrfpy.Caption(300, 170, "Small Text (14pt)")
|
||||||
|
small.font = mcrfpy.default_font
|
||||||
|
small.font_size = 14
|
||||||
|
small.font_color = (255, 255, 255)
|
||||||
|
|
||||||
|
# Different colors
|
||||||
|
color_label = mcrfpy.Caption(100, 230, "Different Colors:")
|
||||||
|
color_label.font = mcrfpy.default_font
|
||||||
|
color_label.font_color = (200, 200, 200)
|
||||||
|
|
||||||
|
white_text = mcrfpy.Caption(300, 230, "White Text")
|
||||||
|
white_text.font = mcrfpy.default_font
|
||||||
|
white_text.font_color = (255, 255, 255)
|
||||||
|
|
||||||
|
green_text = mcrfpy.Caption(300, 260, "Green Text")
|
||||||
|
green_text.font = mcrfpy.default_font
|
||||||
|
green_text.font_color = (100, 255, 100)
|
||||||
|
|
||||||
|
red_text = mcrfpy.Caption(300, 290, "Red Text")
|
||||||
|
red_text.font = mcrfpy.default_font
|
||||||
|
red_text.font_color = (255, 100, 100)
|
||||||
|
|
||||||
|
blue_text = mcrfpy.Caption(300, 320, "Blue Text")
|
||||||
|
blue_text.font = mcrfpy.default_font
|
||||||
|
blue_text.font_color = (100, 150, 255)
|
||||||
|
|
||||||
|
# Caption with background
|
||||||
|
bg_label = mcrfpy.Caption(100, 380, "With Background:")
|
||||||
|
bg_label.font = mcrfpy.default_font
|
||||||
|
bg_label.font_color = (200, 200, 200)
|
||||||
|
|
||||||
|
# Frame background
|
||||||
|
frame = mcrfpy.Frame(280, 370, 250, 50)
|
||||||
|
frame.bgcolor = (64, 64, 128)
|
||||||
|
frame.outline = 2
|
||||||
|
|
||||||
|
framed_text = mcrfpy.Caption(405, 395, "Caption on Frame")
|
||||||
|
framed_text.font = mcrfpy.default_font
|
||||||
|
framed_text.font_size = 18
|
||||||
|
framed_text.font_color = (255, 255, 255)
|
||||||
|
framed_text.centered = True
|
||||||
|
|
||||||
|
# Centered text example
|
||||||
|
center_label = mcrfpy.Caption(100, 460, "Centered Text:")
|
||||||
|
center_label.font = mcrfpy.default_font
|
||||||
|
center_label.font_color = (200, 200, 200)
|
||||||
|
|
||||||
|
centered = mcrfpy.Caption(400, 460, "This text is centered")
|
||||||
|
centered.font = mcrfpy.default_font
|
||||||
|
centered.font_size = 20
|
||||||
|
centered.font_color = (255, 255, 100)
|
||||||
|
centered.centered = True
|
||||||
|
|
||||||
|
# Multi-line example
|
||||||
|
multi_label = mcrfpy.Caption(100, 520, "Multi-line:")
|
||||||
|
multi_label.font = mcrfpy.default_font
|
||||||
|
multi_label.font_color = (200, 200, 200)
|
||||||
|
|
||||||
|
multiline = mcrfpy.Caption(300, 520, "Line 1: McRogueFace\nLine 2: Game Engine\nLine 3: Python API")
|
||||||
|
multiline.font = mcrfpy.default_font
|
||||||
|
multiline.font_size = 14
|
||||||
|
multiline.font_color = (255, 255, 255)
|
||||||
|
|
||||||
|
# Add all to scene
|
||||||
|
ui = mcrfpy.sceneUI("captions")
|
||||||
|
ui.append(title)
|
||||||
|
ui.append(size_label)
|
||||||
|
ui.append(large)
|
||||||
|
ui.append(medium)
|
||||||
|
ui.append(small)
|
||||||
|
ui.append(color_label)
|
||||||
|
ui.append(white_text)
|
||||||
|
ui.append(green_text)
|
||||||
|
ui.append(red_text)
|
||||||
|
ui.append(blue_text)
|
||||||
|
ui.append(bg_label)
|
||||||
|
ui.append(frame)
|
||||||
|
ui.append(framed_text)
|
||||||
|
ui.append(center_label)
|
||||||
|
ui.append(centered)
|
||||||
|
ui.append(multi_label)
|
||||||
|
ui.append(multiline)
|
||||||
|
|
||||||
|
# Switch to scene
|
||||||
|
mcrfpy.setScene("captions")
|
||||||
|
|
||||||
|
# Set timer to capture after rendering starts
|
||||||
|
mcrfpy.setTimer("capture", capture_caption, 100)
|
|
@ -0,0 +1,451 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Generate documentation screenshots for McRogueFace UI elements"""
|
||||||
|
import mcrfpy
|
||||||
|
from mcrfpy import automation
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Crypt of Sokoban color scheme
|
||||||
|
FRAME_COLOR = mcrfpy.Color(64, 64, 128)
|
||||||
|
SHADOW_COLOR = mcrfpy.Color(64, 64, 86)
|
||||||
|
BOX_COLOR = mcrfpy.Color(96, 96, 160)
|
||||||
|
WHITE = mcrfpy.Color(255, 255, 255)
|
||||||
|
BLACK = mcrfpy.Color(0, 0, 0)
|
||||||
|
GREEN = mcrfpy.Color(0, 255, 0)
|
||||||
|
RED = mcrfpy.Color(255, 0, 0)
|
||||||
|
|
||||||
|
# Create texture for sprites
|
||||||
|
sprite_texture = mcrfpy.Texture("assets/kenney_TD_MR_IP.png", 16, 16)
|
||||||
|
|
||||||
|
# Output directory - create it during setup
|
||||||
|
output_dir = "mcrogueface.github.io/images"
|
||||||
|
if not os.path.exists(output_dir):
|
||||||
|
os.makedirs(output_dir)
|
||||||
|
|
||||||
|
def create_caption(x, y, text, font_size=16, text_color=WHITE, outline_color=BLACK):
|
||||||
|
"""Helper function to create captions with common settings"""
|
||||||
|
caption = mcrfpy.Caption(mcrfpy.Vector(x, y), text=text)
|
||||||
|
caption.size = font_size
|
||||||
|
caption.fill_color = text_color
|
||||||
|
caption.outline_color = outline_color
|
||||||
|
return caption
|
||||||
|
|
||||||
|
def create_caption_example():
|
||||||
|
"""Create a scene showing Caption UI element examples"""
|
||||||
|
mcrfpy.createScene("caption_example")
|
||||||
|
ui = mcrfpy.sceneUI("caption_example")
|
||||||
|
|
||||||
|
# Background frame
|
||||||
|
bg = mcrfpy.Frame(0, 0, 800, 600, fill_color=FRAME_COLOR)
|
||||||
|
ui.append(bg)
|
||||||
|
|
||||||
|
# Title caption
|
||||||
|
title = create_caption(200, 50, "Caption Examples", 32)
|
||||||
|
ui.append(title)
|
||||||
|
|
||||||
|
# Different sized captions
|
||||||
|
caption1 = create_caption(100, 150, "Large Caption (24pt)", 24)
|
||||||
|
ui.append(caption1)
|
||||||
|
|
||||||
|
caption2 = create_caption(100, 200, "Medium Caption (18pt)", 18, GREEN)
|
||||||
|
ui.append(caption2)
|
||||||
|
|
||||||
|
caption3 = create_caption(100, 240, "Small Caption (14pt)", 14, RED)
|
||||||
|
ui.append(caption3)
|
||||||
|
|
||||||
|
# Caption with background
|
||||||
|
caption_bg = mcrfpy.Frame(100, 300, 300, 50, fill_color=BOX_COLOR)
|
||||||
|
ui.append(caption_bg)
|
||||||
|
caption4 = create_caption(110, 315, "Caption with Background", 16)
|
||||||
|
ui.append(caption4)
|
||||||
|
|
||||||
|
def create_sprite_example():
|
||||||
|
"""Create a scene showing Sprite UI element examples"""
|
||||||
|
mcrfpy.createScene("sprite_example")
|
||||||
|
ui = mcrfpy.sceneUI("sprite_example")
|
||||||
|
|
||||||
|
# Background frame
|
||||||
|
bg = mcrfpy.Frame(0, 0, 800, 600, fill_color=FRAME_COLOR)
|
||||||
|
ui.append(bg)
|
||||||
|
|
||||||
|
# Title
|
||||||
|
title = create_caption(250, 50, "Sprite Examples", 32)
|
||||||
|
ui.append(title)
|
||||||
|
|
||||||
|
# Create a grid background for sprites
|
||||||
|
sprite_bg = mcrfpy.Frame(100, 150, 600, 300, fill_color=BOX_COLOR)
|
||||||
|
ui.append(sprite_bg)
|
||||||
|
|
||||||
|
# Player sprite (84)
|
||||||
|
player_label = create_caption(150, 180, "Player", 14)
|
||||||
|
ui.append(player_label)
|
||||||
|
player_sprite = mcrfpy.Sprite(150, 200, sprite_texture, 84, 3.0)
|
||||||
|
ui.append(player_sprite)
|
||||||
|
|
||||||
|
# Enemy sprites
|
||||||
|
enemy_label = create_caption(250, 180, "Enemies", 14)
|
||||||
|
ui.append(enemy_label)
|
||||||
|
enemy1 = mcrfpy.Sprite(250, 200, sprite_texture, 123, 3.0) # Basic enemy
|
||||||
|
ui.append(enemy1)
|
||||||
|
enemy2 = mcrfpy.Sprite(300, 200, sprite_texture, 107, 3.0) # Different enemy
|
||||||
|
ui.append(enemy2)
|
||||||
|
|
||||||
|
# Boulder sprite (66)
|
||||||
|
boulder_label = create_caption(400, 180, "Boulder", 14)
|
||||||
|
ui.append(boulder_label)
|
||||||
|
boulder_sprite = mcrfpy.Sprite(400, 200, sprite_texture, 66, 3.0)
|
||||||
|
ui.append(boulder_sprite)
|
||||||
|
|
||||||
|
# Exit sprites
|
||||||
|
exit_label = create_caption(500, 180, "Exit States", 14)
|
||||||
|
ui.append(exit_label)
|
||||||
|
exit_locked = mcrfpy.Sprite(500, 200, sprite_texture, 45, 3.0) # Locked
|
||||||
|
ui.append(exit_locked)
|
||||||
|
exit_open = mcrfpy.Sprite(550, 200, sprite_texture, 21, 3.0) # Open
|
||||||
|
ui.append(exit_open)
|
||||||
|
|
||||||
|
# Item sprites
|
||||||
|
item_label = create_caption(150, 300, "Items", 14)
|
||||||
|
ui.append(item_label)
|
||||||
|
treasure = mcrfpy.Sprite(150, 320, sprite_texture, 89, 3.0) # Treasure
|
||||||
|
ui.append(treasure)
|
||||||
|
sword = mcrfpy.Sprite(200, 320, sprite_texture, 222, 3.0) # Sword
|
||||||
|
ui.append(sword)
|
||||||
|
potion = mcrfpy.Sprite(250, 320, sprite_texture, 175, 3.0) # Potion
|
||||||
|
ui.append(potion)
|
||||||
|
|
||||||
|
# Button sprite
|
||||||
|
button_label = create_caption(350, 300, "Button", 14)
|
||||||
|
ui.append(button_label)
|
||||||
|
button = mcrfpy.Sprite(350, 320, sprite_texture, 250, 3.0)
|
||||||
|
ui.append(button)
|
||||||
|
|
||||||
|
def create_frame_example():
|
||||||
|
"""Create a scene showing Frame UI element examples"""
|
||||||
|
mcrfpy.createScene("frame_example")
|
||||||
|
ui = mcrfpy.sceneUI("frame_example")
|
||||||
|
|
||||||
|
# Background
|
||||||
|
bg = mcrfpy.Frame(0, 0, 800, 600, fill_color=SHADOW_COLOR)
|
||||||
|
ui.append(bg)
|
||||||
|
|
||||||
|
# Title
|
||||||
|
title = create_caption(250, 30, "Frame Examples", 32)
|
||||||
|
ui.append(title)
|
||||||
|
|
||||||
|
# Basic frame
|
||||||
|
frame1 = mcrfpy.Frame(50, 100, 200, 150, fill_color=FRAME_COLOR)
|
||||||
|
ui.append(frame1)
|
||||||
|
label1 = create_caption(60, 110, "Basic Frame", 16)
|
||||||
|
ui.append(label1)
|
||||||
|
|
||||||
|
# Frame with outline
|
||||||
|
frame2 = mcrfpy.Frame(300, 100, 200, 150, fill_color=BOX_COLOR,
|
||||||
|
outline_color=WHITE, outline=2.0)
|
||||||
|
ui.append(frame2)
|
||||||
|
label2 = create_caption(310, 110, "Frame with Outline", 16)
|
||||||
|
ui.append(label2)
|
||||||
|
|
||||||
|
# Nested frames
|
||||||
|
frame3 = mcrfpy.Frame(550, 100, 200, 150, fill_color=FRAME_COLOR,
|
||||||
|
outline_color=WHITE, outline=1)
|
||||||
|
ui.append(frame3)
|
||||||
|
inner_frame = mcrfpy.Frame(570, 130, 160, 90, fill_color=BOX_COLOR)
|
||||||
|
ui.append(inner_frame)
|
||||||
|
label3 = create_caption(560, 110, "Nested Frames", 16)
|
||||||
|
ui.append(label3)
|
||||||
|
|
||||||
|
# Complex layout with frames
|
||||||
|
main_frame = mcrfpy.Frame(50, 300, 700, 250, fill_color=FRAME_COLOR,
|
||||||
|
outline_color=WHITE, outline=2)
|
||||||
|
ui.append(main_frame)
|
||||||
|
|
||||||
|
# Add some UI elements inside
|
||||||
|
ui_label = create_caption(60, 310, "Complex UI Layout", 18)
|
||||||
|
ui.append(ui_label)
|
||||||
|
|
||||||
|
# Status panel
|
||||||
|
status_frame = mcrfpy.Frame(70, 350, 150, 180, fill_color=BOX_COLOR)
|
||||||
|
ui.append(status_frame)
|
||||||
|
status_label = create_caption(80, 360, "Status", 14)
|
||||||
|
ui.append(status_label)
|
||||||
|
|
||||||
|
# Inventory panel
|
||||||
|
inv_frame = mcrfpy.Frame(240, 350, 300, 180, fill_color=BOX_COLOR)
|
||||||
|
ui.append(inv_frame)
|
||||||
|
inv_label = create_caption(250, 360, "Inventory", 14)
|
||||||
|
ui.append(inv_label)
|
||||||
|
|
||||||
|
# Actions panel
|
||||||
|
action_frame = mcrfpy.Frame(560, 350, 170, 180, fill_color=BOX_COLOR)
|
||||||
|
ui.append(action_frame)
|
||||||
|
action_label = create_caption(570, 360, "Actions", 14)
|
||||||
|
ui.append(action_label)
|
||||||
|
|
||||||
|
def create_grid_example():
|
||||||
|
"""Create a scene showing Grid UI element examples"""
|
||||||
|
mcrfpy.createScene("grid_example")
|
||||||
|
ui = mcrfpy.sceneUI("grid_example")
|
||||||
|
|
||||||
|
# Background
|
||||||
|
bg = mcrfpy.Frame(0, 0, 800, 600, fill_color=FRAME_COLOR)
|
||||||
|
ui.append(bg)
|
||||||
|
|
||||||
|
# Title
|
||||||
|
title = create_caption(250, 30, "Grid Example", 32)
|
||||||
|
ui.append(title)
|
||||||
|
|
||||||
|
# Create a grid showing a small dungeon
|
||||||
|
grid = mcrfpy.Grid(20, 15, sprite_texture,
|
||||||
|
mcrfpy.Vector(100, 100), mcrfpy.Vector(320, 240))
|
||||||
|
|
||||||
|
# Set up dungeon tiles
|
||||||
|
# Floor tiles (index 48)
|
||||||
|
# Wall tiles (index 3)
|
||||||
|
for x in range(20):
|
||||||
|
for y in range(15):
|
||||||
|
if x == 0 or x == 19 or y == 0 or y == 14:
|
||||||
|
# Walls around edge
|
||||||
|
grid.at((x, y)).tilesprite = 3
|
||||||
|
grid.at((x, y)).walkable = False
|
||||||
|
else:
|
||||||
|
# Floor
|
||||||
|
grid.at((x, y)).tilesprite = 48
|
||||||
|
grid.at((x, y)).walkable = True
|
||||||
|
|
||||||
|
# Add some internal walls
|
||||||
|
for x in range(5, 15):
|
||||||
|
grid.at((x, 7)).tilesprite = 3
|
||||||
|
grid.at((x, 7)).walkable = False
|
||||||
|
for y in range(3, 8):
|
||||||
|
grid.at((10, y)).tilesprite = 3
|
||||||
|
grid.at((10, y)).walkable = False
|
||||||
|
|
||||||
|
# Add a door
|
||||||
|
grid.at((10, 7)).tilesprite = 131 # Door tile
|
||||||
|
grid.at((10, 7)).walkable = True
|
||||||
|
|
||||||
|
# Add to UI
|
||||||
|
ui.append(grid)
|
||||||
|
|
||||||
|
# Label
|
||||||
|
grid_label = create_caption(100, 480, "20x15 Grid with 2x scale - Simple Dungeon Layout", 16)
|
||||||
|
ui.append(grid_label)
|
||||||
|
|
||||||
|
def create_entity_example():
|
||||||
|
"""Create a scene showing Entity examples in a Grid"""
|
||||||
|
mcrfpy.createScene("entity_example")
|
||||||
|
ui = mcrfpy.sceneUI("entity_example")
|
||||||
|
|
||||||
|
# Background
|
||||||
|
bg = mcrfpy.Frame(0, 0, 800, 600, fill_color=FRAME_COLOR)
|
||||||
|
ui.append(bg)
|
||||||
|
|
||||||
|
# Title
|
||||||
|
title = create_caption(200, 30, "Entity Collection Example", 32)
|
||||||
|
ui.append(title)
|
||||||
|
|
||||||
|
# Create a grid for the entities
|
||||||
|
grid = mcrfpy.Grid(15, 10, sprite_texture,
|
||||||
|
mcrfpy.Vector(150, 100), mcrfpy.Vector(360, 240))
|
||||||
|
|
||||||
|
# Set all tiles to floor
|
||||||
|
for x in range(15):
|
||||||
|
for y in range(10):
|
||||||
|
grid.at((x, y)).tilesprite = 48
|
||||||
|
grid.at((x, y)).walkable = True
|
||||||
|
|
||||||
|
# Add walls
|
||||||
|
for x in range(15):
|
||||||
|
grid.at((x, 0)).tilesprite = 3
|
||||||
|
grid.at((x, 0)).walkable = False
|
||||||
|
grid.at((x, 9)).tilesprite = 3
|
||||||
|
grid.at((x, 9)).walkable = False
|
||||||
|
for y in range(10):
|
||||||
|
grid.at((0, y)).tilesprite = 3
|
||||||
|
grid.at((0, y)).walkable = False
|
||||||
|
grid.at((14, y)).tilesprite = 3
|
||||||
|
grid.at((14, y)).walkable = False
|
||||||
|
|
||||||
|
ui.append(grid)
|
||||||
|
|
||||||
|
# Add entities to the grid
|
||||||
|
# Player entity
|
||||||
|
player = mcrfpy.Entity(mcrfpy.Vector(3, 3), sprite_texture, 84, grid)
|
||||||
|
grid.entities.append(player)
|
||||||
|
|
||||||
|
# Enemy entities
|
||||||
|
enemy1 = mcrfpy.Entity(mcrfpy.Vector(7, 4), sprite_texture, 123, grid)
|
||||||
|
grid.entities.append(enemy1)
|
||||||
|
|
||||||
|
enemy2 = mcrfpy.Entity(mcrfpy.Vector(10, 6), sprite_texture, 107, grid)
|
||||||
|
grid.entities.append(enemy2)
|
||||||
|
|
||||||
|
# Boulder
|
||||||
|
boulder = mcrfpy.Entity(mcrfpy.Vector(5, 5), sprite_texture, 66, grid)
|
||||||
|
grid.entities.append(boulder)
|
||||||
|
|
||||||
|
# Treasure
|
||||||
|
treasure = mcrfpy.Entity(mcrfpy.Vector(12, 2), sprite_texture, 89, grid)
|
||||||
|
grid.entities.append(treasure)
|
||||||
|
|
||||||
|
# Exit (locked)
|
||||||
|
exit_door = mcrfpy.Entity(mcrfpy.Vector(12, 8), sprite_texture, 45, grid)
|
||||||
|
grid.entities.append(exit_door)
|
||||||
|
|
||||||
|
# Button
|
||||||
|
button = mcrfpy.Entity(mcrfpy.Vector(3, 7), sprite_texture, 250, grid)
|
||||||
|
grid.entities.append(button)
|
||||||
|
|
||||||
|
# Items
|
||||||
|
sword = mcrfpy.Entity(mcrfpy.Vector(8, 2), sprite_texture, 222, grid)
|
||||||
|
grid.entities.append(sword)
|
||||||
|
|
||||||
|
potion = mcrfpy.Entity(mcrfpy.Vector(6, 8), sprite_texture, 175, grid)
|
||||||
|
grid.entities.append(potion)
|
||||||
|
|
||||||
|
# Label
|
||||||
|
entity_label = create_caption(150, 500, "Grid with Entity Collection - Game Objects", 16)
|
||||||
|
ui.append(entity_label)
|
||||||
|
|
||||||
|
def create_combined_example():
|
||||||
|
"""Create a scene showing all UI elements combined"""
|
||||||
|
mcrfpy.createScene("combined_example")
|
||||||
|
ui = mcrfpy.sceneUI("combined_example")
|
||||||
|
|
||||||
|
# Background
|
||||||
|
bg = mcrfpy.Frame(0, 0, 800, 600, fill_color=SHADOW_COLOR)
|
||||||
|
ui.append(bg)
|
||||||
|
|
||||||
|
# Title
|
||||||
|
title = create_caption(200, 20, "McRogueFace UI Elements", 28)
|
||||||
|
ui.append(title)
|
||||||
|
|
||||||
|
# Main game area frame
|
||||||
|
game_frame = mcrfpy.Frame(20, 70, 500, 400, fill_color=FRAME_COLOR,
|
||||||
|
outline_color=WHITE, outline=2)
|
||||||
|
ui.append(game_frame)
|
||||||
|
|
||||||
|
# Grid inside game frame
|
||||||
|
grid = mcrfpy.Grid(12, 10, sprite_texture,
|
||||||
|
mcrfpy.Vector(30, 80), mcrfpy.Vector(480, 400))
|
||||||
|
for x in range(12):
|
||||||
|
for y in range(10):
|
||||||
|
if x == 0 or x == 11 or y == 0 or y == 9:
|
||||||
|
grid.at((x, y)).tilesprite = 3
|
||||||
|
grid.at((x, y)).walkable = False
|
||||||
|
else:
|
||||||
|
grid.at((x, y)).tilesprite = 48
|
||||||
|
grid.at((x, y)).walkable = True
|
||||||
|
|
||||||
|
# Add some entities
|
||||||
|
player = mcrfpy.Entity(mcrfpy.Vector(2, 2), sprite_texture, 84, grid)
|
||||||
|
grid.entities.append(player)
|
||||||
|
enemy = mcrfpy.Entity(mcrfpy.Vector(8, 6), sprite_texture, 123, grid)
|
||||||
|
grid.entities.append(enemy)
|
||||||
|
boulder = mcrfpy.Entity(mcrfpy.Vector(5, 4), sprite_texture, 66, grid)
|
||||||
|
grid.entities.append(boulder)
|
||||||
|
|
||||||
|
ui.append(grid)
|
||||||
|
|
||||||
|
# Status panel
|
||||||
|
status_frame = mcrfpy.Frame(540, 70, 240, 200, fill_color=BOX_COLOR,
|
||||||
|
outline_color=WHITE, outline=1)
|
||||||
|
ui.append(status_frame)
|
||||||
|
|
||||||
|
status_title = create_caption(550, 80, "Status", 20)
|
||||||
|
ui.append(status_title)
|
||||||
|
|
||||||
|
hp_label = create_caption(550, 120, "HP: 10/10", 16, GREEN)
|
||||||
|
ui.append(hp_label)
|
||||||
|
|
||||||
|
level_label = create_caption(550, 150, "Level: 1", 16)
|
||||||
|
ui.append(level_label)
|
||||||
|
|
||||||
|
# Inventory panel
|
||||||
|
inv_frame = mcrfpy.Frame(540, 290, 240, 180, fill_color=BOX_COLOR,
|
||||||
|
outline_color=WHITE, outline=1)
|
||||||
|
ui.append(inv_frame)
|
||||||
|
|
||||||
|
inv_title = create_caption(550, 300, "Inventory", 20)
|
||||||
|
ui.append(inv_title)
|
||||||
|
|
||||||
|
# Add some item sprites
|
||||||
|
item1 = mcrfpy.Sprite(560, 340, sprite_texture, 222, 2.0)
|
||||||
|
ui.append(item1)
|
||||||
|
item2 = mcrfpy.Sprite(610, 340, sprite_texture, 175, 2.0)
|
||||||
|
ui.append(item2)
|
||||||
|
|
||||||
|
# Message log
|
||||||
|
log_frame = mcrfpy.Frame(20, 490, 760, 90, fill_color=BOX_COLOR,
|
||||||
|
outline_color=WHITE, outline=1)
|
||||||
|
ui.append(log_frame)
|
||||||
|
|
||||||
|
log_msg = create_caption(30, 500, "Welcome to McRogueFace!", 14)
|
||||||
|
ui.append(log_msg)
|
||||||
|
|
||||||
|
# Set up all the scenes
|
||||||
|
print("Creating UI example scenes...")
|
||||||
|
create_caption_example()
|
||||||
|
create_sprite_example()
|
||||||
|
create_frame_example()
|
||||||
|
create_grid_example()
|
||||||
|
create_entity_example()
|
||||||
|
create_combined_example()
|
||||||
|
|
||||||
|
# Screenshot state
|
||||||
|
current_screenshot = 0
|
||||||
|
screenshots = [
|
||||||
|
("caption_example", "ui_caption_example.png"),
|
||||||
|
("sprite_example", "ui_sprite_example.png"),
|
||||||
|
("frame_example", "ui_frame_example.png"),
|
||||||
|
("grid_example", "ui_grid_example.png"),
|
||||||
|
("entity_example", "ui_entity_example.png"),
|
||||||
|
("combined_example", "ui_combined_example.png")
|
||||||
|
]
|
||||||
|
|
||||||
|
def take_screenshots(runtime):
|
||||||
|
"""Timer callback to take screenshots sequentially"""
|
||||||
|
global current_screenshot
|
||||||
|
|
||||||
|
if current_screenshot >= len(screenshots):
|
||||||
|
print("\nAll screenshots captured successfully!")
|
||||||
|
print(f"Screenshots saved to: {output_dir}/")
|
||||||
|
mcrfpy.exit()
|
||||||
|
return
|
||||||
|
|
||||||
|
scene_name, filename = screenshots[current_screenshot]
|
||||||
|
|
||||||
|
# Switch to the scene
|
||||||
|
mcrfpy.setScene(scene_name)
|
||||||
|
|
||||||
|
# Take screenshot after a short delay to ensure rendering
|
||||||
|
def capture():
|
||||||
|
global current_screenshot
|
||||||
|
full_path = f"{output_dir}/{filename}"
|
||||||
|
result = automation.screenshot(full_path)
|
||||||
|
print(f"Screenshot {current_screenshot + 1}/{len(screenshots)}: {filename} - {'Success' if result else 'Failed'}")
|
||||||
|
|
||||||
|
current_screenshot += 1
|
||||||
|
|
||||||
|
# Schedule next screenshot
|
||||||
|
mcrfpy.setTimer("next_screenshot", take_screenshots, 200)
|
||||||
|
|
||||||
|
# Give scene time to render
|
||||||
|
mcrfpy.setTimer("capture", lambda r: capture(), 100)
|
||||||
|
|
||||||
|
# Start with the first scene
|
||||||
|
mcrfpy.setScene("caption_example")
|
||||||
|
|
||||||
|
# Start the screenshot process
|
||||||
|
print(f"\nStarting screenshot capture of {len(screenshots)} scenes...")
|
||||||
|
mcrfpy.setTimer("start", take_screenshots, 500)
|
||||||
|
|
||||||
|
# Safety timeout
|
||||||
|
def safety_exit(runtime):
|
||||||
|
print("\nERROR: Safety timeout reached! Exiting...")
|
||||||
|
mcrfpy.exit()
|
||||||
|
|
||||||
|
mcrfpy.setTimer("safety", safety_exit, 30000)
|
||||||
|
|
||||||
|
print("Setup complete. Game loop starting...")
|
|
@ -0,0 +1,217 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Generate documentation screenshots for McRogueFace UI elements - Simple version"""
|
||||||
|
import mcrfpy
|
||||||
|
from mcrfpy import automation
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Crypt of Sokoban color scheme
|
||||||
|
FRAME_COLOR = mcrfpy.Color(64, 64, 128)
|
||||||
|
SHADOW_COLOR = mcrfpy.Color(64, 64, 86)
|
||||||
|
BOX_COLOR = mcrfpy.Color(96, 96, 160)
|
||||||
|
WHITE = mcrfpy.Color(255, 255, 255)
|
||||||
|
BLACK = mcrfpy.Color(0, 0, 0)
|
||||||
|
GREEN = mcrfpy.Color(0, 255, 0)
|
||||||
|
RED = mcrfpy.Color(255, 0, 0)
|
||||||
|
|
||||||
|
# Create texture for sprites
|
||||||
|
sprite_texture = mcrfpy.Texture("assets/kenney_TD_MR_IP.png", 16, 16)
|
||||||
|
|
||||||
|
# Output directory
|
||||||
|
output_dir = "mcrogueface.github.io/images"
|
||||||
|
if not os.path.exists(output_dir):
|
||||||
|
os.makedirs(output_dir)
|
||||||
|
|
||||||
|
def create_caption(x, y, text, font_size=16, text_color=WHITE, outline_color=BLACK):
|
||||||
|
"""Helper function to create captions with common settings"""
|
||||||
|
caption = mcrfpy.Caption(mcrfpy.Vector(x, y), text=text)
|
||||||
|
caption.size = font_size
|
||||||
|
caption.fill_color = text_color
|
||||||
|
caption.outline_color = outline_color
|
||||||
|
return caption
|
||||||
|
|
||||||
|
# Screenshot counter
|
||||||
|
screenshot_count = 0
|
||||||
|
total_screenshots = 4
|
||||||
|
|
||||||
|
def screenshot_and_continue(runtime):
|
||||||
|
"""Take a screenshot and move to the next scene"""
|
||||||
|
global screenshot_count
|
||||||
|
|
||||||
|
if screenshot_count == 0:
|
||||||
|
# Caption example
|
||||||
|
print("Creating Caption example...")
|
||||||
|
mcrfpy.createScene("caption_example")
|
||||||
|
ui = mcrfpy.sceneUI("caption_example")
|
||||||
|
|
||||||
|
bg = mcrfpy.Frame(0, 0, 800, 600, fill_color=FRAME_COLOR)
|
||||||
|
ui.append(bg)
|
||||||
|
|
||||||
|
title = create_caption(200, 50, "Caption Examples", 32)
|
||||||
|
ui.append(title)
|
||||||
|
|
||||||
|
caption1 = create_caption(100, 150, "Large Caption (24pt)", 24)
|
||||||
|
ui.append(caption1)
|
||||||
|
|
||||||
|
caption2 = create_caption(100, 200, "Medium Caption (18pt)", 18, GREEN)
|
||||||
|
ui.append(caption2)
|
||||||
|
|
||||||
|
caption3 = create_caption(100, 240, "Small Caption (14pt)", 14, RED)
|
||||||
|
ui.append(caption3)
|
||||||
|
|
||||||
|
caption_bg = mcrfpy.Frame(100, 300, 300, 50, fill_color=BOX_COLOR)
|
||||||
|
ui.append(caption_bg)
|
||||||
|
caption4 = create_caption(110, 315, "Caption with Background", 16)
|
||||||
|
ui.append(caption4)
|
||||||
|
|
||||||
|
mcrfpy.setScene("caption_example")
|
||||||
|
mcrfpy.setTimer("next1", lambda r: capture_screenshot("ui_caption_example.png"), 200)
|
||||||
|
|
||||||
|
elif screenshot_count == 1:
|
||||||
|
# Sprite example
|
||||||
|
print("Creating Sprite example...")
|
||||||
|
mcrfpy.createScene("sprite_example")
|
||||||
|
ui = mcrfpy.sceneUI("sprite_example")
|
||||||
|
|
||||||
|
bg = mcrfpy.Frame(0, 0, 800, 600, fill_color=FRAME_COLOR)
|
||||||
|
ui.append(bg)
|
||||||
|
|
||||||
|
title = create_caption(250, 50, "Sprite Examples", 32)
|
||||||
|
ui.append(title)
|
||||||
|
|
||||||
|
sprite_bg = mcrfpy.Frame(100, 150, 600, 300, fill_color=BOX_COLOR)
|
||||||
|
ui.append(sprite_bg)
|
||||||
|
|
||||||
|
player_label = create_caption(150, 180, "Player", 14)
|
||||||
|
ui.append(player_label)
|
||||||
|
player_sprite = mcrfpy.Sprite(150, 200, sprite_texture, 84, 3.0)
|
||||||
|
ui.append(player_sprite)
|
||||||
|
|
||||||
|
enemy_label = create_caption(250, 180, "Enemies", 14)
|
||||||
|
ui.append(enemy_label)
|
||||||
|
enemy1 = mcrfpy.Sprite(250, 200, sprite_texture, 123, 3.0)
|
||||||
|
ui.append(enemy1)
|
||||||
|
enemy2 = mcrfpy.Sprite(300, 200, sprite_texture, 107, 3.0)
|
||||||
|
ui.append(enemy2)
|
||||||
|
|
||||||
|
boulder_label = create_caption(400, 180, "Boulder", 14)
|
||||||
|
ui.append(boulder_label)
|
||||||
|
boulder_sprite = mcrfpy.Sprite(400, 200, sprite_texture, 66, 3.0)
|
||||||
|
ui.append(boulder_sprite)
|
||||||
|
|
||||||
|
exit_label = create_caption(500, 180, "Exit States", 14)
|
||||||
|
ui.append(exit_label)
|
||||||
|
exit_locked = mcrfpy.Sprite(500, 200, sprite_texture, 45, 3.0)
|
||||||
|
ui.append(exit_locked)
|
||||||
|
exit_open = mcrfpy.Sprite(550, 200, sprite_texture, 21, 3.0)
|
||||||
|
ui.append(exit_open)
|
||||||
|
|
||||||
|
mcrfpy.setScene("sprite_example")
|
||||||
|
mcrfpy.setTimer("next2", lambda r: capture_screenshot("ui_sprite_example.png"), 200)
|
||||||
|
|
||||||
|
elif screenshot_count == 2:
|
||||||
|
# Frame example
|
||||||
|
print("Creating Frame example...")
|
||||||
|
mcrfpy.createScene("frame_example")
|
||||||
|
ui = mcrfpy.sceneUI("frame_example")
|
||||||
|
|
||||||
|
bg = mcrfpy.Frame(0, 0, 800, 600, fill_color=SHADOW_COLOR)
|
||||||
|
ui.append(bg)
|
||||||
|
|
||||||
|
title = create_caption(250, 30, "Frame Examples", 32)
|
||||||
|
ui.append(title)
|
||||||
|
|
||||||
|
frame1 = mcrfpy.Frame(50, 100, 200, 150, fill_color=FRAME_COLOR)
|
||||||
|
ui.append(frame1)
|
||||||
|
label1 = create_caption(60, 110, "Basic Frame", 16)
|
||||||
|
ui.append(label1)
|
||||||
|
|
||||||
|
frame2 = mcrfpy.Frame(300, 100, 200, 150, fill_color=BOX_COLOR,
|
||||||
|
outline_color=WHITE, outline=2.0)
|
||||||
|
ui.append(frame2)
|
||||||
|
label2 = create_caption(310, 110, "Frame with Outline", 16)
|
||||||
|
ui.append(label2)
|
||||||
|
|
||||||
|
frame3 = mcrfpy.Frame(550, 100, 200, 150, fill_color=FRAME_COLOR,
|
||||||
|
outline_color=WHITE, outline=1)
|
||||||
|
ui.append(frame3)
|
||||||
|
inner_frame = mcrfpy.Frame(570, 130, 160, 90, fill_color=BOX_COLOR)
|
||||||
|
ui.append(inner_frame)
|
||||||
|
label3 = create_caption(560, 110, "Nested Frames", 16)
|
||||||
|
ui.append(label3)
|
||||||
|
|
||||||
|
mcrfpy.setScene("frame_example")
|
||||||
|
mcrfpy.setTimer("next3", lambda r: capture_screenshot("ui_frame_example.png"), 200)
|
||||||
|
|
||||||
|
elif screenshot_count == 3:
|
||||||
|
# Grid example
|
||||||
|
print("Creating Grid example...")
|
||||||
|
mcrfpy.createScene("grid_example")
|
||||||
|
ui = mcrfpy.sceneUI("grid_example")
|
||||||
|
|
||||||
|
bg = mcrfpy.Frame(0, 0, 800, 600, fill_color=FRAME_COLOR)
|
||||||
|
ui.append(bg)
|
||||||
|
|
||||||
|
title = create_caption(250, 30, "Grid Example", 32)
|
||||||
|
ui.append(title)
|
||||||
|
|
||||||
|
grid = mcrfpy.Grid(20, 15, sprite_texture,
|
||||||
|
mcrfpy.Vector(100, 100), mcrfpy.Vector(320, 240))
|
||||||
|
|
||||||
|
# Set up dungeon tiles
|
||||||
|
for x in range(20):
|
||||||
|
for y in range(15):
|
||||||
|
if x == 0 or x == 19 or y == 0 or y == 14:
|
||||||
|
# Walls
|
||||||
|
grid.at((x, y)).tilesprite = 3
|
||||||
|
grid.at((x, y)).walkable = False
|
||||||
|
else:
|
||||||
|
# Floor
|
||||||
|
grid.at((x, y)).tilesprite = 48
|
||||||
|
grid.at((x, y)).walkable = True
|
||||||
|
|
||||||
|
# Add some internal walls
|
||||||
|
for x in range(5, 15):
|
||||||
|
grid.at((x, 7)).tilesprite = 3
|
||||||
|
grid.at((x, 7)).walkable = False
|
||||||
|
for y in range(3, 8):
|
||||||
|
grid.at((10, y)).tilesprite = 3
|
||||||
|
grid.at((10, y)).walkable = False
|
||||||
|
|
||||||
|
# Add a door
|
||||||
|
grid.at((10, 7)).tilesprite = 131
|
||||||
|
grid.at((10, 7)).walkable = True
|
||||||
|
|
||||||
|
ui.append(grid)
|
||||||
|
|
||||||
|
grid_label = create_caption(100, 480, "20x15 Grid - Simple Dungeon Layout", 16)
|
||||||
|
ui.append(grid_label)
|
||||||
|
|
||||||
|
mcrfpy.setScene("grid_example")
|
||||||
|
mcrfpy.setTimer("next4", lambda r: capture_screenshot("ui_grid_example.png"), 200)
|
||||||
|
|
||||||
|
else:
|
||||||
|
print("\nAll screenshots captured successfully!")
|
||||||
|
print(f"Screenshots saved to: {output_dir}/")
|
||||||
|
mcrfpy.exit()
|
||||||
|
return
|
||||||
|
|
||||||
|
def capture_screenshot(filename):
|
||||||
|
"""Capture a screenshot"""
|
||||||
|
global screenshot_count
|
||||||
|
full_path = f"{output_dir}/{filename}"
|
||||||
|
result = automation.screenshot(full_path)
|
||||||
|
print(f"Screenshot {screenshot_count + 1}/{total_screenshots}: {filename} - {'Success' if result else 'Failed'}")
|
||||||
|
screenshot_count += 1
|
||||||
|
|
||||||
|
# Schedule next scene
|
||||||
|
mcrfpy.setTimer("continue", screenshot_and_continue, 300)
|
||||||
|
|
||||||
|
# Start the process
|
||||||
|
print("Starting screenshot generation...")
|
||||||
|
mcrfpy.setTimer("start", screenshot_and_continue, 500)
|
||||||
|
|
||||||
|
# Safety timeout
|
||||||
|
mcrfpy.setTimer("safety", lambda r: mcrfpy.exit(), 30000)
|
||||||
|
|
||||||
|
print("Setup complete. Game loop starting...")
|
|
@ -0,0 +1,144 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Generate entity documentation screenshot with proper font loading"""
|
||||||
|
|
||||||
|
import mcrfpy
|
||||||
|
from mcrfpy import automation
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def capture_entity(runtime):
|
||||||
|
"""Capture entity example after render loop starts"""
|
||||||
|
|
||||||
|
# Take screenshot
|
||||||
|
automation.screenshot("mcrogueface.github.io/images/ui_entity_example.png")
|
||||||
|
print("Entity screenshot saved!")
|
||||||
|
|
||||||
|
# Exit after capturing
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
# Create scene
|
||||||
|
mcrfpy.createScene("entities")
|
||||||
|
|
||||||
|
# Use the default font which is already loaded
|
||||||
|
# Instead of: font = mcrfpy.Font("assets/JetbrainsMono.ttf")
|
||||||
|
# We use: mcrfpy.default_font (which is already loaded by the engine)
|
||||||
|
|
||||||
|
# Title
|
||||||
|
title = mcrfpy.Caption((400, 30), "Entity Example - Roguelike Characters", font=mcrfpy.default_font)
|
||||||
|
#title.font = mcrfpy.default_font
|
||||||
|
#title.font_size = 24
|
||||||
|
title.size=24
|
||||||
|
#title.font_color = (255, 255, 255)
|
||||||
|
#title.text_color = (255,255,255)
|
||||||
|
|
||||||
|
# Create a grid background
|
||||||
|
texture = mcrfpy.Texture("assets/kenney_TD_MR_IP.png", 16, 16)
|
||||||
|
|
||||||
|
# Create grid with entities - using 2x scale (32x32 pixel tiles)
|
||||||
|
#grid = mcrfpy.Grid((100, 100), (20, 15), texture, 16, 16) # I can never get the args right for this thing
|
||||||
|
t = mcrfpy.Texture("assets/kenney_TD_MR_IP.png", 16, 16)
|
||||||
|
grid = mcrfpy.Grid(20, 15, t, (10, 10), (1014, 758))
|
||||||
|
grid.zoom = 2.0
|
||||||
|
#grid.texture = texture
|
||||||
|
|
||||||
|
# Define tile types
|
||||||
|
FLOOR = 58 # Stone floor
|
||||||
|
WALL = 11 # Stone wall
|
||||||
|
|
||||||
|
# Fill with floor
|
||||||
|
for x in range(20):
|
||||||
|
for y in range(15):
|
||||||
|
grid.at((x, y)).tilesprite = WALL
|
||||||
|
|
||||||
|
# Add walls around edges
|
||||||
|
for x in range(20):
|
||||||
|
grid.at((x, 0)).tilesprite = WALL
|
||||||
|
grid.at((x, 14)).tilesprite = WALL
|
||||||
|
for y in range(15):
|
||||||
|
grid.at((0, y)).tilesprite = WALL
|
||||||
|
grid.at((19, y)).tilesprite = WALL
|
||||||
|
|
||||||
|
# Create entities
|
||||||
|
# Player at center
|
||||||
|
player = mcrfpy.Entity((10, 7), t, 84)
|
||||||
|
#player.texture = texture
|
||||||
|
#player.sprite_index = 84 # Player sprite
|
||||||
|
|
||||||
|
# Enemies
|
||||||
|
rat1 = mcrfpy.Entity((5, 5), t, 123)
|
||||||
|
#rat1.texture = texture
|
||||||
|
#rat1.sprite_index = 123 # Rat
|
||||||
|
|
||||||
|
rat2 = mcrfpy.Entity((15, 5), t, 123)
|
||||||
|
#rat2.texture = texture
|
||||||
|
#rat2.sprite_index = 123 # Rat
|
||||||
|
|
||||||
|
big_rat = mcrfpy.Entity((7, 10), t, 130)
|
||||||
|
#big_rat.texture = texture
|
||||||
|
#big_rat.sprite_index = 130 # Big rat
|
||||||
|
|
||||||
|
cyclops = mcrfpy.Entity((13, 10), t, 109)
|
||||||
|
#cyclops.texture = texture
|
||||||
|
#cyclops.sprite_index = 109 # Cyclops
|
||||||
|
|
||||||
|
# Items
|
||||||
|
chest = mcrfpy.Entity((3, 3), t, 89)
|
||||||
|
#chest.texture = texture
|
||||||
|
#chest.sprite_index = 89 # Chest
|
||||||
|
|
||||||
|
boulder = mcrfpy.Entity((10, 5), t, 66)
|
||||||
|
#boulder.texture = texture
|
||||||
|
#boulder.sprite_index = 66 # Boulder
|
||||||
|
key = mcrfpy.Entity((17, 12), t, 384)
|
||||||
|
#key.texture = texture
|
||||||
|
#key.sprite_index = 384 # Key
|
||||||
|
|
||||||
|
# Add all entities to grid
|
||||||
|
grid.entities.append(player)
|
||||||
|
grid.entities.append(rat1)
|
||||||
|
grid.entities.append(rat2)
|
||||||
|
grid.entities.append(big_rat)
|
||||||
|
grid.entities.append(cyclops)
|
||||||
|
grid.entities.append(chest)
|
||||||
|
grid.entities.append(boulder)
|
||||||
|
grid.entities.append(key)
|
||||||
|
|
||||||
|
# Labels
|
||||||
|
entity_label = mcrfpy.Caption((100, 580), "Entities move independently on the grid. Grid scale: 2x (32x32 pixels)")
|
||||||
|
#entity_label.font = mcrfpy.default_font
|
||||||
|
#entity_label.font_color = (255, 255, 255)
|
||||||
|
|
||||||
|
info = mcrfpy.Caption((100, 600), "Player (center), Enemies (rats, cyclops), Items (chest, boulder, key)")
|
||||||
|
#info.font = mcrfpy.default_font
|
||||||
|
#info.font_size = 14
|
||||||
|
#info.font_color = (200, 200, 200)
|
||||||
|
|
||||||
|
# Legend frame
|
||||||
|
legend_frame = mcrfpy.Frame(50, 50, 200, 150)
|
||||||
|
#legend_frame.bgcolor = (64, 64, 128)
|
||||||
|
#legend_frame.outline = 2
|
||||||
|
|
||||||
|
legend_title = mcrfpy.Caption((150, 60), "Entity Types")
|
||||||
|
#legend_title.font = mcrfpy.default_font
|
||||||
|
#legend_title.font_color = (255, 255, 255)
|
||||||
|
#legend_title.centered = True
|
||||||
|
|
||||||
|
#legend_text = mcrfpy.Caption((60, 90), "Player: @\nRat: r\nBig Rat: R\nCyclops: C\nChest: $\nBoulder: O\nKey: k")
|
||||||
|
#legend_text.font = mcrfpy.default_font
|
||||||
|
#legend_text.font_size = 12
|
||||||
|
#legend_text.font_color = (255, 255, 255)
|
||||||
|
|
||||||
|
# Add all to scene
|
||||||
|
ui = mcrfpy.sceneUI("entities")
|
||||||
|
ui.append(grid)
|
||||||
|
ui.append(title)
|
||||||
|
ui.append(entity_label)
|
||||||
|
ui.append(info)
|
||||||
|
ui.append(legend_frame)
|
||||||
|
ui.append(legend_title)
|
||||||
|
#ui.append(legend_text)
|
||||||
|
|
||||||
|
# Switch to scene
|
||||||
|
mcrfpy.setScene("entities")
|
||||||
|
|
||||||
|
# Set timer to capture after rendering starts
|
||||||
|
mcrfpy.setTimer("capture", capture_entity, 100)
|
|
@ -0,0 +1,131 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Generate grid documentation screenshot for McRogueFace"""
|
||||||
|
|
||||||
|
import mcrfpy
|
||||||
|
from mcrfpy import automation
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def capture_grid(runtime):
|
||||||
|
"""Capture grid example after render loop starts"""
|
||||||
|
|
||||||
|
# Take screenshot
|
||||||
|
automation.screenshot("mcrogueface.github.io/images/ui_grid_example.png")
|
||||||
|
print("Grid screenshot saved!")
|
||||||
|
|
||||||
|
# Exit after capturing
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
# Create scene
|
||||||
|
mcrfpy.createScene("grid")
|
||||||
|
|
||||||
|
# Load texture
|
||||||
|
texture = mcrfpy.Texture("assets/kenney_TD_MR_IP.png", 16, 16)
|
||||||
|
|
||||||
|
# Title
|
||||||
|
title = mcrfpy.Caption(400, 30, "Grid Example - Dungeon View")
|
||||||
|
title.font = mcrfpy.default_font
|
||||||
|
title.font_size = 24
|
||||||
|
title.font_color = (255, 255, 255)
|
||||||
|
|
||||||
|
# Create main grid (20x15 tiles, each 32x32 pixels)
|
||||||
|
grid = mcrfpy.Grid(100, 100, 20, 15, texture, 32, 32)
|
||||||
|
grid.texture = texture
|
||||||
|
|
||||||
|
# Define tile types from Crypt of Sokoban
|
||||||
|
FLOOR = 58 # Stone floor
|
||||||
|
WALL = 11 # Stone wall
|
||||||
|
DOOR = 28 # Closed door
|
||||||
|
CHEST = 89 # Treasure chest
|
||||||
|
BUTTON = 250 # Floor button
|
||||||
|
EXIT = 45 # Locked exit
|
||||||
|
BOULDER = 66 # Boulder
|
||||||
|
|
||||||
|
# Create a simple dungeon room layout
|
||||||
|
# Fill with walls first
|
||||||
|
for x in range(20):
|
||||||
|
for y in range(15):
|
||||||
|
grid.set_tile(x, y, WALL)
|
||||||
|
|
||||||
|
# Carve out room
|
||||||
|
for x in range(2, 18):
|
||||||
|
for y in range(2, 13):
|
||||||
|
grid.set_tile(x, y, FLOOR)
|
||||||
|
|
||||||
|
# Add door
|
||||||
|
grid.set_tile(10, 2, DOOR)
|
||||||
|
|
||||||
|
# Add some features
|
||||||
|
grid.set_tile(5, 5, CHEST)
|
||||||
|
grid.set_tile(15, 10, BUTTON)
|
||||||
|
grid.set_tile(10, 12, EXIT)
|
||||||
|
grid.set_tile(8, 8, BOULDER)
|
||||||
|
grid.set_tile(12, 8, BOULDER)
|
||||||
|
|
||||||
|
# Create some entities on the grid
|
||||||
|
# Player entity
|
||||||
|
player = mcrfpy.Entity(5, 7)
|
||||||
|
player.texture = texture
|
||||||
|
player.sprite_index = 84 # Player sprite
|
||||||
|
|
||||||
|
# Enemy entities
|
||||||
|
rat1 = mcrfpy.Entity(12, 5)
|
||||||
|
rat1.texture = texture
|
||||||
|
rat1.sprite_index = 123 # Rat
|
||||||
|
|
||||||
|
rat2 = mcrfpy.Entity(14, 9)
|
||||||
|
rat2.texture = texture
|
||||||
|
rat2.sprite_index = 123 # Rat
|
||||||
|
|
||||||
|
cyclops = mcrfpy.Entity(10, 10)
|
||||||
|
cyclops.texture = texture
|
||||||
|
cyclops.sprite_index = 109 # Cyclops
|
||||||
|
|
||||||
|
# Add entities to grid
|
||||||
|
grid.entities.append(player)
|
||||||
|
grid.entities.append(rat1)
|
||||||
|
grid.entities.append(rat2)
|
||||||
|
grid.entities.append(cyclops)
|
||||||
|
|
||||||
|
# Create a smaller grid showing tile palette
|
||||||
|
palette_label = mcrfpy.Caption(100, 600, "Tile Types:")
|
||||||
|
palette_label.font = mcrfpy.default_font
|
||||||
|
palette_label.font_color = (255, 255, 255)
|
||||||
|
|
||||||
|
palette = mcrfpy.Grid(250, 580, 7, 1, texture, 32, 32)
|
||||||
|
palette.texture = texture
|
||||||
|
palette.set_tile(0, 0, FLOOR)
|
||||||
|
palette.set_tile(1, 0, WALL)
|
||||||
|
palette.set_tile(2, 0, DOOR)
|
||||||
|
palette.set_tile(3, 0, CHEST)
|
||||||
|
palette.set_tile(4, 0, BUTTON)
|
||||||
|
palette.set_tile(5, 0, EXIT)
|
||||||
|
palette.set_tile(6, 0, BOULDER)
|
||||||
|
|
||||||
|
# Labels for palette
|
||||||
|
labels = ["Floor", "Wall", "Door", "Chest", "Button", "Exit", "Boulder"]
|
||||||
|
for i, label in enumerate(labels):
|
||||||
|
l = mcrfpy.Caption(250 + i * 32, 615, label)
|
||||||
|
l.font = mcrfpy.default_font
|
||||||
|
l.font_size = 10
|
||||||
|
l.font_color = (255, 255, 255)
|
||||||
|
mcrfpy.sceneUI("grid").append(l)
|
||||||
|
|
||||||
|
# Add info caption
|
||||||
|
info = mcrfpy.Caption(100, 680, "Grid supports tiles and entities. Entities can move independently of the tile grid.")
|
||||||
|
info.font = mcrfpy.default_font
|
||||||
|
info.font_size = 14
|
||||||
|
info.font_color = (200, 200, 200)
|
||||||
|
|
||||||
|
# Add all elements to scene
|
||||||
|
ui = mcrfpy.sceneUI("grid")
|
||||||
|
ui.append(title)
|
||||||
|
ui.append(grid)
|
||||||
|
ui.append(palette_label)
|
||||||
|
ui.append(palette)
|
||||||
|
ui.append(info)
|
||||||
|
|
||||||
|
# Switch to scene
|
||||||
|
mcrfpy.setScene("grid")
|
||||||
|
|
||||||
|
# Set timer to capture after rendering starts
|
||||||
|
mcrfpy.setTimer("capture", capture_grid, 100)
|
|
@ -0,0 +1,160 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Generate sprite documentation screenshots for McRogueFace"""
|
||||||
|
|
||||||
|
import mcrfpy
|
||||||
|
from mcrfpy import automation
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def capture_sprites(runtime):
|
||||||
|
"""Capture sprite examples after render loop starts"""
|
||||||
|
|
||||||
|
# Take screenshot
|
||||||
|
automation.screenshot("mcrogueface.github.io/images/ui_sprite_example.png")
|
||||||
|
print("Sprite screenshot saved!")
|
||||||
|
|
||||||
|
# Exit after capturing
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
# Create scene
|
||||||
|
mcrfpy.createScene("sprites")
|
||||||
|
|
||||||
|
# Load texture
|
||||||
|
texture = mcrfpy.Texture("assets/kenney_TD_MR_IP.png", 16, 16)
|
||||||
|
|
||||||
|
# Title
|
||||||
|
title = mcrfpy.Caption(400, 30, "Sprite Examples")
|
||||||
|
title.font = mcrfpy.default_font
|
||||||
|
title.font_size = 24
|
||||||
|
title.font_color = (255, 255, 255)
|
||||||
|
|
||||||
|
# Create a frame background
|
||||||
|
frame = mcrfpy.Frame(50, 80, 700, 500)
|
||||||
|
frame.bgcolor = (64, 64, 128)
|
||||||
|
frame.outline = 2
|
||||||
|
|
||||||
|
# Player sprite
|
||||||
|
player_label = mcrfpy.Caption(100, 120, "Player")
|
||||||
|
player_label.font = mcrfpy.default_font
|
||||||
|
player_label.font_color = (255, 255, 255)
|
||||||
|
|
||||||
|
player = mcrfpy.Sprite(120, 150)
|
||||||
|
player.texture = texture
|
||||||
|
player.sprite_index = 84 # Player sprite
|
||||||
|
player.scale = (3.0, 3.0)
|
||||||
|
|
||||||
|
# Enemy sprites
|
||||||
|
enemy_label = mcrfpy.Caption(250, 120, "Enemies")
|
||||||
|
enemy_label.font = mcrfpy.default_font
|
||||||
|
enemy_label.font_color = (255, 255, 255)
|
||||||
|
|
||||||
|
rat = mcrfpy.Sprite(250, 150)
|
||||||
|
rat.texture = texture
|
||||||
|
rat.sprite_index = 123 # Rat
|
||||||
|
rat.scale = (3.0, 3.0)
|
||||||
|
|
||||||
|
big_rat = mcrfpy.Sprite(320, 150)
|
||||||
|
big_rat.texture = texture
|
||||||
|
big_rat.sprite_index = 130 # Big rat
|
||||||
|
big_rat.scale = (3.0, 3.0)
|
||||||
|
|
||||||
|
cyclops = mcrfpy.Sprite(390, 150)
|
||||||
|
cyclops.texture = texture
|
||||||
|
cyclops.sprite_index = 109 # Cyclops
|
||||||
|
cyclops.scale = (3.0, 3.0)
|
||||||
|
|
||||||
|
# Items row
|
||||||
|
items_label = mcrfpy.Caption(100, 250, "Items")
|
||||||
|
items_label.font = mcrfpy.default_font
|
||||||
|
items_label.font_color = (255, 255, 255)
|
||||||
|
|
||||||
|
# Boulder
|
||||||
|
boulder = mcrfpy.Sprite(100, 280)
|
||||||
|
boulder.texture = texture
|
||||||
|
boulder.sprite_index = 66 # Boulder
|
||||||
|
boulder.scale = (3.0, 3.0)
|
||||||
|
|
||||||
|
# Chest
|
||||||
|
chest = mcrfpy.Sprite(170, 280)
|
||||||
|
chest.texture = texture
|
||||||
|
chest.sprite_index = 89 # Closed chest
|
||||||
|
chest.scale = (3.0, 3.0)
|
||||||
|
|
||||||
|
# Key
|
||||||
|
key = mcrfpy.Sprite(240, 280)
|
||||||
|
key.texture = texture
|
||||||
|
key.sprite_index = 384 # Key
|
||||||
|
key.scale = (3.0, 3.0)
|
||||||
|
|
||||||
|
# Button
|
||||||
|
button = mcrfpy.Sprite(310, 280)
|
||||||
|
button.texture = texture
|
||||||
|
button.sprite_index = 250 # Button
|
||||||
|
button.scale = (3.0, 3.0)
|
||||||
|
|
||||||
|
# UI elements row
|
||||||
|
ui_label = mcrfpy.Caption(100, 380, "UI Elements")
|
||||||
|
ui_label.font = mcrfpy.default_font
|
||||||
|
ui_label.font_color = (255, 255, 255)
|
||||||
|
|
||||||
|
# Hearts
|
||||||
|
heart_full = mcrfpy.Sprite(100, 410)
|
||||||
|
heart_full.texture = texture
|
||||||
|
heart_full.sprite_index = 210 # Full heart
|
||||||
|
heart_full.scale = (3.0, 3.0)
|
||||||
|
|
||||||
|
heart_half = mcrfpy.Sprite(170, 410)
|
||||||
|
heart_half.texture = texture
|
||||||
|
heart_half.sprite_index = 209 # Half heart
|
||||||
|
heart_half.scale = (3.0, 3.0)
|
||||||
|
|
||||||
|
heart_empty = mcrfpy.Sprite(240, 410)
|
||||||
|
heart_empty.texture = texture
|
||||||
|
heart_empty.sprite_index = 208 # Empty heart
|
||||||
|
heart_empty.scale = (3.0, 3.0)
|
||||||
|
|
||||||
|
# Armor
|
||||||
|
armor = mcrfpy.Sprite(340, 410)
|
||||||
|
armor.texture = texture
|
||||||
|
armor.sprite_index = 211 # Armor
|
||||||
|
armor.scale = (3.0, 3.0)
|
||||||
|
|
||||||
|
# Scale demonstration
|
||||||
|
scale_label = mcrfpy.Caption(500, 120, "Scale Demo")
|
||||||
|
scale_label.font = mcrfpy.default_font
|
||||||
|
scale_label.font_color = (255, 255, 255)
|
||||||
|
|
||||||
|
# Same sprite at different scales
|
||||||
|
for i, scale in enumerate([1.0, 2.0, 3.0, 4.0]):
|
||||||
|
s = mcrfpy.Sprite(500 + i * 60, 150)
|
||||||
|
s.texture = texture
|
||||||
|
s.sprite_index = 84 # Player
|
||||||
|
s.scale = (scale, scale)
|
||||||
|
mcrfpy.sceneUI("sprites").append(s)
|
||||||
|
|
||||||
|
# Add all elements to scene
|
||||||
|
ui = mcrfpy.sceneUI("sprites")
|
||||||
|
ui.append(frame)
|
||||||
|
ui.append(title)
|
||||||
|
ui.append(player_label)
|
||||||
|
ui.append(player)
|
||||||
|
ui.append(enemy_label)
|
||||||
|
ui.append(rat)
|
||||||
|
ui.append(big_rat)
|
||||||
|
ui.append(cyclops)
|
||||||
|
ui.append(items_label)
|
||||||
|
ui.append(boulder)
|
||||||
|
ui.append(chest)
|
||||||
|
ui.append(key)
|
||||||
|
ui.append(button)
|
||||||
|
ui.append(ui_label)
|
||||||
|
ui.append(heart_full)
|
||||||
|
ui.append(heart_half)
|
||||||
|
ui.append(heart_empty)
|
||||||
|
ui.append(armor)
|
||||||
|
ui.append(scale_label)
|
||||||
|
|
||||||
|
# Switch to scene
|
||||||
|
mcrfpy.setScene("sprites")
|
||||||
|
|
||||||
|
# Set timer to capture after rendering starts
|
||||||
|
mcrfpy.setTimer("capture", capture_sprites, 100)
|
|
@ -0,0 +1,45 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Simple screenshot test to verify automation API"""
|
||||||
|
|
||||||
|
import mcrfpy
|
||||||
|
from mcrfpy import automation
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
def take_screenshot(runtime):
|
||||||
|
"""Take screenshot after render starts"""
|
||||||
|
print(f"Timer callback fired at runtime: {runtime}")
|
||||||
|
|
||||||
|
# Try different paths
|
||||||
|
paths = [
|
||||||
|
"test_screenshot.png",
|
||||||
|
"./test_screenshot.png",
|
||||||
|
"mcrogueface.github.io/images/test_screenshot.png"
|
||||||
|
]
|
||||||
|
|
||||||
|
for path in paths:
|
||||||
|
try:
|
||||||
|
print(f"Trying to save to: {path}")
|
||||||
|
automation.screenshot(path)
|
||||||
|
print(f"Success: {path}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Failed {path}: {e}")
|
||||||
|
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
# Create minimal scene
|
||||||
|
mcrfpy.createScene("test")
|
||||||
|
|
||||||
|
# Add a visible element
|
||||||
|
caption = mcrfpy.Caption(100, 100, "Screenshot Test")
|
||||||
|
caption.font = mcrfpy.default_font
|
||||||
|
caption.font_color = (255, 255, 255)
|
||||||
|
caption.font_size = 24
|
||||||
|
|
||||||
|
mcrfpy.sceneUI("test").append(caption)
|
||||||
|
mcrfpy.setScene("test")
|
||||||
|
|
||||||
|
# Use timer to ensure rendering has started
|
||||||
|
print("Setting timer...")
|
||||||
|
mcrfpy.setTimer("screenshot", take_screenshot, 500) # Wait 0.5 seconds
|
||||||
|
print("Timer set, entering game loop...")
|
|
@ -33,34 +33,34 @@ def test_Entity():
|
||||||
|
|
||||||
# Test entity properties
|
# Test entity properties
|
||||||
try:
|
try:
|
||||||
print(f"✓ Entity1 pos: {entity1.pos}")
|
print(f" Entity1 pos: {entity1.pos}")
|
||||||
print(f"✓ Entity1 draw_pos: {entity1.draw_pos}")
|
print(f" Entity1 draw_pos: {entity1.draw_pos}")
|
||||||
print(f"✓ Entity1 sprite_number: {entity1.sprite_number}")
|
print(f" Entity1 sprite_number: {entity1.sprite_number}")
|
||||||
|
|
||||||
# Modify properties
|
# Modify properties
|
||||||
entity1.pos = mcrfpy.Vector(3, 3)
|
entity1.pos = mcrfpy.Vector(3, 3)
|
||||||
entity1.sprite_number = 5
|
entity1.sprite_number = 5
|
||||||
print("✓ Entity properties modified")
|
print(" Entity properties modified")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"✗ Entity property access failed: {e}")
|
print(f"X Entity property access failed: {e}")
|
||||||
|
|
||||||
# Test gridstate access
|
# Test gridstate access
|
||||||
try:
|
try:
|
||||||
gridstate = entity2.gridstate
|
gridstate = entity2.gridstate
|
||||||
print(f"✓ Entity gridstate accessible")
|
print(" Entity gridstate accessible")
|
||||||
|
|
||||||
# Test at() method
|
# Test at() method
|
||||||
point_state = entity2.at(0, 0)
|
point_state = entity2.at()#.at(0, 0)
|
||||||
print(f"✓ Entity at() method works")
|
print(" Entity at() method works")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"✗ Entity gridstate/at() failed: {e}")
|
print(f"X Entity gridstate/at() failed: {e}")
|
||||||
|
|
||||||
# Test index() method (Issue #73)
|
# Test index() method (Issue #73)
|
||||||
print("\nTesting index() method (Issue #73)...")
|
print("\nTesting index() method (Issue #73)...")
|
||||||
try:
|
try:
|
||||||
# Try to find entity2's index
|
# Try to find entity2's index
|
||||||
index = entity2.index()
|
index = entity2.index()
|
||||||
print(f"✓ index() method works: entity2 is at index {index}")
|
print(f":) index() method works: entity2 is at index {index}")
|
||||||
|
|
||||||
# Verify by checking collection
|
# Verify by checking collection
|
||||||
if entities[index] == entity2:
|
if entities[index] == entity2:
|
||||||
|
@ -70,7 +70,7 @@ def test_Entity():
|
||||||
|
|
||||||
# Remove using index
|
# Remove using index
|
||||||
entities.remove(index)
|
entities.remove(index)
|
||||||
print(f"✓ Removed entity using index, now {len(entities)} entities")
|
print(f":) Removed entity using index, now {len(entities)} entities")
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
print("✗ index() method not implemented (Issue #73)")
|
print("✗ index() method not implemented (Issue #73)")
|
||||||
# Try manual removal as workaround
|
# Try manual removal as workaround
|
||||||
|
@ -78,21 +78,21 @@ def test_Entity():
|
||||||
for i in range(len(entities)):
|
for i in range(len(entities)):
|
||||||
if entities[i] == entity2:
|
if entities[i] == entity2:
|
||||||
entities.remove(i)
|
entities.remove(i)
|
||||||
print(f"✓ Manual removal workaround succeeded")
|
print(":) Manual removal workaround succeeded")
|
||||||
break
|
break
|
||||||
except:
|
except:
|
||||||
print("✗ Manual removal also failed")
|
print("✗ Manual removal also failed")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"✗ index() method error: {e}")
|
print(f":) index() method error: {e}")
|
||||||
|
|
||||||
# Test EntityCollection iteration
|
# Test EntityCollection iteration
|
||||||
try:
|
try:
|
||||||
positions = []
|
positions = []
|
||||||
for entity in entities:
|
for entity in entities:
|
||||||
positions.append(entity.pos)
|
positions.append(entity.pos)
|
||||||
print(f"✓ Entity iteration works: {len(positions)} entities")
|
print(f":) Entity iteration works: {len(positions)} entities")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"✗ Entity iteration failed: {e}")
|
print(f"X Entity iteration failed: {e}")
|
||||||
|
|
||||||
# Test EntityCollection extend (Issue #27)
|
# Test EntityCollection extend (Issue #27)
|
||||||
try:
|
try:
|
||||||
|
@ -101,11 +101,11 @@ def test_Entity():
|
||||||
mcrfpy.Entity(mcrfpy.Vector(9, 9), mcrfpy.default_texture, 4, grid)
|
mcrfpy.Entity(mcrfpy.Vector(9, 9), mcrfpy.default_texture, 4, grid)
|
||||||
]
|
]
|
||||||
entities.extend(new_entities)
|
entities.extend(new_entities)
|
||||||
print(f"✓ extend() method works: now {len(entities)} entities")
|
print(f":) extend() method works: now {len(entities)} entities")
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
print("✗ extend() method not implemented (Issue #27)")
|
print("✗ extend() method not implemented (Issue #27)")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"✗ extend() method error: {e}")
|
print(f"X extend() method error: {e}")
|
||||||
|
|
||||||
# Skip screenshot in headless mode
|
# Skip screenshot in headless mode
|
||||||
print("PASS")
|
print("PASS")
|
||||||
|
@ -113,4 +113,4 @@ def test_Entity():
|
||||||
# Run test immediately in headless mode
|
# Run test immediately in headless mode
|
||||||
print("Running test immediately...")
|
print("Running test immediately...")
|
||||||
test_Entity()
|
test_Entity()
|
||||||
print("Test completed.")
|
print("Test completed.")
|
||||||
|
|
Loading…
Reference in New Issue